Import Upstream version 4.92 upstream upstream/4.92
authorClinton Ebadi <clinton@unknownlamer.org>
Sun, 16 Feb 2020 04:06:36 +0000 (23:06 -0500)
committerClinton Ebadi <clinton@unknownlamer.org>
Sun, 16 Feb 2020 04:06:36 +0000 (23:06 -0500)
364 files changed:
.ctags [new file with mode: 0644]
.gitignore
ACKNOWLEDGMENTS
Makefile
OS/Makefile-Base
OS/Makefile-Default
OS/Makefile-FreeBSD
OS/Makefile-OpenBSD
OS/Makefile-SunOS5
OS/os.c-FreeBSD [new file with mode: 0644]
OS/os.c-Linux
OS/os.c-SunOS5 [new file with mode: 0644]
OS/os.c-cygwin [deleted file]
OS/os.h-Darwin
OS/os.h-FreeBSD
OS/os.h-Linux
OS/os.h-OpenBSD
OS/os.h-SunOS5
OS/unsupported/Makefile-AIX [moved from OS/Makefile-AIX with 100% similarity]
OS/unsupported/Makefile-BSDI [moved from OS/Makefile-BSDI with 100% similarity]
OS/unsupported/Makefile-CYGWIN [moved from OS/Makefile-CYGWIN with 84% similarity]
OS/unsupported/Makefile-DGUX [moved from OS/Makefile-DGUX with 100% similarity]
OS/unsupported/Makefile-DragonFly [moved from OS/Makefile-DragonFly with 100% similarity]
OS/unsupported/Makefile-GNU [moved from OS/Makefile-GNU with 100% similarity]
OS/unsupported/Makefile-GNUkFreeBSD [moved from OS/Makefile-GNUkFreeBSD with 100% similarity]
OS/unsupported/Makefile-GNUkNetBSD [moved from OS/Makefile-GNUkNetBSD with 100% similarity]
OS/unsupported/Makefile-HI-OSF [moved from OS/Makefile-HI-OSF with 100% similarity]
OS/unsupported/Makefile-HI-UX [moved from OS/Makefile-HI-UX with 100% similarity]
OS/unsupported/Makefile-HP-UX [moved from OS/Makefile-HP-UX with 96% similarity]
OS/unsupported/Makefile-HP-UX-9 [moved from OS/Makefile-HP-UX-9 with 100% similarity]
OS/unsupported/Makefile-IRIX [moved from OS/Makefile-IRIX with 100% similarity]
OS/unsupported/Makefile-IRIX6 [moved from OS/Makefile-IRIX6 with 100% similarity]
OS/unsupported/Makefile-IRIX632 [moved from OS/Makefile-IRIX632 with 100% similarity]
OS/unsupported/Makefile-IRIX65 [moved from OS/Makefile-IRIX65 with 100% similarity]
OS/unsupported/Makefile-NetBSD [moved from OS/Makefile-NetBSD with 100% similarity]
OS/unsupported/Makefile-NetBSD-a.out [moved from OS/Makefile-NetBSD-a.out with 100% similarity]
OS/unsupported/Makefile-OSF1 [moved from OS/Makefile-OSF1 with 100% similarity]
OS/unsupported/Makefile-OpenUNIX [moved from OS/Makefile-OpenUNIX with 100% similarity]
OS/unsupported/Makefile-QNX [moved from OS/Makefile-QNX with 100% similarity]
OS/unsupported/Makefile-SCO [moved from OS/Makefile-SCO with 100% similarity]
OS/unsupported/Makefile-SCO_SV [moved from OS/Makefile-SCO_SV with 100% similarity]
OS/unsupported/Makefile-SunOS4 [moved from OS/Makefile-SunOS4 with 100% similarity]
OS/unsupported/Makefile-SunOS5-hal [moved from OS/Makefile-SunOS5-hal with 100% similarity]
OS/unsupported/Makefile-ULTRIX [moved from OS/Makefile-ULTRIX with 100% similarity]
OS/unsupported/Makefile-UNIX_SV [moved from OS/Makefile-UNIX_SV with 100% similarity]
OS/unsupported/Makefile-USG [moved from OS/Makefile-USG with 100% similarity]
OS/unsupported/Makefile-Unixware7 [moved from OS/Makefile-Unixware7 with 100% similarity]
OS/unsupported/Makefile-mips [moved from OS/Makefile-mips with 100% similarity]
OS/unsupported/README [new file with mode: 0644]
OS/unsupported/os.c-BSDI [new file with mode: 0644]
OS/unsupported/os.c-GNU [moved from OS/os.c-GNU with 100% similarity]
OS/unsupported/os.c-HI-OSF [moved from OS/os.c-HI-OSF with 100% similarity]
OS/unsupported/os.c-HP-UX [new file with mode: 0644]
OS/unsupported/os.c-IRIX [moved from OS/os.c-IRIX with 99% similarity]
OS/unsupported/os.c-IRIX6 [moved from OS/os.c-IRIX6 with 99% similarity]
OS/unsupported/os.c-IRIX632 [moved from OS/os.c-IRIX632 with 99% similarity]
OS/unsupported/os.c-IRIX65 [moved from OS/os.c-IRIX65 with 99% similarity]
OS/unsupported/os.c-OSF1 [moved from OS/os.c-OSF1 with 100% similarity]
OS/unsupported/os.c-cygwin [new file with mode: 0644]
OS/unsupported/os.h-AIX [moved from OS/os.h-AIX with 89% similarity]
OS/unsupported/os.h-BSDI [moved from OS/os.h-BSDI with 72% similarity]
OS/unsupported/os.h-DGUX [moved from OS/os.h-DGUX with 92% similarity]
OS/unsupported/os.h-DragonFly [moved from OS/os.h-DragonFly with 74% similarity]
OS/unsupported/os.h-GNU [moved from OS/os.h-GNU with 85% similarity]
OS/unsupported/os.h-GNUkFreeBSD [moved from OS/os.h-GNUkFreeBSD with 86% similarity]
OS/unsupported/os.h-GNUkNetBSD [moved from OS/os.h-GNUkNetBSD with 86% similarity]
OS/unsupported/os.h-HI-OSF [moved from OS/os.h-HI-OSF with 77% similarity]
OS/unsupported/os.h-HI-UX [moved from OS/os.h-HI-UX with 86% similarity]
OS/unsupported/os.h-HP-UX [moved from OS/os.h-HP-UX with 84% similarity]
OS/unsupported/os.h-HP-UX-9 [moved from OS/os.h-HP-UX-9 with 87% similarity]
OS/unsupported/os.h-IRIX [moved from OS/os.h-IRIX with 84% similarity]
OS/unsupported/os.h-IRIX6 [moved from OS/os.h-IRIX6 with 83% similarity]
OS/unsupported/os.h-IRIX632 [moved from OS/os.h-IRIX632 with 84% similarity]
OS/unsupported/os.h-IRIX65 [moved from OS/os.h-IRIX65 with 83% similarity]
OS/unsupported/os.h-NetBSD [moved from OS/os.h-NetBSD with 88% similarity]
OS/unsupported/os.h-NetBSD-a.out [moved from OS/os.h-NetBSD-a.out with 100% similarity]
OS/unsupported/os.h-OSF1 [moved from OS/os.h-OSF1 with 82% similarity]
OS/unsupported/os.h-OpenUNIX [moved from OS/os.h-OpenUNIX with 83% similarity]
OS/unsupported/os.h-QNX [moved from OS/os.h-QNX with 86% similarity]
OS/unsupported/os.h-SCO [moved from OS/os.h-SCO with 85% similarity]
OS/unsupported/os.h-SCO_SV [moved from OS/os.h-SCO_SV with 84% similarity]
OS/unsupported/os.h-SunOS4 [moved from OS/os.h-SunOS4 with 93% similarity]
OS/unsupported/os.h-SunOS5-hal [moved from OS/os.h-SunOS5-hal with 80% similarity]
OS/unsupported/os.h-ULTRIX [moved from OS/os.h-ULTRIX with 87% similarity]
OS/unsupported/os.h-UNIX_SV [moved from OS/os.h-UNIX_SV with 89% similarity]
OS/unsupported/os.h-USG [moved from OS/os.h-USG with 83% similarity]
OS/unsupported/os.h-Unixware7 [moved from OS/os.h-Unixware7 with 91% similarity]
OS/unsupported/os.h-cygwin [moved from OS/os.h-cygwin with 61% similarity]
OS/unsupported/os.h-mips [moved from OS/os.h-mips with 88% similarity]
README
README.DSN
README.UPDATING
conf [new file with mode: 0644]
doc/ChangeLog
doc/DANE-draft-notes [new file with mode: 0644]
doc/Exim3.upgrade
doc/Exim4.upgrade
doc/GnuTLS-FAQ.txt
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_version.c
exim_monitor/em_xs.c
scripts/Configure-Makefile
scripts/Configure-os.h
scripts/MakeLinks
scripts/exim_install
scripts/lookups-Makefile
scripts/reversion
scripts/source_checks
src/EDITME
src/acl.c
src/aliases.default
src/arc.c [new file with mode: 0644]
src/auths/Makefile
src/auths/README
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/check_serv_cond.c
src/auths/cram_md5.c
src/auths/cram_md5.h
src/auths/cyrus_sasl.c
src/auths/cyrus_sasl.h
src/auths/dovecot.c
src/auths/get_data.c
src/auths/get_no64_data.c
src/auths/gsasl_exim.c
src/auths/gsasl_exim.h
src/auths/heimdal_gssapi.c
src/auths/md5.c
src/auths/plaintext.c
src/auths/plaintext.h
src/auths/pwcheck.c
src/auths/spa.c
src/auths/spa.h
src/auths/tls.c [new file with mode: 0644]
src/auths/tls.h [new file with mode: 0644]
src/auths/xtextencode.c
src/base64.c [new file with mode: 0644]
src/blob.h [new file with mode: 0644]
src/bmi_spam.c
src/buildconfig.c
src/child.c
src/cnumber.h [new file with mode: 0644]
src/config.h.defaults
src/configure.default
src/convert4r3.src
src/convert4r4.src
src/crypt16.c
src/daemon.c
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/directory.c
src/dkim.c
src/dkim.h
src/dkim_transport.c [new file with mode: 0644]
src/dmarc.c
src/dmarc.h
src/dns.c
src/drtables.c
src/dummies.c
src/enq.c
src/environment.c
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/exiwhat.src
src/expand.c
src/filter.c
src/filtertest.c
src/functions.h
src/globals.c
src/globals.h
src/hash.c [moved from src/auths/sha1.c with 84% 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.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/macro_predef.c [new file with mode: 0644]
src/macro_predef.h [new file with mode: 0644]
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 [deleted file]
src/pdkim/rsa.h [deleted file]
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/pdkim/signing.c [new file with mode: 0644]
src/pdkim/signing.h [new file with mode: 0644]
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_expand_data.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/routers/rf_self_action.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/store.h
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/appendfile.h
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.c
src/version.h [new file with mode: 0644]
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
util/renew-opendmarc-tlds.sh [new file with mode: 0755]

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
 Local
 build-*
 tags
-cscope.out
+cscope.*
index 1c4a934..22e9909 100644 (file)
@@ -67,8 +67,8 @@ Paul Kelly                MySQL interface
 Ian Kirk                  Radius support
 Stuart Levy               Replacement for broken inet_ntoa() on IRIX
 Stuart Lynne              First code for LDAP
 Ian Kirk                  Radius support
 Stuart Levy               Replacement for broken inet_ntoa() on IRIX
 Stuart Lynne              First code for LDAP
-Nigel Metheringham        Setting up the web site and mailing list
-                            Managing the web site and mailing list
+Nigel Metheringham        Setting up the website and mailing list
+                            Managing the website and mailing list
                             Interface to Berkeley DB
                             Support for cdb
                             Support for maildir
                             Interface to Berkeley DB
                             Support for cdb
                             Support for maildir
@@ -350,7 +350,7 @@ John Jetmore
 Tom Kistner               DKIM. Content scanning. SPA.
 Todd Lyons
 Nigel Metheringham        Transitioning out of Default Victim status.
 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.
 
 
 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
 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
 Holger Weiß               Patch leting ${run} return more data than OS pipe
                             buffer size
 Moritz Wilhelmy           Pointed out PCRE_PRERELEASE glitch
index 99f4ab3..761b295 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -2,7 +2,7 @@
 # appropriate links, and then creating and running the main makefile in that
 # directory.
 
 # 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 - 2018
 # See the file NOTICE for conditions of use and distribution.
 
 # IRIX make uses the shell that is in the SHELL variable, which often defaults
 # 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.
 # 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.
 
 # 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)
 
 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"
 Local/Makefile:
        @echo ""
        @echo "*** Please create Local/Makefile by copying src/EDITME and making"
@@ -90,11 +104,11 @@ distclean:; $(RM_COMMAND) -rf build-* cscope*
 cscope.files: FRC
        echo "-q" > $@
        echo "-p3" >> $@
 cscope.files: FRC
        echo "-q" > $@
        echo "-p3" >> $@
-       find src Local OS -name "*.[cshyl]" -print \
-                   -o -name "os.h*" -print \
+       find src Local OS exim_monitor -name "*.[cshyl]" -print \
+                   -o -name "os.[ch]*" -print \
                    -o -name "*akefile*" -print \
                    -o -name "*akefile*" -print \
+                   -o -name config.h.defaults -print \
                    -o -name EDITME -print >> $@
                    -o -name EDITME -print >> $@
-       ls OS/* >> $@
 
 FRC:
 
 
 FRC:
 
index ac832bc..fed3134 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
 # 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.
 # optional, Local/* files at the front of this file, to create Makefile in the
 # build directory.
+#
+# Copyright (c) The Exim Maintainers 1995 - 2018
 
 SHELL      = $(MAKE_SHELL)
 SCRIPTS    = ../scripts
 
 SHELL      = $(MAKE_SHELL)
 SCRIPTS    = ../scripts
+O          = ../OS
 EDITME     = ../Local/Makefile
 EXIMON_EDITME = ../Local/eximon.conf
 
 EDITME     = ../Local/Makefile
 EXIMON_EDITME = ../Local/eximon.conf
 
@@ -32,8 +35,8 @@ 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.
 
 # 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 version.sh macro.c
 
 checklocalmake:
        @if $(SHELL) $(SCRIPTS)/newer $(EDITME)-$(OSTYPE) $(EDITME) || \
 
 checklocalmake:
        @if $(SHELL) $(SCRIPTS)/newer $(EDITME)-$(OSTYPE) $(EDITME) || \
@@ -76,12 +79,41 @@ Makefile: ../OS/Makefile-Base ../OS/Makefile-Default \
 
 # Build (link) the os.h file
 
 
 # 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
+
+os.h:  $(SCRIPTS)/Configure-os.h \
+       $(O)/os.h-FreeBSD       \
+       $(O)/os.h-Linux         \
+       $(O)/os.h-OpenBSD       \
+       $(O)/os.h-SunOS5
        $(SHELL) $(SCRIPTS)/Configure-os.h
 
 # Build the os.c file
 
        $(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
+
+os.c:   ../src/os.c \
+       $(SCRIPTS)/Configure-os.c \
+       $(O)/os.c-Linux
        $(SHELL) $(SCRIPTS)/Configure-os.c
 
 # Build the config.h file.
        $(SHELL) $(SCRIPTS)/Configure-os.c
 
 # Build the config.h file.
@@ -89,25 +121,144 @@ os.c:   ../src/os.c
 config.h: Makefile buildconfig ../src/config.h.defaults $(EDITME)
        $(SHELL) $(SCRIPTS)/Configure-config.h "$(MAKE)"
 
 config.h: Makefile buildconfig ../src/config.h.defaults $(EDITME)
        $(SHELL) $(SCRIPTS)/Configure-config.h "$(MAKE)"
 
+# Build the builtin-macros data struct
+
+MACRO_HSRC = macro_predef.h os.h globals.h config.h \
+       routers/accept.h routers/dnslookup.h routers/ipliteral.h \
+       routers/iplookup.h routers/manualroute.h routers/queryprogram.h \
+       routers/redirect.h
+
+OBJ_MACRO = macro_predef.o \
+       macro-globals.o macro-readconf.o macro-route.o macro-transport.o macro-drtables.o \
+       macro-tls.o \
+       macro-appendfile.o macro-autoreply.o macro-lmtp.o macro-pipe.o macro-queuefile.o \
+       macro-smtp.o macro-accept.o macro-dnslookup.o macro-ipliteral.o macro-iplookup.o \
+       macro-manualroute.o macro-queryprogram.o macro-redirect.o \
+       macro-auth-spa.o macro-cram_md5.o macro-cyrus_sasl.o macro-dovecot.o macro-gsasl_exim.o \
+       macro-heimdal_gssapi.o macro-plaintext.o macro-spa.o macro-authtls.o \
+       macro-dkim.o macro-malware.o macro-signing.o
+
+$(OBJ_MACRO):  $(MACRO_HSRC)
+
+macro_predef.o :       macro_predef.c
+       @echo "$(CC) -DMACRO_PREDEF macro_predef.c"
+       $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ macro_predef.c
+macro-globals.o :      globals.c
+       @echo "$(CC) -DMACRO_PREDEF globals.c"
+       $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ globals.c
+macro-readconf.o :     readconf.c
+       @echo "$(CC) -DMACRO_PREDEF readconf.c"
+       $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ readconf.c
+macro-route.o :                route.c
+       @echo "$(CC) -DMACRO_PREDEF route.c"
+       $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ route.c
+macro-transport.o:     transport.c
+       @echo "$(CC) -DMACRO_PREDEF transport.c"
+       $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ transport.c
+macro-drtables.o :     drtables.c
+       @echo "$(CC) -DMACRO_PREDEF drtables.c"
+       $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ drtables.c
+macro-tls.o:   tls.c
+       @echo "$(CC) -DMACRO_PREDEF tls.c"
+       $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ tls.c
+macro-appendfile.o :   transports/appendfile.c
+       @echo "$(CC) -DMACRO_PREDEF transports/appendfile.c"
+       $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ transports/appendfile.c
+macro-autoreply.o :    transports/autoreply.c
+       @echo "$(CC) -DMACRO_PREDEF transports/autoreply.c"
+       $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ transports/autoreply.c
+macro-lmtp.o:          transports/lmtp.c
+       @echo "$(CC) -DMACRO_PREDEF transports/lmtp.c"
+       $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ transports/lmtp.c
+macro-pipe.o :         transports/pipe.c
+       @echo "$(CC) -DMACRO_PREDEF transports/pipe.c"
+       $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ transports/pipe.c
+macro-queuefile.o :    transports/queuefile.c
+       @echo "$(CC) -DMACRO_PREDEF transports/queuefile.c"
+       $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ transports/queuefile.c
+macro-smtp.o :         transports/smtp.c
+       @echo "$(CC) -DMACRO_PREDEF transports/smtp.c"
+       $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ transports/smtp.c
+macro-accept.o :       routers/accept.c
+       @echo "$(CC) -DMACRO_PREDEF routers/accept.c"
+       $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ routers/accept.c
+macro-dnslookup.o :    routers/dnslookup.c
+       @echo "$(CC) -DMACRO_PREDEF routers/dnslookup.c"
+       $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ routers/dnslookup.c
+macro-ipliteral.o :    routers/ipliteral.c
+       @echo "$(CC) -DMACRO_PREDEF routers/ipliteral.c"
+       $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ routers/ipliteral.c
+macro-iplookup.o :     routers/iplookup.c
+       @echo "$(CC) -DMACRO_PREDEF routers/iplookup.c"
+       $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ routers/iplookup.c
+macro-manualroute.o :  routers/manualroute.c
+       @echo "$(CC) -DMACRO_PREDEF routers/manualroute.c"
+       $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ routers/manualroute.c
+macro-queryprogram.o : routers/queryprogram.c
+       @echo "$(CC) -DMACRO_PREDEF routers/queryprogram.c"
+       $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ routers/queryprogram.c
+macro-redirect.o :     routers/redirect.c
+       @echo "$(CC) -DMACRO_PREDEF routers/redirect.c"
+       $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ routers/redirect.c
+macro-auth-spa.o :     auths/auth-spa.c
+       @echo "$(CC) -DMACRO_PREDEF auths/auth-spa.c"
+       $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ auths/auth-spa.c
+macro-cram_md5.o :     auths/cram_md5.c
+       @echo "$(CC) -DMACRO_PREDEF auths/cram_md5.c"
+       $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ auths/cram_md5.c
+macro-cyrus_sasl.o :   auths/cyrus_sasl.c
+       @echo "$(CC) -DMACRO_PREDEF auths/cyrus_sasl.c"
+       $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ auths/cyrus_sasl.c
+macro-dovecot.o:       auths/dovecot.c
+       @echo "$(CC) -DMACRO_PREDEF auths/dovecot.c"
+       $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ auths/dovecot.c
+macro-gsasl_exim.o :   auths/gsasl_exim.c
+       @echo "$(CC) -DMACRO_PREDEF auths/gsasl_exim.c"
+       $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ auths/gsasl_exim.c
+macro-heimdal_gssapi.o:        auths/heimdal_gssapi.c
+       @echo "$(CC) -DMACRO_PREDEF auths/heimdal_gssapi.c"
+       $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ auths/heimdal_gssapi.c
+macro-plaintext.o :    auths/plaintext.c
+       @echo "$(CC) -DMACRO_PREDEF auths/plaintext.c"
+       $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ auths/plaintext.c
+macro-spa.o :          auths/spa.c
+       @echo "$(CC) -DMACRO_PREDEF auths/spa.c"
+       $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ auths/spa.c
+macro-authtls.o:       auths/tls.c
+       @echo "$(CC) -DMACRO_PREDEF auths/tls.c"
+       $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ auths/tls.c
+macro-dkim.o:          dkim.c
+       @echo "$(CC) -DMACRO_PREDEF dkim.c"
+       $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ dkim.c
+macro-malware.o:       malware.c
+       @echo "$(CC) -DMACRO_PREDEF malware.c"
+       $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ malware.c
+macro-signing.o:       pdkim/signing.c
+       @echo "$(CC) -DMACRO_PREDEF pdkim/signing.c"
+       $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ pdkim/signing.c
+
+macro_predef: $(OBJ_MACRO)
+       @echo "$(LNCC) -o $@"
+       $(FE)$(LNCC) -o $@ $(LFLAGS) $(OBJ_MACRO)
+
+macro.c: macro_predef
+       ./macro_predef > macro.c
 
 # This target is recognized specially by GNU make. It records those targets
 # that do not correspond to files that are being built and which should
 # 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.
 
 
 # This target is recognized specially by GNU make. It records those targets
 # that do not correspond to files that are being built and which should
 # 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
 
 
         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 \
         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
 
 
 # Targets for special-purpose configuration header builders
@@ -119,7 +270,7 @@ buildconfig: buildconfig.c
 # Target for the exicyclog utility script
 exicyclog: config ../src/exicyclog.src
        @rm -f exicyclog
 # Target for the exicyclog utility script
 exicyclog: config ../src/exicyclog.src
        @rm -f exicyclog
-       @sed \
+       @. ./version.sh && sed \
          -e "s?PROCESSED_FLAG?This file has been so processed.?"\
          -e "/^# /p" \
          -e "/^# /d" \
          -e "s?PROCESSED_FLAG?This file has been so processed.?"\
          -e "/^# /p" \
          -e "/^# /d" \
@@ -136,6 +287,8 @@ exicyclog: config ../src/exicyclog.src
          -e "s?MV_COMMAND?$(MV_COMMAND)?" \
          -e "s?RM_COMMAND?$(RM_COMMAND)?" \
          -e "s?TOUCH_COMMAND?$(TOUCH_COMMAND)?" \
          -e "s?MV_COMMAND?$(MV_COMMAND)?" \
          -e "s?RM_COMMAND?$(RM_COMMAND)?" \
          -e "s?TOUCH_COMMAND?$(TOUCH_COMMAND)?" \
+         -e "s?EXIM_RELEASE_VERSION?$${EXIM_RELEASE_VERSION}?" \
+         -e "s?EXIM_VARIANT_VERSION?$${EXIM_VARIANT_VERSION}?" \
          ../src/exicyclog.src > exicyclog-t
        @mv exicyclog-t exicyclog
        @chmod a+x exicyclog
          ../src/exicyclog.src > exicyclog-t
        @mv exicyclog-t exicyclog
        @chmod a+x exicyclog
@@ -144,13 +297,15 @@ exicyclog: config ../src/exicyclog.src
 # Target for the exinext utility script
 exinext: config ../src/exinext.src
        @rm -f exinext
 # Target for the exinext utility script
 exinext: config ../src/exinext.src
        @rm -f exinext
-       @sed \
+       @. ./version.sh && sed \
          -e "s?PROCESSED_FLAG?This file has been so processed.?"\
          -e "/^# /p" \
          -e "/^# /d" \
          -e "s?CONFIGURE_FILE_USE_NODE?$(CONFIGURE_FILE_USE_NODE)?" \
          -e "s?CONFIGURE_FILE?$(CONFIGURE_FILE)?" \
          -e "s?BIN_DIRECTORY?$(BIN_DIRECTORY)?" \
          -e "s?PROCESSED_FLAG?This file has been so processed.?"\
          -e "/^# /p" \
          -e "/^# /d" \
          -e "s?CONFIGURE_FILE_USE_NODE?$(CONFIGURE_FILE_USE_NODE)?" \
          -e "s?CONFIGURE_FILE?$(CONFIGURE_FILE)?" \
          -e "s?BIN_DIRECTORY?$(BIN_DIRECTORY)?" \
+         -e "s?EXIM_RELEASE_VERSION?$${EXIM_RELEASE_VERSION}?" \
+         -e "s?EXIM_VARIANT_VERSION?$${EXIM_VARIANT_VERSION}?" \
          ../src/exinext.src > exinext-t
        @mv exinext-t exinext
        @chmod a+x exinext
          ../src/exinext.src > exinext-t
        @mv exinext-t exinext
        @chmod a+x exinext
@@ -159,7 +314,7 @@ exinext: config ../src/exinext.src
 # Target for the exiwhat utility script
 exiwhat: config ../src/exiwhat.src
        @rm -f exiwhat
 # Target for the exiwhat utility script
 exiwhat: config ../src/exiwhat.src
        @rm -f exiwhat
-       @sed \
+       @. ./version.sh && sed \
          -e "s?PROCESSED_FLAG?This file has been so processed.?"\
          -e "/^# /p" \
          -e "/^# /d" \
          -e "s?PROCESSED_FLAG?This file has been so processed.?"\
          -e "/^# /p" \
          -e "/^# /d" \
@@ -172,6 +327,9 @@ exiwhat: config ../src/exiwhat.src
          -e "s?EXIWHAT_EGREP_ARG?$(EXIWHAT_EGREP_ARG)?" \
          -e "s?EXIWHAT_MULTIKILL_CMD?$(EXIWHAT_MULTIKILL_CMD)?" \
          -e "s?EXIWHAT_MULTIKILL_ARG?$(EXIWHAT_MULTIKILL_ARG)?" \
          -e "s?EXIWHAT_EGREP_ARG?$(EXIWHAT_EGREP_ARG)?" \
          -e "s?EXIWHAT_MULTIKILL_CMD?$(EXIWHAT_MULTIKILL_CMD)?" \
          -e "s?EXIWHAT_MULTIKILL_ARG?$(EXIWHAT_MULTIKILL_ARG)?" \
+         -e "s?EXIM_RELEASE_VERSION?$${EXIM_RELEASE_VERSION}?" \
+         -e "s?EXIM_VARIANT_VERSION?$${EXIM_VARIANT_VERSION}?" \
+         -e "s?RM_COMMAND?$(RM_COMMAND)?" \
          ../src/exiwhat.src > exiwhat-t
        @mv exiwhat-t exiwhat
        @chmod a+x exiwhat
          ../src/exiwhat.src > exiwhat-t
        @mv exiwhat-t exiwhat
        @chmod a+x exiwhat
@@ -180,7 +338,7 @@ exiwhat: config ../src/exiwhat.src
 # Target for the exim_checkaccess utility script
 exim_checkaccess: config ../src/exim_checkaccess.src
        @rm -f exim_checkaccess
 # Target for the exim_checkaccess utility script
 exim_checkaccess: config ../src/exim_checkaccess.src
        @rm -f exim_checkaccess
-       @sed \
+       @. ./version.sh && sed \
          -e "s?PROCESSED_FLAG?This file has been so processed.?"\
          -e "/^# /p" \
          -e "/^# /d" \
          -e "s?PROCESSED_FLAG?This file has been so processed.?"\
          -e "/^# /p" \
          -e "/^# /d" \
@@ -188,6 +346,8 @@ exim_checkaccess: config ../src/exim_checkaccess.src
          -e "s?CONFIGURE_FILE?$(CONFIGURE_FILE)?" \
          -e "s?BIN_DIRECTORY?$(BIN_DIRECTORY)?" \
          -e "s?PERL_COMMAND?$(PERL_COMMAND)?" \
          -e "s?CONFIGURE_FILE?$(CONFIGURE_FILE)?" \
          -e "s?BIN_DIRECTORY?$(BIN_DIRECTORY)?" \
          -e "s?PERL_COMMAND?$(PERL_COMMAND)?" \
+         -e "s?EXIM_RELEASE_VERSION?$${EXIM_RELEASE_VERSION}?" \
+         -e "s?EXIM_VARIANT_VERSION?$${EXIM_VARIANT_VERSION}?" \
          ../src/exim_checkaccess.src > exim_checkaccess-t
        @mv exim_checkaccess-t exim_checkaccess
        @chmod a+x exim_checkaccess
          ../src/exim_checkaccess.src > exim_checkaccess-t
        @mv exim_checkaccess-t exim_checkaccess
        @chmod a+x exim_checkaccess
@@ -198,7 +358,7 @@ eximon: config ../src/eximon.src ../OS/eximon.conf-Default \
           ../Local/eximon.conf
        @rm -f eximon
        $(SHELL) $(SCRIPTS)/Configure-eximon
           ../Local/eximon.conf
        @rm -f eximon
        $(SHELL) $(SCRIPTS)/Configure-eximon
-       @sed \
+       @. ./version.sh && sed \
          -e "s?PROCESSED_FLAG?This file has been so processed.?"\
          -e "/^# /p" \
          -e "/^# /d" \
          -e "s?PROCESSED_FLAG?This file has been so processed.?"\
          -e "/^# /p" \
          -e "/^# /d" \
@@ -208,84 +368,108 @@ eximon: config ../src/eximon.src ../OS/eximon.conf-Default \
          -e "s?BASENAME_COMMAND?$(BASENAME_COMMAND)?" \
          -e "s?HOSTNAME_COMMAND?$(HOSTNAME_COMMAND)?" \
          -e "s?X11_LD_LIBRARY?$(X11_LD_LIB)?" \
          -e "s?BASENAME_COMMAND?$(BASENAME_COMMAND)?" \
          -e "s?HOSTNAME_COMMAND?$(HOSTNAME_COMMAND)?" \
          -e "s?X11_LD_LIBRARY?$(X11_LD_LIB)?" \
+         -e "s?EXIM_RELEASE_VERSION?$${EXIM_RELEASE_VERSION}?" \
+         -e "s?EXIM_VARIANT_VERSION?$${EXIM_VARIANT_VERSION}?" \
          ../src/eximon.src >> eximon
        @echo ">>> eximon script built"; echo ""
 
 # Targets for utilities; these are all Perl scripts that have to get the
 # location of Perl put in them. A few need other things as well.
 
          ../src/eximon.src >> eximon
        @echo ">>> eximon script built"; echo ""
 
 # Targets for utilities; these are all Perl scripts that have to get the
 # location of Perl put in them. A few need other things as well.
 
-exigrep: Makefile ../src/exigrep.src
+exigrep: config ../src/exigrep.src
        @rm -f exigrep
        @rm -f exigrep
-       @sed \
+       @. ./version.sh && sed \
          -e "s?PROCESSED_FLAG?This file has been so processed.?"\
          -e "/^# /p" \
          -e "/^# /d" \
          -e "s?PERL_COMMAND?$(PERL_COMMAND)?" \
          -e "s?ZCAT_COMMAND?$(ZCAT_COMMAND)?" \
           -e "s?COMPRESS_SUFFIX?$(COMPRESS_SUFFIX)?" \
          -e "s?PROCESSED_FLAG?This file has been so processed.?"\
          -e "/^# /p" \
          -e "/^# /d" \
          -e "s?PERL_COMMAND?$(PERL_COMMAND)?" \
          -e "s?ZCAT_COMMAND?$(ZCAT_COMMAND)?" \
           -e "s?COMPRESS_SUFFIX?$(COMPRESS_SUFFIX)?" \
+         -e "s?EXIM_RELEASE_VERSION?$${EXIM_RELEASE_VERSION}?" \
+         -e "s?EXIM_VARIANT_VERSION?$${EXIM_VARIANT_VERSION}?" \
          ../src/exigrep.src > exigrep-t
        @mv exigrep-t exigrep
        @chmod a+x exigrep
        @echo ">>> exigrep script built"
 
          ../src/exigrep.src > exigrep-t
        @mv exigrep-t exigrep
        @chmod a+x exigrep
        @echo ">>> exigrep script built"
 
-eximstats: Makefile ../src/eximstats.src
+eximstats: config ../src/eximstats.src
        @rm -f eximstats
        @rm -f eximstats
-       @sed \
+       @. ./version.sh && sed \
          -e "s?PERL_COMMAND?$(PERL_COMMAND)?" \
          -e "s?PERL_COMMAND?$(PERL_COMMAND)?" \
+         -e "s?EXIM_RELEASE_VERSION?$${EXIM_RELEASE_VERSION}?" \
+         -e "s?EXIM_VARIANT_VERSION?$${EXIM_VARIANT_VERSION}?" \
          ../src/eximstats.src > eximstats-t
        @mv eximstats-t eximstats
        @chmod a+x eximstats
        @echo ">>> eximstats script built"
 
          ../src/eximstats.src > eximstats-t
        @mv eximstats-t eximstats
        @chmod a+x eximstats
        @echo ">>> eximstats script built"
 
-exiqgrep: Makefile ../src/exiqgrep.src
+exiqgrep: config ../src/exiqgrep.src
        @rm -f exiqgrep
        @rm -f exiqgrep
-       @sed \
+       @. ./version.sh && sed \
          -e "s?PROCESSED_FLAG?This file has been so processed.?"\
          -e "/^# /p" \
          -e "/^# /d" \
          -e "s?BIN_DIRECTORY?$(BIN_DIRECTORY)?" \
          -e "s?PERL_COMMAND?$(PERL_COMMAND)?" \
          -e "s?PROCESSED_FLAG?This file has been so processed.?"\
          -e "/^# /p" \
          -e "/^# /d" \
          -e "s?BIN_DIRECTORY?$(BIN_DIRECTORY)?" \
          -e "s?PERL_COMMAND?$(PERL_COMMAND)?" \
+         -e "s?EXIM_RELEASE_VERSION?$${EXIM_RELEASE_VERSION}?" \
+         -e "s?EXIM_VARIANT_VERSION?$${EXIM_VARIANT_VERSION}?" \
          ../src/exiqgrep.src > exiqgrep-t
        @mv exiqgrep-t exiqgrep
        @chmod a+x exiqgrep
        @echo ">>> exiqgrep script built"
 
          ../src/exiqgrep.src > exiqgrep-t
        @mv exiqgrep-t exiqgrep
        @chmod a+x exiqgrep
        @echo ">>> exiqgrep script built"
 
-exiqsumm: Makefile ../src/exiqsumm.src
+exiqsumm: config ../src/exiqsumm.src
        @rm -f exiqsumm
        @rm -f exiqsumm
-       @sed -e "s?PERL_COMMAND?$(PERL_COMMAND)?" \
+       @. ./version.sh && sed \
+         -e "s?PERL_COMMAND?$(PERL_COMMAND)?" \
+         -e "s?EXIM_RELEASE_VERSION?$${EXIM_RELEASE_VERSION}?" \
+         -e "s?EXIM_VARIANT_VERSION?$${EXIM_VARIANT_VERSION}?" \
          ../src/exiqsumm.src > exiqsumm-t
        @mv exiqsumm-t exiqsumm
        @chmod a+x exiqsumm
        @echo ">>> exiqsumm script built"
 
          ../src/exiqsumm.src > exiqsumm-t
        @mv exiqsumm-t exiqsumm
        @chmod a+x exiqsumm
        @echo ">>> exiqsumm script built"
 
-exipick: Makefile ../src/exipick.src
+exipick: config ../src/exipick.src
        @rm -f exipick
        @rm -f exipick
-       @sed -e "s?PERL_COMMAND?$(PERL_COMMAND)?" \
+       @. ./version.sh && sed \
+         -e "s?PERL_COMMAND?$(PERL_COMMAND)?" \
          -e "s?SPOOL_DIRECTORY?$(SPOOL_DIRECTORY)?" \
          -e "s?SPOOL_DIRECTORY?$(SPOOL_DIRECTORY)?" \
+         -e "s?BIN_DIRECTORY?$(BIN_DIRECTORY)?" \
+         -e "s?EXIM_RELEASE_VERSION?$${EXIM_RELEASE_VERSION}?" \
+         -e "s?EXIM_VARIANT_VERSION?$${EXIM_VARIANT_VERSION}?" \
          ../src/exipick.src > exipick-t
        @mv exipick-t exipick
        @chmod a+x exipick
        @echo ">>> exipick script built"
 
          ../src/exipick.src > exipick-t
        @mv exipick-t exipick
        @chmod a+x exipick
        @echo ">>> exipick script built"
 
-transport-filter.pl: Makefile ../src/transport-filter.src
+transport-filter.pl: config ../src/transport-filter.src
        @rm -f transport-filter.pl
        @rm -f transport-filter.pl
-       @sed -e "s?PERL_COMMAND?$(PERL_COMMAND)?" \
+       @. ./version.sh && sed \
+         -e "s?PERL_COMMAND?$(PERL_COMMAND)?" \
+         -e "s?EXIM_RELEASE_VERSION?$${EXIM_RELEASE_VERSION}?" \
+         -e "s?EXIM_VARIANT_VERSION?$${EXIM_VARIANT_VERSION}?" \
          ../src/transport-filter.src > transport-filter.pl-t
        @mv transport-filter.pl-t transport-filter.pl
        @chmod a+x transport-filter.pl
        @echo ">>> transport-filter.pl script built"
 
          ../src/transport-filter.src > transport-filter.pl-t
        @mv transport-filter.pl-t transport-filter.pl
        @chmod a+x transport-filter.pl
        @echo ">>> transport-filter.pl script built"
 
-convert4r3: Makefile ../src/convert4r3.src
+convert4r3: config ../src/convert4r3.src
        @rm -f convert4r3
        @rm -f convert4r3
-       @sed -e "s?PERL_COMMAND?$(PERL_COMMAND)?" \
+       @. ./version.sh && sed \
+         -e "s?PERL_COMMAND?$(PERL_COMMAND)?" \
+         -e "s?EXIM_RELEASE_VERSION?$${EXIM_RELEASE_VERSION}?" \
+         -e "s?EXIM_VARIANT_VERSION?$${EXIM_VARIANT_VERSION}?" \
          ../src/convert4r3.src > convert4r3-t
        @mv convert4r3-t convert4r3
        @chmod a+x convert4r3
        @echo ">>> convert4r3 script built"
 
          ../src/convert4r3.src > convert4r3-t
        @mv convert4r3-t convert4r3
        @chmod a+x convert4r3
        @echo ">>> convert4r3 script built"
 
-convert4r4: Makefile ../src/convert4r4.src
+convert4r4: config ../src/convert4r4.src
        @rm -f convert4r4
        @rm -f convert4r4
-       @sed -e "s?PERL_COMMAND?$(PERL_COMMAND)?" \
+       @. ./version.sh && sed \
+         -e "s?PERL_COMMAND?$(PERL_COMMAND)?" \
+         -e "s?EXIM_RELEASE_VERSION?$${EXIM_RELEASE_VERSION}?" \
+         -e "s?EXIM_VARIANT_VERSION?$${EXIM_VARIANT_VERSION}?" \
          ../src/convert4r4.src > convert4r4-t
        @mv convert4r4-t convert4r4
        @chmod a+x convert4r4
          ../src/convert4r4.src > convert4r4-t
        @mv convert4r4-t convert4r4
        @chmod a+x convert4r4
@@ -297,26 +481,33 @@ 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
 # 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 =     arc.o \
+                       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
 
 
 # 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 \
         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 dkim_transport.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 \
         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 \
+        environment.o macro.o \
         $(OBJ_LOOKUPS) \
         local_scan.o $(EXIM_PERL) $(OBJ_WITH_CONTENT_SCAN) \
         $(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 \
 
 exim:   buildlookups buildauths pdkim/pdkim.a \
         buildrouters buildtransports \
@@ -386,7 +577,7 @@ exim_tidydb: $(OBJ_TIDYDB)
 
 exim_dbmbuild: exim_dbmbuild.o
        @echo "$(LNCC) -o exim_dbmbuild"
 
 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; \
          $(LIBS) $(EXTRALIBS) $(DBMLIB)
        @if [ x"$(STRIP_COMMAND)" != x"" ]; then \
          echo $(STRIP_COMMAND) exim_dbmbuild; \
@@ -397,7 +588,7 @@ exim_dbmbuild: exim_dbmbuild.o
 
 # The utility for locking a mailbox while messing around with it
 
 
 # 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"
        @echo "$(CC) exim_lock.c"
        $(FE)$(CC) -c $(CFLAGS) $(INCLUDE) exim_lock.c
        @echo "$(LNCC) -o exim_lock"
@@ -424,7 +615,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.
 
 # 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 \
+            util-tod.o \
+            tree.o \
+            $(MONBIN)
 
 eximon.bin: $(EXIMON_EDITME) eximon $(OBJ_MONBIN) \
             ../exim_monitor/em_version.c
 
 eximon.bin: $(EXIMON_EDITME) eximon $(OBJ_MONBIN) \
             ../exim_monitor/em_version.c
@@ -433,7 +630,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) \
          $(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; \
          $(LIBS) $(LIBS_EXIMON) $(EXTRALIBS) $(EXTRALIBS_EXIMON) -lc
        @if [ x"$(STRIP_COMMAND)" != x"" ]; then \
          echo $(STRIP_COMMAND) eximon.bin; \
@@ -444,13 +641,36 @@ eximon.bin: $(EXIMON_EDITME) eximon $(OBJ_MONBIN) \
 
 
 # Compile step for most of the exim modules. HDRS is a list of headers
 
 
 # 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.
 
 # 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"
 
 .SUFFIXES: .o .c
 .c.o:;  @echo "$(CC) $*.c"
@@ -458,7 +678,7 @@ PHDRS = ../config.h ../dbfunctions.h ../dbstuff.h ../exim.h ../functions.h ../gl
 
 # Update Exim's version information and build the version object.
 
 
 # Update Exim's version information and build the version object.
 
-version.h::
+version.h version.sh::
        @../scripts/reversion
 
 cnumber.h: version.h
        @../scripts/reversion
 
 cnumber.h: version.h
@@ -504,7 +724,8 @@ exim_tidydb.o:   $(HDRS) exim_dbutil.c
 
 exim_dbmbuild.o: $(HDRS) exim_dbmbuild.c
        @echo "$(CC) exim_dbmbuild.c"
 
 exim_dbmbuild.o: $(HDRS) exim_dbmbuild.c
        @echo "$(CC) exim_dbmbuild.c"
-       $(FE)$(CC) -c $(CFLAGS) $(INCLUDE) -o exim_dbmbuild.o exim_dbmbuild.c
+       $(FE)$(CC) -c $(CFLAGS) $(INCLUDE) -DCOMPILE_UTILITY \
+               -o exim_dbmbuild.o exim_dbmbuild.c
 
 # Utilities use special versions of some modules - typically with debugging
 # calls cut out.
 
 # Utilities use special versions of some modules - typically with debugging
 # calls cut out.
@@ -521,6 +742,14 @@ 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
 
        @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-tod.o:   $(HDRS) tod.c
+       @echo "$(CC) -DCOMPILE_UTILITY tod.c"
+       $(FE)$(CC) -c $(CFLAGS) $(INCLUDE) -DCOMPILE_UTILITY -o util-tod.o tod.c
+
 util-os.o:       $(HDRS) os.c
        @echo "$(CC) -DCOMPILE_UTILITY os.c"
        $(FE)$(CC) -c $(CFLAGS) $(INCLUDE) \
 util-os.o:       $(HDRS) os.c
        @echo "$(CC) -DCOMPILE_UTILITY os.c"
        $(FE)$(CC) -c $(CFLAGS) $(INCLUDE) \
@@ -539,12 +768,13 @@ local_scan.o:    config local_scan.h ../$(LOCAL_SCAN_SOURCE)
 # Dependencies for the "ordinary" exim modules
 
 acl.o:           $(HDRS) acl.c
 # 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
 dbfn.o:          $(HDRS) dbfn.c
 debug.o:         $(HDRS) debug.c
 child.o:         $(HDRS) child.c
 crypt16.o:       $(HDRS) crypt16.c
 daemon.o:        $(HDRS) daemon.c
 dbfn.o:          $(HDRS) dbfn.c
 debug.o:         $(HDRS) debug.c
-deliver.o:       $(HDRS) deliver.c
+deliver.o:       $(HDRS) transports/smtp.h deliver.c
 directory.o:     $(HDRS) directory.c
 dns.o:           $(HDRS) dns.c
 enq.o:           $(HDRS) enq.c
 directory.o:     $(HDRS) directory.c
 dns.o:           $(HDRS) dns.c
 enq.o:           $(HDRS) enq.c
@@ -554,6 +784,7 @@ environment.o:   $(HDRS) environment.c
 filter.o:        $(HDRS) filter.c
 filtertest.o:    $(HDRS) filtertest.c
 globals.o:       $(HDRS) globals.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
 header.o:        $(HDRS) header.c
 host.o:          $(HDRS) host.c
 ip.o:            $(HDRS) ip.c
@@ -561,7 +792,7 @@ log.o:           $(HDRS) log.c
 lss.o:           $(HDRS) lss.c
 match.o:         $(HDRS) match.c
 moan.o:          $(HDRS) moan.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
 parse.o:         $(HDRS) parse.c
 queue.o:         $(HDRS) queue.c
 rda.o:           $(HDRS) rda.c
@@ -580,34 +811,36 @@ spool_out.o:     $(HDRS) spool_out.c
 std-crypto.o:    $(HDRS) std-crypto.c
 store.o:         $(HDRS) store.c
 string.o:        $(HDRS) string.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
 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
+dkim_transport.o: $(HDRS) dkim_transport.c
 
 # Dependencies for WITH_CONTENT_SCAN modules
 
 malware.o:       $(HDRS) malware.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
 
 
 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
 
 # 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
+arc.o:         $(HDRS) pdkim/pdkim.h arc.c
+bmi_spam.o:    $(HDRS) bmi_spam.c
+dane.o:                $(HDRS) dane.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
 
 # The module containing tables of available lookups, routers, auths, and
 # transports must be rebuilt if any of them are. However, because the makefiles
@@ -718,12 +951,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 \
 # 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) -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 \
        $(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
 
 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
@@ -736,23 +970,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
 
          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 \
        $(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 \
        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 \
        $(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
 
        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 \
        $(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
        rm -f string.o
 
 # End
index 60d5ea8..b3990fe 100644 (file)
@@ -186,14 +186,6 @@ EXIWHAT_KILL_SIGNAL=-USR1
 
 # IPV6_USE_INET_PTON=yes
 
 
 # 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
 # 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
 
 #############################################################################
 # 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.
 # 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
 
 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
 
 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
 CFLAGS_DYNAMIC=-shared -rdynamic -fPIC
 
 # FreeBSD always ships with Berkeley DB
index 1022abb..5a89478 100644 (file)
@@ -4,7 +4,7 @@ CHOWN_COMMAND=/usr/sbin/chown
 CHGRP_COMMAND=/usr/sbin/chgrp
 CHMOD_COMMAND=/bin/chmod
 
 CHGRP_COMMAND=/usr/sbin/chgrp
 CHMOD_COMMAND=/bin/chmod
 
-CFLAGS=-O2 -Wall
+CFLAGS=-O2 -Wall -Wno-parentheses -Wno-self-assign -Wno-logical-op-parentheses
 
 LIBS=-lm
 
 
 LIBS=-lm
 
index e60a6c0..568e99f 100644 (file)
@@ -19,4 +19,6 @@ XINCLUDE=-I$(X11)/include
 XLFLAGS=-L$(X11)/lib -R$(X11)/lib
 X11LIB=$(X11)/lib
 
 XLFLAGS=-L$(X11)/lib -R$(X11)/lib
 X11LIB=$(X11)/lib
 
+OS_C_INCLUDES=setenv.c
+
 # End
 # End
diff --git a/OS/os.c-FreeBSD b/OS/os.c-FreeBSD
new file mode 100644 (file)
index 0000000..1261b85
--- /dev/null
@@ -0,0 +1,24 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) Jeremy Harris 1995 - 2018 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+/* FreeBSD-specific code. This is concatenated onto the generic
+src/os.c file. */
+
+
+/*************
+* Sendfile   *
+*************/
+
+ssize_t
+os_sendfile(int out, int in, off_t * off, size_t cnt)
+{
+off_t written;
+return sendfile(in, out, *off, cnt, NULL, &written, 0) < 0
+  ? (ssize_t) -1 : (ssize_t) written;
+}
+
+/* End of os.c-Linux */
index df0dff9..59d81f8 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1997 - 2001 */
+/* Copyright (c) University of Cambridge 1997 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Linux-specific code. This is concatenated onto the generic
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Linux-specific code. This is concatenated onto the generic
@@ -150,4 +150,16 @@ return yield;
 
 #endif  /* FIND_RUNNING_INTERFACES */
 
 
 #endif  /* FIND_RUNNING_INTERFACES */
 
+
+/*************
+* Sendfile   *
+*************/
+#include <sys/sendfile.h>
+
+ssize_t
+os_sendfile(int out, int in, off_t * off, size_t cnt)
+{
+return sendfile(out, in, off, cnt);
+}
+
 /* End of os.c-Linux */
 /* End of os.c-Linux */
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 */
diff --git a/OS/os.c-cygwin b/OS/os.c-cygwin
deleted file mode 100644 (file)
index ea17a43..0000000
+++ /dev/null
@@ -1,862 +0,0 @@
-/*************************************************
-*     Exim - an Internet mail transport agent    *
-*************************************************/
-
-/* Cygwin-specific code. December 2002
-   This is concatenated onto the generic src/os.c file.
-
-   This code was supplied by Pierre A. Humblet <Pierre.Humblet@ieee.org>
-*/
-
-/* We need a special mkdir that
-   allows names starting with // */
-#undef mkdir
-int cygwin_mkdir( const char *path, mode_t mode )
-{
-  const char * p = path;
-  if (*p == '/') while(*(p+1) == '/') p++;
-  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
-
-unsigned int cygwin_WinVersion;
-
-/* Conflict between Windows definitions and others */
-#ifdef NOERROR
-#undef NOERROR
-#endif
-#ifdef DELETE
-#undef DELETE
-#endif
-
-#include <windows.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 */
-
-#undef setuid
-int cygwin_setuid(uid_t uid )
-{
-  int res;
-  if (privileged <= 0) return 0;
-  else {
-    res = setuid(uid);
-    if (cygwin_debug)
-      fprintf(stderr, "setuid %lu %lu %d pid: %d\n",
-              uid, getuid(),res, getpid());
-  }
-  return res;
-}
-
-#undef setgid
-int cygwin_setgid(gid_t gid )
-{
-  int res;
-  if (privileged <= 0) return 0;
-  else {
-    res = setgid(gid);
-    if (cygwin_debug)
-      fprintf(stderr, "setgid %lu %lu %d pid: %d\n",
-              gid, getgid(), res, getpid());
-  }
-  return res;
-}
-
-/* Background processes run at lower priority */
-static void cygwin_setpriority()
-{
-  if (!SetPriorityClass(GetCurrentProcess(), BELOW_NORMAL_PRIORITY_CLASS))
-    SetPriorityClass(GetCurrentProcess(), IDLE_PRIORITY_CLASS);
-  return;
-}
-
-
-/* GetVersion()
-   MSB: 1 for 95/98/ME; Next 7: build number, except for 95/98/ME
-   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 */
-
-/*
-  Routine to find if process or thread is privileged
-*/
-
-enum {
-  CREATE_BIT = 1,
-  RESTORE_BIT = 2
-};
-
-static DWORD get_privileges ()
-{
-  char buffer[1024];
-  DWORD i, length;
-  HANDLE hToken = NULL;
-  PTOKEN_PRIVILEGES privs;
-  LUID cluid, rluid;
-  DWORD ret = 0;
-
-  privs = (PTOKEN_PRIVILEGES) buffer;
-
-  if (OpenProcessToken (GetCurrentProcess(), TOKEN_QUERY, &hToken)
-      && LookupPrivilegeValue (NULL, SE_CREATE_TOKEN_NAME, &cluid)
-      && LookupPrivilegeValue(NULL, SE_RESTORE_NAME, &rluid)
-      && (GetTokenInformation( hToken, TokenPrivileges,
-                               privs, sizeof (buffer), &length)
-          || (GetLastError () == ERROR_INSUFFICIENT_BUFFER
-              && (privs = (PTOKEN_PRIVILEGES) alloca (length))
-              && GetTokenInformation(hToken, TokenPrivileges,
-                                     privs, length, &length)))) {
-    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))
-        break;
-    }
-  }
-  else
-    fprintf(stderr, "has_create_token_privilege %ld\n", GetLastError());
-
-  if (hToken)
-    CloseHandle(hToken);
-
-  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)
-{
-  int i;
-  uid_t myuid, systemuid;
-  gid_t mygid, adminsgid;
-  struct passwd * pwp;
-  char *cygenv, win32_path[MAX_PATH];
-  SID(1, SystemSid, SECURITY_LOCAL_SYSTEM_RID);
-  SID(2, AdminsSid, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS);
-  DWORD priv_flags;
-
-  myuid = getuid();
-  mygid = getgid();
-  cygwin_WinVersion = GetVersion();
-  if ((cygenv = getenv("CYGWIN")) == NULL) cygenv = "";
-  /* Produce some debugging on stderr,
-     cannot yet use exim's debug functions.
-     Exim does not use -c and ignores -n.
-     Set lower priority for daemons */
-  for (i = 1; i < argc; i++) {
-    if (argv[i][0] == '-') {
-      if (argv[i][1] == 'c') {
-        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);
-      }
-      else if (argv[i][1] == 'b' && argv[i][2] == 'd')
-        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",
-       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.
-     If not privileged and unable to chown,
-       we set the exim uid to our uid.
-     If unprivileged, we fake all subsequent setuid. */
-
-  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;
-  }
-  else {
-    * (uid_t *) eup = systemuid;
-    * (gid_t *) egp = adminsgid;
-  }
-
-  /* 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;
-
-  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 */
-  }
-
-  /* Pretend we are root to avoid useless execs.
-     We are limited by file access rights */
-  * (uid_t *) rup = getuid ();
-
-  /* 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;
-
-  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);
-  }
-  return;
-}
-
-#ifndef OS_LOAD_AVERAGE /* Can be set on command line */
-#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.
-
- 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
-
-/* Structure to compute the load average efficiently */
-typedef struct {
-  DWORD Lock;
-  unsigned long long Time100ns;   /* Last measurement time */
-  unsigned long long IdleCount;   /* Latest cumulative idle time */
-  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 {
-   HANDLE handle;
-   pid_t pid;
-   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
-{
-  SystemBasicInformation = 0,
-  SystemPerformanceInformation = 2,
-  SystemTimeOfDayInformation = 3,
-  SystemProcessesAndThreadsInformation = 5,
-  SystemProcessorTimes = 8,
-  SystemPagefileInformation = 18,
-  /* There are a lot more of these... */
-} SYSTEM_INFORMATION_CLASS;
-
-typedef struct _SYSTEM_BASIC_INFORMATION
-{
-  ULONG Unknown;
-  ULONG MaximumIncrement;
-  ULONG PhysicalPageSize;
-  ULONG NumberOfPhysicalPages;
-  ULONG LowestPhysicalPage;
-  ULONG HighestPhysicalPage;
-  ULONG AllocationGranularity;
-  ULONG LowestUserAddress;
-  ULONG HighestUserAddress;
-  ULONG ActiveProcessors;
-  UCHAR NumberProcessors;
-} SYSTEM_BASIC_INFORMATION, *PSYSTEM_BASIC_INFORMATION;
-
-typedef struct __attribute__ ((aligned (8))) _SYSTEM_PROCESSOR_TIMES
-{
-  LARGE_INTEGER IdleTime;
-  LARGE_INTEGER KernelTime;
-  LARGE_INTEGER UserTime;
-  LARGE_INTEGER DpcTime;
-  LARGE_INTEGER InterruptTime;
-  ULONG InterruptCount;
-} SYSTEM_PROCESSOR_TIMES, *PSYSTEM_PROCESSOR_TIMES;
-
-typedef NTSTATUS NTAPI (*NtQuerySystemInformation_t) (SYSTEM_INFORMATION_CLASS, PVOID, ULONG, PULONG);
-typedef ULONG NTAPI (*RtlNtStatusToDosError_t) (NTSTATUS);
-
-static NtQuerySystemInformation_t NtQuerySystemInformation;
-static RtlNtStatusToDosError_t RtlNtStatusToDosError;
-
-/*****************************************************************
- *
- LoadNtdll()
- Load special functions from the NTDLL
- Return TRUE if success.
-
- *****************************************************************/
-
-static BOOL LoadNtdll()
-{
-  HINSTANCE hinstLib;
-
-  if ((hinstLib = LoadLibrary("NTDLL.DLL"))
-      && (NtQuerySystemInformation =
-          (NtQuerySystemInformation_t) GetProcAddress(hinstLib,
-                                                        "NtQuerySystemInformation"))
-      && (RtlNtStatusToDosError =
-          (RtlNtStatusToDosError_t) GetProcAddress(hinstLib,
-                                                     "RtlNtStatusToDosError")))
-    return TRUE;
-
-  DEBUG(D_load)
-    debug_printf("perf: load: %ld (Windows)\n", GetLastError());
-  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)
-{
-  NTSTATUS ret;
-  SYSTEM_BASIC_INFORMATION sbi;
-  PSYSTEM_PROCESSOR_TIMES spt;
-
-  *Time100nsPtr = *IdleCountPtr = 0;
-
-  if ((ret = NtQuerySystemInformation(SystemBasicInformation,
-                                      (PVOID) &sbi, sizeof sbi, NULL))
-      != STATUS_SUCCESS) {
-    DEBUG(D_load)
-      debug_printf("Perf: NtQuerySystemInformation: %lu (Windows)\n",
-                   RtlNtStatusToDosError(ret));
-  }
-  else if (!(spt = (PSYSTEM_PROCESSOR_TIMES) alloca(sizeof(spt[0]) * sbi.NumberProcessors))) {
-    DEBUG(D_load)
-      debug_printf("Perf: alloca: errno %d (%s)\n", errno, strerror(errno));
-  }
-  else if ((ret = NtQuerySystemInformation(SystemProcessorTimes, (PVOID) spt,
-                                           sizeof spt[0] * sbi.NumberProcessors, NULL))
-           != STATUS_SUCCESS) {
-    DEBUG(D_load)
-      debug_printf("Perf: NtQuerySystemInformation: %lu (Windows)\n",
-                   RtlNtStatusToDosError(ret));
-  }
-  else {
-    int i;
-    for (i = 0; i < sbi.NumberProcessors; i++) {
-      *Time100nsPtr += spt[i].KernelTime.QuadPart;;
-      *Time100nsPtr += spt[i].UserTime.QuadPart;
-      *IdleCountPtr += spt[i].IdleTime.QuadPart;
-    }
-    return TRUE;
-  }
-  return FALSE;
-}
-#endif /* PERF_METHODX */
-
-/*****************************************************************
- *
- InitLoadAvg()
- Initialize the cygwin_load.perf structure.
- and set cygwin_load.perf->Flag to TRUE if successful.
- This is called the first time os_getloadavg is called
- *****************************************************************/
-static void InitLoadAvg(cygwin_perf_t *this)
-{
-  BOOL success = TRUE;
-
-  /* Get perf frequency and counter */
-  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,
-                         & this->IdleCount);
-  /* If success, set the Load to 0, else to -1 */
-  if (success) this->LastLoad = 0;
-  else {
-    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
-}
-
-
-/*****************************************************************
- *
- os_getloadavg()
-
- Return -1 if not available;
- Return the previous value if less than AVERAGING sec old.
- else return the processor load on a [0 - 1000] scale.
-
- The first time we are called we initialize the counts
- and return 0 or -1.
- The initial load cannot be measured as we use the processor 100%
-*****************************************************************/
-static SECURITY_ATTRIBUTES sa = {sizeof (SECURITY_ATTRIBUTES), NULL, TRUE};
-#define AVERAGING 10
-
-int os_getloadavg()
-{
-  unsigned long long Time100ns, IdleCount, CurrCounter;
-  int value;
-  pid_t newpid;
-
-  /* New process.
-     Reload the dlls and the file mapping */
-  if ((newpid = getpid()) != cygwin_load.pid) {
-    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);
-    }
-    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);
-    if (new && cygwin_load.perf)
-      InitLoadAvg(cygwin_load.perf);
-  }
-
-  /* Check if initialized OK */
-  if (!cygwin_load.perf || cygwin_load.perf->LastLoad < 0)
-    return -1;
-
-  /* If we cannot get the lock, we return 0.
-     This is to prevent any lock-up possibility.
-     Finding a lock busy is unlikely, and giving up only
-     results in an immediate delivery .*/
-
-  if (InterlockedCompareExchange(&cygwin_load.perf->Lock, 1, 0)) {
-    DEBUG(D_load)
-      debug_printf("Perf: Lock busy\n");
-    return 0;
-  }
-
-    /* Get the current time (PerfCounter) */
-    QueryPerformanceCounter((LARGE_INTEGER *)& CurrCounter);
-    /* Calls closer than AVERAGING sec apart use the previous value */
-  if (CurrCounter - cygwin_load.perf->LastCounter >
-      AVERAGING * cygwin_load.perf->PerfFreq) {
-      /* Get Time100ns and IdleCount */
-      if (ReadStat( & Time100ns, & IdleCount)) { /* Success */
-        /* Return processor load on 1000 scale */
-      value = 1000 - ((1000 * (IdleCount - cygwin_load.perf->IdleCount)) /
-                      (Time100ns - cygwin_load.perf->Time100ns));
-      cygwin_load.perf->Time100ns = Time100ns;
-      cygwin_load.perf->IdleCount = IdleCount;
-      cygwin_load.perf->LastCounter = CurrCounter;
-      cygwin_load.perf->LastLoad = value;
-      DEBUG(D_load)
-        debug_printf("Perf: New load average %d\n", value);
-      }
-      else { /* Something bad happened.
-                Refuse to measure the load anymore
-                but don't bother releasing the buffer */
-        log_write(0, LOG_MAIN, "Cannot obtain Load Average");
-      cygwin_load.perf->LastLoad = -1;
-    }
-  }
-  else
-  DEBUG(D_load)
-      debug_printf("Perf: Old load average %d\n", cygwin_load.perf->LastLoad);
-  cygwin_load.perf->Lock = 0;
-  return cygwin_load.perf->LastLoad;
-}
-#endif /* OS_LOAD_AVERAGE */
-#endif /* COMPILE_UTILITY */
index 559003f..7e3a67c 100644 (file)
@@ -7,8 +7,6 @@
 #define PAM_H_IN_PAM
 #define SIOCGIFCONF_GIVES_ADDR
 
 #define PAM_H_IN_PAM
 #define SIOCGIFCONF_GIVES_ADDR
 
-/* OSX 10.2 does not have poll.h, 10.3 does emulate it badly. */
-#define NO_POLL_H
 
 #define F_FREESP     O_TRUNC
 typedef struct flock flock_t;
 
 #define F_FREESP     O_TRUNC
 typedef struct flock flock_t;
@@ -17,7 +15,7 @@ typedef struct flock flock_t;
                                Consider reducing MAX_LOCALHOST_NUMBER */
 
 #ifndef        _BSD_SOCKLEN_T_
                                Consider reducing MAX_LOCALHOST_NUMBER */
 
 #ifndef        _BSD_SOCKLEN_T_
-#define _BSD_SOCKLEN_T_ int32_t                 /* socklen_t (duh) */
+# define _BSD_SOCKLEN_T_ int32_t                 /* socklen_t (duh) */
 #endif
 
 /* Settings for handling IP options. There's no netinet/ip_var.h. The IP
 #endif
 
 /* Settings for handling IP options. There's no netinet/ip_var.h. The IP
@@ -42,4 +40,19 @@ updating Exim to use the newer interface. */
 #define OFF_T_FMT "%lld"
 #define LONGLONG_T long int
 
 #define OFF_T_FMT "%lld"
 #define LONGLONG_T long int
 
+/* default is non-const */
+#define ICONV_ARG2_TYPE const char **
+
+/* seems arpa/nameser.h does not define this */
+#define NS_MAXMSG 65535
+
+/* There may be very many supplementary groups for the user. See notes
+in "man 2 getgroups". */
+#define _DARWIN_UNLIMITED_GETGROUPS
+#define EXIM_GROUPLIST_SIZE 64
+
+/* TCP Fast Open: Darwin uses a connectx() call
+rather than a modified sendto() */
+#define EXIM_TFO_CONNECTX
+
 /* End */
 /* End */
index c5ed042..4f1c616 100644 (file)
@@ -1,4 +1,10 @@
 /* Exim: OS-specific C header file for FreeBSD */
 /* Exim: OS-specific C header file for FreeBSD */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+
+#include <sys/types.h>
+#include <sys/param.h>
 
 #define HAVE_BSD_GETLOADAVG
 #define HAVE_SETCLASSRESOURCES
 
 #define HAVE_BSD_GETLOADAVG
 #define HAVE_SETCLASSRESOURCES
@@ -8,6 +14,63 @@
 #define HAVE_SRANDOMDEV
 #define HAVE_ARC4RANDOM
 
 #define HAVE_SRANDOMDEV
 #define HAVE_ARC4RANDOM
 
+/* Applications should not call arc4random_stir() explicitly after
+ * FreeBSD r227520 (approximately 1000002).
+ * Set NOT_HAVE_ARC4RANDOM_STIR if the version released is past
+ * that point. */
+#if __FreeBSD_version >= 1000002
+# define NOT_HAVE_ARC4RANDOM_STIR
+#endif
+
 typedef struct flock flock_t;
 
 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, look at __FreeBSD_version
+ * from <sys/param.h> */
+
+/* When using DKIM, setting OS_SENDFILE can increase
+performance on outgoing mail a bit. */
+
+#define OS_SENDFILE
+extern ssize_t os_sendfile(int, int, off_t *, size_t);
+
+
+/*******************/
+
+/* TCP_FASTOPEN support.  There does not seems to be a
+MSG_FASTOPEN defined yet... */
+#define EXIM_TFO_PROBE
+
+#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
+
+/* for TCP state-variable values, for TFO logging */
+#include <netinet/tcp_fsm.h>
+#define TCP_SYN_RECV TCPS_SYN_RECEIVED
+
+/*******************/
+
 /* End */
 /* End */
index 3fead17..63cf9ba 100644 (file)
@@ -1,10 +1,14 @@
 /* Exim: OS-specific C header file for Linux */
 /* Exim: OS-specific C header file for Linux */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* See the file NOTICE for conditions of use and distribution. */
+
 
 /* Some old systems we've received bug-reports for have a <limits.h> which
 does not pull in <features.h>.  Best to just pull it in now and have done
 with the issue. */
 
 #include <features.h>
 
 /* Some old systems we've received bug-reports for have a <limits.h> which
 does not pull in <features.h>.  Best to just pull it in now and have done
 with the issue. */
 
 #include <features.h>
+#include <sys/types.h>
 
 
 #define CRYPT_H
 
 
 #define CRYPT_H
@@ -15,12 +19,14 @@ with the issue. */
 #define NO_IP_VAR_H
 #define SIG_IGN_WORKS
 
 #define NO_IP_VAR_H
 #define SIG_IGN_WORKS
 
-/* When using the DKIM, setting HAVE_LINUX_SENDFILE can increase
+/* When using DKIM, setting OS_SENDFILE can increase
 performance on outgoing mail a bit. Note: With older glibc versions
 this setting will conflict with the _FILE_OFFSET_BITS=64 setting
 performance on outgoing mail a bit. Note: With older glibc versions
 this setting will conflict with the _FILE_OFFSET_BITS=64 setting
-defined as part of the Linux CFLAGS. */
+defined as part of the Linux CFLAGS.  As of 2017 those are declared
+to be too old to build by default. */
 
 
-/* #define HAVE_LINUX_SENDFILE */
+#define OS_SENDFILE
+extern ssize_t os_sendfile(int, int, off_t *, size_t);
 
 #define F_FREESP     O_TRUNC
 typedef struct flock flock_t;
 
 #define F_FREESP     O_TRUNC
 typedef struct flock flock_t;
@@ -29,8 +35,8 @@ typedef struct flock flock_t;
 #define OS_STRSIGNAL
 
 #if defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__NetBSD_kernel__)
 #define OS_STRSIGNAL
 
 #if defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__NetBSD_kernel__)
-#define SIOCGIFCONF_GIVES_ADDR
-#define HAVE_SYS_MOUNT_H
+# define SIOCGIFCONF_GIVES_ADDR
+# define HAVE_SYS_MOUNT_H
 #endif
 
 #if defined(__linux__)
 #endif
 
 #if defined(__linux__)
@@ -44,9 +50,6 @@ storage" as quickly as Exim thinks they are. */
 
 #define NEED_SYNC_DIRECTORY
 
 
 #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
 #define os_find_running_interfaces os_find_running_interfaces_linux
 
 /* Need a prototype for the Linux-specific function. The structure hasn't
@@ -68,5 +71,21 @@ then change the 0 to 1 in the next block. */
 # define LLONG_MAX LONG_LONG_MAX
 #endif
 
 # define LLONG_MAX LONG_LONG_MAX
 #endif
 
+#if _POSIX_C_SOURCE >= 200809L || _ATFILE_SOUCE
+# define EXIM_HAVE_OPENAT
+#endif
+
+/* TCP Fast Open support */
+
+#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
+#define EXIM_HAVE_TCPI_UNACKED
+#ifndef TCPI_OPT_SYN_DATA
+# define TCPI_OPT_SYN_DATA 32
+#endif
+
 
 /* End */
 
 /* End */
index 55bade6..dde779f 100644 (file)
@@ -1,10 +1,20 @@
 /* Exim: OS-specific C header file for OpenBSD */
 /* Exim: OS-specific C header file for OpenBSD */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* See the file NOTICE for conditions of use and distribution. */
+
 
 #define HAVE_BSD_GETLOADAVG
 #define HAVE_MMAP
 #define HAVE_SYS_MOUNT_H
 #define SIOCGIFCONF_GIVES_ADDR
 #define HAVE_ARC4RANDOM
 
 #define HAVE_BSD_GETLOADAVG
 #define HAVE_MMAP
 #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;
 
 
 typedef struct flock flock_t;
 
@@ -13,4 +23,38 @@ typedef struct flock flock_t;
 
 typedef struct __res_state *res_state;
 
 
 typedef struct __res_state *res_state;
 
+/* default is non-const */
+#define ICONV_ARG2_TYPE const char **
+
+#ifndef EPROTO
+# define EPROTO 71
+#endif
+
+/* We need to force this; the automatic in buildconfig.c gets %ld */
+#ifdef OFF_T_FMT
+# undef OFF_T_FMT
+# undef LONGLONG_T
+#endif
+#define OFF_T_FMT "%lld"
+#define LONGLONG_T long long int
+
+#ifdef PID_T_FMT
+# undef PID_T_FMT
+#endif
+#define PID_T_FMT "%d"
+
+#ifdef INO_T_FMT
+# undef INO_T_FMT
+#endif
+#define INO_T_FMT "%llu"
+
+#ifdef TIME_T_FMT
+# undef TIME_T_FMT
+#endif
+#define TIME_T_FMT "%lld"
+
+/* seems arpa/nameser.h does not define this.
+Space-constrained devices could use much smaller; a few k. */
+#define NS_MAXMSG 65535
+
 /* End */
 /* End */
index 8bc0799..dfbd8f1 100644 (file)
@@ -28,4 +28,24 @@ it seems. */
 
 #define PAM_CONVERSE_ARG2_TYPE  struct pam_message
 
 
 #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 */
 /* End */
similarity index 100%
rename from OS/Makefile-AIX
rename to OS/unsupported/Makefile-AIX
similarity index 84%
rename from OS/Makefile-CYGWIN
rename to OS/unsupported/Makefile-CYGWIN
index 774fa4f..006e9fe 100644 (file)
@@ -2,8 +2,10 @@
 
 # This file provided by Pierre A. Humblet <Pierre.Humblet@ieee.org>
 
 
 # This file provided by Pierre A. Humblet <Pierre.Humblet@ieee.org>
 
+HAVE_IPV6 = yes
 HAVE_ICONV = 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
 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
 ##################################################
 # 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
 
 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
 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
 
 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
 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
 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
 
 # End
similarity index 100%
rename from OS/Makefile-GNU
rename to OS/unsupported/Makefile-GNU
similarity index 96%
rename from OS/Makefile-HP-UX
rename to OS/unsupported/Makefile-HP-UX
index 073d67a..ea35144 100644 (file)
@@ -22,4 +22,6 @@ EXIMON_TEXTPOP=
 DBMLIB=-lndbm
 RANLIB=@true
 
 DBMLIB=-lndbm
 RANLIB=@true
 
+OS_C_INCLUDES=setenv.c
+
 # End
 # End
similarity index 100%
rename from OS/Makefile-QNX
rename to OS/unsupported/Makefile-QNX
similarity index 100%
rename from OS/Makefile-SCO
rename to OS/unsupported/Makefile-SCO
similarity index 100%
rename from OS/Makefile-USG
rename to OS/unsupported/Makefile-USG
diff --git a/OS/unsupported/README b/OS/unsupported/README
new file mode 100644 (file)
index 0000000..73790ae
--- /dev/null
@@ -0,0 +1,14 @@
+Files in this directory are historical.  They may have worked once but the
+project has no assurance that they still do.
+
+If you need to use one for a build for your platform, copy it up one directory
+level first.  We'll reinstate it given a current version and evidence of testing.
+For the latter please look into the project regression testsuite, and please
+consider operating a buildfarm animal in the long term (it runs the testsuite).
+
+The buildfarm status page is:
+  https://buildfarm.exim.org/cgi-bin/show_status.pl
+There's a "register" link there with a link to how-to instructions.  Please do
+monitor the status of your animal on an ongoing basis.  The exim-users or
+exim-dev mailinglist are good places to ask for help and to discuss any regressions
+seen in test runs.  There is also the #exim IRC channel on Freenode.
diff --git a/OS/unsupported/os.c-BSDI b/OS/unsupported/os.c-BSDI
new file mode 100644 (file)
index 0000000..03a7a1c
--- /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 uschar * name)
+{
+unsetenv(CS name);
+return 0;
+}
similarity index 100%
rename from OS/os.c-GNU
rename to OS/unsupported/os.c-GNU
similarity index 100%
rename from OS/os.c-HI-OSF
rename to OS/unsupported/os.c-HI-OSF
diff --git a/OS/unsupported/os.c-HP-UX b/OS/unsupported/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 */
similarity index 99%
rename from OS/os.c-IRIX
rename to OS/unsupported/os.c-IRIX
index 487091a..1f6b0e1 100644 (file)
@@ -82,7 +82,7 @@ for (nextaddr = buf; nextaddr < lim; nextaddr += ifm->ifm_msglen)
 
     if ((ifam->ifam_addrs & RTA_IFA) != 0)
       {
 
     if ((ifam->ifam_addrs & RTA_IFA) != 0)
       {
-      char *cp = (char *)mask;
+      char *cp = CS mask;
       struct sockaddr *sa = (struct sockaddr *)mask;
       ADVANCE(cp, sa);
       addr = (struct sockaddr_in *)cp;
       struct sockaddr *sa = (struct sockaddr *)mask;
       ADVANCE(cp, sa);
       addr = (struct sockaddr_in *)cp;
similarity index 99%
rename from OS/os.c-IRIX6
rename to OS/unsupported/os.c-IRIX6
index 487091a..1f6b0e1 100644 (file)
@@ -82,7 +82,7 @@ for (nextaddr = buf; nextaddr < lim; nextaddr += ifm->ifm_msglen)
 
     if ((ifam->ifam_addrs & RTA_IFA) != 0)
       {
 
     if ((ifam->ifam_addrs & RTA_IFA) != 0)
       {
-      char *cp = (char *)mask;
+      char *cp = CS mask;
       struct sockaddr *sa = (struct sockaddr *)mask;
       ADVANCE(cp, sa);
       addr = (struct sockaddr_in *)cp;
       struct sockaddr *sa = (struct sockaddr *)mask;
       ADVANCE(cp, sa);
       addr = (struct sockaddr_in *)cp;
similarity index 99%
rename from OS/os.c-IRIX632
rename to OS/unsupported/os.c-IRIX632
index 487091a..1f6b0e1 100644 (file)
@@ -82,7 +82,7 @@ for (nextaddr = buf; nextaddr < lim; nextaddr += ifm->ifm_msglen)
 
     if ((ifam->ifam_addrs & RTA_IFA) != 0)
       {
 
     if ((ifam->ifam_addrs & RTA_IFA) != 0)
       {
-      char *cp = (char *)mask;
+      char *cp = CS mask;
       struct sockaddr *sa = (struct sockaddr *)mask;
       ADVANCE(cp, sa);
       addr = (struct sockaddr_in *)cp;
       struct sockaddr *sa = (struct sockaddr *)mask;
       ADVANCE(cp, sa);
       addr = (struct sockaddr_in *)cp;
similarity index 99%
rename from OS/os.c-IRIX65
rename to OS/unsupported/os.c-IRIX65
index 487091a..1f6b0e1 100644 (file)
@@ -82,7 +82,7 @@ for (nextaddr = buf; nextaddr < lim; nextaddr += ifm->ifm_msglen)
 
     if ((ifam->ifam_addrs & RTA_IFA) != 0)
       {
 
     if ((ifam->ifam_addrs & RTA_IFA) != 0)
       {
-      char *cp = (char *)mask;
+      char *cp = CS mask;
       struct sockaddr *sa = (struct sockaddr *)mask;
       ADVANCE(cp, sa);
       addr = (struct sockaddr_in *)cp;
       struct sockaddr *sa = (struct sockaddr *)mask;
       ADVANCE(cp, sa);
       addr = (struct sockaddr_in *)cp;
similarity index 100%
rename from OS/os.c-OSF1
rename to OS/unsupported/os.c-OSF1
diff --git a/OS/unsupported/os.c-cygwin b/OS/unsupported/os.c-cygwin
new file mode 100644 (file)
index 0000000..c9464aa
--- /dev/null
@@ -0,0 +1,531 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* 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>
+*/
+
+/* We need a special mkdir that
+   allows names starting with // */
+#undef mkdir
+int cygwin_mkdir( const char *path, mode_t mode )
+{
+  const char * p = path;
+  if (*p == '/') while(*(p+1) == '/') p++;
+  return mkdir(p, mode);
+}
+
+#ifndef COMPILE_UTILITY /* Utilities don't need special code */
+
+#ifdef INCLUDE_PAM
+#include "../pam/pam.c"
+#endif
+#include <alloca.h>
+
+unsigned int cygwin_WinVersion;
+
+/* Conflict between Windows definitions and others */
+#ifdef NOERROR
+#undef NOERROR
+#endif
+#ifdef DELETE
+#undef DELETE
+#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 fakesetugid = 1; /* when not privileged, setugid = noop */
+
+#undef setuid
+int cygwin_setuid(uid_t uid )
+{
+  int res = 0;
+  if (fakesetugid == 0) { 
+    res = setuid(uid);
+    if (cygwin_debug)
+      fprintf(stderr, "setuid %u %u %d pid: %d\n",
+              uid, getuid(),res, getpid());
+  }
+  return res;
+}
+
+#undef setgid
+int cygwin_setgid(gid_t gid )
+{
+  int res = 0;
+  if (fakesetugid == 0) { 
+    res = setgid(gid);
+    if (cygwin_debug)
+      fprintf(stderr, "setgid %u %u %d pid: %d\n",
+              gid, getgid(), res, getpid());
+  }
+  return res;
+}
+
+/* Background processes run at lower priority */
+static void cygwin_setpriority()
+{
+  if (!SetPriorityClass(GetCurrentProcess(), BELOW_NORMAL_PRIORITY_CLASS))
+    SetPriorityClass(GetCurrentProcess(), IDLE_PRIORITY_CLASS);
+  return;
+}
+
+
+/* GetVersion()
+   MSB: 1 for 95/98/ME; Next 7: build number, except for 95/98/ME
+   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 */
+
+/*
+  Routine to find if process or thread is privileged
+*/
+
+enum {
+  CREATE_BIT = 1,
+};
+
+static DWORD get_privileges ()
+{
+  char buffer[1024];
+  DWORD i, length;
+  HANDLE hToken = NULL;
+  PTOKEN_PRIVILEGES privs;
+  LUID cluid, rluid;
+  DWORD ret = 0;
+
+  privs = (PTOKEN_PRIVILEGES) buffer;
+
+  if (OpenProcessToken (GetCurrentProcess(), TOKEN_QUERY, &hToken)
+      && LookupPrivilegeValue (NULL, SE_CREATE_TOKEN_NAME, &cluid)
+      && LookupPrivilegeValue(NULL, SE_RESTORE_NAME, &rluid)
+      && (GetTokenInformation( hToken, TokenPrivileges,
+                               privs, sizeof (buffer), &length)
+          || (GetLastError () == ERROR_INSUFFICIENT_BUFFER
+              && (privs = (PTOKEN_PRIVILEGES) alloca (length))
+              && GetTokenInformation(hToken, TokenPrivileges,
+                                     privs, length, &length)))) {
+    for (i = 0; i < privs->PrivilegeCount; i++) {
+      if (EqualLuid(privs->Privileges[i].Luid, cluid))
+        ret |= CREATE_BIT;
+      if (ret == (CREATE_BIT))
+        break;
+    }
+  }
+  else
+    fprintf(stderr, "has_create_token_privilege %u\n", GetLastError());
+
+  if (hToken)
+    CloseHandle(hToken);
+
+  return ret;
+}
+
+/* 
+  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, res, is_daemon = 0, is_spoolwritable, is_privileged, is_eximuser;
+  uid_t myuid, systemuid;
+  gid_t mygid, adminsgid;
+  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;
+
+  myuid = getuid();
+  mygid = getgid();
+  cygwin_WinVersion = GetVersion();
+  if ((cygenv = getenv("CYGWIN")) == NULL) cygenv = "";
+  /* Produce some debugging on stderr,
+     cannot yet use exim's debug functions.
+     Exim does not use -c and ignores -n.
+     Set lower priority for daemons */
+  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\".\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') {
+        is_daemon = 1;
+        cygwin_setpriority();
+    }
+  }
+  }
+
+  /* Nt/2000/XP
+     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 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 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 ();
+  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 {
+    exim_uid = systemuid;
+    exim_gid = adminsgid;
+    is_eximuser = 0;
+  }
+
+  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 (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 */
+     }
+  }
+
+  /* Set the configuration file uid and gid to the system uid and admins gid. */
+  config_uid = systemuid;
+  config_gid = adminsgid;
+
+  /* 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 %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;
+}
+
+#ifndef OS_LOAD_AVERAGE /* Can be set on command line */
+#define OS_LOAD_AVERAGE /* src/os.c need not provide it */
+
+/*****************************************************************
+ Functions for average load measurements
+
+ Uses NtQuerySystemInformation.
+ This requires definitions that are not part of
+ standard include files.
+
+ This is discouraged starting with WinXP.
+
+*************************************************************/
+/* Structure to compute the load average efficiently */
+typedef struct {
+  DWORD Lock;
+  unsigned long long Time100ns;   /* Last measurement time */
+  unsigned long long IdleCount;   /* Latest cumulative idle time */
+  unsigned long long LastCounter; /* Last measurement counter */
+  unsigned long long PerfFreq;    /* Perf counter frequency */
+  int LastLoad;                   /* Last reported load, or -1 */
+} cygwin_perf_t;
+
+static struct {
+   HANDLE handle;
+   pid_t pid;
+   cygwin_perf_t *perf;
+} cygwin_load = {NULL, 0, NULL};
+
+#include <ntdef.h>
+
+typedef enum _SYSTEM_INFORMATION_CLASS
+{
+  SystemBasicInformation = 0,
+  SystemPerformanceInformation = 2,
+  SystemTimeOfDayInformation = 3,
+  SystemProcessesAndThreadsInformation = 5,
+  SystemProcessorTimes = 8,
+  SystemPagefileInformation = 18,
+  /* There are a lot more of these... */
+} SYSTEM_INFORMATION_CLASS;
+
+typedef struct _SYSTEM_BASIC_INFORMATION
+{
+  ULONG Unknown;
+  ULONG MaximumIncrement;
+  ULONG PhysicalPageSize;
+  ULONG NumberOfPhysicalPages;
+  ULONG LowestPhysicalPage;
+  ULONG HighestPhysicalPage;
+  ULONG AllocationGranularity;
+  ULONG LowestUserAddress;
+  ULONG HighestUserAddress;
+  ULONG ActiveProcessors;
+  UCHAR NumberProcessors;
+} SYSTEM_BASIC_INFORMATION, *PSYSTEM_BASIC_INFORMATION;
+
+typedef struct __attribute__ ((aligned (8))) _SYSTEM_PROCESSOR_TIMES
+{
+  LARGE_INTEGER IdleTime;
+  LARGE_INTEGER KernelTime;
+  LARGE_INTEGER UserTime;
+  LARGE_INTEGER DpcTime;
+  LARGE_INTEGER InterruptTime;
+  ULONG InterruptCount;
+} SYSTEM_PROCESSOR_TIMES, *PSYSTEM_PROCESSOR_TIMES;
+
+typedef NTSTATUS NTAPI (*NtQuerySystemInformation_t) (SYSTEM_INFORMATION_CLASS, PVOID, ULONG, PULONG);
+typedef ULONG NTAPI (*RtlNtStatusToDosError_t) (NTSTATUS);
+
+static NtQuerySystemInformation_t NtQuerySystemInformation;
+static RtlNtStatusToDosError_t RtlNtStatusToDosError;
+
+/*****************************************************************
+ *
+ LoadNtdll()
+ Load special functions from the NTDLL
+ Return TRUE if success.
+
+ *****************************************************************/
+
+static BOOL LoadNtdll()
+{
+  HINSTANCE hinstLib;
+
+  if ((hinstLib = LoadLibrary("NTDLL.DLL"))
+      && (NtQuerySystemInformation =
+          (NtQuerySystemInformation_t) GetProcAddress(hinstLib,
+                                                        "NtQuerySystemInformation"))
+      && (RtlNtStatusToDosError =
+          (RtlNtStatusToDosError_t) GetProcAddress(hinstLib,
+                                                     "RtlNtStatusToDosError")))
+    return TRUE;
+
+  DEBUG(D_load)
+    debug_printf("perf: load: %u (Windows)\n", GetLastError());
+  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)
+{
+  NTSTATUS ret;
+  SYSTEM_BASIC_INFORMATION sbi;
+  PSYSTEM_PROCESSOR_TIMES spt;
+
+  *Time100nsPtr = *IdleCountPtr = 0;
+
+  if ((ret = NtQuerySystemInformation(SystemBasicInformation,
+                                      (PVOID) &sbi, sizeof sbi, NULL))
+      != STATUS_SUCCESS) {
+    DEBUG(D_load)
+      debug_printf("Perf: NtQuerySystemInformation: %u (Windows)\n",
+                   RtlNtStatusToDosError(ret));
+  }
+  else if (!(spt = (PSYSTEM_PROCESSOR_TIMES) alloca(sizeof(spt[0]) * sbi.NumberProcessors))) {
+    DEBUG(D_load)
+      debug_printf("Perf: alloca: errno %d (%s)\n", errno, strerror(errno));
+  }
+  else if ((ret = NtQuerySystemInformation(SystemProcessorTimes, (PVOID) spt,
+                                           sizeof spt[0] * sbi.NumberProcessors, NULL))
+           != STATUS_SUCCESS) {
+    DEBUG(D_load)
+      debug_printf("Perf: NtQuerySystemInformation: %u (Windows)\n",
+                   RtlNtStatusToDosError(ret));
+  }
+  else {
+    int i;
+    for (i = 0; i < sbi.NumberProcessors; i++) {
+      *Time100nsPtr += spt[i].KernelTime.QuadPart;;
+      *Time100nsPtr += spt[i].UserTime.QuadPart;
+      *IdleCountPtr += spt[i].IdleTime.QuadPart;
+    }
+    return TRUE;
+  }
+  return FALSE;
+}
+
+/*****************************************************************
+ *
+ InitLoadAvg()
+ Initialize the cygwin_load.perf structure.
+ and set cygwin_load.perf->Flag to TRUE if successful.
+ This is called the first time os_getloadavg is called
+ *****************************************************************/
+static void InitLoadAvg(cygwin_perf_t *this)
+{
+  BOOL success = TRUE;
+
+  /* Get perf frequency and counter */
+  QueryPerformanceFrequency((LARGE_INTEGER *)& this->PerfFreq);
+  QueryPerformanceCounter((LARGE_INTEGER *)& this->LastCounter);
+
+  /* Get initial values for Time100ns and IdleCount */
+  success = success
+            && ReadStat( & this->Time100ns,
+                         & this->IdleCount);
+  /* If success, set the Load to 0, else to -1 */
+  if (success) this->LastLoad = 0;
+  else {
+    log_write(0, LOG_MAIN, "Cannot obtain Load Average");
+    this->LastLoad = -1;
+  }
+}
+
+
+/*****************************************************************
+ *
+ os_getloadavg()
+
+ Return -1 if not available;
+ Return the previous value if less than AVERAGING sec old.
+ else return the processor load on a [0 - 1000] scale.
+
+ The first time we are called we initialize the counts
+ and return 0 or -1.
+ The initial load cannot be measured as we use the processor 100%
+*****************************************************************/
+static SECURITY_ATTRIBUTES sa = {sizeof (SECURITY_ATTRIBUTES), NULL, TRUE};
+#define AVERAGING 10
+
+int os_getloadavg()
+{
+  unsigned long long Time100ns, IdleCount, CurrCounter;
+  int value;
+  pid_t newpid;
+
+  /* New process.
+     Reload the dlls and the file mapping */
+  if ((newpid = getpid()) != cygwin_load.pid) {
+    BOOL new;
+    cygwin_load.pid = newpid;
+
+    if (!LoadNtdll()) {
+      log_write(0, LOG_MAIN, "Cannot obtain Load Average");
+      cygwin_load.perf = NULL;
+      return -1;
+    }
+
+    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 %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 %p\n", (void *) cygwin_load.perf);
+    if (new && cygwin_load.perf)
+      InitLoadAvg(cygwin_load.perf);
+  }
+
+  /* Check if initialized OK */
+  if (!cygwin_load.perf || cygwin_load.perf->LastLoad < 0)
+    return -1;
+
+  /* If we cannot get the lock, we return 0.
+     This is to prevent any lock-up possibility.
+     Finding a lock busy is unlikely, and giving up only
+     results in an immediate delivery .*/
+
+  if (InterlockedCompareExchange(&cygwin_load.perf->Lock, 1, 0)) {
+    DEBUG(D_load)
+      debug_printf("Perf: Lock busy\n");
+    return 0;
+  }
+
+    /* Get the current time (PerfCounter) */
+    QueryPerformanceCounter((LARGE_INTEGER *)& CurrCounter);
+    /* Calls closer than AVERAGING sec apart use the previous value */
+  if (CurrCounter - cygwin_load.perf->LastCounter >
+      AVERAGING * cygwin_load.perf->PerfFreq) {
+      /* Get Time100ns and IdleCount */
+      if (ReadStat( & Time100ns, & IdleCount)) { /* Success */
+        /* Return processor load on 1000 scale */
+      value = 1000 - ((1000 * (IdleCount - cygwin_load.perf->IdleCount)) /
+                      (Time100ns - cygwin_load.perf->Time100ns));
+      cygwin_load.perf->Time100ns = Time100ns;
+      cygwin_load.perf->IdleCount = IdleCount;
+      cygwin_load.perf->LastCounter = CurrCounter;
+      cygwin_load.perf->LastLoad = value;
+      DEBUG(D_load)
+        debug_printf("Perf: New load average %d\n", value);
+      }
+      else { /* Something bad happened.
+                Refuse to measure the load anymore
+                but don't bother releasing the buffer */
+        log_write(0, LOG_MAIN, "Cannot obtain Load Average");
+      cygwin_load.perf->LastLoad = -1;
+    }
+  }
+  else
+  DEBUG(D_load)
+      debug_printf("Perf: Old load average %d\n", cygwin_load.perf->LastLoad);
+  cygwin_load.perf->Lock = 0;
+  return cygwin_load.perf->LastLoad;
+}
+#endif /* OS_LOAD_AVERAGE */
+#endif /* COMPILE_UTILITY */
similarity index 89%
rename from OS/os.h-AIX
rename to OS/unsupported/os.h-AIX
index f3a84f2..5cd4501 100644 (file)
@@ -20,4 +20,8 @@
 
 typedef struct flock flock_t;
 
 
 typedef struct flock flock_t;
 
+/* default is non-const */
+#define ICONV_ARG2_TYPE const char **
+
+
 /* End */
 /* End */
similarity index 72%
rename from OS/os.h-BSDI
rename to OS/unsupported/os.h-BSDI
index cd91936..a1705ec 100644 (file)
@@ -5,7 +5,11 @@
 #define HAVE_MMAP
 #define HAVE_SYS_MOUNT_H
 #define SIOCGIFCONF_GIVES_ADDR
 #define HAVE_MMAP
 #define HAVE_SYS_MOUNT_H
 #define SIOCGIFCONF_GIVES_ADDR
+#define OS_UNSETENV
 
 typedef struct flock flock_t;
 
 
 typedef struct flock flock_t;
 
+/* default is non-const */
+#define ICONV_ARG2_TYPE const char **
+
 /* End */
 /* End */
similarity index 92%
rename from OS/os.h-DGUX
rename to OS/unsupported/os.h-DGUX
index 838ddd9..9040f0e 100644 (file)
@@ -22,4 +22,7 @@ forego the detection of some source-routing based IP attacks. */
 
 #define NO_IP_OPTIONS
 
 
 #define NO_IP_OPTIONS
 
+/* default is non-const */
+#define ICONV_ARG2_TYPE const char **
+
 /* End */
 /* End */
similarity index 74%
rename from OS/os.h-DragonFly
rename to OS/unsupported/os.h-DragonFly
index 669bb23..4c2f1d5 100644 (file)
@@ -7,4 +7,7 @@
 
 typedef struct flock flock_t;
 
 
 typedef struct flock flock_t;
 
+/* default is non-const */
+#define ICONV_ARG2_TYPE const char **
+
 /* End */
 /* End */
similarity index 85%
rename from OS/os.h-GNU
rename to OS/unsupported/os.h-GNU
index cc4da0e..4499316 100644 (file)
@@ -17,4 +17,7 @@ typedef struct flock flock_t;
 
 /* Hurd-specific bits below */
 
 
 /* Hurd-specific bits below */
 
+/* default is non-const */
+#define ICONV_ARG2_TYPE const char **
+
 /* End */
 /* End */
similarity index 86%
rename from OS/os.h-GNUkFreeBSD
rename to OS/unsupported/os.h-GNUkFreeBSD
index e60690f..ab35031 100644 (file)
@@ -19,4 +19,7 @@ typedef struct flock flock_t;
 #define HAVE_SYS_MOUNT_H
 #define SIOCGIFCONF_GIVES_ADDR
 
 #define HAVE_SYS_MOUNT_H
 #define SIOCGIFCONF_GIVES_ADDR
 
+/* default is non-const */
+#define ICONV_ARG2_TYPE const char **
+
 /* End */
 /* End */
similarity index 86%
rename from OS/os.h-GNUkNetBSD
rename to OS/unsupported/os.h-GNUkNetBSD
index 121f2d3..bc3bc25 100644 (file)
@@ -19,4 +19,7 @@ typedef struct flock flock_t;
 #define HAVE_SYS_MOUNT_H
 #define SIOCGIFCONF_GIVES_ADDR
 
 #define HAVE_SYS_MOUNT_H
 #define SIOCGIFCONF_GIVES_ADDR
 
+/* default is non-const */
+#define ICONV_ARG2_TYPE const char **
+
 /* End */
 /* End */
similarity index 77%
rename from OS/os.h-HI-OSF
rename to OS/unsupported/os.h-HI-OSF
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 *
 
 #define F_FREESP               O_TRUNC
 #define DN_EXPAND_ARG4_TYPE    u_char *
 
+/* default is non-const */
+#define ICONV_ARG2_TYPE const char **
+
 /* End */
 /* End */
similarity index 86%
rename from OS/os.h-HI-UX
rename to OS/unsupported/os.h-HI-UX
index 97b83ed..f3df963 100644 (file)
@@ -15,4 +15,7 @@
 
 typedef struct flock flock_t;
 
 
 typedef struct flock flock_t;
 
+/* default is non-const */
+#define ICONV_ARG2_TYPE const char **
+
 /* End */
 /* End */
similarity index 84%
rename from OS/os.h-HP-UX
rename to OS/unsupported/os.h-HP-UX
index 87e4dfc..4998734 100644 (file)
@@ -1,6 +1,5 @@
 /* Exim: OS-specific C header file for HP-UX versions greater than 9 */
 
 /* 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
 #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)
 
 
 #define strtoll(a,b,c) strtoimax(a,b,c)
 
+/* Determined by sockaddr_un */
+
+struct sockaddr_storage
+{
+  short ss_family;
+  char __ss_padding[92];
+};
+
 /* End */
 /* End */
similarity index 87%
rename from OS/os.h-HP-UX-9
rename to OS/unsupported/os.h-HP-UX-9
index dab965e..5a260d6 100644 (file)
@@ -17,4 +17,7 @@
 
 typedef struct flock flock_t;
 
 
 typedef struct flock flock_t;
 
+/* default is non-const */
+#define ICONV_ARG2_TYPE const char **
+
 /* End */
 /* End */
similarity index 84%
rename from OS/os.h-IRIX
rename to OS/unsupported/os.h-IRIX
index ac5a6b3..1d4bf46 100644 (file)
@@ -14,7 +14,4 @@
 #define F_FAVAIL        f_favail
 #define vfork fork
 
 #define F_FAVAIL        f_favail
 #define vfork fork
 
-/* Other OS have "const" in here */
-#define ICONV_ARG2_TYPE char **
-
 /* End */
 /* End */
similarity index 83%
rename from OS/os.h-IRIX6
rename to OS/unsupported/os.h-IRIX6
index c41a234..bf30767 100644 (file)
@@ -13,7 +13,4 @@
 #define F_FAVAIL        f_favail
 #define vfork fork
 
 #define F_FAVAIL        f_favail
 #define vfork fork
 
-/* Other OS have "const" in here */
-#define ICONV_ARG2_TYPE char **
-
 /* End */
 /* End */
similarity index 84%
rename from OS/os.h-IRIX632
rename to OS/unsupported/os.h-IRIX632
index 0196931..90f1c58 100644 (file)
@@ -15,7 +15,4 @@
 #define F_FAVAIL        f_favail
 #define vfork fork
 
 #define F_FAVAIL        f_favail
 #define vfork fork
 
-/* Other OS have "const" in here */
-#define ICONV_ARG2_TYPE char **
-
 /* End */
 /* End */
similarity index 83%
rename from OS/os.h-IRIX65
rename to OS/unsupported/os.h-IRIX65
index 683c66a..4b248fe 100644 (file)
@@ -13,7 +13,4 @@
 #define F_FAVAIL        f_favail
 #define vfork fork
 
 #define F_FAVAIL        f_favail
 #define vfork fork
 
-/* Other OS have "const" in here */
-#define ICONV_ARG2_TYPE char **
-
 /* End */
 /* End */
similarity index 88%
rename from OS/os.h-NetBSD
rename to OS/unsupported/os.h-NetBSD
index 19a8ac0..d2d3e0d 100644 (file)
@@ -22,4 +22,7 @@ typedef struct flock flock_t;
 #define HAVE_SYS_STATVFS_H
 #endif
 
 #define HAVE_SYS_STATVFS_H
 #endif
 
+/* default is non-const */
+#define ICONV_ARG2_TYPE const char **
+
 /* End */
 /* End */
similarity index 82%
rename from OS/os.h-OSF1
rename to OS/unsupported/os.h-OSF1
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
 
 /* 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 */
 /* End */
similarity index 83%
rename from OS/os.h-OpenUNIX
rename to OS/unsupported/os.h-OpenUNIX
index 90be8d5..67d1063 100644 (file)
@@ -13,4 +13,7 @@
 #define _SVID3
 #define NEED_H_ERRNO
 
 #define _SVID3
 #define NEED_H_ERRNO
 
+/* default is non-const */
+#define ICONV_ARG2_TYPE const char **
+
 /* End */
 /* End */
similarity index 86%
rename from OS/os.h-QNX
rename to OS/unsupported/os.h-QNX
index 106b0a6..798f799 100644 (file)
@@ -18,4 +18,7 @@ doesn't have/need this header file. From Karsten P. Hoffmann. */
 
 extern int h_errno;
 
 
 extern int h_errno;
 
+/* default is non-const */
+#define ICONV_ARG2_TYPE const char **
+
 /* End */
 /* End */
similarity index 85%
rename from OS/os.h-SCO
rename to OS/unsupported/os.h-SCO
index 07d21bd..e5e915e 100644 (file)
@@ -15,4 +15,7 @@
 #define _SVID3
 #define NEED_H_ERRNO
 
 #define _SVID3
 #define NEED_H_ERRNO
 
+/* default is non-const */
+#define ICONV_ARG2_TYPE const char **
+
 /* End */
 /* End */
similarity index 84%
rename from OS/os.h-SCO_SV
rename to OS/unsupported/os.h-SCO_SV
index 486fcbe..0ca29f7 100644 (file)
@@ -13,4 +13,7 @@
 #define _SVID3
 #define NEED_H_ERRNO
 
 #define _SVID3
 #define NEED_H_ERRNO
 
+/* default is non-const */
+#define ICONV_ARG2_TYPE const char **
+
 /* End */
 /* End */
similarity index 93%
rename from OS/os.h-SunOS4
rename to OS/unsupported/os.h-SunOS4
index b0deefc..6555620 100644 (file)
@@ -33,4 +33,7 @@ flag causes this to get done in exim.h. */
 
 #define FUDGE_GETC_AND_FRIENDS
 
 
 #define FUDGE_GETC_AND_FRIENDS
 
+/* default is non-const */
+#define ICONV_ARG2_TYPE const char **
+
 /* End */
 /* End */
similarity index 80%
rename from OS/os.h-SunOS5-hal
rename to OS/unsupported/os.h-SunOS5-hal
index 044e09b..cd9e877 100644 (file)
@@ -8,4 +8,7 @@
 #define LOAD_AVG_SYMBOL       "avenrun_1min"
 #define LOAD_AVG_FIELD         value.ul
 
 #define LOAD_AVG_SYMBOL       "avenrun_1min"
 #define LOAD_AVG_FIELD         value.ul
 
+/* default is non-const */
+#define ICONV_ARG2_TYPE const char **
+
 /* End */
 /* End */
similarity index 87%
rename from OS/os.h-ULTRIX
rename to OS/unsupported/os.h-ULTRIX
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;
 
 #define NO_OPENLOG
 typedef struct flock flock_t;
 
+/* default is non-const */
+#define ICONV_ARG2_TYPE const char **
+
 /* End */
 /* End */
similarity index 89%
rename from OS/os.h-UNIX_SV
rename to OS/unsupported/os.h-UNIX_SV
index 9ad824a..4943a07 100644 (file)
@@ -19,4 +19,7 @@
 #define _SVID3
 #define NEED_H_ERRNO
 
 #define _SVID3
 #define NEED_H_ERRNO
 
+/* default is non-const */
+#define ICONV_ARG2_TYPE const char **
+
 /* End */
 /* End */
similarity index 83%
rename from OS/os.h-USG
rename to OS/unsupported/os.h-USG
index 1c780ee..e769220 100644 (file)
@@ -13,4 +13,7 @@
 #define _SVID3
 #define NEED_H_ERRNO
 
 #define _SVID3
 #define NEED_H_ERRNO
 
+/* default is non-const */
+#define ICONV_ARG2_TYPE const char **
+
 /* End */
 /* End */
similarity index 91%
rename from OS/os.h-Unixware7
rename to OS/unsupported/os.h-Unixware7
index 1592094..4d3ed42 100644 (file)
@@ -2,7 +2,6 @@
 
 #define NO_SYSEXITS
 
 
 #define NO_SYSEXITS
 
-#define ICONV_ARG2_TYPE char **
 #define EXIM_SOCKLEN_T size_t
 
 #define LOAD_AVG_NEEDS_ROOT
 #define EXIM_SOCKLEN_T size_t
 
 #define LOAD_AVG_NEEDS_ROOT
similarity index 61%
rename from OS/os.h-cygwin
rename to OS/unsupported/os.h-cygwin
index 740300a..6ef59e0 100644 (file)
@@ -1,19 +1,7 @@
 /* Exim: OS-specific C header file for Cygwin */
 
 /* 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 */
 
 /* 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
 
 #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
 #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 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 */
 /* 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}}
 
   DWORD SubAuthority[n]; \
 } name = { SID_REVISION, n, {SECURITY_NT_AUTHORITY}, {sid}}
 
+/* default is non-const */
+#define ICONV_ARG2_TYPE const char **
+
 /* End */
 /* End */
similarity index 88%
rename from OS/os.h-mips
rename to OS/unsupported/os.h-mips
index 79f3ff2..325e3a1 100644 (file)
@@ -21,4 +21,7 @@ extern char *strerror(int);
 extern int   sys_nerr;
 extern char *sys_errlist[];
 
 extern int   sys_nerr;
 extern char *sys_errlist[];
 
+/* default is non-const */
+#define ICONV_ARG2_TYPE const char **
+
 /* End */
 /* End */
diff --git a/README b/README
index 652cee6..225295e 100644 (file)
--- a/README
+++ b/README
@@ -1,7 +1,7 @@
 THE EXIM MAIL TRANSFER AGENT VERSION 4
 --------------------------------------
 
 THE EXIM MAIL TRANSFER AGENT VERSION 4
 --------------------------------------
 
-Copyright (c) 1995 - 2012 University of Cambridge.
+Copyright (c) 1995 - 2018 University of Cambridge.
 See the file NOTICE for conditions of use and distribution.
 
 There is a book about Exim by Philip Hazel called "The Exim SMTP Mail Server",
 See the file NOTICE for conditions of use and distribution.
 
 There is a book about Exim by Philip Hazel called "The Exim SMTP Mail Server",
@@ -14,7 +14,7 @@ from Exim 3, though the basic structure and philosophy remains the same. The
 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.
 
 older book may be helpful for the background, but a lot of the detail has
 changed, so it is likely to be confusing to newcomers.
 
-There is a web site at http://www.exim.org; this contains details of the
+There is a website at https://www.exim.org; this contains details of the
 mailing list exim-users@exim.org.
 
 A copy of the Exim FAQ should be available from the same source that you used
 mailing list exim-users@exim.org.
 
 A copy of the Exim FAQ should be available from the same source that you used
index 68d1641..d700dd0 100644 (file)
@@ -113,7 +113,7 @@ ChangeLog
 
                 *) dsn_process switch removed
                   *) every router "processes" DSN by default
 
                 *) 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 
                 *) 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..b619f5e 100644 (file)
@@ -26,11 +26,73 @@ The rest of this document contains information about changes in 4.xx releases
 that might affect a running system.
 
 
 that might affect a running system.
 
 
+Exim version 4.92
+-----------------
+
+ * Exim used to manually follow CNAME chains, to a limited depth.  In this
+   day-and-age we expect the resolver to be doing this for us, so the loop
+   is limited to one retry unless the (new) config option dns_cname_loops
+   is changed.
+
+Exim version 4.91
+-----------------
+
+ * DANE and SPF have been promoted from Experimental to Supported status, thus
+   the options to enable them in Local/Makefile have been renamed.
+   See current src/EDITME for full details, including changes in dependencies,
+   but loosely: replace EXPERIMENTAL_SPF with SUPPORT_SPF and replace
+   EXPERIMENTAL_DANE with SUPPORT_DANE.
+
+ * Ancient ClamAV stream support, long deprecated by ClamAV, has been removed;
+   if you were building with WITH_OLD_CLAMAV_STREAM enabled then your problems
+   have marginally increased.
+
+ * A number of logging changes; if relying upon the previous DKIM additional
+   log-line, explicit log_selector configuration is needed to keep it.
+
+ * Other incompatible changes in EXPERIMENTAL_* features, read NewStuff and
+   ChangeLog carefully if relying upon an experimental feature such as DMARC.
+   Note that this includes changes to SPF as it was promoted into Supported.
+
+
+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
 -----------------
 
  * SPF condition results renamed "permerror" and "temperror".  The old
 Exim version 4.83
 -----------------
 
  * SPF condition results renamed "permerror" and "temperror".  The old
-   names are still accepted for back-compatability, for this release.
+   names are still accepted for back-compatibility, for this release.
 
  * TLS details are now logged on rejects, subject to log selectors.
 
 
  * TLS details are now logged on rejects, subject to log selectors.
 
@@ -71,7 +133,7 @@ Exim version 4.80
    upgrading, then lock the message, replace the new-lines that should be part
    of the -tls_peerdn line with the two-character sequence \n and then unlock
    the message.  No tool has been provided as we believe this is a rare
    upgrading, then lock the message, replace the new-lines that should be part
    of the -tls_peerdn line with the two-character sequence \n and then unlock
    the message.  No tool has been provided as we believe this is a rare
-   occurence.
+   occurrence.
 
  * For OpenSSL, SSLv2 is now disabled by default.  (GnuTLS does not support
    SSLv2).  RFC 6176 prohibits SSLv2 and some informal surveys suggest no
 
  * For OpenSSL, SSLv2 is now disabled by default.  (GnuTLS does not support
    SSLv2).  RFC 6176 prohibits SSLv2 and some informal surveys suggest no
@@ -284,7 +346,7 @@ Exim version 4.70
 -----------------
 
 1. Experimental Yahoo! Domainkeys support has been dropped in this release.
 -----------------
 
 1. Experimental Yahoo! Domainkeys support has been dropped in this release.
-It has been superceded by a native implementation of its successor DKIM.
+It has been superseded by a native implementation of its successor DKIM.
 
 2. Up to version 4.69, Exim came with an embedded version of the PCRE library.
 As of 4.70, this is no longer the case. To compile Exim, you will need PCRE
 
 2. Up to version 4.69, Exim came with an embedded version of the PCRE library.
 As of 4.70, this is no longer the case. To compile Exim, you will need PCRE
@@ -530,7 +592,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
 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.)
 
    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 3c8f592..7da07ad 100644 (file)
 Change log file for Exim from version 4.21
 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.84.2
--------------------
-Portability release
 
 
+Exim version 4.92
+-----------------
+
+JH/01 Remove code calling the customisable local_scan function, unless a new
+      definition "HAVE_LOCAL_SCAN=yes" is present in the Local/Makefile.
+
+JH/02 Bug 1007: Avoid doing logging from signal-handlers, as that can result in
+      non-signal-safe functions being used.
+
+JH/03 Bug 2269: When presented with a received message having a stupidly large
+      number of DKIM-Signature headers, disable DKIM verification to avoid
+      a resource-consumption attack.  The limit is set at twenty.
+
+JH/04 Add variables $arc_domains, $arc_oldest_pass for ARC verify.  Fix the
+      report of oldest_pass in ${authres } in consequence, and separate out
+      some descriptions of reasons for verification fail.
+
+JH/05 Bug 2273: Cutthrough delivery left a window where the received messsage
+      files in the spool were present and unlocked.  A queue-runner could spot
+      them, resulting in a duplicate delivery.  Fix that by doing the unlock
+      after the unlink.  Investigation by Tim Stewart.  Take the opportunity to
+      add more error-checking on spoolfile handling while that code is being
+      messed with.
+
+PP/01 Refuse to open a spool data file (*-D) if it's a symlink.
+      No known attacks, no CVE, this is defensive hardening.
+
+JH/06 Bug 2275: The MIME ACL unlocked the received message files early, and
+      a queue-runner could start a delivery while other operations were ongoing.
+      Cutthrough delivery was a common victim, resulting in duplicate delivery.
+      Found and investigated by Tim Stewart.  Fix by using the open message data
+      file handle rather than opening another, and not locally closing it (which
+      releases a lock) for that case, while creating the temporary .eml format
+      file for the MIME ACL.  Also applies to "regex" and "spam" ACL conditions.
+
+JH/07 Bug 177: Make a random-recipient callout success visible in ACL, by setting
+      $sender_verify_failure/$recipient_verify_failure to "random".
+
+JH/08 When generating a selfsigned cert, use serial number 1 since zero is not
+      legitimate.
+
+JH/09 Bug 2274: Fix logging of cmdline args when starting in an unlinked cwd.
+      Previously this would segfault.
+
+JH/10 Fix ARC signing for case when DKIM signing failed.  Previously this would
+      segfault.
+
+JH/11 Bug 2264: Exim now only follows CNAME chains one step by default. We'd
+      like zero, since the resolver should be doing this for us, But we need one
+      as a CNAME but no MX presence gets the CNAME returned; we need to check
+      that doesn't point to an MX to declare it "no MX returned" rather than
+      "error, loop".  A new main option is added so the older capability of
+      following some limited number of chain links is maintained.
+
+JH/12 Add client-ip info to non-pass iprev ${authres } lines.
+
+JH/13 For receent Openssl versions (1.1 onward) use modern generic protocol
+      methods.  These should support TLS 1.3; they arrived with TLS 1.3 and the
+      now-deprecated earlier definitions used only specified the range up to TLS
+      1.2 (in the older-version library docs).
+
+JH/14 Bug 2284: Fix DKIM signing for body lines starting with a pair of dots.
+
+JH/15 Rework TLS client-side context management.  Stop using a global, and
+      explicitly pass a context around.  This enables future use of TLS for
+      connections to service-daemons (eg. malware scanning) while a client smtp
+      connection is using TLS; with cutthrough connections this is quite likely.
+
+JH/16 Fix ARC verification to do AS checks in reverse order.
+
+JH/17 Support a "tls" option on the ${readsocket } expansion item.
+
+JH/18 Bug 2287: Fix the protocol name (eg utf8esmtp) for multiple messages
+      using the SMTPUTF8 option on their MAIL FROM commands, in one connection.
+      Previously the "utf8" would be re-prepended for every additional message.
+
+JH/19 Reject MAIL FROM commands with SMTPUTF8 when the facility was not advertised.
+      Previously thery were accepted, resulting in issues when attempting to
+      forward messages to a non-supporting MTA.
+
+PP/02 Let -n work with printing macros too, not just options.
+
+JH/20 Bug 2296: Fix cutthrough for >1 address redirection.  Previously only
+      one parent address was copied, and bogus data was used at delivery-logging
+      time.  Either a crash (after delivery) or bogus log data could result.
+      Discovery and analysis by Tim Stewart.
+
+PP/03 Make ${utf8clean:} expansion operator detect incomplete final character.
+      Previously if the string ended mid-character, we did not insert the
+      promised '?' replacement.
+
+PP/04 Documentation: current string operators work on bytes, not codepoints.
+
+JH/21 Change as many as possible of the global flags into one-bit bitfields; these
+      should pack well giving a smaller memory footprint so better caching and
+      therefore performance.  Group the declarations where this can't be done so
+      that the byte-sized flag variables are not interspersed among pointer
+      variables, giving a better chance of good packing by the compiler.
+
+JH/22 Bug 1896: Fix the envelope from for DMARC forensic reports to be possibly
+      non-null, to avoid issues with sites running BATV.  Previously reports were
+      sent with an empty envelope sender so looked like bounces.
+
+JH/23 Bug 2318: Fix the noerror command within filters.  It wasn't working.
+      The ignore_error flag wasn't being returned from the filter subprocess so
+      was not set for later routers.  Investigation and fix by Matthias Kurz.
+
+JH/24 Bug 2310: Raise a msg:fail:internal event for each undelivered recipient,
+      and a msg:complete for the whole, when a message is manually removed using
+      -Mrm.  Developement by Matthias Kurz, hacked on by JH.
+
+JH/25 Avoid fixed-size buffers for pathnames in DB access.  This required using
+      a "Gnu special" function, asprintf() in the DB utility binary builds; I
+      hope that is portable enough.
+
+JH/26 Bug 2311: Fix DANE-TA verification under GnuTLS.  Previously it was also
+      requiring a known-CA anchor certificate; make it now rely entirely on the
+      TLSA as an anchor.  Checking the name on the leaf cert against the name
+      on the A-record for the host is still done for TA (but not for EE mode).
+
+JH/27 Fix logging of proxy address.  Previously, a pointless "PRX=[]:0" would be
+      included in delivery lines for non-proxied connections, when compiled with
+      SUPPORT_SOCKS and running with proxy logging enabled.
+
+JH/28 Bug 2314: Fire msg:fail:delivery event even when error is being ignored.
+      Developement by Matthias Kurz, tweaked by JH.  While in that bit of code,
+      move the existing event to fire before the normal logging of message
+      failure so that custom logging is bracketed by normal logging.
+
+JH/29 Bug 2322: A "fail" command in a non-system filter (file) now fires the
+      msg:fail:internal event.  Developement by Matthias Kurz.
+
+JH/30 Bug 2329: Increase buffer size used for dns lookup from 2k, which was
+      far too small for todays use of crypto signatures stored there.  Go all
+      the way to the max DNS message size of 64kB, even though this might be
+      overmuch for IOT constrained device use.
+
+JH/31 Fix a bad use of a copy function, which could be used to pointlessly
+      copy a string over itself.  The library routine is documented as not
+      supporting overlapping copies, and on MacOS it actually raised a SIGABRT.
+
+JH/32 For main options check_spool_space and check_inode_space, where the
+      platform supports 64b integers, support more than the previous 2^31 kB
+      (i.e. more than 2 TB).  Accept E, P and T multipliers in addition to
+      the previous G, M, k.
+
+JH/33 Bug 2338: Fix the cyrus-sasl authenticator to fill in the
+      $authenticated_fail_id variable on authentication failure.  Previously
+      it was unset.
+
+JH/34 Increase RSA keysize of autogen selfsign cert from 1024 to 2048.  RHEL 8.0
+      OpenSSL didn't want to use such a weak key.  Do for GnuTLS also, and for
+      more-modern GnuTLS move from GNUTLS_SEC_PARAM_LOW to
+      GNUTLS_SEC_PARAM_MEDIUM.
+
+JH/35 OpenSSL: fail the handshake when SNI processing hits a problem, server
+      side.  Previously we would continue as if no SNI had been received.
+
+JH/36 Harden the handling of string-lists.  When a list consisted of a sole
+      "<" character, which should be a list-separator specification, we walked
+      off past the nul-terimation.
+
+JH/37 Bug 2341: Send "message delayed" warning MDNs (restricted to external
+      causes) even when the retry time is not yet met.  Previously they were
+      not, meaning that when (say) an account was over-quota and temp-rejecting,
+      and multiple senders' messages were queued, only one sender would get
+      notified on each configured delay_warning cycle.
+
+JH/38 Bug 2351: Log failures to extract envelope addresses from message headers.
+
+JH/39 OpenSSL: clear the error stack after an SSL_accept().  With anon-auth
+      cipher-suites, an error can be left on the stack even for a succeeding
+      accept; this results in impossible error messages when a later operation
+      actually does fail.
+
+AM/01 Bug 2359: GnuTLS: repeat lowlevel read and write operations while they
+      return error codes indicating retry.  Under TLS1.3 this becomes required.
+
+JH/40 Fix the feature-cache refresh for EXPERIMENTAL_PIPE_CONNECT.  Previously
+      it only wrote the new authenticators, resulting in a lack of tracking of
+      peer changes of ESMTP extensions until the next cache flush.
+
+JH/41 Fix the loop reading a message header line to check for integer overflow,
+      and more-often against header_maxsize.  Previously a crafted message could
+      induce a crash of the recive process; now the message is cleanly rejected.
+
+JH/42 Bug 2366: Fix the behaviour of the dkim_verify_signers option.  It had
+      been totally disabled for all of 4.91.  Discovery and fix by "Mad Alex".
+
+
+Exim version 4.91
+-----------------
+
+GF/01 DEFER rather than ERROR on redis cluster MOVED response.
+      When redis_servers is set to a list of > 1 element, and the Redis servers
+      in that list are in cluster configuration, convert the REDIS_REPLY_ERROR
+      case of MOVED into a DEFER case instead, thus moving the query onto the
+      next server in the list. For a cluster of N elements, all N servers must
+      be defined in redis_servers.
+
+GF/02 Catch and remove uninitialized value warning in exiqsumm
+      Check for existence of @ARGV before looking at $ARGV[0]
+
+JH/01 Replace the store_release() internal interface with store_newblock(),
+      which internalises the check required to safely use the old one, plus
+      the allocate and data copy operations duplicated in both (!) of the
+      extant use locations.
+
+JH/02 Disallow '/' characters in queue names specified for the "queue=" ACL
+      modifier.  This matches the restriction on the commandline.
+
+JH/03 Fix pgsql lookup for multiple result-tuples with a single column.
+      Previously only the last row was returned.
+
+JH/04 Bug 2217: Tighten up the parsing of DKIM signature headers. Previously
+      we assumed that tags in the header were well-formed, and parsed the
+      element content after inspecting only the first char of the tag.
+      Assumptions at that stage could crash the receive process on malformed
+      input.
+
+JH/05 Bug 2215: Fix crash associated with dnsdb lookup done from DKIM ACL.
+      While running the DKIM ACL we operate on the Permanent memory pool so that
+      variables created with "set" persist to the DATA ACL.  Also (at any time)
+      DNS lookups that fail create cache records using the Permanent pool.  But
+      expansions release any allocations made on the current pool - so a dnsdb
+      lookup expansion done in the DKIM ACL releases the memory used for the
+      DNS negative-cache, and bad things result.  Solution is to switch to the
+      Main pool for expansions.
+      While we're in that code, add checks on the DNS cache during store_reset,
+      active in the testsuite.
+      Problem spotted, and debugging aided, by Wolfgang Breyha.
+
+JH/06 Fix issue with continued-connections when the DNS shifts unreliably.
+      When none of the hosts presented to a transport match an already-open
+      connection, close it and proceed with the list.  Previously we would
+      queue the message.  Spotted by Lena with Yahoo, probably involving
+      round-robin DNS.
+
+JH/07 Bug 2214: Fix SMTP responses resulting from non-accept result of MIME ACL.
+      Previously a spurious "250 OK id=" response was appended to the proper
+      failure response.
+
+JH/08 The "support for" informational output now, which built with Content
+      Scanning support, has a line for the malware scanner interfaces compiled
+      in.  Interface can be individually included or not at build time.
+
+JH/09 The "aveserver", "kavdaemon" and "mksd" interfaces are now not included
+      by the template makefile "src/EDITME".  The "STREAM" support for an older
+      ClamAV interface method is removed.
+
+JH/10 Bug 2223: Fix mysql lookup returns for the no-data case (when the number of
+      rows affected is given instead).
+
+JH/11 The runtime Berkeley DB library version is now additionally output by
+      "exim -d -bV".  Previously only the compile-time version was shown.
+
+JH/12 Bug 2230: Fix cutthrough routing for nonfirst messages in an initiating
+      SMTP connection.  Previously, when one had more receipients than the
+      first, an abortive onward connection was made.  Move to full support for
+      multiple onward connections in sequence, handling cutthrough connection
+      for all multi-message initiating connections.
+
+JH/13 Bug 2229: Fix cutthrough routing for nonstandard port numbers defined by
+      routers.  Previously, a multi-recipient message would fail to match the
+      onward-connection opened for the first recipient, and cause its closure.
+
+JH/14 Bug 2174: A timeout on connect for a callout was also erroneously seen as
+      a timeout on read on a GnuTLS initiating connection, resulting in the
+      initiating connection being dropped.  This mattered most when the callout
+      was marked defer_ok.  Fix to keep the two timeout-detection methods
+      separate.
+
+JH/15 Relax results from ACL control request to enable cutthrough, in
+      unsupported situations, from error to silently (except under debug)
+      ignoring.  This covers use with PRDR, frozen messages, queue-only and
+      fake-reject.
+
+HS/01 Fix Buffer overflow in base64d() (CVE-2018-6789)
+
+JH/16 Fix bug in DKIM verify: a buffer overflow could corrupt the malloc
+      metadata, resulting in a crash in free().
+
+PP/01 Fix broken Heimdal GSSAPI authenticator integration.
+      Broken in f2ed27cf5, missing an equals sign for specified-initialisers.
+      Broken also in d185889f4, with init system revamp.
+
+JH/17 Bug 2113: Fix conversation closedown with the Avast malware scanner.
+      Previously we abruptly closed the connection after reading a malware-
+      found indication; now we go on to read the "scan ok" response line,
+      and send a quit.
+
+JH/18 Bug 2239: Enforce non-usability of control=utf8_downconvert in the mail
+      ACL.  Previously, a crash would result.
+
+JH/19 Speed up macro lookups during configuration file read, by skipping non-
+      macro text after a replacement (previously it was only once per line) and
+      by skipping builtin macros when searching for an uppercase lead character.
+
+JH/20 DANE support moved from Experimental to mainline.  The Makefile control
+      for the build is renamed.
+
+JH/21 Fix memory leak during multi-message connections using STARTTLS.  A buffer
+      was allocated for every new TLS startup, meaning one per message.  Fix
+      by only allocating once (OpenSSL) or freeing on TLS-close (GnuTLS).
+
+JH/22 Bug 2236: When a DKIM verification result is overridden by ACL, DMARC
+      reported the original.  Fix to report (as far as possible) the ACL
+      result replacing the original.
+
+JH/23 Fix memory leak during multi-message connections using STARTTLS under
+      OpenSSL.  Certificate information is loaded for every new TLS startup,
+      and the resources needed to be freed.
+
+JH/24 Bug 2242: Fix exim_dbmbuild to permit directoryless filenames.
+
+JH/25 Fix utf8_downconvert propagation through a redirect router.  Previously it
+      was not propagated.
+
+JH/26 Bug 2253: For logging delivery lines under PRDR, append the overall
+      DATA response info to the (existing) per-recipient response info for
+      the "C=" log element.  It can have useful tracking info from the
+      destination system.  Patch from Simon Arlott.
+
+JH/27 Bug 2251: Fix ldap lookups that return a single attribute having zero-
+      length value.  Previously this would segfault.
+
+HS/02 Support Avast multiline protoocol, this allows passing flags to
+      newer versions of the scanner.
+
+JH/28 Ensure that variables possibly set during message acceptance are marked
+      dead before release of memory in the daemon loop.  This stops complaints
+      about them when the debug_store option is enabled.  Discovered specifically
+      for sender_rate_period, but applies to a whole set of variables.
+      Do the same for the queue-runner and queue-list loops, for variables set
+      from spool message files.  Do the same for the SMTP per-message loop, for
+      certain variables indirectly set in ACL operations.
+
+JH/29 Bug 2250: Fix a longstanding bug in heavily-pipelined SMTP input (such
+      as a multi-recipient message from a mailinglist manager).  The coding had
+      an arbitrary cutoff number of characters while checking for more input;
+      enforced by writing a NUL into the buffer.  This corrupted long / fast
+      input.   The problem was exposed more widely when more pipelineing of SMTP
+      responses was introduced, and one Exim system was feeding another.
+      The symptom is log complaints of SMTP syntax error (NUL chars) on the
+      receiving system, and refused recipients seen by the sending system
+      (propating to people being dropped from mailing lists).
+      Discovered and pinpointed by David Carter.
+
+JH/30 The (EXPERIMENTAL_DMARC) variable $dmarc_ar_header is withdrawn, being
+      replaced by the ${authresults } expansion.
+
+JH/31 Bug 2257: Fix pipe transport to not use a socket-only syscall.
+
+HS/03 Set a handler for SIGTERM and call exit(3) if running as PID 1. This
+      allows proper process termination in container environments.
+
+JH/32 Bug 2258: Fix spool_wireformat in combination with LMTP transport.
+      Previously the "final dot" had a newline after it; ensure it is CR,LF.
+
+JH/33 SPF: remove support for the "spf" ACL condition outcome values "err_temp"
+      and "err_perm", deprecated since 4.83 when the RFC-defined words
+      "temperror" and "permerror" were introduced.
+
+JH/34 Re-introduce enforcement of no cutthrough delivery on transports having
+      transport-filters or DKIM-signing.  The restriction was lost in the
+      consolidation of verify-callout and delivery SMTP handling.
+      Extend the restriction to also cover ARC-signing.
+
+JH/35 Cutthrough: for a final-dot response timeout (and nonunderstood responses)
+      in defer=pass mode supply a 450 to the initiator.  Previously the message
+      would be spooled.
+
+PP/02 DANE: add dane_require_tls_ciphers SMTP Transport option; if unset,
+      tls_require_ciphers is used as before.
+
+HS/03 Malware Avast: Better match the Avast multiline protocol. Add
+      "pass_unscanned".  Only tmpfails from the scanner are written to
+      the paniclog, as they may require admin intervention (permission
+      denied, license issues). Other scanner errors (like decompression
+      bombs) do not cause a paniclog entry.
+
+JH/36 Fix reinitialisation of DKIM logging variable between messages.
+      Previously it was possible to log spurious information in receive log
+      lines.
+
+JH/37 Bug 2255: Revert the disable of the OpenSSL session caching.  This
+      triggered odd behaviour from Outlook Express clients.
+
+PP/03 Add util/renew-opendmarc-tlds.sh script for safe renewal of public
+      suffix list.
+
+JH/38 DKIM: accept Ed25519 pubkeys in SubjectPublicKeyInfo-wrapped form,
+      since the IETF WG has not yet settled on that versus the original
+      "bare" representation.
+
+JH/39 Fix syslog logging for syslog_timestamp=no and log_selector +millisec.
+      Previously the millisecond value corrupted the output.
+      Fix also for syslog_pid=no and log_selector +pid, for which the pid
+      corrupted the output.
+
+
+Exim version 4.90
+-----------------
+
+JH/01 Rework error string handling in TLS interface so that the caller in
+      more cases is responsible for logging.  This permits library-sourced
+      string to be attached to addresses during delivery, and collapses
+      pairs of long lines into single ones.
+
+PP/01 Allow PKG_CONFIG_PATH to be set in Local/Makefile and use it correctly
+      during configuration.  Wildcards are allowed and expanded.
+
+JH/02 Rework error string handling in DKIM to pass more info back to callers.
+      This permits better logging.
+
+JH/03 Rework the transport continued-connection mechanism: when TLS is active,
+      do not close it down and have the child transport start it up again on
+      the passed-on TCP connection.  Instead, proxy the child (and any
+      subsequent ones) for TLS via a unix-domain socket channel.  Logging is
+      affected: the continued delivery log lines do not have any DNSSEC, TLS
+      Certificate or OCSP information.  TLS cipher information is still logged.
+
+JH/04 Shorten the log line for daemon startup by collapsing adjacent sets of
+      identical IP addresses on different listening ports.  Will also affect
+      "exiwhat" output.
+
+PP/02 Bug 2070: uClibc defines __GLIBC__ without providing glibc headers;
+      add noisy ifdef guards to special-case this sillyness.
+      Patch from Bernd Kuhls.
+
+JH/05 Tighten up the checking in isip4 (et al): dotted-quad components larger
+      than 255 are no longer allowed.
+
+JH/06 Default openssl_options to include +no_ticket, to reduce load on peers.
+      Disable the session-cache too, which might reduce our load.  Since we
+      currrectly use a new context for every connection, both as server and
+      client, there is no benefit for these.
+      GnuTLS appears to not support tickets server-side by default (we don't
+      call gnutls_session_ticket_enable_server()) but client side is enabled
+      by default on recent versions (3.1.3 +) unless the PFS priority string
+      is used (3.2.4 +).
+
+PP/03 Add $SOURCE_DATE_EPOCH support for reproducible builds, per spec at
+      <https://reproducible-builds.org/specs/source-date-epoch/>.
+
+JH/07 Fix smtp transport use of limited max_rcpt under mua_wrapper. Previously
+      the check for any unsuccessful recipients did not notice the limit, and
+      erroneously found still-pending ones.
+
+JH/08 Pipeline CHUNKING command and data together, on kernels that support
+      MSG_MORE.  Only in-clear (not on TLS connections).
+
+JH/09 Avoid using a temporary file during transport using dkim.  Unless a
+      transport-filter is involved we can buffer the headers in memory for
+      creating the signature, and read the spool data file once for the
+      signature and again for transmission.
+
+JH/10 Enable use of sendfile in Linux builds as default.  It was disabled in
+      4.77 as the kernel support then wasn't solid, having issues in 64bit
+      mode.  Now, it's been long enough.  Add support for FreeBSD also.
+
+JH/11 Bug 2104: Fix continued use of a transport connection with TLS.  In the
+      case where the routing stage had gathered several addresses to send to
+      a host before calling the transport for the first, we previously failed
+      to close down TLS in the old transport process before passing the TCP
+      connection to the new process.  The new one sent a STARTTLS command
+      which naturally failed, giving a failed delivery and bloating the retry
+      database.  Investigation and fix prototype from Wolfgang Breyha.
+
+JH/12 Fix check on SMTP command input synchronisation.  Previously there were
+      false-negatives in the check that the sender had not preempted a response
+      or prompt from Exim (running as a server), due to that code's lack of
+      awareness of the SMTP input buffering.
+
+PP/04 Add commandline_checks_require_admin option.
+      Exim drops privileges sanely, various checks such as -be aren't a
+      security problem, as long as you trust local users with access to their
+      own account.  When invoked by services which pass untrusted data to
+      Exim, this might be an issue.  Set this option in main configuration
+      AND make fixes to the calling application, such as using `--` to stop
+      processing options.
+
+JH/13 Do pipelining under TLS.  Previously, although safe, no advantage was
+      taken.  Now take care to pack both (client) MAIL,RCPT,DATA, and (server)
+      responses to those, into a single TLS record each way (this usually means
+      a single packet).  As a side issue, smtp_enforce_sync now works on TLS
+      connections.
+
+PP/05 OpenSSL/1.1: use DH_bits() for more accurate DH param sizes.  This
+      affects you only if you're dancing at the edge of the param size limits.
+      If you are, and this message makes sense to you, then: raise the
+      configured limit or use OpenSSL 1.1.  Nothing we can do for older
+      versions.
+
+JH/14 For the "sock" variant of the malware scanner interface, accept an empty
+      cmdline element to get the documented default one.  Previously it was
+      inaccessible.
+
+JH/15 Fix a crash in the smtp transport caused when two hosts in succession
+      are unsuable for non-message-specific reasons - eg. connection timeout,
+      banner-time rejection.
+
+JH/16 Fix logging of delivery remote port, when specified by router, under
+      callout/hold.
+
+PP/06 Repair manualroute's ability to take options in any order, even if one
+      is the name of a transport.
+      Fixes bug 2140.
+
+HS/01 Cleanup, prevent repeated use of -p/-oMr (CVE-2017-1000369)
+
+JH/17 Change the list-building routines interface to use the expanding-string
+      triplet model, for better allocation and copying behaviour.
+
+JH/18 Prebuild the data-structure for "builtin" macros, for faster startup.
+      Previously it was constructed the first time a possibly-matching string
+      was met in the configuration file input during startup; now it is done
+      during compilation.
+
+JH/19 Bug 2141: Use the full-complex API for Berkeley DB rather than the legacy-
+      compatible one, to avoid the (poorly documented) possibility of a config
+      file in the working directory redirecting the DB files, possibly correpting
+      some existing file.  CVE-2017-10140 assigned for BDB.
+
+JH/20 Bug 2147: Do not defer for a verify-with-callout-and-random which is not
+      cache-hot.  Previously, although the result was properly cached, the
+      initial verify call returned a defer.
+
+JH/21 Bug 2151: Avoid using SIZE on the MAIL for a callout verify, on any but
+      the main verify for receipient in uncached-mode.
+
+JH/22 Retire historical build files to an "unsupported" subdir.  These are
+      defined as "ones for which we have no current evidence of testing".
+
+JH/23 DKIM: enforce the DNS pubkey record "h" permitted-hashes optional field,
+      if present.  Previously it was ignored.
+
+JH/24 Start using specified-initialisers in C structure init coding.  This is
+      a C99 feature (it's 2017, so now considered safe).
+
+JH/25 Use one-bit bitfields for flags in the "addr" data structure.  Previously
+      if was a fixed-sized field and bitmask ops via macros; it is now more
+      extensible.
+
+PP/07 GitHub PR 56: Apply MariaDB build fix.
+      Patch provided by Jaroslav Škarvada.
+
+PP/08 Bug 2161: Fix regression in sieve quoted-printable handling introduced
+      during Coverity cleanups [4.87 JH/47]
+      Diagnosis and fix provided by Michael Fischer v. Mollard.
+
+JH/26 Fix DKIM bug: when the pseudoheader generated for signing was exactly
+      the right size to place the terminating semicolon on its own folded
+      line, the header hash was calculated to an incorrect value thanks to
+      the (relaxed) space the fold became.
+
+HS/02 Fix Bug 2130: large writes from the transport subprocess were chunked
+      and confused the parent.
+
+JH/27 Fix SOCKS bug: an unitialized pointer was deref'd by the transport process
+      which could crash as a result.  This could lead to undeliverable messages.
+
+JH/28 Logging: "next input sent too soon" now shows where input was truncated
+      for log purposes.
+
+JH/29 Fix queue_run_in_order to ignore the PID portion of the message ID.  This
+      matters on fast-turnover and PID-randomising systems, which were getting
+      out-of-order delivery.
+
+JH/30 Fix a logging bug on aarch64: an unsafe routine was previously used for
+      a possibly-overlapping copy.  The symptom was that "Remote host closed
+      connection in response to HELO" was logged instead of the actual 4xx
+      error for the HELO.
+
+JH/31 Fix CHUNKING code to properly flush the unwanted chunk after an error.
+      Previously only that bufferd was discarded, resulting in SYMTP command
+      desynchronisation.
+
+JH/32 DKIM: when a message has multiple signatures matching an identity given
+      in dkim_verify_signers, run the dkim acl once for each.  Previously only
+      one run was done.  Bug 2189.
+
+JH/33 Downgrade an unfound-list name (usually a typo in the config file) from
+      "panic the current process" to "deliberately defer".  The panic log is
+      still written with the problem list name; the mail and reject logs now
+      get a temp-reject line for the message that was being handled, saying
+      something like "domains check lookup or other defer".  The SMTP 451
+      message is still "Temporary local problem".
+
+JH/34 Bug 2199: Fix a use-after-free while reading smtp input for header lines.
+      A crafted sequence of BDAT commands could result in in-use memory beeing
+      freed.  CVE-2017-16943.
+
+HS/03 Bug 2201: Fix checking for leading-dot on a line during headers reading
+      from SMTP input.  Previously it was always done; now only done for DATA
+      and not BDAT commands.  CVE-2017-16944.
+
+JH/35 Bug 2201: Flush received data in BDAT mode after detecting an error fatal
+      to the message (such as an overlong header line).  Previously this was
+      not done and we did not exit BDAT mode.  Followon from the previous item
+      though a different problem.
+
+
+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.
 
 
-Exim version 4.84.1
--------------------
 HS/04 Add support for keep_environment and add_environment options.
 HS/04 Add support for keep_environment and add_environment options.
-      Fix for CVE-2016-1531
+
+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
 
 
 Exim version 4.84
@@ -19,12 +1285,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.
       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
 
 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
 
 
 Exim version 4.83
@@ -350,7 +1616,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.
       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".
       on the smtp transport, and the server advertisement.
       Default client logging of deliveries and rejections involving
       PRDR are flagged with the string "PRDR".
@@ -378,7 +1644,7 @@ PP/20 Added force_command boolean option to pipe transport.
 JH/15 AUTH support on callouts (and hence cutthrough-deliveries).
       Bugzilla 321, 823.
 
 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
 
 PP/21 Fix eximon continuous updating with timestamped log-files.
       Broken in a format-string cleanup in 4.80, missed when I repaired the
@@ -497,7 +1763,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
       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.
       Patch from Dirk Mueller <dmueller@suse.com>
 
 PP/13 tls_peerdn now print-escaped for spool files.
@@ -522,7 +1788,7 @@ PP/15 LDAP: Check for errors of TLS initialisation, to give correct
       diagnostics.
       Report and patch from Dmitry Banschikov.
 
       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.
 
       Removed SSL_clear() after SSL_new() which led to protocol negotiation
       failures.  We appear to now support TLS1.1+ with Exim.
 
@@ -652,7 +1918,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
       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
 
       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
@@ -787,7 +2053,7 @@ PP/09 Fix another SIGFPE (x86) in ${eval:...} expansion, this time related to
 Exim version 4.75
 -----------------
 
 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.
       Bugzilla 1073
 
 TF/01 Update valgrind.h and memcheck.h to copies from valgrind-3.6.0.
@@ -857,7 +2123,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.
 
       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.
 
 
 PP/16 Make DISABLE_DKIM build knob functional.
 
@@ -1282,7 +2548,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.
 
       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.
 
       ordinary lsearch keys. However, making the change in all cases is
       incompatible and would probably break a number of configurations.
 
@@ -1452,7 +2718,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
 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,
       other ACL rejections are.
 
 PH/22 Applied Jori Hamalainen's speed-up changes and typo fixes to exigrep,
@@ -1859,7 +3125,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
       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
       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
@@ -1928,7 +3194,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
 
 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
 
 JJ/04 exipick.20061117.2, fixed format and added $message_headers_raw to
       match 4.64-PH/13
@@ -2250,7 +3516,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
 
       (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
 
       (b) The queryprogram router detects when it is not running as root,
           outputs suitable debugging information if -d is set, and then runs
@@ -2610,7 +3876,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
       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
          result)
 
 PH/10 Exim was violating RFC 3834 ("Recommendations for Automatic Responses to
@@ -2619,7 +3885,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
         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:
       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:
@@ -2984,7 +4250,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
 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.
 
       scanned for macro replacements. I have been disabused of this notion,
       so now the code just undefines EX_OK before #including unistd.h.
 
@@ -3004,7 +4270,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/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.
 
 
 SC/03 Eximstats: allow any combination of xls, txt, and html output.
 
@@ -5029,7 +6295,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.
 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.
 
     entire SMTP session, which doesn't seem right. The memory is now reset if a
     new message is started.
 
@@ -5119,7 +6385,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
     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
     incident is still logged to the main and panic logs.
 
 74. Buglet left over from Exim 3 conversion. The message "too many messages
@@ -5183,7 +6449,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
     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
 
  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
@@ -5235,7 +6501,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
 
 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.
 
     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.
 
@@ -5316,7 +6582,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
 
 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
 
 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
@@ -5761,7 +7027,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.
 
 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.
     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.
@@ -5795,7 +7061,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,
 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
     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
@@ -5908,7 +7174,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
 
 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
     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
 
 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
 
 
 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
 . 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.
 
 
   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,
 
 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.
 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:
 
 
 . 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
 
   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 8970875..ab4e5aa 100644 (file)
@@ -6,7 +6,7 @@ Using Exim 4.80+ with GnuTLS
 (3) I'm seeing:
     "(gnutls_handshake): A TLS packet with unexpected length was received"
     Why?
 (3) I'm seeing:
     "(gnutls_handshake): A TLS packet with unexpected length was received"
     Why?
-(4) What's the deal with MD5?
+(4) What's the deal with MD5?  (And SHA-1?)
 (5) What happened to gnutls_require_kx / gnutls_require_mac /
     gnutls_require_protocols?
 (6) What's the deal with tls_dh_max_bits?  What's DH?
 (5) What happened to gnutls_require_kx / gnutls_require_mac /
     gnutls_require_protocols?
 (6) What's the deal with tls_dh_max_bits?  What's DH?
@@ -89,8 +89,8 @@ option fixes the problem, this was the cause.  See Q6.
 
 
 
 
 
 
-(4): What's the deal with MD5?
-------------------------------
+(4): What's the deal with MD5?  (And SHA-1?)
+--------------------------------------------
 
 MD5 is a hash algorithm.  Hash algorithms are used to reduce a lot of data
 down to a fairly short value, which is supposed to be extremely hard to
 
 MD5 is a hash algorithm.  Hash algorithms are used to reduce a lot of data
 down to a fairly short value, which is supposed to be extremely hard to
@@ -119,6 +119,10 @@ the ongoing costs of proving a trust relationship, such as providing
 revocation protocols.  This is just another of those ongoing costs you have
 already paid for.
 
 revocation protocols.  This is just another of those ongoing costs you have
 already paid for.
 
+The same has happened to SHA-1: there are real-world collision attacks against
+SHA-1, so SHA-1 is mostly defunct in certificates.  GnuTLS no longer supports
+its use in TLS certificates.
+
 
 
 (5): ... gnutls_require_kx / gnutls_require_mac / gnutls_require_protocols?
 
 
 (5): ... gnutls_require_kx / gnutls_require_mac / gnutls_require_protocols?
index 1b7ad35..c3c69eb 100644 (file)
@@ -3,9 +3,313 @@ 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
 
 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.
 
 the documentation is updated, this file is reduced to a short list.
 
+Version 4.92
+--------------
+
+ 1. ${l_header:<name>} and ${l_h:<name>} expansion items, giving a colon-sep
+    list when there are multiple headers having a given name.  This matters
+    when individual headers are wrapped onto multiple lines; with previous
+    facilities hard to parse.
+
+ 2. The ${readsocket } expansion item now takes a "tls" option, doing the
+    obvious thing.
+
+ 3. EXPERIMENTAL_REQUIRETLS and EXPERIMENTAL_PIPE_CONNECT optional build
+    features.  See the experimental.spec file.
+
+ 4. If built with SUPPORT_I18N a "utf8_downconvert" option on the smtp transport.
+
+ 5. A "pipelining" log_selector.
+
+ 6. Builtin macros for supported log_selector and openssl_options values.
+
+ 7. JSON variants of the ${extract } expansion item.
+
+ 8. A "noutf8" debug option, for disabling the UTF-8 characters in debug output.
+
+ 9. TCP Fast Open support on MacOS.
+
+Version 4.91
+--------------
+
+ 1. Dual-certificate stacks on servers now support OCSP stapling, under GnuTLS
+    version 3.5.6 or later.
+
+ 2. DANE is now supported under GnuTLS version 3.0.0 or later.  Both GnuTLS and
+    OpenSSL versions are moved to mainline support from Experimental.
+    New SMTP transport option "dane_require_tls_ciphers".
+
+ 3. Feature macros for the compiled-in set of malware scanner interfaces.
+
+ 4. SPF support is promoted from Experimental to mainline status.  The template
+    src/EDITME makefile does not enable its inclusion.
+
+ 5. Logging control for DKIM verification.  The existing DKIM log line is
+    controlled by a "dkim_verbose" selector which is _not_ enabled by default.
+    A new tag "DKIM=<domain>" is added to <= lines by default, controlled by
+    a "dkim" log_selector.
+
+ 6. Receive duration on <= lines, under a new log_selector "receive_time".
+
+ 7. Options "ipv4_only" and "ipv4_prefer" on the dnslookup router and on
+    routing rules in the manualroute router.
+
+ 8. Expansion item ${sha3:<string>} / ${sha3_<N>:<string>} now also supported
+    under OpenSSL version 1.1.1 or later.
+
+ 9. DKIM operations can now use the Ed25519 algorithm in addition to RSA, under
+    GnuTLS 3.6.0 or OpenSSL 1.1.1 or later.
+
+10. Builtin feature-macros _CRYPTO_HASH_SHA3 and _CRYPTO_SIGN_ED25519, library
+    version dependent.
+
+11. "exim -bP macro <name>" returns caller-usable status.
+
+12. Expansion item ${authresults {<machine>}} for creating an
+    Authentication-Results: header.
+
+13. EXPERIMENTAL_ARC.  See the experimental.spec file.
+    See also new util/renew-opendmarc-tlds.sh script for use with DMARC/ARC.
+
+14: A dane:fail event, intended to facilitate reporting.
+
+15. "Lightweight" support for Redis Cluster. Requires redis_servers list to
+    contain all the servers in the cluster, all of which must be reachable from
+    the running exim instance. If the cluster has master/slave replication, the
+    list must contain all the master and slave servers.
+
+16. Add an option to the Avast scanner interface: "pass_unscanned". This
+    allows to treat unscanned files as clean. Files may be unscanned for
+    several reasons: decompression bombs, broken archives.
+
+
+Version 4.90
+------------
+
+ 1. PKG_CONFIG_PATH can now be set in Local/Makefile;
+    wildcards will be expanded, values are collapsed.
+
+ 2. The ${readsocket } expansion now takes an option to not shutdown the
+    connection after sending the query string.  The default remains to do so.
+
+ 3. An smtp transport option "hosts_noproxy_tls" to control whether multiple
+    deliveries on a single TCP connection can maintain a TLS connection
+    open.  By default disabled for all hosts, doing so saves the cost of
+    making new TLS sessions, at the cost of having to proxy the data via
+    another process.  Logging is also affected.
+
+ 4. A malware connection type for the FPSCAND protocol.
+
+ 5. An option for recipient verify callouts to hold the connection open for
+    further recipients and for delivery.
+
+ 6. The reproducible build $SOURCE_DATE_EPOCH environment variable is now
+    supported.
+
+ 7. Optionally, an alternate format for spool data-files which matches the
+    wire format - meaning more efficient reception and transmission (at the
+    cost of difficulty with standard Unix tools).  Only used for messages
+    received using the ESMTP CHUNKING option, and when a new main-section
+    option "spool_wireformat" (false by default) is set.
+
+ 8. New main configuration option "commandline_checks_require_admin" to
+    restrict who can use various introspection options.
+
+ 9. New option modifier "no_check" for quota and quota_filecount
+    appendfile transport.
+
+10. Variable $smtp_command_history returning a comma-sep list of recent
+    SMTP commands.
+
+11. Millisecond timetamps in logs, on log_selector "millisec".  Also affects
+    log elements QT, DT and D, and timstamps in debug output.
+
+12. TCP Fast Open logging.  As a server, logs when the SMTP banner was sent
+    while still in SYN_RECV state; as a client logs when the connection
+    is opened with a TFO cookie.
+
+13. DKIM support for multiple signing, by domain and/or key-selector.
+    DKIM support for multiple hashes, and for alternate-identity tags.
+    Builtin macro with default list of signed headers.
+    Better syntax for specifying oversigning.
+    The DKIM ACL can override verification status, and status is visible in
+    the data ACL.
+
+14. Exipick understands -C|--config for an alternative Exim
+    configuration file.
+
+15. TCP Fast Open used, with data-on-SYN, for client SMTP via SOCKS5 proxy,
+    for ${readsocket } expansions, and for ClamAV.
+
+16. The "-be" expansion test mode now supports macros.  Macros are expanded
+    in test lines, and new macros can be defined.
+
+17. Support for server-side dual-certificate-stacks (eg. RSA + ECDSA).
+
+
+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
 ------------
 
 Version 4.84
 ------------
 
@@ -28,7 +332,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
 
  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
     be included in the command line.
 
  5. The smtp transport now supports options "tls_verify_hosts" and
@@ -57,7 +361,7 @@ Version 4.83
 12. OCSP stapling is now supported by default.
 
 13. If built with the EXPERIMENTAL_DSN feature enabled, Exim will output
 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.
 
 
     DSN features per RFC 3461.
 
 
@@ -112,20 +416,20 @@ Version 4.82
     ignored.
 
  7. New cutthrough routing feature.  Requested by a "control = cutthrough_delivery"
     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
     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.
 
     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 "=>".
     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 +483,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
     "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
 
 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
     require privilege whereas -d does.
 
 18. If built with EXPERIMENTAL_PRDR, per-recipient data responses per a
@@ -204,13 +508,14 @@ Version 4.82
     It adds new expansion variables $dmarc_ar_header, $dmarc_status,
     $dmarc_status_text, and $dmarc_used_domain.  It adds a new acl modifier
     dmarc_status.  It adds new control flags dmarc_disable_verify and
     It adds new expansion variables $dmarc_ar_header, $dmarc_status,
     $dmarc_status_text, and $dmarc_used_domain.  It adds a new acl modifier
     dmarc_status.  It adds new control flags dmarc_disable_verify and
-    dmarc_enable_forensic.
+    dmarc_enable_forensic. The default for the dmarc_tld_file option is
+    "/etc/exim/opendmarc.tlds" and can be changed via EDITME.
 
 22. Add expansion variable $authenticated_fail_id, which is the username
     provided to the authentication method which failed.  It is available
     for use in subsequent ACL processing (typically quit or notquit ACLs).
 
 
 22. Add expansion variable $authenticated_fail_id, which is the username
     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
     UDP host and port.
 
 24. New ${hexquote:..string..} expansion operator converts non-printable
@@ -645,7 +950,7 @@ Version 4.68
     longest line that was received as part of the message, not counting the
     line termination character(s).
 
     longest line that was received as part of the message, not counting the
     line termination character(s).
 
- 7. Host lists can now include +ignore_defer and +include_defer, analagous to
+ 7. Host lists can now include +ignore_defer and +include_defer, analogous to
     +ignore_unknown and +include_unknown. These options should be used with
     care, probably only in non-critical host lists such as whitelists.
 
     +ignore_unknown and +include_unknown. These options should be used with
     care, probably only in non-critical host lists such as whitelists.
 
index ef61956..0ef35f5 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_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, 4.83 unless disable_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
 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
@@ -82,6 +82,7 @@ allow_localhost                      boolean         false         smtp
 allow_mx_to_ip                       boolean         false         main              3.14
 allow_symlink                        boolean         false         appendfile
 allow_utf8_domains                   boolean         false         main              4.14
 allow_mx_to_ip                       boolean         false         main              3.14
 allow_symlink                        boolean         false         appendfile
 allow_utf8_domains                   boolean         false         main              4.14
+arc_sign                            string*         unset         smtp              4.91 with Experimental_ARC
 auth_advertise_hosts                 host list       "*"           main              4.00
 authenticated_sender                 string*         unset         smtp              4.14
 authenticated_sender_force           boolean         false         smtp              4.61
 auth_advertise_hosts                 host list       "*"           main              4.00
 authenticated_sender                 string*         unset         smtp              4.14
 authenticated_sender_force           boolean         false         smtp              4.61
@@ -137,6 +138,7 @@ command                              string*         unset         lmtp
 command_group                        string          unset         queryprogram      4.00
 command_timeout                      time            5m            smtp
 command_user                         string          unset         queryprogram      4.00
 command_group                        string          unset         queryprogram      4.00
 command_timeout                      time            5m            smtp
 command_user                         string          unset         queryprogram      4.00
+commandline_checks_require_admin     boolean         false         main              4.90
 condition                            string*         unset         routers           4.00
 connect_timeout                      time            0s            smtp              1.60
 connection_max_messages              integer         500           smtp              4.00 replaces batch_max
 condition                            string*         unset         routers           4.00
 connect_timeout                      time            0s            smtp              1.60
 connection_max_messages              integer         500           smtp              4.00 replaces batch_max
@@ -147,11 +149,13 @@ current_directory                    string          unset         transports
 daemon_smtp_ports                    string          unset         main              1.75  pluralised in 4.21
 daemon_startup_retries               int             9             main              4.52
 daemon_startup_sleep                 time            30s           main              4.52
 daemon_smtp_ports                    string          unset         main              1.75  pluralised in 4.21
 daemon_startup_retries               int             9             main              4.52
 daemon_startup_sleep                 time            30s           main              4.52
+dane_require_tls_ciphers             string*         unset         smtp              4.91
 data                                 string          unset         redirect          4.00
 data_timeout                         time            5m            smtp
 debug_print                          string*         unset         authenticators    4.00
                                                      unset         routers           4.00
                                                      unset         transports        2.00
 data                                 string          unset         redirect          4.00
 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
 delay_after_cutoff                   boolean         true          smtp
 delay_warning                        time list       24h           main
 delay_warning_condition              string*         +             main              1.73
@@ -165,6 +169,7 @@ dkim_private_key                     string*         unset         smtp
 dkim_selector                        string*         unset         smtp              4.70
 dkim_sign_headers                    string*         (RFC4871)     smtp              4.70
 dkim_strict                          string*         unset         smtp              4.70
 dkim_selector                        string*         unset         smtp              4.70
 dkim_sign_headers                    string*         (RFC4871)     smtp              4.70
 dkim_strict                          string*         unset         smtp              4.70
+dkim_timestamps                      integer*        unset         smtp              4.92
 dkim_verify_signers                  string*         $dkim_signers main              4.70
 directory                            string*         unset         appendfile
 directory_file                       string*         +             appendfile
 dkim_verify_signers                  string*         $dkim_signers main              4.70
 directory                            string*         unset         appendfile
 directory_file                       string*         +             appendfile
@@ -178,9 +183,11 @@ dmarc_history_file                   string          unset         main
 dmarc_tld_file                       string          unset         main              4.82 if experimental_dmarc
 dns_again_means_nonexist             domain list     unset         main              1.89
 dns_check_names_pattern              string          +             main              2.11
 dmarc_tld_file                       string          unset         main              4.82 if experimental_dmarc
 dns_again_means_nonexist             domain list     unset         main              1.89
 dns_check_names_pattern              string          +             main              2.11
+dns_cname_loops                      integer         0             main              4.92 Set to 9 for older behaviour
 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_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
 dns_ipv4_lookup                      boolean         false         main              3.20
 dns_qualify_single                   boolean         true          smtp
 dns_retrans                          time            0s            main              1.60
@@ -291,14 +298,19 @@ hosts_connection_nolog               host list       unset         main
 hosts_max_try                        integer         5             smtp              3.20
 hosts_max_try_hardlimit              integer         50            smtp              4.50
 hosts_nopass_tls                     host list       unset         smtp              4.00
 hosts_max_try                        integer         5             smtp              3.20
 hosts_max_try_hardlimit              integer         50            smtp              4.50
 hosts_nopass_tls                     host list       unset         smtp              4.00
+hosts_noproxy_tls                    host list       "*"           smtp              4.90
 hosts_override                       boolean         false         smtp              2.11
 hosts_override                       boolean         false         smtp              2.11
+hosts_pipe_connect                  host_list       unset         smtp              4.93 if experimental_pipe_connect
 hosts_randomize                      boolean         false         manualroute       4.00
                                                      false         smtp              3.14
 hosts_require_auth                   host list       unset         smtp              4.00
 hosts_randomize                      boolean         false         manualroute       4.00
                                                      false         smtp              3.14
 hosts_require_auth                   host list       unset         smtp              4.00
+hosts_require_dane                   host list       unset         smtp              4.91 (4.85 experimental)
 hosts_require_ocsp                   host list       unset         smtp              4.82 if experimental_ocsp
 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_require_ocsp                   host list       unset         smtp              4.82 if experimental_ocsp
 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_dane                       host list       unset         smtp              4.91 (4.85 experimental)
+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
 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
@@ -401,6 +413,7 @@ pid_file_path                        string          ++            main
 pipe_as_creator                      boolean         false         pipe
 pipe_transport                       string*         unset         redirect          4.00
 pipelining_advertise_hosts           host list       "*"           main              4.14
 pipe_as_creator                      boolean         false         pipe
 pipe_transport                       string*         unset         redirect          4.00
 pipelining_advertise_hosts           host list       "*"           main              4.14
+pipelining__connect_advertise_hosts  host list       "*"           main              4.92 if experimental_pipe_connect
 port                                 integer         0             iplookup          4.00
                                      string          "smtp"        smtp
 preserve_message_logs                boolean         false         main
 port                                 integer         0             iplookup          4.00
                                      string          "smtp"        smtp
 preserve_message_logs                boolean         false         main
@@ -485,6 +498,9 @@ server_keytab                        string*         unset         heimdal_gssap
 server_mail_auth_condition           string*         unset         authenticators    3.22
 server_mech                          string          public_name   cyrus_sasl,gsasl  4.43 (cyrus-only) 4.80 (others)
 server_password                      string          unset         gsasl             4.80
 server_mail_auth_condition           string*         unset         authenticators    3.22
 server_mech                          string          public_name   cyrus_sasl,gsasl  4.43 (cyrus-only) 4.80 (others)
 server_password                      string          unset         gsasl             4.80
+server_param1                       string*         unset         tls (auth)        4.86
+server_param2                       string*         unset         tls (auth)        4.86
+server_param3                       string*         unset         tls (auth)        4.86
 server_prompts                       string*         unset         plaintext         3.10
 server_realm                         string          unset         cyrus_sasl,gsasl  4.43 (cyrus-only) 4.80 (others)
 server_scram_iter                    string*         unset         gsasl             4.80
 server_prompts                       string*         unset         plaintext         3.10
 server_realm                         string          unset         cyrus_sasl,gsasl  4.43 (cyrus-only) 4.80 (others)
 server_scram_iter                    string*         unset         gsasl             4.80
@@ -525,6 +541,7 @@ socket                               string*         unset         lmtp
 spamd_address                        string*         +             main              4.50 with content scan
 split_spool_directory                boolean         false         main              1.70
 spool_directory                      string          ++            main
 spamd_address                        string*         +             main              4.50 with content scan
 split_spool_directory                boolean         false         main              1.70
 spool_directory                      string          ++            main
+spool_wireformat                     boolean         false         main              4.90
 sqlite_lock_timeout                  time            5s            main              4.53
 strict_acl_vars                      boolean         false         main              4.64
 srv_fail_domains                     domain list     unset         dnslookup         4.43
 sqlite_lock_timeout                  time            5s            main              4.53
 strict_acl_vars                      boolean         false         main              4.64
 srv_fail_domains                     domain list     unset         dnslookup         4.43
@@ -557,6 +574,7 @@ timeout_defer                        boolean         false         pipe
 timeout_frozen_after                 time            0s            main              3.20
 timezone                             string          +             main              3.15
 tls_advertise_hosts                  host list       *             main              3.20
 timeout_frozen_after                 time            0s            main              3.20
 timezone                             string          +             main              3.15
 tls_advertise_hosts                  host list       *             main              3.20
+tls_advertise_requiretls             host list       *             main              4.92 if experimental_requiretls
 tls_certificate                      string*         unset         main              3.20
                                                      unset         smtp              3.20
 tls_dh_max_bits                      integer         2236          main              4.80
 tls_certificate                      string*         unset         main              3.20
                                                      unset         smtp              3.20
 tls_dh_max_bits                      integer         2236          main              4.80
@@ -600,6 +618,7 @@ use_mbx_lock                         boolean         +             appendfile
 use_shell                            boolean         false         pipe              1.70
 user                                 string          +             routers           4.00
                                                      unset         transports        4.00 replaces individual options
 use_shell                            boolean         false         pipe              1.70
 user                                 string          +             routers           4.00
                                                      unset         transports        4.00 replaces individual options
+utf8_downconvert                     integer         unset         smtp              4.92 if SUPPORT_I18N
 uucp_from_pattern                    string          +             main              1.75
 uucp_from_sender                     string*         "$1"          main              1.75
 verify                               boolean         true          routers           4.00
 uucp_from_pattern                    string          +             main              1.75
 uucp_from_sender                     string*         "$1"          main              1.75
 verify                               boolean         true          routers           4.00
@@ -868,7 +887,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
 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
 EXIWHAT_MULTIKILL_CMD        system**
 EXIWHAT_MULTIKILL_ARG        system**
 EXIWHAT_PS_ARG               system**     to list all processes
@@ -967,7 +986,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
 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
 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
 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).
 
 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..7df044e 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
 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.)
 
 appropriate Local/Makefile-xxx file. (If you are compiling for just one OS, it
 can go in Local/Makefile itself.)
 
@@ -223,7 +223,7 @@ files in other formats that are created by other programs.
 Berkeley DB 4.x
 ---------------
 
 Berkeley DB 4.x
 ---------------
 
-The 4.x series is a developement of the 2.x and 3.x series, and the above
+The 4.x series is a development of the 2.x and 3.x series, and the above
 comments apply.
 
 
 comments apply.
 
 
index 09dad3c..566fe23 100644 (file)
@@ -159,17 +159,22 @@ function, which provides extensive line\-editing facilities, for reading the
 test data. A line history is supported.
 .sp
 Long expansion expressions can be split over several lines by using backslash
 test data. A line history is supported.
 .sp
 Long expansion expressions can be split over several lines by using backslash
-continuations. As in Exim's run time configuration, white space at the start of
+continuations. As in Exim's runtime 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
 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
 files or databases you are using, you must exit and restart Exim before trying
 the same lookup again. Otherwise, because each Exim process caches the results
 of lookups, you will just get the same result as before.
 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
 files or databases you are using, you must exit and restart Exim before trying
 the same lookup again. Otherwise, because each Exim process caches the results
 of lookups, you will just get the same result as before.
+.sp
+Macro processing is done on lines before string\-expansion: new macros can be
+defined and macros will be expanded.
+Because macros in the config file are often used for secrets, those are only
+available to admin users.
 .TP 10
 \fB\-bem\fP <\fIfilename\fP>
 This option operates like \fB\-be\fP except that it must be followed by the name
 .TP 10
 \fB\-bem\fP <\fIfilename\fP>
 This option operates like \fB\-be\fP except that it must be followed by the name
@@ -370,7 +375,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>
 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
 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 +425,12 @@ users, the output is as in this example:
 .sp
   mysql_servers = <value not displayable>
 .sp
 .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 runtime
+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
 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
@@ -454,13 +464,15 @@ 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
 \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 supresses the value of the
+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.
 The output format is one item per line.
 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.
 The output format is one item per line.
+For the "\-bP macro <name>" form, if no such macro is found
+the exit status will be nonzero.
 .TP 10
 \fB\-bp\fP
 This option requests a listing of the contents of the mail queue on the
 .TP 10
 \fB\-bp\fP
 This option requests a listing of the contents of the mail queue on the
@@ -469,13 +481,13 @@ just those messages are listed. By default, this option can be used only by an
 admin user. However, the \fBqueue_list_requires_admin\fP option can be set false
 to allow any user to see the queue.
 .sp
 admin user. However, the \fBqueue_list_requires_admin\fP option can be set false
 to allow any user to see the queue.
 .sp
-Each message on the queue is displayed as in the following example:
+Each message in the queue is displayed as in the following example:
 .sp
   25m  2.9K 0t5C6f\-0000c8\-00 <alice@wonderland.fict.example>
             red.king@looking\-glass.fict.example
             <other addresses>
 .sp
 .sp
   25m  2.9K 0t5C6f\-0000c8\-00 <alice@wonderland.fict.example>
             red.king@looking\-glass.fict.example
             <other addresses>
 .sp
-The first line contains the length of time the message has been on the queue
+The first line contains the length of time the message has been in the queue
 (in this case 25 minutes), the size of the message (2.9K), the unique local
 identifier for the message, and the message sender, as contained in the
 envelope. For bounce messages, the sender address is empty, and appears as
 (in this case 25 minutes), the size of the message (2.9K), the unique local
 identifier for the message, and the message sender, as contained in the
 envelope. For bounce messages, the sender address is empty, and appears as
@@ -500,14 +512,14 @@ alias or forwarding operations. These addresses are flagged with "+D" instead
 of just "D".
 .TP 10
 \fB\-bpc\fP
 of just "D".
 .TP 10
 \fB\-bpc\fP
-This option counts the number of messages on the queue, and writes the total
+This option counts the number of messages in the queue, and writes the total
 to the standard output. It is restricted to admin users, unless
 \fBqueue_list_requires_admin\fP is set false.
 .TP 10
 \fB\-bpr\fP
 This option operates like \fB\-bp\fP, but the output is not sorted into
 chronological order of message arrival. This can speed it up when there are
 to the standard output. It is restricted to admin users, unless
 \fBqueue_list_requires_admin\fP is set false.
 .TP 10
 \fB\-bpr\fP
 This option operates like \fB\-bp\fP, but the output is not sorted into
 chronological order of message arrival. This can speed it up when there are
-lots of messages on the queue, and is particularly useful if the output is
+lots of messages in the queue, and is particularly useful if the output is
 going to be post\-processed in a way that doesn't need the sorting.
 .TP 10
 \fB\-bpra\fP
 going to be post\-processed in a way that doesn't need the sorting.
 .TP 10
 \fB\-bpra\fP
@@ -646,7 +658,7 @@ This option causes Exim to write the current version number, compilation
 number, and compilation date of the \fIexim\fP binary to the standard output.
 It also lists the DBM library that is being used, the optional modules (such as
 specific lookup types), the drivers that are included in the binary, and the
 number, and compilation date of the \fIexim\fP binary to the standard output.
 It also lists the DBM library that is being used, the optional modules (such as
 specific lookup types), the drivers that are included in the binary, and the
-name of the run time configuration file that is in use.
+name of the runtime configuration file that is in use.
 .sp
 As part of its operation, \fB\-bV\fP causes Exim to read and syntax check its
 configuration file. However, this is a static check only. It cannot check
 .sp
 As part of its operation, \fB\-bV\fP causes Exim to read and syntax check its
 configuration file. However, this is a static check only. It cannot check
@@ -721,15 +733,13 @@ If the option is given as \fB\-bw\fP<\fItime\fP> then the time is a timeout, aft
 which the daemon will exit, which should cause inetd to listen once more.
 .TP 10
 \fB\-C\fP <\fIfilelist\fP>
 which the daemon will exit, which should cause inetd to listen once more.
 .TP 10
 \fB\-C\fP <\fIfilelist\fP>
-This option causes Exim to find the run time configuration file from the given
+This option causes Exim to find the runtime configuration file from the given
 list instead of from the list specified by the CONFIGURE_FILE
 list instead of from the list specified by the CONFIGURE_FILE
-compile\-time setting. Usually, the list will consist of just a single file
-name, but it can be a colon\-separated list of names. In this case, the first
+compile\-time setting. Usually, the list will consist of just a single filename,
+but it can be a colon\-separated list of names. In this case, the first
 file that exists is used. Failure to open an existing file stops Exim from
 proceeding any further along the list, and an error is generated.
 .sp
 file that exists is used. Failure to open an existing file stops Exim from
 proceeding any further along the list, and an error is generated.
 .sp
-The file names need to be absolute names.
-.sp
 When this option is used by a caller other than root, and the list is different
 from the compiled\-in list, Exim gives up its root privilege immediately, and
 runs with the real and effective uid and gid set to those of the caller.
 When this option is used by a caller other than root, and the list is different
 from the compiled\-in list, Exim gives up its root privilege immediately, and
 runs with the real and effective uid and gid set to those of the caller.
@@ -746,15 +756,15 @@ even if the caller is root. The reception works, but by that time, Exim is
 running as the Exim user, so when it re\-executes to regain privilege for the
 delivery, the use of \fB\-C\fP causes privilege to be lost. However, root can
 test reception and delivery using two separate commands (one to put a message
 running as the Exim user, so when it re\-executes to regain privilege for the
 delivery, the use of \fB\-C\fP causes privilege to be lost. However, root can
 test reception and delivery using two separate commands (one to put a message
-on the queue, using \fB\-odq\fP, and another to do the delivery, using \fB\-M\fP).
+in the queue, using \fB\-odq\fP, and another to do the delivery, using \fB\-M\fP).
 .sp
 If ALT_CONFIG_PREFIX is defined in Local/Makefile, it specifies a
 prefix string with which any file named in a \fB\-C\fP command line option
 .sp
 If ALT_CONFIG_PREFIX is defined in Local/Makefile, it specifies a
 prefix string with which any file named in a \fB\-C\fP command line option
-must start. In addition, the file name must not contain the sequence /../.
+must start. In addition, the filename must not contain the sequence /../.
 However, if the value of the \fB\-C\fP option is identical to the value of
 CONFIGURE_FILE in Local/Makefile, Exim ignores \fB\-C\fP and proceeds as
 usual. There is no default setting for ALT_CONFIG_PREFIX; when it is
 However, if the value of the \fB\-C\fP option is identical to the value of
 CONFIGURE_FILE in Local/Makefile, Exim ignores \fB\-C\fP and proceeds as
 usual. There is no default setting for ALT_CONFIG_PREFIX; when it is
-unset, any file name can be used with \fB\-C\fP.
+unset, any filename can be used with \fB\-C\fP.
 .sp
 ALT_CONFIG_PREFIX can be used to confine alternative configuration files
 to a directory to which only root has access. This prevents someone who has
 .sp
 ALT_CONFIG_PREFIX can be used to confine alternative configuration files
 to a directory to which only root has access. This prevents someone who has
@@ -796,6 +806,7 @@ example:
   exim '\-D ABC = something' ...
 .sp
 \fB\-D\fP may be repeated up to 10 times on a command line.
   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
 .TP 10
 \fB\-d\fP<\fIdebug options\fP>
 This option causes debugging information to be written to the standard
@@ -831,7 +842,8 @@ are:
   local_scan      can be used by local_scan()
   lookup          general lookup code and all lookups
   memory          memory handling
   local_scan      can be used by local_scan()
   lookup          general lookup code and all lookups
   memory          memory handling
-  pid             add pid to debug output lines
+  noutf8          modifier: avoid UTF\-8 line\-drawing
+  pid             modifier: add pid to debug output lines
   process_info    setting info for the process log
   queue_run       queue runs
   receive         general message reception logic
   process_info    setting info for the process log
   queue_run       queue runs
   receive         general message reception logic
@@ -839,7 +851,7 @@ are:
   retry           retry handling
   rewrite         address rewriting
   route           address routing
   retry           retry handling
   rewrite         address rewriting
   route           address routing
-  timestamp       add timestamp to debug output lines
+  timestamp       modifier: add timestamp to debug output lines
   tls             TLS logic
   transport       transports
   uid             changes of uid/gid and looking up uid/gid
   tls             TLS logic
   transport       transports
   uid             changes of uid/gid and looking up uid/gid
@@ -868,6 +880,10 @@ run in parallel.
 The timestamp selector causes the current time to be inserted at the start
 of all debug output lines. This can be useful when trying to track down delays
 in processing.
 The timestamp selector causes the current time to be inserted at the start
 of all debug output lines. This can be useful when trying to track down delays
 in processing.
+The noutf8 selector disables the use of
+UTF\-8 line\-drawing characters to group related information.
+When disabled. ascii\-art is used instead.
+Using the +all option does not set this modifier,
 .sp
 If the \fBdebug_print\fP option is set in any driver, it produces output whenever
 any debugging is selected, or if \fB\-v\fP is used.
 .sp
 If the \fBdebug_print\fP option is set in any driver, it produces output whenever
 any debugging is selected, or if \fB\-v\fP is used.
@@ -1009,6 +1025,21 @@ 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
 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 <\fIqueue name\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 argument.
+.TP 10
+\fB\-MCK\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 a
+remote host supports the ESMTP CHUNKING extension.
+.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
 \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
@@ -1033,8 +1064,14 @@ This option is not intended for use by external callers. It is used internally
 by Exim in conjunction with the \fB\-MC\fP option, and passes on the fact that the
 host to which Exim is connected supports TLS encryption.
 .TP 10
 by Exim in conjunction with the \fB\-MC\fP option, and passes on the fact that the
 host to which Exim is connected supports TLS encryption.
 .TP 10
+\fB\-MCt\fP <\fIIP address\fP> <\fIport\fP> <\fIcipher\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, and passes on the fact that the
+connection is being proxied by a parent process for handling TLS encryption.
+The arguments give the local address and port being proxied, and the TLS cipher.
+.TP 10
 \fB\-Mc\fP <\fImessage id\fP> <\fImessage id\fP> ...
 \fB\-Mc\fP <\fImessage id\fP> <\fImessage id\fP> ...
-This option requests Exim to run a delivery attempt on each message in turn,
+This option requests Exim to run a delivery attempt on each message, in turn,
 but unlike the \fB\-M\fP 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
 but unlike the \fB\-M\fP 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
@@ -1089,7 +1126,7 @@ This option requests Exim to remove the given messages from the queue. No
 bounce messages are sent; each message is simply forgotten. However, if any of
 the messages are active, their status is not altered. This option can be used
 only by an admin user or by the user who originally caused the message to be
 bounce messages are sent; each message is simply forgotten. However, if any of
 the messages are active, their status is not altered. This option can be used
 only by an admin user or by the user who originally caused the message to be
-placed on the queue.
+placed in the queue.
 .TP 10
 \fB\-Mset\fP <\fImessage id\fP>
 This option is useful only in conjunction with \fB\-be\fP (that is, when testing
 .TP 10
 \fB\-Mset\fP <\fImessage id\fP>
 This option is useful only in conjunction with \fB\-be\fP (that is, when testing
@@ -1147,7 +1184,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.
 \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
 .TP 10
 \fB\-O\fP <\fIdata\fP>
 This option is interpreted by Sendmail to mean set option. It is ignored by
@@ -1155,7 +1193,7 @@ Exim.
 .TP 10
 \fB\-oA\fP <\fIfile name\fP>
 This option is used by Sendmail in conjunction with \fB\-bi\fP to specify an
 .TP 10
 \fB\-oA\fP <\fIfile name\fP>
 This option is used by Sendmail in conjunction with \fB\-bi\fP to specify an
-alternative alias file name. Exim handles \fB\-bi\fP differently; see the
+alternative alias filename. Exim handles \fB\-bi\fP differently; see the
 description above.
 .TP 10
 \fB\-oB\fP <\fIn\fP>
 description above.
 .TP 10
 \fB\-oB\fP <\fIn\fP>
@@ -1194,7 +1232,7 @@ However, like \fB\-odb\fP, this option has no effect if \fBqueue_only_override\f
 false and one of the queueing options in the configuration file is in effect.
 .sp
 If there is a temporary delivery error during foreground delivery, the
 false and one of the queueing options in the configuration file is in effect.
 .sp
 If there is a temporary delivery error during foreground delivery, the
-message is left on the queue for later delivery, and the original reception
+message is left in the queue for later delivery, and the original reception
 process exits.
 .TP 10
 \fB\-odi\fP
 process exits.
 .TP 10
 \fB\-odi\fP
@@ -1205,7 +1243,7 @@ Sendmail.
 This option applies to all modes in which Exim accepts incoming messages,
 including the listening daemon. It specifies that the accepting process should
 not automatically start a delivery process for each message received. Messages
 This option applies to all modes in which Exim accepts incoming messages,
 including the listening daemon. It specifies that the accepting process should
 not automatically start a delivery process for each message received. Messages
-are placed on the queue, and remain there until a subsequent queue runner
+are placed in the queue, and remain there until a subsequent queue runner
 process encounters them. There are several configuration options (such as
 \fBqueue_only\fP) that can be used to queue incoming messages under certain
 conditions. This option overrides all of them and also \fB\-odqs\fP. It always
 process encounters them. There are several configuration options (such as
 \fBqueue_only\fP) that can be used to queue incoming messages under certain
 conditions. This option overrides all of them and also \fB\-odqs\fP. It always
@@ -1221,7 +1259,7 @@ When \fB\-odqs\fP does operate, a delivery process is started for each incoming
 message, in the background by default, but in the foreground if \fB\-odi\fP is
 also present. The recipient addresses are routed, and local deliveries are done
 in the normal way. However, if any SMTP deliveries are required, they are not
 message, in the background by default, but in the foreground if \fB\-odi\fP is
 also present. The recipient addresses are routed, and local deliveries are done
 in the normal way. However, if any SMTP deliveries are required, they are not
-done at this time, so the message remains on the queue until a subsequent queue
+done at this time, so the message remains in the queue until a subsequent queue
 runner process encounters it. Because routing was done, Exim knows which
 messages are waiting for which hosts, and so a number of messages for the same
 host can be sent in a single SMTP connection. The \fBqueue_smtp_domains\fP
 runner process encounters it. Because routing was done, Exim knows which
 messages are waiting for which hosts, and so a number of messages for the same
 host can be sent in a single SMTP connection. The \fBqueue_smtp_domains\fP
@@ -1335,7 +1373,7 @@ option sets the received protocol value that is stored in
 or \fB\-bs\fP is used. For \fB\-bh\fP, the protocol is forced to one of the standard
 SMTP protocol names. For \fB\-bs\fP, the protocol is always "local\-" followed by
 one of those same names. For \fB\-bS\fP (batched SMTP) however, the protocol can
 or \fB\-bs\fP is used. For \fB\-bh\fP, the protocol is forced to one of the standard
 SMTP protocol names. For \fB\-bs\fP, the protocol is always "local\-" followed by
 one of those same names. For \fB\-bS\fP (batched SMTP) however, the protocol can
-be set by \fB\-oMr\fP.
+be set by \fB\-oMr\fP. Repeated use of this option is not supported.
 .TP 10
 \fB\-oMs\fP <\fIhost name\fP>
 See \fB\-oMa\fP above for general remarks about the \fB\-oM\fP options. The \fB\-oMs\fP
 .TP 10
 \fB\-oMs\fP <\fIhost name\fP>
 See \fB\-oMa\fP above for general remarks about the \fB\-oM\fP options. The \fB\-oMs\fP
@@ -1381,7 +1419,7 @@ This option has exactly the same effect as \fB\-v\fP.
 \fB\-oX\fP <\fInumber or string\fP>
 This option is relevant only when the \fB\-bd\fP (start listening daemon) option
 is also given. It controls which ports and interfaces the daemon uses. When \fB\-oX\fP is used to start a daemon, no pid
 \fB\-oX\fP <\fInumber or string\fP>
 This option is relevant only when the \fB\-bd\fP (start listening daemon) option
 is also given. It controls which ports and interfaces the daemon uses. When \fB\-oX\fP is used to start a daemon, no pid
-file is written unless \fB\-oP\fP is also present to specify a pid file name.
+file is written unless \fB\-oP\fP is also present to specify a pid filename.
 .TP 10
 \fB\-pd\fP
 This option applies when an embedded Perl interpreter is linked with Exim. It overrides the setting of the \fBperl_at_start\fP
 .TP 10
 \fB\-pd\fP
 This option applies when an embedded Perl interpreter is linked with Exim. It overrides the setting of the \fBperl_at_start\fP
@@ -1403,6 +1441,7 @@ host name and its colon can be omitted when only the protocol is to be set.
 Note the Exim already has two private options, \fB\-pd\fP and \fB\-ps\fP, that refer
 to embedded Perl. It is therefore impossible to set a protocol value of d
 or s using this option (but that does not seem a real limitation).
 Note the Exim already has two private options, \fB\-pd\fP and \fB\-ps\fP, that refer
 to embedded Perl. It is therefore impossible to set a protocol value of d
 or s using this option (but that does not seem a real limitation).
+Repeated use of this option is not supported.
 .TP 10
 \fB\-q\fP
 This option is normally restricted to admin users. However, there is a
 .TP 10
 \fB\-q\fP
 This option is normally restricted to admin users. However, there is a
@@ -1410,7 +1449,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
 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
 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
@@ -1458,7 +1498,7 @@ intermittently.
 \fB\-q[q]i...\fP
 If the \fIi\fP flag is present, the queue runner runs delivery processes only for
 those messages that haven't previously been tried. (\fIi\fP stands for "initial
 \fB\-q[q]i...\fP
 If the \fIi\fP flag is present, the queue runner runs delivery processes only for
 those messages that haven't previously been tried. (\fIi\fP stands for "initial
-delivery".) This can be helpful if you are putting messages on the queue using
+delivery".) This can be helpful if you are putting messages in the queue using
 \fB\-odq\fP and want a queue runner just to process the new messages.
 .TP 10
 \fB\-q[q][i]f...\fP
 \fB\-odq\fP and want a queue runner just to process the new messages.
 .TP 10
 \fB\-q[q][i]f...\fP
@@ -1472,9 +1512,24 @@ frozen or not.
 .TP 10
 \fB\-q[q][i][f[f]]l\fP
 The \fIl\fP (the letter "ell") flag specifies that only local deliveries are to
 .TP 10
 \fB\-q[q][i][f[f]]l\fP
 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
+be done. If a message requires any remote deliveries, it remains in the queue
 for later delivery.
 .TP 10
 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
 \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
@@ -1640,6 +1695,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.
 \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"
 .sp
 .
 .SH "SEE ALSO"
index 6657f63..84fd547 100644 (file)
@@ -6,7 +6,7 @@ about experimental  features, all  of which  are unstable and
 liable to incompatible change.
 
 
 liable to incompatible change.
 
 
-Brightmail AntiSpam (BMI) suppport
+Brightmail AntiSpam (BMI) support
 --------------------------------------------------------------
 
 Brightmail  AntiSpam  is  a  commercial  package.  Please  see
 --------------------------------------------------------------
 
 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
 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
   (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
@@ -292,172 +292,18 @@ These four steps are explained in more details below.
 
 
 
 
 
 
-Sender Policy Framework (SPF) support
---------------------------------------------------------------
-
-To learn  more  about  SPF, visit   http://www.openspf.org. This
-document does   not explain  the SPF  fundamentals, you should
-read and understand the implications of deploying SPF on  your
-system before doing so.
-
-SPF support is added via the libspf2 library. Visit
-
-  http://www.libspf2.org/
-
-to obtain  a copy,  then compile  and install  it. By default,
-this will  put headers  in /usr/local/include  and the  static
-library in /usr/local/lib.
-
-To compile Exim with SPF support, set these additional flags in
-Local/Makefile:
-
-EXPERIMENTAL_SPF=yes
-CFLAGS=-DSPF -I/usr/local/include
-EXTRALIBS_EXIM=-L/usr/local/lib -lspf2
-
-This assumes   that the   libspf2 files   are installed  in
-their default locations.
-
-You can now run SPF checks in incoming SMTP by using the "spf"
-ACL condition  in either  the MAIL,  RCPT or  DATA ACLs.  When
-using it in the RCPT ACL, you can make the checks dependent on
-the RCPT  address (or  domain), so  you can  check SPF records
-only  for   certain  target   domains.  This   gives  you  the
-possibility  to opt-out  certain customers  that do  not want
-their mail to be subject to SPF checking.
-
-The spf condition  takes a list  of strings on  its right-hand
-side. These strings describe the outcome of the SPF check  for
-which the spf condition should succeed. Valid strings are:
-
-  o pass      The SPF check passed, the sending host
-              is positively verified by SPF.
-  o fail      The SPF check failed, the sending host
-              is NOT allowed to send mail for the domain
-              in the envelope-from address.
-  o softfail  The SPF check failed, but the queried
-              domain can't absolutely confirm that this
-              is a forgery.
-  o none      The queried domain does not publish SPF
-              records.
-  o neutral   The SPF check returned a "neutral" state.
-              This means the queried domain has published
-              a SPF record, but wants to allow outside
-              servers to send mail under its domain as well.
-              This should be treated like "none".
-  o permerror This indicates a syntax error in the SPF
-              record of the queried domain. You may deny
-              messages when this occurs. (Changed in 4.83)
-  o temperror This indicates a temporary error during all
-              processing, including Exim's SPF processing.
-              You may defer messages when this occurs.
-              (Changed in 4.83)
-  o err_temp  Same as permerror, deprecated in 4.83, will be
-              removed in a future release.
-  o err_perm  Same as temperror, deprecated in 4.83, will be
-              removed in a future release.
-
-You can prefix each string with an exclamation mark to  invert
-its meaning,  for example  "!fail" will  match all  results but
-"fail".  The  string  list is  evaluated  left-to-right,  in a
-short-circuit fashion.  When a  string matches  the outcome of
-the SPF check, the condition  succeeds. If none of the  listed
-strings matches the  outcome of the  SPF check, the  condition
-fails.
-
-Here is an example to fail forgery attempts from domains that
-publish SPF records:
-
-/* -----------------
-deny message = $sender_host_address is not allowed to send mail from ${if def:sender_address_domain {$sender_address_domain}{$sender_helo_name}}.  \
-              Please see http://www.openspf.org/Why?scope=${if def:sender_address_domain {mfrom}{helo}};identity=${if def:sender_address_domain {$sender_address}{$sender_helo_name}};ip=$sender_host_address
-     spf = fail
---------------------- */
-
-You can also give special treatment to specific domains:
-
-/* -----------------
-deny message = AOL sender, but not from AOL-approved relay.
-     sender_domains = aol.com
-     spf = fail:neutral
---------------------- */
-
-Explanation: AOL  publishes SPF  records, but  is liberal  and
-still allows  non-approved relays  to send  mail from aol.com.
-This will result in a "neutral" state, while mail from genuine
-AOL servers  will result  in "pass".  The example  above takes
-this into account and  treats "neutral" like "fail",  but only
-for aol.com. Please note that this violates the SPF draft.
-
-When the spf condition has run, it sets up several expansion
-variables.
-
-  $spf_header_comment
-  This contains a human-readable string describing the outcome
-  of the SPF check. You can add it to a custom header or use
-  it for logging purposes.
-
-  $spf_received
-  This contains a complete Received-SPF: header that can be
-  added to the message. Please note that according to the SPF
-  draft, this header must be added at the top of the header
-  list. Please see section 10 on how you can do this.
-
-  Note: in case of "Best-guess" (see below), the convention is
-  to put this string in a header called X-SPF-Guess: instead.
-
-  $spf_result
-  This contains the outcome of the SPF check in string form,
-  one of pass, fail, softfail, none, neutral, permerror or
-  temperror.
-
-  $spf_smtp_comment
-  This contains a string that can be used in a SMTP response
-  to the calling party. Useful for "fail".
-
-In addition to SPF, you can also perform checks for so-called
-"Best-guess".  Strictly speaking, "Best-guess" is not standard
-SPF, but it is supported by the same framework that enables SPF
-capability.  Refer to http://www.openspf.org/FAQ/Best_guess_record
-for a description of what it means.
-
-To access this feature, simply use the spf_guess condition in place
-of the spf one.  For example:
-
-/* -----------------
-deny message = $sender_host_address doesn't look trustworthy to me
-     spf_guess = fail
---------------------- */
-
-In case you decide to reject messages based on this check, you
-should note that although it uses the same framework, "Best-guess"
-is NOT SPF, and therefore you should not mention SPF at all in your
-reject message.
-
-When the spf_guess condition has run, it sets up the same expansion
-variables as when spf condition is run, described above.
-
-Additionally, since Best-guess is not standardized, you may redefine
-what "Best-guess" means to you by redefining spf_guess variable in
-global config.  For example, the following:
-
-/* -----------------
-spf_guess = v=spf1 a/16 mx/16 ptr ?all
---------------------- */
-
-would relax host matching rules to a broader network range.
-
-
 SRS (Sender Rewriting Scheme) Support
 --------------------------------------------------------------
 
 Exiscan  currently  includes SRS  support  via Miles  Wilton's
 libsrs_alt library. The current version of the 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
 
 
 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
 
 Unpack the tarball, then refer to MTAs/README.EXIM
 to proceed. You need to set
@@ -467,8 +313,10 @@ EXPERIMENTAL_SRS=yes
 in your Local/Makefile.
 
 
 in your Local/Makefile.
 
 
+
 DCC Support
 --------------------------------------------------------------
 DCC Support
 --------------------------------------------------------------
+Distributed Checksum Clearinghouse; http://www.rhyolite.com/dcc/
 
 *) Building exim
 
 
 *) Building exim
 
@@ -538,7 +386,9 @@ Then set something like
 mout-xforward.gmx.net           82.165.159.12
 mout.gmx.net                    212.227.15.16
 
 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
 --------------------------------------------------------------
 
 DMARC Support
 --------------------------------------------------------------
@@ -559,7 +409,7 @@ that headers will be in /usr/local/include, and that the libraries
 are in /usr/local/lib.
 
 1. To compile Exim with DMARC support, you must first enable SPF.
 are in /usr/local/lib.
 
 1. To compile Exim with DMARC support, you must first enable SPF.
-Please read the above section on enabling the EXPERIMENTAL_SPF
+Please read the Local/Makefile comments on enabling the SUPPORT_SPF
 feature.  You must also have DKIM support, so you cannot set the
 DISABLE_DKIM feature.  Once both of those conditions have been met
 you can enable DMARC in Local/Makefile:
 feature.  You must also have DKIM support, so you cannot set the
 DISABLE_DKIM feature.  Once both of those conditions have been met
 you can enable DMARC in Local/Makefile:
@@ -586,6 +436,7 @@ dmarc_tld_file      Defines the location of a text file of valid
                     during domain parsing. Maintained by Mozilla,
                     the most current version can be downloaded
                     from a link at http://publicsuffix.org/list/.
                     during domain parsing. Maintained by Mozilla,
                     the most current version can be downloaded
                     from a link at http://publicsuffix.org/list/.
+                    See also util/renew-opendmarc-tlds.sh script.
 
 Optional:
 dmarc_history_file  Defines the location of a file to log results
 
 Optional:
 dmarc_history_file  Defines the location of a file to log results
@@ -596,11 +447,19 @@ dmarc_history_file  Defines the location of a file to log results
                     directory of this file is writable by the user
                     exim runs as.
 
                     directory of this file is writable by the user
                     exim runs as.
 
-dmarc_forensic_sender The email address to use when sending a
+dmarc_forensic_sender Alternate email address to use when sending a
                     forensic report detailing alignment failures
                     if a sender domain's dmarc record specifies it
                     and you have configured Exim to send them.
                     forensic report detailing alignment failures
                     if a sender domain's dmarc record specifies it
                     and you have configured Exim to send them.
-                    Default: do-not-reply@$default_hostname
+
+                   If set, this is expanded and used for the
+                   From: header line; the address is extracted
+                   from it and used for the envelope from.
+                   If not set, the From: header is expanded from
+                   the dsn_from option, and <> is used for the
+                   envelope from.
+
+                    Default: unset.
 
 
 3. By default, the DMARC processing will run for any remote,
 
 
 3. By default, the DMARC processing will run for any remote,
@@ -623,10 +482,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.
 
 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
 
 (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.)
 
 your exim config.  If you don't tell it to send them, it will not
 send them.)
 
@@ -673,6 +532,9 @@ Of course, you can also use any other lookup method that Exim
 supports, including LDAP, Postgres, MySQL, etc, as long as the
 result is a list of colon-separated strings.
 
 supports, including LDAP, Postgres, MySQL, etc, as long as the
 result is a list of colon-separated strings.
 
+Performing the check sets up information used by the
+${authresults } expansion item.
+
 Several expansion variables are set before the DATA ACL is
 processed, and you can use them in this ACL.  The following
 expansion variables are available:
 Several expansion variables are set before the DATA ACL is
 processed, and you can use them in this ACL.  The following
 expansion variables are available:
@@ -696,9 +558,8 @@ expansion variables are available:
     are "none", "reject" and "quarantine".  It is blank when there
     is any error, including no DMARC record.
 
     are "none", "reject" and "quarantine".  It is blank when there
     is any error, including no DMARC record.
 
-  o $dmarc_ar_header
-    This is the entire Authentication-Results header which you can
-    add using an add_header modifier.
+A now-redundant variable $dmarc_ar_header has now been withdrawn.
+Use the ${authresults } expansion instead.
 
 
 5. How to enable DMARC advanced operation:
 
 
 5. How to enable DMARC advanced operation:
@@ -738,7 +599,6 @@ b. Configure, somewhere before the DATA ACL, the control option to
   warn    dmarc_status   = accept : none : off
           !authenticated = *
           log_message    = DMARC DEBUG: $dmarc_status $dmarc_used_domain
   warn    dmarc_status   = accept : none : off
           !authenticated = *
           log_message    = DMARC DEBUG: $dmarc_status $dmarc_used_domain
-          add_header     = $dmarc_ar_header
 
   warn    dmarc_status   = !accept
           !authenticated = *
 
   warn    dmarc_status   = !accept
           !authenticated = *
@@ -755,397 +615,372 @@ b. Configure, somewhere before the DATA ACL, the control option to
 
   deny    dmarc_status   = reject
           !authenticated = *
 
   deny    dmarc_status   = reject
           !authenticated = *
-          message        = Message from $domain_used_domain failed sender's DMARC policy, REJECT
-
+          message        = Message from $dmarc_used_domain failed sender's DMARC policy, REJECT
 
 
+  warn    add_header     = :at_start:${authresults {$primary_hostname}}
 
 
-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.
+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.
 
 
 
 
-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.
-
+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.
 
 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
 
 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
 # 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
 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).
 
 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.
+${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}}
 
 
-For example:
 
 
-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
+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.
 
 
-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.
+The spool files can then be processed by external processes and then
+requeued into exim spool directories for final delivery.
+However, note carefully the warnings in the main documentation on
+qpool file formats.
 
 
-4. Example usage:
+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.
 
 
-(Host List)
-hostlist relay_from_ips = <\n ${lookup redis{SMEMBERS relay_from_ips}}
+The mailscanner package is one of the processes that can take advantage
+of this transport to filter email.
 
 
-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 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.
 
 
-(Domain list)
-domainlist virtual_domains = ${lookup redis {HGET $domain domain}}
+The transport only takes one option:
 
 
-Where $domain is a hash which includes the key 'domain' and the value '$domain'.
+* directory - This is used to specify the directory messages should be
+copied to.  Expanded.
 
 
-(Adding or updating an existing key)
-set acl_c_spammer = ${if eq{${lookup redis{SPAMMER_SET}}}{OK}}
+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.
 
 
-Where SPAMMER_SET is a macro and it is defined as
+Sample configuration:
 
 
-"SET SPAMMER <some_value>"
+(Router)
 
 
-(Getting a value from Redis)
+scan:
+   driver = accept
+   transport = scan
 
 
-set acl_c_spam_host = ${lookup redis{GET...}}
+(Transport)
 
 
+scan:
+  driver = queuefile
+  directory = /var/spool/baruwa-scanner/input
 
 
-Proxy Protocol Support
---------------------------------------------------------------
 
 
-Exim now has Experimental "Proxy Protocol" support.  It was built on
-specifications from:
-http://haproxy.1wt.eu/download/1.5/doc/proxy-protocol.txt
-Above URL revised May 2014 to change version 2 spec:
-http://git.1wt.eu/web?p=haproxy.git;a=commitdiff;h=afb768340c9d7e50d8e
-
-The purpose of this function is so that an application load balancer,
-such as HAProxy, can sit in front of several Exim servers and Exim
-will log the IP that is connecting to the proxy server instead of
-the IP of the proxy server when it connects to Exim.  It resets the
-$sender_address_host and $sender_address_port to the IP:port of the
-connection to the proxy.  It also re-queries the DNS information for
-this new IP address so that the original sender's hostname and IP
-get logged in the Exim logfile.  There is no logging if a host passes or
-fails Proxy Protocol negotiation, but it can easily be determined and
-recorded in an ACL (example is below).
-
-1. To compile Exim with Proxy Protocol support, put this in
-Local/Makefile:
-
-EXPERIMENTAL_PROXY=yes
-
-2. Global configuration settings:
-
-proxy_required_hosts = HOSTLIST
-
-The proxy_required_hosts option will require any IP in that hostlist
-to use Proxy Protocol. The specification of Proxy Protocol is very
-strict, and if proxy negotiation fails, Exim will not allow any SMTP
-command other than QUIT. (See end of this section for an example.)
-The option is expanded when used, so it can be a hostlist as well as
-string of IP addresses.  Since it is expanded, specifying an alternate
-separator is supported for ease of use with IPv6 addresses.
-
-To log the IP of the proxy in the incoming logline, add:
-  log_selector = +proxy
-
-A default incoming logline (wrapped for appearance) will look like this:
-
-  2013-11-04 09:25:06 1VdNti-0001OY-1V <= me@example.net
-  H=mail.example.net [1.2.3.4] P=esmtp S=433
-
-With the log selector enabled, an email that was proxied through a
-Proxy Protocol server at 192.168.1.2 will look like this:
-
-  2013-11-04 09:25:06 1VdNti-0001OY-1V <= me@example.net
-  H=mail.example.net [1.2.3.4] P=esmtp PRX=192.168.1.2 S=433
-
-3. In the ACL's the following expansion variables are available.
-
-proxy_host_address   The (internal) src IP of the proxy server
-                     making the connection to the Exim server.
-proxy_host_port      The (internal) src port the proxy server is
-                     using to connect to the Exim server.
-proxy_target_address The dest (public) IP of the remote host to
-                     the proxy server.
-proxy_target_port    The dest port the remote host is using to
-                     connect to the proxy server.
-proxy_session        Boolean, yes/no, the connected host is required
-                     to use Proxy Protocol.
-
-There is no expansion for a failed proxy session, however you can detect
-it by checking if $proxy_session is true but $proxy_host is empty.  As
-an example, in my connect ACL, I have:
-
-  warn    condition      = ${if and{ {bool{$proxy_session}} \
-                                     {eq{$proxy_host_address}{}} } }
-          log_message    = Failed required proxy protocol negotiation \
-                           from $sender_host_name [$sender_host_address]
-
-  warn    condition      = ${if and{ {bool{$proxy_session}} \
-                                     {!eq{$proxy_host_address}{}} } }
-          # But don't log health probes from the proxy itself
-          condition      = ${if eq{$proxy_host_address}{$sender_host_address} \
-                                {false}{true}}
-          log_message    = Successfully proxied from $sender_host_name \
-                           [$sender_host_address] through proxy protocol \
-                           host $proxy_host_address
-
-  # Possibly more clear
-  warn logwrite = Remote Source Address: $sender_host_address:$sender_host_port
-       logwrite = Proxy Target Address: $proxy_target_address:$proxy_target_port
-       logwrite = Proxy Internal Address: $proxy_host_address:$proxy_host_port
-       logwrite = Internal Server Address: $received_ip_address:$received_port
-
-
-4. Recommended ACL additions:
-   - Since the real connections are all coming from your proxy, and the
-     per host connection tracking is done before Proxy Protocol is
-     evaluated, smtp_accept_max_per_host must be set high enough to
-     handle all of the parallel volume you expect per inbound proxy.
-   - With the smtp_accept_max_per_host set so high, you lose the ability
-     to protect your server from massive numbers of inbound connections
-     from one IP.  In order to prevent your server from being DOS'd, you
-     need to add a per connection ratelimit to your connect ACL.  I
-     suggest something like this:
-
-  # Set max number of connections per host
-  LIMIT   = 5
-  # Or do some kind of IP lookup in a flat file or database
-  # LIMIT = ${lookup{$sender_host_address}iplsearch{/etc/exim/proxy_limits}}
-
-  defer   message        = Too many connections from this IP right now
-          ratelimit      = LIMIT / 5s / per_conn / strict
-
-
-5. Runtime issues to be aware of:
-   - The proxy has 3 seconds (hard-coded in the source code) to send the
-     required Proxy Protocol header after it connects.  If it does not,
-     the response to any commands will be:
-     "503 Command refused, required Proxy negotiation failed"
-   - If the incoming connection is configured in Exim to be a Proxy
-     Protocol host, but the proxy is not sending the header, the banner
-     does not get sent until the timeout occurs.  If the sending host
-     sent any input (before the banner), this causes a standard Exim
-     synchronization error (i.e. trying to pipeline before PIPELINING
-     was advertised).
-   - This is not advised, but is mentioned for completeness if you have
-     a specific internal configuration that you want this:  If the Exim
-     server only has an internal IP address and no other machines in your
-     organization will connect to it to try to send email, you may
-     simply set the hostlist to "*", however, this will prevent local
-     mail programs from working because that would require mail from
-     localhost to use Proxy Protocol.  Again, not advised!
-
-6. Example of a refused connection because the Proxy Protocol header was
-not sent from a host configured to use Proxy Protocol.  In the example,
-the 3 second timeout occurred (when a Proxy Protocol banner should have
-been sent), the banner was displayed to the user, but all commands are
-rejected except for QUIT:
-
-# nc mail.example.net 25
-220-mail.example.net, ESMTP Exim 4.82+proxy, Mon, 04 Nov 2013 10:45:59
-220 -0800 RFC's enforced
-EHLO localhost
-503 Command refused, required Proxy negotiation failed
-QUIT
-221 mail.example.net closing connection
-
-
-DSN Support
---------------------------------------------------------------
+In order to build exim with Queuefile transport support add or uncomment
 
 
-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
+EXPERIMENTAL_QUEUEFILE=yes
 
 
-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.
+to your Local/Makefile. (Re-)build/install exim. exim -d should show
+Experimental_QUEUEFILE in the line "Support for:".
 
 
-All the other changes are made without changing any defaults
 
 
-Building exim:
---------------
+ARC support
+-----------
+Specification: https://tools.ietf.org/html/draft-ietf-dmarc-arc-protocol-11
+Note that this is not an RFC yet, so may change.
 
 
-Define
-EXPERIMENTAL_DSN=YES
-in your Local/Makefile.
+ARC is intended to support the utility of SPF and DKIM in the presence of
+intermediaries in the transmission path - forwarders and mailinglists -
+by establishing a cryptographically-signed chain in headers.
 
 
-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.
+Normally one would only bother doing ARC-signing when functioning as
+an intermediary.  One might do verify for local destinations.
 
 
-Failure and delay DSNs are triggered as usual except a sender used NOTIFY=...
-to prevent them.
+ARC uses the notion of a "ADministrative Management Domain" (ADMD).
+Described in RFC 5598 (section 2.3), this is essentially the set of
+mail-handling systems that the mail transits.  A label should be chosen to
+identify the ADMD.  Messages should be ARC-verified on entry to the ADMD,
+and ARC-signed on exit from it.
 
 
-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
+Verification
+--
+An ACL condition is provided to perform the "verifier actions" detailed
+in section 6 of the above specification.  It may be called from the DATA ACL
+and succeeds if the result matches any of a given list.
+It also records the highest ARC instance number (the chain size)
+and verification result for later use in creating an Authentication-Results:
+standard header.
 
 
-Those hosts can then use NOTIFY,ENVID,RET,ORCPT options.
+  verify = arc/<acceptable_list>   none:fail:pass
 
 
-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.
+  add_header = :at_start:${authresults {<admd-identifier>}}
 
 
-A redirect router will always trigger a success DSN if requested and the DSN
-options are not passed any further.
+       Note that it would be wise to strip incoming messages of A-R headers
+       that claim to be from our own <admd-identifier>.
 
 
-A success DSN always contains the recipient address as submitted by the
-client as required by RFC. Rewritten addresses are never exposed.
+There are four new variables:
 
 
-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.
+  $arc_state           One of pass, fail, none
+  $arc_state_reason    (if fail, why)
+  $arc_domains         colon-sep list of ARC chain domains, in chain order.
+                       problematic elements may have empty list elements
+  $arc_oldest_pass     lowest passing instance number of chain
 
 
-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.
+Example:
+  logwrite = oldest-p-ams: <${reduce {$lh_ARC-Authentication-Results:} \
+                               {} \
+                               {${if = {$arc_oldest_pass} \
+                                       {${extract {i}{${extract {1}{;}{$item}}}}} \
+                                       {$item} {$value}}} \
+                           }>
+
+Receive log lines for an ARC pass will be tagged "ARC".
+
+
+Signing
+--
+arc_sign = <admd-identifier> : <selector> : <privkey> [ : <options> ]
+An option on the smtp transport, which constructs and prepends to the message
+an ARC set of headers.  The textually-first Authentication-Results: header
+is used as a basis (you must have added one on entry to the ADMD).
+Expanded as a whole; if unset, empty or forced-failure then no signing is done.
+If it is set, all of the first three elements must be non-empty.
+
+The fourth element is optional, and if present consists of a comma-separated list
+of options.  The options implemented are
+
+  timestamps           Add a t= tag to the generated AMS and AS headers, with the
+                       current time.
+  expire[=<val>]       Add an x= tag to the generated AMS header, with an expiry time.
+                       If the value <val> is an plain number it is used unchanged.
+                       If it starts with a '+' then the following number is added
+                       to the current time, as an offset in seconds.
+                       If a value is not given it defaults to a one month offset.
+
+[As of writing, gmail insist that a t= tag on the AS is mandatory]
+
+Caveats:
+ * There must be an Authentication-Results header, presumably added by an ACL
+   while receiving the message, for the same ADMD, for arc_sign to succeed.
+   This requires careful coordination between inbound and outbound logic.
+
+   Only one A-R header is taken account of.  This is a limitation versus
+   the ARC spec (which says that all A-R headers from within the ADMD must
+   be used).
+
+ * If passing a message to another system, such as a mailing-list manager
+   (MLM), between receipt and sending, be wary of manipulations to headers made
+   by the MLM.
+   + For instance, Mailman with REMOVE_DKIM_HEADERS==3 might improve
+     deliverability in a pre-ARC world, but that option also renames the
+     Authentication-Results header, which breaks signing.
+
+ * Even if you use multiple DKIM keys for different domains, the ARC concept
+   should try to stick to one ADMD, so pick a primary domain and use that for
+   AR headers and outbound signing.
+
+Signing is not compatible with cutthrough delivery; any (before expansion)
+value set for the option will result in cutthrough delivery not being
+used via the transport in question.
+
+
+
+
+REQUIRETLS support
+------------------
+Ref: https://tools.ietf.org/html/draft-ietf-uta-smtp-require-tls-03
+
+If compiled with EXPERIMENTAL_REQUIRETLS support is included for this
+feature, where a REQUIRETLS option is added to the MAIL command.
+The client may not retry in clear if the MAIL+REQUIRETLS fails (or was never
+offered), and the server accepts an obligation that any onward transmission
+by SMTP of the messages accepted will also use REQUIRETLS - or generate a
+fail DSN.
+
+The Exim implementation includes
+- a main-part option tls_advertise_requiretls; host list, default "*"
+- an observability variable $requiretls returning yes/no
+- an ACL "control = requiretls" modifier for setting the requirement
+- Log lines and Received: headers capitalise the S in the protocol
+  element: "P=esmtpS"
+
+Differences from spec:
+- we support upgrading the requirement for REQUIRETLS, including adding
+  it from cold, within an MTA.  The spec only define the sourcing MUA
+  as being able to source the requirement, and makes no mention of upgrade.
+- No support is coded for the RequireTLS header (which can be used
+  to annul DANE and/or STS policiy). [this can _almost_ be done in
+  transport option expansions, but not quite: it requires tha DANE-present
+  but STARTTLS-failing targets fallback to cleartext, which current DANE
+  coding specifically blocks]
+
+Note that REQUIRETLS is only advertised once a TLS connection is achieved
+(in contrast to STARTTLS).  If you want to check the advertising, do something
+like "swaks -s 127.0.0.1 -tls -q HELO".
+
+
+
+
+Early pipelining support
+------------------------
+Ref: https://datatracker.ietf.org/doc/draft-harris-early-pipe/
+
+If compiled with EXPERIMENTAL_PIPE_CONNECT support is included for this feature.
+The server advertises the feature in its EHLO response, currently using the name
+"X_PIPE_CONNECT" (this will change, some time in the future).
+A client may cache this information, along with the rest of the EHLO response,
+and use it for later connections.  Those later ones can send esmtp commands before
+a banner is received.
+
+Up to 1.5 roundtrip times can be taken out of cleartext connections, 2.5 on
+STARTTLS connections.
+
+In combination with the traditional PIPELINING feature the following example
+sequences are possible (among others):
+
+(client)                (server)
+
+EHLO,MAIL,RCPT,DATA ->
+                     <- banner,EHLO-resp,MAIL-ack,RCPT-ack,DATA-goahead
+message-data        ->
+------
+
+EHLO,MAIL,RCPT,BDAT              ->
+                                  <- banner,EHLO-resp,MAIL-ack,RCPT-ack
+message-data                     ->
+------
+
+EHLO,STARTTLS                     ->
+                                   <- banner,EHLO-resp,TLS-goahead
+TLS1.2-client-hello               ->
+                                   <- TLS-server-hello,cert,hello-done
+client-Kex,change-cipher,finished ->
+                                   <- change-cipher,finished
+EHLO,MAIL,RCPT,DATA               ->
+                                   <- EHLO-resp,MAIL-ack,RCPT-ack,DATA-goahead
+
+------
+(tls-on-connect)
+TLS1.2-client-hello               ->
+                                   <- TLS-server-hello,cert,hello-done
+client-Kex,change-cipher,finished ->
+                                   <- change-cipher,finshed
+                                   <- banner
+EHLO,MAIL,RCPT,DATA               ->
+                                   <- EHLO-resp,MAIL-ack,RCPT-ack,DATA-goahead
+
+Where the initial client packet is SMTP, it can combine with the TCP Fast Open
+feature and be sent in the TCP SYN.
+
+
+A main-section option "pipelining_connect_advertise_hosts" (default: *)
+and an smtp transport option "hosts_pipe_connect" (default: unset)
+control the feature.
+
+If the "pipelining" log_selector is enabled, the "L" field in server <=
+log lines has a period appended if the feature was advertised but not used;
+or has an asterisk appended if the feature was used.  In client => lines
+the "L" field has an asterisk appended if the feature was used.
+
+The "retry_data_expire" option controls cache invalidation.
+Entries are also rewritten (or cleared) if the adverised features
+change.
+
+
+NOTE: since the EHLO command must be constructed before the connection is
+made it cannot depend on the interface IP address that will be used.
+Transport configurations should be checked for this.  An example avoidance:
+
+ helo_data =   ${if def:sending_ip_address \
+                  {${lookup dnsdb{>! ptr=$sending_ip_address} \
+                       {${sg{$value} {^([^!]*).*\$} {\$1}}} fail}} \
+                  {$primary_hostname}}
 
 
-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).
 
 
 
 
 
 
index 89d2d65..c361acc 100644 (file)
@@ -4,7 +4,7 @@ Philip Hazel
 
 Copyright (c) 2014 University of Cambridge
 
 
 Copyright (c) 2014 University of Cambridge
 
-Revision 4.84.2  02 Mar 2016 PH
+Revision 4.92  10 Feb 2019 PH
 
 -------------------------------------------------------------------------------
 
 
 -------------------------------------------------------------------------------
 
@@ -77,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
 
 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.2.
+to Exim version 4.92.
 
 
 1.1 Introduction
 
 
 1.1 Introduction
@@ -1192,12 +1192,13 @@ the logwrite command can be used to write to it:
 e.g. logwrite "$tod_log $message_id processed"
 
 It is possible to have more than one logfile command, to specify writing to
 e.g. logwrite "$tod_log $message_id processed"
 
 It is possible to have more than one logfile command, to specify writing to
-different log files in different circumstances. Writing takes place at the end
-of the file, and a newline character is added to the end of each string if
-there isn't one already there. Newlines can be put in the middle of the string
-by using the "\n" escape sequence. Lines from simultaneous deliveries may get
-interleaved in the file, as there is no interlocking, so you should plan your
-logging with this in mind. However, data should not get lost.
+different log files in different circumstances. A previously opened log is
+closed on a subsequent logfile command. Writing takes place at the end of the
+file, and a newline character is added to the end of each string if there isn't
+one already there. Newlines can be put in the middle of the string by using the
+"\n" escape sequence. Lines from simultaneous deliveries may get interleaved in
+the file, as there is no interlocking, so you should plan your logging with
+this in mind. However, data should not get lost.
 
 
 3.16 The finish command
 
 
 3.16 The finish command
diff --git a/doc/openssl.txt b/doc/openssl.txt
new file mode 100644 (file)
index 0000000..3efa833
--- /dev/null
@@ -0,0 +1,165 @@
+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.
+
+
+Insecure versions and ciphers
+-----------------------------
+
+Email delivery to MX hosts is usually done with automatic fallback to
+plaintext if TLS could not be negotiated.  There are good historical reasons
+for this.  You can and should avoid it by using DNSSEC for signing your DNS
+and publishing TLSA records, to enable "DANE" security.  This signals to
+senders that they should be able to verify your certificates, and that they
+should not fallback to cleartext.
+
+In the absence of DANE, trying to increase the security of TLS by removing
+support for older generations of ciphers and protocols will actually _lower_
+the security, because the clients fallback to plaintext and retry anyway.  As
+a result, you should give serious thought to enabling older features which are
+no longer default in OpenSSL.
+
+The examples below explicitly enable ssl3 and weak ciphers.
+
+We don't like this, but reality doesn't care and is messy.
+
+
+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.
+
+If you pick a location shared amongst various local packages, such as
+`/usr/local` on Linux, then the new OpenSSL will be used by all of those
+packages.  If that's what you want, great!  If instead you want to
+ensure that only software you explicitly set to use the newer OpenSSL
+will try to use the new OpenSSL, then stick to something like
+`/opt/openssl`.
+
+    ./config --prefix=/opt/openssl --openssldir=/etc/ssl  \
+        -L/opt/openssl/lib -Wl,-R/opt/openssl/lib         \
+        enable-ssl-trace shared                           \
+        enable-ssl3 enable-ssl3-method enable-weak-ssl-ciphers
+    make
+    make install
+
+On some systems, the linker uses `-rpath` instead of `-R`; on such systems,
+replace the parameter starting `-Wl` with: `-Wl,-rpath,/opt/openssl/lib`.
+There are more variations on less common systems.
+
+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:
+
+    PKG_CONFIG_PATH=/opt/openssl/lib/pkgconfig
+
+    SUPPORT_TLS=yes
+    USE_OPENSSL_PC=openssl
+    LDFLAGS+=-ldl -Wl,-rpath,/opt/openssl/lib
+
+The -ldl is needed by OpenSSL 1.0.2+ on Linux and is not needed on most
+other platforms.  The LDFLAGS is needed because `pkg-config` doesn't know
+how to emit information about RPATH-stamping, but we can still leverage
+`pkg-config` for everything else.
+
+Then build Exim:
+
+    make
+    sudo make install
+
+
+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
+
+It is important to use `RPATH` and not `RUNPATH`!
+
+The gory details about `RUNPATH` (skip unless interested):
+The OpenSSL library might be opened indirectly by some other library
+which Exim depends upon.  If the executable does have `RUNPATH` then
+that will inhibit using either of `RPATH` or `RUNPATH` from the
+executable for finding the OpenSSL library when that other library tries
+to load it.
+In fact, if the intermediate library has a `RUNPATH` stamped into it,
+then this will block `RPATH` too, and will create problems with Exim.
+If you're in such a situation, and those libraries were supplied to you
+instead of built by you, then you're reaching the limits of sane
+repairability and it's time to prioritize rebuilding your mail-server
+hosts to be a current OS release which natively pulls in an
+upstream-supported OpenSSL, or stick to the OS releases of Exim.
+
+
+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 9f8e1ac..b522487 100644 (file)
@@ -2,9 +2,9 @@ Specification of the Exim Mail Transfer Agent
 
 Exim Maintainers
 
 
 Exim Maintainers
 
-Copyright (c) 2014 University of Cambridge
+Copyright (c) 2018 University of Cambridge
 
 
-Revision 4.84.2  02 Mar 2016 EM
+Revision 4.92  10 Feb 2019 EM
 
 -------------------------------------------------------------------------------
 
 
 -------------------------------------------------------------------------------
 
@@ -13,15 +13,14 @@ TABLE OF CONTENTS
 1. Introduction
 
     1.1. Exim documentation
 1. Introduction
 
     1.1. Exim documentation
-    1.2. FTP and web sites
+    1.2. FTP site and websites
     1.3. Mailing lists
     1.3. Mailing lists
-    1.4. Exim training
-    1.5. Bug reports
-    1.6. Where to find the Exim distribution
-    1.7. Limitations
-    1.8. Run time configuration
-    1.9. Calling interface
-    1.10. Terminology
+    1.4. Bug reports
+    1.5. Where to find the Exim distribution
+    1.6. Limitations
+    1.7. Runtime configuration
+    1.8. Calling interface
+    1.9. Terminology
 
 2. Incorporated code
 3. How Exim receives and delivers mail
 
 2. Incorporated code
 3. How Exim receives and delivers mail
@@ -75,7 +74,7 @@ TABLE OF CONTENTS
     5.2. Trusted and admin users
     5.3. Command line options
 
     5.2. Trusted and admin users
     5.3. Command line options
 
-6. The Exim run time configuration file
+6. The Exim runtime configuration file
 
     6.1. Using a different configuration file
     6.2. Configuration file format
 
     6.1. Using a different configuration file
     6.2. Configuration file format
@@ -85,30 +84,32 @@ TABLE OF CONTENTS
     6.6. Redefining macros
     6.7. Overriding macro values
     6.8. Example of macro usage
     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
 
 
 7. The default configuration file
 
-    7.1. Main configuration settings
-    7.2. ACL configuration
-    7.3. Router configuration
-    7.4. Transport configuration
-    7.5. Default retry rule
-    7.6. Rewriting configuration
-    7.7. Authenticators configuration
+    7.1. Macros
+    7.2. Main configuration settings
+    7.3. ACL configuration
+    7.4. Router configuration
+    7.5. Transport configuration
+    7.6. Default retry rule
+    7.7. Rewriting configuration
+    7.8. Authenticators configuration
 
 8. Regular expressions
 9. File and database lookups
 
 8. Regular expressions
 9. File and database lookups
@@ -123,21 +124,23 @@ TABLE OF CONTENTS
     9.8. Lookup caching
     9.9. Quoting lookup data
     9.10. More about dnsdb
     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
 
 
 10. Domain, host, address, and local part lists
 
@@ -187,7 +190,7 @@ TABLE OF CONTENTS
     13.1. Starting a listening daemon
     13.2. Special IP listening addresses
     13.3. Overriding local_interfaces and daemon_smtp_ports
     13.1. Starting a listening daemon
     13.2. Special IP listening addresses
     13.3. Overriding local_interfaces and daemon_smtp_ports
-    13.4. Support for the obsolete SSMTP (or SMTPS) protocol
+    13.4. Support for the submissions (aka SSMTP or SMTPS) protocol
     13.5. IPv6 address scopes
     13.6. Disabling IPv6
     13.7. Examples of starting a listening daemon
     13.5. IPv6 address scopes
     13.6. Disabling IPv6
     13.7. Examples of starting a listening daemon
@@ -367,246 +370,262 @@ TABLE OF CONTENTS
     40.1. Using spa as a server
     40.2. Using spa as a client
 
     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 "submissions" (aka "ssmtp" and "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
+    42.15. DANE
+
+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
+    56.2. Format of the -D file
+
+57. DKIM and SPF
+
+    57.1. DKIM (DomainKeys Identified Mail)
+    57.2. Signing outgoing messages
+    57.3. Verifying DKIM signatures in incoming mail
+    57.4. SPF (Sender Policy Framework)
+
+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
 
 
 
 
 
 
@@ -622,7 +641,7 @@ Configuration files currently exist for the following operating systems: AIX,
 BSD/OS (aka BSDI), Darwin (Mac OS X), DGUX, Dragonfly, FreeBSD, GNU/Hurd, GNU/
 Linux, HI-OSF (Hitachi), HI-UX, HP-UX, IRIX, MIPS RISCOS, NetBSD, OpenBSD,
 OpenUNIX, QNX, SCO, SCO SVR4.2 (aka UNIX-SV), Solaris (aka SunOS5), SunOS4,
 BSD/OS (aka BSDI), Darwin (Mac OS X), DGUX, Dragonfly, FreeBSD, GNU/Hurd, GNU/
 Linux, HI-OSF (Hitachi), HI-UX, HP-UX, IRIX, MIPS RISCOS, NetBSD, OpenBSD,
 OpenUNIX, QNX, SCO, SCO SVR4.2 (aka UNIX-SV), Solaris (aka SunOS5), SunOS4,
-Tru64-Unix (formerly Digital UNIX, formerly DEC-OSF1), Ultrix, and Unixware.
+Tru64-Unix (formerly Digital UNIX, formerly DEC-OSF1), Ultrix, and UnixWare.
 Some of these operating systems are no longer current and cannot easily be
 tested, so the configuration files may no longer work in practice.
 
 Some of these operating systems are no longer current and cannot easily be
 tested, so the configuration files may no longer work in practice.
 
@@ -634,11 +653,11 @@ The terms and conditions for the use and distribution of Exim are contained in
 the file NOTICE. Exim is distributed under the terms of the GNU General Public
 Licence, a copy of which may be found in the file LICENCE.
 
 the file NOTICE. Exim is distributed under the terms of the GNU General Public
 Licence, a copy of which may be found in the file LICENCE.
 
-The use, supply or promotion of Exim for the purpose of sending bulk,
-unsolicited electronic mail is incompatible with the basic aims of the program,
-which revolve around the free provision of a service that enhances the quality
-of personal communications. The author of Exim regards indiscriminate
-mass-mailing as an antisocial, irresponsible abuse of the Internet.
+The use, supply, or promotion of Exim for the purpose of sending bulk,
+unsolicited electronic mail is incompatible with the basic aims of Exim, which
+revolve around the free provision of a service that enhances the quality of
+personal communications. The author of Exim regards indiscriminate mass-mailing
+as an antisocial, irresponsible abuse of the Internet.
 
 Exim owes a great deal to Smail 3 and its author, Ron Karr. Without the
 experience of running and working on the Smail 3 code, I could never have
 
 Exim owes a great deal to Smail 3 and its author, Ron Karr. Without the
 experience of running and working on the Smail 3 code, I could never have
@@ -655,8 +674,8 @@ ACKNOWLEDGMENTS, in which I have started recording the names of contributors.
 1.1 Exim documentation
 ----------------------
 
 1.1 Exim documentation
 ----------------------
 
-This edition of the Exim specification applies to version 4.84.2 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.92 of Exim.
+Substantive changes from the 4.91 edition are marked in some renditions of this
 document; this paragraph is so marked if the rendition is capable of showing a
 change indicator.
 
 document; this paragraph is so marked if the rendition is capable of showing a
 change indicator.
 
@@ -665,16 +684,16 @@ is expected to have some familiarity with the SMTP mail transfer protocol and
 with general Unix system administration. Although there are some discussions
 and examples in places, the information is mostly organized in a way that makes
 it easy to look up, rather than in a natural order for sequential reading.
 with general Unix system administration. Although there are some discussions
 and examples in places, the information is mostly organized in a way that makes
 it easy to look up, rather than in a natural order for sequential reading.
-Furthermore, the manual aims to cover every aspect of Exim in detail, including
-a number of rarely-used, special-purpose features that are unlikely to be of
-very wide interest.
+Furthermore, this manual aims to cover every aspect of Exim in detail,
+including a number of rarely-used, special-purpose features that are unlikely
+to be of very wide interest.
 
 An "easier" discussion of Exim which provides more in-depth explanatory,
 introductory, and tutorial material can be found in a book entitled The Exim
 
 An "easier" discussion of Exim which provides more in-depth explanatory,
 introductory, and tutorial material can be found in a book entitled The Exim
-SMTP Mail Server (second edition, 2007), published by UIT Cambridge (http://
+SMTP Mail Server (second edition, 2007), published by UIT Cambridge (https://
 www.uit.co.uk/exim-book/).
 
 www.uit.co.uk/exim-book/).
 
-This book also contains a chapter that gives a general introduction to SMTP and
+The book also contains a chapter that gives a general introduction to SMTP and
 Internet mail. Inevitably, however, the book is unlikely to be fully up-to-date
 with the latest release of Exim. (Note that the earlier book about Exim,
 published by O'Reilly, covers Exim 3, and many things have changed in Exim 4.)
 Internet mail. Inevitably, however, the book is unlikely to be fully up-to-date
 with the latest release of Exim. (Note that the earlier book about Exim,
 published by O'Reilly, covers Exim 3, and many things have changed in Exim 4.)
@@ -684,8 +703,8 @@ Debian-specific features in the file /usr/share/doc/exim4-base/README.Debian.
 The command man update-exim.conf is another source of Debian-specific
 information.
 
 The command man update-exim.conf is another source of Debian-specific
 information.
 
-As the program develops, there may be features in newer versions that have not
-yet made it into this document, which is updated only when the most significant
+As Exim develops, there may be features in newer versions that have not yet
+made it into this document, which is updated only when the most significant
 digit of the fractional part of the version number changes. Specifications of
 new features that are not yet in this manual are placed in the file doc/
 NewStuff in the Exim distribution.
 digit of the fractional part of the version number changes. Specifications of
 new features that are not yet in this manual are placed in the file doc/
 NewStuff in the Exim distribution.
@@ -695,8 +714,8 @@ incompatibly while they are developing, or even be withdrawn. For this reason,
 they are not documented in this manual. Information about experimental features
 can be found in the file doc/experimental.txt.
 
 they are not documented in this manual. Information about experimental features
 can be found in the file doc/experimental.txt.
 
-All changes to the program (whether new features, bug fixes, or other kinds of
-change) are noted briefly in the file called doc/ChangeLog.
+All changes to Exim (whether new features, bug fixes, or other kinds of change)
+are noted briefly in the file called doc/ChangeLog.
 
 This specification itself is available as an ASCII file in doc/spec.txt so that
 it can easily be searched with a text editor. Other files in the doc directory
 
 This specification itself is available as an ASCII file in doc/spec.txt so that
 it can easily be searched with a text editor. Other files in the doc directory
@@ -709,31 +728,32 @@ 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
 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
 
 The main specification and the specification of the filtering language are also
-available in other formats (HTML, PostScript, PDF, and Texinfo). Section 1.6
+available in other formats (HTML, PostScript, PDF, and Texinfo). Section 1.5
 below tells you how to get hold of these.
 
 
 below tells you how to get hold of these.
 
 
-1.2 FTP and web sites
----------------------
+1.2 FTP site and websites
+-------------------------
 
 
-The primary site for Exim source distributions is currently the University of
-Cambridge's FTP site, whose contents are described in Where to find the Exim
-distribution below. In addition, there is a web site and an FTP site at 
-exim.org. These are now also hosted at the University of Cambridge. The 
-exim.org site was previously hosted for a number of years by Energis Squared,
-formerly Planet Online Ltd, whose support I gratefully acknowledge.
+The primary site for Exim source distributions is the exim.org FTP site,
+available over HTTPS, HTTP and FTP. These services, and the exim.org website,
+are hosted at the University of Cambridge.
 
 
-As well as Exim distribution tar files, the Exim web site contains a number of
+As well as Exim distribution tar files, the Exim website contains a number of
 differently formatted versions of the documentation. A recent addition to the
 differently formatted versions of the documentation. A recent addition to the
-online information is the Exim wiki (http://wiki.exim.org), which contains what
-used to be a separate FAQ, as well as various other examples, tips, and
-know-how that have been contributed by Exim users.
+online information is the Exim wiki (https://wiki.exim.org), which contains
+what used to be a separate FAQ, as well as various other examples, tips, and
+know-how that have been contributed by Exim users. The wiki site should always
+redirect to the correct place, which is currently provided by GitHub, and is
+open to editing by anyone with a GitHub account.
 
 
-An Exim Bugzilla exists at http://bugs.exim.org. You can use this to report
+An Exim Bugzilla exists at https://bugs.exim.org. You can use this to report
 bugs, and also to add items to the wish list. Please search first to check that
 bugs, and also to add items to the wish list. Please search first to check that
-you are not duplicating a previous entry.
+you are not duplicating a previous entry. Please do not ask for configuration
+help in the bug-tracker.
 
 
 1.3 Mailing lists
 
 
 1.3 Mailing lists
@@ -752,83 +772,79 @@ are using a Debian distribution of Exim, you may wish to subscribe to the
 Debian-specific mailing list pkg-exim4-users@lists.alioth.debian.org via this
 web page:
 
 Debian-specific mailing list pkg-exim4-users@lists.alioth.debian.org via this
 web page:
 
-http://lists.alioth.debian.org/mailman/listinfo/pkg-exim4-users
+https://alioth-lists.debian.net/cgi-bin/mailman/listinfo/pkg-exim4-users
 
 
-Please ask Debian-specific questions on this list and not on the general Exim
+Please ask Debian-specific questions on that list and not on the general Exim
 lists.
 
 
 lists.
 
 
-1.4 Exim training
------------------
-
-Training courses in Cambridge (UK) used to be run annually by the author of
-Exim, before he retired. At the time of writing, there are no plans to run
-further Exim courses in Cambridge. However, if that changes, relevant
-information will be posted at http://www-tus.csx.cam.ac.uk/courses/exim/.
-
-
-1.5 Bug reports
+1.4 Bug reports
 ---------------
 
 Reports of obvious bugs can be emailed to bugs@exim.org or reported via the
 ---------------
 
 Reports of obvious bugs can be emailed to bugs@exim.org or reported via the
-Bugzilla (http://bugs.exim.org). However, if you are unsure whether some
+Bugzilla (https://bugs.exim.org). However, if you are unsure whether some
 behaviour is a bug or not, the best thing to do is to post a message to the 
 exim-dev mailing list and have it discussed.
 
 
 behaviour is a bug or not, the best thing to do is to post a message to the 
 exim-dev mailing list and have it discussed.
 
 
-1.6 Where to find the Exim distribution
+1.5 Where to find the Exim distribution
 ---------------------------------------
 
 ---------------------------------------
 
-The master ftp site for the Exim distribution is
+The master distribution site for the Exim distribution is
 
 
-ftp://ftp.csx.cam.ac.uk/pub/software/email/exim
+https://downloads.exim.org/
 
 
-This is mirrored by
+The service is available over HTTPS, HTTP and FTP. We encourage people to
+migrate to HTTPS.
 
 
-ftp://ftp.exim.org/pub/exim
+The content served at https://downloads.exim.org/ is identical to the content
+served at https://ftp.exim.org/pub/exim and ftp://ftp.exim.org/pub/exim.
 
 
-The file references that follow are relative to the exim directories at these
-sites. There are now quite a number of independent mirror sites around the
-world. Those that I know about are listed in the file called Mirrors.
+If accessing via a hostname containing ftp, then the file references that
+follow are relative to the exim directories at these sites. If accessing via
+the hostname downloads then the subdirectories described here are top-level
+directories.
 
 
-Within the exim directory there are subdirectories called exim3 (for previous
-Exim 3 distributions), exim4 (for the latest Exim 4 distributions), and Testing
-for testing versions. In the exim4 subdirectory, the current release can always
-be found in files called
+There are now quite a number of independent mirror sites around the world.
+Those that I know about are listed in the file called Mirrors.
 
 
+Within the top exim directory there are subdirectories called exim3 (for
+previous Exim 3 distributions), exim4 (for the latest Exim 4 distributions),
+and Testing for testing versions. In the exim4 subdirectory, the current
+release can always be found in files called
+
+exim-n.nn.tar.xz
 exim-n.nn.tar.gz
 exim-n.nn.tar.bz2
 
 exim-n.nn.tar.gz
 exim-n.nn.tar.bz2
 
-where n.nn is the highest such version number in the directory. The two files
-contain identical data; the only difference is the type of compression. The
-.bz2 file is usually a lot smaller than the .gz file.
+where n.nn is the highest such version number in the directory. The three files
+contain identical data; the only difference is the type of compression. The .xz
+file is usually the smallest, while the .gz file is the most portable to old
+systems.
 
 The distributions will be PGP signed by an individual key of the Release
 Coordinator. This key will have a uid containing an email address in the 
 exim.org domain and will have signatures from other people, including other
 Exim maintainers. We expect that the key will be in the "strong set" of PGP
 
 The distributions will be PGP signed by an individual key of the Release
 Coordinator. This key will have a uid containing an email address in the 
 exim.org domain and will have signatures from other people, including other
 Exim maintainers. We expect that the key will be in the "strong set" of PGP
-keys. There should be a trust path to that key from Nigel Metheringham's PGP
-key, a version of which can be found in the release directory in the file
-nigel-pubkey.asc. All keys used will be available in public keyserver pools,
-such as pool.sks-keyservers.net.
-
-At time of last update, releases were being made by Phil Pennock and signed
-with key 0x403043153903637F, although that key is expected to be replaced in
-2013. A trust path from Nigel's key to Phil's can be observed at https://
-www.security.spodhuis.org/exim-trustpath.
+keys. There should be a trust path to that key from the Exim Maintainer's PGP
+keys, a version of which can be found in the release directory in the file
+Exim-Maintainers-Keyring.asc. All keys used will be available in public
+keyserver pools, such as pool.sks-keyservers.net.
 
 
-Releases have also been authorized to be performed by Todd Lyons who signs with
-key 0xC4F4F94804D29EBA. A direct trust path exists between previous RE Phil
-Pennock and Todd Lyons through a common associate.
+At the time of the last update, releases were being made by Jeremy Harris and
+signed with key 0xBCE58C8CE41F32DF. Other recent keys used for signing are
+those of Heiko Schlittermann, 0x26101B62F69376CE, and of Phil Pennock, 
+0x4D1E900E14C1CC04.
 
 The signatures for the tar bundles are in:
 
 
 The signatures for the tar bundles are in:
 
+exim-n.nn.tar.xz.asc
 exim-n.nn.tar.gz.asc
 exim-n.nn.tar.bz2.asc
 
 exim-n.nn.tar.gz.asc
 exim-n.nn.tar.bz2.asc
 
-For each released version, the log of changes is made separately available in a
-separate file in the directory ChangeLogs so that it is possible to find out
-what has changed without having to download the entire distribution.
+For each released version, the log of changes is made available in a separate
+file in the directory ChangeLogs so that it is possible to find out what has
+changed without having to download the entire distribution.
 
 The main distribution contains ASCII versions of this specification and other
 documentation; other formats of the documents are available in separate files
 
 The main distribution contains ASCII versions of this specification and other
 documentation; other formats of the documents are available in separate files
@@ -840,10 +856,10 @@ exim-postscript-n.nn.tar.gz
 exim-texinfo-n.nn.tar.gz
 
 These tar files contain only the doc directory, not the complete distribution,
 exim-texinfo-n.nn.tar.gz
 
 These tar files contain only the doc directory, not the complete distribution,
-and are also available in .bz2 as well as .gz forms.
+and are also available in .bz2 and .xz forms.
 
 
 
 
-1.7 Limitations
+1.6 Limitations
 ---------------
 
   * Exim is designed for use as an Internet MTA, and therefore handles
 ---------------
 
   * Exim is designed for use as an Internet MTA, and therefore handles
@@ -879,17 +895,17 @@ and are also available in .bz2 as well as .gz forms.
     straightforward interfaces to a number of common scanners are provided.
 
 
     straightforward interfaces to a number of common scanners are provided.
 
 
-1.8 Run time configuration
---------------------------
+1.7 Runtime configuration
+-------------------------
 
 
-Exim's run time configuration is held in a single text file that is divided
-into a number of sections. The entries in this file consist of keywords and
-values, in the style of Smail 3 configuration files. A default configuration
-file which is suitable for simple online installations is provided in the
-distribution, and is described in chapter 7 below.
+Exim's runtime configuration is held in a single text file that is divided into
+a number of sections. The entries in this file consist of keywords and values,
+in the style of Smail 3 configuration files. A default configuration file which
+is suitable for simple online installations is provided in the distribution,
+and is described in chapter 7 below.
 
 
 
 
-1.9 Calling interface
+1.8 Calling interface
 ---------------------
 
 Like many MTAs, Exim has adopted the Sendmail command line interface so that it
 ---------------------
 
 Like many MTAs, Exim has adopted the Sendmail command line interface so that it
@@ -897,24 +913,24 @@ can be a straight replacement for /usr/lib/sendmail or /usr/sbin/sendmail when
 sending mail, but you do not need to know anything about Sendmail in order to
 run Exim. For actions other than sending messages, Sendmail-compatible options
 also exist, but those that produce output (for example, -bp, which lists the
 sending mail, but you do not need to know anything about Sendmail in order to
 run Exim. For actions other than sending messages, Sendmail-compatible options
 also exist, but those that produce output (for example, -bp, which lists the
-messages on the queue) do so in Exim's own format. There are also some
+messages in the queue) do so in Exim's own format. There are also some
 additional options that are compatible with Smail 3, and some further options
 that are new to Exim. Chapter 5 documents all Exim's command line options. This
 information is automatically made into the man page that forms part of the Exim
 distribution.
 
 additional options that are compatible with Smail 3, and some further options
 that are new to Exim. Chapter 5 documents all Exim's command line options. This
 information is automatically made into the man page that forms part of the Exim
 distribution.
 
-Control of messages on the queue can be done via certain privileged command
+Control of messages in the queue can be done via certain privileged command
 line options. There is also an optional monitor program called eximon, which
 displays current information in an X window, and which contains a menu
 interface to Exim's command line administration options.
 
 
 line options. There is also an optional monitor program called eximon, which
 displays current information in an X window, and which contains a menu
 interface to Exim's command line administration options.
 
 
-1.10 Terminology
-----------------
+1.9 Terminology
+---------------
 
 The body of a message is the actual data that the sender wants to transmit. It
 
 The body of a message is the actual data that the sender wants to transmit. It
-is the last part of a message, and is separated from the header (see below) by
-blank line.
+is the last part of a message and is separated from the header (see below) by a
+blank line.
 
 When a message cannot be delivered, it is normally returned to the sender in a
 delivery failure message or a "non-delivery report" (NDR). The term bounce is
 
 When a message cannot be delivered, it is normally returned to the sender in a
 delivery failure message or a "non-delivery report" (NDR). The term bounce is
@@ -950,9 +966,9 @@ number of lines, each of which has a name such as From:, To:, Subject:, etc.
 Long header lines can be split over several text lines by indenting the
 continuations. The header is separated from the body by a blank line.
 
 Long header lines can be split over several text lines by indenting the
 continuations. The header is separated from the body by a blank line.
 
-The term local part, which is taken from RFC 2822, is used to refer to that
-part of an email address that precedes the @ sign. The part that follows the @
-sign is called the domain or mail domain.
+The term local part, which is taken from RFC 2822, is used to refer to the part
+of an email address that precedes the @ sign. The part that follows the @ sign
+is called the domain or mail domain.
 
 The terms local delivery and remote delivery are used to distinguish delivery
 to a file or a pipe on the local host from delivery by SMTP over TCP/IP to
 
 The terms local delivery and remote delivery are used to distinguish delivery
 to a file or a pipe on the local host from delivery by SMTP over TCP/IP to
@@ -962,18 +978,18 @@ running on are remote.
 Return path is another name that is used for the sender address in a message's
 envelope.
 
 Return path is another name that is used for the sender address in a message's
 envelope.
 
-The term queue is used to refer to the set of messages awaiting delivery,
+The term queue is used to refer to the set of messages awaiting delivery
 because this term is in widespread use in the context of MTAs. However, in
 because this term is in widespread use in the context of MTAs. However, in
-Exim's case the reality is more like a pool than a queue, because there is
+Exim's case, the reality is more like a pool than a queue, because there is
 normally no ordering of waiting messages.
 
 The term queue runner is used to describe a process that scans the queue and
 attempts to deliver those messages whose retry times have come. This term is
 normally no ordering of waiting messages.
 
 The term queue runner is used to describe a process that scans the queue and
 attempts to deliver those messages whose retry times have come. This term is
-used by other MTAs, and also relates to the command runq, but in Exim the
+used by other MTAs and also relates to the command runq, but in Exim the
 waiting messages are normally processed in an unpredictable order.
 
 The term spool directory is used for a directory in which Exim keeps the
 waiting messages are normally processed in an unpredictable order.
 
 The term spool directory is used for a directory in which Exim keeps the
-messages on its queue - that is, those that it is in the process of delivering.
+messages in its queue - that is, those that it is in the process of delivering.
 This should not be confused with the directory in which local mailboxes are
 stored, which is called a "spool directory" by some people. In the Exim
 documentation, "spool" is always used in the first sense.
 This should not be confused with the directory in which local mailboxes are
 stored, which is called a "spool directory" by some people. In the Exim
 documentation, "spool" is always used in the first sense.
@@ -1005,9 +1021,9 @@ A number of pieces of external code are included in the Exim distribution.
         Free Software Foundation; either version 2 of the License, or (at your
         option) any later version. This code implements Dan Bernstein's
         Constant DataBase (cdb) spec. Information, the spec and sample code for
         Free Software Foundation; either version 2 of the License, or (at your
         option) any later version. This code implements Dan Bernstein's
         Constant DataBase (cdb) spec. Information, the spec and sample code for
-        cdb can be obtained from http://www.pobox.com/~djb/cdb.html. This
-        implementation borrows some code from Dan Bernstein's implementation
-        (which has no license restrictions applied to it).
+        cdb can be obtained from https://cr.yp.to/cdb.html. This implementation
+        borrows some code from Dan Bernstein's implementation (which has no
+        license restrictions applied to it).
 
   * Client support for Microsoft's Secure Password Authentication is provided
     by code contributed by Marc Prud'hommeaux. Server support was contributed
 
   * Client support for Microsoft's Secure Password Authentication is provided
     by code contributed by Marc Prud'hommeaux. Server support was contributed
@@ -1049,7 +1065,7 @@ A number of pieces of external code are included in the Exim distribution.
             acknowledgment:
 
             "This product includes software developed by Computing Services at
             acknowledgment:
 
             "This product includes software developed by Computing Services at
-            Carnegie Mellon University (http://www.cmu.edu/computing/."
+            Carnegie Mellon University (https://www.cmu.edu/computing/."
 
             CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
             THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
 
             CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
             THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
@@ -1094,7 +1110,7 @@ A number of pieces of external code are included in the Exim distribution.
     the distributed source code.
 
   * Many people have contributed code fragments, some large, some small, that
     the distributed source code.
 
   * Many people have contributed code fragments, some large, some small, that
-    were not covered by any specific licence requirements. It is assumed that
+    were not covered by any specific license requirements. It is assumed that
     the contributors are happy to see their code incorporated into Exim under
     the GPL.
 
     the contributors are happy to see their code incorporated into Exim under
     the GPL.
 
@@ -1119,9 +1135,9 @@ has been down, and it also maintains per-host retry information.
 ------------------
 
 Policy controls are now an important feature of MTAs that are connected to the
 ------------------
 
 Policy controls are now an important feature of MTAs that are connected to the
-Internet. Perhaps their most important job is to stop MTAs being abused as
+Internet. Perhaps their most important job is to stop MTAs from being abused as
 "open relays" by misguided individuals who send out vast amounts of unsolicited
 "open relays" by misguided individuals who send out vast amounts of unsolicited
-junk, and want to disguise its source. Exim provides flexible facilities for
+junk and want to disguise its source. Exim provides flexible facilities for
 specifying policy controls on incoming mail:
 
   * Exim 4 (unlike previous versions of Exim) implements policy controls on
 specifying policy controls on incoming mail:
 
   * Exim 4 (unlike previous versions of Exim) implements policy controls on
@@ -1131,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,
     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
     results in an SMTP error code.
 
   * An ACL is also available for locally generated, non-SMTP messages. In this
@@ -1145,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
   * 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
     accepted, the list of recipients can be modified by the function.
 
   * Using the local_scan() mechanism is another way of calling external scanner
@@ -1153,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
     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.
 
 
     the start of every delivery process.
 
 
@@ -1184,7 +1200,7 @@ long. It is divided into three parts, separated by hyphens, for example
 encoding numbers in base 62. However, in the Darwin operating system (Mac OS X)
 and when Exim is compiled to run under Cygwin, base 36 (avoiding the use of
 lower case letters) is used instead, because the message id is used to
 encoding numbers in base 62. However, in the Darwin operating system (Mac OS X)
 and when Exim is compiled to run under Cygwin, base 36 (avoiding the use of
 lower case letters) is used instead, because the message id is used to
-construct file names, and the names of files in those systems are not always
+construct filenames, and the names of files in those systems are not always
 case-sensitive.
 
 The detail of the contents of the message id have changed as Exim has evolved.
 case-sensitive.
 
 The detail of the contents of the message id have changed as Exim has evolved.
@@ -1235,8 +1251,8 @@ are several possibilities:
   * If the process runs Exim with the -bS option, the message is also read
     non-interactively, but in this case the recipients are listed at the start
     of the message in a series of SMTP RCPT commands, terminated by a DATA
   * If the process runs Exim with the -bS option, the message is also read
     non-interactively, but in this case the recipients are listed at the start
     of the message in a series of SMTP RCPT commands, terminated by a DATA
-    command. This is so-called "batch SMTP" format, but it isn't really SMTP.
-    The SMTP commands are just another way of passing envelope addresses in a
+    command. This is called "batch SMTP" format, but it isn't really SMTP. The
+    SMTP commands are just another way of passing envelope addresses in a
     non-interactive submission.
 
   * If the process runs Exim with the -bs option, the message is read
     non-interactive submission.
 
   * If the process runs Exim with the -bs option, the message is read
@@ -1255,7 +1271,7 @@ constructed from the login name of the user that called Exim and a default
 qualification domain (which can be set by the qualify_domain configuration
 option). For local or batch SMTP, a sender address that is passed using the
 SMTP MAIL command is ignored. However, the system administrator may allow
 qualification domain (which can be set by the qualify_domain configuration
 option). For local or batch SMTP, a sender address that is passed using the
 SMTP MAIL command is ignored. However, the system administrator may allow
-certain users ("trusted users") to specify a different sender address
+certain users ("trusted users") to specify a different sender addresses
 unconditionally, or all users to specify certain forms of different sender
 address. The -f option or the SMTP MAIL command is used to specify these
 different addresses. See section 5.2 for details of trusted users, and the 
 unconditionally, or all users to specify certain forms of different sender
 address. The -f option or the SMTP MAIL command is used to specify these
 different addresses. See section 5.2 for details of trusted users, and the 
@@ -1263,11 +1279,11 @@ untrusted_set_sender option for a way of allowing untrusted users to change
 sender addresses.
 
 Messages received by either of the non-interactive mechanisms are subject to
 sender addresses.
 
 Messages received by either of the non-interactive mechanisms are subject to
-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
+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
 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
+individual recipients or the entire message can be rejected if local policy
+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
 all incoming messages.
 
 Exim can be configured not to start a delivery process when a message is
@@ -1287,7 +1303,7 @@ the header lines, and the second contains the body of the message. The names of
 the two spool files consist of the message id, followed by "-H" for the file
 containing the envelope and header, and "-D" for the data file.
 
 the two spool files consist of the message id, followed by "-H" for the file
 containing the envelope and header, and "-D" for the data file.
 
-By default all these message files are held in a single directory called input
+By default, all these message files are held in a single directory called input
 inside the general Exim spool directory. Some operating systems do not perform
 very well if the number of files in a directory gets large; to improve
 performance in such cases, the split_spool_directory option can be used. This
 inside the general Exim spool directory. Some operating systems do not perform
 very well if the number of files in a directory gets large; to improve
 performance in such cases, the split_spool_directory option can be used. This
@@ -1301,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
 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
 
 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
@@ -1321,7 +1337,7 @@ chapters 15 and 24).
 A message remains in the spool directory until it is completely delivered to
 its recipients or to an error address, or until it is deleted by an
 administrator or by the user who originally created it. In cases when delivery
 A message remains in the spool directory until it is completely delivered to
 its recipients or to an error address, or until it is deleted by an
 administrator or by the user who originally created it. In cases when delivery
-cannot proceed - for example, when a message can neither be delivered to its
+cannot proceed - for example when a message can neither be delivered to its
 recipients nor returned to its sender, the message is marked "frozen" on the
 spool, and no more deliveries are attempted.
 
 recipients nor returned to its sender, the message is marked "frozen" on the
 spool, and no more deliveries are attempted.
 
@@ -1331,13 +1347,13 @@ administrator can force a delivery error, causing a bounce message to be sent.
 
 There are options called ignore_bounce_errors_after and timeout_frozen_after,
 which discard frozen messages after a certain time. The first applies only to
 
 There are options called ignore_bounce_errors_after and timeout_frozen_after,
 which discard frozen messages after a certain time. The first applies only to
-frozen bounces, the second to any frozen messages.
+frozen bounces, the second to all 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
 
 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
 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
+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
 message logs can be disabled by setting no_message_logs; this might give an
 improvement in performance on very busy systems.
 spool files when processing of a message is complete. The use of individual
 message logs can be disabled by setting no_message_logs; this might give an
 improvement in performance on very busy systems.
@@ -1351,11 +1367,11 @@ updated to indicate which these are, and the journal file is then deleted.
 Updating the spool file is done by writing a new file and renaming it, to
 minimize the possibility of data loss.
 
 Updating the spool file is done by writing a new file and renaming it, to
 minimize the possibility of data loss.
 
-Should the system or the program crash after a successful delivery but before
-the spool file has been updated, the journal is left lying around. The next
-time Exim attempts to deliver the message, it reads the journal file and
-updates the spool file before proceeding. This minimizes the chances of double
-deliveries caused by crashes.
+Should the system or Exim crash after a successful delivery but before the
+spool file has been updated, the journal is left lying around. The next time
+Exim attempts to deliver the message, it reads the journal file and updates the
+spool file before proceeding. This minimizes the chances of double deliveries
+caused by crashes.
 
 
 3.8 Processing an address for delivery
 
 
 3.8 Processing an address for delivery
@@ -1364,10 +1380,10 @@ deliveries caused by crashes.
 The main delivery processing elements of Exim are called routers and transports
 , and collectively these are known as drivers. Code for a number of them is
 provided in the source distribution, and compile-time options specify which
 The main delivery processing elements of Exim are called routers and transports
 , and collectively these are known as drivers. Code for a number of them is
 provided in the source distribution, and compile-time options specify which
-ones are included in the binary. Run time options specify which ones are
+ones are included in the binary. Runtime options specify which ones are
 actually used for delivering messages.
 
 actually used for delivering messages.
 
-Each driver that is specified in the run time configuration is an instance of
+Each driver that is specified in the runtime configuration is an instance of
 that particular driver type. Multiple instances are allowed; for example, you
 can set up several different smtp transports, each with different option values
 that might specify different ports or different timeouts. Each instance has its
 that particular driver type. Multiple instances are allowed; for example, you
 can set up several different smtp transports, each with different option values
 that might specify different ports or different timeouts. Each instance has its
@@ -1400,14 +1416,14 @@ routers in many different ways, and there may be any number of routers in a
 configuration.
 
 The first router that is specified in a configuration is often one that handles
 configuration.
 
 The first router that is specified in a configuration is often one that handles
-addresses in domains that are not recognized specially by the local host. These
-are typically addresses for arbitrary domains on the Internet. A precondition
-is set up which looks for the special domains known to the host (for example,
-its own domain name), and the router is run for addresses that do not match.
-Typically, this is a router that looks up domains in the DNS in order to find
-the hosts to which this address routes. If it succeeds, the address is assigned
-to a suitable SMTP transport; if it does not succeed, the router is configured
-to fail the address.
+addresses in domains that are not recognized specifically by the local host.
+Typically these are addresses for arbitrary domains on the Internet. A
+precondition is set up which looks for the special domains known to the host
+(for example, its own domain name), and the router is run for addresses that do
+not match. Typically, this is a router that looks up domains in the DNS in
+order to find the hosts to which this address routes. If it succeeds, the
+address is assigned to a suitable SMTP transport; if it does not succeed, the
+router is configured to fail the address.
 
 The second router is reached only when the domain is recognized as one that
 "belongs" to the local host. This router does redirection - also known as
 
 The second router is reached only when the domain is recognized as one that
 "belongs" to the local host. This router does redirection - also known as
@@ -1437,7 +1453,7 @@ When an address is being verified, the routers are run in "verify mode". This
 does not affect the way the routers work, but it is a state that can be
 detected. By this means, a router can be skipped or made to behave differently
 when verifying. A common example is a configuration in which the first router
 does not affect the way the routers work, but it is a state that can be
 detected. By this means, a router can be skipped or made to behave differently
 when verifying. A common example is a configuration in which the first router
-sends all messages to a message-scanning program, unless they have been
+sends all messages to a message-scanning program unless they have been
 previously scanned. Thus, the first router accepts all addresses without any
 checking, making it useless for verifying. Normally, the no_verify option would
 be set for such a router, causing it to be skipped in verify mode.
 previously scanned. Thus, the first router accepts all addresses without any
 checking, making it useless for verifying. Normally, the no_verify option would
 be set for such a router, causing it to be skipped in verify mode.
@@ -1453,12 +1469,12 @@ router is run. What happens next depends on the outcome, which is one of the
 following:
 
   * accept: The router accepts the address, and either assigns it to a
 following:
 
   * accept: The router accepts the address, and either assigns it to a
-    transport, or generates one or more "child" addresses. Processing the
-    original address ceases, unless the unseen option is set on the router.
-    This option can be used to set up multiple deliveries with different
-    routing (for example, for keeping archive copies of messages). When unseen
-    is set, the address is passed to the next router. Normally, however, an 
-    accept return marks the end of routing.
+    transport or generates one or more "child" addresses. Processing the
+    original address ceases unless the unseen option is set on the router. This
+    option can be used to set up multiple deliveries with different routing
+    (for example, for keeping archive copies of messages). When unseen is set,
+    the address is passed to the next router. Normally, however, an accept
+    return marks the end of routing.
 
     Any child addresses generated by the router are processed independently,
     starting with the first router by default. It is possible to change this by
 
     Any child addresses generated by the router are processed independently,
     starting with the first router by default. It is possible to change this by
@@ -1467,7 +1483,7 @@ following:
     redirect_router may be anywhere in the router configuration.
 
   * pass: The router recognizes the address, but cannot handle it itself. It
     redirect_router may be anywhere in the router configuration.
 
   * pass: The router recognizes the address, but cannot handle it itself. It
-    requests that the address be passed to another router. By default the
+    requests that the address be passed to another router. By default, the
     address is passed to the next router, but this can be changed by setting
     the pass_router option. However, (unlike redirect_router) the named router
     must be below the current router (to avoid loops).
     address is passed to the next router, but this can be changed by setting
     the pass_router option. However, (unlike redirect_router) the named router
     must be below the current router (to avoid loops).
@@ -1507,8 +1523,8 @@ for this purpose.
 ------------------------
 
 Once routing is complete, Exim scans the addresses that are assigned to local
 ------------------------
 
 Once routing is complete, Exim scans the addresses that are assigned to local
-and remote transports, and discards any duplicates that it finds. During this
-check, local parts are treated as case-sensitive. This happens only when
+and remote transports and discards any duplicates that it finds. During this
+check, local parts are treated case-sensitively. This happens only when
 actually delivering a message; when testing routers with -bt, all the routed
 addresses are shown.
 
 actually delivering a message; when testing routers with -bt, all the routed
 addresses are shown.
 
@@ -1605,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.)
 
     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
     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
@@ -1613,7 +1629,7 @@ When a message is to be delivered, the sequence of events is as follows:
     condition first_delivery can be used to detect the first run of the system
     filter.
 
     condition first_delivery can be used to detect the first run of the system
     filter.
 
-  * Each recipient address is offered to each configured router in turn,
+  * Each recipient address is offered to each configured router, in turn,
     subject to its preconditions, until one is able to handle it. If no router
     can handle the address, that is, if they all decline, the address is
     failed. Because routers can be targeted at particular domains, several
     subject to its preconditions, until one is able to handle it. If no router
     can handle the address, that is, if they all decline, the address is
     failed. Because routers can be targeted at particular domains, several
@@ -1684,9 +1700,9 @@ When a message is to be delivered, the sequence of events is as follows:
 Exim's mechanism for retrying messages that fail to get delivered at the first
 attempt is the queue runner process. You must either run an Exim daemon that
 uses the -q option with a time interval to start queue runners at regular
 Exim's mechanism for retrying messages that fail to get delivered at the first
 attempt is the queue runner process. You must either run an Exim daemon that
 uses the -q option with a time interval to start queue runners at regular
-intervals, or use some other means (such as cron) to start them. If you do not
+intervals or use some other means (such as cron) to start them. If you do not
 arrange for queue runners to be run, messages that fail temporarily at the
 arrange for queue runners to be run, messages that fail temporarily at the
-first attempt will remain on your queue for ever. A queue runner process works
+first attempt will remain in your queue forever. A queue runner process works
 its way through the queue, one message at a time, trying each delivery that has
 passed its retry time. You can run several queue runners at once.
 
 its way through the queue, one message at a time, trying each delivery that has
 passed its retry time. You can run several queue runners at once.
 
@@ -1730,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.
 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
 
 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
@@ -1741,14 +1757,14 @@ 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
 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
 ----------------------------------------
 
 If a bounce message (either locally generated or received from a remote host)
 
 
 3.17 Failures to deliver bounce messages
 ----------------------------------------
 
 If a bounce message (either locally generated or received from a remote host)
-itself suffers a permanent delivery failure, the message is left on the queue,
+itself suffers a permanent delivery failure, the message is left in the queue,
 but it is frozen, awaiting the attention of an administrator. There are options
 that can be used to make Exim discard such failed messages, or to keep them for
 only a short time (see timeout_frozen_after and ignore_bounce_errors_after).
 but it is frozen, awaiting the attention of an administrator. There are options
 that can be used to make Exim discard such failed messages, or to keep them for
 only a short time (see timeout_frozen_after and ignore_bounce_errors_after).
@@ -1764,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 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.2) into which the following files are placed:
+exim-4.92) into which the following files are placed:
 
     ACKNOWLEDGMENTS contains some acknowledgments
     CHANGES         contains a reference to where changes are documented
 
     ACKNOWLEDGMENTS contains some acknowledgments
     CHANGES         contains a reference to where changes are documented
@@ -1784,9 +1800,9 @@ subdirectories are created:
     src          remaining source files
     util         independent utilities
 
     src          remaining source files
     util         independent utilities
 
-The main utility programs are contained in the src directory, and are built
-with the Exim binary. The util directory contains a few optional scripts that
-may be useful to some sites.
+The main utility programs are contained in the src directory and are built with
+the Exim binary. The util directory contains a few optional scripts that may be
+useful to some sites.
 
 
 4.2 Multiple machine architectures and operating systems
 
 
 4.2 Multiple machine architectures and operating systems
@@ -1799,7 +1815,7 @@ build directory is created for each architecture and operating system. Symbolic
 links to the sources are installed in this directory, which is where the actual
 building takes place. In most cases, Exim can discover the machine architecture
 and operating system for itself, but the defaults can be overridden if
 links to the sources are installed in this directory, which is where the actual
 building takes place. In most cases, Exim can discover the machine architecture
 and operating system for itself, but the defaults can be overridden if
-necessary.
+necessary. A C99-capable compiler will be required for the build.
 
 
 4.3 PCRE library
 
 
 4.3 PCRE library
@@ -1807,14 +1823,14 @@ necessary.
 
 Exim no longer has an embedded PCRE library as the vast majority of modern
 systems include PCRE as a system library, although you may need to install the
 
 Exim no longer has an embedded PCRE library as the vast majority of modern
 systems include PCRE as a system library, although you may need to install the
-PCRE or PCRE development package for your operating system. If your system has
-a normal PCRE installation the Exim build process will need no further
-configuration. If the library or the headers are in an unusual location you
-will need to either set the PCRE_LIBS and INCLUDE directives appropriately, or
-set PCRE_CONFIG=yes to use the installed pcre-config command. If your operating
-system has no PCRE support then you will need to obtain and build the current
-PCRE from ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/. More
-information on PCRE is available at http://www.pcre.org/.
+PCRE package or the PCRE development package for your operating system. If your
+system has a normal PCRE installation the Exim build process will need no
+further configuration. If the library or the headers are in an unusual location
+you will need to either set the PCRE_LIBS and INCLUDE directives appropriately,
+or set PCRE_CONFIG=yes to use the installed pcre-config command. If your
+operating system has no PCRE support then you will need to obtain and build the
+current PCRE from ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/. More
+information on PCRE is available at https://www.pcre.org/.
 
 
 4.4 DBM libraries
 
 
 4.4 DBM libraries
@@ -1847,8 +1863,8 @@ possibilities:
 
  2. The GNU library, gdbm, operates on a single file. If used via its ndbm
     compatibility interface it makes two different hard links to it with names
 
  2. The GNU library, gdbm, operates on a single file. If used via its ndbm
     compatibility interface it makes two different hard links to it with names
-    dbmfile.dir and dbmfile.pag, but if used via its native interface, the file
-    name is used unmodified.
+    dbmfile.dir and dbmfile.pag, but if used via its native interface, the
+    filename is used unmodified.
 
  3. The Berkeley DB package, if called via its ndbm compatibility interface,
     operates on a single file called dbmfile.db, but otherwise looks to the
 
  3. The Berkeley DB package, if called via its ndbm compatibility interface,
     operates on a single file called dbmfile.db, but otherwise looks to the
@@ -1860,13 +1876,18 @@ possibilities:
 
  5. To complicate things further, there are several very different versions of
     the Berkeley DB package. Version 1.85 was stable for a very long time,
 
  5. To complicate things further, there are several very different versions of
     the Berkeley DB package. Version 1.85 was stable for a very long time,
-    releases 2.x and 3.x were current for a while, but the latest versions are
-    now numbered 4.x. Maintenance of some of the earlier releases has ceased.
-    All versions of Berkeley DB can be obtained from http://www.sleepycat.com/.
-
- 6. Yet another DBM library, called tdb, is available from http://
-    download.sourceforge.net/tdb. It has its own interface, and also operates
-    on a single file.
+    releases 2.x and 3.x were current for a while, but the latest versions when
+    Exim last revamped support were numbered 4.x. Maintenance of some of the
+    earlier releases has ceased. All versions of Berkeley DB could be obtained
+    from http://www.sleepycat.com/, which is now a redirect to their new
+    owner's page with far newer versions listed. It is probably wise to plan to
+    move your storage configurations away from Berkeley DB format, as today
+    there are smaller and simpler alternatives more suited to Exim's usage
+    model.
+
+ 6. Yet another DBM library, called tdb, is available from https://
+    sourceforge.net/projects/tdb/files/. It has its own interface, and also
+    operates on a single file.
 
 Exim and its utilities can be compiled to use any of these interfaces. In order
 to use any version of the Berkeley DB package in native mode, you must set
 
 Exim and its utilities can be compiled to use any of these interfaces. In order
 to use any version of the Berkeley DB package in native mode, you must set
@@ -1917,17 +1938,17 @@ first time, the simplest thing to do is to copy src/EDITME to Local/Makefile,
 then read it and edit it appropriately.
 
 There are three settings that you must supply, because Exim will not build
 then read it and edit it appropriately.
 
 There are three settings that you must supply, because Exim will not build
-without them. They are the location of the run time configuration file
+without them. They are the location of the runtime configuration file
 (CONFIGURE_FILE), the directory in which Exim binaries will be installed
 (BIN_DIRECTORY), and the identity of the Exim user (EXIM_USER and maybe
 EXIM_GROUP as well). The value of CONFIGURE_FILE can in fact be a
 (CONFIGURE_FILE), the directory in which Exim binaries will be installed
 (BIN_DIRECTORY), and the identity of the Exim user (EXIM_USER and maybe
 EXIM_GROUP as well). The value of CONFIGURE_FILE can in fact be a
-colon-separated list of file names; Exim uses the first of them that exists.
+colon-separated list of filenames; Exim uses the first of them that exists.
 
 There are a few other parameters that can be specified either at build time or
 
 There are a few other parameters that can be specified either at build time or
-at run time, to enable the same binary to be used on a number of different
+at runtime, to enable the same binary to be used on a number of different
 machines. However, if the locations of Exim's spool directory and log file
 directory (if not within the spool directory) are fixed, it is recommended that
 machines. However, if the locations of Exim's spool directory and log file
 directory (if not within the spool directory) are fixed, it is recommended that
-you specify them in Local/Makefile instead of at run time, so that errors
+you specify them in Local/Makefile instead of at runtime, so that errors
 detected early in Exim's execution (such as a malformed configuration file) can
 be logged.
 
 detected early in Exim's execution (such as a malformed configuration file) can
 be logged.
 
@@ -1938,7 +1959,7 @@ facilities, you need to set
 WITH_CONTENT_SCAN=yes
 
 in your Local/Makefile. For details of the facilities themselves, see chapter
 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
 
 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
@@ -1949,8 +1970,8 @@ empty, but it must exist.
 This is all the configuration that is needed in straightforward cases for known
 operating systems. However, the building process is set up so that it is easy
 to override options that are set by default or by operating-system-specific
 This is all the configuration that is needed in straightforward cases for known
 operating systems. However, the building process is set up so that it is easy
 to override options that are set by default or by operating-system-specific
-configuration files, for example to change the name of the C compiler, which
-defaults to gcc. See section 4.13 below for details of how to do this.
+configuration files, for example, to change the C compiler, which defaults to 
+gcc. See section 4.13 below for details of how to do this.
 
 
 4.6 Support for iconv()
 
 
 4.6 Support for iconv()
@@ -1961,11 +1982,11 @@ 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
 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
 
 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/
+many conversions. The GNU libiconv library (available from https://www.gnu.org/
 software/libiconv/) can be installed on such systems to remedy this deficiency,
 as well as on systems that do not supply iconv() at all. After installing 
 libiconv, you should add
 software/libiconv/) can be installed on such systems to remedy this deficiency,
 as well as on systems that do not supply iconv() at all. After installing 
 libiconv, you should add
@@ -2027,7 +2048,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
 
 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
 
 
 4.8 Use of tcpwrappers
@@ -2071,10 +2092,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
 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
 
 
 4.10 Dynamically loaded lookup module support
@@ -2110,9 +2129,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.
 
 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.
 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.
@@ -2220,7 +2236,7 @@ need to be installed before compiling Exim. However, there are some optional
 lookup types (such as cdb) for which the code is entirely contained within
 Exim, and no external include files or libraries are required. When a lookup
 type is not included in the binary, attempts to configure Exim to use it cause
 lookup types (such as cdb) for which the code is entirely contained within
 Exim, and no external include files or libraries are required. When a lookup
 type is not included in the binary, attempts to configure Exim to use it cause
-run time configuration errors.
+runtime configuration errors.
 
 Many systems now use a tool called pkg-config to encapsulate information about
 how to compile against a library; Exim has some initial support for being able
 
 Many systems now use a tool called pkg-config to encapsulate information about
 how to compile against a library; Exim has some initial support for being able
@@ -2310,8 +2326,8 @@ As with Exim itself, the final three files need not exist, and in this case the
 OS/eximon.conf-<ostype> file is also optional. The default values in OS/
 eximon.conf-Default can be overridden dynamically by setting environment
 variables of the same name, preceded by EXIMON_. For example, setting
 OS/eximon.conf-<ostype> file is also optional. The default values in OS/
 eximon.conf-Default can be overridden dynamically by setting environment
 variables of the same name, preceded by EXIMON_. For example, setting
-EXIMON_LOG_DEPTH in the environment overrides the value of LOG_DEPTH at run
-time.
+EXIMON_LOG_DEPTH in the environment overrides the value of LOG_DEPTH at
+runtime.
 
 
 4.16 Installing Exim binaries and scripts
 
 
 4.16 Installing Exim binaries and scripts
@@ -2325,15 +2341,15 @@ 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
 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).
 
 details).
 
-Exim's run time configuration file is named by the CONFIGURE_FILE setting in
+Exim's runtime configuration file is named by the CONFIGURE_FILE setting in
 Local/Makefile. If this names a single file, and the file does not exist, the
 default configuration file src/configure.default is copied there by the
 Local/Makefile. If this names a single file, and the file does not exist, the
 default configuration file src/configure.default is copied there by the
-installation script. If a run time configuration file already exists, it is
-left alone. If CONFIGURE_FILE is a colon-separated list, naming several
-alternative files, no default is installed.
+installation script. If a runtime configuration file already exists, it is left
+alone. If CONFIGURE_FILE is a colon-separated list, naming several alternative
+files, no default is installed.
 
 One change is made to the default configuration file when it is installed: the
 default configuration contains a router that references a system aliases file.
 
 One change is made to the default configuration file when it is installed: the
 default configuration contains a router that references a system aliases file.
@@ -2374,7 +2390,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 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.2-1. The script then arranges for a symbolic link called
+for example, exim-4.92-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).
 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).
@@ -2415,8 +2431,8 @@ make INSTALL_ARG='-no_symlink exim' install
 
 Not all systems use the GNU info system for documentation, and for this reason,
 the Texinfo source of Exim's documentation is not included in the main
 
 Not all systems use the GNU info system for documentation, and for this reason,
 the Texinfo source of Exim's documentation is not included in the main
-distribution. Instead it is available separately from the ftp site (see section
-1.6).
+distribution. Instead it is available separately from the FTP site (see section
+1.5).
 
 If you have defined INFO_DIRECTORY in Local/Makefile and the Texinfo source of
 the documentation is found in the source tree, running "make install"
 
 If you have defined INFO_DIRECTORY in Local/Makefile and the Texinfo source of
 the documentation is found in the source tree, running "make install"
@@ -2435,7 +2451,7 @@ necessary.
 4.19 Testing
 ------------
 
 4.19 Testing
 ------------
 
-Having installed Exim, you can check that the run time configuration file is
+Having installed Exim, you can check that the runtime configuration file is
 syntactically valid by running the following command, which assumes that the
 Exim binary directory is within your PATH environment variable:
 
 syntactically valid by running the following command, which assumes that the
 Exim binary directory is within your PATH environment variable:
 
@@ -2502,7 +2518,7 @@ can be used to check out policy controls on incoming SMTP mail.
 
 Testing a new version on a system that is already running Exim can most easily
 be done by building a binary with a different CONFIGURE_FILE setting. From
 
 Testing a new version on a system that is already running Exim can most easily
 be done by building a binary with a different CONFIGURE_FILE setting. From
-within the run time configuration, all other file and directory names that Exim
+within the runtime configuration, all other file and directory names that Exim
 uses can be altered, in order to keep it entirely clear of the production
 version.
 
 uses can be altered, in order to keep it entirely clear of the production
 version.
 
@@ -2762,18 +2778,22 @@ brief message about itself and exits.
     data. A line history is supported.
 
     Long expansion expressions can be split over several lines by using
     data. A line history is supported.
 
     Long expansion expressions can be split over several lines by using
-    backslash 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.
+    backslash continuations. As in Exim's runtime 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, $qualify_domain)
     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
     trying the same lookup again. Otherwise, because each Exim process caches
     the results of lookups, you will just get the same result as before.
 
 
     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
     trying the same lookup again. Otherwise, because each Exim process caches
     the results of lookups, you will just get the same result as before.
 
+    Macro processing is done on lines before string-expansion: new macros can
+    be defined and macros will be expanded. Because macros in the config file
+    are often used for secrets, those are only available to admin users.
+
 -bem <filename>
 
     This option operates like -be except that it must be followed by the name
 -bem <filename>
 
     This option operates like -be except that it must be followed by the name
@@ -2883,7 +2903,7 @@ brief message about itself and exits.
     actually perform an ident callout when testing using -bh because there is
     no incoming SMTP connection.
 
     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.
 
     skipped when testing using -bh. If you want these callouts to occur, use 
     -bhc instead.
 
@@ -2896,7 +2916,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
 
     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
 
     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
@@ -2961,7 +2981,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
     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.
 
     The return code is zero if the message is successfully accepted. Otherwise,
     the action is controlled by the -oex option setting - see below.
@@ -2985,8 +3005,9 @@ brief message about itself and exits.
 
 -bmalware <filename>
 
 
 -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
     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
@@ -3036,10 +3057,13 @@ brief message about itself and exits.
 
     mysql_servers = <value not displayable>
 
 
     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 runtime
+    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.
 
     If the -n flag is given, then for most modes of -bP operation the name will
     not be output.
@@ -3070,13 +3094,14 @@ brief message about itself and exits.
     settings can be obtained by using routers, transports, or authenticators.
 
     If environment is given as an argument, the set of environment variables is
     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 supresses the value of the
+    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
     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
-    per line.
+    per line. For the "-bP macro <name>" form, if no such macro is found the
+    exit status will be nonzero.
 
 -bp
 
 
 -bp
 
@@ -3086,13 +3111,13 @@ brief message about itself and exits.
     an admin user. However, the queue_list_requires_admin option can be set
     false to allow any user to see the queue.
 
     an admin user. However, the queue_list_requires_admin option can be set
     false to allow any user to see the queue.
 
-    Each message on the queue is displayed as in the following example:
+    Each message in the queue is displayed as in the following example:
 
     25m  2.9K 0t5C6f-0000c8-00 <alice@wonderland.fict.example>
               red.king@looking-glass.fict.example
               <other addresses>
 
 
     25m  2.9K 0t5C6f-0000c8-00 <alice@wonderland.fict.example>
               red.king@looking-glass.fict.example
               <other addresses>
 
-    The first line contains the length of time the message has been on the
+    The first line contains the length of time the message has been in the
     queue (in this case 25 minutes), the size of the message (2.9K), the unique
     local identifier for the message, and the message sender, as contained in
     the envelope. For bounce messages, the sender address is empty, and appears
     queue (in this case 25 minutes), the size of the message (2.9K), the unique
     local identifier for the message, and the message sender, as contained in
     the envelope. For bounce messages, the sender address is empty, and appears
@@ -3119,7 +3144,7 @@ brief message about itself and exits.
 
 -bpc
 
 
 -bpc
 
-    This option counts the number of messages on the queue, and writes the
+    This option counts the number of messages in the queue, and writes the
     total to the standard output. It is restricted to admin users, unless 
     queue_list_requires_admin is set false.
 
     total to the standard output. It is restricted to admin users, unless 
     queue_list_requires_admin is set false.
 
@@ -3127,7 +3152,7 @@ brief message about itself and exits.
 
     This option operates like -bp, but the output is not sorted into
     chronological order of message arrival. This can speed it up when there are
 
     This option operates like -bp, but the output is not sorted into
     chronological order of message arrival. This can speed it up when there are
-    lots of messages on the queue, and is particularly useful if the output is
+    lots of messages in the queue, and is particularly useful if the output is
     going to be post-processed in a way that doesn't need the sorting.
 
 -bpra
     going to be post-processed in a way that doesn't need the sorting.
 
 -bpra
@@ -3191,7 +3216,7 @@ brief message about itself and exits.
     follow.
 
     As for other local message submissions, the contents of incoming batch SMTP
     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.
 
     Unqualified addresses are automatically qualified using qualify_domain and 
     qualify_recipient, as appropriate, unless the -bnq option is used.
 
@@ -3204,13 +3229,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.
 
     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
 
 -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.
 
     applied. Some user agents use this interface as a way of passing
     locally-generated messages to the MTA.
 
@@ -3275,7 +3300,7 @@ brief message about itself and exits.
     number, and compilation date of the exim binary to the standard output. It
     also lists the DBM library that is being used, the optional modules (such
     as specific lookup types), the drivers that are included in the binary, and
     number, and compilation date of the exim binary to the standard output. It
     also lists the DBM library that is being used, the optional modules (such
     as specific lookup types), the drivers that are included in the binary, and
-    the name of the run time configuration file that is in use.
+    the name of the runtime configuration file that is in use.
 
     As part of its operation, -bV causes Exim to read and syntax check its
     configuration file. However, this is a static check only. It cannot check
 
     As part of its operation, -bV causes Exim to read and syntax check its
     configuration file. However, this is a static check only. It cannot check
@@ -3291,7 +3316,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
     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
     including callouts, see the -bh and -bhc options.
 
     If verification fails, and the caller is not an admin user, no details of
@@ -3354,15 +3379,13 @@ brief message about itself and exits.
 
 -C <filelist>
 
 
 -C <filelist>
 
-    This option causes Exim to find the run time configuration file from the
+    This option causes Exim to find the runtime configuration file from the
     given list instead of from the list specified by the CONFIGURE_FILE
     given list instead of from the list specified by the CONFIGURE_FILE
-    compile-time setting. Usually, the list will consist of just a single file
-    name, but it can be a colon-separated list of names. In this case, the
+    compile-time setting. Usually, the list will consist of just a single
+    filename, but it can be a colon-separated list of names. In this case, the
     first file that exists is used. Failure to open an existing file stops Exim
     from proceeding any further along the list, and an error is generated.
 
     first file that exists is used. Failure to open an existing file stops Exim
     from proceeding any further along the list, and an error is generated.
 
-    The file names need to be absolute names.
-
     When this option is used by a caller other than root, and the list is
     different from the compiled-in list, Exim gives up its root privilege
     immediately, and runs with the real and effective uid and gid set to those
     When this option is used by a caller other than root, and the list is
     different from the compiled-in list, Exim gives up its root privilege
     immediately, and runs with the real and effective uid and gid set to those
@@ -3379,15 +3402,15 @@ brief message about itself and exits.
     running as the Exim user, so when it re-executes to regain privilege for
     the delivery, the use of -C causes privilege to be lost. However, root can
     test reception and delivery using two separate commands (one to put a
     running as the Exim user, so when it re-executes to regain privilege for
     the delivery, the use of -C causes privilege to be lost. However, root can
     test reception and delivery using two separate commands (one to put a
-    message on the queue, using -odq, and another to do the delivery, using -M
+    message in the queue, using -odq, and another to do the delivery, using -M
     ).
 
     If ALT_CONFIG_PREFIX is defined in Local/Makefile, it specifies a prefix
     string with which any file named in a -C command line option must start. In
     ).
 
     If ALT_CONFIG_PREFIX is defined in Local/Makefile, it specifies a prefix
     string with which any file named in a -C command line option must start. In
-    addition, the file name must not contain the sequence "/../". However, if
+    addition, the filename must not contain the sequence "/../". However, if
     the value of the -C option is identical to the value of CONFIGURE_FILE in
     Local/Makefile, Exim ignores -C and proceeds as usual. There is no default
     the value of the -C option is identical to the value of CONFIGURE_FILE in
     Local/Makefile, Exim ignores -C and proceeds as usual. There is no default
-    setting for ALT_CONFIG_PREFIX; when it is unset, any file name can be used
+    setting for ALT_CONFIG_PREFIX; when it is unset, any filename can be used
     with -C.
 
     ALT_CONFIG_PREFIX can be used to confine alternative configuration files to
     with -C.
 
     ALT_CONFIG_PREFIX can be used to confine alternative configuration files to
@@ -3431,7 +3454,8 @@ brief message about itself and exits.
 
     exim '-D ABC = something' ...
 
 
     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>
 
 
 -d<debug options>
 
@@ -3465,10 +3489,11 @@ brief message about itself and exits.
     interface       lists of local interfaces
     lists           matching things in lists
     load            system load checks
     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
     lookup          general lookup code and all lookups
     memory          memory handling
-    pid             add pid to debug output lines
+    noutf8          modifier: avoid UTF-8 line-drawing
+    pid             modifier: add pid to debug output lines
     process_info    setting info for the process log
     queue_run       queue runs
     receive         general message reception logic
     process_info    setting info for the process log
     queue_run       queue runs
     receive         general message reception logic
@@ -3476,7 +3501,7 @@ brief message about itself and exits.
     retry           retry handling
     rewrite         address rewriting
     route           address routing
     retry           retry handling
     rewrite         address rewriting
     route           address routing
-    timestamp       add timestamp to debug output lines
+    timestamp       modifier: add timestamp to debug output lines
     tls             TLS logic
     transport       transports
     uid             changes of uid/gid and looking up uid/gid
     tls             TLS logic
     transport       transports
     uid             changes of uid/gid and looking up uid/gid
@@ -3505,6 +3530,10 @@ brief message about itself and exits.
     start of all debug output lines. This can be useful when trying to track
     down delays in processing.
 
     start of all debug output lines. This can be useful when trying to track
     down delays in processing.
 
+    The "noutf8" selector disables the use of UTF-8 line-drawing characters to
+    group related information. When disabled. ascii-art is used instead. Using
+    the "+all" option does not set this modifier,
+
     If the debug_print option is set in any driver, it produces output whenever
     any debugging is selected, or if -v is used.
 
     If the debug_print option is set in any driver, it produces output whenever
     any debugging is selected, or if -v is used.
 
@@ -3520,7 +3549,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
 
     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
 
 
 -E
 
@@ -3652,7 +3681,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
     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
     option, and the caller must be root or the Exim user in order to use it.
 
 -MCA
@@ -3661,6 +3690,24 @@ 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.
 
     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 <queue name>
+
+    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 argument.
+
+-MCK
+
+    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 a
+    remote host supports the ESMTP CHUNKING extension.
+
 -MCP
 
     This option is not intended for use by external callers. It is used
 -MCP
 
     This option is not intended for use by external callers. It is used
@@ -3689,13 +3736,21 @@ brief message about itself and exits.
     internally by Exim in conjunction with the -MC option, and passes on the
     fact that the host to which Exim is connected supports TLS encryption.
 
     internally by Exim in conjunction with the -MC option, and passes on the
     fact that the host to which Exim is connected supports TLS encryption.
 
+-MCt <IP address> <port> <cipher>
+
+    This option is not intended for use by external callers. It is used
+    internally by Exim in conjunction with the -MC option, and passes on the
+    fact that the connection is being proxied by a parent process for handling
+    TLS encryption. The arguments give the local address and port being
+    proxied, and the TLS cipher.
+
 -Mc <message id> <message id> ...
 
 -Mc <message id> <message id> ...
 
-    This option requests Exim to run a delivery attempt on each message in
+    This option requests Exim to run a delivery attempt on each message, in
     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
     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.
     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.
@@ -3752,7 +3807,7 @@ brief message about itself and exits.
     bounce messages are sent; each message is simply forgotten. However, if any
     of the messages are active, their status is not altered. This option can be
     used only by an admin user or by the user who originally caused the message
     bounce messages are sent; each message is simply forgotten. However, if any
     of the messages are active, their status is not altered. This option can be
     used only by an admin user or by the user who originally caused the message
-    to be placed on the queue.
+    to be placed in the queue.
 
 -Mset <message id>
 
 
 -Mset <message id>
 
@@ -3820,8 +3875,9 @@ brief message about itself and exits.
 -n
 
     This option is interpreted by Sendmail to mean "no aliasing". For normal
 -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>
 
 
 -O <data>
 
@@ -3831,7 +3887,7 @@ brief message about itself and exits.
 -oA <file name>
 
     This option is used by Sendmail in conjunction with -bi to specify an
 -oA <file name>
 
     This option is used by Sendmail in conjunction with -bi to specify an
-    alternative alias file name. Exim handles -bi differently; see the
+    alternative alias filename. Exim handles -bi differently; see the
     description above.
 
 -oB <n>
     description above.
 
 -oB <n>
@@ -3874,8 +3930,8 @@ brief message about itself and exits.
     effect.
 
     If there is a temporary delivery error during foreground delivery, the
     effect.
 
     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
+    message is left in the queue for later delivery, and the original reception
+    process exits. See chapter 51 for a way of setting up a restricted
     configuration that never queues messages.
 
 -odi
     configuration that never queues messages.
 
 -odi
@@ -3888,7 +3944,7 @@ brief message about itself and exits.
     This option applies to all modes in which Exim accepts incoming messages,
     including the listening daemon. It specifies that the accepting process
     should not automatically start a delivery process for each message
     This option applies to all modes in which Exim accepts incoming messages,
     including the listening daemon. It specifies that the accepting process
     should not automatically start a delivery process for each message
-    received. Messages are placed on the queue, and remain there until a
+    received. Messages are placed in the queue, and remain there until a
     subsequent queue runner process encounters them. There are several
     configuration options (such as queue_only) that can be used to queue
     incoming messages under certain conditions. This option overrides all of
     subsequent queue runner process encounters them. There are several
     configuration options (such as queue_only) that can be used to queue
     incoming messages under certain conditions. This option overrides all of
@@ -3904,7 +3960,7 @@ brief message about itself and exits.
     message, in the background by default, but in the foreground if -odi is
     also present. The recipient addresses are routed, and local deliveries are
     done in the normal way. However, if any SMTP deliveries are required, they
     message, in the background by default, but in the foreground if -odi is
     also present. The recipient addresses are routed, and local deliveries are
     done in the normal way. However, if any SMTP deliveries are required, they
-    are not done at this time, so the message remains on the queue until a
+    are not done at this time, so the message remains in the queue until a
     subsequent queue runner process encounters it. Because routing was done,
     Exim knows which messages are waiting for which hosts, and so a number of
     messages for the same host can be sent in a single SMTP connection. The 
     subsequent queue runner process encounters it. Because routing was done,
     Exim knows which messages are waiting for which hosts, and so a number of
     messages for the same host can be sent in a single SMTP connection. The 
@@ -4033,7 +4089,8 @@ brief message about itself and exits.
     -bh, the protocol is forced to one of the standard SMTP protocol names (see
     the description of $received_protocol in section 11.9). For -bs, the
     protocol is always "local-" followed by one of those same names. For -bS
     -bh, the protocol is forced to one of the standard SMTP protocol names (see
     the description of $received_protocol in section 11.9). For -bs, the
     protocol is always "local-" followed by one of those same names. For -bS
-    (batched SMTP) however, the protocol can be set by -oMr.
+    (batched SMTP) however, the protocol can be set by -oMr. Repeated use of
+    this option is not supported.
 
 -oMs <host name>
 
 
 -oMs <host name>
 
@@ -4073,14 +4130,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
     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
 
 -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
 
 
 -ov
 
@@ -4092,7 +4149,7 @@ brief message about itself and exits.
     is also given. It controls which ports and interfaces the daemon uses.
     Details of the syntax, and how it interacts with configuration file
     options, are given in chapter 13. When -oX is used to start a daemon, no
     is also given. It controls which ports and interfaces the daemon uses.
     Details of the syntax, and how it interacts with configuration file
     options, are given in chapter 13. When -oX is used to start a daemon, no
-    pid file is written unless -oP is also present to specify a pid file name.
+    pid file is written unless -oP is also present to specify a pid filename.
 
 -pd
 
 
 -pd
 
@@ -4117,7 +4174,8 @@ brief message about itself and exits.
     name and its colon can be omitted when only the protocol is to be set. Note
     the Exim already has two private options, -pd and -ps, that refer to
     embedded Perl. It is therefore impossible to set a protocol value of "d" or
     name and its colon can be omitted when only the protocol is to be set. Note
     the Exim already has two private options, -pd and -ps, that refer to
     embedded Perl. It is therefore impossible to set a protocol value of "d" or
-    "s" using this option (but that does not seem a real limitation).
+    "s" using this option (but that does not seem a real limitation). Repeated
+    use of this option is not supported.
 
 -q
 
 
 -q
 
@@ -4126,12 +4184,12 @@ brief message about itself and exits.
     relax this restriction (and also the same requirement for the -M, -R, and 
     -S options).
 
     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
 
     If the delivery process spawns other processes to deliver other messages
     down passed SMTP connections, the queue runner waits for these to finish
@@ -4177,7 +4235,7 @@ brief message about itself and exits.
 
     If the i flag is present, the queue runner runs delivery processes only for
     those messages that haven't previously been tried. (i stands for "initial
 
     If the i flag is present, the queue runner runs delivery processes only for
     those messages that haven't previously been tried. (i stands for "initial
-    delivery".) This can be helpful if you are putting messages on the queue
+    delivery".) This can be helpful if you are putting messages in the queue
     using -odq and want a queue runner just to process the new messages.
 
 -q[q][i]f...
     using -odq and want a queue runner just to process the new messages.
 
 -q[q][i]f...
@@ -4194,9 +4252,23 @@ brief message about itself and exits.
 -q[q][i][f[f]]l
 
     The l (the letter "ell") flag specifies that only local deliveries are to
 -q[q][i][f[f]]l
 
     The l (the letter "ell") flag specifies that only local deliveries are to
-    be done. If a message requires any remote deliveries, it remains on the
+    be done. If a message requires any remote deliveries, it remains in the
     queue for later delivery.
 
     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
 -q<qflags> <start id> <end id>
 
     When scanning the queue, Exim can be made to skip over messages whose ids
@@ -4222,7 +4294,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
 
     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
     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
@@ -4283,7 +4355,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
 
     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.
 
     to run Exim with the -R option, but it can be configured to run an
     arbitrary command instead.
 
@@ -4350,7 +4422,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
 
     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
     further details.
 
 -U
@@ -4382,12 +4454,18 @@ 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.
 
     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.
+
 
 
 ===============================================================================
 
 
 ===============================================================================
-6. THE EXIM RUN TIME CONFIGURATION FILE
+6. THE EXIM RUNTIME CONFIGURATION FILE
 
 
-Exim uses a single run time configuration file that is read whenever an Exim
+Exim uses a single runtime configuration file that is read whenever an Exim
 binary is executed. Note that in normal operation, this happens frequently,
 because Exim is designed to operate in a distributed manner, without central
 control.
 binary is executed. Note that in normal operation, this happens frequently,
 because Exim is designed to operate in a distributed manner, without central
 control.
@@ -4402,29 +4480,29 @@ the string.
 The name of the configuration file is compiled into the binary for security
 reasons, and is specified by the CONFIGURE_FILE compilation option. In most
 configurations, this specifies a single file. However, it is permitted to give
 The name of the configuration file is compiled into the binary for security
 reasons, and is specified by the CONFIGURE_FILE compilation option. In most
 configurations, this specifies a single file. However, it is permitted to give
-a colon-separated list of file names, in which case Exim uses the first
-existing file in the list.
+a colon-separated list of filenames, in which case Exim uses the first existing
+file in the list.
 
 
-The run time configuration file must be owned by root or by the user that is
+The runtime configuration file must be owned by root or by the user that is
 specified at compile time by the CONFIGURE_OWNER option (if set). The
 configuration file must not be world-writeable, or group-writeable unless its
 group is the root group or the one specified at compile time by the
 CONFIGURE_GROUP option.
 
 Warning: In a conventional configuration, where the Exim binary is setuid to
 specified at compile time by the CONFIGURE_OWNER option (if set). The
 configuration file must not be world-writeable, or group-writeable unless its
 group is the root group or the one specified at compile time by the
 CONFIGURE_GROUP option.
 
 Warning: In a conventional configuration, where the Exim binary is setuid to
-root, anybody who is able to edit the run time configuration file has an easy
+root, anybody who is able to edit the runtime configuration file has an easy
 way to run commands as root. If you specify a user or group in the
 CONFIGURE_OWNER or CONFIGURE_GROUP options, then that user and/or any users who
 are members of that group will trivially be able to obtain root privileges.
 
 way to run commands as root. If you specify a user or group in the
 CONFIGURE_OWNER or CONFIGURE_GROUP options, then that user and/or any users who
 are members of that group will trivially be able to obtain root privileges.
 
-Up to Exim version 4.72, the run time configuration file was also permitted to
+Up to Exim version 4.72, the runtime configuration file was also permitted to
 be writeable by the Exim user and/or group. That has been changed in Exim 4.73
 since it offered a simple privilege escalation for any attacker who managed to
 compromise the Exim user account.
 
 A default configuration file, which will work correctly in simple situations,
 is provided in the file src/configure.default. If CONFIGURE_FILE defines just
 be writeable by the Exim user and/or group. That has been changed in Exim 4.73
 since it offered a simple privilege escalation for any attacker who managed to
 compromise the Exim user account.
 
 A default configuration file, which will work correctly in simple situations,
 is provided in the file src/configure.default. If CONFIGURE_FILE defines just
-one file name, the installation process copies the default configuration to a
+one filename, the installation process copies the default configuration to a
 new file of that name if it did not previously exist. If CONFIGURE_FILE is a
 list, no default is automatically installed. Chapter 7 is a "walk-through"
 discussion of the default configuration.
 new file of that name if it did not previously exist. If CONFIGURE_FILE is a
 list, no default is automatically installed. Chapter 7 is a "walk-through"
 discussion of the default configuration.
@@ -4450,13 +4528,13 @@ configuration using -C right through message reception and delivery, even if
 the caller is root. The reception works, but by that time, Exim is running as
 the Exim user, so when it re-execs to regain privilege for the delivery, the
 use of -C causes privilege to be lost. However, root can test reception and
 the caller is root. The reception works, but by that time, Exim is running as
 the Exim user, so when it re-execs to regain privilege for the delivery, the
 use of -C causes privilege to be lost. However, root can test reception and
-delivery using two separate commands (one to put a message on the queue, using 
+delivery using two separate commands (one to put a message in the queue, using 
 -odq, and another to do the delivery, using -M).
 
 If ALT_CONFIG_PREFIX is defined in Local/Makefile, it specifies a prefix string
 with which any file named in a -C command line option must start. In addition,
 -odq, and another to do the delivery, using -M).
 
 If ALT_CONFIG_PREFIX is defined in Local/Makefile, it specifies a prefix string
 with which any file named in a -C command line option must start. In addition,
-the file name must not contain the sequence "/../". There is no default setting
-for ALT_CONFIG_PREFIX; when it is unset, any file name can be used with -C.
+the filename must not contain the sequence "/../". There is no default setting
+for ALT_CONFIG_PREFIX; when it is unset, any filename can be used with -C.
 
 One-off changes to a configuration can be specified by the -D command line
 option, which defines and overrides values for macros used inside the
 
 One-off changes to a configuration can be specified by the -D command line
 option, which defines and overrides values for macros used inside the
@@ -4477,10 +4555,10 @@ Acceptable values for the macros satisfy the regexp: "^[A-Za-z0-9_/.-]*$"
 Some sites may wish to use the same Exim binary on different machines that
 share a file system, but to use different configuration files on each machine.
 If CONFIGURE_FILE_USE_NODE is defined in Local/Makefile, Exim first looks for a
 Some sites may wish to use the same Exim binary on different machines that
 share a file system, but to use different configuration files on each machine.
 If CONFIGURE_FILE_USE_NODE is defined in Local/Makefile, Exim first looks for a
-file whose name is the configuration file name followed by a dot and the
+file whose name is the configuration filename followed by a dot and the
 machine's node name, as obtained from the uname() function. If this file does
 machine's node name, as obtained from the uname() function. If this file does
-not exist, the standard name is tried. This processing occurs for each file
-name in the list given by CONFIGURE_FILE or -C.
+not exist, the standard name is tried. This processing occurs for each filename
+in the list given by CONFIGURE_FILE or -C.
 
 In some esoteric situations different versions of Exim may be run under
 different effective uids and the CONFIGURE_FILE_USE_EUID is defined to help
 
 In some esoteric situations different versions of Exim may be run under
 different effective uids and the CONFIGURE_FILE_USE_EUID is defined to help
@@ -4493,11 +4571,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
 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
 
   * 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).
 
   * authenticators: Configuration settings for the authenticator drivers. These
     are concerned with the SMTP AUTH command (see chapter 33).
@@ -4526,7 +4604,7 @@ optional parts are:
     LOCAL_SCAN_HAS_OPTIONS=yes
 
     in Local/Makefile before building Exim. Details of the local_scan()
     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.
 
 
 Leading and trailing white space in configuration lines is always ignored.
 
@@ -4546,25 +4624,26 @@ 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
 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
 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.
 
 
 6.3 File inclusions in the configuration file
 ---------------------------------------------
 
 conditional facilities are described.
 
 
 6.3 File inclusions in the configuration file
 ---------------------------------------------
 
-You can include other files inside Exim's run time configuration file by using
+You can include other files inside Exim's runtime configuration file by using
 this syntax:
 
 this syntax:
 
-.include <file name>
-.include_if_exists <file name>
+.include <filename>
+.include_if_exists <filename>
 
 
-on a line by itself. Double quotes round the file name are optional. If you use
+on a line by itself. Double quotes round the filename are optional. If you use
 the first form, a configuration error occurs if the file does not exist; the
 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 filename 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
 
 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
@@ -4608,7 +4687,7 @@ ACL, or in the local_scan, retry, or rewrite sections of the configuration.
 
 Once a macro is defined, all subsequent lines in the file (and any included
 files) are scanned for the macro name; if there are several macros, the line is
 
 Once a macro is defined, all subsequent lines in the file (and any included
 files) are scanned for the macro name; if there are several macros, the line is
-scanned for each in turn, in the order in which the macros are defined. The
+scanned for each, in turn, in the order in which the macros are defined. The
 replacement text is not re-scanned for the current macro, though it is scanned
 for subsequently defined macros. For this reason, a macro name may not contain
 the name of a previously defined macro as a substring. You could, for example,
 replacement text is not re-scanned for the current macro, though it is scanned
 for subsequently defined macros. For this reason, a macro name may not contain
 the name of a previously defined macro as a substring. You could, for example,
@@ -4678,8 +4757,34 @@ address lists. In Exim 4 these are handled better by named lists - see section
 10.5.
 
 
 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
+ _LOG_*                       log_selector values
+ _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
 
 You can use the directives ".ifdef", ".ifndef", ".elifdef", ".elifndef",
 ".else", and ".endif" to dynamically include or exclude portions of the
@@ -4697,10 +4802,10 @@ message_size_limit = 50M
 message_size_limit = 100M
 .endif
 
 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
 
 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
@@ -4710,7 +4815,7 @@ Text following ".else" and ".endif" is ignored, and can be used as comment to
 clarify complicated nestings.
 
 
 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
 -------------------------
 
 For the main set of options, driver options, and local_scan() options, each
@@ -4739,7 +4844,7 @@ The following sections describe the syntax used for the different data types
 that are found in option settings.
 
 
 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
 --------------------
 
 Options whose type is given as boolean are on/off switches. There are two
@@ -4761,7 +4866,7 @@ queue_only = false
 You can use whichever syntax you prefer.
 
 
 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,
 -------------------
 
 If an option's type is given as "integer", the value can be given in decimal,
@@ -4771,14 +4876,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
 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
 -------------------------
 
 If an option's type is given as "octal integer", its value is always
@@ -4786,14 +4891,14 @@ interpreted as an octal number, whether or not it starts with the digit zero.
 Such options are always output in octal.
 
 
 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.
 
 
 ------------------------
 
 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
 -------------------
 
 A time interval is specified as a sequence of numbers, each followed by one of
@@ -4810,7 +4915,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".
 
 
 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
 ------------------
 
 If an option's type is specified as "string", the value can be specified with
@@ -4849,7 +4954,7 @@ in order to continue lines, so you may come across older configuration files
 and examples that apparently quote unnecessarily.
 
 
 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
 ---------------------
 
 Some strings in the configuration file are subjected to string expansion, by
@@ -4862,7 +4967,7 @@ required for that reason must be doubled if they are within a quoted
 configuration string.
 
 
 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
 -------------------------
 
 User and group names are specified as strings, using the syntax described
@@ -4871,7 +4976,7 @@ either consist entirely of digits, or be a name that can be looked up using the
 getpwnam() or getgrnam() function, as appropriate.
 
 
 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
 ----------------------
 
 The data for some configuration options is a list of items, with colon as the
@@ -4882,7 +4987,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
 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,
 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,
@@ -4898,7 +5003,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.
 
 
 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
 -----------------------------
 
 Doubling colons in IPv6 addresses is an unwelcome chore, so a mechanism was
@@ -4936,7 +5041,7 @@ set as the separator. Two such characters in succession are interpreted as
 enclosing an empty list item.
 
 
 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
 -------------------------
 
 An empty item at the end of a list is always ignored. In other words, trailing
@@ -4961,7 +5066,7 @@ In this case, the first item is empty, and the second is discarded because it
 is at the end of the list.
 
 
 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,
 ------------------------------------
 
 There are separate parts in the configuration for defining routers, transports,
@@ -5054,12 +5159,31 @@ settings. However, note that there are many options that are not mentioned at
 all in the default configuration.
 
 
 all in the default configuration.
 
 
-7.1 Main configuration settings
+7.1 Macros
+----------
+
+All macros should be defined before any options.
+
+One macro is specified, but commented out, in the default configuration:
+
+# ROUTER_SMARTHOST=MAIL.HOSTNAME.FOR.CENTRAL.SERVER.EXAMPLE
+
+If all off-site mail is expected to be delivered to a "smarthost", then set the
+hostname here and uncomment the macro. This will affect which router is used
+later on. If this is left commented out, then Exim will perform direct-to-MX
+deliveries using a dnslookup router.
+
+In addition to macros defined here, Exim includes a number of built-in macros
+to enable configuration to be guarded by a binary built with support for a
+given feature. See section 6.9 for more details.
+
+
+7.2 Main configuration settings
 -------------------------------
 
 -------------------------------
 
-The main (global) configuration option settings must always come first in the
-file. The first thing you'll see in the file, after some initial comments, is
-the line
+The main (global) configuration option settings section must always come first
+in the file, after the macros. The first thing you'll see in the file, after
+some initial comments, is the line
 
 # primary_hostname =
 
 
 # primary_hostname =
 
@@ -5128,7 +5252,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
 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:
 
 
 Three more commented-out option settings follow:
 
@@ -5139,10 +5263,10 @@ Three more commented-out option settings follow:
 These are example settings that can be used when Exim is compiled with support
 for TLS (aka SSL) as described in section 4.7. The first one specifies the list
 of clients that are allowed to use TLS when connecting to this server; in this
 These are example settings that can be used when Exim is compiled with support
 for TLS (aka SSL) as described in section 4.7. The first one specifies the list
 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
+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
 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:
 
 
 Another two commented-out option settings follow:
 
@@ -5152,11 +5276,15 @@ Another two commented-out option settings follow:
 These options provide better support for roaming users who wish to use this
 server for message submission. They are not much use unless you have turned on
 TLS (as described in the previous paragraph) and authentication (about which
 These options provide better support for roaming users who wish to use this
 server for message submission. They are not much use unless you have turned on
 TLS (as described in the previous paragraph) and authentication (about which
-more in section 7.7). The usual SMTP port 25 is often blocked on end-user
-networks, so RFC 4409 specifies that message submission should use port 587
-instead. However some software (notably Microsoft Outlook) cannot be configured
-to use port 587 correctly, so these settings also enable the non-standard
-"smtps" (aka "ssmtp") port 465 (see section 13.4).
+more in section 7.8). Mail submission from mail clients (MUAs) should be
+separate from inbound mail to your domain (MX delivery) for various good
+reasons (eg, ability to impose much saner TLS protocol and ciphersuite
+requirements without unintended consequences). RFC 6409 (previously 4409)
+specifies use of port 587 for SMTP Submission, which uses STARTTLS, so this is
+the "submission" port. RFC 8314 specifies use of port 465 as the "submissions"
+protocol, which should be used in preference to 587. You should also consider
+deploying SRV records to help clients find these ports. Older names for
+"submissions" are "smtps" and "ssmtp".
 
 Two more commented-out options settings follow:
 
 
 Two more commented-out options settings follow:
 
@@ -5220,7 +5348,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.
 
 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
 not expected to cause problems but can be disabled if needed.
 
 prdr_enable = true
@@ -5236,6 +5364,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.
 
 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 =
 The percent_hack_domains option is also commented out:
 
 # percent_hack_domains =
@@ -5244,7 +5378,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.
 
 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
 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
@@ -5257,13 +5391,46 @@ ignore_bounce_errors_after = 2d
 timeout_frozen_after = 7d
 
 The first of these options specifies that failing bounce messages are to be
 timeout_frozen_after = 7d
 
 The first of these options specifies that failing bounce messages are to be
-discarded after 2 days on the queue. The second specifies that any frozen
+discarded after 2 days in the queue. The second specifies that any frozen
 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.
 
 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
 
 
-7.2 ACL configuration
+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.3 ACL configuration
 ---------------------
 
 In the default configuration, the ACL section follows the main configuration.
 ---------------------
 
 In the default configuration, the ACL section follows the main configuration.
@@ -5340,10 +5507,10 @@ them because they have been encountered in practice. (Consider the common
 convention of local parts constructed as "
 first-initial.second-initial.family-name" when applied to someone like the
 author of Exim, who has no second initial.) However, a local part starting with
 convention of local parts constructed as "
 first-initial.second-initial.family-name" when applied to someone like the
 author of Exim, who has no second initial.) However, a local part starting with
-a dot or containing "/../" can cause trouble if it is used as part of a file
-name (for example, for a mailing list). This is also true for local parts that
-contain slashes. A pipe symbol can also be troublesome if the local part is
-incorporated unthinkingly into a shell command line.
+a dot or containing "/../" can cause trouble if it is used as part of a
+filename (for example, for a mailing list). This is also true for local parts
+that contain slashes. A pipe symbol can also be troublesome if the local part
+is incorporated unthinkingly into a shell command line.
 
 The second rule above applies to all other domains, and is less strict. This
 allows your own users to send outgoing messages to sites that use slashes and
 
 The second rule above applies to all other domains, and is less strict. This
 allows your own users to send outgoing messages to sites that use slashes and
@@ -5373,7 +5540,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
 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
 address verification.
 
 accept  hosts         = +relay_from_hosts
@@ -5384,7 +5551,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
 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.
 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.
@@ -5396,7 +5563,7 @@ This statement accepts the address if the client host has authenticated itself.
 Submission mode is again specified, on the grounds that such messages are most
 likely to come from MUAs. The default configuration does not define any
 authenticators, though it does include some nearly complete commented-out
 Submission mode is again specified, on the grounds that such messages are most
 likely to come from MUAs. The default configuration does not define any
 authenticators, though it does include some nearly complete commented-out
-examples described in 7.7. This means that no client can in fact authenticate
+examples described in 7.8. This means that no client can in fact authenticate
 until you complete the authenticator definitions.
 
 require message = relay not permitted
 until you complete the authenticator definitions.
 
 require message = relay not permitted
@@ -5467,7 +5634,7 @@ accept
 This final line in the DATA ACL accepts the message unconditionally.
 
 
 This final line in the DATA ACL accepts the message unconditionally.
 
 
-7.3 Router configuration
+7.4 Router configuration
 ------------------------
 
 The router configuration comes next in the default configuration, introduced by
 ------------------------
 
 The router configuration comes next in the default configuration, introduced by
@@ -5476,7 +5643,7 @@ the line
 begin routers
 
 Routers are the modules in Exim that make decisions about where to send
 begin routers
 
 Routers are the modules in Exim that make decisions about where to send
-messages. An address is passed to each router in turn, until it is either
+messages. An address is passed to each router, in turn, until it is either
 accepted, or failed. This means that the order in which you define the routers
 matters. Each router is fully described in its own chapter later in this
 manual. Here we give only brief overviews.
 accepted, or failed. This means that the order in which you define the routers
 matters. Each router is fully described in its own chapter later in this
 manual. Here we give only brief overviews.
@@ -5491,15 +5658,32 @@ support domain literal addresses (those of the form user@[10.9.8.7]). If you
 uncomment this router, you also need to uncomment the setting of 
 allow_domain_literals in the main part of the configuration.
 
 uncomment this router, you also need to uncomment the setting of 
 allow_domain_literals in the main part of the configuration.
 
+Which router is used next depends upon whether or not the ROUTER_SMARTHOST
+macro has been defined, per
+
+.ifdef ROUTER_SMARTHOST
+smarthost:
+#...
+.else
 dnslookup:
 dnslookup:
-  driver = dnslookup
+#...
+.endif
+
+If ROUTER_SMARTHOST has been defined, either at the top of the file or on the
+command-line, then we route all non-local mail to that smarthost; otherwise,
+we'll perform DNS lookups for direct-to-MX lookup. Any mail which is to a local
+domain will skip these routers because of the domains option.
+
+smarthost:
+  driver = manualroute
   domains = ! +local_domains
   domains = ! +local_domains
-  transport = remote_smtp
-  ignore_target_hosts = 0.0.0.0 : 127.0.0.0/8
+  transport = smarthost_smtp
+  route_data = ROUTER_SMARTHOST
+  ignore_target_hosts = <; 0.0.0.0 ; 127.0.0.0/8 ; ::1
   no_more
 
   no_more
 
-The first uncommented router handles addresses that do not involve any local
-domains. This is specified by the line
+This router only handles mail which is not to any local domains; this is
+specified by the line
 
 domains = ! +local_domains
 
 
 domains = ! +local_domains
 
@@ -5510,6 +5694,28 @@ start of the configuration). The plus sign before local_domains indicates that
 it is referring to a named list. Addresses in other domains are passed on to
 the following routers.
 
 it is referring to a named list. Addresses in other domains are passed on to
 the following routers.
 
+The name of the router driver is manualroute because we are manually specifying
+how mail should be routed onwards, instead of using DNS MX. While the name of
+this router instance is arbitrary, the driver option must be one of the driver
+modules that is in the Exim binary.
+
+With no pre-conditions other than domains, all mail for non-local domains will
+be handled by this router, and the no_more setting will ensure that no other
+routers will be used for messages matching the pre-conditions. See 3.12 for
+more on how the pre-conditions apply. For messages which are handled by this
+router, we provide a hostname to deliver to in route_data and the macro
+supplies the value; the address is then queued for the smarthost_smtp
+transport.
+
+dnslookup:
+  driver = dnslookup
+  domains = ! +local_domains
+  transport = remote_smtp
+  ignore_target_hosts = 0.0.0.0 : 127.0.0.0/8
+  no_more
+
+The domains option behaves as per smarthost, above.
+
 The name of the router driver is dnslookup, and is specified by the driver
 option. Do not be confused by the fact that the name of this router instance is
 the same as the name of the driver. The instance name is arbitrary, but the
 The name of the router driver is dnslookup, and is specified by the driver
 option. Do not be confused by the fact that the name of this router instance is
 the same as the name of the driver. The instance name is arbitrary, but the
@@ -5636,7 +5842,7 @@ routers, so the address is bounced. The commented suffix settings fulfil the
 same purpose as they do for the userforward router.
 
 
 same purpose as they do for the userforward router.
 
 
-7.4 Transport configuration
+7.5 Transport configuration
 ---------------------------
 
 Transports define mechanisms for actually delivering messages. They operate
 ---------------------------
 
 Transports define mechanisms for actually delivering messages. They operate
@@ -5645,17 +5851,87 @@ not matter. The transports section of the configuration starts with
 
 begin transports
 
 
 begin transports
 
-One remote transport and four local transports are defined.
+Two remote transports and four local transports are defined.
 
 remote_smtp:
   driver = smtp
 
 remote_smtp:
   driver = smtp
+  message_size_limit = ${if > {$max_received_linelength}{998} {1}{0}}
+.ifdef _HAVE_DANE
+  dnssec_request_domains = *
+  hosts_try_dane = *
+.endif
+.ifdef _HAVE_PRDR
   hosts_try_prdr = *
   hosts_try_prdr = *
+.endif
 
 This transport is used for delivering messages over SMTP connections. The list
 
 This transport is used for delivering messages over SMTP connections. The list
-of remote hosts comes from the router. The hosts_try_prdr option enables an
-efficiency SMTP option. It is negotiated between client and server and not
-expected to cause problems but can be disabled if needed. All other options are
-defaulted.
+of remote hosts comes from the router. The message_size_limit usage is a hack
+to avoid sending on messages with over-long lines. The built-in macro
+_HAVE_DANE guards configuration to try to use DNSSEC for all queries and to use
+DANE for delivery; see section 42.15 for more details.
+
+The hosts_try_prdr option enables an efficiency SMTP option. It is negotiated
+between client and server and not expected to cause problems but can be
+disabled if needed. The built-in macro _HAVE_PRDR guards the use of the 
+hosts_try_prdr configuration option.
+
+The other remote transport is used when delivering to a specific smarthost with
+whom there must be some kind of existing relationship, instead of the usual
+federated system.
+
+smarthost_smtp:
+  driver = smtp
+  message_size_limit = ${if > {$max_received_linelength}{998} {1}{0}}
+  multi_domain
+  #
+.ifdef _HAVE_TLS
+  # Comment out any of these which you have to, then file a Support
+  # request with your smarthost provider to get things fixed:
+  hosts_require_tls = *
+  tls_verify_hosts = *
+  # As long as tls_verify_hosts is enabled, this won't matter, but if you
+  # have to comment it out then this will at least log whether you succeed
+  # or not:
+  tls_try_verify_hosts = *
+  #
+  # The SNI name should match the name which we'll expect to verify;
+  # many mail systems don't use SNI and this doesn't matter, but if it does,
+  # we need to send a name which the remote site will recognize.
+  # This _should_ be the name which the smarthost operators specified as
+  # the hostname for sending your mail to.
+  tls_sni = ROUTER_SMARTHOST
+  #
+.ifdef _HAVE_OPENSSL
+  tls_require_ciphers = HIGH:!aNULL:@STRENGTH
+.endif
+.ifdef _HAVE_GNUTLS
+  tls_require_ciphers = SECURE192:-VERS-SSL3.0:-VERS-TLS1.0:-VERS-TLS1.1
+.endif
+.endif
+.ifdef _HAVE_PRDR
+  hosts_try_prdr = *
+.endif
+
+After the same message_size_limit hack, we then specify that this Transport can
+handle messages to multiple domains in one run. The assumption here is that
+you're routing all non-local mail to the same place and that place is happy to
+take all messages from you as quickly as possible. All other options depend
+upon built-in macros; if Exim was built without TLS support then no other
+options are defined. If TLS is available, then we configure "stronger than
+default" TLS ciphersuites and versions using the tls_require_ciphers option,
+where the value to be used depends upon the library providing TLS. Beyond that,
+the options adopt the stance that you should have TLS support available from
+your smarthost on today's Internet, so we turn on requiring TLS for the mail to
+be delivered, and requiring that the certificate be valid, and match the
+expected hostname. The tls_sni option can be used by service providers to
+select an appropriate certificate to present to you and here we re-use the
+ROUTER_SMARTHOST macro, because that is unaffected by CNAMEs present in DNS.
+You want to specify the hostname which you'll expect to validate for, and that
+should not be subject to insecure tampering via DNS results.
+
+For the hosts_try_prdr option see the previous transport.
+
+All other options are defaulted.
 
 local_delivery:
   driver = appendfile
 
 local_delivery:
   driver = appendfile
@@ -5683,8 +5959,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
 
 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
 
 address_file:
   driver = appendfile
@@ -5703,7 +5979,7 @@ This transport is used for handling automatic replies generated by users'
 filter files.
 
 
 filter files.
 
 
-7.5 Default retry rule
+7.6 Default retry rule
 ----------------------
 
 The retry section of the configuration file contains rules which affect the way
 ----------------------
 
 The retry section of the configuration file contains rules which affect the way
@@ -5720,14 +5996,15 @@ 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
 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
 temporary errors into permanent errors.
 
 
 
 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
 temporary errors into permanent errors.
 
 
-7.6 Rewriting configuration
+7.7 Rewriting configuration
 ---------------------------
 
 The rewriting section of the configuration, introduced by
 ---------------------------
 
 The rewriting section of the configuration, introduced by
@@ -5738,7 +6015,7 @@ contains rules for rewriting addresses in messages as they arrive. There are no
 rewriting rules in the default configuration file.
 
 
 rewriting rules in the default configuration file.
 
 
-7.7 Authenticators configuration
+7.8 Authenticators configuration
 --------------------------------
 
 The authenticators section of the configuration, introduced by
 --------------------------------
 
 The authenticators section of the configuration, introduced by
@@ -5776,7 +6053,7 @@ implements the details of the specific authentication mechanism, i.e. PLAIN or
 LOGIN. The server_advertise_condition setting controls when Exim offers
 authentication to clients; in the examples, this is only when TLS or SSL has
 been started, so to enable the authenticators you also need to add support for
 LOGIN. The server_advertise_condition setting controls when Exim offers
 authentication to clients; in the examples, this is only when TLS or SSL has
 been started, so to enable the authenticators you also need to add support for
-TLS as described in section 7.1.
+TLS as described in section 7.2.
 
 The server_condition setting defines how to verify that the username and
 password are correct. In the examples it just produces an error message. To
 
 The server_condition setting defines how to verify that the username and
 password are correct. In the examples it just produces an error message. To
@@ -5794,9 +6071,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
 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
 
 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
@@ -5851,13 +6128,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
     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
 
  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
 
 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
@@ -5949,11 +6228,12 @@ The following single-key lookup types are implemented:
     indexed files that are read frequently and never updated, except by total
     re-creation. As such, it is particularly suitable for large files
     containing aliases or other indexed data referenced by an MTA. Information
     indexed files that are read frequently and never updated, except by total
     re-creation. As such, it is particularly suitable for large files
     containing aliases or other indexed data referenced by an MTA. Information
-    about cdb can be found in several places:
+    about cdb and tools for building the files can be found in several places:
 
 
-    http://www.pobox.com/~djb/cdb.html
-    ftp://ftp.corpit.ru/pub/tinycdb/
-    http://packages.debian.org/stable/utils/freecdb.html
+    https://cr.yp.to/cdb.html
+    http://www.corpit.ru/mjt/tinycdb.html
+    https://packages.debian.org/stable/utils/freecdb
+    https://github.com/philpennock/cdbtools (in Go)
 
     A cdb distribution is not needed in order to build Exim with cdb support,
     because the code for reading cdb files is included directly in Exim itself.
 
     A cdb distribution is not needed in order to build Exim with cdb support,
     because the code for reading cdb files is included directly in Exim itself.
@@ -5988,14 +6268,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
     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
 
   * 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
 
   * 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
@@ -6046,7 +6326,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
     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.
 
     after quoted keys (exactly as for unquoted keys). There is no special
     handling of quotes for the data part of an lsearch line.
 
@@ -6117,6 +6397,9 @@ The following single-key lookup types are implemented:
     wildlsearch can not be turned into a DBM or cdb file, because those lookup
     types support only literal keys.
 
     wildlsearch can not be turned into a DBM or cdb file, because those lookup
     types support only literal keys.
 
+  * If Exim is built with SPF support, manual lookups can be done (as opposed
+    to the standard ACL condition method. For details see section 57.4.
+
 
 9.4 Query-style lookup types
 ----------------------------
 
 9.4 Query-style lookup types
 ----------------------------
@@ -6134,16 +6417,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
     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: 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
 
   * 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: 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
 
   * 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
@@ -6154,10 +6437,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
     *: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.
+  * sqlite: The format of the query is a filename followed by an SQL statement
+    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.
 
   * testdb: This is a lookup type that is used for testing Exim. It is not
     likely to be useful in normal operation.
@@ -6388,23 +6674,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.
 
 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
 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
@@ -6413,13 +6687,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
 ${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,
 
 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}}
 
 ${lookup dnsdb{>\n,: txt=a.b.example}}
 ${lookup dnsdb{>\n; txt=a.b.example}}
@@ -6428,8 +6717,57 @@ ${lookup dnsdb{spf=example.org}}
 It is permitted to specify a space as the separator character. Further white
 space is ignored.
 
 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.
 
 
-9.11 Pseudo dnsdb record types
+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.12 Pseudo dnsdb record types
 ------------------------------
 
 By default, both the preference value and the host name are returned for each
 ------------------------------
 
 By default, both the preference value and the host name are returned for each
@@ -6464,7 +6802,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
 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:
 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:
@@ -6475,14 +6813,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 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}}
 
 
 
 ${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.
 ---------------------------
 
 In the previous sections, dnsdb lookups for a single domain are described.
@@ -6503,35 +6841,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.
 
 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
 --------------------
 
 The original LDAP implementation came from the University of Michigan; this has
@@ -6570,7 +6881,7 @@ data returned by a successful lookup is described in the next section. First we
 explain how LDAP queries are coded.
 
 
 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
 ---------------------------
 
 An LDAP query takes the form of a URL as defined in RFC 2255. For example, in
@@ -6598,7 +6909,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.
 
 
 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
 -----------------
 
 Two levels of quoting are required in LDAP queries, the first for LDAP itself
@@ -6656,7 +6967,7 @@ There are some further comments about quoting in the section on LDAP
 authentication below.
 
 
 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
 ---------------------
 
 The connection to an LDAP server may either be over TCP/IP, or, when OpenLDAP
@@ -6726,7 +7037,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.
 
 
 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
 ------------------------------------------------
 
 The LDAP URL syntax provides no way of passing authentication and other control
@@ -6764,12 +7075,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
 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:
 
 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:
@@ -6815,13 +7126,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.
 
 
 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
 
 ------------------------------------
 
 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
 
 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
@@ -6832,39 +7143,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
 
 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.
 
 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 
 
 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)
 
 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?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)
 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)
 
 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
 --------------------
 
 NIS+ queries consist of a NIS+ indexed name followed by an optional colon and
@@ -6893,12 +7215,12 @@ for the given indexed key. The effect of the quote_nisplus expansion operator
 is to double any quote characters within the text.
 
 
 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}
 
 ${lookup mysql{select mailbox from users where id='userx'}\
   {$value}fail}
@@ -6923,18 +7245,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.
 
 
 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
 
 
 hide oracle_servers = oracle.plc.example//userx/abcdwxyz
 
@@ -6952,21 +7274,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.
 
 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
 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:...;
 
 
 servers=server1:server2:server3:...;
 
@@ -7003,17 +7340,18 @@ option, you can still update it by a query of this form:
 ${lookup pgsql{servers=master/db/name/pw; UPDATE ...} }
 
 
 ${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.
 ---------------------------
 
 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
 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
@@ -7027,7 +7365,7 @@ Warning: This can be misleading. If an update does not actually change anything
 because no rows are affected.
 
 
 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.
 --------------------------------
 
 PostgreSQL lookups can also use Unix domain socket connections to the database.
@@ -7047,10 +7385,10 @@ update, or delete command), the result of the lookup is the number of rows
 affected.
 
 
 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
+SQLite is different to the other SQL lookups because a filename is required in
 addition to the SQL query. An SQLite database is a single file, and there is no
 daemon as in the other SQL databases. The interface to Exim requires the name
 of the file, as an absolute path, to be given at the start of the query. It is
 addition to the SQL query. An SQLite database is a single file, and there is no
 daemon as in the other SQL databases. The interface to Exim requires the name
 of the file, as an absolute path, to be given at the start of the query. It is
@@ -7076,6 +7414,26 @@ 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.
 
 
 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}}
+
+As of release 4.91, "lightweight" support for Redis Cluster is available.
+Requires redis_servers list to contain all the servers in the cluster, all of
+which must be reachable from the running exim instance. If the cluster has
+master/slave replication, the list must contain all the master and slave
+servers.
+
+When the Redis Cluster returns a "MOVED" response to a query, Exim does not
+immediately follow the redirection but treats the response as a DEFER, moving
+on to the next server in the redis_servers list until the correct server is
+reached.
+
+
 
 ===============================================================================
 10. DOMAIN, HOST, ADDRESS, AND LOCAL PART LISTS
 
 ===============================================================================
 10. DOMAIN, HOST, ADDRESS, AND LOCAL PART LISTS
@@ -7083,7 +7441,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
 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,
 conditions such as match_domain.
 
 Each item in one of these lists is a pattern to be matched against a domain,
@@ -7091,16 +7449,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.
 
 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
 -----------------------
 
 
 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
 
 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
@@ -7154,10 +7519,10 @@ the connector as "or" after a positive item and as "and" after a negative item.
 10.3 File names in lists
 ------------------------
 
 10.3 File names in lists
 ------------------------
 
-If an item in a domain, host, address, or local part list is an absolute file
-name (beginning with a slash character), each line of the file is read and
+If an item in a domain, host, address, or local part list is an absolute
+filename (beginning with a slash character), each line of the file is read and
 processed as if it were an independent item in the list, except that further
 processed as if it were an independent item in the list, except that further
-file names are not allowed, and no expansion of the data from the file takes
+filenames are not allowed, and no expansion of the data from the file takes
 place. Empty lines in the file are ignored, and the file may also contain
 comment lines:
 
 place. Empty lines in the file are ignored, and the file may also contain
 comment lines:
 
@@ -7170,13 +7535,13 @@ comment lines:
 
     not#comment@x.y.z   # but this is a comment
 
 
     not#comment@x.y.z   # but this is a comment
 
-Putting a file name in a list has the same effect as inserting each line of the
+Putting a filename in a list has the same effect as inserting each line of the
 file as an item in the list (blank lines and comments excepted). However, there
 is one important difference: the file is read each time the list is processed,
 so if its contents vary over time, Exim's behaviour changes.
 
 file as an item in the list (blank lines and comments excepted). However, there
 is one important difference: the file is read each time the list is processed,
 so if its contents vary over time, Exim's behaviour changes.
 
-If a file name is preceded by an exclamation mark, the sense of any match
-within the file is inverted. For example, if
+If a filename is preceded by an exclamation mark, the sense of any match within
+the file is inverted. For example, if
 
 hold_domains = !/etc/nohold-domains
 
 
 hold_domains = !/etc/nohold-domains
 
@@ -7201,9 +7566,9 @@ This is not the case. The keys in an lsearch file are always fixed strings,
 just as for any other single-key lookup type.
 
 If you want to use a file to contain wild-card patterns that form part of a
 just as for any other single-key lookup type.
 
 If you want to use a file to contain wild-card patterns that form part of a
-list, just give the file name on its own, without a search type, as described
-in the previous section. You could also use the wildlsearch or nwildlsearch,
-but there is no advantage in doing this.
+list, just give the filename on its own, without a search type, as described in
+the previous section. You could also use the wildlsearch or nwildlsearch, but
+there is no advantage in doing this.
 
 
 10.5 Named lists
 
 
 10.5 Named lists
@@ -7419,7 +7784,7 @@ following types of item may appear in domain lists:
 
   * If a pattern starts with the name of a single-key lookup type followed by a
     semicolon (for example, "dbm;" or "lsearch;"), the remainder of the pattern
 
   * If a pattern starts with the name of a single-key lookup type followed by a
     semicolon (for example, "dbm;" or "lsearch;"), the remainder of the pattern
-    must be a file name in a suitable format for the lookup type. For example,
+    must be a filename in a suitable format for the lookup type. For example,
     for "cdb;" it must be an absolute path:
 
     domains = cdb;/etc/mail/local_domains.cdb
     for "cdb;" it must be an absolute path:
 
     domains = cdb;/etc/mail/local_domains.cdb
@@ -7782,7 +8147,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
     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.
 
 
     was discussed in depth in the first example in this section.
 
 
@@ -7791,7 +8156,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
 
 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.
 "+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.
@@ -8040,7 +8405,7 @@ Exim are used for this kind of control, Exim attempts to do this by default.
 The domain portion of an address is always lowercased before matching it to an
 address list. The local part is lowercased by default, and any string
 comparisons that take place are done caselessly. This means that the data in
 The domain portion of an address is always lowercased before matching it to an
 address list. The local part is lowercased by default, and any string
 comparisons that take place are done caselessly. This means that the data in
-the address list itself, in files included as plain file names, and in any file
+the address list itself, in files included as plain filenames, and in any file
 that is looked up using the "@@" mechanism, can be in any case. However, the
 keys in files that are looked up by a search type other than lsearch (which
 works caselessly) must be in lower case, because these lookups are not
 that is looked up using the "@@" mechanism, can be in any case. However, the
 keys in files that are looked up by a search type other than lsearch (which
 works caselessly) must be in lower case, because these lookups are not
@@ -8080,7 +8445,7 @@ other available item types.
 ===============================================================================
 11. STRING EXPANSIONS
 
 ===============================================================================
 11. STRING EXPANSIONS
 
-Many strings in Exim's run time configuration are expanded before use. Some of
+Many strings in Exim's runtime configuration are expanded before use. Some of
 them are expanded every time they are used; others are expanded only once.
 
 When a string is being expanded it is copied verbatim from left to right except
 them are expanded every time they are used; others are expanded only once.
 
 When a string is being expanded it is copied verbatim from left to right except
@@ -8104,7 +8469,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
 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
 
 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
@@ -8148,7 +8513,7 @@ using -be for reading files to which they do not have access.
 
 If you want to test expansions that include variables whose values are taken
 from a message, there are two other options that can be used. The -bem option
 
 If you want to test expansions that include variables whose values are taken
 from a message, there are two other options that can be used. The -bem option
-is like -be except that it is followed by a file name. The file is read as a
+is like -be except that it is followed by a filename. The file is read as a
 message before doing the test expansions. For example:
 
 exim -bem /tmp/test.message '$h_subject:'
 message before doing the test expansions. For example:
 
 exim -bem /tmp/test.message '$h_subject:'
@@ -8222,7 +8587,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
     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
     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
@@ -8230,10 +8595,30 @@ ${acl{<name>}{<arg>}...}
     is an empty string. If the ACL returns defer the result is a forced-fail.
     Otherwise the expansion fails.
 
     is an empty string. If the ACL returns defer the result is a forced-fail.
     Otherwise the expansion fails.
 
+${authresults{<authserv-id>}}
+
+    This item returns a string suitable for insertion as an 
+    Authentication-Results" header line. The given <authserv-id> is included in
+    the result; typically this will be a domain name identifying the system
+    performing the authentications. Methods that might be present in the result
+    include:
+
+    none
+    iprev
+    auth
+    spf
+    dkim
+
+    Example use (as an ACL modifier):
+
+          add_header = :at_start:${authresults {$primary_hostname}}
+
+    This is safe even if no authentication results are available.
+
 ${certextract{<field>}{<certificate>}{<string2>}{<string3>}}
 
     The <certificate> must be a variable of type certificate. The field name is
 ${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        
     Supported fields are:
 
     version        
@@ -8261,15 +8646,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
 
     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
     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".
 
     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
 
     The field selectors marked as "list" above return a list, newline-separated
     by default, (embedded separator characters in elements are doubled). The
@@ -8278,7 +8665,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
 
     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.
     tags are omitted.
 
     If not otherwise noted field values are presented in human-readable form.
@@ -8320,19 +8707,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.
 
     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
 ${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
 
     <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
     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
@@ -8355,6 +8764,21 @@ ${extract{<key>}{<string1>}{<string2>}{<string3>}}
     This forces an expansion failure (see section 11.4); {<string2>} must be
     present for "fail" to be recognized.
 
     This forces an expansion failure (see section 11.4); {<string2>} must be
     present for "fail" to be recognized.
 
+${extract json{<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 be empty and must not consist entirely of digits. The expanded
+    <string1> must be of the form:
+
+    { <"key1"> : <value1> ,  <"key2"> , <value2> ... }
+
+    The braces, commas and colons, and the quoting of the member name are
+    required; the spaces are optional. Matching of the key against the member
+    names is done case-sensitively.
+
+    The results of matching are handled as above.
+
 ${extract{<number>}{<separators>}{<string1>}{<string2>}{<string3>}}
 
     The <number> argument must consist entirely of decimal digits, apart from
 ${extract{<number>}{<separators>}{<string1>}{<string2>}{<string3>}}
 
     The <number> argument must consist entirely of decimal digits, apart from
@@ -8382,17 +8806,25 @@ ${extract{<number>}{<separators>}{<string1>}{<string2>}{<string3>}}
     yields "99". Two successive separators mean that the field between them is
     empty (for example, the fifth field above).
 
     yields "99". Two successive separators mean that the field between them is
     empty (for example, the fifth field above).
 
+${extract json{<number>}}{<string1>}{<string2>}{<string3>}}
+
+    The <number> argument must consist entirely of decimal digits, apart from
+    leading and trailing white space, which is ignored.
+
+    Field selection and result handling is as above; there is no choice of
+    field separator.
+
 ${filter{<string>}{<condition>}}
 
     After expansion, <string> is interpreted as a list, colon-separated by
 ${filter{<string>}{<condition>}}
 
     After expansion, <string> is interpreted as a list, colon-separated by
-    default, but the separator can be changed in the usual way. For each item
-    in this list, its value is place in $item, and then the condition is
+    default, but the separator can be changed in the usual way (6.21). For each
+    item in this list, its value is place in $item, and then the condition is
     evaluated. If the condition is true, $item is added to the output as an
     item in a new list; if the condition is false, the item is discarded. The
     separator used for the output list is the same as the one used for the
     input, but a separator setting is not included in the output. For example:
 
     evaluated. If the condition is true, $item is added to the output as an
     item in a new list; if the condition is false, the item is discarded. The
     separator used for the output list is the same as the one used for the
     input, but a separator setting is not included in the output. For example:
 
-    ${filter{a:b:c}{!eq{$item}{b}}
+    ${filter{a:b:c}{!eq{$item}{b}}}
 
     yields "a:c". At the end of the expansion, the value of $item is restored
     to what it was before. See also the map and reduce expansion items.
 
     yields "a:c". At the end of the expansion, the value of $item is restored
     to what it was before. See also the map and reduce expansion items.
@@ -8426,9 +8858,10 @@ ${hash{<string1>}{<string2>}{<string3>}}
     $hash{4}{62}{monty python}}   yields  fbWx
 
 $header_<header name>: or $h_<header name>:, $bheader_<header name>: or $bh_<
     $hash{4}{62}{monty python}}   yields  fbWx
 
 $header_<header name>: or $h_<header name>:, $bheader_<header name>: or $bh_<
-    header name>:, $rheader_<header name>: or $rh_<header name>:
+    header name>:, $lheader_<header name>: or $lh_<header name>:
 
 
-    Substitute the contents of the named message header line, for example
+    "$rheader_<header name>: or $rh_<header name>:" Substitute the contents of
+    the named message header line, for example
 
     $header_reply-to:
 
 
     $header_reply-to:
 
@@ -8436,14 +8869,20 @@ $header_<header name>: or $h_<header name>:, $bheader_<header name>: or $bh_<
     but internal newlines (caused by splitting the header line over several
     physical lines) may be present.
 
     but internal newlines (caused by splitting the header line over several
     physical lines) may be present.
 
-    The difference between rheader, bheader, and header is in the way the data
+    The difference between the four pairs of expansions is in the way the data
     in the header line is interpreted.
 
     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.
 
         processing at all, and without the removal of leading and trailing
         white space.
 
-      * bheader removes leading and trailing white space, and then decodes
+      + lheader gives a colon-separated list, one element per header when there
+        are multiple headers with a given name. Any embedded colon characters
+        within an element are doubled, so normal Exim list-processing
+        facilities can be used. The terminating newline of each element is
+        removed; in other respects the content is "raw".
+
+      + 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
         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
@@ -8451,7 +8890,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.
 
         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
         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
@@ -8483,19 +8922,21 @@ $header_<header name>: or $h_<header name>:, $bheader_<header name>: or $bh_<
     router or transport are not accessible.
 
     For incoming SMTP messages, no header lines are visible in ACLs that are
     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.
+    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
 
     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
 
     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
@@ -8540,7 +8981,7 @@ ${hmac{<hashname>}{<secret>}{<string>}}
     X-Spam-Scanned: header line. If you know the secret, you can check that
     this header line is authentic by recomputing the authentication code from
     the host name, message ID and the Message-id: header line. This can be done
     X-Spam-Scanned: header line. If you know the secret, you can check that
     this header line is authentic by recomputing the authentication code from
     the host name, message ID and the Message-id: header line. This can be done
-    using Exim's -be option, or by other means, for example by using the 
+    using Exim's -be option, or by other means, for example, by using the 
     hmac_md5_hex() function in Perl.
 
 ${if <condition> {<string1>}{<string2>}}
     hmac_md5_hex() function in Perl.
 
 ${if <condition> {<string1>}{<string2>}}
@@ -8568,6 +9009,12 @@ ${if <condition> {<string1>}{<string2>}}
 
     condition = ${if >{$acl_m4}{3}}
 
 
     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
 ${length{<string1>}{<string2>}}
 
     The length item is used to extract the initial portion of a string. Both
@@ -8578,9 +9025,10 @@ ${length{<string1>}{<string2>}}
 
     ${length_<n>:<string>}
 
 
     ${length_<n>:<string>}
 
-    The result of this item is either the first <n> characters or the whole of
-    <string2>, whichever is the shorter. Do not confuse length with strlen,
-    which gives the length of a string.
+    The result of this item is either the first <n> bytes or the whole of <
+    string2>, whichever is the shorter. Do not confuse length with strlen,
+    which gives the length of a string. All measurement is done in bytes and is
+    not UTF-8 aware.
 
 ${listextract{<number>}{<string1>}{<string2>}{<string3>}}
 
 
 ${listextract{<number>}{<string1>}{<string2>}{<string3>}}
 
@@ -8589,7 +9037,7 @@ ${listextract{<number>}{<string1>}{<string2>}{<string3>}}
     ignored).
 
     After expansion, <string1> is interpreted as a list, colon-separated by
     ignored).
 
     After expansion, <string1> is interpreted as a list, colon-separated by
-    default, but the separator can be changed in the usual way.
+    default, but the separator can be changed in the usual way (6.21).
 
     The first field of the list is numbered one. If the number is negative, the
     fields are counted from the end of the list, with the rightmost one
 
     The first field of the list is numbered one. If the number is negative, the
     fields are counted from the end of the list, with the rightmost one
@@ -8607,7 +9055,7 @@ ${listextract{<number>}{<string1>}{<string2>}{<string3>}}
 
     ${listextract{-3}{<, x,42,99,& Mailer,,/bin/bash}{result: $value}}
 
 
     ${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
 
     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
@@ -8672,11 +9120,11 @@ ${lookup <search type> {<query>} {<string1>} {<string2>}}
 ${map{<string1>}{<string2>}}
 
     After expansion, <string1> is interpreted as a list, colon-separated by
 ${map{<string1>}{<string2>}}
 
     After expansion, <string1> is interpreted as a list, colon-separated by
-    default, but the separator can be changed in the usual way. For each item
-    in this list, its value is place in $item, and then <string2> is expanded
-    and added to the output as an item in a new list. The separator used for
-    the output list is the same as the one used for the input, but a separator
-    setting is not included in the output. For example:
+    default, but the separator can be changed in the usual way (6.21). For each
+    item in this list, its value is place in $item, and then <string2> is
+    expanded and added to the output as an item in a new list. The separator
+    used for the output list is the same as the one used for the input, but a
+    separator setting is not included in the output. For example:
 
     ${map{a:b:c}{[$item]}} ${map{<- x-y-z}{($item)}}
 
 
     ${map{a:b:c}{[$item]}} ${map{<- x-y-z}{($item)}}
 
@@ -8732,7 +9180,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
     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>}}
 
 
 ${prvscheck{<address>}{<secret>}{<string>}}
 
@@ -8758,12 +9206,12 @@ ${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
 
     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>}}
 
 
 ${readfile{<file name>}{<eol string>}}
 
-    The file name and end-of-line string are first expanded separately. The
-    file is then read, and its contents replace the entire item. All newline
+    The filename and end-of-line string are first expanded separately. The file
+    is then read, and its contents replace the entire item. All newline
     characters in the file are replaced by the end-of-line string if it is
     present. Otherwise, newlines are left in the string. String expansion is
     not applied to the contents of the file. If you want this, you must wrap
     characters in the file are replaced by the end-of-line string if it is
     present. Otherwise, newlines are left in the string. String expansion is
     not applied to the contents of the file. If you want this, you must wrap
@@ -8773,11 +9221,11 @@ ${readfile{<file name>}{<eol string>}}
     The redirect router has an option called forbid_filter_readfile which locks
     out the use of this expansion item in filter files.
 
     The redirect router has an option called forbid_filter_readfile which locks
     out the use of this expansion item in filter files.
 
-${readsocket{<name>}{<request>}{<timeout>}{<eol string>}{<fail string>}}
+${readsocket{<name>}{<request>}{<options>}{<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}}
 
     ${readsocket{/socket/name}{request string}}
     ${readsocket{inet:some.host:1234}{request string}}
@@ -8794,13 +9242,29 @@ ${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
     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}}
 
 
     ${readsocket{/socket/name}{request string}{3s}}
 
+    The third argument is a list of options, of which the first element is the
+    timeout and must be present if the argument is given. Further elements are
+    options of form name=value. Two option types is currently recognised:
+    shutdown and tls. The first defines whether (the default) or not a shutdown
+    is done on the connection after sending the request. Example, to not do so
+    (preferred, eg. by some webservers):
+
+    ${readsocket{/socket/name}{request string}{3s:shutdown=no}}
+
+    The second, tls, controls the use of TLS on the connection. Example:
+
+    ${readsocket{/socket/name}{request string}{3s:tls=yes}}
+
+    The default is to not use TLS. If it is enabled, a shutdown as descripbed
+    above is never done.
+
     A fourth argument allows you to change any newlines that are in the data
     that is read, in the same way as for readfile (see above). This example
     turns them into spaces:
     A fourth argument allows you to change any newlines that are in the data
     that is read, in the same way as for readfile (see above). This example
     turns them into spaces:
@@ -8811,13 +9275,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:
 
     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
 
     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
@@ -8840,13 +9304,13 @@ ${reduce{<string1>}{<string2>}{<string3>}}
 
     This operation reduces a list to a single, scalar string. After expansion,
     <string1> is interpreted as a list, colon-separated by default, but the
 
     This operation reduces a list to a single, scalar string. After expansion,
     <string1> is interpreted as a list, colon-separated by default, but the
-    separator can be changed in the usual way. Then <string2> is expanded and
-    assigned to the $value variable. After this, each item in the <string1>
-    list is assigned to $item in turn, and <string3> is expanded for each of
-    them. The result of that expansion is assigned to $value before the next
-    iteration. When the end of the list is reached, the final value of $value
-    is added to the expansion output. The reduce expansion item can be used in
-    a number of ways. For example, to add up a list of numbers:
+    separator can be changed in the usual way (6.21). Then <string2> is
+    expanded and assigned to the $value variable. After this, each item in the
+    <string1> list is assigned to $item, in turn, and <string3> is expanded for
+    each of them. The result of that expansion is assigned to $value before the
+    next iteration. When the end of the list is reached, the final value of
+    $value is added to the expansion output. The reduce expansion item can be
+    used in a number of ways. For example, to add up a list of numbers:
 
     ${reduce {<, 1,2,3}{0}{${eval:$value+$item}}}
 
 
     ${reduce {<, 1,2,3}{0}{${eval:$value+$item}}}
 
@@ -8862,7 +9326,7 @@ ${reduce{<string1>}{<string2>}{<string3>}}
 $rheader_<header name>: or $rh_<header name>:
 
     This item inserts "raw" header lines. It is described with the header
 $rheader_<header name>: or $rh_<header name>:
 
     This item inserts "raw" header lines. It is described with the header
-    expansion item above.
+    expansion item in section 11.5 above.
 
 ${run{<command> <args>}{<string1>}{<string2>}}
 
 
 ${run{<command> <args>}{<string1>}{<string2>}}
 
@@ -8940,8 +9404,8 @@ ${sg{<subject>}{<regex>}{<replacement>}}
     ${sg{abcdefabcdef}{abc}{xyz}}
 
     yields "xyzdefxyzdef". Because all three arguments are expanded before use,
     ${sg{abcdefabcdef}{abc}{xyz}}
 
     yields "xyzdefxyzdef". Because all three arguments are expanded before use,
-    if any $ or \ characters are required in the regular expression or in the
-    substitution string, they have to be escaped. For example:
+    if any $, } or \ characters are required in the regular expression or in
+    the substitution string, they have to be escaped. For example:
 
     ${sg{abcdef}{^(...)(...)\$}{\$2\$1}}
 
 
     ${sg{abcdef}{^(...)(...)\$}{\$2\$1}}
 
@@ -8952,6 +9416,33 @@ ${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.
 
     yields "K1=A K4=D K3=C". Note the use of "\N" to protect the contents of
     the regular expression from string expansion.
 
+    The regular expression is compiled in 8-bit mode, working against bytes
+    rather than any Unicode-aware character handling.
+
+${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 (6.21). 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
 ${substr{<string1>}{<string2>}{<string3>}}
 
     The three strings are expanded; the first two must yield numbers. Call them
@@ -8973,10 +9464,10 @@ ${substr{<string1>}{<string2>}{<string3>}}
     If the starting offset is greater than the string length the result is the
     null string; if the length plus starting offset is greater than the string
     length, the result is the right-hand part of the string, starting from the
     If the starting offset is greater than the string length the result is the
     null string; if the length plus starting offset is greater than the string
     length, the result is the right-hand part of the string, starting from the
-    given offset. The first character in the string has offset zero.
+    given offset. The first byte (character) in the string has offset zero.
 
     The substr expansion item can take negative offset values to count from the
 
     The substr expansion item can take negative offset values to count from the
-    right-hand end of its operand. The last character is offset -1, the
+    right-hand end of its operand. The last byte (character) is offset -1, the
     second-last is offset -2, and so on. Thus, for example,
 
     ${substr{-5}{2}{1234567}}
     second-last is offset -2, and so on. Thus, for example,
 
     ${substr{-5}{2}{1234567}}
@@ -8994,21 +9485,24 @@ ${substr{<string1>}{<string2>}{<string3>}}
     yields "1".
 
     When the second number is omitted from substr, the remainder of the string
     yields "1".
 
     When the second number is omitted from substr, the remainder of the string
-    is taken if the offset is positive. If it is negative, all characters in
-    the string preceding the offset point are taken. For example, an offset of
-    -1 and no length, as in these semantically identical examples:
+    is taken if the offset is positive. If it is negative, all bytes
+    (characters) in the string preceding the offset point are taken. For
+    example, an offset of -1 and no length, as in these semantically identical
+    examples:
 
     ${substr_-1:abcde}
     ${substr{-1}{abcde}}
 
     yields all but the last character of the string, that is, "abcd".
 
 
     ${substr_-1:abcde}
     ${substr{-1}{abcde}}
 
     yields all but the last character of the string, that is, "abcd".
 
+    All measurement is done in bytes and is not UTF-8 aware.
+
 ${tr{<subject>}{<characters>}{<replacements>}}
 
 ${tr{<subject>}{<characters>}{<replacements>}}
 
-    This item does single-character translation on its subject string. The
-    second argument is a list of characters to be translated in the subject
-    string. Each matching character is replaced by the corresponding character
-    from the replacement list. For example
+    This item does single-character (in bytes) translation on its subject
+    string. The second argument is a list of characters to be translated in the
+    subject string. Each matching character is replaced by the corresponding
+    character from the replacement list. For example
 
     ${tr{abcdea}{ac}{13}}
 
 
     ${tr{abcdea}{ac}{13}}
 
@@ -9017,6 +9511,8 @@ ${tr{<subject>}{<characters>}{<replacements>}}
     second, its last character is replicated. However, if it is empty, no
     translation takes place.
 
     second, its last character is replicated. However, if it is empty, no
     translation takes place.
 
+    All character handling is done in bytes and is not UTF-8 aware.
+
 
 11.6 Expansion operators
 ------------------------
 
 11.6 Expansion operators
 ------------------------
@@ -9032,6 +9528,8 @@ ${address:<string>}
     header line, and the effective address is extracted from it. If the string
     does not parse successfully, the result is empty.
 
     header line, and the effective address is extracted from it. If the string
     does not parse successfully, the result is empty.
 
+    The parsing correctly handles SMTPUTF8 Unicode in the string.
+
 ${addresses:<string>}
 
     The string (after expansion) is interpreted as a list of addresses in RFC
 ${addresses:<string>}
 
     The string (after expansion) is interpreted as a list of addresses in RFC
@@ -9047,15 +9545,21 @@ ${addresses:<string>}
 
     ${addresses:>& Chief <ceo@up.stairs>, sec@base.ment (dogsbody)}
 
 
     ${addresses:>& Chief <ceo@up.stairs>, sec@base.ment (dogsbody)}
 
-    expands to "ceo@up.stairs&sec@base.ment". Compare the address (singular)
-    expansion item, which extracts the working address from a single RFC2822
-    address. See the filter, map, and reduce items for ways of processing
-    lists.
+    expands to "ceo@up.stairs&sec@base.ment". The string is expanded first, so
+    if the expanded string starts with >, it may change the output separator
+    unintentionally. This can be avoided by setting the output separator
+    explicitly:
+
+    ${addresses:>:$h_from:}
+
+    Compare the address (singular) expansion item, which extracts the working
+    address from a single RFC2822 address. See the filter, map, and reduce
+    items for ways of processing lists.
 
     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
 
     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>
 
 
     From: =?iso-8859-2?Q?Last=2C_First?= <user@example.com>
 
@@ -9065,7 +9569,7 @@ ${addresses:<string>}
     "=2C". The second example below is passed the contents of "$header_from:",
     meaning it gets de-mimed. Exim sees the decoded "," so it treats it as two
     email addresses. The third example shows that the presence of a comma is
     "=2C". The second example below is passed the contents of "$header_from:",
     meaning it gets de-mimed. Exim sees the decoded "," so it treats it as two
     email addresses. The third example shows that the presence of a comma is
-    skipped when it is quoted.
+    skipped when it is quoted. The fourth example shows SMTPUTF8 handling.
 
     # exim -be '${addresses:From: \
     =?iso-8859-2?Q?Last=2C_First?= <user@example.com>}'
 
     # exim -be '${addresses:From: \
     =?iso-8859-2?Q?Last=2C_First?= <user@example.com>}'
@@ -9074,6 +9578,19 @@ ${addresses:<string>}
     Last:user@example.com
     # exim -be '${addresses:From: "Last, First" <user@example.com>}'
     user@example.com
     Last:user@example.com
     # exim -be '${addresses:From: "Last, First" <user@example.com>}'
     user@example.com
+    # exim -be '${addresses:?????? <??????????@example.jp>}'
+    ??????????@example.jp
+
+${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>}
 
 
 ${base62:<digits>}
 
@@ -9081,7 +9598,7 @@ ${base62:<digits>}
     to base 62 and output as a string of six characters, including leading
     zeros. In the few operating environments where Exim uses base 36 instead of
     base 62 for its message identifiers (because those systems do not have
     to base 62 and output as a string of six characters, including leading
     zeros. In the few operating environments where Exim uses base 36 instead of
     base 62 for its message identifiers (because those systems do not have
-    case-sensitive file names), base 36 is used by this operator, despite its
+    case-sensitive filenames), base 36 is used by this operator, despite its
     name. Note: Just to be absolutely clear: this is not base64 encoding.
 
 ${base62d:<base-62 digits>}
     name. Note: Just to be absolutely clear: this is not base64 encoding.
 
 ${base62d:<base-62 digits>}
@@ -9091,6 +9608,17 @@ ${base62d:<base-62 digits>}
     identifiers, base-36 digits. The number is converted to decimal and output
     as a string.
 
     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
 ${domain:<string>}
 
     The string is interpreted as an RFC 2822 address and the domain is
@@ -9104,6 +9632,12 @@ ${escape:<string>}
     most significant bit set (so-called "8-bit characters") count as printing
     or not is controlled by the print_topbitchars option.
 
     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
 ${eval:<string>} and ${eval10:<string>}
 
     These items supports simple arithmetic and bitwise logical operations in
@@ -9204,15 +9738,28 @@ ${hash_<n>_<m>:<string>}
 ${hex2b64:<hexstring>}
 
     This operator converts a hex string into one that is base64 encoded. This
 ${hex2b64:<hexstring>}
 
     This operator converts a hex string into one that is base64 encoded. This
-    can be useful for processing the output of the MD5 and SHA-1 hashing
-    functions.
+    can be useful for processing the output of the various hashing functions.
 
 ${hexquote:<string>}
 
     This operator converts non-printable characters in a string into a hex
     escape form. Byte values between 33 (!) and 126 (~) inclusive are left as
 
 ${hexquote:<string>}
 
     This operator converts non-printable characters in a string into a hex
     escape form. Byte values between 33 (!) and 126 (~) inclusive are left as
-    is, and other byte values are converted to "\xNN", for example a byte value
-    127 is converted to "\x7f".
+    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>}
 
 
 ${lc:<string>}
 
@@ -9220,6 +9767,8 @@ ${lc:<string>}
 
     ${lc:$local_part}
 
 
     ${lc:$local_part}
 
+    Case is defined per the system C locale.
+
 ${length_<number>:<string>}
 
     The length operator is a simpler interface to the length function that can
 ${length_<number>:<string>}
 
     The length operator is a simpler interface to the length function that can
@@ -9230,7 +9779,8 @@ ${length_<number>:<string>}
 
     See the description of the general length item above for details. Note that
     length is not the same as strlen. The abbreviation l can be used when 
 
     See the description of the general length item above for details. Note that
     length is not the same as strlen. The abbreviation l can be used when 
-    length is used as an operator.
+    length is used as an operator. All measurement is done in bytes and is not
+    UTF-8 aware.
 
 ${listcount:<string>}
 
 
 ${listcount:<string>}
 
@@ -9249,7 +9799,7 @@ ${local_part:<string>}
 
     The string is interpreted as an RFC 2822 address and the local part is
     extracted from it. If the string does not parse successfully, the result is
 
     The string is interpreted as an RFC 2822 address and the local part is
     extracted from it. If the string does not parse successfully, the result is
-    empty.
+    empty. The parsing correctly handles SMTPUTF8 Unicode in the string.
 
 ${mask:<IP address>/<bit count>}
 
 
 ${mask:<IP address>/<bit count>}
 
@@ -9279,6 +9829,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.
 
     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
 ${nhash_<n>_<m>:<string>}
 
     The nhash operator is a simpler interface to the numeric hashing function
@@ -9314,6 +9867,10 @@ ${quote_local_part:<string>}
     you are creating a new email address from the contents of $local_part (or
     any other unknown data), you should always use this operator.
 
     you are creating a new email address from the contents of $local_part (or
     any other unknown data), you should always use this operator.
 
+    This quoting determination is not SMTPUTF8-aware, thus quoting non-ASCII
+    data will likely use the quoting form. Thus ${quote_local_part:??????} will
+    always become "??????".
+
 ${quote_<lookup-type>:<string>}
 
     This operator applies lookup-specific quoting rules to the string. Each
 ${quote_<lookup-type>:<string>}
 
     This operator applies lookup-specific quoting rules to the string. Each
@@ -9343,7 +9900,7 @@ ${randint:<n>}
 ${reverse_ip:<ipaddr>}
 
     This operator reverses an IP address; for IPv4 addresses, the result is in
 ${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,
 
     dotted-nibble hexadecimal form. In both cases, this is the "natural" form
     for DNS. For example,
 
@@ -9360,7 +9917,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 
     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
 
     contains only characters in the range 33-126, and no instances of the
     characters
 
@@ -9393,12 +9950,31 @@ ${sha1:<string>}
     it as a 40-digit hexadecimal number, in which any letters are in upper
     case.
 
     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.
 
 
-    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.
+${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.
+
+    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, or OpenSSL 1.1.1 or later. The macro
+    "_CRYPTO_HASH_SHA3" will be defined if it is supported.
 
 ${stat:<string>}
 
 
 ${stat:<string>}
 
@@ -9418,12 +9994,13 @@ ${stat:<string>}
 
 ${str2b64:<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>}
 
     The item is replace by the length of the expanded string, expressed as a
 
 ${strlen:<string>}
 
     The item is replace by the length of the expanded string, expressed as a
-    decimal number. Note: Do not confuse strlen with length.
+    decimal number. Note: Do not confuse strlen with length. All measurement is
+    done in bytes and is not UTF-8 aware.
 
 ${substr_<start>_<length>:<string>}
 
 
 ${substr_<start>_<length>:<string>}
 
@@ -9434,7 +10011,8 @@ ${substr_<start>_<length>:<string>}
     ${substr{<start>}{<length>}{<string>}}
 
     See the description of the general substr item above for details. The
     ${substr{<start>}{<length>}{<string>}}
 
     See the description of the general substr item above for details. The
-    abbreviation s can be used when substr is used as an operator.
+    abbreviation s can be used when substr is used as an operator. All
+    measurement is done in bytes and is not UTF-8 aware.
 
 ${time_eval:<string>}
 
 
 ${time_eval:<string>}
 
@@ -9450,13 +10028,33 @@ ${time_interval:<string>}
 
 ${uc:<string>}
 
 
 ${uc:<string>}
 
-    This forces the letters in the string into upper-case.
+    This forces the letters in the string into upper-case. Case is defined per
+    the system C locale.
 
 ${utf8clean:<string>}
 
     This replaces any invalid utf-8 sequence in the string by the character "?
     ".
 
 
 ${utf8clean:<string>}
 
     This replaces any invalid utf-8 sequence in the string by the character "?
     ".
 
+    In versions of Exim before 4.92, this did not correctly do so for a
+    truncated final codepoint's encoding, and the character would be silently
+    dropped. If you must handle detection of this scenario across both sets of
+    Exim behavior, the complexity will depend upon the task. For instance, to
+    detect if the first character is multibyte and a 1-byte extraction can be
+    successfully used as a path component (as is common for dividing up
+    delivery folders), you might use:
+
+    condition = ${if inlist{${utf8clean:${length_1:$local_part}}}{:?}{yes}{no}}
+
+    (which will false-positive if the first character of the local part is a
+    literal question mark).
+
+${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
 -------------------------
 
 11.7 Expansion conditions
 -------------------------
@@ -9501,7 +10099,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
     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.
     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.
@@ -9559,26 +10157,26 @@ crypteq {<string1>}{<string2>}
     The following encryption types (whose names are matched case-independently)
     are supported:
 
     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.
 
         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.
 
         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.
 
         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.
 
         use up to 16 characters of the password in some operating systems.
         Again, in modern operating systems, more characters may be used.
 
@@ -9630,7 +10228,8 @@ eq {<string1>}{<string2>}, eqi {<string1>}{<string2>}
 
     The two substrings are first expanded. The condition is true if the two
     resulting strings are identical. For eq the comparison includes the case of
 
     The two substrings are first expanded. The condition is true if the two
     resulting strings are identical. For eq the comparison includes the case of
-    letters, whereas for eqi the comparison is case-independent.
+    letters, whereas for eqi the comparison is case-independent, where case is
+    defined per the system C locale.
 
 exists {<file name>}
 
 
 exists {<file name>}
 
@@ -9649,16 +10248,16 @@ forall{<a list>}{<a condition>}, forany{<a list>}{<a condition>}
 
     These conditions iterate over a list. The first argument is expanded to
     form the list. By default, the list separator is a colon, but it can be
 
     These conditions iterate over a list. The first argument is expanded to
     form the list. By default, the list separator is a colon, but it can be
-    changed by the normal method. The second argument is interpreted as a
-    condition that is to be applied to each item in the list in turn. During
+    changed by the normal method (6.21). The second argument is interpreted as
+    condition that is to be applied to each item in the list in turn. During
     the interpretation of the condition, the current list item is placed in a
     variable called $item.
 
     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.
 
         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.
 
         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.
 
@@ -9679,20 +10278,23 @@ ge {<string1>}{<string2>}, gei {<string1>}{<string2>}
     The two substrings are first expanded. The condition is true if the first
     string is lexically greater than or equal to the second string. For ge the
     comparison includes the case of letters, whereas for gei the comparison is
     The two substrings are first expanded. The condition is true if the first
     string is lexically greater than or equal to the second string. For ge the
     comparison includes the case of letters, whereas for gei the comparison is
-    case-independent.
+    case-independent. Case and collation order are defined per the system C
+    locale.
 
 gt {<string1>}{<string2>}, gti {<string1>}{<string2>}
 
     The two substrings are first expanded. The condition is true if the first
     string is lexically greater than the second string. For gt the comparison
     includes the case of letters, whereas for gti the comparison is
 
 gt {<string1>}{<string2>}, gti {<string1>}{<string2>}
 
     The two substrings are first expanded. The condition is true if the first
     string is lexically greater than the second string. For gt the comparison
     includes the case of letters, whereas for gti the comparison is
-    case-independent.
+    case-independent. Case and collation order are defined per the system C
+    locale.
 
 inlist {<string1>}{<string2>}, inlisti {<string1>}{<string2>}
 
     Both strings are expanded; the second string is treated as a list of simple
     strings; if the first string is a member of the second, then the condition
 
 inlist {<string1>}{<string2>}, inlisti {<string1>}{<string2>}
 
     Both strings are expanded; the second string is treated as a list of simple
     strings; if the first string is a member of the second, then the condition
-    is true.
+    is true. For the case-independent inlisti condition, case is defined per
+    the system C locale.
 
     These are simpler to use versions of the more powerful forany condition.
     Examples, and the forany equivalents:
 
     These are simpler to use versions of the more powerful forany condition.
     Examples, and the forany equivalents:
@@ -9715,11 +10317,12 @@ isip {<string>}, isip4 {<string>}, isip6 {<string>}
     empty component (adjacent colons) is present. Only one empty component is
     permitted.
 
     empty component (adjacent colons) is present. Only one empty component is
     permitted.
 
-    Note: The checks are just on the form of the address; actual numerical
-    values are not considered. Thus, for example, 999.999.999.999 passes the
-    IPv4 check. The main use of these tests is to distinguish between IP
-    addresses and host names, or between IPv4 and IPv6 addresses. For example,
-    you could use
+    Note: The checks used to be just on the form of the address; actual
+    numerical values were not considered. Thus, for example, 999.999.999.999
+    passed the IPv4 check. This is no longer the case.
+
+    The main use of these tests is to distinguish between IP addresses and host
+    names, or between IPv4 and IPv6 addresses. For example, you could use
 
     ${if isip4{$sender_host_address}...
 
 
     ${if isip4{$sender_host_address}...
 
@@ -9727,7 +10330,7 @@ isip {<string>}, isip4 {<string>}, isip6 {<string>}
 
 ldapauth {<ldap query>}
 
 
 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
     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
@@ -9742,14 +10345,16 @@ le {<string1>}{<string2>}, lei {<string1>}{<string2>}
     The two substrings are first expanded. The condition is true if the first
     string is lexically less than or equal to the second string. For le the
     comparison includes the case of letters, whereas for lei the comparison is
     The two substrings are first expanded. The condition is true if the first
     string is lexically less than or equal to the second string. For le the
     comparison includes the case of letters, whereas for lei the comparison is
-    case-independent.
+    case-independent. Case and collation order are defined per the system C
+    locale.
 
 lt {<string1>}{<string2>}, lti {<string1>}{<string2>}
 
     The two substrings are first expanded. The condition is true if the first
     string is lexically less than the second string. For lt the comparison
     includes the case of letters, whereas for lti the comparison is
 
 lt {<string1>}{<string2>}, lti {<string1>}{<string2>}
 
     The two substrings are first expanded. The condition is true if the first
     string is lexically less than the second string. For lt the comparison
     includes the case of letters, whereas for lti the comparison is
-    case-independent.
+    case-independent. Case and collation order are defined per the system C
+    locale.
 
 match {<string1>}{<string2>}
 
 
 match {<string1>}{<string2>}
 
@@ -9772,7 +10377,8 @@ match {<string1>}{<string2>}
     there is no circumflex, the expression is not anchored, and it may match
     anywhere in the subject, not just at the start. If you want the pattern to
     match at the end of the subject, you must include the "$" metacharacter at
     there is no circumflex, the expression is not anchored, and it may match
     anywhere in the subject, not just at the start. If you want the pattern to
     match at the end of the subject, you must include the "$" metacharacter at
-    an appropriate point.
+    an appropriate point. All character handling is done in bytes and is not
+    UTF-8 aware, but we might change this in a future Exim release.
 
     At the start of an if expansion the values of the numeric variable
     substitutions $1 etc. are remembered. Obeying a match condition that
 
     At the start of an if expansion the values of the numeric variable
     substitutions $1 etc. are remembered. Obeying a match condition that
@@ -9802,11 +10408,11 @@ match_ip {<string1>}{<string2>}
 
     The specific types of host list item that are permitted in the list are:
 
 
     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
 
         could be useful for testing for a locally submitted message or one from
         specific hosts in a single test such as
 
@@ -9814,9 +10420,9 @@ match_ip {<string1>}{<string2>}
 
         where the first item in the list is the empty string.
 
 
         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
         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
@@ -9851,9 +10457,9 @@ match_local_part {<string1>}{<string2>}
     ${if match_domain{a.b.c}{x.y.z:a.b.c:p.q.r}{yes}{no}}
 
     In each case, the second argument may contain any of the allowable items
     ${if match_domain{a.b.c}{x.y.z:a.b.c:p.q.r}{yes}{no}}
 
     In each case, the second argument may contain any of the allowable items
-    for a list of the appropriate type. Also, because the second argument
-    (after expansion) is a standard form of list, it is possible to refer to a
-    named list. Thus, you can use conditions like this:
+    for a list of the appropriate type. Also, because the second argument is a
+    standard form of list, it is possible to refer to a named list. Thus, you
+    can use conditions like this:
 
     ${if match_domain{$domain}{+local_domains}{...
 
 
     ${if match_domain{$domain}{+local_domains}{...
 
@@ -9872,11 +10478,11 @@ match_local_part {<string1>}{<string2>}
 
 pam {<string1>:<string2>:...}
 
 
 pam {<string1>:<string2>:...}
 
-    Pluggable Authentication Modules (http://www.kernel.org/pub/linux/libs/pam/
-    ) are a facility that is available in the latest releases of Solaris and in
-    some GNU/Linux distributions. The Exim support, which is intended for use
-    in conjunction with the SMTP AUTH command, is available only if Exim is
-    compiled with
+    Pluggable Authentication Modules (https://mirrors.edge.kernel.org/pub/linux
+    /libs/pam/) are a facility that is available in the latest releases of
+    Solaris and in some GNU/Linux distributions. The Exim support, which is
+    intended for use in conjunction with the SMTP AUTH command, is available
+    only if Exim is compiled with
 
     SUPPORT_PAM=yes
 
 
     SUPPORT_PAM=yes
 
@@ -9907,11 +10513,7 @@ pam {<string1>:<string2>:...}
     In some operating systems, PAM authentication can be done only from a
     process running as root. Since Exim is running as the Exim user when
     receiving messages, this means that PAM cannot be used directly in those
     In some operating systems, PAM authentication can be done only from a
     process running as root. Since Exim is running as the Exim user when
     receiving messages, this means that PAM cannot be used directly in those
-    systems. A patched version of the pam_unix module that comes with the Linux
-    PAM package is available from http://www.e-admin.de/pam_exim/. The patched
-    module allows one special uid/gid combination, in addition to root, to
-    authenticate. If you build the patched module to allow the Exim user and
-    group, PAM can then be used from an Exim authenticator.
+    systems.
 
 pwcheck {<string1>:<string2>}
 
 
 pwcheck {<string1>:<string2>}
 
@@ -10042,13 +10644,13 @@ $0, $1, etc
 
     When a match expansion condition succeeds, these variables contain the
     captured substrings identified by the regular expression during subsequent
 
     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
 
 
 $acl_arg1, $acl_arg2, etc
 
@@ -10144,7 +10746,7 @@ $address_pipe
 
 $auth1 - $auth3
 
 
 $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
     Elsewhere, they are empty.
 
 $authenticated_id
@@ -10154,10 +10756,13 @@ $authenticated_id
     $authenticated_id (see chapter 33). For example, a user/password
     authenticator configuration might preserve the user name for use in the
     routers. Note that this is not the same information that is saved in
     $authenticated_id (see chapter 33). For example, a user/password
     authenticator configuration might preserve the user name for use in the
     routers. Note that this is not the same information that is saved in
-    $sender_host_authenticated. When a message is submitted locally (that is,
-    not over a TCP connection) the value of $authenticated_id is normally the
-    login name of the calling process. However, a trusted user can override
-    this by means of the -oMai command line option.
+    $sender_host_authenticated.
+
+    When a message is submitted locally (that is, not over a TCP connection)
+    the value of $authenticated_id is normally the login name of the calling
+    process. However, a trusted user can override this by means of the -oMai
+    command line option. This second case also sets up information used by the
+    $authresults expansion item.
 
 $authenticated_fail_id
 
 
 $authenticated_fail_id
 
@@ -10216,13 +10821,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
 
     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
 
 $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
 
 
 $caller_gid
 
@@ -10238,32 +10843,53 @@ $caller_uid
     $originator_uid). If Exim re-execs itself, this variable in the new
     incarnation normally contains the Exim 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
 
     The building process for Exim keeps a count of the number of times it has
     been compiled. This serves to distinguish different compilations of the
 
 $compile_number
 
     The building process for Exim keeps a count of the number of times it has
     been compiled. This serves to distinguish different compilations of the
-    same version of the program.
+    same version of Exim.
 
 
-$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_verify_status
+
+    Results of DKIM verification. For details see section 57.3.
+
+$dkim_cur_signer, $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
+    section 57.3.
+
+$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
+    section 57.3.
 
 $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
 
 $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
 
 
 $domain
 
@@ -10289,7 +10915,7 @@ $domain
 
     The $domain variable is also used in some other circumstances:
 
 
     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
         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
@@ -10297,20 +10923,20 @@ $domain
         sender domain is placed in $domain during the expansions of hosts, 
         interface, and port in the smtp transport.
 
         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.
 
         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).
 
         $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
 
 
 $domain_data
 
@@ -10337,22 +10963,25 @@ $exim_uid
 
     This variable contains the numerical value of the Exim user id.
 
 
     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>
 
     This is not strictly an expansion variable. It is expansion syntax for
     inserting the message header line with the given name. Note that the name
     must be terminated by colon or white space, because it may contain a wide
 
 $header_<name>
 
     This is not strictly an expansion variable. It is expansion syntax for
     inserting the message header line with the given name. Note that the name
     must be terminated by colon or white space, because it may contain a wide
-    variety of characters. Note also that braces must not be used.
+    variety of characters. Note also that braces must not be used. See the full
+    description in section 11.5 above.
 
 $headers_added
 
     Within an ACL this variable contains the headers added so far by the ACL
 
 $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
     list.
 
 $home
@@ -10364,7 +10993,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
     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
 
 
 $host
 
@@ -10405,11 +11035,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".
 
     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".
 
         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".
 
         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,10 +11054,25 @@ $host_lookup_deferred
     checking the result, the name is not accepted, and $host_lookup_deferred is
     set to "1". See also $sender_host_name.
 
     checking the result, the name is not accepted, and $host_lookup_deferred is
     set to "1". See also $sender_host_name.
 
+    Performing these checks sets up information used by the $authresults
+    expansion item.
+
 $host_lookup_failed
 
     See $host_lookup_deferred.
 
 $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
 $inode
 
     The only time this variable is set is while expanding the directory_file
@@ -10482,7 +11127,7 @@ $local_part
 
     When a message is being delivered to a file, pipe, or autoreply transport
     as a result of aliasing or forwarding, $local_part is set to the local part
 
     When a message is being delivered to a file, pipe, or autoreply transport
     as a result of aliasing or forwarding, $local_part is set to the local part
-    of the parent address, not to the file name or command (see $address_file
+    of the parent address, not to the filename or command (see $address_file
     and $address_pipe).
 
     When an ACL is running for a RCPT command, $local_part contains the local
     and $address_pipe).
 
     When an ACL is running for a RCPT command, $local_part contains the local
@@ -10540,7 +11185,7 @@ $local_part_suffix
 $local_scan_data
 
     This variable contains the text returned by the local_scan() function when
 $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
 
 
 $local_user_gid
 
@@ -10580,7 +11225,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
     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
 
 
 $mailstore_basename
 
@@ -10595,13 +11242,13 @@ $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 
 
     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
 
     This variable contains the number of bytes in the longest line that was
     received as part of the message, not counting the line termination
 
 $max_received_linelength
 
     This variable contains the number of bytes in the longest line that was
     received as part of the message, not counting the line termination
-    character(s).
+    character(s). It is not valid if the spool_files_wireformat option is used.
 
 $message_age
 
 
 $message_age
 
@@ -10633,6 +11280,9 @@ $message_body_size
     that separates the body from the header. Newlines are included in the
     count. See also $message_size, $body_linecount, and $body_zerocount.
 
     that separates the body from the header. Newlines are included in the
     count. See also $message_size, $body_linecount, and $body_zerocount.
 
+    If the spool file is wireformat (see the spool_files_wireformat main
+    option) the CRLF line-terminators are included in the count.
+
 $message_exim_id
 
     When a message is being received or delivered, this variable contains the
 $message_exim_id
 
     When a message is being received or delivered, this variable contains the
@@ -10657,7 +11307,7 @@ $message_headers_raw
 
 $message_id
 
 
 $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
 
 
 $message_linecount
 
@@ -10685,6 +11335,8 @@ $message_linecount
     In the MAIL and RCPT ACLs, the value is zero because at that stage the
     message has not yet been received.
 
     In the MAIL and RCPT ACLs, the value is zero because at that stage the
     message has not yet been received.
 
+    This variable is not valid if the spool_files_wireformat option is used.
+
 $message_size
 
     When a message is being processed, this variable contains its size in
 $message_size
 
     When a message is being processed, this variable contains its size in
@@ -10704,7 +11356,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
 
     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
 
 
 $n0 - $n9
 
@@ -10788,20 +11440,31 @@ $primary_hostname
     where available) in an attempt to acquire a fully qualified host name. See
     also $smtp_active_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,
 $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,
 
 $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,
 
 $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
 
 
 $qualify_domain
 
@@ -10812,6 +11475,10 @@ $qualify_recipient
     The value set for the qualify_recipient option in the configuration file,
     or if not set, the value of $qualify_domain.
 
     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
 $rcpt_count
 
     When a message is being received by SMTP, this variable contains the number
@@ -10854,15 +11521,12 @@ $received_ip_address
     line option.
 
     As well as being useful in ACLs (including the "connect" ACL), these
     line option.
 
     As well as being useful in ACLs (including the "connect" ACL), these
-    variable could be used, for example, to make the file name for a TLS
+    variable could be used, for example, to make the filename for a TLS
     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
     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
 
 
 $received_port
 
@@ -10914,18 +11578,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:
 
     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.
 
         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).
 
         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.
 
     The main use of this variable is expected to be to distinguish between
     rejections of MAIL and rejections of RCPT.
@@ -10956,7 +11620,12 @@ $recipients_count
 $regex_match_string
 
     This variable is set to contain the matching regular expression after a 
 $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
 
 
 $reply_address
 
@@ -11059,6 +11728,11 @@ $sender_fullhost
     if it is identical to the verified host name or to the host's IP address in
     square brackets.
 
     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
 $sender_helo_name
 
     When a message is received from a remote host that has issued a HELO or
@@ -11068,8 +11742,9 @@ $sender_helo_name
 
 $sender_host_address
 
 
 $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
 
 
 $sender_host_authenticated
 
@@ -11082,8 +11757,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,
 
     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:
 
     It is likely that you will need to coerce DNSSEC support on in the resolver
     library, by setting:
@@ -11091,14 +11766,16 @@ $sender_host_dnssec
     dns_dnssec_ok = 1
 
     Exim does not perform DNSSEC validation itself, instead leaving that to a
     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.
 
 
     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
 $sender_host_name
 
     When a message is received from a remote host, this variable contains the
@@ -11127,21 +11804,21 @@ $sender_host_name
     these lookups altogether. The lookup happens only if one or more of the
     following are true:
 
     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.)
 
         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.
 
         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.
 
         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 = @ : @[]
         domains in helo_lookup_domains. The default value of this option is
 
           helo_lookup_domains = @ : @[]
@@ -11164,7 +11841,7 @@ $sender_ident
 $sender_rate_xxx
 
     A number of variables whose names begin $sender_rate_ are set as part of
 $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
 
 
 $sender_rcvhost
 
@@ -11233,6 +11910,12 @@ $smtp_command_argument
     variable is somewhat redundant, but is retained for backwards
     compatibility.
 
     variable is somewhat redundant, but is retained for backwards
     compatibility.
 
+$smtp_command_history
+
+    A comma-separated list (with no whitespace) of the most-recent SMTP
+    commands received, in time-order left to right. Only a limited number of
+    commands are remembered.
+
 $smtp_count_at_connection_start
 
     This variable is set greater than zero only in processes spawned by the
 $smtp_count_at_connection_start
 
     This variable is set greater than zero only in processes spawned by the
@@ -11258,7 +11941,13 @@ $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
 
     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.
+
+$spf_header_comment, $spf_received, $spf_result, $spf_result_guessed, 
+    $spf_smtp_comment
+
+    These variables are only available if Exim is built with SPF support. For
+    details see section 57.4.
 
 $spool_directory
 
 
 $spool_directory
 
@@ -11313,33 +12002,41 @@ $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
 
     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.
+
+    Note: Under versions of OpenSSL preceding 1.1.1, when a list of more than
+    one file is used for tls_certificate, this variable is not reliable.
 
 $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
 
 $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
 
 $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
 
 $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.
 
 
 $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
     refers to the outbound.
 
 $tls_out_certificate_verified
@@ -11353,7 +12050,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.
     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
     encrypted and non-encrypted connections during ACL processing.
 
     The deprecated $tls_cipher variable is the same as $tls_in_cipher during
@@ -11363,10 +12060,14 @@ $tls_in_cipher
 $tls_out_cipher
 
     This variable is cleared before any outgoing SMTP connection is made, and
 $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.
 
     for details of TLS support and chapter 30 for details of the smtp
     transport.
 
+$tls_out_dane
+
+    DANE active status. See section 42.15.
+
 $tls_in_ocsp
 
     When a message is received from a remote client connection the result of
 $tls_in_ocsp
 
     When a message is received from a remote client connection the result of
@@ -11388,7 +12089,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
     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
 
     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
@@ -11399,14 +12102,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
     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,
 
 $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.
 
     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.
 
@@ -11419,6 +12124,10 @@ $tls_out_sni
     During outbound SMTP deliveries, this variable reflects the value of the 
     tls_sni option on the transport.
 
     During outbound SMTP deliveries, this variable reflects the value of the 
     tls_sni option on the transport.
 
+$tls_out_tlsa_usage
+
+    Bitfield of TLSA record types found. See section 42.15.
+
 $tod_bsdinbox
 
     The time of day and the date, in the format required for BSD-style mailbox
 $tod_bsdinbox
 
     The time of day and the date, in the format required for BSD-style mailbox
@@ -11471,6 +12180,12 @@ $value
     operation, or external command, as described above. It is also used during
     a reduce expansion.
 
     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.
 $version_number
 
     The version number of Exim.
@@ -11478,12 +12193,12 @@ $version_number
 $warn_message_delay
 
     This variable is set only during the creation of a message warning about a
 $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
 
 $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.
 
 
 
 
 
 
@@ -11537,6 +12252,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.
 
 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
 -----------------------------
 
 12.2 Calling Perl subroutines
 -----------------------------
@@ -11669,7 +12389,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
     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.21. 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 ; \
 to change the separator to avoid having to double all the colons. For example:
 
 local_interfaces = <; 127.0.0.1 ; \
@@ -11732,9 +12452,9 @@ Another way of doing this would be to use macros and the -D option. However,
 configuration by -D is allowed only when the caller is root or exim.
 
 The value of -oX is a list of items. The default colon separator can be changed
 configuration by -D is allowed only when the caller is root or exim.
 
 The value of -oX is a list of items. The default colon separator can be changed
-in the usual way if required. If there are any items that do not contain dots
-or colons (that is, are not IP addresses), the value of daemon_smtp_ports is
-replaced by the list of those items. If there are any items that do contain
+in the usual way (6.21) if required. If there are any items that do not contain
+dots or colons (that is, are not IP addresses), the value of daemon_smtp_ports
+is replaced by the list of those items. If there are any items that do contain
 dots or colons, the value of local_interfaces is replaced by those items. Thus,
 for example,
 
 dots or colons, the value of local_interfaces is replaced by those items. Thus,
 for example,
 
@@ -11749,20 +12469,27 @@ since local_interfaces now contains no items without ports, the value of
 daemon_smtp_ports is no longer relevant in this example.)
 
 
 daemon_smtp_ports is no longer relevant in this example.)
 
 
-13.4 Support for the obsolete SSMTP (or SMTPS) protocol
--------------------------------------------------------
+13.4 Support for the submissions (aka SSMTP or SMTPS) protocol
+--------------------------------------------------------------
+
+Exim supports the use of TLS-on-connect, used by mail clients in the
+"submissions" protocol, historically also known as SMTPS or SSMTP. For some
+years, IETF Standards Track documents only blessed the STARTTLS-based
+Submission service (port 587) while common practice was to support the same
+feature set on port 465, but using TLS-on-connect. If your installation needs
+to provide service to mail clients (Mail User Agents, MUAs) then you should
+provide service on both the 587 and the 465 TCP ports.
+
+If the tls_on_connect_ports option is set to a list of port numbers or service
+names, connections to those ports must first establish TLS, before proceeding
+to the application layer use of the SMTP protocol.
 
 
-Exim supports the obsolete SSMTP protocol (also known as SMTPS) that was used
-before the STARTTLS command was standardized for SMTP. Some legacy clients
-still use this protocol. If the tls_on_connect_ports option is set to a list of
-port numbers or service names, connections to those ports must use SSMTP. The
-most common use of this option is expected to be
+The common use of this option is expected to be
 
 tls_on_connect_ports = 465
 
 
 tls_on_connect_ports = 465
 
-because 465 is the usual port number used by the legacy clients. There is also
-a command line option -tls-on-connect, which forces all ports to behave in this
-way when a daemon is started.
+per RFC 8314. There is also a command line option -tls-on-connect, which forces
+all ports to behave in this way when a daemon is started.
 
 Warning: Setting tls_on_connect_ports does not of itself cause the daemon to
 listen on those ports. You must still specify them in daemon_smtp_ports, 
 
 Warning: Setting tls_on_connect_ports does not of itself cause the daemon to
 listen on those ports. You must still specify them in daemon_smtp_ports, 
@@ -11906,7 +12633,7 @@ the smtp transport in chapter 30 for more details.
 ===============================================================================
 14. MAIN CONFIGURATION
 
 ===============================================================================
 14. MAIN CONFIGURATION
 
-The first part of the run time configuration file contains three types of item:
+The first part of the runtime configuration file contains three types of item:
 
   * Macro definitions: These lines start with an upper case letter. See section
     6.4 for details of macro processing.
 
   * Macro definitions: These lines start with an upper case letter. See section
     6.4 for details of macro processing.
@@ -11918,7 +12645,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.
   * 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
 
 This chapter specifies all the main configuration options, along with their
 types and default values. For ease of finding a particular option, they appear
@@ -11932,6 +12659,7 @@ more than one group.
 ------------------
 
 bi_command            to run for -bi command line option
 ------------------
 
 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
 disable_ipv6          do no IPv6 processing
 keep_malformed        for broken files - should not happen
 localhost_number      for unique message ids in clusters
@@ -11939,6 +12667,7 @@ message_body_newlines retain newlines in $message_body
 message_body_visible  how much to show in $message_body
 mua_wrapper           run in "MUA wrapper" mode
 print_topbitchars     top-bit characters are printing
 message_body_visible  how much to show in $message_body
 mua_wrapper           run in "MUA wrapper" mode
 print_topbitchars     top-bit characters are printing
+spool_wireformat      use wire-format spool data files when possible
 timezone              force time zone
 
 
 timezone              force time zone
 
 
@@ -11956,22 +12685,24 @@ spool_directory       override compiled-in value
 14.3 Privilege controls
 -----------------------
 
 14.3 Privilege controls
 -----------------------
 
-admin_groups              groups that are Exim admin users
-deliver_drop_privilege    drop root for delivery processes
-local_from_check          insert Sender: if necessary
-local_from_prefix         for testing From: for local sender
-local_from_suffix         for testing From: for local sender
-local_sender_retain       keep Sender: from untrusted user
-never_users               do not run deliveries as these
-prod_requires_admin       forced delivery requires admin user
-queue_list_requires_admin queue listing requires admin user
-trusted_groups            groups that are trusted
-trusted_users             users that are trusted
+admin_groups                     groups that are Exim admin users
+commandline_checks_require_admin require admin for various checks
+deliver_drop_privilege           drop root for delivery processes
+local_from_check                 insert Sender: if necessary
+local_from_prefix                for testing From: for local sender
+local_from_suffix                for testing From: for local sender
+local_sender_retain              keep Sender: from untrusted user
+never_users                      do not run deliveries as these
+prod_requires_admin              forced delivery requires admin user
+queue_list_requires_admin        queue listing requires admin user
+trusted_groups                   groups that are trusted
+trusted_users                    users that are trusted
 
 
 14.4 Logging
 ------------
 
 
 
 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
 hosts_connection_nolog exemption from connect logging
 log_file_path          override compiled-in value
 log_selector           set/unset optional logging
@@ -11979,8 +12710,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
 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_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
 syslog_processname     set syslog "ident" field
 syslog_timestamp       timestamp syslog lines
 write_rejectlog        control use of message log
@@ -12025,8 +12758,9 @@ message_id_header_text   ditto
 14.8 Embedded Perl Startup
 --------------------------
 
 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
 
 
 14.9 Daemon
@@ -12084,6 +12818,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_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
 acl_smtp_predata       ACL for start of data
 acl_smtp_quit          ACL for QUIT
 acl_smtp_rcpt          ACL for RCPT
@@ -12091,6 +12826,7 @@ acl_smtp_starttls      ACL for STARTTLS
 acl_smtp_vrfy          ACL for VRFY
 av_scanner             specify virus scanner
 check_rfc2047_length   check length of RFC 2047 "encoded words"
 acl_smtp_vrfy          ACL for VRFY
 av_scanner             specify virus scanner
 check_rfc2047_length   check length of RFC 2047 "encoded words"
+dns_cname_loops        follow CNAMEs returned by resolver
 dns_csa_search_limit   control CSA parent search depth
 dns_csa_use_reverse    en/disable CSA IP reverse search
 header_maxsize         total size of message header
 dns_csa_search_limit   control CSA parent search depth
 dns_csa_use_reverse    en/disable CSA IP reverse search
 header_maxsize         total size of message header
@@ -12102,6 +12838,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
 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()
 host_reject_connection reject connection from these hosts
 hosts_treat_as_local   useful in some cluster configurations
 local_scan_timeout     timeout for local_scan()
@@ -12132,6 +12869,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_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
 tls_ocsp_file            location of server certificate status proof
 tls_on_connect_ports     specify SSMTP (SMTPS) ports
 tls_privatekey           location of server private key
@@ -12179,6 +12917,7 @@ receive_timeout for non-SMTP messages
 
 See also the Policy controls section above.
 
 
 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
 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
@@ -12216,10 +12955,13 @@ smtp_return_error_details        give detail on rejections
 
 accept_8bitmime            advertise 8BITMIME
 auth_advertise_hosts       advertise AUTH to these hosts
 
 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
 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
 
 
 tls_advertise_hosts        advertise TLS to these hosts
 
 
@@ -12264,6 +13006,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_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
 dns_use_edns0            parameter for resolver
 hold_domains             hold delivery for these domains
 local_interfaces         for routing checks
@@ -12288,6 +13031,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_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
 bounce_return_message        include original message in bounce
 bounce_return_size_limit     limit on returned message
 bounce_sender_authentication send authenticated sender with bounce
@@ -12306,9 +13050,9 @@ warn_message_file            content of warning message
 
 Those options that undergo string expansion before use are marked with *.
 
 
 Those options that undergo string expansion before use are marked with *.
 
-+---------------+---------+-------------+-------------+
++-----------------------------------------------------+
 |accept_8bitmime|Use: main|Type: boolean|Default: true|
 |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
 
 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
@@ -12320,155 +13064,170 @@ that in today's Internet, this causes more problems than it solves. It now
 defaults to true. A more detailed analysis of the issues is provided by Dan
 Bernstein:
 
 defaults to true. A more detailed analysis of the issues is provided by Dan
 Bernstein:
 
-http://cr.yp.to/smtp/8bitmime.html
+https://cr.yp.to/smtp/8bitmime.html
 
 To log received 8BITMIME status use
 
 log_selector = +8bitmime
 
 
 To log received 8BITMIME status use
 
 log_selector = +8bitmime
 
-+------------+---------+-------------+--------------+
++---------------------------------------------------+
 |acl_not_smtp|Use: main|Type: string*|Default: unset|
 |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
 
 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|
 |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.
 
 
 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|
 |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
 
 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|
 |acl_smtp_auth|Use: main|Type: string*|Default: unset|
-+-------------+---------+-------------+--------------+
++----------------------------------------------------+
 
 This option defines the ACL that is run when an SMTP AUTH command is received.
 
 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|
 |acl_smtp_connect|Use: main|Type: string*|Default: unset|
-+----------------+---------+-------------+--------------+
++-------------------------------------------------------+
 
 This option defines the ACL that is run when an SMTP connection is received.
 
 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|
 |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
 
 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
 
 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
+section 57.3 for further details.
 
 
-+-------------+---------+-------------+--------------+
++----------------------------------------------------+
 |acl_smtp_etrn|Use: main|Type: string*|Default: unset|
 |acl_smtp_etrn|Use: main|Type: string*|Default: unset|
-+-------------+---------+-------------+--------------+
++----------------------------------------------------+
 
 This option defines the ACL that is run when an SMTP ETRN command is received.
 
 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|
 |acl_smtp_expn|Use: main|Type: string*|Default: unset|
-+-------------+---------+-------------+--------------+
++----------------------------------------------------+
 
 This option defines the ACL that is run when an SMTP EXPN command is received.
 
 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|
 |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
 
 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|
 |acl_smtp_mail|Use: main|Type: string*|Default: unset|
-+-------------+---------+-------------+--------------+
++----------------------------------------------------+
 
 This option defines the ACL that is run when an SMTP MAIL command is received.
 
 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|
 |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
 
 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.
 
 authentication.
 
-+-------------+---------+-------------+--------------+
++----------------------------------------------------+
 |acl_smtp_mime|Use: main|Type: string*|Default: unset|
 |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
 
 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|
 |acl_smtp_predata|Use: main|Type: string*|Default: unset|
-+----------------+---------+-------------+--------------+
++-------------------------------------------------------+
 
 This option defines the ACL that is run when an SMTP DATA command is received,
 
 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|
 |acl_smtp_quit|Use: main|Type: string*|Default: unset|
-+-------------+---------+-------------+--------------+
++----------------------------------------------------+
 
 This option defines the ACL that is run when an SMTP QUIT command is received.
 
 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|
 |acl_smtp_rcpt|Use: main|Type: string*|Default: unset|
-+-------------+---------+-------------+--------------+
++----------------------------------------------------+
 
 This option defines the ACL that is run when an SMTP RCPT command is received.
 
 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|
 |acl_smtp_starttls|Use: main|Type: string*|Default: unset|
-+-----------------+---------+-------------+--------------+
++--------------------------------------------------------+
 
 This option defines the ACL that is run when an SMTP STARTTLS command is
 
 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|
 |acl_smtp_vrfy|Use: main|Type: string*|Default: unset|
-+-------------+---------+-------------+--------------+
++----------------------------------------------------+
 
 This option defines the ACL that is run when an SMTP VRFY command is received.
 
 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|
 |add_environment|Use: main|Type: string list|Default: empty|
-+---------------+---------+-----------------+--------------+
++----------------------------------------------------------+
 
 This option allows to set individual environment variables that the currently
 
 This option allows to set individual environment variables that the currently
-linked libraries and programs in child processes use. The default list is
-empty,
+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|
 |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
 
 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
@@ -12478,9 +13237,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.
 
 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|
 |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
 
 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
@@ -12494,21 +13253,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.
 
 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|
 |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
 
 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.
 
 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|
 |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
 
 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
@@ -12530,9 +13289,9 @@ dns_check_names_pattern =
 
 That is, set the option to an empty string so that no check is done.
 
 
 That is, set the option to an empty string so that no check is done.
 
-+--------------------+---------+----------------+----------+
++----------------------------------------------------------+
 |auth_advertise_hosts|Use: main|Type: host list*|Default: *|
 |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.
 
 If any server authentication mechanisms are configured, Exim advertises them in
 response to an EHLO command only if the calling host matches this list.
@@ -12559,9 +13318,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.
 
 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|
 |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
 
 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
@@ -12573,9 +13332,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.
 
 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|
 |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:
 
 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:
@@ -12583,36 +13342,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
 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|
 |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.
 
 
 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|
 |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
 
 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|
 |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.
 
 
 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|
 |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
 
 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
@@ -12622,17 +13381,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.
 
 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|
 |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.
 
 
 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|
 |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
 
 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
@@ -12647,9 +13421,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.
 
 size). The idea is to save bandwidth on those undeliverable 15-megabyte
 messages.
 
-+----------------------------+---------+------------+--------------+
++------------------------------------------------------------------+
 |bounce_sender_authentication|Use: main|Type: string|Default: unset|
 |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
 
 This option provides an authenticated sender address that is sent with any
 bounce messages generated by Exim that are sent over an authenticated SMTP
@@ -12664,64 +13438,64 @@ MAIL FROM:<> AUTH=mailer-daemon@my.domain.example
 The value of bounce_sender_authentication must always be a complete email
 address.
 
 The value of bounce_sender_authentication must always be a complete email
 address.
 
-+------------------------------+---------+----------+-----------+
++---------------------------------------------------------------+
 |callout_domain_negative_expire|Use: main|Type: time|Default: 3h|
 |callout_domain_negative_expire|Use: main|Type: time|Default: 3h|
-+------------------------------+---------+----------+-----------+
++---------------------------------------------------------------+
 
 This option specifies the expiry time for negative callout cache data for a
 
 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|
 |callout_domain_positive_expire|Use: main|Type: time|Default: 7d|
-+------------------------------+---------+----------+-----------+
++---------------------------------------------------------------+
 
 This option specifies the expiry time for positive callout cache data for a
 
 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|
 |callout_negative_expire|Use: main|Type: time|Default: 2h|
-+-----------------------+---------+----------+-----------+
++--------------------------------------------------------+
 
 This option specifies the expiry time for negative callout cache data for an
 
 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|
 |callout_positive_expire|Use: main|Type: time|Default: 24h|
-+-----------------------+---------+----------+------------+
++---------------------------------------------------------+
 
 This option specifies the expiry time for positive callout cache data for an
 
 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|
 |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
 
 
 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.
 
 
 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.
 
 
 See check_spool_space below.
 
-+--------------------+---------+-------------+-------------+
++----------------------------------------------------------+
 |check_rfc2047_length|Use: main|Type: boolean|Default: true|
 |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
 
 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
@@ -12731,28 +13505,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.
 
 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.
 
 
 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.
 
 
 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 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
 check_spool_inodes = 100
 
 The spool partition is the one that contains the directory defined by
@@ -12770,23 +13544,50 @@ 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
 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.
 
 
 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.
+
++-------------------------------------------------------------------------+
+|commandline_checks_require_admin|Use: main|Type: boolean|Default: "false"|
++-------------------------------------------------------------------------+
+
+This option restricts various basic checking features to require an
+administrative user. This affects most of the -b* options, such as -be.
+
++----------------------------------------------------+
+|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"|
 |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.
 
 
 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|
 |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
 
 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
@@ -12794,21 +13595,21 @@ 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.
 
 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|
 |daemon_startup_sleep|Use: main|Type: time|Default: 30s|
-+--------------------+---------+----------+------------+
++------------------------------------------------------+
 
 See daemon_startup_retries.
 
 
 See daemon_startup_retries.
 
-+-------------+---------+---------------+------------+
++----------------------------------------------------+
 |delay_warning|Use: main|Type: time list|Default: 24h|
 |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
 after which to send warning messages. If the value of the option is an empty
 string or a zero time, no warnings are sent. Up to 10 times may be given. If a
 
 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
 after which to send warning messages. If the value of the option is an empty
 string or a zero time, no warnings are sent. Up to 10 times may be given. If a
-message has been on the queue for longer than the last time, the last interval
+message has been in the queue for longer than the last time, the last interval
 between the times is used to compute subsequent warning times. For example,
 with
 
 between the times is used to compute subsequent warning times. For example,
 with
 
@@ -12830,9 +13631,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.
 
 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|
 |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
 
 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
@@ -12852,28 +13653,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.
 
 Precedence: header, or have "auto-generated" or "auto-replied" in an 
 Auto-Submitted: header.
 
-+----------------------+---------+-------------+--------------+
++-------------------------------------------------------------+
 |deliver_drop_privilege|Use: main|Type: boolean|Default: false|
 |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
 
 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|
 |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.
 
 
 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|
 |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
 
 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
@@ -12882,9 +13683,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.
 
 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|
 |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
 
 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
@@ -12898,9 +13699,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.
 
 crashes and power outages may cause data to be lost or scrambled. Here be
 Dragons. Beware.
 
-+------------+---------+-------------+--------------+
++---------------------------------------------------+
 |disable_ipv6|Use: main|Type: boolean|Default: false|
 |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
 
 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
@@ -12908,9 +13709,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.
 
 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 section 57.3.
+
++--------------------------------------------------------------------+
 |dns_again_means_nonexist|Use: main|Type: domain list*|Default: unset|
 |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
 
 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
@@ -12930,9 +13739,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.
 
 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|
 |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
 
 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
@@ -12951,24 +13760,35 @@ 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.
 
 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|
 |dns_csa_search_limit|Use: main|Type: integer|Default: 5|
-+--------------------+---------+-------------+----------+
++-------------------------------------------------------+
 
 This option controls the depth of parental searching for CSA SRV records in the
 
 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|
 |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
 
 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_cname_loops|Use: main|Type: integer|Default: 1|
++--------------------------------------------------+
+
+This option controls the following of CNAME chains, needed if the resolver does
+not do it internally. As of 2018 most should, and the default can be left. If
+you have an ancient one, a value of 10 is likely needed.
+
+The default value of one CNAME-follow is needed thanks to the observed return
+for an MX request, given no MX presence but a CNAME to an A, of the CNAME.
 
 
-+-------------+---------+-------------+-----------+
++-------------------------------------------------+
 |dns_dnssec_ok|Use: main|Type: integer|Default: -1|
 |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
 
 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
@@ -12976,9 +13796,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.
 
 
 If the resolver library does not support DNSSEC then this option has no effect.
 
-+---------------+---------+------------------+--------------+
++-----------------------------------------------------------+
 |dns_ipv4_lookup|Use: main|Type: domain list*|Default: unset|
 |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
 
 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
@@ -12989,9 +13809,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.
 
 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|
 |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
 
 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
@@ -13000,17 +13820,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
 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|
 |dns_retry|Use: main|Type: integer|Default: 0|
-+---------+---------+-------------+----------+
++--------------------------------------------+
 
 See dns_retrans above.
 
 
 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|
 |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
 
 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
@@ -13018,17 +13864,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.
 
 
 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|
 |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
 
 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|
 |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" -
 
 This option can be used to vary the contents of From: header lines in bounces
 and other automatically generated messages ("Delivery Status Notifications" -
@@ -13039,21 +13899,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.
 
 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|
 |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. 
 
 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.
 
 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|
 |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
 
 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
@@ -13077,9 +13937,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.
 
 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|
 |errors_reply_to|Use: main|Type: string|Default: unset|
-+---------------+---------+------------+--------------+
++-----------------------------------------------------+
 
 By default, Exim's bounce and delivery warning messages contain the header line
 
 
 By default, Exim's bounce and delivery warning messages contain the header line
 
@@ -13100,19 +13960,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.
 
 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|
 |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
 
 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|
 |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
 
 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
@@ -13123,9 +13990,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.)
 
 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|
 |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
 
 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
@@ -13134,20 +14001,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,
 
 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.
 
 for a discussion of security issues.
 
-+----------------------+---------+-----------------+--------------+
++-----------------------------------------------------------------+
 |extra_local_interfaces|Use: main|Type: string list|Default: unset|
 |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.
 
 
 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
 
 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
@@ -13159,9 +14026,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.
 
 argument headers. If it is set false, Exim adds rather than removes argument
 addresses.
 
-+----------------+---------+-------------+----------+
++---------------------------------------------------+
 |finduser_retries|Use: main|Type: integer|Default: 0|
 |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
 
 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
@@ -13174,9 +14041,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.
 
 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|
 |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
 
 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
@@ -13192,9 +14059,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.
 
 configure freezing in a filter or ACL, you must arrange for any logging that
 you require.
 
-+----------+---------+-------------+--------------+
++-------------------------------------------------+
 |gecos_name|Use: main|Type: string*|Default: unset|
 |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
 
 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
@@ -13215,30 +14082,33 @@ terminates at the first comma, the following can be used:
 gecos_pattern = ([^,]*)
 gecos_name = $1
 
 gecos_pattern = ([^,]*)
 gecos_name = $1
 
-+-------------+---------+------------+--------------+
++---------------------------------------------------+
 |gecos_pattern|Use: main|Type: string|Default: unset|
 |gecos_pattern|Use: main|Type: string|Default: unset|
-+-------------+---------+------------+--------------+
++---------------------------------------------------+
 
 See gecos_name above.
 
 
 See gecos_name above.
 
-+------------------+---------+-------------+--------------+
++---------------------------------------------------------+
 |gnutls_compat_mode|Use: main|Type: boolean|Default: unset|
 |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
 implementations of TLS.
 
 
 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.
 
-option gnutls_allow_auto_pkcs11 main boolean unset This option will let GnuTLS
-(2.12.0 or later) autoload PKCS11 modules with the p11-kit configuration files
-in /etc/pkcs11/modules/.
++---------------------------------------------------------------+
+|gnutls_allow_auto_pkcs11|Use: main|Type: boolean|Default: unset|
++---------------------------------------------------------------+
 
 
-See http://www.gnutls.org/manual/gnutls.html#Smart-cards-and-HSMs for
+This option will let GnuTLS (2.12.0 or later) autoload PKCS11 modules with the
+p11-kit configuration files in /etc/pkcs11/modules/.
+
+See https://www.gnutls.org/manual/gnutls.html#Smart-cards-and-HSMs for
 documentation.
 
 documentation.
 
-+---------------+---------+------------+------------------+
++---------------------------------------------------------+
 |headers_charset|Use: main|Type: string|Default: see below|
 |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
 
 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
@@ -13246,26 +14116,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.
 
 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|
 |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.
 
 
 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|
 |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".
 
 
 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|
 |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
 
 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
@@ -13274,9 +14144,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.
 
 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|
 |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,
 
 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,
@@ -13286,18 +14156,18 @@ helo_allow_chars = _
 
 Note that the value is one string, not a list.
 
 
 Note that the value is one string, not a list.
 
-+-------------------+---------+------------------+----------------+
++-----------------------------------------------------------------+
 |helo_lookup_domains|Use: main|Type: domain list*|Default: "@:@[]"|
 |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.
 
 
 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|
 |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
 
 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
@@ -13318,16 +14188,18 @@ command either:
   * matches the host name that Exim obtains by doing a reverse lookup of the
     calling host address, or
 
   * 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.
 
 
 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|
 |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
 
 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
@@ -13336,11 +14208,11 @@ 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.
 
 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|
 |hold_domains|Use: main|Type: domain list*|Default: unset|
-+------------+---------+------------------+--------------+
++--------------------------------------------------------+
 
 
-This option allows mail for particular domains to be held on the queue
+This option allows mail for particular domains to be held in the queue
 manually. The option is overridden if a message delivery is forced with the -M,
 -qf, -Rf or -Sf options, and also while testing or verifying addresses using 
 -bt or -bv. Otherwise, if a domain matches an item in hold_domains, no routing
 manually. The option is overridden if a message delivery is forced with the -M,
 -qf, -Rf or -Sf options, and also while testing or verifying addresses using 
 -bt or -bv. Otherwise, if a domain matches an item in hold_domains, no routing
@@ -13358,9 +14230,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.
 
 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|
 |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 
 
 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 
@@ -13382,9 +14254,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.
 
 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"|
 |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
 
 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
@@ -13397,9 +14269,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.
 
 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|
 |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
 
 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
@@ -13409,12 +14281,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
 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|
 |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
 
 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
@@ -13428,9 +14300,16 @@ hosts_connection_nolog = :
 
 If the smtp_connection log selector is not set, this option has no effect.
 
 
 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|
 |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
 
 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
@@ -13445,17 +14324,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.
 
 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|
 |ibase_servers|Use: main|Type: string list|Default: unset|
-+-------------+---------+-----------------+--------------+
++--------------------------------------------------------+
 
 This option provides a list of InterBase servers and associated connection
 
 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.
 
 option is available only if Exim has been built with InterBase support.
 
-+--------------------------+---------+----------+------------+
++------------------------------------------------------------+
 |ignore_bounce_errors_after|Use: main|Type: time|Default: 10w|
 |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
 
 This option affects the processing of bounce messages that cannot be delivered,
 that is, those that suffer a permanent delivery failure. (Bounce messages that
@@ -13463,7 +14342,7 @@ suffer temporary delivery failures are of course retried in the usual way.)
 
 After a permanent delivery failure, bounce messages are frozen, because there
 is no sender to whom they can be returned. When a frozen bounce message has
 
 After a permanent delivery failure, bounce messages are frozen, because there
 is no sender to whom they can be returned. When a frozen bounce message has
-been on the queue for more than the given time, it is unfrozen at the next
+been in the queue for more than the given time, it is unfrozen at the next
 queue run, and a further delivery is attempted. If delivery fails again, the
 bounce message is discarded. This makes it possible to keep failed bounce
 messages around for a shorter time than the normal maximum retry time for
 queue run, and a further delivery is attempted. If delivery fails again, the
 bounce message is discarded. This makes it possible to keep failed bounce
 messages around for a shorter time than the normal maximum retry time for
@@ -13478,9 +14357,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.
 
 dealing with other kinds of frozen message, see auto_thaw and 
 timeout_frozen_after.
 
-+---------------------+---------+----------------+--------------+
++---------------------------------------------------------------+
 |ignore_fromline_hosts|Use: main|Type: host list*|Default: unset|
 |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
 
 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
@@ -13490,15 +14369,15 @@ 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.
 
 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|
 |ignore_fromline_local|Use: main|Type: boolean|Default: false|
-+---------------------+---------+-------------+--------------+
++------------------------------------------------------------+
 
 See ignore_fromline_hosts above.
 
 
 See ignore_fromline_hosts above.
 
-+----------------+---------+-----------------+--------------+
++-----------------------------------------------------------+
 |keep_environment|Use: main|Type: string list|Default: unset|
 |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
 
 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
@@ -13516,91 +14395,96 @@ 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$.
 
 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 startupif you do not mention 
-keep_environment or add_environment in your runtime configuration file.
+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|
 |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.
 
 
 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|
 |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.
 
 
 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|
 |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.
 
 
 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|
 |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.
 
 
 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|
 |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.
 
 
 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|
 |ldap_cipher_suite|Use: main|Type: string|Default: unset|
-+-----------------+---------+------------+--------------+
++-------------------------------------------------------+
 
 This controls the TLS cipher-suite negotiation during TLS negotiation with the
 
 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).
 
 with OpenSSL (as used by LDAP client libraries).
 
-+--------------------+---------+-----------------+--------------+
++---------------------------------------------------------------+
 |ldap_default_servers|Use: main|Type: string list|Default: unset|
 |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
 
 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.
 
 queries. This option is available only when Exim has been built with LDAP
 support.
 
-+-----------------+---------+------------+---------------+
++--------------------------------------------------------+
 |ldap_require_cert|Use: main|Type: string|Default: unset.|
 |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.
 
 
 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|
 |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
 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 
 
 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
 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.
+ldap_require_cert. This option is ignored for "ldapi" connections.
 
 
-+------------+---------+-------------+--------------+
++---------------------------------------------------+
 |ldap_version|Use: main|Type: integer|Default: unset|
 |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.
 
 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.
@@ -13608,9 +14492,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.
 
 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|
 |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
 
 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
@@ -13636,12 +14520,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
 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.
 
 about Sender: processing.
 
-+-----------------+---------+------------+--------------+
++-------------------------------------------------------+
 |local_from_prefix|Use: main|Type: string|Default: unset|
 |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
 
 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
@@ -13660,15 +14544,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.
 
 the actual sender address that is constructed from the login name and qualify
 domain.
 
-+-----------------+---------+------------+--------------+
++-------------------------------------------------------+
 |local_from_suffix|Use: main|Type: string|Default: unset|
 |local_from_suffix|Use: main|Type: string|Default: unset|
-+-----------------+---------+------------+--------------+
++-------------------------------------------------------+
 
 See local_from_prefix above.
 
 
 See local_from_prefix above.
 
-+----------------+---------+-----------------+------------------+
++---------------------------------------------------------------+
 |local_interfaces|Use: main|Type: string list|Default: see below|
 |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
 
 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
@@ -13682,30 +14566,30 @@ when Exim is built without IPv6 support; otherwise it is
 
 local_interfaces = <; ::0 ; 0.0.0.0
 
 
 local_interfaces = <; ::0 ; 0.0.0.0
 
-+------------------+---------+----------+-----------+
++---------------------------------------------------+
 |local_scan_timeout|Use: main|Type: time|Default: 5m|
 |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.
 
 "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|
 |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
 
 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.
 
 about Sender: processing.
 
-+----------------+---------+-------------+--------------+
++-------------------------------------------------------+
 |localhost_number|Use: main|Type: string*|Default: unset|
 |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
 
 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
@@ -13718,26 +14602,26 @@ $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.
 
 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|
 |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
 
 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
-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.
-
-+------------+---------+------------+--------------+
+name. If no specific path is set for the log files at compile or runtime, or if
+the option is unset at runtime (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|
 |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
 
 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
@@ -13746,11 +14630,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
 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|
 |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
 
 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
@@ -13762,9 +14646,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.
 
 $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|
 |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
 
 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
@@ -13775,33 +14659,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.
 
 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|
 |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.
 
 
 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|
 |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.
 
 
 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|
 |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.
 
 
 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|
 |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
 
 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
@@ -13811,9 +14695,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.
 
 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|
 |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
 
 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
@@ -13827,9 +14711,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.
 
 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|
 |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
 
 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
@@ -13838,9 +14722,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.
 
 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|
 |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
 
 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
@@ -13865,7 +14749,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
 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.
 
 default Exim message size of 50M and a default ClamAV StreamMaxLength of 10M,
 some problems may result.
 
@@ -13873,9 +14757,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.
 
 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|
 |move_frozen_messages|Use: main|Type: boolean|Default: false|
-+--------------------+---------+-------------+--------------+
++-----------------------------------------------------------+
 
 This option, which is available only if Exim has been built with the setting
 
 
 This option, which is available only if Exim has been built with the setting
 
@@ -13887,25 +14771,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.
 
 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|
 |mua_wrapper|Use: main|Type: boolean|Default: false|
-+-----------+---------+-------------+--------------+
++--------------------------------------------------+
 
 Setting this option true causes Exim to run in a very restrictive mode in which
 
 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.
 
 description of this facility.
 
-+-------------+---------+-----------------+--------------+
++--------------------------------------------------------+
 |mysql_servers|Use: main|Type: string list|Default: unset|
 |mysql_servers|Use: main|Type: string list|Default: unset|
-+-------------+---------+-----------------+--------------+
++--------------------------------------------------------+
 
 This option provides a list of MySQL servers and associated connection data, to
 
 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.
 
 available only if Exim has been built with MySQL support.
 
-+-----------+---------+------------------+--------------+
++-------------------------------------------------------+
 |never_users|Use: main|Type: string list*|Default: unset|
 |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
 
 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
@@ -13929,9 +14813,10 @@ 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.
 
 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:   | Type: string |    Default: +no_sslv2 +single_dh_use|
+|               |  main  |     list     |                           +no_ticket|
++-----------------------------------------------------------------------------+
 
 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
 
 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
@@ -13951,17 +14836,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.
 
 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.
 
 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
 
 
 # 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"
 Possible options may include:
 
   * "all"
@@ -14025,17 +14915,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.
 
 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|
 |oracle_servers|Use: main|Type: string list|Default: unset|
-+--------------+---------+-----------------+--------------+
++---------------------------------------------------------+
 
 This option provides a list of Oracle servers and associated connection data,
 
 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.
 
 available only if Exim has been built with Oracle support.
 
-+--------------------+---------+------------------+--------------+
++----------------------------------------------------------------+
 |percent_hack_domains|Use: main|Type: domain list*|Default: unset|
 |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 @.
 
 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 @.
@@ -14052,31 +14942,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.
 
 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|
 |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.
 
 
 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|
 |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.
 
 
 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|
 |pgsql_servers|Use: main|Type: string list|Default: unset|
-+-------------+---------+-----------------+--------------+
++--------------------------------------------------------+
 
 This option provides a list of PostgreSQL servers and associated connection
 
 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.
 
 option is available only if Exim has been built with PostgreSQL support.
 
-+-------------+---------+-------------+----------------------------+
++------------------------------------------------------------------+
 |pid_file_path|Use: main|Type: string*|Default: set at compile time|
 |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
 
 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
@@ -14089,31 +14985,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.
 
 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: *|
 |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
 
 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).
 
 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|
 |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
 
 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|
 |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
 
 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
@@ -14121,9 +15017,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!
 
 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|
 |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
 
 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
@@ -14138,9 +15034,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.
 
 $primary_hostname contains the host name, whether set explicitly by this
 option, or defaulted.
 
-+-----------------+---------+-------------+--------------+
++--------------------------------------------------------+
 |print_topbitchars|Use: main|Type: boolean|Default: false|
 |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,
 
 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,
@@ -14151,13 +15047,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
 
 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.
 
 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|
 |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
 
 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
@@ -14166,16 +15062,17 @@ 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.
 
 useful in environments where two different Exims are running, using different
 spool directories.
 
-+-------------------+---------+-------------+-------------+
++---------------------------------------------------------+
 |prod_requires_admin|Use: main|Type: boolean|Default: true|
 |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
 
 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.
+unless prod_requires_admin is set false. See also queue_list_requires_admin and
+commandline_checks_require_admin.
 
 
-+--------------+---------+------------+------------------+
++--------------------------------------------------------+
 |qualify_domain|Use: main|Type: string|Default: see below|
 |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
 
 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
@@ -14191,36 +15088,36 @@ 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.
 
 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|
 |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.
 
 
 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|
 |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.
 
 
 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|
 |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
 
 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.
+false. See also prod_requires_admin and commandline_checks_require_admin.
 
 
-+----------+---------+-------------+--------------+
++-------------------------------------------------+
 |queue_only|Use: main|Type: boolean|Default: false|
 |queue_only|Use: main|Type: boolean|Default: false|
-+----------+---------+-------------+--------------+
++-------------------------------------------------+
 
 If queue_only is set, a delivery process is not automatically started whenever
 
 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
+a message is received. Instead, the message waits in the queue for the next
 queue run. Even if queue_only is false, incoming messages may not get delivered
 immediately when certain conditions (such as heavy load) occur.
 
 queue run. Even if queue_only is false, incoming messages may not get delivered
 immediately when certain conditions (such as heavy load) occur.
 
@@ -14228,9 +15125,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.
 
 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|
 |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
 
 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
@@ -14244,9 +15141,9 @@ queue_only_file = smtp/some/file
 causes Exim to behave as if queue_smtp_domains were set to "*" whenever /some/
 file exists.
 
 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|
 |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
 
 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
@@ -14259,9 +15156,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.
 
 determine the load average. See also deliver_queue_load_max and 
 smtp_load_reserve.
 
-+---------------------+---------+-------------+-------------+
++-----------------------------------------------------------+
 |queue_only_load_latch|Use: main|Type: boolean|Default: true|
 |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
 
 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
@@ -14274,18 +15171,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.
 
 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|
 |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.
 
 
 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|
 |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
 
 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
@@ -14299,9 +15196,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 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
 
 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
@@ -14316,14 +15213,17 @@ 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.
 
 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|
 |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
 any SMTP deliveries are required for domains that match queue_smtp_domains,
 
 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
 any SMTP deliveries are required for domains that match queue_smtp_domains,
-they are not immediately delivered, but instead the message waits on the queue
+they are not immediately delivered, but instead the message waits in the queue
 for the next queue run. Since routing of the message has taken place, Exim
 knows to which remote hosts it must be delivered, and so when the queue run
 happens, multiple messages for the same host are delivered over a single SMTP
 for the next queue run. Since routing of the message has taken place, Exim
 knows to which remote hosts it must be delivered, and so when the queue run
 happens, multiple messages for the same host are delivered over a single SMTP
@@ -14331,19 +15231,19 @@ 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.
 
 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|
 |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
 
 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
-the value is zero, it will wait for ever. This setting is overridden by the -or
+the value is zero, it will wait forever. This setting is overridden by the -or
 command line option. The timeout for incoming SMTP messages is controlled by 
 smtp_receive_timeout.
 
 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|
 |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
 
 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
@@ -14385,18 +15285,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.
 
 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|
 |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.
 
 
 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|
 |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
 
 This option lists those hosts from which Exim is prepared to accept unqualified
 recipient addresses in message envelopes. The addresses are made fully
@@ -14406,9 +15306,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.
 
 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|
 |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
 
 If this option is set greater than zero, it specifies the maximum number of
 original recipients for any message. Additional recipients that are generated
@@ -14420,9 +15320,9 @@ done.
 Note: The RFCs specify that an SMTP server should accept at least 100 RCPT
 commands in a single message.
 
 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|
 |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
 
 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
@@ -14431,9 +15331,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.
 
 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|
 |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
 
 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
@@ -14468,9 +15368,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.
 
 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|
 |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,
 
 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,
@@ -14480,27 +15380,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.
 
 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|
 |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.
 
 
 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|
 |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.
 
 
 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|
 |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:
 
 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:
@@ -14511,29 +15411,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.
 
 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|
 |return_size_limit|Use: main|Type: integer|Default: 100K|
-+-----------------+---------+-------------+-------------+
++-------------------------------------------------------+
 
 This option is an obsolete synonym for bounce_return_size_limit.
 
 
 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
 
 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.
 
 
 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|
 |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 
 
 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 
@@ -14543,17 +15444,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.
 
 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|
 |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,
 
 
 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|
 |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
 
 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
@@ -14565,9 +15474,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.
 
 call properly. The keepalive mechanism takes several hours to detect
 unreachable hosts.
 
-+---------------+---------+-------------+-----------+
++---------------------------------------------------+
 |smtp_accept_max|Use: main|Type: integer|Default: 20|
 |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
 
 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
@@ -14581,9 +15490,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.
 
 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|
 |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
 
 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
@@ -14600,17 +15509,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.
 
 counted. Otherwise, all commands other than MAIL, RCPT, DATA, and QUIT are
 counted.
 
-+-----------------------------+---------+----------------+----------+
++-------------------------------------------------------------------+
 |smtp_accept_max_nonmail_hosts|Use: main|Type: host list*|Default: *|
 |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.
 
 
 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|
 |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
 
 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
@@ -14619,9 +15528,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).
 
 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|
 |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
 
 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
@@ -14639,13 +15548,13 @@ 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.
 
 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|
 |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
 
 If the number of simultaneous incoming SMTP connections being handled via the
 listening daemon exceeds this value, messages received by SMTP are just placed
-on the queue; no delivery processes are started automatically. The count is
+in the queue; no delivery processes are started automatically. The count is
 fixed at the start of an SMTP connection. It cannot be updated in the
 subprocess that receives messages, and so the queueing or not queueing applies
 to all messages received in the same connection.
 fixed at the start of an SMTP connection. It cannot be updated in the
 subprocess that receives messages, and so the queueing or not queueing applies
 to all messages received in the same connection.
@@ -14655,23 +15564,23 @@ 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.
 
 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|
 |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
 the use of -bs or -bS. If the value of the option is greater than zero, and the
 number of messages received in a single SMTP session exceeds this number,
 
 This option limits the number of delivery processes that Exim starts
 automatically when receiving messages via SMTP, whether via the daemon or by
 the use of -bs or -bS. If the value of the option is greater than zero, and the
 number of messages received in a single SMTP session exceeds this number,
-subsequent messages are placed on the queue, but no delivery processes are
+subsequent messages are placed in the queue, but no delivery processes are
 started. This helps to limit the number of Exim processes when a server
 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).
 
 started. This helps to limit the number of Exim processes when a server
 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|
 |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
 
 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
@@ -14687,9 +15596,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.
 
 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|
 |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
 
 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
@@ -14714,9 +15623,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.
 
 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|
 |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:
 
 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:
@@ -14730,9 +15639,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).
 
 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|
 |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
 
 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
@@ -14740,9 +15649,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.
 
 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|
 |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
 
 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
@@ -14753,9 +15662,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.
 
 (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|
 |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
 
 The SMTP protocol specification requires the client to wait for a response from
 the server at certain points in the dialogue. Without PIPELINING these
@@ -14773,15 +15682,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
 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|
 |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
 
 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:
 
 expanded. The expansion variable $domain is set to the argument of the ETRN
 command, and no syntax checking is done on it. For example:
 
@@ -14795,17 +15704,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, so it is not possible for it to change the uid before running the
 command.
 
-+-------------------+---------+-------------+-------------+
++---------------------------------------------------------+
 |smtp_etrn_serialize|Use: main|Type: boolean|Default: true|
 |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
 
 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|
 |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 
 
 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 
@@ -14814,9 +15723,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.
 
 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|
 |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:
 
 Exim rejects SMTP commands that contain syntax or protocol errors. In
 particular, a syntactically invalid email address, as in this command:
@@ -14835,18 +15744,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.
 
 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|
 |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.
 
 
 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|
 |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
 
 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
@@ -14854,7 +15763,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
 
 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
 details of the newer facility.
 
 When a host matches smtp_ratelimit_hosts, the values of smtp_ratelimit_mail and
@@ -14883,21 +15792,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.
 
 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|
 |smtp_ratelimit_mail|Use: main|Type: string|Default: unset|
-+-------------------+---------+------------+--------------+
++---------------------------------------------------------+
 
 See smtp_ratelimit_hosts above.
 
 
 See smtp_ratelimit_hosts above.
 
-+-------------------+---------+------------+--------------+
++---------------------------------------------------------+
 |smtp_ratelimit_rcpt|Use: main|Type: string|Default: unset|
 |smtp_ratelimit_rcpt|Use: main|Type: string|Default: unset|
-+-------------------+---------+------------+--------------+
++---------------------------------------------------------+
 
 See smtp_ratelimit_hosts above.
 
 
 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
 
 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
@@ -14911,22 +15820,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.
 
 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.
 
 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|
 |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.
 
 
 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|
 |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
 
 In the default state, Exim uses bland messages such as "Administrative
 prohibition" when it rejects SMTP commands for policy reasons. Many sysadmins
@@ -14939,21 +15852,33 @@ prohibition", it might give:
 550-Rejected after DATA: '>' missing at end of address:
 550 failing address in "From" header is: <user@dom.ain
 
 550-Rejected after DATA: '>' missing at end of address:
 550 failing address in "From" header is: <user@dom.ain
 
-+-------------+---------+------------+------------------+
-|spamd_address|Use: main|Type: string|Default: see below|
-+-------------+---------+------------+------------------+
++--------------------------------------------------------------+
+|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: 127.0.0.1 783|
++-----------------------------------------------------------+
 
 This option is available when Exim is compiled with the content-scanning
 
 This option is available when Exim is compiled with the content-scanning
-extension. It specifies how Exim connects to SpamAssassin's spamd daemon. The
-default value is
+extension. It specifies how Exim connects to SpamAssassin's spamd daemon. See
+section 44.2 for more details.
 
 
-127.0.0.1 783
++--------------------------------------------------------------------+
+|spf_guess|Use: main|Type: string|Default: v=spf1 a/24 mx/24 ptr ?all|
++--------------------------------------------------------------------+
 
 
-See section 43.2 for more details.
+This option is available when Exim is compiled with SPF support. See section
+57.4 for more details.
 
 
-+---------------------+---------+-------------+--------------+
++------------------------------------------------------------+
 |split_spool_directory|Use: main|Type: boolean|Default: false|
 |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
 
 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
@@ -14975,17 +15900,17 @@ automatically deleted.
 
 When split_spool_directory is set, the behaviour of queue runner processes
 changes. Instead of creating a list of all messages in the queue, and then
 
 When split_spool_directory is set, the behaviour of queue runner processes
 changes. Instead of creating a list of all messages in the queue, and then
-trying to deliver each one in turn, it constructs a list of those in one
+trying to deliver each one, in turn, it constructs a list of those in one
 sub-directory and tries to deliver them, before moving on to the next
 sub-directory. The sub-directories are processed in a random order. This
 spreads out the scanning of the input directories, and uses less memory. It is
 sub-directory and tries to deliver them, before moving on to the next
 sub-directory. The sub-directories are processed in a random order. This
 spreads out the scanning of the input directories, and uses less memory. It is
-particularly beneficial when there are lots of messages on the queue. However,
+particularly beneficial when there are lots of messages in 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.
 
 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|
 |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
 
 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
@@ -15002,25 +15927,49 @@ 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.
 
 By using this option to override the compiled-in path, it is possible to run
 tests of Exim without using the standard spool.
 
-+-------------------+---------+----------+-----------+
++-------------------------------------------------------+
+|spool_wireformat|Use: main|Type: boolean|Default: false|
++-------------------------------------------------------+
+
+If this option is set, Exim may for some messages use an alternative format for
+data-files in the spool which matches the wire format. Doing this permits more
+efficient message reception and transmission. Currently it is only done for
+messages received using the ESMTP CHUNKING option.
+
+The following variables will not have useful values:
+
+$max_received_linelength
+$body_linecount
+$body_zerocount
+
+Users of the local_scan() API (see 45), and any external programs which are
+passed a reference to a message data file (except via the "regex", "malware" or
+"spam") ACL conditions) will need to be aware of the different formats
+potentially available.
+
+Using any of the ACL conditions noted will negate the reception benefit (as a
+Unix-mbox-format file is constructed for them). The transmission benefit is
+maintained.
+
++----------------------------------------------------+
 |sqlite_lock_timeout|Use: main|Type: time|Default: 5s|
 |sqlite_lock_timeout|Use: main|Type: time|Default: 5s|
-+-------------------+---------+----------+-----------+
++----------------------------------------------------+
 
 This option controls the timeout that the sqlite lookup uses when trying to
 
 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|
 |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
 
 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.
 
 details of ACL variables.
 
-+---------------------------+---------+-------------+--------------+
++------------------------------------------------------------------+
 |strip_excess_angle_brackets|Use: main|Type: boolean|Default: false|
 |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 
 
 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 
@@ -15028,9 +15977,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.
 
 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|
 |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
 
 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
@@ -15038,9 +15987,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.
 
 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|
 |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
 
 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
@@ -15052,85 +16001,96 @@ 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.
 
 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|
 |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
 
 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"|
 |syslog_processname|Use: main|Type: string|Default: "exim"|
-+------------------+---------+------------+---------------+
++---------------------------------------------------------+
 
 This option sets the syslog "ident" name, used when Exim is logging to syslog.
 
 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.
 
 Exim's logging.
 
-+----------------+---------+-------------+-------------+
++------------------------------------------------------+
 |syslog_timestamp|Use: main|Type: boolean|Default: true|
 |syslog_timestamp|Use: main|Type: boolean|Default: true|
-+----------------+---------+-------------+-------------+
++------------------------------------------------------+
 
 If syslog_timestamp is set false, the timestamps on Exim's log lines are
 
 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.
 
 Exim's logging.
 
-+-------------+---------+-------------+--------------+
++----------------------------------------------------+
 |system_filter|Use: main|Type: string*|Default: unset|
 |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
 
 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|
 |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.
 
 
 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|
 |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.
 
 
 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|
 |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.
 
 
 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|
 |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.
 
 
 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|
 |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.
 
 
 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|
 |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
 
 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
@@ -15144,9 +16104,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.
 
 under which the filter is run is used when transporting them, unless a
 transport option overrides.
 
-+-----------+---------+-------------+-------------+
++-------------------------------------------------+
 |tcp_nodelay|Use: main|Type: boolean|Default: true|
 |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
 
 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
@@ -15157,12 +16117,12 @@ 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.
 
 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|
 |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
 
 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
+any kind that has been in the queue for longer than the given time is
 automatically cancelled at the next queue run. If the frozen message is a
 bounce message, it is just discarded; otherwise, a bounce is sent to the
 sender, in a similar manner to cancellation by the -Mg command line option. If
 automatically cancelled at the next queue run. If the frozen message is a
 bounce message, it is just discarded; otherwise, a bounce is sent to the
 sender, in a similar manner to cancellation by the -Mg command line option. If
@@ -15170,12 +16130,12 @@ you want to timeout frozen bounce messages earlier than other kinds of frozen
 message, see ignore_bounce_errors_after.
 
 Note: the default value of zero means no timeouts; with this setting, frozen
 message, see ignore_bounce_errors_after.
 
 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
+messages remain in the queue forever (except for any frozen bounce messages
 that are released by ignore_bounce_errors_after).
 
 that are released by ignore_bounce_errors_after).
 
-+--------+---------+------------+--------------+
++----------------------------------------------+
 |timezone|Use: main|Type: string|Default: unset|
 |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
 
 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
@@ -15191,46 +16151,67 @@ 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.
 
 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
 
 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|
-+---------------+---------+-------------+--------------+
++-----------------------------------------------------+
+|tls_certificate|Use: main|Type: string|Default: list*|
++-----------------------------------------------------+
 
 
-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
-further details.
+The value of this option is expanded, and must then be a list of absolute paths
+to files which contains the server's certificates. Commonly only one file is
+needed. The server's private key is also 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
 receiving incoming messages as a server. If you want to supply certificates for
 use when sending messages as a client, you must set the tls_certificate option
 in the relevant smtp transport.
 
 
 Note: The certificates defined by this option are used only when Exim is
 receiving incoming messages as a server. If you want to supply certificates for
 use when sending messages as a client, you must set the tls_certificate option
 in the relevant smtp transport.
 
+Note: If you use filenames based on IP addresses, change the list separator in
+the usual way (6.21) >to avoid confusion under IPv6.
+
+Note: Under versions of OpenSSL preceding 1.1.1, when a list of more than one
+file is used, the $tls_in_ourcert variable is unreliable.
+
+Note: OCSP stapling is not usable under OpenSSL when a list of more than one
+file is used.
+
 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
 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.
 
 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|
 |tls_crl|Use: main|Type: string*|Default: unset|
-+-------+---------+-------------+--------------+
++----------------------------------------------+
 
 This option specifies a certificate revocation list. The expanded value must be
 
 This option specifies a certificate revocation list. The expanded value must be
-the name of a file that contains a CRL in PEM format.
+the name of a file that contains CRLs in PEM format.
 
 
-See 41.10 for discussion of when this option might be re-expanded.
+Under OpenSSL the option can specify a directory with CRL files.
 
 
-+---------------+---------+-------------+-------------+
+Note: Under OpenSSL the option must, if given, supply a CRL for each signing
+element of the certificate chain (i.e. all but the leaf). For the file variant
+this can be multiple PEM blocks in the one file.
+
+See 42.10 for discussion of when this option might be re-expanded.
+
++-----------------------------------------------------+
 |tls_dh_max_bits|Use: main|Type: integer|Default: 2236|
 |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.
 
 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.
@@ -15250,44 +16231,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.
 
 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|
 |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.
 
 
 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
 
 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
 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
 
 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
 
 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,
 
 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,
@@ -15303,47 +16304,70 @@ 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.
 
 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 when used with OpenSSL. It has
+no effect when Exim is used with GnuTLS.
+
+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|
 |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.
 
 
 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).
+
+For GnuTLS 3.5.6 or later the expanded value of this option can be a list of
+files, to match a list given for the tls_certificate option. The ordering of
+the two lists must match.
+
++---------------------------------------------------------------+
 |tls_on_connect_ports|Use: main|Type: string list|Default: unset|
 |tls_on_connect_ports|Use: main|Type: string list|Default: unset|
-+--------------------+---------+-----------------+--------------+
++---------------------------------------------------------------+
 
 This option specifies a list of incoming SSMTP (aka SMTPS) ports that should
 
 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
+operate the 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.
 
 details, see section 13.4.
 
-+--------------+---------+-------------+--------------+
-|tls_privatekey|Use: main|Type: string*|Default: unset|
-+--------------+---------+-------------+--------------+
++----------------------------------------------------+
+|tls_privatekey|Use: main|Type: string|Default: list*|
++----------------------------------------------------+
 
 
-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.
+The value of this option is expanded, and must then be a list of absolute paths
+to files which contains the server's private keys. 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 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|
 |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.
 
 
 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|
 |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
 
 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
@@ -15351,41 +16375,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
 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|
 |tls_try_verify_hosts|Use: main|Type: host list*|Default: unset|
-+--------------------+---------+----------------+--------------+
++--------------------------------------------------------------+
 
 See tls_verify_hosts below.
 
 
 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 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 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 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
 
 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.
 
 
 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|
 |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 
 
 This option, along with tls_try_verify_hosts, controls the checking of
 certificates from clients. The expected certificates are defined by 
@@ -15412,9 +16443,9 @@ if encrypted, even without a verified certificate".
 Client hosts that match neither of these lists are not asked to present
 certificates.
 
 Client hosts that match neither of these lists are not asked to present
 certificates.
 
-+--------------+---------+------------------+--------------+
++----------------------------------------------------------+
 |trusted_groups|Use: main|Type: string list*|Default: unset|
 |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
 
 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
@@ -15423,9 +16454,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.
 
 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|
 |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
 
 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
@@ -15433,9 +16464,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.
 
 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|
 |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
 
 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
@@ -15444,15 +16475,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_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|
 |unknown_username|Use: main|Type: string|Default: unset|
-+----------------+---------+------------+--------------+
++------------------------------------------------------+
 
 See unknown_login.
 
 
 See unknown_login.
 
-+--------------------+---------+-------------------+--------------+
++-----------------------------------------------------------------+
 |untrusted_set_sender|Use: main|Type: address list*|Default: unset|
 |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
 
 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
@@ -15486,16 +16517,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
 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.
 
 
 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|
 |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
 
 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
@@ -15518,28 +16549,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 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"|
 |uucp_from_sender|Use: main|Type: string*|Default: "$1"|
-+----------------+---------+-------------+-------------+
++------------------------------------------------------+
 
 See uucp_from_pattern above.
 
 
 See uucp_from_pattern above.
 
-+-----------------+---------+------------+--------------+
++-------------------------------------------------------+
 |warn_message_file|Use: main|Type: string|Default: unset|
 |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
 
 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 
+been in the queue for a specified amount of time, as specified by delay_warning
+. Details of the file's contents are given in chapter 49. See also 
 bounce_message_file.
 
 bounce_message_file.
 
-+---------------+---------+-------------+-------------+
++-----------------------------------------------------+
 |write_rejectlog|Use: main|Type: boolean|Default: true|
 |write_rejectlog|Use: main|Type: boolean|Default: true|
-+---------------+---------+-------------+-------------+
++-----------------------------------------------------+
 
 If this option is set false, Exim no longer writes anything to the reject log.
 
 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.
 
 
 
 
 
 
@@ -15554,9 +16585,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.
 
 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|
 |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
 
 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
@@ -15599,18 +16630,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.
 
 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|
 |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.
 
 
 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|
 |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
 
 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
@@ -15631,9 +16662,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.
 
 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|
 |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.
 
 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.
@@ -15652,11 +16683,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
 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|
 |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
 
 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
@@ -15680,9 +16711,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.
 
 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|
 |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
 
 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
@@ -15717,9 +16748,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.
 
 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|
 |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
 
 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
@@ -15733,18 +16801,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.
 
 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|
 |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.
 
 
 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|
 |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
 
 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
@@ -15752,16 +16837,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.
 
 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|
 |driver|Use: routers|Type: string|Default: unset|
-+------+------------+------------+--------------+
++-----------------------------------------------+
 
 This option must always be set. It specifies which of the available routers is
 to be used.
 
 
 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|
 |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
 
 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
@@ -15800,12 +16894,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
 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|
 |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
 
 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
@@ -15813,38 +16907,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.
 
 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.
 
 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|
 |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.
 
 
 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|
 |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.
 
 
 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|
 |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.
 
 
 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|
 |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
 
 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.21), 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).
 
 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).
 
@@ -15854,9 +16948,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.
 
 randomized for each use. See the fallback_hosts option of the smtp transport
 for further details.
 
-+-----+------------+-------------+------------------+
++---------------------------------------------------+
 |group|Use: routers|Type: string*|Default: see below|
 |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
 
 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
@@ -15865,18 +16959,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.
 
 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|
 |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 6.21), 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,
 
 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,
@@ -15898,18 +16993,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.
 
 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|
 |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 6.21), 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
 
 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
@@ -15926,9 +17022,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.
 
 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|
 |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
 
 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
@@ -15966,9 +17066,9 @@ domain that is being routed.
 During its expansion, $host_address is set to the IP address that is being
 checked.
 
 During its expansion, $host_address is set to the IP address that is being
 checked.
 
-+----------+------------+-------------+--------------+
++----------------------------------------------------+
 |initgroups|Use: routers|Type: boolean|Default: false|
 |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
 
 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
@@ -15976,9 +17076,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.
 
 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|
 |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
 
 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
@@ -15989,7 +17089,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
 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
 
 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
@@ -16027,15 +17127,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.
 
 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|
 |local_part_prefix_optional|Use: routers|Type: boolean|Default: false|
-+--------------------------+------------+-------------+--------------+
++--------------------------------------------------------------------+
 
 See local_part_prefix above.
 
 
 See local_part_prefix above.
 
-+-----------------+--------------+-----------------+--------------+
++-----------------------------------------------------------------+
 |local_part_suffix|Use: routers**|Type: string list|Default: unset|
 |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 
 
 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 
@@ -16044,15 +17144,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.
 
 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|
 |local_part_suffix_optional|Use: routers|Type: boolean|Default: false|
-+--------------------------+------------+-------------+--------------+
++--------------------------------------------------------------------+
 
 See local_part_suffix above.
 
 
 See local_part_suffix above.
 
-+-----------+--------------+----------------------+--------------+
++----------------------------------------------------------------+
 |local_parts|Use: routers**|Type: local part list*|Default: unset|
 |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
 
 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
@@ -16073,9 +17173,9 @@ postmaster:
   local_parts = postmaster
   data = postmaster@real.domain.example
 
   local_parts = postmaster
   data = postmaster@real.domain.example
 
-+------------+------------+-------------+------------------+
++----------------------------------------------------------+
 |log_as_local|Use: routers|Type: boolean|Default: see below|
 |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
 
 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
@@ -16085,9 +17185,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.
 
 assigns an address to a transport. It has no effect on routers that redirect
 addresses.
 
-+----+------------+--------------+-------------+
++----------------------------------------------+
 |more|Use: routers|Type: boolean*|Default: true|
 |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
 
 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
@@ -16110,9 +17210,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.
 
 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|
 |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
 
 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
@@ -16124,9 +17224,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.
 
 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|
 |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
 
 Routers that recognize the generic self option (dnslookup, ipliteral, and 
 manualroute) are able to return "pass", forcing routing to continue, and
@@ -16138,9 +17238,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.
 
 does not apply when a router returns "decline" because it cannot handle an
 address.
 
-+---------------+------------+------------+--------------+
++--------------------------------------------------------+
 |redirect_router|Use: routers|Type: string|Default: unset|
 |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,
 
 Sometimes an administrator knows that it is pointless to reprocess addresses
 generated from alias or forward files with the same router again. For example,
@@ -16152,9 +17252,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.
 
 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|
 |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.
 
 This option provides a general mechanism for predicating the running of a
 router on the existence or non-existence of certain files or directories.
@@ -16162,8 +17262,8 @@ Before running a router, as one of its precondition tests, Exim works its way
 through the require_files list, expanding each item separately.
 
 Because the list is split before expansion, any colons in expansion items must
 through the require_files list, expanding each item separately.
 
 Because the list is split before expansion, any colons in expansion items must
-be doubled, or the facility for using a different list separator must be used.
-If any expansion is forced to fail, the item is ignored. Other expansion
+be doubled, or the facility for using a different list separator must be used (
+6.21). If any expansion is forced to fail, the item is ignored. Other expansion
 failures cause routing of the address to be deferred.
 
 If any expanded string is empty, it is ignored. Otherwise, except as described
 failures cause routing of the address to be deferred.
 
 If any expanded string is empty, it is ignored. Otherwise, except as described
@@ -16182,8 +17282,8 @@ domain, local part, or sender. (See section 3.12 for a full list of the order
 in which preconditions are evaluated.) However, as these options are all
 expanded, you can use the exists expansion condition to make such tests. The 
 require_files option is intended for checking files that the router may be
 in which preconditions are evaluated.) However, as these options are all
 expanded, you can use the exists expansion condition to make such tests. The 
 require_files option is intended for checking files that the router may be
-going to use internally, or which are needed by a transport (for example
-.procmailrc).
+going to use internally, or which are needed by a transport (e.g., .procmailrc
+).
 
 During delivery, the stat() function is run as root, but there is a facility
 for some checking of the accessibility of a file by another user. This is not a
 
 During delivery, the stat() function is run as root, but there is a facility
 for some checking of the accessibility of a file by another user. This is not a
@@ -16222,9 +17322,9 @@ The default action for handling an unresolved EACCES is to consider it to be
 caused by a configuration error, and routing is deferred because the existence
 or non-existence of the file cannot be determined. However, in some
 circumstances it may be desirable to treat this condition as if the file did
 caused by a configuration error, and routing is deferred because the existence
 or non-existence of the file cannot be determined. However, in some
 circumstances it may be desirable to treat this condition as if the file did
-not exist. If the file name (or the exclamation mark that precedes the file
-name for non-existence) is preceded by a plus sign, the EACCES error is treated
-as if the file did not exist. For example:
+not exist. If the filename (or the exclamation mark that precedes the filename
+for non-existence) is preceded by a plus sign, the EACCES error is treated as
+if the file did not exist. For example:
 
 require_files = +/some/file
 
 
 require_files = +/some/file
 
@@ -16232,9 +17332,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.
 
 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|
 |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
 
 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
@@ -16254,9 +17354,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.
 
 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|
 |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.)
 
 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.)
@@ -16286,9 +17386,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.
 
 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|
 |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 
 
 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 
@@ -16351,9 +17451,9 @@ send
     Exim with a different configuration file that handles the domain in another
     way.
 
     Exim with a different configuration file that handles the domain in another
     way.
 
-+-------+--------------+-------------------+--------------+
++---------------------------------------------------------+
 |senders|Use: routers**|Type: address list*|Default: unset|
 |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
 
 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
@@ -16367,9 +17467,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.
 
 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|
 |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
 
 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
@@ -16401,9 +17501,9 @@ The file would contain lines like
 You should not make use of this facility unless you really understand what you
 are doing.
 
 You should not make use of this facility unless you really understand what you
 are doing.
 
-+---------+------------+-------------+--------------+
++---------------------------------------------------+
 |transport|Use: routers|Type: string*|Default: unset|
 |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
 
 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
@@ -16416,9 +17516,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).
 
 private options that set up transports for pipe and file deliveries (see
 chapter 22).
 
-+---------------------------+------------+-------------+--------------+
++---------------------------------------------------------------------+
 |transport_current_directory|Use: routers|Type: string*|Default: unset|
 |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
 
 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
@@ -16429,9 +17529,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.
 
 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|
 |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
 
 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
@@ -16449,9 +17549,9 @@ if not, no home directory is set for the transport.
 
 See chapter 23 for further details of the local delivery environment.
 
 
 See chapter 23 for further details of the local delivery environment.
 
-+------+------------+--------------+--------------+
++-------------------------------------------------+
 |unseen|Use: routers|Type: boolean*|Default: false|
 |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
 
 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
@@ -16489,9 +17589,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.
 
 address_data option in the current or previous routers is passed on to
 subsequent routers.
 
-+----+------------+-------------+------------------+
++--------------------------------------------------+
 |user|Use: routers|Type: string*|Default: see below|
 |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.
 
 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.
@@ -16503,16 +17603,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.
 
 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|
 |verify|Use: routers**|Type: boolean|Default: true|
-+------+--------------+-------------+-------------+
++-------------------------------------------------+
 
 Setting this option has the effect of setting verify_sender and 
 verify_recipient to the same value.
 
 
 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|
 |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
 
 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
@@ -16525,22 +17625,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.
 
 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|
 |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
 
 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|
 |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
 
 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.
 
 
 
 
 
 
@@ -16582,9 +17682,9 @@ mx_domains can be set to disable the direct use of address records.
 MX records of equal priority are sorted by Exim into a random order. Exim then
 looks for address records for the host names obtained from MX or SRV records.
 When a host has more than one IP address, they are sorted into a random order,
 MX records of equal priority are sorted by Exim into a random order. Exim then
 looks for address records for the host names obtained from MX or SRV records.
 When a host has more than one IP address, they are sorted into a random order,
-except that IPv6 addresses are always sorted before IPv4 addresses. If all the
-IP addresses found are discarded by a setting of the ignore_target_hosts
-generic option, the router declines.
+except that IPv6 addresses are sorted before IPv4 addresses. If all the IP
+addresses found are discarded by a setting of the ignore_target_hosts generic
+option, the router declines.
 
 Unless they have the highest priority (lowest MX value), MX records that point
 to the local host, or to any host name that matches hosts_treat_as_local, are
 
 Unless they have the highest priority (lowest MX value), MX records that point
 to the local host, or to any host name that matches hosts_treat_as_local, are
@@ -16599,7 +17699,7 @@ happens is controlled by the generic self option.
 ------------------------------
 
 There have been problems with DNS servers when SRV records are looked up. Some
 ------------------------------
 
 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.
 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.
@@ -16621,6 +17721,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.
 
 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
 Reasons for a dnslookup router to decline currently include:
 
   * The domain does not exist in DNS
@@ -16651,9 +17754,9 @@ Reasons for a dnslookup router to decline currently include:
 
 The private options for the dnslookup router are as follows:
 
 
 The private options for the dnslookup router are as follows:
 
-+------------------+--------------+-------------+--------------+
++--------------------------------------------------------------+
 |check_secondary_mx|Use: dnslookup|Type: boolean|Default: false|
 |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
 
 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
@@ -16661,9 +17764,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.
 
 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|
 |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
 
 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
@@ -16697,26 +17800,36 @@ 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.
 
 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.
+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.
 
 
-+----------------------+--------------+------------------+--------------+
-|dnssec_require_domains|Use: dnslookup|Type: domain list*|Default: unset|
-+----------------------+--------------+------------------+--------------+
++-------------------------------------------+
+|ipv4_only|Use: string*|Type: unset|Default:|
++-------------------------------------------+
 
 
-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.
+The string is expanded, and if the result is anything but a forced failure, or
+an empty string, or one of the strings ?0? or ?no? or ?false? (checked without
+regard to the case of the letters), only A records are used.
+
++---------------------------------------------+
+|ipv4_prefer|Use: string*|Type: unset|Default:|
++---------------------------------------------+
 
 
-+----------+--------------+------------------+--------------+
+The string is expanded, and if the result is anything but a forced failure, or
+an empty string, or one of the strings ?0? or ?no? or ?false? (checked without
+regard to the case of the letters), A records are sorted before AAAA records
+(inverting the default).
+
++-----------------------------------------------------------+
 |mx_domains|Use: dnslookup|Type: domain list*|Default: unset|
 |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.)
 
 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.)
@@ -16730,17 +17843,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.
 
 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|
 |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.
 
 
 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|
 |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
 
 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
@@ -16749,9 +17862,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.
 
 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|
 |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
 
 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
@@ -16772,9 +17885,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.
 
 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|
 |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
 
 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
@@ -16797,9 +17910,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.
 
   * The router did not change the address in any way, for example, by
     "widening" the domain.
 
-+--------------+--------------+-------------+--------------+
++----------------------------------------------------------+
 |search_parents|Use: dnslookup|Type: boolean|Default: false|
 |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
 
 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
@@ -16814,17 +17927,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.
 
 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|
 |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.
 
 
 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|
 |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,
 
 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,
@@ -16910,40 +18023,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.
 
 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|
 |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.
 
 
 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|
 |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.
 
 
 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|
 |port|Use: iplookup|Type: integer|Default: 0|
-+----+-------------+-------------+----------+
++-------------------------------------------+
 
 This option must be supplied. It specifies the port number for the TCP or UDP
 call.
 
 
 This option must be supplied. It specifies the port number for the TCP or UDP
 call.
 
-+--------+-------------+------------+------------+
++------------------------------------------------+
 |protocol|Use: iplookup|Type: string|Default: udp|
 |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.
 
 
 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|
 |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:
 
 This defines the content of the query that is sent to the remote hosts. The
 default value is:
@@ -16953,9 +18066,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).
 
 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|
 |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
 
 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
@@ -16965,9 +18078,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.
 
 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|
 |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
 
 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
@@ -16980,9 +18093,9 @@ For example, if the response is just a new domain, the following could be used:
 response_pattern = ^([^@]+)$
 reroute = $local_part@$1
 
 response_pattern = ^([^@]+)$
 reroute = $local_part@$1
 
-+-------+-------------+----------+-----------+
++--------------------------------------------+
 |timeout|Use: iplookup|Type: time|Default: 5s|
 |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
 
 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
@@ -17028,15 +18141,15 @@ list of private options.
 
 The private options for the manualroute router are as follows:
 
 
 The private options for the manualroute router are as follows:
 
-+----------------+----------------+------------+--------------+
++-------------------------------------------------------------+
 |host_all_ignored|Use: manualroute|Type: string|Default: defer|
 |host_all_ignored|Use: manualroute|Type: string|Default: defer|
-+----------------+----------------+------------+--------------+
++-------------------------------------------------------------+
 
 See host_find_failed.
 
 
 See host_find_failed.
 
-+----------------+----------------+------------+---------------+
++--------------------------------------------------------------+
 |host_find_failed|Use: manualroute|Type: string|Default: freeze|
 |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
 
 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
@@ -17064,9 +18177,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.
 
 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|
 |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
 
 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
@@ -17089,9 +18202,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.
 
 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|
 |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
 
 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
@@ -17103,17 +18216,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.
 
 router declines. Other kinds of expansion failure cause delivery to be
 deferred.
 
-+----------+----------------+-----------------+--------------+
++------------------------------------------------------------+
 |route_list|Use: manualroute|Type: string list|Default: unset|
 |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.
 
 
 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|
 |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
 
 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
@@ -17138,7 +18251,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
 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>
 The format of each rule is
 
 <domain pattern>  <list of hosts>  <options>
@@ -17195,9 +18308,10 @@ be enclosed in quotes if it contains white space.
 A list of hosts, whether obtained via route_data or route_list, is always
 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
 A list of hosts, whether obtained via route_data or route_list, is always
 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.
+addresses, optionally also including ports. If the list is written with spaces,
+it must be protected with quotes. 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.21.
 
 If the list of hosts was obtained from a route_list item, the following
 variables are set during its expansion:
 
 If the list of hosts was obtained from a route_list item, the following
 variables are set during its expansion:
@@ -17301,12 +18415,12 @@ whether obtained from an MX lookup or not.
 20.7 How the options are used
 -----------------------------
 
 20.7 How the options are used
 -----------------------------
 
-The options are a sequence of words; in practice no more than three are ever
-present. One of the words can be the name of a transport; this overrides the 
-transport option on the router for this particular routing rule only. The other
-words (if present) control randomization of the list of hosts on a per-rule
-basis, and how the IP addresses of the hosts are to be found when routing to a
-remote transport. These options are as follows:
+The options are a sequence of words, space-separated. One of the words can be
+the name of a transport; this overrides the transport option on the router for
+this particular routing rule only. The other words (if present) control
+randomization of the list of hosts on a per-rule basis, and how the IP
+addresses of the hosts are to be found when routing to a remote transport.
+These options are as follows:
 
   * randomize: randomize the order of the hosts in this list, overriding the
     setting of hosts_randomize for this routing rule only.
 
   * randomize: randomize the order of the hosts in this list, overriding the
     setting of hosts_randomize for this routing rule only.
@@ -17322,6 +18436,10 @@ remote transport. These options are as follows:
     no address records are found. If there is a temporary DNS error (such as a
     timeout), delivery is deferred.
 
     no address records are found. If there is a temporary DNS error (such as a
     timeout), delivery is deferred.
 
+  * ipv4_only: in direct DNS lookups, look up only A records.
+
+  * ipv4_prefer: in direct DNS lookups, sort A records before AAAA records.
+
 For example:
 
 route_list = domain1  host1:host2:host3  randomize bydns;\
 For example:
 
 route_list = domain1  host1:host2:host3  randomize bydns;\
@@ -17337,6 +18455,10 @@ via getipnodebyname() times out, HOST_NOT_FOUND is returned instead of
 TRY_AGAIN. That is why the default action is to try a DNS lookup first. Only if
 that gives a definite "no such host" is the local function called.
 
 TRY_AGAIN. That is why the default action is to try a DNS lookup first. Only if
 that gives a definite "no such host" is the local function called.
 
+Compatibility: From Exim 4.85 until fixed for 4.90, there was an inadvertent
+constraint that a transport name as an option had to be the last option
+specified.
+
 If no IP address for a host can be found, what happens is controlled by the 
 host_find_failed option.
 
 If no IP address for a host can be found, what happens is controlled by the 
 host_find_failed option.
 
@@ -17487,26 +18609,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:
 
 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|
 |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).
 
 
 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|
 |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().
 
 
 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|
 |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
 
 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
@@ -17522,16 +18644,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.
 
 the command. In this circumstance the command runs under the current uid and
 gid.
 
-+-----------------+-----------------+------------+----------+
++-----------------------------------------------------------+
 |current_directory|Use: queryprogram|Type: string|Default: /|
 |current_directory|Use: queryprogram|Type: string|Default: /|
-+-----------------+-----------------+------------+----------+
++-----------------------------------------------------------+
 
 This option specifies an absolute path which is made the current directory
 before running the command.
 
 
 This option specifies an absolute path which is made the current directory
 before running the command.
 
-+-------+-----------------+----------+-----------+
++------------------------------------------------+
 |timeout|Use: queryprogram|Type: time|Default: 1h|
 |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.
 
 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.
@@ -17628,6 +18750,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.
 
 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
 ---------------------
 
 22.1 Redirection data
 ---------------------
@@ -17695,8 +18820,8 @@ interpreted in two different ways:
   * Otherwise, the data must be a comma-separated list of redirection items, as
     described in the next section.
 
   * Otherwise, the data must be a comma-separated list of redirection items, as
     described in the next section.
 
-When a message is redirected to a file (a "mail folder"), the file name given
-in a non-filter redirection list must always be an absolute path. A filter may
+When a message is redirected to a file (a "mail folder"), the filename given in
+a non-filter redirection list must always be an absolute path. A filter may
 generate a relative path - how this is handled depends on the transport's
 configuration. See section 26.1 for a discussion of this issue for the 
 appendfile transport.
 generate a relative path - how this is handled depends on the transport's
 configuration. See section 26.1 for a discussion of this issue for the 
 appendfile transport.
@@ -17707,9 +18832,9 @@ appendfile transport.
 
 When the redirection data is not an Exim or Sieve filter, for example, if it
 comes from a conventional alias or forward file, it consists of a list of
 
 When the redirection data is not an Exim or Sieve filter, for example, if it
 comes from a conventional alias or forward file, it consists of a list of
-addresses, file names, pipe commands, or certain special items (see section
-22.6 below). The special items can be individually enabled or disabled by means
-of options whose names begin with allow_ or forbid_, depending on their default
+addresses, filenames, pipe commands, or certain special items (see section 22.6
+below). The special items can be individually enabled or disabled by means of
+options whose names begin with allow_ or forbid_, depending on their default
 values. The items in the list are separated by commas or newlines. If a comma
 is required in an item, the entire item must be enclosed in double quotes.
 
 values. The items in the list are separated by commas or newlines. If a comma
 is required in an item, the entire item must be enclosed in double quotes.
 
@@ -17825,14 +18950,14 @@ lists (that is, in non-filter redirection data):
 
     /home/world/minbari
 
 
     /home/world/minbari
 
-    is treated as a file name, but
+    is treated as a filename, but
 
     /s=molari/o=babylon/@x400gate.way
 
 
     /s=molari/o=babylon/@x400gate.way
 
-    is treated as an address. For a file name, a transport must be specified
+    is treated as an address. For a filename, a transport must be specified
     using the file_transport option. However, if the generated path name ends
     with a forward slash character, it is interpreted as a directory name
     using the file_transport option. However, if the generated path name ends
     with a forward slash character, it is interpreted as a directory name
-    rather than a file name, and directory_transport is used instead.
+    rather than a filename, and directory_transport is used instead.
 
     Normally, either the router or the transport specifies a user and a group
     under which to run the delivery. The default is to use the Exim user and
 
     Normally, either the router or the transport specifies a user and a group
     under which to run the delivery. The default is to use the Exim user and
@@ -17861,10 +18986,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
 
   * 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
 
     Warning: If :blackhole: appears anywhere in a redirection list, no delivery
     is done for the original local part, even if other redirection items are
@@ -17913,7 +19041,7 @@ lists (that is, in non-filter redirection data):
 
     During routing for message delivery (as opposed to verification), a
     redirection containing :fail: causes an immediate failure of the incoming
 
     During routing for message delivery (as opposed to verification), a
     redirection containing :fail: causes an immediate failure of the incoming
-    address, whereas :defer: causes the message to remain on the queue so that
+    address, whereas :defer: causes the message to remain in the queue so that
     a subsequent delivery attempt can happen at a later time. If an address is
     deferred for too long, it will ultimately fail, because the normal retry
     rules still apply.
     a subsequent delivery attempt can happen at a later time. If an address is
     deferred for too long, it will ultimately fail, because the normal retry
     rules still apply.
@@ -17976,23 +19104,23 @@ deferred. See also syntax_errors_to.
 
 The private options for the redirect router are as follows:
 
 
 The private options for the redirect router are as follows:
 
-+-----------+-------------+-------------+--------------+
++------------------------------------------------------+
 |allow_defer|Use: redirect|Type: boolean|Default: false|
 |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.
 
 
 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|
 |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.
 
 
 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|
 |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
 
 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
@@ -18008,18 +19136,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.
 
 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|
 |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.
 
 
 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|
 |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.
 
 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.
@@ -18044,9 +19172,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.
 
 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|
 |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
 
 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
@@ -18056,9 +19184,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.
 
 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|
 |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;
 
 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;
@@ -18066,9 +19194,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.
 
 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|
 |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
 
 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
@@ -18087,18 +19215,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.
 
 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|
 |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.
 
 
 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|
 |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;
 
 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;
@@ -18115,42 +19243,42 @@ 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.
 
 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|
 |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
 specified by this option, which, after expansion, must be the name of a
 configured transport. This should normally be an appendfile transport. When it
 
 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
 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.
+is running, the filename is in $address_file.
 
 
-+-------------------+-------------+-------------+-------------+
++-------------------------------------------------------------+
 |filter_prepend_home|Use: redirect|Type: boolean|Default: true|
 |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.
 
 
 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|
 |forbid_blackhole|Use: redirect|Type: boolean|Default: false|
-+----------------+-------------+-------------+--------------+
++-----------------------------------------------------------+
 
 If this option is true, the :blackhole: item may not appear in a redirection
 list.
 
 
 If this option is true, the :blackhole: item may not appear in a redirection
 list.
 
-+------------------+-------------+-------------+--------------+
++-------------------------------------------------------------+
 |forbid_exim_filter|Use: redirect|Type: boolean|Default: false|
 |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.
 
 
 If this option is set true, only Sieve filters are permitted when allow_filter
 is true.
 
-+-----------+-------------+-------------+--------------+
++------------------------------------------------------+
 |forbid_file|Use: redirect|Type: boolean|Default: false|
 |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
 
 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
@@ -18158,77 +19286,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.
 
 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|
 |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.
 
 
 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|
 |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.
 
 
 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|
 |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).
 
 
 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|
 |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.
 
 
 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|
 |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.
 
 
 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|
 |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.
 
 
 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|
 |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.
 
 
 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|
 |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.
 
 
 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|
 |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.
 
 
 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|
 |forbid_include|Use: redirect|Type: boolean|Default: false|
-+--------------+-------------+-------------+--------------+
++---------------------------------------------------------+
 
 If this option is true, items of the form
 
 
 If this option is true, items of the form
 
@@ -18236,32 +19364,32 @@ If this option is true, items of the form
 
 are not permitted in non-filter redirection lists.
 
 
 are not permitted in non-filter redirection lists.
 
-+-----------+-------------+-------------+--------------+
++------------------------------------------------------+
 |forbid_pipe|Use: redirect|Type: boolean|Default: false|
 |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.
 
 
 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|
 |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.
 
 
 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|
 |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.
 
 
 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|
 |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
 
 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
@@ -18269,17 +19397,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.
 
 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|
 |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.
 
 
 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|
 |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
 
 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
@@ -18294,23 +19422,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.
 
 (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|
 |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.
 
 
 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|
 |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.
 
 
 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|
 |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
 
 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
@@ -18344,25 +19472,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.
 
 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|
 |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.
 
 
 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|
 |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.
 
 
 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|
 |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
 
 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
@@ -18370,9 +19498,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.
 
 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|
 |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
 
 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
@@ -18385,9 +19513,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.
 
 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|
 |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
 
 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
@@ -18396,9 +19524,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.
 
 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|
 |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
 
 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
@@ -18406,9 +19534,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.
 
 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|
 |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,
 
 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,
@@ -18416,32 +19544,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.
 
 normally be an autoreply transport. Other transports are unlikely to do
 anything sensible or useful.
 
-+-------+-------------+-------------+-------------+
++-------------------------------------------------+
 |rewrite|Use: redirect|Type: boolean|Default: true|
 |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.
 
 
 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|
 |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.
 
 
 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|
 |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.
 
 
 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|
 |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
 
 To enable the "vacation" extension for Sieve filters, you must set 
 sieve_vacation_directory to the directory where vacation databases are held (do
@@ -18449,9 +19577,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.
 
 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|
 |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 
 
 If skip_syntax_errors is set, syntactically malformed addresses in non-filter
 redirection data are skipped, and each failing address is logged. If 
@@ -18519,15 +19647,15 @@ router to locally-generated messages, using a condition such as this:
   condition = ${if match {$sender_host_address}\
                          {\N^(|127\.0\.0\.1)$\N}}
 
   condition = ${if match {$sender_host_address}\
                          {\N^(|127\.0\.0\.1)$\N}}
 
-+------------------+-------------+-------------+--------------+
++-------------------------------------------------------------+
 |syntax_errors_text|Use: redirect|Type: string*|Default: unset|
 |syntax_errors_text|Use: redirect|Type: string*|Default: unset|
-+------------------+-------------+-------------+--------------+
++-------------------------------------------------------------+
 
 See skip_syntax_errors above.
 
 
 See skip_syntax_errors above.
 
-+----------------+-------------+------------+--------------+
++----------------------------------------------------------+
 |syntax_errors_to|Use: redirect|Type: string|Default: unset|
 |syntax_errors_to|Use: redirect|Type: string|Default: unset|
-+----------------+-------------+------------+--------------+
++----------------------------------------------------------+
 
 See skip_syntax_errors above.
 
 
 See skip_syntax_errors above.
 
@@ -18570,7 +19698,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 
 
 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.
 
 
 algorithm that Exim itself uses.
 
 
@@ -18694,35 +19822,35 @@ $original_domain is never set.
 
 The following generic options apply to all transports:
 
 
 The following generic options apply to all transports:
 
-+---------+---------------+-------------+--------------+
++------------------------------------------------------+
 |body_only|Use: transports|Type: boolean|Default: false|
 |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.
 
 
 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|
 |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.
 
 
 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|
 |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.
 
 
 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|
 |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
 
 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
@@ -18735,9 +19863,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.
 
 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|
 |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,
 
 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,
@@ -18745,16 +19873,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.
 
 removal from incoming messages, so that delivered messages can safely be resent
 to other recipients.
 
-+------+---------------+------------+--------------+
++--------------------------------------------------+
 |driver|Use: transports|Type: string|Default: unset|
 |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.
 
 
 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|
 |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
 
 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
@@ -18765,54 +19893,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.
 
 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|
 |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).
 
 
 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|
 |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 6.21), 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.
 
 
 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|
 |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.
 
 
 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|
 |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 6.21); 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
 
 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|
 |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
 
 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
@@ -18833,9 +19974,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.
 
 change the return path using return_path, but you cannot change envelope
 recipients at this time.
 
-+--------------+---------------+-------------+--------------+
++-----------------------------------------------------------+
 |home_directory|Use: transports|Type: string*|Default: unset|
 |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
 
 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
@@ -18845,17 +19986,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.
 
 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|
 |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.
 
 
 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|
 |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
 
 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
@@ -18867,9 +20029,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.
 
 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|
 |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
 
 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
@@ -18889,9 +20051,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.
 
 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|
 |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
 
 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
@@ -18910,9 +20072,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.
 
 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|
 |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
 
 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
@@ -18929,7 +20091,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
 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
 
 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
@@ -18937,9 +20099,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.
 
 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|
 |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
 
 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
@@ -18952,15 +20114,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.
 
 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|
 |shadow_condition|Use: transports|Type: string*|Default: unset|
-+----------------+---------------+-------------+--------------+
++-------------------------------------------------------------+
 
 See shadow_transport below.
 
 
 See shadow_transport below.
 
-+----------------+---------------+------------+--------------+
++------------------------------------------------------------+
 |shadow_transport|Use: transports|Type: string|Default: unset|
 |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.
 
 A local transport may set the shadow_transport option to the name of another
 local transport. Shadow remote transports are not supported.
@@ -18987,13 +20149,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.
 
 provides, and implementing automatic acknowledgment policies based on message
 headers that some sites insist on.
 
-+----------------+---------------+-------------+--------------+
++-------------------------------------------------------------+
 |transport_filter|Use: transports|Type: string*|Default: unset|
 |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
 
 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
 
 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
@@ -19092,9 +20255,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.
 
 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|
 |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
 
 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
@@ -19104,9 +20267,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.
 
 if the pipe transport's timeout_defer option is set true, it becomes a
 temporary error.
 
-+----+---------------+-------------+------------------+
++-----------------------------------------------------+
 |user|Use: transports|Type: string*|Default: Exim user|
 |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
 
 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
@@ -19189,7 +20352,7 @@ check_string = "."
 escape_string = ".."
 
 when batched SMTP is in use. A full description of the batch SMTP mechanism is
 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 
 because it always delivers using the SMTP protocol.
 
 If the generic envelope_to_add option is set for a batching transport, the 
@@ -19285,7 +20448,7 @@ require "fileinto";
 fileinto "folder23";
 
 In this situation, the expansion of file or directory in the transport must
 fileinto "folder23";
 
 In this situation, the expansion of file or directory in the transport must
-transform the relative path into an appropriate absolute file name. In the case
+transform the relative path into an appropriate absolute filename. In the case
 of Sieve filters, the name inbox must be handled. It is the name that is used
 as a result of a "keep" action in the filter. This example shows one way of
 handling this requirement:
 of Sieve filters, the name inbox must be handled. It is the name that is used
 as a result of a "keep" action in the filter. This example shows one way of
 handling this requirement:
@@ -19315,17 +20478,17 @@ directory option is still used if it is set.
 26.2 Private options for appendfile
 -----------------------------------
 
 26.2 Private options for appendfile
 -----------------------------------
 
-+----------+---------------+-------------+--------------+
++-------------------------------------------------------+
 |allow_fifo|Use: appendfile|Type: boolean|Default: false|
 |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.
 
 
 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|
 |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
 
 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
@@ -19333,40 +20496,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.
 
 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|
 |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.
 
 
 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|
 |batch_max|Use: appendfile|Type: integer|Default: 1|
-+---------+---------------+-------------+----------+
++--------------------------------------------------+
 
 See the description of local delivery batching in chapter 25.
 
 
 See the description of local delivery batching in chapter 25.
 
-+-----------+---------------+-------------+--------------+
++--------------------------------------------------------+
 |check_group|Use: appendfile|Type: boolean|Default: false|
 |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.
 
 
 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|
 |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.
 
 
 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|
 |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
 
 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
@@ -19389,9 +20552,9 @@ escape_string = "\1\1\1\1 \n"
 message_prefix = "\1\1\1\1\n"
 message_suffix = "\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|
 |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
 
 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
@@ -19403,9 +20566,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.
 
 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|
 |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
 
 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
@@ -19414,14 +20577,14 @@ it applies to the top level directory, not the maildir directories beneath.
 
 The option must be set to one of the words "anywhere", "inhome", or
 "belowhome". In the second and third cases, a home directory must have been set
 
 The option must be set to one of the words "anywhere", "inhome", or
 "belowhome". In the second and third cases, a home directory must have been set
-for the transport. This option is not useful when an explicit file name is
-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
+for the transport. This option is not useful when an explicit filename is given
+for normal mailbox deliveries. It is intended for the case when filenames are
+generated from users' .forward files. These are usually handled by an 
 appendfile transport called address_file. See also file_must_exist.
 
 appendfile transport called address_file. See also file_must_exist.
 
-+---------+---------------+-------------+--------------+
++------------------------------------------------------+
 |directory|Use: appendfile|Type: string*|Default: unset|
 |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
 
 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
@@ -19433,9 +20596,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.
 
 (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|
 |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
 
 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
@@ -19447,22 +20610,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.
 
 inode of the file. The variable $inode is available only when expanding this
 option.
 
-+--------------+---------------+-------------------+-------------+
++----------------------------------------------------------------+
 |directory_mode|Use: appendfile|Type: octal integer|Default: 0700|
 |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.
 
 
 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|
 |escape_string|Use: appendfile|Type: string|Default: see description|
-+-------------+---------------+------------+------------------------+
++-------------------------------------------------------------------+
 
 See check_string above.
 
 
 See check_string above.
 
-+----+---------------+-------------+--------------+
++-------------------------------------------------+
 |file|Use: appendfile|Type: string*|Default: unset|
 |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
 
 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
@@ -19487,9 +20650,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.
 
 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|
 |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
 
 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
@@ -19511,17 +20674,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.
 
 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|
 |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.
 
 
 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|
 |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
 
 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
@@ -19552,54 +20715,54 @@ local deliveries because of errors of the form
 
 failed to lock mailbox /some/file (fcntl)
 
 
 failed to lock mailbox /some/file (fcntl)
 
-+------------------+---------------+----------+-----------+
++---------------------------------------------------------+
 |lock_flock_timeout|Use: appendfile|Type: time|Default: 0s|
 |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.
 
 
 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|
 |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.
 
 
 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|
 |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.
 
 
 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|
 |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).
 
 
 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|
 |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.
 
 
 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|
 |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.
 
 
 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|
 |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.
 
 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.
@@ -19607,9 +20770,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.
 
 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|
 |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
 
 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
@@ -19619,9 +20782,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.
 
 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|
 |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
 
 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
@@ -19641,23 +20804,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.
 
 calculations, quota processing is bypassed for any messages that are delivered
 directly into that directory.
 
-+---------------+---------------+-------------+-----------+
++---------------------------------------------------------+
 |maildir_retries|Use: appendfile|Type: integer|Default: 10|
 |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.
 
 
 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|
 |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.
 
 
 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|
 |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
 
 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
@@ -19665,9 +20828,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.
 
 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|
 |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
 
 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
@@ -19677,31 +20840,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.
 
 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|
 |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.
 
 
 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|
 |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.
 
 
 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|
 |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.
 
 
 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|
 |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
 
 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
@@ -19731,9 +20894,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.
 
 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|
 |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
 
 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
@@ -19745,9 +20908,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.
 
 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|
 |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
 
 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
@@ -19759,9 +20922,9 @@ message_suffix =
 Note: If you set use_crlf true, you must change any occurrences of "\n" to "\r\
 n" in 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|
 |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
 
 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
@@ -19770,26 +20933,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.
 
 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|
 |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.
 
 
 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|
 |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.
 
 
 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|
 |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
 
 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
@@ -19812,9 +20975,12 @@ obvious value which users understand most easily.
 
 The value of the option is expanded, and must then be a numerical value
 (decimal point allowed), optionally followed by one of the letters K, M, or G,
 
 The value of the option is expanded, and must then be a numerical value
 (decimal point allowed), optionally followed by one of the letters K, M, or G,
-for kilobytes, megabytes, or gigabytes. If Exim is running on a system with
-large file support (Linux and FreeBSD have this), mailboxes larger than 2G can
-be handled.
+for kilobytes, megabytes, or gigabytes, optionally followed by a slash and
+further option modifiers. If Exim is running on a system with large file
+support (Linux and FreeBSD have this), mailboxes larger than 2G can be handled.
+
+The option modifier no_check can be used to force delivery even if the over
+quota condition is met. The quota gets updated as usual.
 
 Note: A value of zero is interpreted as "no quota".
 
 
 Note: A value of zero is interpreted as "no quota".
 
@@ -19835,18 +21001,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.
 
 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|
 |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.
 
 
 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|
 |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
 
 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
@@ -19854,27 +21020,30 @@ 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".
 
 failure causes delivery to be deferred. A value of zero is interpreted as "no
 quota".
 
-+------------------+---------------+-------------+-------------+
+The option modifier no_check can be used to force delivery even if the over
+quota condition is met. The quota gets updated as usual.
+
++--------------------------------------------------------------+
 |quota_is_inclusive|Use: appendfile|Type: boolean|Default: true|
 |quota_is_inclusive|Use: appendfile|Type: boolean|Default: true|
-+------------------+---------------+-------------+-------------+
++--------------------------------------------------------------+
 
 See quota above.
 
 
 See quota above.
 
-+----------------+---------------+------------+--------------+
++------------------------------------------------------------+
 |quota_size_regex|Use: appendfile|Type: string|Default: unset|
 |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
 these files in order to test the quota, it first checks quota_size_regex. If
 
 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
 these files in order to test the quota, it first checks quota_size_regex. If
-this is set to a regular expression that matches the file name, and it captures
+this is set to a regular expression that matches the filename, and it captures
 one string, that string is interpreted as a representation of the file's size.
 The value of quota_size_regex is not expanded.
 
 This feature is useful only when users have no shell access to their mailboxes
 - otherwise they could defeat the quota simply by renaming the files. This
 facility can be used with maildir deliveries, by setting maildir_tag to add the
 one string, that string is interpreted as a representation of the file's size.
 The value of quota_size_regex is not expanded.
 
 This feature is useful only when users have no shell access to their mailboxes
 - otherwise they could defeat the quota simply by renaming the files. This
 facility can be used with maildir deliveries, by setting maildir_tag to add the
-file length to the file name. For example:
+file length to the filename. For example:
 
 maildir_tag = ,S=$message_size
 quota_size_regex = ,S=(\d+)
 
 maildir_tag = ,S=$message_size
 quota_size_regex = ,S=(\d+)
@@ -19883,14 +21052,14 @@ An alternative to $message_size is $message_linecount, which contains the
 number of lines in the message.
 
 The regular expression should not assume that the length is at the end of the
 number of lines in the message.
 
 The regular expression should not assume that the length is at the end of the
-file name (even though maildir_tag puts it there) because maildir MUAs
-sometimes add other information onto the ends of message file names.
+filename (even though maildir_tag puts it there) because maildir MUAs sometimes
+add other information onto the ends of message filenames.
 
 Section 26.7 contains further information.
 
 
 Section 26.7 contains further information.
 
-+------------------+---------------+-------------+------------------+
++-------------------------------------------------------------------+
 |quota_warn_message|Use: appendfile|Type: string*|Default: see below|
 |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
 
 See below for the use of this option. If it is not set when 
 quota_warn_threshold is set, it defaults to
@@ -19904,9 +21073,9 @@ quota_warn_message = "\
   a warning threshold that is\n\
   set by the system administrator.\n"
 
   a warning threshold that is\n\
   set by the system administrator.\n"
 
-+--------------------+---------------+-------------+----------+
++-------------------------------------------------------------+
 |quota_warn_threshold|Use: appendfile|Type: string*|Default: 0|
 |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
 
 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
@@ -19935,18 +21104,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.
 
 independent of one another except when the threshold is specified as a
 percentage.
 
-+---------+---------------+-------------+--------------+
++------------------------------------------------------+
 |use_bsmtp|Use: appendfile|Type: boolean|Default: false|
 |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
 
 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|
 |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
 
 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
@@ -19960,9 +21129,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.
 
 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|
 |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 
 
 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 
@@ -19970,9 +21139,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.
 
 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|
 |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
 
 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
@@ -19992,9 +21161,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).
 
 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|
 |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().
 
 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().
@@ -20012,9 +21181,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.
 
 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|
 |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
 
 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
@@ -20055,7 +21224,7 @@ Before appending to a file, the following preparations are made:
         for writing as a new file. If this fails with an access error, delivery
         is deferred.
 
         for writing as a new file. If this fails with an access error, delivery
         is deferred.
 
-     2. Close the hitching post file, and hard link it to the lock file name.
+     2. Close the hitching post file, and hard link it to the lock filename.
 
      3. If the call to link() succeeds, creation of the lock file has
         succeeded. Unlink the hitching post name.
 
      3. If the call to link() succeeds, creation of the lock file has
         succeeded. Unlink the hitching post name.
@@ -20202,10 +21371,10 @@ to a file whose name is tmp/<stime>.H<mtime>P<pid>.<host> in the directory that
 is defined by the directory option (the "delivery directory"). If the delivery
 is successful, the file is renamed into the new subdirectory.
 
 is defined by the directory option (the "delivery directory"). If the delivery
 is successful, the file is renamed into the new subdirectory.
 
-In the file name, <stime> is the current time of day in seconds, and <mtime> is
+In the filename, <stime> is the current time of day in seconds, and <mtime> is
 the microsecond fraction of the time. After a maildir delivery, Exim checks
 that the time-of-day clock has moved on by at least one microsecond before
 the microsecond fraction of the time. After a maildir delivery, Exim checks
 that the time-of-day clock has moved on by at least one microsecond before
-terminating the delivery process. This guarantees uniqueness for the file name.
+terminating the delivery process. This guarantees uniqueness for the filename.
 However, as a precaution, Exim calls stat() for the file before opening it. If
 any response other than ENOENT (does not exist) is given, Exim waits 2 seconds
 and tries again, up to maildir_retries times.
 However, as a precaution, Exim calls stat() for the file before opening it. If
 any response other than ENOENT (does not exist) is given, Exim waits 2 seconds
 and tries again, up to maildir_retries times.
@@ -20355,7 +21524,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
 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
 
 
 directory = /var/bsmtp/$host
 
@@ -20431,89 +21600,89 @@ are just discarded.
 27.1 Private options for autoreply
 ----------------------------------
 
 27.1 Private options for autoreply
 ----------------------------------
 
-+---+--------------+-------------+--------------+
++-----------------------------------------------+
 |bcc|Use: autoreply|Type: string*|Default: unset|
 |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.
 
 
 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|
 |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.
 
 
 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|
 |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.
 
 
 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|
 |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.
 
 
 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|
 |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.
 
 
 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|
 |from|Use: autoreply|Type: string*|Default: unset|
-+----+--------------+-------------+--------------+
++------------------------------------------------+
 
 This specifies the contents of the From: header when the message is specified
 by the transport.
 
 
 This specifies the contents of the From: header when the message is specified
 by the transport.
 
-+-------+--------------+-------------+--------------+
++---------------------------------------------------+
 |headers|Use: autoreply|Type: string*|Default: unset|
 |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.
 
 
 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|
 |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.
 
 
 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|
 |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.
 
 
 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|
 |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.
 
 
 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|
 |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
 does not apply to Cc: or Bcc: recipients.
 
 If once is unset, or is set to an empty string, the message is always sent. By
 
 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
 does not apply to Cc: or Bcc: recipients.
 
 If once is unset, or is set to an empty string, the message is always sent. By
-default, if once is set to a non-empty file name, the message is not sent if a
+default, if once is set to a non-empty filename, the message is not sent if a
 potential recipient is already listed in the database. However, if the 
 once_repeat option specifies a time greater than zero, the message is sent if
 that much time has elapsed since a message was last sent to this recipient. A
 potential recipient is already listed in the database. However, if the 
 once_repeat option specifies a time greater than zero, the message is sent if
 that much time has elapsed since a message was last sent to this recipient. A
@@ -20533,37 +21702,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.
 
 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|
 |once_file_size|Use: autoreply|Type: integer|Default: 0|
-+--------------+--------------+-------------+----------+
++------------------------------------------------------+
 
 See once above.
 
 
 See once above.
 
-+-----------+--------------+-----------+-----------+
++--------------------------------------------------+
 |once_repeat|Use: autoreply|Type: time*|Default: 0s|
 |once_repeat|Use: autoreply|Type: time*|Default: 0s|
-+-----------+--------------+-----------+-----------+
++--------------------------------------------------+
 
 See once above. After expansion, the value of this option must be a valid time
 value.
 
 
 See once above. After expansion, the value of this option must be a valid time
 value.
 
-+--------+--------------+-------------+--------------+
++----------------------------------------------------+
 |reply_to|Use: autoreply|Type: string*|Default: unset|
 |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.
 
 
 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|
 |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.
 
 
 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|
 |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
 
 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
@@ -20577,17 +21746,17 @@ bounce messages as subscription confirmations. Well-managed lists require a
 non-bounce message to confirm a subscription, so the danger is relatively
 small.
 
 non-bounce message to confirm a subscription, so the danger is relatively
 small.
 
-+----+--------------+-------------+--------------+
++------------------------------------------------+
 |text|Use: autoreply|Type: string*|Default: unset|
 |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.
 
 
 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|
 |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.
 
 This specifies recipients of the message and the contents of the To: header
 when the message is specified by the transport.
@@ -20610,24 +21779,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:
 
 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|
 |batch_id|Use: lmtp|Type: string*|Default: unset|
-+--------+---------+-------------+--------------+
++-----------------------------------------------+
 
 See the description of local delivery batching in chapter 25.
 
 
 See the description of local delivery batching in chapter 25.
 
-+---------+---------+-------------+----------+
++--------------------------------------------+
 |batch_max|Use: lmtp|Type: integer|Default: 1|
 |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.
 
 
 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|
 |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
 
 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
@@ -20636,25 +21805,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.
 
 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|
 |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.
 
 
 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|
 |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.
 
 
 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|
 |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
 
 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
@@ -20718,7 +21887,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
 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
 
 
 29.2 Returned status and data
@@ -20790,12 +21960,12 @@ example:
 command = /bin/sh -c ${lookup{$local_part}lsearch{/some/file}}
 
 Special handling takes place when an argument consists of precisely the text
 command = /bin/sh -c ${lookup{$local_part}lsearch{/some/file}}
 
 Special handling takes place when an argument consists of precisely the text
-"$pipe_addresses". This is not a general expansion variable; the only place
-this string is recognized is when it appears as an argument for a pipe or
-transport filter command. It causes each address that is being handled to be
-inserted in the argument list at that point as a separate argument. This avoids
-any problems with spaces or shell metacharacters, and is of use when a pipe
-transport is handling groups of addresses in a batch.
+"$pipe_addresses" (no quotes). This is not a general expansion variable; the
+only place this string is recognized is when it appears as an argument for a
+pipe or transport filter command. It causes each address that is being handled
+to be inserted in the argument list at that point as a separate argument. This
+avoids any problems with spaces or shell metacharacters, and is of use when a 
+pipe transport is handling groups of addresses in a batch.
 
 If force_command is enabled on the transport, Special handling takes place for
 an argument that consists of precisely the text "$address_pipe". It is handled
 
 If force_command is enabled on the transport, Special handling takes place for
 an argument that consists of precisely the text "$address_pipe". It is handled
@@ -20833,7 +22003,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
 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
 
 DOMAIN               the domain of the address
 HOME                 the home directory, if set
@@ -20872,9 +22043,9 @@ directory if check_local_user is set.
 29.5 Private options for pipe
 -----------------------------
 
 29.5 Private options for pipe
 -----------------------------
 
-+--------------+---------+------------------+--------------+
++----------------------------------------------------------+
 |allow_commands|Use: pipe|Type: string list*|Default: unset|
 |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
 
 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
@@ -20891,22 +22062,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.
 
 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|
 |batch_id|Use: pipe|Type: string*|Default: unset|
-+--------+---------+-------------+--------------+
++-----------------------------------------------+
 
 See the description of local delivery batching in chapter 25.
 
 
 See the description of local delivery batching in chapter 25.
 
-+---------+---------+-------------+----------+
++--------------------------------------------+
 |batch_max|Use: pipe|Type: integer|Default: 1|
 |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.
 
 
 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|
 |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
 
 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
@@ -20916,9 +22087,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.
 
 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|
 |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
 
 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
@@ -20927,41 +22098,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.
 
 Exim, and each argument is separately expanded, as described in section 29.3
 above.
 
-+-----------+---------+-------------+--------------+
++--------------------------------------------------+
 |environment|Use: pipe|Type: string*|Default: unset|
 |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>.
 
 
 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|
 |escape_string|Use: pipe|Type: string|Default: unset|
-+-------------+---------+------------+--------------+
++---------------------------------------------------+
 
 See check_string above.
 
 
 See check_string above.
 
-+----------------+---------+-------------+--------------+
++-------------------------------------------------------+
 |freeze_exec_fail|Use: pipe|Type: boolean|Default: false|
 |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.
 
 
 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|
 |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.
 
 
 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|
 |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 
 
 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 
@@ -20975,9 +22146,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.
 
 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|
 |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.
 
 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.
@@ -20988,35 +22159,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.
 
 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|
 |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
 
 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|
 |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|
 |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|
 |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
 
 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
@@ -21026,9 +22199,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.
 
 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|
 |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
 
 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
@@ -21046,9 +22219,9 @@ message_prefix =
 Note: If you set use_crlf true, you must change any occurrences of "\n" to "\r\
 n" in 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|
 |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
 
 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
@@ -21059,22 +22232,18 @@ message_suffix =
 Note: If you set use_crlf true, you must change any occurrences of "\n" to "\r\
 n" in 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:
-
-/bin:/usr/bin
++---------------------------------------------------+
+|path|Use: pipe|Type: string*|Default: /bin:/usr/bin|
++---------------------------------------------------+
 
 
-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.
+This option is expanded and 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|
 |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
 
 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
@@ -21085,9 +22254,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.
 
 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|
 |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
 
 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
@@ -21095,9 +22264,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.
 
 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|
 |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
 
 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
@@ -21105,9 +22274,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.
 
 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|
 |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,
 
 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,
@@ -21116,9 +22285,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.
 
 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|
 |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
 
 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
@@ -21128,9 +22297,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.
 
 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|
 |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
 
 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
@@ -21142,9 +22311,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.
 
 that does not define these macros, it assumes values of 75 and 73,
 respectively.
 
-+-------+---------+----------+-----------+
++----------------------------------------+
 |timeout|Use: pipe|Type: time|Default: 1h|
 |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
 
 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
@@ -21153,42 +22322,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.
 
 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|
 |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.
 
 
 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|
 |umask|Use: pipe|Type: octal integer|Default: 022|
-+-----+---------+-------------------+------------+
++------------------------------------------------+
 
 This specifies the umask setting for the subprocess that runs the command.
 
 
 This specifies the umask setting for the subprocess that runs the command.
 
-+---------+---------+-------------+--------------+
++------------------------------------------------+
 |use_bsmtp|Use: pipe|Type: boolean|Default: false|
 |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
 
 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|
 |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.
 
 
 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|
 |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
 
 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
@@ -21201,9 +22370,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.
 
 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|
 |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
 
 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
@@ -21308,7 +22477,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
     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
 
   * 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
@@ -21355,9 +22524,9 @@ removed in a future release.
 
 The private options of the smtp transport are as follows:
 
 
 The private options of the smtp transport are as follows:
 
-+----------------------------+---------+-------------+-------------+
++------------------------------------------------------------------+
 |address_retry_include_sender|Use: smtp|Type: boolean|Default: true|
 |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
 
 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
@@ -21366,9 +22535,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.
 
 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|
 |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
 
 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
@@ -21377,9 +22546,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).
 
 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|
 |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,
 
 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,
@@ -21408,25 +22577,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.
 
 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|
 |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.
 
 
 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|
 |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.
 
 
 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|
 |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
 
 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
@@ -21435,25 +22604,77 @@ 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.
 
 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|
 |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.
 
 
 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.
 
-+------------+---------+----------+-----------+
++---------------------------------------------------------------+
+|dane_require_tls_ciphers|Use: smtp|Type: string*|Default: unset|
++---------------------------------------------------------------+
+
+This option may be used to override tls_require_ciphers for connections where
+DANE has been determined to be in effect. If not set, then tls_require_ciphers
+will be used. Normal SMTP delivery is not able to make strong demands of TLS
+cipher configuration, because delivery will fall back to plaintext. Once DANE
+has been determined to be in effect, there is no plaintext fallback and making
+the TLS cipherlist configuration stronger will increase security, rather than
+counter-intuitively decreasing it. If the option expands to be empty or is
+forced to fail, then it will be treated as unset and tls_require_ciphers will
+be used instead.
+
++---------------------------------------------+
 |data_timeout|Use: smtp|Type: time|Default: 5m|
 |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.
 
 
 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_canon|Use: smtp|Type: string*|Default: unset|
++-------------------------------------------------+
+
++-------------------------------------------------+
+|dkim_domain|Use: smtp|Type: string|Default: list*|
++-------------------------------------------------+
+
++-------------------------------------------------+
+|dkim_hash|Use: smtp|Type: string*|Default: sha256|
++-------------------------------------------------+
+
++----------------------------------------------------+
+|dkim_identity|Use: smtp|Type: string*|Default: unset|
++----------------------------------------------------+
+
++-------------------------------------------------------+
+|dkim_private_key|Use: smtp|Type: string*|Default: unset|
++-------------------------------------------------------+
+
++----------------------------------------------------+
+|dkim_selector|Use: smtp|Type: string*|Default: unset|
++----------------------------------------------------+
+
++--------------------------------------------------+
+|dkim_strict|Use: smtp|Type: string*|Default: unset|
++--------------------------------------------------+
+
++----------------------------------------------------------+
+|dkim_sign_headers|Use: smtp|Type: string*|Default: per RFC|
++----------------------------------------------------------+
+
++------------------------------------------------------+
+|dkim_timestamps|Use: smtp|Type: string*|Default: unset|
++------------------------------------------------------+
+
+DKIM signing options. For details see section 57.2.
+
++--------------------------------------------------------+
 |delay_after_cutoff|Use: smtp|Type: boolean|Default: true|
 |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
 
 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
@@ -21475,42 +22696,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.
 
 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|
 |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.
 
 
 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|
 |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.
 
 
 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|
 |dnssec_request_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_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.
 
 sequence.
 
-+----------------------+---------+------------------+--------------+
++------------------------------------------------------------------+
 |dnssec_require_domains|Use: smtp|Type: domain list*|Default: unset|
 |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
 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|
 |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
 
 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
@@ -21524,13 +22745,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.
 
 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|
 |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
 
 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.
 
 . 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.
 
@@ -21557,16 +22778,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.
 
 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|
 |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.
 
 
 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|
 |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
 
 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
@@ -21574,17 +22795,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.
 
 the DNS, but it may also consult other sources of information such as /etc/
 hosts.
 
-+------------------+---------+-------------+--------------+
++---------------------------------------------------------+
 |gnutls_compat_mode|Use: smtp|Type: boolean|Default: unset|
 |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.
 
 
 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|
 |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
 
 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
@@ -21606,9 +22827,9 @@ helo_data = ${lookup dnsdb{ptr=$sending_ip_address}{$value}\
 
 The use of helo_data applies both to sending messages and when doing callouts.
 
 
 The use of helo_data applies both to sending messages and when doing callouts.
 
-+-----+---------+------------------+--------------+
++-------------------------------------------------+
 |hosts|Use: smtp|Type: string list*|Default: unset|
 |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
 
 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
@@ -21624,7 +22845,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
 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.
 
 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.
 
@@ -21639,9 +22860,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.
 
 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|
 |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
 
 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
@@ -21649,63 +22870,77 @@ 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.
 
 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|
 |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.
 
 
 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|
 |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
 
 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
 
 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|
 |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.
 
 
 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|
 |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.
 
 
 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|
 |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
 
 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.
 
 this might be needed.
 
-+--------------+---------+-------------+--------------+
++-------------------------------------------------------+
+|hosts_noproxy_tls|Use: smtp|Type: host list*|Default: *|
++-------------------------------------------------------+
+
+For any host that matches this list, a TLS session which has been started will
+not be passed to a new delivery process for sending another message on the same
+session.
+
+The traditional implementation closes down TLS and re-starts it in the new
+process, on the same open TCP connection, for each successive message sent. If
+permitted by this option a pipe to to the new process is set up instead, and
+the original process maintains the TLS connection and proxies the SMTP
+connection from and to the new process and any subsequents. The new process has
+no access to TLS information, so cannot include it in logging.
+
++-----------------------------------------------------+
 |hosts_override|Use: smtp|Type: boolean|Default: false|
 |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.
 
 
 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|
 |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
 
 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
@@ -21725,9 +22960,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.
 
 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|
 |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
 
 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
@@ -21737,34 +22972,43 @@ 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.
 
 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: *|
 |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.
 
 
 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_dane|Use: smtp|Type: host list*|Default: unset|
++------------------------------------------------------------+
+
+If built with DANE support, Exim will require that a DNSSEC-validated TLSA
+record is present for any host matching the list, and that a DANE-verified TLS
+connection is made. There will be no fallback to in-clear communication. See
+section 42.15.
+
++------------------------------------------------------------+
 |hosts_require_ocsp|Use: smtp|Type: host list*|Default: unset|
 |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.
 
 
 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|
 |hosts_require_tls|Use: smtp|Type: host list*|Default: unset|
-+-----------------+---------+----------------+--------------+
++-----------------------------------------------------------+
 
 Exim will insist on using a TLS session when delivering to any host that
 
 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.
 
 outgoing mail only. To insist on TLS for incoming messages, use an appropriate
 ACL.
 
-+--------------+---------+----------------+--------------+
++--------------------------------------------------------+
 |hosts_try_auth|Use: smtp|Type: host list*|Default: unset|
 |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
 
 This option provides a list of servers to which, provided they announce
 authentication support, Exim will attempt to authenticate as a client when it
@@ -21772,16 +23016,53 @@ connects. If authentication fails, Exim will try to transfer the message
 unauthenticated. See also hosts_require_auth, and chapter 33 for details of
 authentication.
 
 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_dane|Use: smtp|Type: host list*|Default: unset|
++--------------------------------------------------------+
+
+If built with DANE support, Exim will lookup a TLSA record for any host
+matching the list. If found and verified by DNSSEC, a DANE-verified TLS
+connection is made to that host; there will be no fallback to in-clear
+communication. See section 42.15.
+
++------------------------------------------------------------+
+|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. There is no option for
+control of the server side; if the system supports it it is always enabled.
+Note that lengthy operations in the connect ACL, such as DNSBL lookups, will
+still delay the emission of the SMTP banner.
+
++----------------------------------------------------+
+|hosts_try_prdr|Use: smtp|Type: host list*|Default: *|
++----------------------------------------------------+
 
 This option provides a list of servers to which, provided they announce PRDR
 
 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|
 |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
 
 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
@@ -21797,7 +23078,7 @@ $host_address refer to the host to which a connection is about to be made
 during the expansion of the string. Forced expansion failure, or an empty
 string result causes the option to be ignored. Otherwise, after expansion, the
 string must be a list of IP addresses, colon-separated by default, but the
 during the expansion of the string. Forced expansion failure, or an empty
 string result causes the option to be ignored. Otherwise, after expansion, the
 string must be a list of IP addresses, colon-separated by default, but the
-separator can be changed in the usual way. For example:
+separator can be changed in the usual way (6.21). For example:
 
 interface = <; 192.168.123.123 ; 3ffe:ffff:836f::fe86:a061
 
 
 interface = <; 192.168.123.123 ; 3ffe:ffff:836f::fe86:a061
 
@@ -21806,9 +23087,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.
 
 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|
 |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
 
 This option controls the setting of SO_KEEPALIVE on outgoing TCP/IP socket
 connections. When set, it causes the kernel to probe idle connections
@@ -21820,26 +23101,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.
 
 call properly. The keepalive mechanism takes several hours to detect
 unreachable hosts.
 
-+-----------------+---------+-------------+--------------+
++--------------------------------------------------------+
 |lmtp_ignore_quota|Use: smtp|Type: boolean|Default: false|
 |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.
 
 
 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|
 |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.
 
 
 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
 
 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
@@ -21848,9 +23129,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.
 
 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|
 |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
 
 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
@@ -21860,12 +23144,16 @@ variable that contains an outgoing port.
 
 If the value of this option begins with a digit it is taken as a port number;
 otherwise it is looked up using getservbyname(). The default value is normally
 
 If the value of this option begins with a digit it is taken as a port number;
 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.
+"smtp", but if protocol is set to "lmtp" the default is "lmtp" and if protocol
+is set to "smtps" the default is "smtps". If the expansion fails, or if a port
+number cannot be found, delivery is deferred.
+
+Note that at least one Linux distribution has been seen failing to put "smtps"
+in its "/etc/services" file, resulting is such deferrals.
 
 
-+--------+---------+------------+-------------+
++---------------------------------------------+
 |protocol|Use: smtp|Type: string|Default: smtp|
 |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
 
 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
@@ -21873,14 +23161,17 @@ 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.
 
 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
 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.
+outbound SSL-on-connect, instead of using STARTTLS to upgrade.
 
 
-+------------------------+---------+-------------+-------------+
-|retry_include_ip_address|Use: smtp|Type: boolean|Default: true|
-+------------------------+---------+-------------+-------------+
+The Internet standards bodies used to strongly discourage use of this mode, but
+as of RFC 8314 it is perferred over STARTTLS for message submission (as
+distinct from MTA-MTA communication).
+
++---------------------------------------------------------------+
+|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
 
 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
@@ -21891,12 +23182,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
 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|
 |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
 
 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
@@ -21917,9 +23208,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.
 
 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|
 |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
 
 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
@@ -21931,14 +23224,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.
 
 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|
 |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
 
 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
 
 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
@@ -21946,16 +23246,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.
 
 assumed that the same certificate should be used when Exim is operating as a
 client.
 
-+-------+---------+-------------+--------------+
++----------------------------------------------+
 |tls_crl|Use: smtp|Type: string*|Default: unset|
 |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.
 
 
 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|
 |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.
 
 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.
@@ -21964,9 +23264,9 @@ parameter offered by the server is too small, then the TLS handshake will fail.
 
 Only supported when using GnuTLS.
 
 
 Only supported when using GnuTLS.
 
-+--------------+---------+-------------+--------------+
++-----------------------------------------------------+
 |tls_privatekey|Use: smtp|Type: string*|Default: unset|
 |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
 
 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
@@ -21974,37 +23274,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.
 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|
 |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
 
 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.
 
 the order of the ciphers is a preference order.
 
-+-------+---------+-------------+--------------+
++----------------------------------------------+
 |tls_sni|Use: smtp|Type: string*|Default: unset|
 |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.
 
 
 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.
 
 
 Note that for OpenSSL, this feature requires a build of OpenSSL that supports
 TLS extensions.
 
-+---------------------+---------+-------------+-------------+
++-----------------------------------------------------------+
 |tls_tempfail_tryclear|Use: smtp|Type: boolean|Default: true|
 |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
 
 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
@@ -22015,43 +23315,71 @@ 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.
 
 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
 
 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_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: *|
++---------------------------------------------------------------+
 
 
-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.
+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.
 
 
-For back-compatability, if neither tls_verify_hosts nor tls_try_verify_hosts
-are set and certificate verification fails the TLS connection is closed.
+There is no equivalent checking on client certificates.
 
 
-+----------------+---------+----------------------+--------+
-|tls_verify_hosts|Use: smtp|Type: host list* unset|Default:|
-+----------------+---------+----------------------+--------+
++---------------------------------------------------------------+
+|tls_verify_certificates|Use: smtp|Type: string*|Default: system|
++---------------------------------------------------------------+
 
 
-This option gives a list of hosts for which. on encrypted connections,
+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.
+
+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.
+
+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.
+
+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.
 
 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.
 
++---------------------------------------------------------+
+|utf8_downconvert|Use: smtp|Type: integer!!|Default: unset|
++---------------------------------------------------------+
+
+If built with internationalization support, this option controls conversion of
+UTF-8 in message addresses to a-label form. For details see section 59.1.
+
 
 30.5 How the limits for the number of hosts to try are used
 -----------------------------------------------------------
 
 30.5 How the limits for the number of hosts to try are used
 -----------------------------------------------------------
@@ -22241,7 +23569,7 @@ transport time.
 31.3 Testing the rewriting rules that apply on input
 ----------------------------------------------------
 
 31.3 Testing the rewriting rules that apply on input
 ----------------------------------------------------
 
-Exim's input rewriting configuration appears in a part of the run time
+Exim's input rewriting configuration appears in a part of the runtime
 configuration file headed by "begin rewrite". It can be tested by the -brw
 command line option. This takes an address (which can be a full RFC 2822
 address) as its argument. The output is a list of how the address would be
 configuration file headed by "begin rewrite". It can be tested by the -brw
 command line option. This takes an address (which can be a full RFC 2822
 address) as its argument. The output is a list of how the address would be
@@ -22488,8 +23816,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
     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
 
     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
@@ -22559,7 +23887,7 @@ rules that control how often Exim tries to deliver messages that cannot be
 delivered at the first attempt. If there are no retry rules (the section is
 empty or not present), there are no retries. In this situation, temporary
 errors are treated as permanent. The default configuration contains a single,
 delivered at the first attempt. If there are no retry rules (the section is
 empty or not present), there are no retries. In this situation, temporary
 errors are treated as permanent. The default configuration contains a single,
-general-purpose retry rule (see section 7.5). The -brt command line option can
+general-purpose retry rule (see section 7.6). The -brt command line option can
 be used to test which retry rule will be used for a given address, domain and
 error.
 
 be used to test which retry rule will be used for a given address, domain and
 error.
 
@@ -22570,7 +23898,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
 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
 details of the handling of errors during remote deliveries.
 
 Retry processing applies to routing as well as to delivering, except as covered
@@ -22755,6 +24083,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.
 
     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.
 refused_MX
 
     A connection to a host obtained from an MX record was refused.
@@ -22935,7 +24270,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
 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.
 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.
@@ -23024,11 +24359,12 @@ post-cutoff retry time is not used.
 
 If the delivery is remote, there are two possibilities, controlled by the 
 delay_after_cutoff option of the smtp transport. The option is true by default.
 
 If the delivery is remote, there are two possibilities, controlled by the 
 delay_after_cutoff option of the smtp transport. The option is true by default.
-Until the post-cutoff retry time for one of the IP addresses is reached, the
-failing email address is bounced immediately, without a delivery attempt taking
-place. After that time, one new delivery attempt is made to those IP addresses
-that are past their retry times, and if that still fails, the address is
-bounced and new retry times are computed.
+Until the post-cutoff retry time for one of the IP addresses, as set by the 
+retry_data_expire option, is reached, the failing email address is bounced
+immediately, without a delivery attempt taking place. After that time, one new
+delivery attempt is made to those IP addresses that are past their retry times,
+and if that still fails, the address is bounced and new retry times are
+computed.
 
 In other words, when all the hosts for a given email address have been failing
 for a long time, Exim bounces rather then defers until one of the hosts' retry
 
 In other words, when all the hosts for a given email address have been failing
 for a long time, Exim bounces rather then defers until one of the hosts' retry
@@ -23056,12 +24392,12 @@ intermittently available, or when a message has some attribute that prevents
 its delivery when others to the same address get through. In this situation,
 because some messages are successfully delivered, the "retry clock" for the
 host or address keeps getting reset by the successful deliveries, and so
 its delivery when others to the same address get through. In this situation,
 because some messages are successfully delivered, the "retry clock" for the
 host or address keeps getting reset by the successful deliveries, and so
-failing messages remain on the queue for ever because the cutoff time is never
+failing messages remain in the queue for ever because the cutoff time is never
 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.
 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
 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
@@ -23079,7 +24415,7 @@ considered immediately.
 ===============================================================================
 33. SMTP AUTHENTICATION
 
 ===============================================================================
 33. SMTP AUTHENTICATION
 
-The "authenticators" section of Exim's run time configuration is concerned with
+The "authenticators" section of Exim's runtime configuration is concerned with
 SMTP authentication. This facility is an extension to the SMTP protocol,
 described in RFC 2554, which allows a client SMTP host to authenticate itself
 to a server. This is a common way for a server to recognize clients that are
 SMTP authentication. This facility is an extension to the SMTP protocol,
 described in RFC 2554, which allows a client SMTP host to authenticate itself
 to a server. This is a common way for a server to recognize clients that are
@@ -23143,6 +24479,7 @@ AUTH_GSASL=yes
 AUTH_HEIMDAL_GSSAPI=yes
 AUTH_PLAINTEXT=yes
 AUTH_SPA=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
 
 in Local/Makefile, respectively. The first of these supports the CRAM-MD5
 authentication mechanism (RFC 2195), and the second provides an interface to
@@ -23154,10 +24491,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
 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
 
 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
 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
@@ -23211,9 +24549,9 @@ not treat the realm as secure data to be blindly trusted.
 33.1 Generic options for authenticators
 ---------------------------------------
 
 33.1 Generic options for authenticators
 ---------------------------------------
 
-+----------------+-------------------+-------------+--------------+
++-----------------------------------------------------------------+
 |client_condition|Use: authenticators|Type: string*|Default: unset|
 |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
 
 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
@@ -23222,24 +24560,24 @@ by a setting such as:
 
 client_condition = ${if !eq{$tls_out_cipher}{}}
 
 
 client_condition = ${if !eq{$tls_out_cipher}{}}
 
-+-------------+-------------------+-------------+--------------+
++--------------------------------------------------------------+
 |client_set_id|Use: authenticators|Type: string*|Default: unset|
 |client_set_id|Use: authenticators|Type: string*|Default: unset|
-+-------------+-------------------+-------------+--------------+
++--------------------------------------------------------------+
 
 When client authentication succeeds, this condition is expanded; the result is
 
 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|
 |driver|Use: authenticators|Type: string|Default: unset|
-+------+-------------------+------------+--------------+
++------------------------------------------------------+
 
 This option must always be set. It specifies which of the available
 authenticators is to be used.
 
 
 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|
 |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
 
 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
@@ -23247,9 +24585,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.
 
 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|
 |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
 
 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
@@ -23257,9 +24595,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.
 
 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|
 |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.
 
 This option must be set for a plaintext server authenticator, where it is used
 directly to control authentication. See section 34.2 for details.
@@ -23278,9 +24616,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.
 
 result, a temporary error code is returned, with the expanded string as the
 error text.
 
-+------------------+-------------------+-------------+--------------+
++-------------------------------------------------------------------+
 |server_debug_print|Use: authenticators|Type: string*|Default: unset|
 |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
 
 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
@@ -23288,21 +24626,22 @@ 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.
 
 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|
 |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
 messages in the variable $authenticated_id. It is also included in the log
 lines for incoming messages. For example, a user/password authenticator
 configuration might preserve the user name that was used to authenticate, and
 
 When an Exim server successfully authenticates a client, this string is
 expanded using data from the authentication, and preserved for any incoming
 messages in the variable $authenticated_id. It is also included in the log
 lines for incoming messages. For example, a user/password authenticator
 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.
+refer to it subsequently during delivery of the message. On a failing
+authentication the expansion result is instead saved in the
+$authenticated_fail_id variable. If expansion fails, the option is ignored.
 
 
-+--------------------------+-------------------+-------------+--------------+
++---------------------------------------------------------------------------+
 |server_mail_auth_condition|Use: authenticators|Type: string*|Default: unset|
 |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
 
 This option allows a server to discard authenticated sender addresses supplied
 as part of MAIL commands in SMTP connections that are authenticated by the
@@ -23420,6 +24759,9 @@ name) of the authenticator driver that successfully authenticated the client
 from which the message was received. This variable is empty if there was no
 successful authentication.
 
 from which the message was received. This variable is empty if there was no
 successful authentication.
 
+Successful authentication sets up information used by the $authresults
+expansion item.
+
 
 33.4 Testing server authentication
 ----------------------------------
 
 33.4 Testing server authentication
 ----------------------------------
@@ -23502,6 +24844,17 @@ 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.
 
     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 upon and the time
+that the transport runs. For example, with a manualroute router given a host
+name, and with DNS "round-robin" used 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
 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
@@ -23522,7 +24875,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
 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.
 
 plain text, you should not use the same passwords for SMTP connections as you
 do for login accounts.
 
@@ -23532,16 +24885,16 @@ do for login accounts.
 
 When configured as a server, plaintext uses the following options:
 
 
 When configured as a server, plaintext uses the following options:
 
-+----------------+-------------------+-------------+--------------+
++-----------------------------------------------------------------+
 |server_condition|Use: authenticators|Type: string*|Default: unset|
 |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.
 
 
 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|
 |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
 
 The contents of this option, after expansion, must be a colon-separated list of
 prompt strings. If expansion fails, a temporary authentication rejection is
@@ -23575,7 +24928,7 @@ result of a successful expansion is an empty string, "0", "no", or "false",
 authentication fails. If the result of the expansion is "1", "yes", or "true",
 authentication succeeds and the generic server_set_id option is expanded and
 saved in $authenticated_id. For any other result, a temporary error code is
 authentication fails. If the result of the expansion is "1", "yes", or "true",
 authentication succeeds and the generic server_set_id option is expanded and
 saved in $authenticated_id. For any other result, a temporary error code is
-returned, with the expanded string as the error text
+returned, with the expanded string as the error text.
 
 Warning: If you use a lookup in the expansion to find the user's password, be
 sure to make the authentication fail if the user is unknown. There are good and
 
 Warning: If you use a lookup in the expansion to find the user's password, be
 sure to make the authentication fail if the user is unknown. There are good and
@@ -23725,18 +25078,18 @@ Radius, ldapauth, pwcheck, and saslauthd. For details see section 11.7.
 
 The plaintext authenticator has two client options:
 
 
 The plaintext authenticator has two client options:
 
-+----------------------------+--------------+-------------+--------------+
++------------------------------------------------------------------------+
 |client_ignore_invalid_base64|Use: plaintext|Type: boolean|Default: false|
 |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.
 
 
 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|
 |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
 
 The string is a colon-separated list of authentication data strings. Each
 string is independently expanded before being sent to the server. The first
@@ -23798,9 +25151,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:
 
 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|
 |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
 
 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
@@ -23848,7 +25201,7 @@ cyrusless_crammd5:
   driver = cram_md5
   public_name = CRAM-MD5
   server_secret = ${lookup{$auth1:mail.example.org:userPassword}\
   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
 
 
   server_set_id = $auth1
 
 
@@ -23857,16 +25210,16 @@ cyrusless_crammd5:
 
 When used as a client, the cram_md5 authenticator has two options:
 
 
 When used as a client, the cram_md5 authenticator has two options:
 
-+-----------+-------------+-------------+------------------------------+
++----------------------------------------------------------------------+
 |client_name|Use: cram_md5|Type: string*|Default: the primary host name|
 |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.
 
 
 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|
 |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.
 
 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.
@@ -23892,8 +25245,8 @@ fixed_cram:
 ===============================================================================
 36. THE CYRUS_SASL AUTHENTICATOR
 
 ===============================================================================
 36. THE CYRUS_SASL AUTHENTICATOR
 
-The code for this authenticator was provided by Matthew Byng-Maddick of A L
-Digital Ltd (http://www.aldigital.co.uk).
+The code for this authenticator was provided by Matthew Byng-Maddick while at A
+L Digital Ltd.
 
 The cyrus_sasl authenticator provides server support for the Cyrus SASL library
 implementation of the RFC 2222 ("Simple Authentication and Security Layer").
 
 The cyrus_sasl authenticator provides server support for the Cyrus SASL library
 implementation of the RFC 2222 ("Simple Authentication and Security Layer").
@@ -23906,7 +25259,7 @@ Cyrus interface, so if your Cyrus library can do, for example, CRAM-MD5, then
 so can the cyrus_sasl authenticator. By default it uses the public name of the
 driver to determine which mechanism to support.
 
 so can the cyrus_sasl authenticator. By default it uses the public name of the
 driver to determine which mechanism to support.
 
-Where access to some kind of secret file is required, for example in GSSAPI or
+Where access to some kind of secret file is required, for example, in GSSAPI or
 CRAM-MD5, it is worth noting that the authenticator runs as the Exim user, and
 that the Cyrus SASL library has no way of escalating privileges by default. You
 may also find you need to set environment variables, depending on the driver
 CRAM-MD5, it is worth noting that the authenticator runs as the Exim user, and
 that the Cyrus SASL library has no way of escalating privileges by default. You
 may also find you need to set environment variables, depending on the driver
@@ -23937,17 +25290,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.
 
 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|
 |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.
 
 
 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|
 |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
 
 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
@@ -23959,15 +25312,15 @@ sasl:
   server_mech = CRAM-MD5
   server_set_id = $auth1
 
   server_mech = CRAM-MD5
   server_set_id = $auth1
 
-+------------+---------------+-------------+--------------+
++---------------------------------------------------------+
 |server_realm|Use: cyrus_sasl|Type: string*|Default: unset|
 |server_realm|Use: cyrus_sasl|Type: string*|Default: unset|
-+------------+---------------+-------------+--------------+
++---------------------------------------------------------+
 
 This specifies the SASL realm that the server claims to be in.
 
 
 This specifies the SASL realm that the server claims to be in.
 
-+--------------+---------------+------------+---------------+
++-----------------------------------------------------------+
 |server_service|Use: cyrus_sasl|Type: string|Default: "smtp"|
 |server_service|Use: cyrus_sasl|Type: string|Default: "smtp"|
-+--------------+---------------+------------+---------------+
++-----------------------------------------------------------+
 
 This is the SASL service that the server claims to implement.
 
 
 This is the SASL service that the server claims to implement.
 
@@ -23997,18 +25350,16 @@ 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.
 
 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|
 |server_socket|Use: dovecot|Type: string|Default: unset|
-+-------------+------------+------------+--------------+
++------------------------------------------------------+
 
 
-This option must specify the socket that is the interface to Dovecot
+This option must specify the UNIX socket that is the interface to Dovecot
 authentication. The public_name option must specify an authentication mechanism
 that Dovecot is configured to support. You can have several authenticators for
 different mechanisms. For example:
 authentication. The public_name option must specify an authentication mechanism
 that Dovecot is configured to support. You can have several authenticators for
 different mechanisms. For example:
@@ -24044,9 +25395,14 @@ future authentication mechanisms, so no guarantee can be made that any
 particular new authentication mechanism will be supported without code changes
 in Exim.
 
 particular new authentication mechanism will be supported without code changes
 in Exim.
 
-+---------------------+----------+-------------+--------------+
+Exim's gsasl authenticator does not have client-side support at this time; only
+the server-side support is implemented. Patches welcome.
+
++-------------------------------------------------------------+
 |server_channelbinding|Use: gsasl|Type: boolean|Default: false|
 |server_channelbinding|Use: gsasl|Type: boolean|Default: false|
-+---------------------+----------+-------------+--------------+
++-------------------------------------------------------------+
+
+Do not set this true without consulting a cryptographic engineer.
 
 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
 
 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
@@ -24054,29 +25410,33 @@ authentication process if that context differs. Specifically, some TLS
 ciphersuites can provide identifying information about the cryptographic
 context.
 
 ciphersuites can provide identifying information about the cryptographic
 context.
 
-This means that certificate identity and verification becomes a non-issue, as a
-man-in-the-middle attack will cause the correct client and server to see
-different identifiers and authentication will fail.
+This should have meant that certificate identity and verification becomes a
+non-issue, as a man-in-the-middle attack will cause the correct client and
+server to see different identifiers and authentication will fail.
 
 This is currently only supported when using the GnuTLS library. This is only
 usable by mechanisms which support "channel binding"; at time of writing,
 that's the SCRAM family.
 
 This defaults off to ensure smooth upgrade across Exim releases, in case this
 
 This is currently only supported when using the GnuTLS library. This is only
 usable by mechanisms which support "channel binding"; at time of writing,
 that's the SCRAM family.
 
 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.
+option causes some clients to start failing. Some future release of Exim might
+have switched the default to be true.
+
+However, Channel Binding in TLS has proven to be broken in current versions. Do
+not plan to rely upon this feature for security, ever, without consulting with
+a subject matter expert (a cryptographic engineer).
 
 
-+---------------+----------+-------------+------------------+
++-----------------------------------------------------------+
 |server_hostname|Use: gsasl|Type: string*|Default: see below|
 |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.
 
 
 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|
 |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
 
 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
@@ -24088,9 +25448,9 @@ sasl:
   server_mech = CRAM-MD5
   server_set_id = $auth1
 
   server_mech = CRAM-MD5
   server_set_id = $auth1
 
-+---------------+----------+-------------+--------------+
++-------------------------------------------------------+
 |server_password|Use: gsasl|Type: string*|Default: unset|
 |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
 
 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
@@ -24106,30 +25466,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".
 
 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|
 |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.
 
 
 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|
 |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)
 
 
 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|
 |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)
 
 
 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"|
 |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.
 
 This is the SASL service that the server claims to implement. Some mechanisms
 will use this data.
@@ -24185,28 +25545,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.
 
 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|
 |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".
 
 
 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|
 |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.
 
 
 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|
 |server_service|Use: heimdal_gssapi|Type: string*|Default: smtp|
-+--------------+-------------------+-------------+-------------+
++--------------------------------------------------------------+
 
 This option specifies the service identifier used, in conjunction with 
 
 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.
 
 
 keytab.
 
 
@@ -24236,8 +25596,8 @@ For instance, "joe/admin@EXAMPLE.ORG".
 The spa authenticator provides client support for Microsoft's Secure Password
 Authentication mechanism, which is also sometimes known as NTLM (NT LanMan).
 The code for client side of this authenticator was contributed by Marc
 The spa authenticator provides client support for Microsoft's Secure Password
 Authentication mechanism, which is also sometimes known as NTLM (NT LanMan).
 The code for client side of this authenticator was contributed by Marc
-Prud'hommeaux, and much of it is taken from the Samba project (http://
-www.samba.org). The code for the server side was subsequently contributed by
+Prud'hommeaux, and much of it is taken from the Samba project (https://
+www.samba.org/). The code for the server side was subsequently contributed by
 Tom Kistner. The mechanism works as follows:
 
   * After the AUTH command has been accepted, the client sends an SPA
 Tom Kistner. The mechanism works as follows:
 
   * After the AUTH command has been accepted, the client sends an SPA
@@ -24256,9 +25616,9 @@ Encryption is used to protect the password in transit.
 
 The spa authenticator has just one server option:
 
 
 The spa authenticator has just one server option:
 
-+---------------+--------+-------------+--------------+
++-----------------------------------------------------+
 |server_password|Use: spa|Type: string*|Default: unset|
 |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
 
 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
@@ -24282,21 +25642,21 @@ failure causes a temporary error code to be returned.
 
 The spa authenticator has the following client options:
 
 
 The spa authenticator has the following client options:
 
-+-------------+--------+-------------+--------------+
++---------------------------------------------------+
 |client_domain|Use: spa|Type: string*|Default: unset|
 |client_domain|Use: spa|Type: string*|Default: unset|
-+-------------+--------+-------------+--------------+
++---------------------------------------------------+
 
 This option specifies an optional domain for the authentication.
 
 
 This option specifies an optional domain for the authentication.
 
-+---------------+--------+-------------+--------------+
++-----------------------------------------------------+
 |client_password|Use: spa|Type: string*|Default: unset|
 |client_password|Use: spa|Type: string*|Default: unset|
-+---------------+--------+-------------+--------------+
++-----------------------------------------------------+
 
 This option specifies the user's password, and must be set.
 
 
 This option specifies the user's password, and must be set.
 
-+---------------+--------+-------------+--------------+
++-----------------------------------------------------+
 |client_username|Use: spa|Type: string*|Default: unset|
 |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:
 
 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:
@@ -24311,7 +25671,79 @@ 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 and { {eq{$tls_in_certificate_verified}{1}} \
+                                 {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, up to TLS1.2, the client cert is on the wire in-clear, including the
+SAN, The account name is therefore guessable by an opponent. TLS 1.3 protects
+both server and client certificates, and is not vulnerable in this way.
+Likewise, a traditional 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
 
 Support for TLS (Transport Layer Security), formerly known as SSL (Secure
 Sockets Layer), is implemented by making use of the OpenSSL library or the
@@ -24339,19 +25771,29 @@ TLS connections. You need to turn off SMTP scanning for these products in order
 to get TLS to work.
 
 
 to get TLS to work.
 
 
-41.1 Support for the legacy "ssmtp" (aka "smtps") protocol
-----------------------------------------------------------
+42.1 Support for the "submissions" (aka "ssmtp" and "smtps") protocol
+---------------------------------------------------------------------
 
 
-Early implementations of encrypted SMTP used a different TCP port from normal
-SMTP, and expected an encryption negotiation to start immediately, instead of
-waiting for a STARTTLS command from the client using the standard SMTP port.
-The protocol was called "ssmtp" or "smtps", and port 465 was allocated for this
-purpose.
+The history of port numbers for TLS in SMTP is a little messy and has been
+contentious. As of RFC 8314, the common practice of using the historically
+allocated port 465 for "email submission but with TLS immediately upon connect
+instead of using STARTTLS" is officially blessed by the IETF, and recommended
+by them in preference to STARTTLS.
 
 
-This approach was abandoned when encrypted SMTP was standardized, but there are
-still some legacy clients that use it. Exim supports these clients by means of
-the tls_on_connect_ports global option. Its value must be a list of port
-numbers; the most common use is expected to be:
+The name originally assigned to the port was "ssmtp" or "smtps", but as clarity
+emerged over the dual roles of SMTP, for MX delivery and Email Submission,
+nomenclature has shifted. The modern name is now "submissions".
+
+This approach was, for a while, officially abandoned when encrypted SMTP was
+standardized, but many clients kept using it, even as the TCP port number was
+reassigned for other use. Thus you may encounter guidance claiming that you
+shouldn't enable use of this port. In practice, a number of mail-clients have
+only ever supported submissions, not submission with STARTTLS upgrade. Ideally,
+offer both submission (587) and submissions (465) service.
+
+Exim supports TLS-on-connect by means of the tls_on_connect_ports global
+option. Its value must be a list of port numbers; the most common use is
+expected to be:
 
 tls_on_connect_ports = 465
 
 
 tls_on_connect_ports = 465
 
@@ -24362,10 +25804,10 @@ command line option) because tls_on_connect_ports does not add an extra port -
 rather, it specifies different behaviour on a port that is defined elsewhere.
 
 There is also a -tls-on-connect command line option. This overrides 
 rather, it specifies different behaviour on a port that is defined elsewhere.
 
 There is also a -tls-on-connect command line option. This overrides 
-tls_on_connect_ports; it forces the legacy behaviour for all ports.
+tls_on_connect_ports; it forces the TLS-only 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
 ----------------------
 
 The first TLS support in Exim was implemented using OpenSSL. Support for GnuTLS
@@ -24383,8 +25825,9 @@ files and libraries for GnuTLS can be found.
 
 There are some differences in usage when using GnuTLS instead of OpenSSL:
 
 
 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.
 
 
   * The default value for tls_dhparam differs for historical reasons.
 
@@ -24402,20 +25845,23 @@ 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
     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
     be configured in this way, let the Exim Maintainers know and we'll likely
     use it).
 
 
   * 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
     be configured in this way, let the Exim Maintainers know and we'll likely
     use it).
 
+  * With GnuTLS, if an explicit list is used for the tls_privatekey main option
+    main option, it must be ordered to match the tls_certificate list.
+
   * Some other recently added features may only be available in one or the
     other. This should be documented with the feature. If the documentation
     does not explicitly state that the feature is infeasible in the other TLS
     implementation, then patches are welcome.
 
 
   * Some other recently added features may only be available in one or the
     other. This should be documented with the feature. If the documentation
     does not explicitly state that the feature is infeasible in the other TLS
     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
 ---------------------------------
 
 This section only applies if tls_dhparam is set to "historic" or to an explicit
@@ -24498,15 +25944,18 @@ 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.
 
 
 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
 suites before the cipher negotiation takes place. This specifies which ciphers
 ------------------------------------------
 
 There is a function in the OpenSSL library that can be passed a list of cipher
 suites before the cipher negotiation takes place. This specifies which ciphers
-are acceptable. The list is colon separated and may contain names like
-DES-CBC3-SHA. Exim passes the expanded value of tls_require_ciphers directly to
-this function call. Many systems will install the OpenSSL manual-pages, so you
-may have ciphers(1) available to you. The following quotation from the OpenSSL
+
+are acceptable for TLS versions prior to 1.3.
+
+The list is colon separated and may contain names like DES-CBC3-SHA. Exim
+passes the expanded value of tls_require_ciphers directly to this function
+call. Many systems will install the OpenSSL manual-pages, so you may have 
+ciphers(1) available to you. The following quotation from the OpenSSL
 documentation specifies what forms of item are allowed in the cipher string:
 
   * It can consist of a single cipher suite such as RC4-SHA.
 documentation specifies what forms of item are allowed in the cipher string:
 
   * It can consist of a single cipher suite such as RC4-SHA.
@@ -24554,15 +26003,28 @@ tls_require_ciphers = ${if =={$received_port}{25}\
                            {DEFAULT}\
                            {HIGH:!MD5:!SHA1}}
 
                            {DEFAULT}\
                            {HIGH:!MD5:!SHA1}}
 
+This example will prefer ECDSA-authenticated ciphers over RSA ones:
+
+tls_require_ciphers = ECDSA:RSA:!COMPLEMENTOFDEFAULT
 
 
-41.5 Requiring specific ciphers or other parameters in GnuTLS
+For TLS version 1.3 the control available is less fine-grained and Exim does
+not provide access to it at present. The value of the tls_require_ciphers
+option is ignored when TLS version 1.3 is negotiated.
+
+As of writing the library default cipher suite list for TLSv1.3 is
+
+TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256
+
+
+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 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
 
 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
@@ -24572,10 +26034,17 @@ given to the GnuTLS library, so that Exim does not need to be aware of future
 feature enhancements of GnuTLS.
 
 Documentation of the strings accepted may be found in the GnuTLS manual, under
 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". This is online as https://www.gnutls.org/manual/html_node/
 Priority-Strings.html, but beware that this relates to GnuTLS 3, which may be
 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 https://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 "
 
 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 "
@@ -24593,15 +26062,17 @@ tls_require_ciphers = ${if =={$received_port}{25}\
                            {SECURE128}}
 
 
                            {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
 the STARTTLS command to client hosts that match tls_advertise_hosts, but not to
 ------------------------------------------
 
 When Exim has been built with TLS support, it advertises the availability of
 the STARTTLS command to client hosts that match tls_advertise_hosts, but not to
-any others. The default value of this option is unset, which means that
-STARTTLS is not advertised at all. This default is chosen because you need to
-set some other options in order to make TLS available, and also it is sensible
-for systems that want to use TLS only as a client.
+any others. The default value of this option is *, which means that STARTTLS is
+always advertised. Set it to blank to never advertise; this is reasonable for
+systems that want to use TLS only as a client.
+
+If STARTTLS is to be used you need to set some other options in order to make
+TLS available.
 
 If a client issues a STARTTLS command and there is some configuration problem
 in the server, the command is rejected with a 454 error. If the client persists
 
 If a client issues a STARTTLS command and there is some configuration problem
 in the server, the command is rejected with a 454 error. If the client persists
@@ -24612,13 +26083,17 @@ 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.
 
 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.
 
 
-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,
+Further protection requires some further configuration at the server end.
+
+To make TLS work you need to set, in the server,
 
 tls_certificate = /some/file/name
 tls_privatekey = /some/file/name
 
 tls_certificate = /some/file/name
 tls_privatekey = /some/file/name
@@ -24626,17 +26101,24 @@ 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
 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.
+
+For dual-stack (eg. RSA and ECDSA) configurations, these options can be
+colon-separated lists of file paths. Ciphers using given authentication
+algorithms require the presence of a suitable certificate to supply the
+public-key. The server selects among the certificates to present to the client
+depending on the selected cipher, hence the priority ordering for ciphers will
+affect which certificate is used.
 
 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
 
 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
 
 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
@@ -24689,7 +26171,7 @@ For outgoing SMTP deliveries, $tls_out_cipher is used and logged (again
 depending on the tls_cipher log selector).
 
 
 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
 -------------------------------------------------
 
 If you want an Exim server to request a certificate when negotiating a TLS
@@ -24698,8 +26180,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
 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.
+trust-anchors or 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,
 
 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,
@@ -24711,6 +26194,9 @@ openssl x509 -hash -noout -in /cert/file
 
 where /cert/file contains a single certificate.
 
 
 where /cert/file contains a single certificate.
 
+There is no checking of names of the client against the certificate Subject
+Name or Subject Alternate Names.
+
 The difference between tls_verify_hosts and tls_try_verify_hosts is what
 happens if the client does not supply a certificate, or if the certificate does
 not match any of the certificates in the collection named by 
 The difference between tls_verify_hosts and tls_try_verify_hosts is what
 happens if the client does not supply a certificate, or if the certificate does
 not match any of the certificates in the collection named by 
@@ -24733,7 +26219,7 @@ to change the Received: header. When no certificate is supplied, $tls_in_peerdn
 is empty.
 
 
 is empty.
 
 
-41.8 Revoked certificates
+42.8 Revoked certificates
 -------------------------
 
 Certificate issuing authorities issue Certificate Revocation Lists (CRLs) when
 -------------------------
 
 Certificate issuing authorities issue Certificate Revocation Lists (CRLs) when
@@ -24742,7 +26228,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
 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
 
 The way with most moving parts at query time is Online Certificate Status
 Protocol (OCSP), where the client verifies the certificate against an OCSP
@@ -24765,7 +26251,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
 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
 
 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
@@ -24802,7 +26288,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.
 
 
   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
 ------------------------------------------
 
 The tls_cipher and tls_peerdn log selectors apply to outgoing SMTP deliveries
@@ -24839,18 +26325,22 @@ 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 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. 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
 succeed respectively.
 
 
 The tls_verify_hosts and tls_try_verify_hosts options restrict certificate
 verification to the listed servers. Verification either must or need not
 succeed respectively.
 
+The tls_verify_cert_hostnames option lists hosts for which additional checks
+are made: that the host name (the one in the DNS A record) is valid for the
+certificate. The option defaults to always checking.
+
 The smtp transport has two OCSP-related options: hosts_require_ocsp; a
 host-list for which a Certificate Status is requested and required for the
 connection to proceed. The default value is empty. hosts_request_ocsp; a
 The smtp transport has two OCSP-related options: hosts_require_ocsp; a
 host-list for which a Certificate Status is requested and required for the
 connection to proceed. The default value is empty. hosts_request_ocsp; a
@@ -24883,7 +26373,7 @@ received.) If STARTTLS is subsequently successfully obeyed, these variables are
 set to the relevant values for the outgoing connection.
 
 
 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
 ---------------------------------------
 
 With TLS1.0 or above, there is an extension mechanism by which extra
@@ -24894,7 +26384,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.
 
 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
 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
@@ -24929,17 +26419,19 @@ session handshake, to permit alternative values to be chosen:
 
   * tls_verify_certificates
 
 
   * 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
 
 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.
 
 
 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.
 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.
@@ -24949,7 +26441,7 @@ When Exim is built against GnuTLS, SNI support is available as of GnuTLS
 built, then you have SNI support).
 
 
 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
 ---------------------------------------------------------------
 
 Exim sends multiple messages down the same TCP/IP connection by starting up an
@@ -24957,10 +26449,17 @@ entirely new delivery process for each message, passing the socket from one
 process to the next. This implementation does not fit well with the use of TLS,
 because there is quite a lot of state information associated with a TLS
 connection, not just a socket identification. Passing all the state information
 process to the next. This implementation does not fit well with the use of TLS,
 because there is quite a lot of state information associated with a TLS
 connection, not just a socket identification. Passing all the state information
-to a new process is not feasible. Consequently, Exim shuts down an existing TLS
-session before passing the socket to a new process. The new process may then
-try to start a new TLS session, and if successful, may try to re-authenticate
-if AUTH is in use, before sending the next message.
+to a new process is not feasible. Consequently, for sending using TLS Exim
+starts an additional proxy process for handling the encryption, piping the
+unencrypted data stream from and to the delivery processes.
+
+An older mode of operation can be enabled on a per-host basis by the 
+hosts_noproxy_tls option on the smtp transport. If the host matches this list
+the proxy process described above is not used; instead Exim shuts down an
+existing TLS session being run by the delivery process before passing the
+socket to a new process. The new process may then try to start a new TLS
+session, and if successful, may try to re-authenticate if AUTH is in use,
+before sending the next message.
 
 The RFC is not clear as to whether or not an SMTP session continues in clear
 after TLS has been shut down, or whether TLS may be restarted again later, as
 
 The RFC is not clear as to whether or not an SMTP session continues in clear
 after TLS has been shut down, or whether TLS may be restarted again later, as
@@ -24982,26 +26481,32 @@ transport to match those hosts for which Exim should not pass connections to
 new processes if TLS has been used.
 
 
 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
 -------------------------------
 
 In order to understand fully how TLS works, you need to know about
-certificates, certificate signing, and certificate authorities. This is not the
-place to give a tutorial, especially as I do not know very much about it
-myself. Some helpful introduction can be found in the FAQ for the SSL addition
-to Apache, currently at
+certificates, certificate signing, and certificate authorities. This is a large
+topic and an introductory guide is unsuitable for the Exim reference manual, so
+instead we provide pointers to existing documentation.
+
+The Apache web-server was for a long time the canonical guide, so their
+documentation is a good place to start; their SSL module's Introduction
+document is currently at
+
+https://httpd.apache.org/docs/current/ssl/ssl_intro.html
 
 
-http://www.modssl.org/docs/2.7/ssl_faq.html#ToC24
+and their FAQ is at
 
 
-Other parts of the modssl documentation are also helpful, and have links to
-further files. Eric Rescorla's book, SSL and TLS, published by Addison-Wesley
-(ISBN 0-201-61598-3), contains both introductory and more in-depth
-descriptions. Some sample programs taken from the book are available from
+https://httpd.apache.org/docs/current/ssl/ssl_faq.html
 
 
-http://www.rtfm.com/openssl-examples/
+Eric Rescorla's book, SSL and TLS, published by Addison-Wesley (ISBN
+0-201-61598-3) in 2001, contains both introductory and more in-depth
+descriptions. More recently Ivan Risti?'s book Bulletproof SSL and TLS,
+published by Feisty Duck (ISBN 978-1907117046) in 2013 is good. Ivan is the
+author of the popular TLS testing tools at https://www.ssllabs.com/.
 
 
 
 
-41.13 Certificate chains
+42.13 Certificate chains
 ------------------------
 
 The file named by tls_certificate may contain more than one certificate. This
 ------------------------
 
 The file named by tls_certificate may contain more than one certificate. This
@@ -25023,7 +26528,7 @@ increasingly clients will not accept such certificates. The error diagnostics
 in such a case can be frustratingly vague.
 
 
 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
 ------------------------------
 
 You can create a self-signed certificate using the req command provided with
@@ -25062,14 +26567,204 @@ that self-signed certificate.
 
 For information on creating self-signed CA certificates and using them to sign
 user certificates, see the General implementation overview chapter of the
 
 For information on creating self-signed CA certificates and using them to sign
 user certificates, see the General implementation overview chapter of the
-Open-source PKI book, available online at http://ospkibook.sourceforge.net/.
+Open-source PKI book, available online at https://sourceforge.net/projects/
+ospkibook/.
+
+
+42.15 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 is anchored by one of the TLSA records.
+
+There are no changes to Exim specific to server-side operation of DANE. Support
+for client-side operation of DANE can be included at compile time by defining
+SUPPORT_DANE=yes in Local/Makefile. If it has been included, the macro
+"_HAVE_DANE" will be defined.
+
+The TLSA record for the server may have "certificate usage" of DANE-TA(2) or
+DANE-EE(3). These are the "Trust Anchor" and "End Entity" variants. The latter
+specifies the End Entity directly, i.e. the certificate involved is that of the
+server (and if only DANE-EE is used then it 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 (with certain attributes)
+which is used to sign server certificates, but running one securely does
+require careful arrangement. With DANE-TA, as implemented in Exim and commonly
+in other MTAs, the server TLS handshake must transmit the entire certificate
+chain from CA to server-certificate. 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. DANE-TA and DANE-EE can both be used
+together.
+
+Our recommendation is to use DANE with a certificate from a public CA, because
+this enables a variety of strategies for remote clients to verify your
+certificate. You can then publish information both via DANE and another
+technology, "MTA-STS", described below.
+
+When you use DANE-TA to publish trust anchor information, you ask entities
+outside your administrative control to trust the Certificate Authority for
+connections to you. If using a private CA then you should expect others to
+still apply the technical criteria they'd use for a public CA to your
+certificates. In particular, you should probably try to follow current best
+practices for CA operation around hash algorithms and key sizes. Do not expect
+other organizations to lower their security expectations just because a
+particular profile might be reasonable for your own internal use.
+
+When this text was last updated, this in practice means to avoid use of SHA-1
+and MD5; if using RSA to use key sizes of at least 2048 bits (and no larger
+than 4096, for interoperability); to use keyUsage fields correctly; to use
+random serial numbers. The list of requirements is subject to change as best
+practices evolve. If you're not already using a private CA, or it doesn't meet
+these requirements, then we encourage you to avoid all these issues and use a
+public CA such as Let's Encrypt instead.
+
+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 Certificate issued by the CA published in the DANE-TA model should be
+issued using a strong hash algorithm. Exim, and importantly various other MTAs
+sending to you, will not re-enable hash algorithms which have been disabled by
+default in TLS libraries. This means no MD5 and no SHA-1. SHA2-256 is the
+minimum for reliable interoperability (and probably the maximum too, in 2018).
+
+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 three new smtp transport options, hosts_try_dane
+, hosts_require_dane and dane_require_tls_ciphers. The require variant 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 usable, then the TLS cipher list configuration prefers
+to use the option dane_require_tls_ciphers and falls back to 
+tls_require_ciphers only if that is unset. This lets you configure "decent
+crypto" for DANE and "better than nothing crypto" as the default. Note though
+that while GnuTLS lets the string control which versions of TLS/SSL will be
+negotiated, OpenSSL does not and you're limited to ciphersuite constraints.
+
+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 the dnssec_request_domains must be active 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
+events; see 60), and a new variable $tls_out_tlsa_usage (detailed above).
+
+An event (see 60) of type "dane:fail" will be raised on failures to achieve
+DANE-verified connection, if one was either requested and offered, or required.
+This is intended to support TLS-reporting as defined in https://tools.ietf.org/
+html/draft-ietf-uta-smtp-tlsrpt-17. The $event_data will be one of the Result
+Types defined in Section 4.3 of that document.
+
+Under GnuTLS, DANE is only supported from version 3.0.0 onwards.
+
+DANE is specified in published RFCs and decouples certificate authority trust
+selection from a "race to the bottom" of "you must trust everything for mail to
+get through". There is an alternative technology called MTA-STS, which instead
+publishes MX trust anchor information on an HTTPS website. At the time this
+text was last updated, MTA-STS was still a draft, not yet an RFC. Exim has no
+support for MTA-STS as a client, but Exim mail server operators can choose to
+publish information describing their TLS configuration using MTA-STS to let
+those clients who do use that protocol derive trust information.
+
+The MTA-STS design requires a certificate from a public Certificate Authority
+which is recognized by clients sending to you. That selection of which CAs are
+trusted by others is outside your control.
+
+The most interoperable course of action is probably to use Let's Encrypt, with
+automated certificate renewal; to publish the anchor information in
+DNSSEC-secured DNS via TLSA records for DANE clients (such as Exim and Postfix)
+and to publish anchor information for MTA-STS as well. This is what is done for
+the exim.org domain itself (with caveats around occasionally broken MTA-STS
+because of incompatible specification changes prior to reaching RFC status).
 
 
 
 ===============================================================================
 
 
 
 ===============================================================================
-42. ACCESS CONTROL LISTS
+43. ACCESS CONTROL LISTS
 
 
-Access Control Lists (ACLs) are defined in a separate section of the run time
+Access Control Lists (ACLs) are defined in a separate section of the runtime
 configuration file, headed by "begin acl". Each ACL definition starts with a
 name, terminated by a colon. Here is a complete ACL section that contains just
 one very small ACL:
 configuration file, headed by "begin acl". Each ACL definition starts with a
 name, terminated by a colon. Here is a complete ACL section that contains just
 one very small ACL:
@@ -25090,16 +26785,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.
 
 
 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
 -----------------
 
 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
 ----------------------------------
 
 In order to cause an ACL to be used, you have to name it in one of the relevant
@@ -25112,6 +26805,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_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
     acl_smtp_etrn      ACL for ETRN
     acl_smtp_expn      ACL for EXPN
     acl_smtp_helo      ACL for HELO or EHLO
@@ -25138,7 +26832,7 @@ the message. It is therefore recommended that you do as much testing as
 possible at RCPT time.
 
 
 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
 ----------------------
 
 The non-SMTP ACLs apply to all non-interactive incoming messages, that is, they
@@ -25165,14 +26859,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
 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.
 
 
 
 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
 -------------------------
 
 The ACL test specified by acl_smtp_connect happens at the start of an SMTP
@@ -25183,7 +26877,7 @@ message override the banner message that is otherwise specified by the
 smtp_banner option.
 
 
 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
 ----------------------
 
 The ACL test specified by acl_smtp_helo happens when the client issues an EHLO
@@ -25193,13 +26887,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.
 
 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.
 
 
 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,
 ------------------
 
 Two ACLs are associated with the DATA command, because it is two-stage command,
@@ -25218,6 +26916,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.
 
 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
 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
@@ -25228,7 +26930,7 @@ The acl_smtp_data ACL is run after the acl_smtp_data_prdr, the acl_smtp_dkim
 and the acl_smtp_mime ACLs.
 
 
 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
 ----------------------
 
 The acl_smtp_dkim ACL is available only when Exim is compiled with DKIM support
@@ -25240,19 +26942,19 @@ otherwise specified, the default action is to accept.
 
 This ACL is evaluated before acl_smtp_mime and acl_smtp_data.
 
 
 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 section 57.1.
 
 
 
 
-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
 ----------------------
 
 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.
 
 
 
 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
 ----------------------
 
 The acl_smtp_data_prdr ACL is available only when Exim is compiled with PRDR
@@ -25260,19 +26962,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.
 
 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
 
 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.
 
 See also the prdr_enable global option and the hosts_try_prdr smtp transport
 option.
@@ -25282,13 +26984,13 @@ is not defined, processing completes as if the feature was not requested by the
 client.
 
 
 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
 ------------------
 
 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
 
 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
@@ -25309,7 +27011,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.
 
 
 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
 ----------------------
 
 The not-QUIT ACL, specified by acl_smtp_notquit, is run in most cases when an
@@ -25344,7 +27046,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.
 
 
 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
 ---------------------------
 
 The value of an acl_smtp_xxx option is expanded before use, so you can use
@@ -25354,16 +27056,16 @@ acl_smtp_rcpt = ${if ={25}{$interface_port} \
                      {acl_check_rcpt} {acl_check_rcpt_submit} }
 
 In the default configuration file there are some example settings for providing
                      {acl_check_rcpt} {acl_check_rcpt_submit} }
 
 In the default configuration file there are some example settings for providing
-an RFC 4409 message submission service on port 587 and a non-standard "smtps"
-service on port 465. You can use a string expansion like this to choose an ACL
-for MUAs on these ports which is more appropriate for this purpose than the
-default ACL on port 25.
+an RFC 4409 message "submission" service on port 587 and an RFC 8314
+"submissions" service on port 465. You can use a string expansion like this to
+choose an ACL for MUAs on these ports which is more appropriate for this
+purpose than the default ACL on port 25.
 
 The expanded string does not have to be the name of an ACL in the configuration
 file; there are other possibilities. Having expanded the string, Exim searches
 for an ACL as follows:
 
 
 The expanded string does not have to be the name of an ACL in the configuration
 file; there are other possibilities. Having expanded the string, Exim searches
 for an ACL as follows:
 
-  * If the string begins with a slash, Exim uses it as a file name, and reads
+  * If the string begins with a slash, Exim uses it as a filename, and reads
     its contents as an ACL. The lines are processed in the same way as lines in
     the Exim configuration file. In particular, continuation lines are
     supported, blank lines are ignored, as are lines whose first non-whitespace
     its contents as an ACL. The lines are processed in the same way as lines in
     the Exim configuration file. In particular, continuation lines are
     supported, blank lines are ignored, as are lines whose first non-whitespace
@@ -25395,11 +27097,11 @@ for an ACL as follows:
     file.
 
 
     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
 ----------------------
 
 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
 "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
@@ -25422,11 +27124,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.
 
 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.
 
 
 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
 -----------------------
 
 The default actions when any of the acl_xxx options are unset are not all the
@@ -25448,7 +27153,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.
 
 
 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
 ---------------------------
 
 When a MAIL or RCPT ACL, or either of the DATA ACLs, is running, the variables
@@ -25475,7 +27180,7 @@ $rcpt_count contains the total number of RCPT commands, and $recipients_count
 contains the total number of accepted recipients.
 
 
 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,
 -------------------------------
 
 When an ACL is being run for AUTH, EHLO, ETRN, EXPN, HELO, STARTTLS, or VRFY,
@@ -25498,7 +27203,7 @@ encrypted. You can use the generic server_advertise_condition authenticator
 option to do this.)
 
 
 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
 ----------------------
 
 An individual ACL consists of a number of statements. Each statement starts
@@ -25512,7 +27217,7 @@ provides a means of specifying an "and" conjunction between conditions. For
 example:
 
 deny  dnslists = list1.example
 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
 
 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
@@ -25521,7 +27226,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.
 
 
 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:
 ---------------
 
 The ACL verbs are as follows:
@@ -25534,8 +27239,8 @@ The ACL verbs are as follows:
     RCPT command:
 
     accept domains = +local_domains
     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,
 
     If the recipient domain does not match the domains condition, control
     passes to the next statement. If it does match, the recipient is verified,
@@ -25614,7 +27319,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
     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
 
   * 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
@@ -25626,7 +27331,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
     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.
 
     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.
@@ -25652,7 +27357,7 @@ continue it onto several physical lines by the usual backslash continuation
 mechanism. It is conventional to align the conditions vertically.
 
 
 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
 -------------------
 
 There are some special variables that can be set during ACL processing. They
@@ -25694,7 +27399,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.
 
 
 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:
 ---------------------------------------
 
 An exclamation mark preceding a condition negates its result. For example:
@@ -25767,7 +27472,7 @@ The "deny" result does not happen until the end of the statement is reached, by
 which time Exim has set up the message.
 
 
 which time Exim has set up the message.
 
 
-42.21 ACL modifiers
+43.21 ACL modifiers
 -------------------
 
 The ACL modifiers are as follows:
 -------------------
 
 The ACL modifiers are as follows:
@@ -25776,7 +27481,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
 
     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>
 
 
 continue = <text>
 
@@ -25802,10 +27507,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
     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:
 
     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
 
             accept  ...some conditions
                     control = queue_only
@@ -25813,7 +27518,7 @@ control = <text>
         In this case, the control is applied when this statement yields
         "accept", in other words, when the conditions are all true.
 
         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
 
             accept  ...some conditions...
                     control = queue_only
@@ -25824,7 +27529,7 @@ control = <text>
         conditions is false. In this case, some subsequent statement must yield
         "accept" for the control to be relevant.
 
         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...
         about accepting or denying to a subsequent verb. For example:
 
             warn    ...some conditions...
@@ -25835,7 +27540,7 @@ control = <text>
         , so it does not add anything to the message and does not write a log
         entry.
 
         , 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
         require verb. For example:
 
             require  control = no_multiline_responses
@@ -26016,6 +27721,9 @@ message = <text>
     processed anyway. If the message contains newlines, this gives rise to a
     multi-line SMTP response.
 
     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
     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
@@ -26031,15 +27739,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.
 
     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
 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>
 
 
 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>
     ).
 
 udpsend = <parameters>
@@ -26056,7 +27773,7 @@ udpsend = <parameters>
                  $tod_zulu $sender_host_address
 
 
                  $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:
 ---------------------------------
 
 The control modifier supports the following settings:
@@ -26110,29 +27827,53 @@ 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.
 
     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
 
     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
 
     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.
+    (subject to the above) and transports. The Received-By: header is generated
+    as soon as the body reception starts, rather than the traditional time
+    after the full message is received; this will affect the timestamp.
+
+    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
 
     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
 
     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
 
     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
@@ -26141,21 +27882,24 @@ control = cutthrough_delivery
 control = debug/<options>
 
     This control turns on debug logging, almost as though Exim had been invoked
 control = debug/<options>
 
     This control turns on debug logging, almost as though Exim had been invoked
-    with "-d", with the output going to a new logfile, by default called 
-    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):
+    with "-d", with the output going to a new logfile in the usual logs
+    directory, by default called 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. Logging started this way 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
           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
 
 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 section 57.1.
 
 control = dscp/<value>
 
 
 control = dscp/<value>
 
@@ -26270,11 +28014,11 @@ control = no_multiline_responses
     after all only a sop to broken clients, is implemented by doing two very
     easy things:
 
     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.
 
         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
         line is output.
 
     The setting of the switch can, of course, be made conditional on the
@@ -26308,8 +28052,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).
 
     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.
     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.
@@ -26320,12 +28064,12 @@ control = suppress_local_fixups
     complement of "control = submission". It disables the fixups that are
     normally applied to locally-submitted messages. Specifically:
 
     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).
 
         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
 
     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
@@ -26336,8 +28080,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.
 
     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:
 --------------------------------------
 
 All four possibilities for message fixups can be specified:
@@ -26352,7 +28101,7 @@ All four possibilities for message fixups can be specified:
   * Remotely submitted, fixups applied: use "control = submission".
 
 
   * 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
 ---------------------------------
 
 The add_header modifier can be used to add one or more extra header lines to an
@@ -26369,7 +28118,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
 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
 
 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
@@ -26395,7 +28144,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
 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.
 
 
 The list of headers yet to be added is given by the $headers_added variable.
 
@@ -26445,7 +28194,7 @@ an ACL. It does NOT work for header lines that are added in a system filter or
 in a router or transport.
 
 
 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
 -----------------------------------
 
 The remove_header modifier can be used to remove one or more header lines from
@@ -26461,8 +28210,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.
 
 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
 
 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
@@ -26475,10 +28224,11 @@ warn   hosts           = +internal_hosts
 warn   message         = Remove internal headers
        remove_header   = $acl_c_ihdrs
 
 warn   message         = Remove internal headers
        remove_header   = $acl_c_ihdrs
 
-Removed header lines are accumulated during the MAIL, RCPT, and predata ACLs.
-They are removed from the message before processing the DATA and MIME ACLs.
-There is no harm in attempting to remove the same header twice nor is removing
-a non-existent header. Further header lines to be removed may be accumulated
+Header names for removal are accumulated during the MAIL, RCPT, and predata
+ACLs. Matching header lines are removed from the message before processing the
+DATA and MIME ACLs. If multiple header lines match, all are removed. There is
+no harm in attempting to remove the same header twice nor in removing a
+non-existent header. Further header lines to be removed may be accumulated
 during the DATA and MIME ACLs, after which they are removed from the message,
 if present. In the case of non-SMTP messages, headers to be removed are
 accumulated during the non-SMTP ACLs, and are removed from the message after
 during the DATA and MIME ACLs, after which they are removed from the message,
 if present. In the case of non-SMTP messages, headers to be removed are
 accumulated during the non-SMTP ACLs, and are removed from the message after
@@ -26492,7 +28242,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,
 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:
 
 The remove_header modifier acts immediately as it is encountered during the
 processing of an ACL. Notice the difference between these two cases:
@@ -26514,13 +28264,13 @@ during ACL processing. It does NOT remove header lines that are added in a
 system filter or in a router or transport.
 
 
 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
 --------------------
 
 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
 
 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
@@ -26544,7 +28294,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
     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 
 
     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 
@@ -26582,12 +28333,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
     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>
 
 
 dnslists = <list of domain names and other data>
 
@@ -26595,7 +28341,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
     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>
 
 
 domains = <domain list>
 
@@ -26668,19 +28414,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
 
     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
 
 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
 
 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>
 
 
 recipients = <address list>
 
@@ -26692,7 +28438,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
     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>
 
 
 sender_domains = <domain list>
 
@@ -26721,19 +28467,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
 
     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 
 
 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
 
 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
 
 
 verify = header_names_ascii
 
@@ -26761,7 +28507,7 @@ verify = header_sender/<options>
     command.
 
     Details of address verification and the options are given later, starting
     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:
 
     this condition with the senders condition to restrict it to bounce messages
     only:
 
@@ -26774,10 +28520,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
     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
 
     Note that this condition is a syntax check only. However, a common spamming
     ploy used to be to send syntactically invalid headers such as
@@ -26815,14 +28562,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,
 
     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.
 
     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
 
     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
@@ -26831,6 +28578,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.
 
     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.
 
     If this condition is used for a locally generated message (that is, when
     there is no client host involved), it always succeeds.
 
@@ -26848,7 +28598,7 @@ verify = sender/<options>
     you want to preserve the value for longer, you can save it in an ACL
     variable.
 
     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.
 
     caches the result of sender verification, to avoid doing it more than once
     per message.
 
@@ -26857,8 +28607,14 @@ verify = sender=<address>/<options>
     This is a variation of the previous option, in which a modified address is
     verified as a sender.
 
     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
 ---------------------
 
 In its simplest form, the dnslists condition tests whether the calling host is
@@ -26909,13 +28665,18 @@ deny  dnslists = blackholes.mail-abuse.org
 warn  message  = X-Warn: sending host is on dialups list
       dnslists = dialups.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).
 
 
+There are a number of DNS lists to choose from, some commercial, some free, or
+free for small deployments. An overview can be found at https://
+en.wikipedia.org/wiki/Comparison_of_DNS_blacklists.
 
 
-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
 -----------------------------------------------------
 
 By default, the IP address that is used in a DNS list lookup is the IP address
@@ -26927,14 +28688,14 @@ 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
 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
 -------------------------------------
 
 There are some lists that are keyed on domain names rather than inverted IP
-addresses (see for example the domain based zones link at http://
+addresses (see, e.g., the domain based zones link at http://
 www.rfc-ignorant.org/). No reversing of components is used with these lists.
 You can change the name that is looked up in a DNS list by listing it after the
 domain name, introduced by a slash. For example,
 www.rfc-ignorant.org/). No reversing of components is used with these lists.
 You can change the name that is looked up in a DNS list by listing it after the
 domain name, introduced by a slash. For example,
@@ -26959,7 +28720,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.
 
 
 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
 -------------------------------------------
 
 The syntax described above for looking up explicitly-defined values (either
@@ -26987,7 +28748,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
 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
 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
@@ -27016,16 +28777,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:
 
 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
 
 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
 --------------------------------
 
 DNS lists are constructed using address records in the DNS. The original RBL
@@ -27041,12 +28802,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
 
 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.
 
 
 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
 ----------------------------------
 
 When an entry is found in a DNS list, the variable $dnslist_domain contains the
@@ -27060,7 +28821,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.
 
 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|...
 a dnslists lookup like this:
 
 deny dnslists = spamhaus.example/<|192.168.1.2|192.168.6.7|...
@@ -27072,7 +28833,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
 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
 
 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
@@ -27084,7 +28845,7 @@ deny    hosts = !+local_networks
         dnslists = rbl-plus.mail-abuse.example
 
 
         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
 --------------------------------------------------
 
 You can add an equals sign and an IP address after a dnslists domain name in
@@ -27095,7 +28856,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
 
 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
 records are handled.
 
 More than one IP address may be given for checking, using a comma as a
@@ -27129,7 +28890,7 @@ matches if the final component of the address is an odd number or two times an
 odd number.
 
 
 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.
 -------------------------------------
 
 You can supply a negative list of IP addresses as part of a dnslists condition.
@@ -27175,7 +28936,7 @@ deny  dnslists = relays.ordb.org
 which is less clear, and harder to maintain.
 
 
 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,
 ---------------------------------------------------
 
 A DNS lookup for a dnslists condition may return more than one DNS record,
@@ -27237,7 +28998,7 @@ When the DNS lookup yields only a single IP address, there is no difference
 between "=" and "==" and between "&" and "=&".
 
 
 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,
 ------------------------------------------------
 
 When the facility for restricting the matching IP values in a DNS list is used,
@@ -27257,7 +29018,7 @@ TXT record. As a byproduct of this, there is also a check that the IP being
 tested is indeed on the first list. The first domain is the one that is put in
 $dnslist_domain. For example:
 
 tested is indeed on the first list. The first domain is the one that is put in
 $dnslist_domain. For example:
 
-reject message  = \
+deny message  = \
          rejected because $sender_host_address is blacklisted \
          at $dnslist_domain\n$dnslist_text
        dnslists = \
          rejected because $sender_host_address is blacklisted \
          at $dnslist_domain\n$dnslist_text
        dnslists = \
@@ -27275,7 +29036,7 @@ If you are interested in more than one merged list, the same list must be given
 several times, but because the results of the DNS lookups are cached, the DNS
 calls themselves are not repeated. For example:
 
 several times, but because the results of the DNS lookups are cached, the DNS
 calls themselves are not repeated. For example:
 
-reject dnslists = \
+deny dnslists = \
          http.dnsbl.sorbs.net,dnsbl.sorbs.net=127.0.0.2 : \
          socks.dnsbl.sorbs.net,dnsbl.sorbs.net=127.0.0.3 : \
          misc.dnsbl.sorbs.net,dnsbl.sorbs.net=127.0.0.4 : \
          http.dnsbl.sorbs.net,dnsbl.sorbs.net=127.0.0.2 : \
          socks.dnsbl.sorbs.net,dnsbl.sorbs.net=127.0.0.3 : \
          misc.dnsbl.sorbs.net,dnsbl.sorbs.net=127.0.0.4 : \
@@ -27286,7 +29047,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.
 
 
 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
 ------------------------
 
 If Exim is asked to do a dnslist lookup for an IPv6 address, it inverts it
@@ -27318,7 +29079,7 @@ list and inner (lookup keys) list:
        dnslists = <; dnsbl.example.com/<|$acl_m_addrslist
 
 
        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
 -------------------------------------
 
 The ratelimit ACL condition can be used to measure and control the rate at
@@ -27368,7 +29129,7 @@ at which a recipient receives messages, you can use the key
 "$local_part@$domain" with the per_rcpt option (see below) in a RCPT ACL.
 
 Each ratelimit condition can have up to four options. A per_* option specifies
 "$local_part@$domain" with the per_rcpt option (see below) in a RCPT ACL.
 
 Each ratelimit condition can have up to four options. A per_* option specifies
-what Exim measures the rate of, for example messages or recipients or bytes.
+what Exim measures the rate of, for example, messages or recipients or bytes.
 You can adjust the measurement using the unique= and/or count= options. You can
 also control when Exim updates the recorded rate using a strict, leaky, or 
 readonly option. The options are separated by a slash, like the other
 You can adjust the measurement using the unique= and/or count= options. You can
 also control when Exim updates the recorded rate using a strict, leaky, or 
 readonly option. The options are separated by a slash, like the other
@@ -27382,7 +29143,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.
 
 
 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
 --------------------------------------------------
 
 The per_conn option limits the client's connection rate. It is not normally
@@ -27405,8 +29166,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
 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
 
 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
@@ -27425,10 +29187,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.
 
 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
 ----------------------------
 
 You can specify one of three options with the ratelimit condition to control
@@ -27467,7 +29229,7 @@ ACLs the default update mode is leaky (see the next section) so you must
 specify the readonly option explicitly.
 
 
 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
 -------------------------------------------------
 
 If a client's average rate is greater than the maximum, the rate limiting
@@ -27477,13 +29239,12 @@ as rejecting the message) that may be specified by the rest of the ACL.
 
 The leaky (default) option means that the client's recorded rate is not updated
 if it is above the limit. The effect of this is that Exim measures the client's
 
 The leaky (default) option means that the client's recorded rate is not updated
 if it is above the limit. The effect of this is that Exim measures the client's
-average rate of successfully sent email, which cannot be greater than the
-maximum allowed. If the client is over the limit it may suffer some
-counter-measures (as specified in the ACL), but it will still be able to send
-email at the configured maximum rate, whatever the rate of its attempts. This
-is generally the better choice if you have clients that retry automatically.
-For example, it does not prevent a sender with an over-aggressive retry rate
-from getting any email through.
+average rate of successfully sent email,
+
+up to the given limit. This is appropriate if the countermeasure when the
+condition is true consists of refusing the message, and is generally the better
+choice if you have clients that retry automatically. If the action when true is
+anything more complex then this option is likely not what is wanted.
 
 The strict option means that the client's recorded rate is always updated. The
 effect of this is that Exim measures the client's average rate of attempts to
 
 The strict option means that the client's recorded rate is always updated. The
 effect of this is that Exim measures the client's average rate of attempts to
@@ -27497,7 +29258,7 @@ with this formula:
         ln(peakrate/maxrate)
 
 
         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
 -------------------------------------------
 
 The ratelimit unique= option controls a mechanism for counting the rate of
@@ -27534,7 +29295,7 @@ logged incorrectly; any countermeasures you configure will be as effective as
 intended.
 
 
 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
 -------------------------
 
 Exim's other ACL facilities are used to define what counter-measures are taken
@@ -27578,11 +29339,11 @@ its hints data after a reboot (including retry hints, the callout cache, and
 ratelimit data).
 
 
 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:
 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:
@@ -27606,13 +29367,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 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
     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
 
 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
@@ -27643,8 +29404,12 @@ appropriate) contains one of the following words:
 The main use of these variables is expected to be to distinguish between
 rejections of MAIL and rejections of RCPT in callouts.
 
 The main use of these variables is expected to be to distinguish between
 rejections of MAIL and rejections of RCPT in callouts.
 
+The above variables may also be set after a successful address verification to:
+
+  * random: A random local-part callout succeeded
 
 
-42.45 Callout verification
+
+43.45 Callout verification
 --------------------------
 
 For non-local addresses, routing verifies the domain, but is unable to do any
 --------------------------
 
 For non-local addresses, routing verifies the domain, but is unable to do any
@@ -27662,7 +29427,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
 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
 
 Recipient callouts are usually used only between hosts that are controlled by
 the same administration. For example, a corporate gateway host could use
@@ -27721,7 +29486,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.
 
 
 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
 ----------------------------------------
 
 The callout option can be followed by an equals sign and a number of optional
@@ -27865,6 +29630,20 @@ use_sender
     of the sender when checking recipients. If used indiscriminately, it
     reduces the usefulness of callout caching.
 
     of the sender when checking recipients. If used indiscriminately, it
     reduces the usefulness of callout caching.
 
+hold
+
+    This option applies to recipient callouts only. For example:
+
+    require  verify = recipient/callout=use_sender,hold
+
+    It causes the connection to be held open and used for any further
+    recipients and for eventual delivery (should that be done quickly). Doing
+    this saves on TCP and SMTP startup costs, and TLS costs also when that is
+    used for the connections. The advantage is only gained if there are no
+    callout cache hits (which could be enforced by the no_cache option), if the
+    use_sender option is used, if neither the random nor the use_postmaster
+    option is used, and if no other callouts intervene.
+
 If you use any of the parameters that set a non-empty sender for the MAIL
 command (mailfrom, postmaster_mailfrom, use_postmaster, or use_sender), you
 should think about possible loops. Recipient checking is usually done between
 If you use any of the parameters that set a non-empty sender for the MAIL
 command (mailfrom, postmaster_mailfrom, use_postmaster, or use_sender), you
 should think about possible loops. Recipient checking is usually done between
@@ -27886,7 +29665,7 @@ sender/recipient combination; thus, for any given recipient, many more actual
 callouts are performed than when an empty sender or postmaster is used.
 
 
 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
 ---------------------
 
 Exim caches the results of callouts in order to reduce the amount of resources
@@ -27927,10 +29706,10 @@ being tested. If the domain routes to several hosts, it is assumed that their
 behaviour will be the same.
 
 
 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:
 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:
@@ -27952,7 +29731,7 @@ the ACL statement that requests sender verification. For example:
 verify = sender/no_details
 
 
 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
 ---------------------------------
 
 A dilemma arises when a local address is redirected by aliasing or forwarding
@@ -27995,7 +29774,7 @@ also specified. In that case, full verification is done for every generated
 address and a report is output for each of them.
 
 
 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
 -------------------------------------
 
 Client SMTP Authorization is a system that allows a site to advertise which
@@ -28065,7 +29844,7 @@ The authorization code can be "Y" for yes, "N" for no, "X" for explicit
 authorization required but absent, or "?" for unknown.
 
 
 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
 -----------------------------------
 
 Bounce address tag validation (BATV) is a scheme whereby the envelope senders
@@ -28080,7 +29859,8 @@ There are two expansion items to help with the implementation of the BATV
 the original envelope sender address by using a simple key to add a hash of the
 address and some time-based randomizing information. The prvs expansion item
 creates a signed address, and the prvscheck expansion item checks one. The
 the original envelope sender address by using a simple key to add a hash of the
 address and some time-based randomizing information. The prvs expansion item
 creates a signed address, and the prvscheck expansion item checks one. The
-syntax of these expansion items is described in section 11.5.
+syntax of these expansion items is described in section 11.5. The validity
+period on signed addresses is seven days.
 
 As an example, suppose the secret per-address keys are stored in an MySQL
 database. A query to look up the key for an address could be defined as a macro
 
 As an example, suppose the secret per-address keys are stored in an MySQL
 database. A query to look up the key for an address could be defined as a macro
@@ -28148,7 +29928,7 @@ external_smtp_batv:
 If no key can be found for the existing return path, no signing takes place.
 
 
 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
 --------------------------------------
 
 An MTA is said to relay a message if it receives it from some host and delivers
@@ -28203,25 +29983,17 @@ default configuration includes a more comprehensive example, which is described
 in chapter 7.
 
 
 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.
 
 ------------------------------------
 
 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
 
 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
@@ -28230,7 +30002,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
 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).
 
 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).
 
@@ -28253,10 +30025,6 @@ Makefile. When you do that, the Exim binary is built with:
 
   * Two new main configuration options: av_scanner and spamd_address.
 
 
   * 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
 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
@@ -28281,7 +30049,7 @@ has been encountered. When the MIME ACL decodes files, they are put into the
 same directory by default.
 
 
 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
 -------------------------
 
 The malware ACL condition lets you connect virus scanner software to Exim. It
@@ -28289,8 +30057,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.
 
 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>:[...]
 needed. The basic syntax is as follows:
 
 av_scanner = <scanner-type>:<option1>:<option2>:[...]
@@ -28300,13 +30071,51 @@ 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
 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
-following scanner types are supported in this release:
+before use. The usual list-parsing of the content (see 6.20) applies. The
+following scanner types are supported in this release, though individual ones
+can be included or not at build time:
+
+avast
+
+    This is the scanner daemon of Avast. It has been tested with Avast Core
+    Security (currently at version 2.2.0). You can get a trial version at 
+    https://www.avast.com or for Linux at https://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. A list of options
+    may follow. These options are interpreted on the Exim's side of the malware
+    scanner, or are given on separate lines to the daemon as options before the
+    main scan command.
+
+    If "pass_unscanned" is set, any files the Avast scanner can't scan (e.g.
+    decompression bombs, or invalid archives) are considered clean. Use with
+    care.
+
+    For example:
+
+    av_scanner = avast:/var/run/avast/scan.sock:FLAGS -fullfiles:SENSITIVITY -pup
+    av_scanner = avast:/var/run/avast/scan.sock:pass_unscanned: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
+
+    If the scanner returns a temporary failure (e.g. license issues, or
+    permission problems), the message is deferred and a paniclog entry is
+    written. The usual "defer_ok" option is available.
 
 aveserver
 
     This is the scanner daemon of Kaspersky Version 5. You can get a trial
 
 aveserver
 
     This is the scanner daemon of Kaspersky Version 5. You can get a trial
-    version at http://www.kaspersky.com. This scanner type takes one option,
+    version at https://www.kaspersky.com/. This scanner type takes one option,
     which is the path to the daemon's UNIX socket. The default is shown in this
     example:
 
     which is the path to the daemon's UNIX socket. The default is shown in this
     example:
 
@@ -28314,26 +30123,41 @@ aveserver
 
 clamd
 
 
 clamd
 
-    This daemon-type scanner is GPL and free. You can get it at http://
+    This daemon-type scanner is GPL and free. You can get it at https://
     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
     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:/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
     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
-    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
-    filesystem with the remote host. There is an option WITH_OLD_CLAMAV_STREAM
-    in src/EDITME available, should you be running a version of ClamAV prior to
-    0.95.
+    "local" option, then the ClamAV interface will pass a filename containing
+    the data to be scanned, which 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 filesystem with the
+    remote host.
 
     The final example shows that multiple TCP targets can be specified. Exim
     will randomly use one for each incoming email (i.e. it load balances them).
 
     The final example shows that multiple TCP targets can be specified. Exim
     will randomly use one for each incoming email (i.e. it load balances them).
@@ -28386,9 +30210,10 @@ cmdline
 
 drweb
 
 
 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 (https://www.sald.ru/) 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
 
     av_scanner = drweb:/var/run/drwebd.sock
     av_scanner = drweb:192.168.2.20 31337
@@ -28396,9 +30221,29 @@ 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.
 
     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 shown above are used.
+
+f-prot6d
+
+    The f-prot6d scanner is accessed using the FPSCAND protocol over TCP. One
+    argument is taken, being a space-separated hostname and port number. For
+    example:
+
+    av_scanner = f-prot6d:localhost 10200
+
+    If you omit the argument, the default values show above are used.
+
 fsecure
 
 fsecure
 
-    The F-Secure daemon scanner (http://www.f-secure.com) takes one argument
+    The F-Secure daemon scanner (https://www.f-secure.com/) takes one argument
     which is the path to a UNIX socket. For example:
 
     av_scanner = fsecure:/path/to/.fsav
     which is the path to a UNIX socket. For example:
 
     av_scanner = fsecure:/path/to/.fsav
@@ -28419,12 +30264,14 @@ kavdaemon
 
 mksd
 
 
 mksd
 
-    This is a daemon type scanner that is aimed mainly at Polish users, though
-    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:
+    This was a daemon type scanner that is aimed mainly at Polish users, though
+    some documentation was available in English. The history can be shown at 
+    https://en.wikipedia.org/wiki/Mks_vir and this appears to be a candidate
+    for removal from Exim, unless we are informed of other virus scanners which
+    use the same protocol to integrate. The only option for this scanner type
+    is the maximum number of processes used simultaneously to scan the
+    attachments, provided that mksd has been run with at least the same number
+    of child processes. For example:
 
     av_scanner = mksd:2
 
 
     av_scanner = mksd:2
 
@@ -28434,21 +30281,24 @@ 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
 
     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
     (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:
+    file to be scanned), an RE to trigger on from the returned data, and an RE
+    to extract malware_name from the returned data. For example:
 
 
-    av_scanner = sock:127.0.0.1 6001:%s:(SPAM|VIRUS):(.*)\$
+    av_scanner = sock:127.0.0.1 6001:%s:(SPAM|VIRUS):(.*)$
 
 
-    Default for the socket specifier is /tmp/malware.sock. Default for the
-    commandline is %s\n. Both regular-expressions are required.
+    Note that surrounding whitespace is stripped from each option, meaning
+    there is no way to specify a trailing newline. The socket specifier and
+    both regular-expressions are required. Default for the commandline is %s\n
+    (note this does have a trailing newline); specify an empty element to get
+    this.
 
 sophie
 
     Sophie is a daemon that uses Sophos' libsavi library to scan for viruses.
 
 sophie
 
     Sophie is a daemon that uses Sophos' libsavi library to scan for viruses.
-    You can get Sophie at http://www.clanfield.info/sophie/. The only option
-    for this scanner type is the path to the UNIX socket that Sophie uses for
+    You can get Sophie at http://sophie.sourceforge.net/. The only option for
+    this scanner type is the path to the UNIX socket that Sophie uses for
     client communication. For example:
 
     av_scanner = sophie:/tmp/sophie
     client communication. For example:
 
     av_scanner = sophie:/tmp/sophie
@@ -28466,8 +30316,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.
 
 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
 
   * "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
@@ -28479,33 +30330,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
   * 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 6.21).
+
+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 "/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 "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.
 
 
 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)
 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)
      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
      malware = */defer_ok
 
 The next example shows how to use an ACL variable to scan with both sophie and
@@ -28524,13 +30381,17 @@ deny message = This message contains malware ($malware_name)
      malware = *
 
 
      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
 
 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 https://spamassassin.apache.org/
+and https://www.rspamd.com/
+
+SpamAssassin can be installed with CPAN by running:
 
 perl -MCPAN -e 'install Mail::SpamAssassin'
 
 
 perl -MCPAN -e 'install Mail::SpamAssassin'
 
@@ -28538,42 +30399,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.
 
 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 783
 
 
-spamd_address = 192.168.99.45 387
+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.
 
 
-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:
+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 filename 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
 
 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 6.21):
 
 spamd_address = 192.168.2.10 783 : \
                 192.168.2.11 783 : \
                 192.168.2.12 783
 
 
 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 (6.21); 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
+
+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.
 
 
-Warning: It is not possible to use the UNIX socket connection method with
-multiple spamd servers.
+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.
 
 
 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:
 ------------------------------------------
 
 Here is a simple example of the use of the spam condition in a DATA ACL:
@@ -28584,14 +30496,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
 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 
 
 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"
 
 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"
@@ -28612,12 +30527,12 @@ 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.
 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
 
 
 $spam_score
 
-    The spam score of the message, for example "3.4" or "30.5". This is useful
+    The spam score of the message, for example, "3.4" or "30.5". This is useful
     for inclusion in log or reject messages.
 
 $spam_score_int
     for inclusion in log or reject messages.
 
 $spam_score_int
@@ -28632,12 +30547,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,
     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
 
 $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
 
 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
@@ -28671,7 +30595,7 @@ deny  message = This message scored $spam_score spam points.
       condition = ${if >{$spam_score_int}{120}{1}{0}}
 
 
       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
 ------------------------
 
 The acl_smtp_mime global option specifies an ACL that is called once for each
@@ -28692,7 +30616,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
 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
 
 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
@@ -28708,13 +30632,13 @@ The right hand side is expanded before use. After expansion, the value can be:
 
  2. The string "default". In that case, the file is put in the temporary
     "default" directory <spool_directory>/scan/<message_id>/ with a sequential
 
  2. The string "default". In that case, the file is put in the temporary
     "default" directory <spool_directory>/scan/<message_id>/ with a sequential
-    file name consisting of the message id and a sequence number. The full path
+    filename consisting of the message id and a sequence number. The full path
     and name is available in $mime_decoded_filename after decoding.
 
  3. A full path name starting with a slash. If the full name is an existing
     directory, it is used as a replacement for the default directory. The
     filename is then sequentially assigned. If the path does not exist, it is
     and name is available in $mime_decoded_filename after decoding.
 
  3. A full path name starting with a slash. If the full name is an existing
     directory, it is used as a replacement for the default directory. The
     filename is then sequentially assigned. If the path does not exist, it is
-    used as the full path and file name.
+    used as the full path and filename.
 
  4. If the string does not start with a slash, it is used as the filename, and
     the default path is then used.
 
  4. If the string does not start with a slash, it is used as the filename, and
     the default path is then used.
@@ -28737,7 +30661,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.
 
 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:
 
 The following list describes all expansion variables that are available in the
 MIME ACL:
@@ -28810,7 +30734,7 @@ $mime_content_type
 $mime_decoded_filename
 
     This variable is set only after the decode modifier (see above) has been
 $mime_decoded_filename
 
     This variable is set only after the decode modifier (see above) has been
-    successfully run. It contains the full path and file name of the file
+    successfully run. It contains the full path and filename of the file
     containing the decoded data.
 
 $mime_filename
     containing the decoded data.
 
 $mime_filename
@@ -28818,8 +30742,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
     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
 
 
 $mime_is_coverletter
 
@@ -28844,7 +30768,7 @@ $mime_is_coverletter
 
     As an example, the following will ban "HTML mail" (including that sent with
     alternative plain text), while allowing HTML files to be attached. HTML
 
     As an example, the following will ban "HTML mail" (including that sent with
     alternative plain text), while allowing HTML files to be attached. HTML
-    coverletter mail attached to non-HMTL coverletter mail will also be
+    coverletter mail attached to non-HTML coverletter mail will also be
     allowed:
 
     deny message = HTML mail is not accepted here
     allowed:
 
     deny message = HTML mail is not accepted here
@@ -28855,9 +30779,9 @@ $mime_is_coverletter
 $mime_is_multipart
 
     This variable has the value 1 (true) when the current part has the main
 $mime_is_multipart
 
     This variable has the value 1 (true) when the current part has the main
-    type "multipart", for example "multipart/alternative" or "multipart/mixed".
-    Since multipart entities only serve as containers for other parts, you may
-    not want to carry out specific actions on them.
+    type "multipart", for example, "multipart/alternative" or "multipart/
+    mixed". Since multipart entities only serve as containers for other parts,
+    you may not want to carry out specific actions on them.
 
 $mime_is_rfc822
 
 
 $mime_is_rfc822
 
@@ -28876,7 +30800,7 @@ $mime_part_count
     -1.
 
 
     -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
 --------------------------------------
 
 You can specify your own custom regular expression matches on the full body of
@@ -28906,99 +30830,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
 
 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.
 
 
 
 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.
 
 
 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
 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
 
 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
@@ -29020,14 +30869,18 @@ 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.
 
 
 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
 -----------------------------------------------
 
 To make use of the local scan function feature, you must tell Exim where your
-function is before building Exim, by setting LOCAL_SCAN_SOURCE in your Local/
-Makefile. A recommended place to put it is in the Local directory, so you might
-set
+function is before building Exim, by setting
 
 
+both HAVE_LOCAL_SCAN and
+
+LOCAL_SCAN_SOURCE in your Local/Makefile. A recommended place to put it is in
+the Local directory, so you might set
+
+HAVE_LOCAL_SCAN=yes
 LOCAL_SCAN_SOURCE=Local/local_scan.c
 
 for example. The function must be called local_scan(). It is called by Exim
 LOCAL_SCAN_SOURCE=Local/local_scan.c
 
 for example. The function must be called local_scan(). It is called by Exim
@@ -29037,15 +30890,15 @@ function controls whether the message is actually accepted or not. There is a
 commented template function (that just accepts the message) in the file _src/
 local_scan.c_.
 
 commented template function (that just accepts the message) in the file _src/
 local_scan.c_.
 
-If you want to make use of Exim's run time configuration file to set options
-for your local_scan() function, you must also set
+If you want to make use of Exim's runtime configuration file to set options for
+your local_scan() function, you must also set
 
 LOCAL_SCAN_HAS_OPTIONS=yes
 
 
 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:
 -------------------------
 
 You must include this line near the start of your code:
@@ -29124,7 +30977,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
     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"
 
 
 "LOCAL_SCAN_TEMPREJECT_NOLOGHDR"
 
@@ -29136,7 +30989,7 @@ reported by writing to stderr or by sending an email, as configured by the -oe
 command line options.
 
 
 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
 -------------------------------------------
 
 It is possible to have option settings in the main configuration file that set
@@ -29225,7 +31078,7 @@ If the -bP command line option is followed by "local_scan", Exim prints out the
 values of all the local_scan() options.
 
 
 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
 -----------------------------
 
 The header local_scan.h gives you access to a number of C variables. These are
@@ -29236,12 +31089,13 @@ as follows:
 
 int body_linecount
 
 
 int body_linecount
 
-    This variable contains the number of lines in the message's body.
+    This variable contains the number of lines in the message's body. It is not
+    valid if the spool_files_wireformat option is used.
 
 int body_zerocount
 
     This variable contains the number of binary zero bytes in the message's
 
 int body_zerocount
 
     This variable contains the number of binary zero bytes in the message's
-    body.
+    body. It is not valid if the spool_files_wireformat option is used.
 
 unsigned int debug_selector
 
 
 unsigned int debug_selector
 
@@ -29249,11 +31103,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:
 
     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.
 
         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.
 
         by the "+local_scan" debug selector. It is not included in the default
         set of debugging bits.
 
@@ -29355,10 +31209,10 @@ BOOL smtp_batched_input
 int store_pool
 
     The contents of this variable control which pool of memory is used for new
 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
 ------------------------------
 
 The header_line structure contains the members listed below. You can add
@@ -29372,7 +31226,7 @@ struct header_line *next
 int type
 
     A code identifying certain headers that Exim recognizes. The codes are
 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:
     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:
@@ -29389,7 +31243,7 @@ uschar *text
     followed by a zero byte. Internal newlines are preserved.
 
 
     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:
 ---------------------------------
 
 The recipient_item structure contains these members:
@@ -29415,7 +31269,7 @@ uschar *errors_to
     field is NULL for all recipients.
 
 
     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
 -----------------------------
 
 The header local_scan.h gives you access to a number of Exim functions. These
@@ -29444,21 +31298,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:
 
     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.
 
 
         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.
 
 
         The process was terminated by a signal and the value is the negation of
         the signal number.
 
-      * -256
+      + -256
 
         The process timed out.
 
 
         The process timed out.
 
-      * -257
+      + -257
 
         The was some other error in wait(); errno is still set.
 
 
         The was some other error in wait(); errno is still set.
 
@@ -29508,7 +31362,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
     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, ...)
 
 
 void header_add(int type, char *format, ...)
 
@@ -29748,7 +31602,7 @@ uschar *string_sprintf(char *format, ...)
     See the next section for more discussion.
 
 
     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
 --------------------------------------
 
 No function is provided for freeing memory, because that is never needed. The
@@ -29780,7 +31634,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
 
 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
@@ -29804,11 +31658,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
 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.
 
 
 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
 -------------------------------
 
 The name of the file that contains the system filter must be specified by
@@ -29826,7 +31680,7 @@ respectively. Similarly, system_filter_reply_transport must be set to handle
 any messages generated by the reply command.
 
 
 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
 ----------------------------
 
 You can run simple tests of a system filter in the same way as for a user
@@ -29837,7 +31691,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.
 
 
 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
 --------------------------------
 
 The language used to specify system filters is the same as for users' filter
@@ -29865,7 +31719,7 @@ files. Thus a system filter can, for example, set up "scores" to which users'
 filter files can refer.
 
 
 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
 -------------------------------------------
 
 The expansion variable $recipients, containing a list of all the recipients of
@@ -29873,7 +31727,7 @@ the message (separated by commas and white space), is available in system
 filters. It is not available in users' filters for privacy reasons.
 
 
 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
 --------------------------------------------------------
 
 There are three extra commands (defer, freeze and fail) which are always
@@ -29934,7 +31788,7 @@ failing) a message. The normal deliveries for the message do not, of course,
 take place.
 
 
 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:
 ---------------------------------------------------
 
 Two filter commands that are available only in system filters are:
@@ -29979,7 +31833,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
 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
 
 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
@@ -29999,7 +31853,7 @@ headers add "Subject: new subject (was: $h_old-subject:)"
 headers remove "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
 -------------------------------------------------
 
 In a system filter, if a deliver command is followed by
@@ -30017,7 +31871,7 @@ to take a copy which would not be sent back to the normal error reporting
 address if its delivery failed.
 
 
 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
 --------------------------
 
 In contrast to the system filter, which is run just once per message for each
@@ -30050,7 +31904,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
 
 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
@@ -30073,7 +31927,7 @@ If you want the loopback interface to be treated specially, you must ensure
 that there are appropriate entries in your ACLs.
 
 
 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 
 -------------------------------------------
 
 Processing that happens automatically for locally-originated messages (unless 
@@ -30083,8 +31937,8 @@ state. Submission mode is set by the modifier
 
 control = submission
 
 
 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
 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
@@ -30111,8 +31965,8 @@ to be used when generating a From: or Sender: header line. For example:
 
 control = submission/domain=some.domain
 
 
 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 = *
 full name for inclusion in a created Sender: or From: header line. For example:
 
 accept authenticated = *
@@ -30143,7 +31997,7 @@ does mean that you can configure ACL checks to spot that a user is trying to
 spoof another's address.
 
 
 spoof another's address.
 
 
-46.2 Line endings
+47.2 Line endings
 -----------------
 
 RFC 2821 specifies that CRLF (two characters: carriage-return, followed by
 -----------------
 
 RFC 2821 specifies that CRLF (two characters: carriage-return, followed by
@@ -30181,7 +32035,7 @@ follows:
     header line.
 
 
     header line.
 
 
-46.3 Unqualified addresses
+47.3 Unqualified addresses
 --------------------------
 
 By default, Exim expects every envelope address it receives from an external
 --------------------------
 
 By default, Exim expects every envelope address it receives from an external
@@ -30204,7 +32058,7 @@ other words, such qualification is also controlled by sender_unqualified_hosts
 and recipient_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
 -----------------------
 
 Messages that have come from UUCP (and some other applications) often begin
@@ -30241,7 +32095,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.
 
 
 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
 -------------------------
 
 RFC 2822 makes provision for sets of header lines starting with the string
@@ -30274,7 +32128,7 @@ address rewriting are concerned. Exim treats Resent- header lines as follows:
     Resent- header lines are present.
 
 
     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
 ------------------------------------
 
 Whenever Exim generates an autoreply, a bounce, or a delay warning message, it
@@ -30283,7 +32137,7 @@ includes the header line:
 Auto-Submitted: auto-replied
 
 
 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
 -------------------------
 
 If Exim is called with the -t option, to take recipient addresses from a
@@ -30292,7 +32146,7 @@ extracting its addresses). If -t is not present on the command line, any
 existing Bcc: is not removed.
 
 
 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,
 --------------------------
 
 If a locally-generated or submission-mode message has no Date: header line,
@@ -30300,7 +32154,7 @@ Exim adds one, using the current date and time, unless the
 suppress_local_fixups control has been specified.
 
 
 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.
 -----------------------------------
 
 Delivery-date: header lines are not part of the standard RFC 2822 header set.
@@ -30310,7 +32164,7 @@ messages in transit. If the delivery_date_remove configuration option is set
 (the default), Exim removes Delivery-date: header lines from incoming messages.
 
 
 (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.
 ----------------------------------
 
 Envelope-to: header lines are not part of the standard RFC 2822 header set.
@@ -30320,7 +32174,7 @@ messages in transit. If the envelope_to_remove configuration option is set (the
 default), Exim removes Envelope-to: header lines from incoming messages.
 
 
 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
 ---------------------------
 
 If a submission-mode message does not contain a From: header line, Exim adds
@@ -30345,17 +32199,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
 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
 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 
 ---------------------------------
 
 If a locally-generated or submission-mode incoming message does not contain a 
@@ -30368,7 +32222,7 @@ Additional information can be included in this header line by setting the
 message_id_header_text and/or message_id_header_domain options.
 
 
 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
 -------------------------------
 
 A Received: header line is added at the start of every message. The contents
@@ -30385,7 +32239,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.
 
 
 -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.
 ---------------------------------
 
 Messages created by the autoreply transport include a References: header line.
@@ -30399,7 +32253,7 @@ more than 12, the first one and then the final 11 are copied, before adding the
 message ID of the incoming message.
 
 
 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
 ----------------------------------
 
 Return-path: header lines are defined as something an MTA may insert when it
@@ -30409,7 +32263,7 @@ return_path_remove configuration option is set (the default), Exim removes
 Return-path: header lines from incoming messages.
 
 
 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
 -----------------------------
 
 For a locally-originated message from an untrusted user, Exim may remove an
@@ -30459,14 +32313,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.
 
 
 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
 ----------------------------------------------------------------
 
 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
 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
 
 In contrast to what happens in a system filter, header modifications that are
 specified on routers and transports apply only to the particular recipient
@@ -30501,7 +32355,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
 
 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
 
 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
@@ -30557,7 +32412,7 @@ Warning: The headers_add and headers_remove options cannot be used for a
 redirect router that has the one_time option set.
 
 
 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
 ---------------------------
 
 When Exim constructs a sender address for a locally-generated message, it uses
@@ -30588,7 +32443,7 @@ print_topbitchars controls whether characters with the top bit set (that is,
 with codes greater than 127) count as printing characters or not.
 
 
 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
 -------------------------
 
 RFC 2822 states that the case of letters in the local parts of addresses cannot
@@ -30618,7 +32473,7 @@ subsequent routers which process your domains, they will operate on local parts
 with the correct case in a case-sensitive manner.
 
 
 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
 -------------------------
 
 RFC 2822 forbids empty components in local parts. That is, an unquoted local
@@ -30627,7 +32482,7 @@ middle. However, it seems that many MTAs do not enforce this, so Exim permits
 empty components for compatibility.
 
 
 empty components for compatibility.
 
 
-46.21 Rewriting addresses
+47.21 Rewriting addresses
 -------------------------
 
 Rewriting of sender and recipient addresses, and addresses in headers, can
 -------------------------
 
 Rewriting of sender and recipient addresses, and addresses in headers, can
@@ -30660,7 +32515,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
 
 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
@@ -30691,7 +32546,7 @@ in or read from files (or pipes), in a format in which SMTP commands are used
 to contain the envelope information.
 
 
 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 
 ---------------------------------------
 
 Outgoing SMTP and LMTP over TCP/IP is implemented by the smtp transport. The 
@@ -30711,7 +32566,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
 
 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.
 
 in that or hosts_verify_avoid_tls apply when the transport is called for
 verification.
 
@@ -30760,7 +32615,7 @@ identified in the main log by the addition of an asterisk after the closing
 square bracket of the IP address.
 
 
 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,
 ----------------------------
 
 Three different kinds of error are recognized for outgoing SMTP: host errors,
@@ -30771,17 +32626,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:
 
     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
         the "." at the end of the data.
 
     For a host error, a permanent error response on connection, or in response
@@ -30800,12 +32655,12 @@ Message errors
     particular host, but not with a particular recipient of the message. The
     message errors are:
 
     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,
 
         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.
 
         timeout after the DATA command itself is treated as a host error, as is
         loss of connection at any other time.
 
@@ -30829,9 +32684,9 @@ Recipient errors
     A recipient error is associated with a particular recipient of a message.
     The recipient errors are:
 
     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
 
     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
@@ -30890,7 +32745,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.
 
 
 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
 ---------------------------------------
 
 Incoming SMTP messages can be accepted in one of two ways: by running a
@@ -30973,7 +32828,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
 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
 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
@@ -30981,7 +32836,7 @@ MAIL and RCPT commands in a single SMTP session. See the smtp_ratelimit_hosts
 option.
 
 
 option.
 
 
-47.4 Unrecognized SMTP commands
+48.4 Unrecognized SMTP commands
 -------------------------------
 
 If Exim receives more than smtp_max_unknown_commands unrecognized SMTP commands
 -------------------------------
 
 If Exim receives more than smtp_max_unknown_commands unrecognized SMTP commands
@@ -30992,7 +32847,7 @@ that subvert web servers into making connections to SMTP ports; in these
 circumstances, a number of non-SMTP lines are sent first.
 
 
 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
 ------------------------------------------------
 
 A syntax error is detected if an SMTP command is recognized, but there is
@@ -31005,7 +32860,7 @@ smtp_max_synprot_errors is 3. This is a defence against broken clients that
 loop sending bad commands (yes, it has been seen).
 
 
 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
 ----------------------------------
 
 The "non-mail" SMTP commands are those other than MAIL, RCPT, and DATA. Exim
@@ -31031,26 +32886,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.
 
 
 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
 -------------------------------
 
 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
 ---------------------
 
 RFC 1985 describes an SMTP command called ETRN that is designed to overcome the
@@ -31102,7 +32960,7 @@ its own uid and gid when receiving incoming SMTP, so it is not possible for it
 to change them before running the command.
 
 
 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
 ------------------------
 
 Some user agents use SMTP to pass messages to their local MTA using the
@@ -31120,7 +32978,7 @@ accept hosts = :
 This accepts SMTP messages from local processes without doing any other tests.
 
 
 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.
 ---------------------------
 
 Both the appendfile and pipe transports can be used for handling batched SMTP.
@@ -31165,7 +33023,7 @@ to /var/bsmtp/batch.host.example, with only a single copy of each message
 (unless there are more than 1000 recipients).
 
 
 (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
 ---------------------------
 
 The -bS command line option causes Exim to accept one or more messages by
@@ -31211,9 +33069,9 @@ 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
+When a message fails to be delivered, or remains in the queue for more than a
 configured amount of time, Exim sends a message to the original sender, or to
 an alternative configured address. The text of these messages is built into the
 code of Exim, but it is possible to change it, either by adding a single
 configured amount of time, Exim sends a message to the original sender, or to
 an alternative configured address. The text of these messages is built into the
 code of Exim, but it is possible to change it, either by adding a single
@@ -31228,7 +33086,7 @@ Auto-Submitted: auto-generated
 to all warning and bounce messages,
 
 
 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
 --------------------------------
 
 If bounce_message_text is set, its contents are included in the default message
@@ -31260,13 +33118,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 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
 
 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
@@ -31297,7 +33150,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
 ---------------------------------
 
 The option warn_message_file can be pointed at a template file for use when
@@ -31326,7 +33179,7 @@ A message ${if eq{$sender_address}{$warn_message_recipients}
 <$sender_address>
 
 }}has not been delivered to all of its recipients after
 <$sender_address>
 
 }}has not been delivered to all of its recipients after
-more than $warn_message_delay on the queue on $primary_hostname.
+more than $warn_message_delay in the queue on $primary_hostname.
 
 The message identifier is:     $message_exim_id
 The subject of the message is: $h_subject
 
 The message identifier is:     $message_exim_id
 The subject of the message is: $h_subject
@@ -31350,13 +33203,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.
 
 
 
 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
 ---------------------------------
 
 If you want to send all mail for non-local domains to a "smart host", you
@@ -31371,10 +33224,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
 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
 ---------------------------------------
 
 Exim can be used to run simple mailing lists, but for large and/or complicated
@@ -31401,7 +33254,7 @@ such file, the router declines, but because no_more is set, no subsequent
 routers are tried, and so the whole delivery fails.
 
 The forbid_pipe and forbid_file options prevent a local part from being
 routers are tried, and so the whole delivery fails.
 
 The forbid_pipe and forbid_file options prevent a local part from being
-expanded into a file name or a pipe delivery, which is usually inappropriate in
+expanded into a filename or a pipe delivery, which is usually inappropriate in
 a mailing list.
 
 The errors_to option specifies that any delivery errors caused by addresses
 a mailing list.
 
 The errors_to option specifies that any delivery errors caused by addresses
@@ -31419,7 +33272,7 @@ local_part_suffix options) to handle addresses of the form owner-xxx or xxx-
 request, are also possible.
 
 
 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
 -----------------------------------
 
 If an entry in redirection data contains a syntax error, Exim normally defers
@@ -31435,7 +33288,7 @@ whenever a broken address is skipped. It is usually appropriate to set
 syntax_errors_to to the same address as errors_to.
 
 
 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,
 ----------------------------------
 
 Exim remembers every individual address to which a message has been delivered,
@@ -31463,7 +33316,7 @@ all_parents selector is set, but for mailing lists there is normally only one
 level of expansion anyway.
 
 
 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
 -------------------------
 
 The examples so far have assumed open mailing lists, to which anybody may send
@@ -31517,14 +33370,14 @@ a mailing list exists, but the sender is not on it. This router forcibly fails
 the address, giving a suitable error message.
 
 
 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
-of helping mailing list administrators discover which subscription address is
-the cause of a particular delivery failure. The idea is to encode the original
-recipient address in the outgoing envelope sender address, so that if the
-message is forwarded by another host and then subsequently bounces, the
+Variable Envelope Return Paths - see https://cr.yp.to/proto/verp.txt - are a
+way of helping mailing list administrators discover which subscription address
+is the cause of a particular delivery failure. The idea is to encode the
+original recipient address in the outgoing envelope sender address, so that if
+the message is forwarded by another host and then subsequently bounces, the
 original recipient can be extracted from the recipient address of the bounce.
 
 Envelope sender addresses can be modified by Exim using two different
 original recipient can be extracted from the recipient address of the bounce.
 
 Envelope sender addresses can be modified by Exim using two different
@@ -31605,7 +33458,7 @@ than sending a single copy with many recipients (for which VERP cannot be
 used).
 
 
 used).
 
 
-49.7 Virtual domains
+50.7 Virtual domains
 --------------------
 
 The phrase virtual domain is unfortunately used with two rather different
 --------------------
 
 The phrase virtual domain is unfortunately used with two rather different
@@ -31639,7 +33492,7 @@ the file to find a new address (or list of addresses). The no_more setting
 ensures that if the lookup fails (leading to data being an empty string), Exim
 gives up on the address without trying any subsequent routers.
 
 ensures that if the lookup fails (leading to data being an empty string), Exim
 gives up on the address without trying any subsequent routers.
 
-This one router can handle all the virtual domains because the alias file names
+This one router can handle all the virtual domains because the alias filenames
 follow a fixed pattern. Permissions can be arranged so that appropriate people
 can edit the different alias files. A successful aliasing operation results in
 a new envelope recipient address, which is then routed from scratch.
 follow a fixed pattern. Permissions can be arranged so that appropriate people
 can edit the different alias files. A successful aliasing operation results in
 a new envelope recipient address, which is then routed from scratch.
@@ -31674,7 +33527,7 @@ up, for example, by using a database instead of separate files to hold all the
 information about the domains.
 
 
 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
 ----------------------------
 
 Heavy email users often want to operate with multiple mailboxes, into which
@@ -31724,7 +33577,7 @@ routers, which could, if required, look for an unqualified .forward file to use
 as a default.
 
 
 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
 -----------------------------------
 
 The traditional way of running the vacation program is for a user to set up a
@@ -31749,7 +33602,7 @@ Another advantage of both these methods is that they both work even when the
 use of arbitrary pipes by users is locked out.
 
 
 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
 ---------------------------
 
 Some installations have policies that require archive copies of all messages to
@@ -31763,7 +33616,7 @@ delivery. This could be used, inter alia, to implement automatic notification
 of delivery by sites that insist on doing such things.
 
 
 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
 ------------------------------------
 
 It has become quite common (because it is cheaper) for hosts to connect to the
@@ -31776,11 +33629,11 @@ particularly well-suited to use in an intermittently connected environment.
 Nevertheless there are some features that can be used.
 
 
 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
 --------------------------------------
 
 It is tempting to arrange for incoming mail for the intermittently connected
-host to remain on Exim's queue until the client connects. However, this
+host to remain in Exim's queue until the client connects. However, this
 approach does not scale very well. Two different kinds of waiting message are
 being mixed up in the same queue - those that cannot be delivered because of
 some temporary problem, and those that are waiting for their destination host
 approach does not scale very well. Two different kinds of waiting message are
 being mixed up in the same queue - those that cannot be delivered because of
 some temporary problem, and those that are waiting for their destination host
@@ -31804,7 +33657,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,
 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.
 
 messages to be delivered, often down a single SMTP connection. While the host
 remains connected, any new messages get delivered immediately.
 
@@ -31817,7 +33670,7 @@ has disadvantages for permanently connected hosts, it is best to arrange a
 separate transport for the intermittently connected ones.
 
 
 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
 ------------------------------------------------------
 
 The value of smtp_accept_queue_per_connection should probably be increased, or
@@ -31837,7 +33690,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
 
 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
@@ -31926,13 +33779,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 
 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.
 
 
 
 ===============================================================================
 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:
 
 Exim writes three different logs, referred to as the main log, the reject log,
 and the panic log:
@@ -31944,7 +33797,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
     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
 
   * The reject log records information from messages that are rejected as a
     result of a configuration option (that is, for policy reasons). The first
@@ -31990,12 +33843,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
     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.
 
 
 ). 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
 -------------------------------
 
 The logs may be written to local files, or to syslog, or both. However, it
@@ -32007,14 +33860,14 @@ need to tweak syslog to prevent it syncing the file with each write - on Linux
 this has been seen to make syslog take 90% plus of CPU time.
 
 The destination for Exim's logs is configured by setting LOG_FILE_PATH in Local
 this has been seen to make syslog take 90% plus of CPU time.
 
 The destination for Exim's logs is configured by setting LOG_FILE_PATH in Local
-/Makefile or by setting log_file_path in the run time configuration. This
-latter string is expanded, so it can contain, for example, references to the
-host name:
+/Makefile or by setting log_file_path in the runtime configuration. This latter
+string is expanded, so it can contain, for example, references to the host
+name:
 
 log_file_path = /var/log/$primary_hostname/exim_%slog
 
 It is generally advisable, however, to set the string in Local/Makefile rather
 
 log_file_path = /var/log/$primary_hostname/exim_%slog
 
 It is generally advisable, however, to set the string in Local/Makefile rather
-than at run time, because then the setting is available right from the start of
+than at runtime, because then the setting is available right from the start of
 Exim's execution. Otherwise, if there's something it wants to log before it has
 read the configuration file (for example, an error in the configuration file)
 it will not use the path you want, and may not be able to log at all.
 Exim's execution. Otherwise, if there's something it wants to log before it has
 read the configuration file (for example, an error in the configuration file)
 it will not use the path you want, and may not be able to log at all.
@@ -32036,11 +33889,12 @@ the setting:
 
 log_file_path = $spool_directory/log/%slog
 
 
 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 runtime, or if you unset the
+option at runtime (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.
+A log file path may also contain "%D" or "%M" if datestamped log filenames are
+in use - see section 52.3 below.
 
 Here are some examples of possible settings:
 
 
 Here are some examples of possible settings:
 
@@ -32053,12 +33907,12 @@ If there are more than two paths in the list, the first is used and a panic
 error is logged.
 
 
 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
 ----------------------------------------------------------
 
 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.
 
 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.
 
@@ -32075,7 +33929,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.
 
 
 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,
 --------------------------
 
 Instead of cycling the main and reject log files by renaming them periodically,
@@ -32116,7 +33970,7 @@ removed. Thus, the four examples above would give these panic log names:
 /var/log/exim/panic
 
 
 /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,
 ----------------------
 
 The use of syslog does not change what Exim logs or the format of its messages,
@@ -32201,7 +34055,7 @@ environment variable EXIMON_LOG_FILE_PATH is set to tell the monitor where it
 is.
 
 
 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
 -------------------
 
 One line is written to the main log for each message received, and for each
@@ -32210,6 +34064,7 @@ picked out by the distinctive two-character flags that immediately follow the
 timestamp. The flags are:
 
 <=     message arrival
 timestamp. The flags are:
 
 <=     message arrival
+(=     message fakereject
 =>     normal message delivery
 ->     additional address in same delivery
 >>     cutthrough message delivery
 =>     normal message delivery
 ->     additional address in same delivery
 >>     cutthrough message delivery
@@ -32218,7 +34073,7 @@ timestamp. The flags are:
 ==     delivery deferred; temporary problem
 
 
 ==     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
 ------------------------------
 
 The format of the single-line entry in the main log that is written for every
@@ -32283,16 +34138,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
 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
 -----------------------
 
 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
 
 2002-10-31 08:59:13 16ZCW1-0005MB-00 => marv
   <marv@hitch.fict.example> R=localuser T=local_delivery
@@ -32323,7 +34178,10 @@ When more than one address is included in a single delivery (for example, two
 SMTP RCPT commands in one transaction) the second and subsequent addresses are
 flagged with "->" instead of "=>". When two or more messages are delivered down
 a single SMTP connection, an asterisk follows the IP address in the log lines
 SMTP RCPT commands in one transaction) the second and subsequent addresses are
 flagged with "->" instead of "=>". When two or more messages are delivered down
 a single SMTP connection, an asterisk follows the IP address in the log lines
-for the second and subsequent messages.
+for the second and subsequent messages. When two or more messages are delivered
+down a single TLS connection, the DNS and some TLS-related information logged
+for the first message delivered will not be present in the log lines for the
+second and subsequent messages. TLS cipher information is still available.
 
 When delivery is done in cutthrough mode it is flagged with ">>" and the log
 line precedes the reception line, since cutthrough waits for a possible
 
 When delivery is done in cutthrough mode it is flagged with ">>" and the log
 line precedes the reception line, since cutthrough waits for a possible
@@ -32333,10 +34191,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
 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
 -------------------------
 
 When a message is discarded as a result of the command "seen finish" being
@@ -32352,7 +34210,7 @@ because it is aliased to ":blackhole:" the log line is like this:
   <hole@nowhere.example> R=blackhole_router
 
 
   <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:
 ------------------------
 
 When a delivery is deferred, a line of the following form is logged:
@@ -32372,7 +34230,7 @@ a message is written to the log, but this can be suppressed by setting an
 appropriate value in log_selector.
 
 
 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
 -----------------------
 
 If a delivery fails because an address cannot be routed, a line of the
@@ -32396,7 +34254,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
 ---------------------
 
 If a delivery does not actually take place because the -N option has been used
@@ -32404,7 +34262,7 @@ to suppress it, a normal delivery line is written to the log, except that "=>"
 is replaced by "*>".
 
 
 is replaced by "*>".
 
 
-51.12 Completion
+52.12 Completion
 ----------------
 
 A line of the form
 ----------------
 
 A line of the form
@@ -32415,7 +34273,7 @@ is written to the main log when a message is about to be removed from the spool
 at the end of its processing.
 
 
 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
 ------------------------------------
 
 A summary of the field identifiers that are used in log lines is shown in the
@@ -32426,28 +34284,38 @@ C           SMTP confirmation on delivery
             command list for "no mail in SMTP session"
 CV          certificate verification status
 D           duration of "no mail in SMTP session"
             command list for "no mail in SMTP session"
 CV          certificate verification status
 D           duration of "no mail in SMTP session"
+DKIM        domain verified in incoming message
 DN          distinguished name from peer certificate
 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
 id          message id for incoming message
 DT          on => lines: time taken for a delivery
 F           sender address (on delivery lines)
 H           host name and IP address
 I           local interface used
 id          message id for incoming message
+K           CHUNKING extension used
+L           on <= and => lines: PIPELINING extension used
+M8S         8BITMIME status for incoming message
 P           on <= lines: protocol used
             on => and ** lines: return path
 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
 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
+RT          on <= lines: time taken for reception
+S           size of message in bytes
 SNI         server name indication from TLS client hello
 ST          shadow transport name
 T           on <= lines: message subject (topic)
 SNI         server name indication from TLS client hello
 ST          shadow transport name
 T           on <= lines: message subject (topic)
+TFO         connection took advantage of TCP Fast Open
             on => ** and == lines: transport name
 U           local user or RFC 1413 identity
 X           TLS cipher suite
 
 
             on => ** and == lines: transport name
 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
 -----------------------
 
 Various other types of log entry are written from time to time. Most should be
@@ -32483,8 +34351,11 @@ self-explanatory. Among the more common are:
 
         failed. The delivery was discarded.
 
 
         failed. The delivery was discarded.
 
+  * DKIM: d=  Verbose results of a DKIM verification attempt, if enabled for
+    logging and the message has a DKIM signature header.
+
 
 
-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
 -------------------------------------------
 
 By setting the log_selector global option, you can disable some of Exim's
@@ -32506,18 +34377,26 @@ selection marked by asterisks:
 *delay_delivery               immediate delivery delayed
  deliver_time                 time taken to perform delivery
  delivery_size                add S=nnn to => lines
 *delay_delivery               immediate delivery delayed
  deliver_time                 time taken to perform delivery
  delivery_size                add S=nnn to => lines
+*dkim                         DKIM verified domain on <= lines
+ dkim_verbose                 separate full DKIM verification result line, per signature
 *dnslist_defer                defers of DNS list (aka RBL) lookups
 *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
 *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)
 *lost_incoming_connection     as it says (includes timeouts)
+ millisec                     millisecond timestamps and RT,QT,DT,D times
+ 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
  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
+ pipelining                   PIPELINING use, on <= and => lines
+ proxy                        proxy address on <= and => lines
+ receive_time                 time taken to receive message
  received_recipients          recipients on <= lines
  received_sender              sender on <= lines
 *rejected_header              header contents on reject log
  received_recipients          recipients on <= lines
  received_sender              sender on <= lines
 *rejected_header              header contents on reject log
@@ -32528,14 +34407,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
 *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
  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
 *tls_cipher                   TLS cipher suite on <= and => lines
  tls_peerdn                   TLS peer DN on <= and => lines
  tls_sni                      TLS SNI on <= lines
@@ -32543,6 +34422,8 @@ selection marked by asterisks:
 
  all                          all of the above
 
 
  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,
 More details on each of these items follows:
 
   * 8bitmime: This causes Exim to log any 8BITMIME status of received messages,
@@ -32584,13 +34465,27 @@ More details on each of these items follows:
 
   * deliver_time: For each delivery, the amount of real time it has taken to
     perform the actual delivery is logged as DT=<time>, for example, "DT=1s".
 
   * deliver_time: For each delivery, the amount of real time it has taken to
     perform the actual delivery is logged as DT=<time>, for example, "DT=1s".
+    If millisecond logging is enabled, short times will be shown with greater
+    precision, eg. "DT=0.304s".
 
   * delivery_size: For each delivery, the size of message delivered is added to
     the "=>" line, tagged with S=.
 
 
   * delivery_size: For each delivery, the size of message delivered is added to
     the "=>" line, tagged with S=.
 
+  * dkim: For message acceptance log lines, when an DKIM signature in the
+    header verifies successfully a tag of DKIM is added, with one of the
+    verified domains.
+
+  * dkim_verbose: A log entry is written for each attempted DKIM verification.
+
   * dnslist_defer: A log entry is written if an attempt to look up a host in a
     DNS black list suffers a temporary error.
 
   * 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
   * 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
@@ -32607,8 +34502,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
   * 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
 
   * 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
@@ -32620,14 +34521,30 @@ More details on each of these items follows:
   * lost_incoming_connection: A log line is written when an incoming SMTP
     connection is unexpectedly dropped.
 
   * lost_incoming_connection: A log line is written when an incoming SMTP
     connection is unexpectedly dropped.
 
+  * millisec: Timestamps have a period and three decimal places of finer
+    granularity appended to the seconds value.
+
+  * 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
   * 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.
 
 
   * pid: The current process id is added to every log line, in square brackets,
     immediately after the time and date.
 
+  * pipelining: A field is added to delivery and accept log lines when the
+    ESMTP PIPELINING extension was used. The field is a single "L".
+
+    On accept lines, where PIPELINING was offered but not used by the client,
+    the field has a minus appended.
+
   * queue_run: The start and end of every queue run are logged.
 
   * queue_time: The amount of time the message has been in the queue on the
   * queue_run: The start and end of every queue run are logged.
 
   * queue_time: The amount of time the message has been in the queue on the
@@ -32636,13 +34553,20 @@ More details on each of these items follows:
     includes reception time as well as the delivery time for the current
     address. This means that it may be longer than the difference between the
     arrival and delivery log line times, because the arrival log line is not
     includes reception time as well as the delivery time for the current
     address. This means that it may be longer than the difference between the
     arrival and delivery log line times, because the arrival log line is not
-    written until the message has been successfully received.
+    written until the message has been successfully received. If millisecond
+    logging is enabled, short times will be shown with greater precision, eg.
+    "QT=1.578s".
 
   * queue_time_overall: The amount of time the message has been in the queue on
     the local host is logged as QT=<time> on "Completed" lines, for example,
     "QT=3m45s". The clock starts when Exim starts to receive the message, so it
     includes reception time as well as the total delivery time.
 
 
   * queue_time_overall: The amount of time the message has been in the queue on
     the local host is logged as QT=<time> on "Completed" lines, for example,
     "QT=3m45s". The clock starts when Exim starts to receive the message, so it
     includes reception time as well as the total delivery time.
 
+  * receive_time: For each message, the amount of real time it has taken to
+    perform the reception is logged as RT=<time>, for example, "RT=1s". If
+    millisecond logging is enabled, short times will be shown with greater
+    precision, eg. "RT=0.204s".
+
   * received_recipients: The recipients of a message are listed in the main log
     as soon as the message is received. The list appears at the end of the log
     line that is written when a message is received, preceded by the word
   * received_recipients: The recipients of a message are listed in the main log
     as soon as the message is received. The list appears at the end of the log
     line that is written when a message is received, preceded by the word
@@ -32657,7 +34581,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_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"
 
   * 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"
@@ -32691,8 +34615,8 @@ More details on each of these items follows:
     "C="<text>. A number of MTAs (including Exim) return an identifying string
     in this response.
 
     "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
     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
@@ -32736,7 +34660,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
     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,
     have been aborted before 20 non-mail commands are processed.
 
   * smtp_mailauth: A third subfield with the authenticated sender,
@@ -32764,7 +34688,8 @@ More details on each of these items follows:
 
   * tls_certificate_verified: An extra item is added to <= and => log lines
     when TLS is in use. The item is "CV=yes" if the peer's certificate was
 
   * tls_certificate_verified: An extra item is added to <= and => log lines
     when TLS is in use. The item is "CV=yes" if the peer's certificate was
-    verified, and "CV=no" if not.
+    verified using a CA trust anchor, "CA=dane" if using a DNS trust anchor,
+    and "CV=no" if not.
 
   * tls_cipher: When a message is sent or received over an encrypted
     connection, the cipher suite used is added to the log line, preceded by X=.
 
   * tls_cipher: When a message is sent or received over an encrypted
     connection, the cipher suite used is added to the log line, preceded by X=.
@@ -32781,7 +34706,7 @@ More details on each of these items follows:
     result of a list match is failure because a DNS lookup failed.
 
 
     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
 -----------------
 
 In addition to the general log files, Exim writes a log file for each message
@@ -32800,33 +34725,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:
 
 
 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 
 
 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.
+exilog. It provides log visualizations across multiple Exim servers. See https:
+//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
 --------------------------------------------------------
 
 On operating systems that can restart a system call after receiving a signal
@@ -32866,7 +34791,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.
 
 
 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
 ---------------------------------------
 
 This utility is a Perl script contributed by Matt Hubbard. It runs
@@ -32893,8 +34818,8 @@ match given criteria. The following selection options are available:
 
 -r <regex>
 
 
 -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>
 
 
 -s <regex>
 
@@ -32946,11 +34871,11 @@ The following options control the format of the output:
 There is one more option, -h, which outputs a list of options.
 
 
 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
 -------------------------------------
 
 The exiqsumm utility is a Perl script which reads the output of "exim -bp" and
-produces a summary of the messages on the queue. Thus, you use it by running a
+produces a summary of the messages in the queue. Thus, you use it by running a
 command such as
 
 exim -bp | exiqsumm
 command such as
 
 exim -bp | exiqsumm
@@ -32978,7 +34903,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).
 
 
 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
 -----------------------------------------------------------
 
 The exigrep utility is a Perl script that searches one or more main log files
@@ -32990,13 +34915,13 @@ 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:
 
 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.
+If no log filenames are given on the command line, the standard input is read.
 
 The -t argument specifies a number of seconds. It adds an additional condition
 for message selection. Messages that are complete are shown only if they spent
 
 The -t argument specifies a number of seconds. It adds an additional condition
 for message selection. Messages that are complete are shown only if they spent
-more than <n> seconds on the queue.
+more than <n> seconds in the queue.
 
 By default, exigrep does case-insensitive matching. The -I option makes it
 case-sensitive. This may give a performance improvement when searching large
 
 By default, exigrep does case-insensitive matching. The -I option makes it
 case-sensitive. This may give a performance improvement when searching large
@@ -33011,26 +34936,37 @@ expression.
 The -v option inverts the matching condition. That is, a line is selected if it
 does not match the pattern.
 
 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
 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
 messages from the queue according to a variety of criteria. For details of 
 -----------------------------------------------------
 
 John Jetmore's exipick utility is included in the Exim distribution. It lists
 messages from the queue according to a variety of criteria. For details of 
-exipick's facilities, visit the web page at http://www.exim.org/eximwiki/
-ToolExipickManPage or run exipick with the --help option.
+exipick's facilities, 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
 ----------------------------------
 
 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:
 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:
@@ -33043,8 +34979,8 @@ exicyclog:
     the script's default, which is to find the setting from Exim's
     configuration.
 
     the script's default, which is to find the setting from Exim's
     configuration.
 
-Each time exicyclog is run the file names get "shuffled down" by one. If the
-main log file name is mainlog (the default) then when exicyclog is run mainlog
+Each time exicyclog is run the filenames get "shuffled down" by one. If the
+main log filename is mainlog (the default) then when exicyclog is run mainlog
 becomes mainlog.01, the previous mainlog.01 becomes mainlog.02 and so on, up to
 the limit that is set in the script or by the -k option. Log files whose
 numbers exceed the limit are discarded. Reject logs are handled similarly.
 becomes mainlog.01, the previous mainlog.01 becomes mainlog.02 and so on, up to
 the limit that is set in the script or by the -k option. Log files whose
 numbers exceed the limit are discarded. Reject logs are handled similarly.
@@ -33066,13 +35002,11 @@ 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.
 
 
 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
 --------------------------------
 
 A Perl script called eximstats is provided for extracting statistical
-information from log files. The output is either plain text, or HTML. Exim log
-files are also supported by the Lire system produced by the LogReport
-Foundation http://www.logreport.org.
+information from log files. The output is either plain text, or HTML.
 
 The eximstats script has been hacked about quite a bit over time. The latest
 version is the result of some extensive revision by Steve Campbell. A lot of
 
 The eximstats script has been hacked about quite a bit over time. The latest
 version is the result of some extensive revision by Steve Campbell. A lot of
@@ -33112,7 +35046,7 @@ one address that failed.
 The remainder of the output is in sections that can be independently disabled
 or modified by various options. It consists of a summary of deliveries by
 transport, histograms of messages received and delivered per time interval
 The remainder of the output is in sections that can be independently disabled
 or modified by various options. It consists of a summary of deliveries by
 transport, histograms of messages received and delivered per time interval
-(default per hour), information about the time messages spent on the queue, a
+(default per hour), information about the time messages spent in the queue, a
 list of relayed messages, lists of the top fifty sending hosts, local senders,
 destination hosts, and destination local users by count and by volume, and a
 list of delivery errors that occurred.
 list of relayed messages, lists of the top fifty sending hosts, local senders,
 destination hosts, and destination local users by count and by volume, and a
 list of delivery errors that occurred.
@@ -33128,7 +35062,7 @@ the command perldoc on the script. For example:
 perldoc /usr/exim/bin/eximstats
 
 
 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
 ----------------------------------------------
 
 The -bh command line argument allows you to run a fake SMTP session with
@@ -33167,7 +35101,7 @@ running its checks. You can run checks that include callouts by using -bhc, but
 this is not yet available in a "packaged" form.
 
 
 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
 -------------------------------------
 
 The exim_dbmbuild program reads an input file containing keys and data in the
@@ -33187,9 +35121,9 @@ It creates the output under a temporary name, and then renames it if all went
 well.
 
 If the native DB interface is in use (USE_DB is set in a compile-time
 well.
 
 If the native DB interface is in use (USE_DB is set in a compile-time
-configuration file - this is common in free versions of Unix) the two file
-names must be different, because in this mode the Berkeley DB functions create
-single output file using exactly the name given. For example,
+configuration file - this is common in free versions of Unix) the two filenames
+must be different, because in this mode the Berkeley DB functions create a
+single output file using exactly the name given. For example,
 
 exim_dbmbuild /etc/aliases /etc/aliases.db
 
 
 exim_dbmbuild /etc/aliases /etc/aliases.db
 
@@ -33200,7 +35134,7 @@ two files are used, with the suffixes .dir and .pag. In this environment, the
 suffixes are added to the second argument of exim_dbmbuild, so it can be the
 same as the first. This is also the case when the Berkeley functions are used
 in compatibility mode (though this is not recommended), because in that case it
 suffixes are added to the second argument of exim_dbmbuild, so it can be the
 same as the first. This is also the case when the Berkeley functions are used
 in compatibility mode (though this is not recommended), because in that case it
-adds a .db suffix to the file name.
+adds a .db suffix to the filename.
 
 If a duplicate key is encountered, the program outputs a warning, and when it
 finishes, its return code is 1 rather than zero, unless the -noduperr option is
 
 If a duplicate key is encountered, the program outputs a warning, and when it
 finishes, its return code is 1 rather than zero, unless the -noduperr option is
@@ -33211,7 +35145,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.
 
 
 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
 ----------------------------------------------
 
 A utility called exinext (mostly a Perl script) provides the ability to fish
@@ -33236,7 +35170,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
 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
 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
@@ -33247,7 +35181,7 @@ features are mainly to help in testing, but might also be useful in
 environments where more than one configuration file is in use.
 
 
 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
 --------------------------------
 
 Three utility programs are provided for maintaining the DBM files that Exim
@@ -33273,8 +35207,11 @@ The misc database is used for
   * Serializing delivery to a specific host (when serialize_hosts is set in an 
     smtp transport)
 
   * 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 
 -----------------
 
 The entire contents of a database are written to the standard output by the 
@@ -33311,7 +35248,7 @@ routed to several alternative hosts, and Exim makes no effort to keep
 cross-references.
 
 
 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
 -----------------
 
 The exim_tidydb utility program is used to tidy up the contents of a hints
@@ -33359,7 +35296,7 @@ Warning: If you never run exim_tidydb, the space used by the hints databases is
 likely to keep on increasing.
 
 
 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
 ----------------
 
 The exim_fixdb program is a utility for interactively modifying databases. Its
@@ -33382,7 +35319,7 @@ sequence of digit pairs for year, month, day, hour, and minute. Colons can be
 used as optional separators.
 
 
 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.
 -------------------------------------
 
 The exim_lock utility locks a mailbox file using the same algorithm as Exim.
@@ -33447,7 +35384,7 @@ If none of -fcntl, -flock, -lockfile or -mbx are given, the default is to
 create a lock file and also to use fcntl() locking on the mailbox, which is the
 same as Exim's default. The use of -flock or -fcntl requires that the file be
 writeable; the use of -lockfile requires that the directory containing the file
 create a lock file and also to use fcntl() locking on the mailbox, which is the
 same as Exim's default. The use of -flock or -fcntl requires that the file be
 writeable; the use of -lockfile requires that the directory containing the file
-be writeable. Locking by lock file does not last for ever; Exim assumes that a
+be writeable. Locking by lock file does not last forever; Exim assumes that a
 lock file is expired if it is more than 30 minutes old.
 
 The -mbx option can be used with either or both of -fcntl or -flock. It assumes
 lock file is expired if it is more than 30 minutes old.
 
 The -mbx option can be used with either or both of -fcntl or -flock. It assumes
@@ -33484,7 +35421,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
 
 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
@@ -33493,7 +35430,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.
 
 
 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
 ------------------------
 
 The monitor is started by running the script called eximon. This is a shell
@@ -33531,7 +35468,7 @@ xrdb -merge <<End
 Eximon*highlight: gray
 End
 
 Eximon*highlight: gray
 End
 
-In order to see the contents of messages on the queue, and to operate on them, 
+In order to see the contents of messages in the queue, and to operate on them, 
 eximon must either be run as root or by an admin user.
 
 The command-line parameters of eximon are passed to eximon.bin and may contain
 eximon must either be run as root or by an admin user.
 
 The command-line parameters of eximon are passed to eximon.bin and may contain
@@ -33547,10 +35484,10 @@ delivery, with two more action buttons. The following sections describe these
 different parts of the display.
 
 
 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
+The first stripchart is always a count of messages in the queue. Its name can
 be configured by setting QUEUE_STRIPCHART_NAME in the Local/eximon.conf file.
 The remaining stripcharts are defined in the configuration script by regular
 expression matches on log file entries, making it possible to display, for
 be configured by setting QUEUE_STRIPCHART_NAME in the Local/eximon.conf file.
 The remaining stripcharts are defined in the configuration script by regular
 expression matches on log file entries, making it possible to display, for
@@ -33577,7 +35514,7 @@ SIZE_STRIPCHART and (optionally) SIZE_STRIPCHART_NAME in the Local/eximon.conf
 file.
 
 
 file.
 
 
-53.3 Main action buttons
+54.3 Main action buttons
 ------------------------
 
 Below the stripcharts there is an action button for quitting the monitor. Next
 ------------------------
 
 Below the stripcharts there is an action button for quitting the monitor. Next
@@ -33604,7 +35541,7 @@ built so that it starts up with the window at its smallest size, by setting
 START_SMALL=yes in Local/eximon.conf.
 
 
 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
 --------------------
 
 The second section of the window is an area in which a display of the tail of
@@ -33656,11 +35593,11 @@ modified TextPop, making it possible to build Eximon on these systems, at the
 expense of having unwanted items in the search popup window.
 
 
 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
 ----------------------
 
 The bottom section of the monitor window contains a list of all messages that
-are on the queue, which includes those currently being received or delivered,
+are in the queue, which includes those currently being received or delivered,
 as well as those awaiting delivery. The size of this subwindow is controlled by
 parameters in the configuration file Local/eximon.conf, and the frequency at
 which it is updated is controlled by another parameter in the same file - the
 as well as those awaiting delivery. The size of this subwindow is controlled by
 parameters in the configuration file Local/eximon.conf, and the frequency at
 which it is updated is controlled by another parameter in the same file - the
@@ -33669,7 +35606,7 @@ is an "Update" action button just above the display which can be used to force
 an update of the queue display at any time.
 
 When a host is down for some time, a lot of pending mail can build up for it,
 an update of the queue display at any time.
 
 When a host is down for some time, a lot of pending mail can build up for it,
-and this can make it hard to deal with other messages on the queue. To help
+and this can make it hard to deal with other messages in the queue. To help
 with this situation there is a button next to "Update" called "Hide". If
 pressed, a dialogue box called "Hide addresses ending with" is put up. If you
 type anything in here and press "Return", the text is added to a chain of such
 with this situation there is a button next to "Update" called "Hide". If
 pressed, a dialogue box called "Hide addresses ending with" is put up. If you
 type anything in here and press "Return", the text is added to a chain of such
@@ -33690,7 +35627,7 @@ queue display to use in the dialogue box, you have to do the cutting before
 pressing the "Hide" button.
 
 The queue display contains, for each unhidden queued message, the length of
 pressing the "Hide" button.
 
 The queue display contains, for each unhidden queued message, the length of
-time it has been on the queue, the size of the message, the message id, the
+time it has been in the queue, the size of the message, the message id, the
 message sender, and the first undelivered recipient, all on one line. If it is
 a bounce message, the sender is shown as "<>". If there is more than one
 recipient to which the message has not yet been delivered, subsequent ones are
 message sender, and the first undelivered recipient, all on one line. If it is
 a bounce message, the sender is shown as "<>". If there is more than one
 recipient to which the message has not yet been delivered, subsequent ones are
@@ -33707,7 +35644,7 @@ available, but the caret is always moved to the end of the text when the queue
 display is updated.
 
 
 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
 -------------------
 
 If the shift key is held down and the left button is clicked when the mouse
@@ -33730,13 +35667,13 @@ follows:
     in a new text window.
 
   * headers: Information from the spool file that contains the envelope
     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
     displayed in a new text window. There is a default limit of 20,000 bytes to
     the amount of data displayed. This can be changed by setting the BODY_MAX
     for a description of the format of spool files.
 
   * body: The contents of the spool file containing the body of the message are
     displayed in a new text window. There is a default limit of 20,000 bytes to
     the amount of data displayed. This can be changed by setting the BODY_MAX
-    option at compile time, or the EXIMON_BODY_MAX option at run time.
+    option at compile time, or the EXIMON_BODY_MAX option at runtime.
 
   * deliver message: A call to Exim is made using the -M option to request
     delivery of the message. This causes an automatic thaw if the message is
 
   * deliver message: A call to Exim is made using the -M option to request
     delivery of the message. This causes an automatic thaw if the message is
@@ -33805,7 +35742,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.
 
 This chapter discusses a number of issues concerned with security, some of
 which are also covered in other parts of this manual.
@@ -33823,7 +35760,7 @@ absence of bugs can never be guaranteed. Any that are reported will get fixed
 as soon as possible.
 
 
 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
 ------------------------------------
 
 There are a number of build-time options that can be set in Local/Makefile to
@@ -33832,10 +35769,10 @@ administrator who does not have the root password, or by someone who has
 penetrated the Exim (but not the root) account. These options are as follows:
 
   * ALT_CONFIG_PREFIX can be set to a string that is required to match the
 penetrated the Exim (but not the root) account. These options are as follows:
 
   * ALT_CONFIG_PREFIX can be set to a string that is required to match the
-    start of any file names used with the -C option. When it is set, these file
-    names are also not allowed to contain the sequence "/../". (However, if the
-    value of the -C option is identical to the value of CONFIGURE_FILE in Local
-    /Makefile, Exim ignores -C and proceeds as usual.) There is no default
+    start of any filenames used with the -C option. When it is set, these
+    filenames are also not allowed to contain the sequence "/../". (However, if
+    the value of the -C option is identical to the value of CONFIGURE_FILE in
+    Local/Makefile, Exim ignores -C and proceeds as usual.) There is no default
     setting for ALT_CONFIG_PREFIX.
 
     If the permitted configuration files are confined to a directory to which
     setting for ALT_CONFIG_PREFIX.
 
     If the permitted configuration files are confined to a directory to which
@@ -33874,7 +35811,7 @@ penetrated the Exim (but not the root) account. These options are as follows:
     to get root.
 
 
     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
 -------------------
 
 The Exim binary is normally setuid to root, which means that it gains root
@@ -33897,7 +35834,7 @@ receiving messages and delivering them externally over SMTP, and it is
 obviously more secure if Exim does not run as root except when necessary. For
 this reason, a user and group for Exim to use must be defined in Local/Makefile
 . These are known as "the Exim user" and "the Exim group". Their values can be
 obviously more secure if Exim does not run as root except when necessary. For
 this reason, a user and group for Exim to use must be defined in Local/Makefile
 . These are known as "the Exim user" and "the Exim group". Their values can be
-changed by the run time configuration, though this is not recommended. Often a
+changed by the runtime configuration, though this is not recommended. Often a
 user called exim is used, but some sites use mail or another user name
 altogether.
 
 user called exim is used, but some sites use mail or another user name
 altogether.
 
@@ -33959,7 +35896,7 @@ The processes that initially retain root privilege behave as follows:
     the routing is done in the same environment as a message delivery.
 
 
     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
 -----------------------------------
 
 Some installations like to run Exim in an unprivileged state for more of its
@@ -34040,18 +35977,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.
 
 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.
 
 
 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.
 
 
 ------------------------------
 
 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
 ---------------------------
 
 There are a number of ways in which an administrator can configure Exim to run
@@ -34081,7 +36018,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.
 
   * 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.
 
     administrators are well advised to view its use with suspicion, in case
     (for instance) it allows a local-part to contain embedded Exim directives.
 
@@ -34094,7 +36031,7 @@ commands. Configuration to check includes, but is not limited to:
     the use of the inlisti expansion condition instead.
 
 
     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
 --------------------------------
 
 If configuration data for Exim can come from untrustworthy sources, there are
@@ -34120,7 +36057,7 @@ some issues to be aware of:
     only expected to yield one result.
 
 
     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
 ------------------------
 
 Many operating systems suppress IP source-routed packets in the kernel, but
@@ -34129,14 +36066,14 @@ IPv4 source-routed TCP calls, and then drops them. Things are all different in
 IPv6. No special checking is currently done.
 
 
 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.
 
 
 ----------------------------------------------
 
 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
 ---------------------
 
 Exim recognizes two sets of users with special privileges. Trusted users are
@@ -34179,8 +36116,13 @@ the contents of files on the spool via the Exim monitor (which runs
 unprivileged), Exim must be built to allow group read access to its spool
 files.
 
 unprivileged), Exim must be built to allow group read access to its spool
 files.
 
+By default, regular users are trusted to perform basic testing and
+introspection commands, as themselves. This setting can be tightened by setting
+the commandline_checks_require_admin option. This affects most of the checking
+options, such as -be and anything else -b*.
+
 
 
-54.10 Spool files
+55.10 Spool files
 -----------------
 
 Exim's spool directory and everything it contains is owned by the Exim user and
 -----------------
 
 Exim's spool directory and everything it contains is owned by the Exim user and
@@ -34189,7 +36131,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.
 
 
 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
 --------------------
 
 Exim examines the last component of argv[0], and if it matches one of a set of
@@ -34198,7 +36140,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.
 
 
 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
 --------------------------
 
 The only use made of "%f" by Exim is in formatting load average values. These
@@ -34207,7 +36149,7 @@ Consequently, their range is limited and so therefore is the length of the
 converted output.
 
 
 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
 ------------------------
 
 Exim uses its own path name, which is embedded in the code, only when it needs
@@ -34216,14 +36158,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.
 
 
 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.
 
 
 ------------------------------
 
 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 
 ----------------------
 
 A large number of occurrences of "sprintf" in the code are actually calls to 
@@ -34236,7 +36178,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.
 
 
 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
 -------------------------------------------
 
 Arbitrary strings are passed to both these functions, but they do their
@@ -34244,7 +36186,7 @@ formatting by calling the function string_vformat(), which runs through the
 format string itself, and checks the length of each conversion.
 
 
 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
 ----------------------------------
 
 These are used only in cases where the output buffer is known to be large
@@ -34253,7 +36195,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
 
 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
@@ -34263,6 +36205,10 @@ two files contains the final component of its own name as its first line. This
 is insurance against disk crashes where the directory is lost but the files
 themselves are recoverable.
 
 is insurance against disk crashes where the directory is lost but the files
 themselves are recoverable.
 
+The file formats may be changed, or new formats added, at any release. Spool
+files are not intended as an interface to other programs and should not be used
+as such.
+
 Some people are tempted into editing -D files in order to modify messages. You
 need to be extremely careful if you do this; it is not recommended and you are
 on your own if you do it. Here are some of the pitfalls:
 Some people are tempted into editing -D files in order to modify messages. You
 need to be extremely careful if you do this; it is not recommended and you are
 on your own if you do it. Here are some of the pitfalls:
@@ -34274,9 +36220,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
     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.
 
 
   * If the message is in MIME format, you must take care not to break it.
 
@@ -34295,8 +36240,13 @@ file remains in existence. When Exim next processes the message, it notices the
 -J file and uses it to update the -H file before starting the next delivery
 attempt.
 
 -J file and uses it to update the -H file before starting the next delivery
 attempt.
 
+Files whose names end with -K or .eml may also be seen in the spool. These are
+temporaries used for DKIM or malware processing, when that is used. They should
+be tidied up by normal operations; any old ones are probably relics of crashes
+and can be removed.
+
 
 
-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
 --------------------------
 
 The second line of the -H file contains the login name for the uid of the
@@ -34380,8 +36330,8 @@ order, and are omitted when not relevant:
 
 -body_linecount <number>
 
 
 -body_linecount <number>
 
-    This records the number of lines in the body of the message, and is always
-    present.
+    This records the number of lines in the body of the message, and is present
+    unless -spool_file_wireformat is.
 
 -body_zerocount <number>
 
 
 -body_zerocount <number>
 
@@ -34479,6 +36429,12 @@ order, and are omitted when not relevant:
     If a message was scanned by SpamAssassin, this is present. It records the
     value of $spam_score_int.
 
     If a message was scanned by SpamAssassin, this is present. It records the
     value of $spam_score_int.
 
+-spool_file_wireformat
+
+    The -D file for this message is in wire-format (for ESMTP CHUNKING) rather
+    than Unix-format. The line-ending is CRLF rather than newline. There is
+    still, however, no leading-dot-stuffing.
+
 -tls_certificate_verified
 
     A TLS certificate was received from the client that sent this message, and
 -tls_certificate_verified
 
     A TLS certificate was received from the client that sent this message, and
@@ -34582,25 +36538,46 @@ header have been rewritten, the last one because routing expanded the
 unqualified domain foundation.
 
 
 unqualified domain foundation.
 
 
+56.2 Format of the -D file
+--------------------------
+
+The data file is traditionally in Unix-standard format: lines are ended with an
+ASCII newline character. However, when the spool_wireformat main option is used
+some -D files can have an alternate format. This is flagged by a 
+-spool_file_wireformat line in the corresponding -H file. The -D file lines
+(not including the first name-component line) are suitable for direct copying
+to the wire when transmitting using the ESMTP CHUNKING option, meaning lower
+processing overhead. Lines are terminated with an ASCII CRLF pair. There is no
+dot-stuffing (and no dot-termination).
+
+
 
 ===============================================================================
 
 ===============================================================================
-56. SUPPORT FOR DKIM (DOMAINKEYS IDENTIFIED MAIL)
+57. DKIM AND SPF
+
+
+57.1 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
 
 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.
+is documented in RFC 6376.
 
 
-Since version 4.70, DKIM support is compiled into Exim by default. It can be
-disabled by setting DISABLE_DKIM=yes in Local/Makefile.
+As DKIM relies on the message being unchanged in transit, messages handled by a
+mailing-list (which traditionally adds to the message) will not match any
+original DKIM signature.
 
 
-Exim's DKIM implementation allows to
+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.
 
 
- 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.
+Exim's DKIM implementation allows for
 
 
- 2. Verify signatures in incoming messages: This is implemented by an
+ 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. 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.
 
     additional ACL (acl_smtp_dkim), which can be called several times per
     message, with different signature contexts.
 
@@ -34609,9 +36586,12 @@ default "policy". Instead it enables you to build your own policy using Exim's
 standard controls.
 
 Please note that verification of DKIM signatures in incoming mail is turned on
 standard controls.
 
 Please note that verification of DKIM signatures in incoming mail is turned on
-by default for logging purposes. For each signature in incoming email, exim
-will log a line displaying the most important signature details, and the
-signature status. Here is an example (with line-breaks added for clarity):
+by default for logging (in the <= line) purposes.
+
+Additional log detail can be enabled using the dkim_verbose log_selector. When
+set, for each signature in incoming email, exim will log a line displaying the
+most important signature details, and the signature status. Here is an example
+(with line-breaks added for clarity):
 
 2009-09-09 10:22:28 1MlIRf-0003LU-U3 DKIM:
     d=facebookmail.com s=q1-2009b
 
 2009-09-09 10:22:28 1MlIRf-0003LU-U3 DKIM:
     d=facebookmail.com s=q1-2009b
@@ -34624,85 +36604,196 @@ modifier. This should typically be done in the RCPT ACL, at points where you
 accept mail from relay sources (internal hosts or authenticated senders).
 
 
 accept mail from relay sources (internal hosts or authenticated senders).
 
 
-56.1 Signing outgoing messages
+57.2 Signing outgoing messages
 ------------------------------
 
 ------------------------------
 
-Signing is implemented by setting private options on the SMTP transport. These
+For signing to be usable you must have published a DKIM record in DNS. Note
+that RFC 8301 says:
+
+rsa-sha1 MUST NOT be used for signing or verifying.
+
+Signers MUST use RSA keys of at least 1024 bits for all keys.
+Signers SHOULD use RSA keys of at least 2048 bits.
+
+Note also that the key content (the 'p=' field) in the DNS record is different
+between RSA and EC keys; for the former it is the base64 of the ASN.1 for the
+RSA public key (equivalent to the private-key .pem with the header/trailer
+stripped) but for EC keys it is the base64 of the pure key; no ASN.1 wrapping.
+
+Signing is enabled by setting private options on the SMTP transport. These
 options take (expandable) strings as arguments.
 
 options take (expandable) strings as arguments.
 
-+-----------+---------+-------------+--------------+
-|dkim_domain|Use: smtp|Type: string*|Default: unset|
-+-----------+---------+-------------+--------------+
++-------------------------------------------------+
+|dkim_domain|Use: smtp|Type: string|Default: list*|
++-------------------------------------------------+
 
 
-MANDATORY: The domain you want to sign with. The result of this expanded option
-is put into the $dkim_domain expansion variable.
+The domain(s) you want to sign with. After expansion, this can be a list. Each
+element in turn is put into the $dkim_domain expansion variable while expanding
+the remaining signing options. If it is empty after expansion, DKIM signing is
+not done, and no error will result even if dkim_strict is set.
 
 
-+-------------+---------+-------------+--------------+
-|dkim_selector|Use: smtp|Type: string*|Default: unset|
-+-------------+---------+-------------+--------------+
++---------------------------------------------------+
+|dkim_selector|Use: smtp|Type: string|Default: list*|
++---------------------------------------------------+
 
 
-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
-option along with $dkim_domain.
+This sets the key selector string. After expansion, which can use $dkim_domain,
+this can be a list. Each element in turn is put in the expansion variable 
+$dkim_selector which may be used in the dkim_private_key option along with 
+$dkim_domain. If the option is empty after expansion, DKIM signing is not done
+for this domain, and no error will result even if dkim_strict is set.
 
 
-+----------------+---------+-------------+--------------+
++-------------------------------------------------------+
 |dkim_private_key|Use: smtp|Type: string*|Default: unset|
 |dkim_private_key|Use: smtp|Type: string*|Default: unset|
-+----------------+---------+-------------+--------------+
++-------------------------------------------------------+
 
 
-MANDATORY: This sets the private key to use. You can use the $dkim_domain and 
+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
 result can either
 
 $dkim_selector expansion variables to determine the private key to use. The
 result can either
 
-  * be a valid RSA private key in ASCII armor, including line breaks.
+  * be a valid RSA private key in ASCII armor (.pem file), including line
+    breaks
+
+  * with GnuTLS 3.6.0 or OpenSSL 1.1.1 or later, be a valid Ed25519 private key
+    (same format as above)
 
   * start with a slash, in which case it is treated as a file that contains the
 
   * start with a slash, in which case it is treated as a file that contains the
-    private key.
+    private key
 
   * 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.
 
 
   * 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.
 
-+----------+---------+-------------+--------------+
+To generate keys under OpenSSL:
+
+openssl genrsa -out dkim_rsa.private 2048
+openssl rsa -in dkim_rsa.private -out /dev/stdout -pubout -outform PEM
+
+Take the base-64 lines from the output of the second command, concatenated, for
+the DNS TXT record. See section 3.6 of RFC6376 for the record specification.
+
+Under GnuTLS:
+
+certtool --generate-privkey --rsa --bits=2048 --password='' -8 --outfile=dkim_rsa.private
+certtool --load-privkey=dkim_rsa.private --pubkey-info
+
+Note that RFC 8301 says:
+
+Signers MUST use RSA keys of at least 1024 bits for all keys.
+Signers SHOULD use RSA keys of at least 2048 bits.
+
+Support for EC keys is being developed under https://datatracker.ietf.org/doc/
+draft-ietf-dcrup-dkim-crypto/. They are considerably smaller than RSA keys for
+equivalent protection. As they are a recent development, users should consider
+dual-signing (by setting a list of selectors, and an expansion for this option)
+for some transition period. The "_CRYPTO_SIGN_ED25519" macro will be defined if
+support is present for EC keys.
+
+OpenSSL 1.1.1 and GnuTLS 3.6.0 can create Ed25519 private keys:
+
+openssl genpkey -algorithm ed25519 -out dkim_ed25519.private
+certtool --generate-privkey --key-type=ed25519 --outfile=dkim_ed25519.private
+
+To produce the required public key value for a DNS record:
+
+openssl pkey -outform DER -pubout -in dkim_ed25519.private | tail -c +13 | base64
+certtool --load_privkey=dkim_ed25519.private --pubkey_info --outder | tail -c +13 | base64
+
+Note that the format of Ed25519 keys in DNS has not yet been decided; this
+release supports both of the leading candidates at this time, a future release
+will probably drop support for whichever proposal loses.
+
++-------------------------------------------------+
+|dkim_hash|Use: smtp|Type: string*|Default: sha256|
++-------------------------------------------------+
+
+Can be set to any one of the supported hash methods, which are:
+
+  * "sha1" - should not be used, is old and insecure
+
+  * "sha256" - the default
+
+  * "sha512" - possibly more secure but less well supported
+
+Note that RFC 8301 says:
+
+rsa-sha1 MUST NOT be used for signing or verifying.
+
++----------------------------------------------------+
+|dkim_identity|Use: smtp|Type: string*|Default: unset|
++----------------------------------------------------+
+
+If set after expansion, the value is used to set an "i=" tag in the signing
+header. The DKIM standards restrict the permissible syntax of this optional tag
+to a mail address, with possibly-empty local part, an @, and a domain identical
+to or subdomain of the "d=" tag value. Note that Exim does not check the value.
+
++-------------------------------------------------+
 |dkim_canon|Use: smtp|Type: string*|Default: unset|
 |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.
+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 signing with the same canonicalization method for both headers and
+body.
 
 
-+-----------+---------+-------------+--------------+
++--------------------------------------------------+
 |dkim_strict|Use: smtp|Type: string*|Default: unset|
 |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.
+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|
-+-----------------+---------+-------------+--------------+
++------------------------------------------------------------+
+|dkim_sign_headers|Use: smtp|Type: string*|Default: see below|
++------------------------------------------------------------+
 
 
-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
-in the message signature. When unspecified, the header names recommended in
-RFC4871 will be used.
+If set, this option must expand to a colon-separated list of header names.
+Headers with these names, or the absence or such a header, will be included in
+the message signature. When unspecified, the header names listed in RFC4871
+will be used, whether or not each header is present in the message. The default
+list is available for the expansion in the macro "_DKIM_SIGN_HEADERS".
 
 
+If a name is repeated, multiple headers by that name (or the absence thereof)
+will be signed. The textually later headers in the headers part of the message
+are signed first, if there are multiples.
 
 
-56.2 Verifying DKIM signatures in incoming mail
+A name can be prefixed with either an '=' or a '+' character. If an '=' prefix
+is used, all headers that are present with this name will be signed. If a '+'
+prefix if used, all headers that are present with this name will be signed, and
+one signature added for a missing header with the name will be appended.
+
++-------------------------------------------------------+
+|dkim_timestamps|Use: smtp|Type: integer*|Default: unset|
++-------------------------------------------------------+
+
+This option controls the inclusion of timestamp information in the signature.
+If not set, no such information will be included. Otherwise, must be an
+unsigned number giving an offset in seconds from the current time for the
+expiry tag (eg. 1209600 for two weeks); both creation (t=) and expiry (x=) tags
+will be included.
+
+RFC 6376 lists these tags as RECOMMENDED.
+
+
+57.3 Verifying DKIM signatures in incoming mail
 -----------------------------------------------
 
 -----------------------------------------------
 
-Verification of DKIM signatures in incoming email is implemented via the 
-acl_smtp_dkim ACL. By default, this ACL is called once for each syntactically
-(!) correct signature in the incoming message. A missing ACL definition
-defaults to accept. If any ACL call does not acccept, the message is not
-accepted. If a cutthrough delivery was in progress for the message it is
-summarily dropped (having wasted the transmission effort).
+Verification of DKIM signatures in SMTP incoming email is done for all messages
+for which an ACL control dkim_disable_verify has not been set. Performing
+verification sets up information used by the $authresults expansion item.
+
+The acl_smtp_dkim ACL, which can examine and modify them. 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
+accept, the message is not accepted. If a cutthrough delivery was in progress
+for the message, that is summarily dropped (having wasted the transmission
+effort).
 
 
-To evaluate the signature in the ACL a large number of expansion variables
-containing the signature status and its details are set up during the runtime
-of the ACL.
+To evaluate the verification result in the ACL a large number of expansion
+variables containing the signature status and its details are set up during the
+runtime of the ACL.
 
 Calling the ACL only for existing signatures is not sufficient to build more
 advanced policies. For that reason, the global option dkim_verify_signers, and
 
 Calling the ACL only for existing signatures is not sufficient to build more
 advanced policies. For that reason, the global option dkim_verify_signers, and
@@ -34733,6 +36824,9 @@ dkim_verify_signers = $sender_address_domain:$dkim_signers
 If a domain or identity is listed several times in the (expanded) value of 
 dkim_verify_signers, the ACL is only called once for that domain or identity.
 
 If a domain or identity is listed several times in the (expanded) value of 
 dkim_verify_signers, the ACL is only called once for that domain or identity.
 
+If multiple signatures match a domain (or identity), the ACL is called once for
+each matching signature.
+
 Inside the acl_smtp_dkim, the following expansion variables are available (from
 most to least important):
 
 Inside the acl_smtp_dkim, the following expansion variables are available (from
 most to least important):
 
@@ -34744,40 +36838,61 @@ $dkim_cur_signer
 
 $dkim_verify_status
 
 
 $dkim_verify_status
 
-    A string describing the general status of the signature. One of
+    Within the DKIM ACL, 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).
 
         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.
 
         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.
 
         $dkim_verify_reason.
 
-      * pass: The signature passed verification. It is valid.
+      + pass: The signature passed verification. It is valid.
+
+    This variable can be overwritten using an ACL 'set' modifier. This might,
+    for instance, be done to enforce a policy restriction on hash-method or
+    key-size:
+
+      warn condition       = ${if eq {$dkim_verify_status}{pass}}
+           condition       = ${if eq {${length_3:$dkim_algo}}{rsa}}
+           condition       = ${if or {{eq {$dkim_algo}{rsa-sha1}} \
+                                      {< {$dkim_key_length}{1024}}}}
+           logwrite        = NOTE: forcing DKIM verify fail (was pass)
+           set dkim_verify_status = fail
+           set dkim_verify_reason = hash too weak or key too short
+
+    So long as a DKIM ACL is defined (it need do no more than accept), after
+    all the DKIM ACL runs have completed, the value becomes a colon-separated
+    list of the values after each run. This is maintained for the mime, prdr
+    and data ACLs.
 
 $dkim_verify_reason
 
 
 $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
 
     "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.
 
         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.
 
         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.
 
         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
         forged.
 
         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
         forged.
 
+    This variable can be overwritten, with any value, using an ACL 'set'
+    modifier.
+
 $dkim_domain
 
     The signing domain. IMPORTANT: This variable is only populated if there is
 $dkim_domain
 
     The signing domain. IMPORTANT: This variable is only populated if there is
@@ -34796,20 +36911,36 @@ $dkim_selector
 
 $dkim_algo
 
 
 $dkim_algo
 
-    The algorithm used. One of 'rsa-sha1' or 'rsa-sha256'.
+    The algorithm used. One of 'rsa-sha1' or 'rsa-sha256'. If running under
+    GnuTLS 3.6.0 or OpenSSL 1.1.1 or later, may also be 'ed25519-sha256'. The
+    "_CRYPTO_SIGN_ED25519" macro will be defined if support is present for EC
+    keys.
+
+    Note that RFC 8301 says:
+
+    rsa-sha1 MUST NOT be used for signing or verifying.
+
+    DKIM signatures identified as having been signed with historic
+    algorithms (currently, rsa-sha1) have permanently failed evaluation
+
+    To enforce this you must have a DKIM ACL which checks this variable and
+    overwrites the $dkim_verify_status variable as discussed above.
 
 $dkim_canon_body
 
     The body canonicalization method. One of 'relaxed' or 'simple'.
 
 
 $dkim_canon_body
 
     The body canonicalization method. One of 'relaxed' or 'simple'.
 
-dkim_canon_headers
+$dkim_canon_headers
 
     The header canonicalization method. One of 'relaxed' or 'simple'.
 
 $dkim_copiedheaders
 
     A transcript of headers and their values which are included in the
 
     The header canonicalization method. One of 'relaxed' or 'simple'.
 
 $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
 
 
 $dkim_bodylength
 
@@ -34817,6 +36948,11 @@ $dkim_bodylength
     limit was set by the signer, "9999999999999" is returned. This makes sure
     that this variable always expands to an integer value.
 
     limit was set by the signer, "9999999999999" is returned. This makes sure
     that this variable always expands to an integer value.
 
+    Note: The presence of the signature tag specifying a signing body length is
+    one possible route to spoofing of valid DKIM signatures. A paranoid
+    implementation might wish to regard signature where this variable shows
+    less than the "no limit" return as being invalid.
+
 $dkim_created
 
     UNIX timestamp reflecting the date and time when the signature was created.
 $dkim_created
 
     UNIX timestamp reflecting the date and time when the signature was created.
@@ -34827,7 +36963,8 @@ $dkim_expires
     UNIX timestamp reflecting the date and time when the signer wants the
     signature to be treated as "expired". When this was not specified by the
     signer, "9999999999999" is returned. This makes it possible to do useful
     UNIX timestamp reflecting the date and time when the signer wants the
     signature to be treated as "expired". When this was not specified by the
     signer, "9999999999999" is returned. This makes it possible to do useful
-    integer size comparisons against this value.
+    integer size comparisons against this value. Note that Exim does not check
+    this value.
 
 $dkim_headernames
 
 
 $dkim_headernames
 
@@ -34855,6 +36992,19 @@ $dkim_key_notes
 
     Notes from the key record (tag n=).
 
 
     Notes from the key record (tag n=).
 
+$dkim_key_length
+
+    Number of bits in the key.
+
+    Note that RFC 8301 says:
+
+    Verifiers MUST NOT consider signatures using RSA keys of
+    less than 1024 bits as valid signatures.
+
+    To enforce this you must have a DKIM ACL which checks this variable and
+    overwrites the $dkim_verify_status variable as discussed above. As EC keys
+    are much smaller, the check should only do this for RSA keys.
+
 In addition, two ACL conditions are provided:
 
 dkim_signers
 In addition, two ACL conditions are provided:
 
 dkim_signers
@@ -34864,12 +37014,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:
 
     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
 
          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
 dkim_status
 
     ACL condition that checks a colon-separated list of possible DKIM
@@ -34887,9 +37040,493 @@ dkim_status
     above for more information of what they mean.
 
 
     above for more information of what they mean.
 
 
+57.4 SPF (Sender Policy Framework)
+----------------------------------
+
+SPF is a mechanism whereby a domain may assert which IP addresses may transmit
+messages with its domain in the envelope from, documented by RFC 7208. For more
+information on SPF see http://www.openspf.org.
+
+Messages sent by a system not authorised will fail checking of such assertions.
+This includes retransmissions done by traditional forwarders.
+
+SPF verification support is built into Exim if SUPPORT_SPF=yes is set in Local/
+Makefile. The support uses the libspf2 library https://www.libspf2.org/. There
+is no Exim involvement in the transmission of messages; publishing certain DNS
+records is all that is required.
+
+For verification, an ACL condition and an expansion lookup are provided. 
+Performing verification sets up information used by the $authresults expansion
+item.
+
+The ACL condition "spf" can be used at or after the MAIL ACL. It takes as an
+argument a list of strings giving the outcome of the SPF check, and will
+succeed for any matching outcome. Valid strings are:
+
+pass
+
+    The SPF check passed, the sending host is positively verified by SPF.
+
+fail
+
+    The SPF check failed, the sending host is NOT allowed to send mail for the
+    domain in the envelope-from address.
+
+softfail
+
+    The SPF check failed, but the queried domain can't absolutely confirm that
+    this is a forgery.
+
+none
+
+    The queried domain does not publish SPF records.
+
+neutral
+
+    The SPF check returned a "neutral" state. This means the queried domain has
+    published a SPF record, but wants to allow outside servers to send mail
+    under its domain as well. This should be treated like "none".
+
+permerror
+
+    This indicates a syntax error in the SPF record of the queried domain. You
+    may deny messages when this occurs.
+
+temperror
+
+    This indicates a temporary error during all processing, including Exim's
+    SPF processing. You may defer messages when this occurs.
+
+You can prefix each string with an exclamation mark to invert its meaning, for
+example "!fail" will match all results but "fail". The string list is evaluated
+left-to-right, in a short-circuit fashion.
+
+Example:
+
+deny spf = fail
+     message = $sender_host_address is not allowed to send mail from \
+               ${if def:sender_address_domain \
+                    {$sender_address_domain}{$sender_helo_name}}.  \
+               Please see http://www.openspf.org/Why?scope=\
+               ${if def:sender_address_domain {mfrom}{helo}};\
+               identity=${if def:sender_address_domain \
+                             {$sender_address}{$sender_helo_name}};\
+               ip=$sender_host_address
+
+When the spf condition has run, it sets up several expansion variables:
+
+$spf_header_comment
+
+    This contains a human-readable string describing the outcome of the SPF
+    check. You can add it to a custom header or use it for logging purposes.
+
+$spf_received
+
+    This contains a complete Received-SPF: header that can be added to the
+    message. Please note that according to the SPF draft, this header must be
+    added at the top of the header list. Please see section 10 on how you can
+    do this.
+
+    Note: in case of "Best-guess" (see below), the convention is to put this
+    string in a header called X-SPF-Guess: instead.
+
+$spf_result
+
+    This contains the outcome of the SPF check in string form, one of pass,
+    fail, softfail, none, neutral, permerror or temperror.
+
+$spf_result_guessed
+
+    This boolean is true only if a best-guess operation was used and required
+    in order to obtain a result.
+
+$spf_smtp_comment
+
+    This contains a string that can be used in a SMTP response to the calling
+    party. Useful for "fail".
+
+In addition to SPF, you can also perform checks for so-called "Best-guess".
+Strictly speaking, "Best-guess" is not standard SPF, but it is supported by the
+same framework that enables SPF capability. Refer to http://www.openspf.org/FAQ
+/Best_guess_record for a description of what it means.
+
+To access this feature, simply use the spf_guess condition in place of the spf
+one. For example:
+
+deny spf_guess = fail
+     message = $sender_host_address doesn't look trustworthy to me
+
+In case you decide to reject messages based on this check, you should note that
+although it uses the same framework, "Best-guess" is not SPF, and therefore you
+should not mention SPF at all in your reject message.
+
+When the spf_guess condition has run, it sets up the same expansion variables
+as when spf condition is run, described above.
+
+Additionally, since Best-guess is not standardized, you may redefine what
+"Best-guess" means to you by redefining the main configuration spf_guess
+option. For example, the following:
+
+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 can appear in $spf_result
+(pass,fail,softfail,neutral,none,err_perm,err_temp). Currently, only IPv4
+addresses are supported.
+
+
+
+===============================================================================
+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 the HAProxy specification, found at https://www.haproxy.org/
+download/1.8/doc/proxy-protocol.txt.
+
+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.
+
+
 
 ===============================================================================
 
 ===============================================================================
-57. ADDING NEW DRIVERS OR LOOKUP TYPES
+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}
+
+The RCPT ACL 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.
+
+The smtp transport has an option utf8_downconvert. If set it must expand to one
+of the three values described above, and it overrides any previously set value.
+
+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 occurrences 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:
+
+dane:fail              after    transport   per connection
+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    transport   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).
+
+The third column in the table above says what section of the configuration
+should define the event action.
+
+An additional variable, $event_data, is filled with information varying with
+the event type:
+
+dane:fail             failure reason
+msg:delivery          smtp confirmation message
+msg:fail:internal     failure reason
+msg:fail:delivery     smtp error 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:
+
+tcp:connect        do not connect
+tls:cert           refuse verification
+smtp:connect       close connection
+
+All other message types ignore the result string, and no other use is made of
+it.
+
+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:
 
 The following actions have to be taken in order to add a new router, transport,
 authenticator, or lookup type to Exim:
@@ -34923,9 +37560,12 @@ authenticator, or lookup type to Exim:
     , src/auths, or src/lookups); add a line for the new driver or lookup type
     and add it to the definition of OBJ.
 
     , src/auths, or src/lookups); add a line for the new driver or lookup type
     and add it to the definition of OBJ.
 
- 7. Create newdriver.h and newdriver.c in the appropriate sub-directory of src.
+ 7. Edit OS/Makefile-Base adding a .o file for the predefined-macros, to the
+    definition of OBJ_MACRO. Add a set of line to do the compile also.
+
+ 8. Create newdriver.h and newdriver.c in the appropriate sub-directory of src.
 
 
8. Edit scripts/MakeLinks and add commands to link the .h and .c files as for
9. Edit scripts/MakeLinks and add commands to link the .h and .c files as for
     other drivers and lookups.
 
 Then all you need to do is write the code! A good way to start is to make a
     other drivers and lookups.
 
 Then all you need to do is write the code! A good way to start is to make a
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.
  */
  *       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
  * 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);
  * 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:
  *
  *
  * 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.
  */
  * 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);
 }
 
   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.
  *     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.
  * 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
  *                "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.
 
 /*     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 */
  */
 
 /* ARGSUSED */
@@ -468,7 +468,7 @@ struct SearchAndReplace * search;
   text.firstPos = 0;
   text.format = FMT8BIT;
 
   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);
                                R_OFFSET);
 
   pos = XawTextSearch( tw, dir, &text);
@@ -628,7 +628,7 @@ XEvent *event;
  *
  * NOTE:
  *
  *
  * 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.
  *
  * form - the from widget that is the dialog.
  * ptr - the initial string for the dialog's text widget.
index 671bd7f..0a4f92e 100644 (file)
@@ -2,7 +2,7 @@
 *                Exim Monitor                    *
 *************************************************/
 
 *                Exim Monitor                    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2012 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -139,16 +139,18 @@ uschar *dkim_signers             = NULL;
 uschar *dkim_signing_domain      = NULL;
 uschar *dkim_signing_selector    = NULL;
 uschar *dkim_verify_signers      = US"$dkim_signers";
 uschar *dkim_signing_domain      = NULL;
 uschar *dkim_signing_selector    = NULL;
 uschar *dkim_verify_signers      = US"$dkim_signers";
-BOOL    dkim_collect_input       = FALSE;
+unsigned dkim_collect_input      = 0;
 BOOL    dkim_disable_verify      = FALSE;
 #endif
 
 BOOL    dont_deliver           = FALSE;
 
 BOOL    dkim_disable_verify      = FALSE;
 #endif
 
 BOOL    dont_deliver           = FALSE;
 
-#ifdef EXPERIMENTAL_DSN
 int     dsn_ret                = 0;
 uschar *dsn_envid              = NULL;
 int     dsn_ret                = 0;
 uschar *dsn_envid              = NULL;
-#endif
+
+struct global_flags f = {
+ .sender_local         = FALSE,
+};
 
 #ifdef WITH_CONTENT_SCAN
 int     fake_response          = OK;
 
 #ifdef WITH_CONTENT_SCAN
 int     fake_response          = OK;
@@ -189,13 +191,15 @@ uid_t   originator_uid;
 
 uschar *primary_hostname       = NULL;
 
 
 uschar *primary_hostname       = NULL;
 
+uschar *queue_name             = US"";
+
 int     received_count         = 0;
 uschar *received_protocol      = NULL;
 int     received_count         = 0;
 uschar *received_protocol      = NULL;
-int     received_time          = 0;
+struct timeval received_time   = { 0, 0 };
 int     recipients_count       = 0;
 recipient_item *recipients_list = NULL;
 int     recipients_list_max    = 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;
 
 uschar *sender_address         = NULL;
 uschar *sender_fullhost        = NULL;
@@ -205,7 +209,6 @@ uschar *sender_host_authenticated = NULL;
 uschar *sender_host_name       = NULL;
 int     sender_host_port       = 0;
 uschar *sender_ident           = NULL;
 uschar *sender_host_name       = NULL;
 int     sender_host_port       = 0;
 uschar *sender_ident           = NULL;
-BOOL    sender_local           = FALSE;
 BOOL    sender_set_untrusted   = FALSE;
 uschar *smtp_active_hostname   = NULL;
 
 BOOL    sender_set_untrusted   = FALSE;
 uschar *smtp_active_hostname   = NULL;
 
@@ -217,9 +220,13 @@ int     string_datestamp_type  = -1;
 
 BOOL    timestamps_utc         = FALSE;
 tls_support tls_in = {
 
 BOOL    timestamps_utc         = FALSE;
 tls_support tls_in = {
-1,   /* tls_active */
{-1}, /* tls_active */
  0,    /* bits */
  FALSE,        /* tls_certificate_verified */
  0,    /* bits */
  FALSE,        /* tls_certificate_verified */
+#ifdef SUPPORT_DANE
+ FALSE, /* dane_verified */
+ 0,     /* tlsa_usage */
+#endif
  NULL, /* tls_cipher */
  FALSE,        /* tls_on_connect */
  NULL, /* tls_on_connect_ports */
  NULL, /* tls_cipher */
  FALSE,        /* tls_on_connect */
  NULL, /* tls_on_connect_ports */
index ed95716..ada9f36 100644 (file)
@@ -87,11 +87,13 @@ anything. */
 
 #include <pcre.h>
 
 
 #include <pcre.h>
 
-/* Includes from the main source of Exim. We need to have MAXPACKET defined for
-the benefit of structs.h. One of these days I should tidy up this interface so
-that this kind of kludge isn't needed. */
+/* Includes from the main source of Exim.  One of these days I should tidy up
+this interface so that this kind of kludge isn't needed. */
 
 
-#define MAXPACKET 1024
+#ifndef NS_MAXMSG
+# define NS_MAXMSG 65535
+#endif
+typedef void hctx;
 
 #include "config.h"
 #include "mytypes.h"
 
 #include "config.h"
 #include "mytypes.h"
@@ -99,6 +101,7 @@ that this kind of kludge isn't needed. */
 
 #include "local_scan.h"
 #include "structs.h"
 
 #include "local_scan.h"
 #include "structs.h"
+#include "blob.h"
 #include "globals.h"
 #include "dbstuff.h"
 #include "functions.h"
 #include "globals.h"
 #include "dbstuff.h"
 #include "functions.h"
index 6efd9c0..52eef6b 100644 (file)
@@ -2,7 +2,7 @@
 *                 Exim Monitor                   *
 *************************************************/
 
 *                 Exim Monitor                   *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* This module contains code for scanning the main log,
 /* 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)
   {
 
 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)
     {
 
   while (Ufgets(buffer, log_buffer_len, LOG) != NULL)
     {
@@ -277,12 +281,8 @@ if (LOG != NULL)
     if (strstric(buffer, US"frozen", FALSE) != NULL)
       {
       queue_item *qq = find_queue(id, queue_noop, 0);
     if (strstric(buffer, US"frozen", FALSE) != NULL)
       {
       queue_item *qq = find_queue(id, queue_noop, 0);
-      if (qq != NULL)
-        {
-        if (strstric(buffer, US"unfrozen", FALSE) != NULL)
-          qq->frozen = FALSE;
-        else qq->frozen = TRUE;
-        }
+      if (qq)
+        qq->frozen = strstric(buffer, US"unfrozen", FALSE) == NULL;
       }
 
     /* Notice defer messages, and add the destination if it
       }
 
     /* Notice defer messages, and add the destination if it
@@ -393,7 +393,11 @@ if (LOG == NULL ||
     {
     if (LOG != NULL) fclose(LOG);
     LOG = TEST;
     {
     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;
     }
   }
     log_inode = statdata.st_ino;
     }
   }
index d210a07..7aa760e 100644 (file)
@@ -2,7 +2,7 @@
 *                  Exim Monitor                  *
 *************************************************/
 
 *                  Exim Monitor                  *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2012 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
 /* 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. */
 
 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 */
 big_buffer = store_get(big_buffer_size);
 
 /* Set up the version string and date and output them */
@@ -632,7 +631,7 @@ signal(SIGCHLD, sigchld_handler);
 
 /* Get the buffer for storing the string for the log display. */
 
 
 /* Get the buffer for storing the string for the log display. */
 
-log_display_buffer = (uschar *)store_malloc(log_buffer_size);
+log_display_buffer = US store_malloc(log_buffer_size);
 log_display_buffer[0] = 0;
 
 /* Initialize the data structures for the stripcharts */
 log_display_buffer[0] = 0;
 
 /* Initialize the data structures for the stripcharts */
@@ -670,8 +669,14 @@ if (log_file[0] != 0)
     {
     fseek(LOG, 0, SEEK_END);
     log_position = ftell(LOG);
     {
     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
     }
   }
 else
index 9d3ca1c..31ce1a3 100644 (file)
@@ -2,7 +2,7 @@
 *                  Exim Monitor                  *
 *************************************************/
 
 *                  Exim Monitor                  *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -133,32 +133,33 @@ menu_is_up = FALSE;
 *          Display the message log               *
 *************************************************/
 
 *          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;
 {
 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 */
 
 
 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
   {
 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);
   }
 }
   fclose(f);
   }
 }
@@ -169,11 +170,11 @@ else
 *          Display the message body               *
 *************************************************/
 
 *          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;
 {
 int i;
-uschar buffer[256];
-Widget text = text_create((uschar *)client_data, text_depth);
+Widget text = text_create(US client_data, text_depth);
 FILE *f = NULL;
 
 w = w;      /* Keep picky compilers happy */
 FILE *f = NULL;
 
 w = w;      /* Keep picky compilers happy */
@@ -181,19 +182,21 @@ call_data = call_data;
 
 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/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 ? (US 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
   {
   }
 
 if (f == NULL)
   text_showf(text, "Failed to open file: %s\n", strerror(errno));
 else
   {
+  uschar buffer[256];
   int count = 0;
   int count = 0;
-  while (Ufgets(buffer, 256, f) != NULL)
+
+  while (Ufgets(buffer, sizeof(buffer), f) != NULL)
     {
     text_show(text, buffer);
     count += Ustrlen(buffer);
     {
     text_show(text, buffer);
     count += Ustrlen(buffer);
@@ -273,8 +276,12 @@ if (pipe(pipe_fd) != 0)
   return;
   }
 
   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
 
 /* 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
@@ -327,9 +334,9 @@ if (!delivery)
   if (rc == 0 && Ustrcmp(action + Ustrlen(action) - 4, "-Mes") == 0)
     {
     queue_item *q = find_queue(id, queue_noop, 0);
   if (rc == 0 && Ustrcmp(action + Ustrlen(action) - 4, "-Mes") == 0)
     {
     queue_item *q = find_queue(id, queue_noop, 0);
-    if (q != NULL)
+    if (q)
       {
       {
-      if (q->sender != NULL) store_free(q->sender);
+      if (q->sender) store_free(q->sender);
       q->sender = store_malloc(Ustrlen(address_arg) + 1);
       Ustrcpy(q->sender, address_arg);
       }
       q->sender = store_malloc(Ustrlen(address_arg) + 1);
       Ustrcpy(q->sender, address_arg);
       }
@@ -373,7 +380,7 @@ if ((pid = fork()) == 0)
 
 /* Main process - set up an item for the main ticker to watch. */
 
 
 /* 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));
 
   {
   pipe_item *p = (pipe_item *)store_malloc(sizeof(pipe_item));
 
@@ -404,7 +411,7 @@ static void deliverAction(Widget w, XtPointer client_data, XtPointer call_data)
 {
 w = w;      /* Keep picky compilers happy */
 call_data = call_data;
 {
 w = w;      /* Keep picky compilers happy */
 call_data = call_data;
-ActOnMessage((uschar *)client_data, US"-v -M", US"");
+ActOnMessage(US client_data, US"-v -M", US"");
 }
 
 
 }
 
 
@@ -417,7 +424,7 @@ static void freezeAction(Widget w, XtPointer client_data, XtPointer call_data)
 {
 w = w;      /* Keep picky compilers happy */
 call_data = call_data;
 {
 w = w;      /* Keep picky compilers happy */
 call_data = call_data;
-ActOnMessage((uschar *)client_data, US"-Mf", US"");
+ActOnMessage(US client_data, US"-Mf", US"");
 }
 
 
 }
 
 
@@ -430,7 +437,7 @@ static void thawAction(Widget w, XtPointer client_data, XtPointer call_data)
 {
 w = w;      /* Keep picky compilers happy */
 call_data = call_data;
 {
 w = w;      /* Keep picky compilers happy */
 call_data = call_data;
-ActOnMessage((uschar *)client_data, US"-Mt", US"");
+ActOnMessage(US client_data, US"-Mt", US"");
 }
 
 
 }
 
 
@@ -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;
 {
 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"");
 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;
 {
 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"");
 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;
 {
 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;
 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);
 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);
 action_required = US"-Mes";
 dialog_ref_widget = menushell;
 create_dialog(US"New sender address?", sender);
@@ -614,7 +624,7 @@ static void giveupAction(Widget w, XtPointer client_data, XtPointer call_data)
 {
 w = w;      /* Keep picky compilers happy */
 call_data = call_data;
 {
 w = w;      /* Keep picky compilers happy */
 call_data = call_data;
-ActOnMessage((uschar *)client_data, US"-v -Mg", US"");
+ActOnMessage(US client_data, US"-v -Mg", US"");
 }
 
 
 }
 
 
@@ -627,7 +637,7 @@ static void removeAction(Widget w, XtPointer client_data, XtPointer call_data)
 {
 w = w;      /* Keep picky compilers happy */
 call_data = call_data;
 {
 w = w;      /* Keep picky compilers happy */
 call_data = call_data;
-ActOnMessage((uschar *)client_data, US"-Mrm", US"");
+ActOnMessage(US client_data, US"-Mrm", US"");
 }
 
 
 }
 
 
@@ -640,7 +650,7 @@ static void headersAction(Widget w, XtPointer client_data, XtPointer call_data)
 {
 uschar buffer[256];
 header_line *h, *next;
 {
 uschar buffer[256];
 header_line *h, *next;
-Widget text = text_create((uschar *)client_data, text_depth);
+Widget text = text_create(US client_data, text_depth);
 void *reset_point;
 
 w = w;      /* Keep picky compilers happy */
 void *reset_point;
 
 w = w;      /* Keep picky compilers happy */
@@ -651,7 +661,7 @@ Then use Exim's function to read the header. */
 
 reset_point = store_get(0);
 
 
 reset_point = store_get(0);
 
-sprintf(CS buffer, "%s-H", (uschar *)client_data);
+sprintf(CS buffer, "%s-H", US client_data);
 if (spool_read_header(buffer, TRUE, FALSE) != spool_read_OK)
   {
   if (errno == ERRNO_SPOOLFORMAT)
 if (spool_read_header(buffer, TRUE, FALSE) != spool_read_OK)
   {
   if (errno == ERRNO_SPOOLFORMAT)
@@ -670,7 +680,7 @@ if (spool_read_header(buffer, TRUE, FALSE) != spool_read_OK)
 
 if (sender_address != NULL)
   {
 
 if (sender_address != NULL)
   {
-  text_showf(text, "%s sender: <%s>\n", sender_local? "Local" : "Remote",
+  text_showf(text, "%s sender: <%s>\n", f.sender_local ? "Local" : "Remote",
     sender_address);
   }
 
     sender_address);
   }
 
index c01a80f..c8d9a40 100644 (file)
@@ -2,7 +2,7 @@
 *                 Exim Monitor                   *
 *************************************************/
 
 *                 Exim Monitor                   *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -63,7 +63,8 @@ if it is dest_remove, remove if present and return NULL. The
 address is lowercased to start with, unless it begins with
 "*", which it does for error messages. */
 
 address is lowercased to start with, unless it begins with
 "*", which it does for error messages. */
 
-dest_item *find_dest(queue_item *q, uschar *name, int action, BOOL caseless)
+dest_item *
+find_dest(queue_item *q, uschar *name, int action, BOOL caseless)
 {
 dest_item *dd;
 dest_item **d = &(q->destinations);
 {
 dest_item *dd;
 dest_item **d = &(q->destinations);
@@ -108,7 +109,8 @@ return dd;
 *            Clean up a dead queue item          *
 *************************************************/
 
 *            Clean up a dead queue item          *
 *************************************************/
 
-static void clean_up(queue_item *p)
+static void
+clean_up(queue_item *p)
 {
 dest_item *dd = p->destinations;
 while (dd != NULL)
 {
 dest_item *dd = p->destinations;
 while (dd != NULL)
@@ -149,7 +151,8 @@ return node;
 *             Set up new queue item              *
 *************************************************/
 
 *             Set up new queue item              *
 *************************************************/
 
-static queue_item *set_up(uschar *name, int dir_char)
+static queue_item *
+set_up(uschar *name, int dir_char)
 {
 int i, rc, save_errno;
 struct stat statdata;
 {
 int i, rc, save_errno;
 struct stat statdata;
@@ -162,7 +165,7 @@ uschar buffer[256];
 
 q->next = q->prev = NULL;
 q->destinations = NULL;
 
 q->next = q->prev = NULL;
 q->destinations = NULL;
-Ustrcpy(q->name, name);
+Ustrncpy(q->name, name, sizeof(q->name));
 q->seen = TRUE;
 q->frozen = FALSE;
 q->dir_char = dir_char;
 q->seen = TRUE;
 q->frozen = FALSE;
 q->dir_char = dir_char;
@@ -201,7 +204,7 @@ if it's there. */
 
 else
   {
 
 else
   {
-  q->update_time = q->input_time = received_time;
+  q->update_time = q->input_time = received_time.tv_sec;
   if ((p = strstric(sender_address+1, qualify_domain, FALSE)) != NULL &&
     *(--p) == '@') *p = 0;
   }
   if ((p = strstric(sender_address+1, qualify_domain, FALSE)) != NULL &&
     *(--p) == '@') *p = 0;
   }
@@ -231,7 +234,7 @@ if (rc != spool_read_OK)
     }
   else
     {
     }
   else
     {
-    deliver_freeze = FALSE;
+    f.deliver_freeze = FALSE;
     sender_address = msg;
     recipients_count = 0;
     }
     sender_address = msg;
     recipients_count = 0;
     }
@@ -239,9 +242,9 @@ if (rc != spool_read_OK)
 
 /* Now set up the remaining data. */
 
 
 /* Now set up the remaining data. */
 
-q->frozen = deliver_freeze;
+q->frozen = f.deliver_freeze;
 
 
-if (sender_set_untrusted)
+if (f.sender_set_untrusted)
   {
   if (sender_address[0] == 0)
     {
   {
   if (sender_address[0] == 0)
     {
@@ -263,7 +266,8 @@ else
 
 sender_address = NULL;
 
 
 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;
 
 if (Ustat(buffer, &statdata) == 0)
   q->size = message_size + statdata.st_size - SPOOL_DATA_START_OFFSET + 1;
 
@@ -271,7 +275,6 @@ if (Ustat(buffer, &statdata) == 0)
 been delivered, and removing visible names. */
 
 if (recipients_list != NULL)
 been delivered, and removing visible names. */
 
 if (recipients_list != NULL)
-  {
   for (i = 0; i < recipients_count; i++)
     {
     uschar *r = recipients_list[i].address;
   for (i = 0; i < recipients_count; i++)
     {
     uschar *r = recipients_list[i].address;
@@ -282,7 +285,6 @@ if (recipients_list != NULL)
       (void)find_dest(q, r, dest_add, FALSE);
       }
     }
       (void)find_dest(q, r, dest_add, FALSE);
       }
     }
-  }
 
 /* Recover the dynamic store used by spool_read_header(). */
 
 
 /* Recover the dynamic store used by spool_read_header(). */
 
@@ -332,7 +334,8 @@ while (p != NULL)
 
 
 
 
 
 
-queue_item *find_queue(uschar *name, int action, int dir_char)
+queue_item *
+find_queue(uschar *name, int action, int dir_char)
 {
 int first = 0;
 int last = queue_index_size - 1;
 {
 int first = 0;
 int last = queue_index_size - 1;
@@ -617,11 +620,13 @@ uschar buffer[1024];
 
 message_subdir[0] = p->dir_char;
 
 
 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;
   }
   if (Ustat(buffer, &statdata) < 0 || p->update_time == statdata.st_mtime)
     return;
   }
@@ -655,32 +660,23 @@ if (jread != NULL)
 been delivered, and removing visible names. In the nonrecipients tree,
 domains are lower cased. */
 
 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++)
     {
   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);
     }
       (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
 
 /* 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_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 */
 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. */
 
 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};
 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;
 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. */
 
 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;
 
 {
 Widget chart;
 
@@ -249,7 +251,7 @@ xs_SetValues(chart, 11,
   XtNfromVert,  label);
 
 XtAddCallback(chart, "getValue", stripchartAction,
   XtNfromVert,  label);
 
 XtAddCallback(chart, "getValue", stripchartAction,
-  (XtPointer)stripchart_count);
+  (XtPointer)(long)stripchart_count);
 
 stripchart_last_total[stripchart_count] = 0;
 stripchart_max[stripchart_count] = 10;
 
 stripchart_last_total[stripchart_count] = 0;
 stripchart_max[stripchart_count] = 10;
index a10aac4..ff9ac5c 100644 (file)
@@ -2,7 +2,7 @@
 *                  Exim Monitor                  *
 *************************************************/
 
 *                  Exim Monitor                  *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "mytypes.h"
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "mytypes.h"
@@ -10,6 +10,8 @@
 #include <string.h>
 #include <stdlib.h>
 
 #include <string.h>
 #include <stdlib.h>
 
+#include "version.h"
+
 extern uschar *version_string;
 extern uschar *version_date;
 
 extern uschar *version_string;
 extern uschar *version_date;
 
@@ -21,17 +23,28 @@ uschar today[20];
 
 version_string = US"2.06";
 
 
 version_string = US"2.06";
 
+#ifdef EXIM_BUILD_DATE_OVERRIDE
+/* Reproducible build support; build tooling should have given us something looking like
+ * "25-Feb-2017 20:15:40" in EXIM_BUILD_DATE_OVERRIDE based on $SOURCE_DATE_EPOCH in environ
+ * per <https://reproducible-builds.org/specs/source-date-epoch/>
+ */
+version_date = US malloc(32);
+version_date[0] = 0;
+Ustrncat(version_date, EXIM_BUILD_DATE_OVERRIDE, 31);
+
+#else
 Ustrcpy(today, __DATE__);
 if (today[4] == ' ') i = 1;
 today[3] = today[6] = '-';
 
 Ustrcpy(today, __DATE__);
 if (today[4] == ' ') i = 1;
 today[3] = today[6] = '-';
 
-version_date = (uschar *)malloc(32);
+version_date = US malloc(32);
 version_date[0] = 0;
 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__);
 version_date[0] = 0;
 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__);
+#endif
 }
 
 /* End of em_version.c */
 }
 
 /* End of em_version.c */
index 87371b5..b145fb9 100644 (file)
@@ -2,7 +2,7 @@
 *               Exim Monitor                     *
 *************************************************/
 
 *               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
 /* 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);
   }
   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);
 }
 XtSetValues(w, aa, num_args);
 if (num_args > 15) free(aa);
 }
index eeb26ee..7e0bf38 100755 (executable)
@@ -1,9 +1,13 @@
 #! /bin/sh
 #! /bin/sh
+LC_ALL=C
+export LC_ALL
 
 # Shell script to build Makefile in a build directory. It must be called
 # 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.
 
 # Shell script to build Makefile in a build directory. It must be called
 # 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 1995 - 2018
 
 
 # First off, get the OS type, and check that there is a make file for it.
 
 
 # First off, get the OS type, and check that there is a make file for it.
@@ -76,6 +80,8 @@ fi
 mf=Makefile
 mft=$mf-t
 mftt=$mf-tt
 mf=Makefile
 mft=$mf-t
 mftt=$mf-tt
+mftepcp=$mf-tepcp
+mftepcp2=$mf-tepcp2
 
 look_mf=lookups/Makefile
 look_mf_pre=${look_mf}.predynamic
 
 look_mf=lookups/Makefile
 look_mf_pre=${look_mf}.predynamic
@@ -84,7 +90,7 @@ look_mf_post=${look_mf}.postdynamic
 # Ensure the temporary does not exist and start the new one by setting
 # the OSTYPE and ARCHTYPE variables.
 
 # Ensure the temporary does not exist and start the new one by setting
 # the OSTYPE and ARCHTYPE variables.
 
-rm -f $mft $mftt $look_mf-t
+rm -f $mft $mftt $mftepcp $mftepcp2 $look_mf-t
 (echo "OSTYPE=$ostype"; echo "ARCHTYPE=$archtype"; echo "") > $mft || exit 1
 
 # Now concatenate the files to the temporary file. Copy the files using sed to
 (echo "OSTYPE=$ostype"; echo "ARCHTYPE=$archtype"; echo "") > $mft || exit 1
 
 # Now concatenate the files to the temporary file. Copy the files using sed to
@@ -110,11 +116,37 @@ do   if test -r ../$f
             echo "# End of $f"
             echo ""
      fi
             echo "# End of $f"
             echo ""
      fi
-done >> $mft || exit 1
+done \
+     | sed 's/^TMPDIR=/EXIM_&/' \
+     >> $mft || exit 1
+
+# handle PKG_CONFIG_PATH because we need it in our env, and we want to handle
+# wildcards; note that this logic means all setting _appends_ values, never
+# replacing; if that's a problem, we can revisit.
+sed -n "s/^[$st]*PKG_CONFIG_PATH[$st]*[+]*=[$st]*//p" $mft | \
+  sed "s/[$st]*\$//" >> $mftepcp
+if test -s ./$mftepcp
+then
+  # expand any wildcards and strip spaces, to make it a real PATH-like variable
+  ( IFS=":${IFS-$st}"; for P in `cat ./$mftepcp`; do echo "$P"; done ) | xargs | sed "s/[$st]/:/g" >./$mftepcp2
+  sed "s/^/PKG_CONFIG_PATH='/" < ./$mftepcp2 | sed "s/\$/'/" > ./$mftepcp
+  . ./$mftepcp
+  export PKG_CONFIG_PATH
+  egrep -v "^[$st]*PKG_CONFIG_PATH[$st]*=" ./$mft > ./$mftt
+  rm -f ./$mft
+  (
+    echo "# Collapsed PKG_CONFIG_PATH in build-prep:"
+    sed "s/'//g" ./$mftepcp
+    echo "# End of collapsed PKG_CONFIG_PATH"
+    echo ""
+    cat ./$mftt
+  ) > ./$mft
+  rm -f ./$mftt
+fi
+rm -f ./$mftepcp ./$mftepcp2
 
 # handle pkg-config
 # beware portability of extended regexps with sed.
 
 # handle pkg-config
 # beware portability of extended regexps with sed.
-
 egrep "^[$st]*(AUTH|LOOKUP)_[A-Z0-9_]*[$st]*=[$st]*" $mft | \
   sed "s/[$st]*=/='/" | \
   sed "s/\$/'/" > $mftt
 egrep "^[$st]*(AUTH|LOOKUP)_[A-Z0-9_]*[$st]*=[$st]*" $mft | \
   sed "s/[$st]*=/='/" | \
   sed "s/\$/'/" > $mftt
@@ -132,13 +164,16 @@ then
       USE_*_PC)
         eval "pc_value=\"\$$var\""
         need_this=''
       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
         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
         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`
         fi
         if [ ".$need_this" != "." ]; then
           tls_include=`pkg-config --cflags $pc_value`
@@ -149,6 +184,19 @@ then
           tls_libs=`pkg-config --libs $pc_value`
           echo "TLS_INCLUDE=$tls_include"
           echo "TLS_LIBS=$tls_libs"
           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
         ;;
 
         fi
         ;;
 
index e9f6afd..ae1ecf9 100755 (executable)
@@ -28,7 +28,7 @@ then    echo ""
 fi
 rm -f os.h
 
 fi
 rm -f os.h
 
-# In order to accomodate for the fudge below, copy the file instead of
+# In order to accommodate for the fudge below, copy the file instead of
 # symlinking it. Otherwise we pollute the clean copy with the fudge.
 cp -p ../OS/os.h-$os os.h || exit 1
 
 # symlinking it. Otherwise we pollute the clean copy with the fudge.
 cp -p ../OS/os.h-$os os.h || exit 1
 
dissimilarity index 86%
index 39a0fe1..f5a4e50 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/environment.c     environment.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 1995 - 2018
+
+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 signing.c signing.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 dkim_transport.c dmarc.c dmarc.h \
+  valgrind.h memcheck.h \
+  macro_predef.c macro_predef.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  arc.c bmi_spam.c bmi_spam.h dcc.c dcc.h dane.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
   # 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 ""
       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 ***"
       echo $com "*** Exim installation ${ver}failed ***"
+      $exim
       exit 1
     fi
 
       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 \
 
 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
 do
   emit_module_rule $name_mod
 done
@@ -177,12 +177,9 @@ fi
 
 OBJ="${OBJ} spf.o"
 
 
 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
 then
-  OBJ="${OBJ} redis.o"
+  OBJ="${OBJ} lmdb.o"
 fi
 
 echo "MODS = $MODS"
 fi
 
 echo "MODS = $MODS"
index c461886..3657cfc 100755 (executable)
@@ -1,4 +1,9 @@
 #!/bin/sh
 #!/bin/sh
+# Copyright (c) The Exim Maintainers 1995 - 2018
+
+set -e
+LC_ALL=C
+export LC_ALL
 
 # Update Exim's version header file.
 
 
 # Update Exim's version header file.
 
@@ -24,57 +29,100 @@ fi
 # Read version information that was generated by a previous run of
 # this script, or during the release process.
 
 # Read version information that was generated by a previous run of
 # this script, or during the release process.
 
-if   [ -f ./version.sh ]
-then .    ./version.sh
-elif [ -f ../src/version.sh ]
-then .    ../src/version.sh
-fi
-
-# If this tree is a git working directory, use that to get version information.
-
-if [ -d ../../.git ] || [ "$1" = "release" ]
-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|-|')
-       # Only update if we need to
-       if [ "$2 $3" != "$EXIM_RELEASE_VERSION $EXIM_VARIANT_VERSION" ]
-       then
-               EXIM_RELEASE_VERSION="$2"
-               EXIM_VARIANT_VERSION="$3"
-               rm -f version.h
-       fi
+if   [ -f ./version.sh ]; then
+    .    ./version.sh
+elif [ -f ../src/version.sh ]; then
+    .    ../src/version.sh
+elif [ -d ../../.git ] || [ -f ../../.git ] || [ "$1" = release ]; 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.
+    if [ "$2" ]
+    then
+        description=$(git describe "$2")
+    else
+        description=$(git describe --dirty=-XX --match 'exim-4*')
+    fi
+    set $(echo "$description" | sed 's/-/ /; s/-g/-/')
+    # Only update if we need to
+    if [ "$2 $3" != "$EXIM_RELEASE_VERSION $EXIM_VARIANT_VERSION" ]
+    then
+            EXIM_RELEASE_VERSION="$2"
+            EXIM_VARIANT_VERSION="$3"
+            rm -f version.h
+    fi
+else
+    echo "Cannot determine the release number" >&2
+    exit
 fi
 
 # If you are maintaining a patched version of Exim, you can either
 # create your own version.sh as part of your release process, or you
 # can modify EXIM_VARIANT_VERSION at this point in this script.
 
 fi
 
 # If you are maintaining a patched version of Exim, you can either
 # create your own version.sh as part of your release process, or you
 # can modify EXIM_VARIANT_VERSION at this point in this script.
 
-case "$EXIM_RELEASE_VERSION" in
-'')    echo "*** Your copy of Exim lacks any version information."
-       exit 1
-esac
+if test -z "$EXIM_RELEASE_VERSION"; then
+    echo "$0: Your copy of Exim lacks any version information." >&2
+    exit 1
+fi
 
 EXIM_COMPILE_NUMBER=$(expr "${EXIM_COMPILE_NUMBER:-0}" + 1)
 
 echo "$EXIM_COMPILE_NUMBER" >cnumber.h
 
 
 EXIM_COMPILE_NUMBER=$(expr "${EXIM_COMPILE_NUMBER:-0}" + 1)
 
 echo "$EXIM_COMPILE_NUMBER" >cnumber.h
 
+# Reproducible builds, accept a build timestamp override from environ per
+# <https://reproducible-builds.org/specs/source-date-epoch/>.
+# We require a fairly modern date(1) command here, which is not portable
+# to some of the systems Exim is built on.  That's okay, because the scenarios
+# are:
+#  1) Local postmaster building, not using $SOURCE_DATE_EPOCH, doesn't matter
+#  2) Packaging folks who don't care about reproducible builds
+#  3) Packaging folks who care but are using systems where date Just Works
+#  3) Packaging folks who care and can put a modern date(1) in $PATH
+#  4) Packaging folks who care and can supply us with a clean patch to support
+#     their requirements
+#  5) Packaging folks who care but won't do any work to support their strange
+#     old systems and want us to do the work for them.  We don't care either,
+#     they're SOL and have to live without reproducible builds.
+#
+exim_build_date_override=''
+if [ ".${SOURCE_DATE_EPOCH:-}" != "." ]; then
+  fmt='+%d-%b-%Y %H:%M:%S'
+  # Non-reproducible, we use __DATE__ and __TIME__ in C, which respect timezone
+  # (think localtime, not gmtime); for reproduction between systems, UTC makes
+  # more sense and the examples available use UTC without explicitly mandating
+  # it.  I think that we can switch behavior and use UTC for reproducible
+  # builds without it causing any problems: nothing really cares about timezone.
+  # GNU date: "date -d @TS"
+  # BSD date: "date -r TS"
+  exim_build_date_override="$(date -u -d "@${SOURCE_DATE_EPOCH}" "$fmt" 2>/dev/null || date -u -r "${SOURCE_DATE_EPOCH}" "$fmt" 2>/dev/null)"
+fi
+
 ( echo '# automatically generated file - see ../scripts/reversion'
   echo EXIM_RELEASE_VERSION='"'"$EXIM_RELEASE_VERSION"'"'
 ( echo '# automatically generated file - see ../scripts/reversion'
   echo EXIM_RELEASE_VERSION='"'"$EXIM_RELEASE_VERSION"'"'
+  test -n "$EXIM_VARIANT_VERSION" && \
   echo EXIM_VARIANT_VERSION='"'"$EXIM_VARIANT_VERSION"'"'
   echo EXIM_COMPILE_NUMBER='"'"$EXIM_COMPILE_NUMBER"'"'
   echo EXIM_VARIANT_VERSION='"'"$EXIM_VARIANT_VERSION"'"'
   echo EXIM_COMPILE_NUMBER='"'"$EXIM_COMPILE_NUMBER"'"'
+  if [ ".${exim_build_date_override:-}" != "." ]; then
+    echo EXIM_BUILD_DATE_OVERRIDE='"'"${exim_build_date_override}"'"'
+  fi
 ) >version.sh
 
 if [ ! -f version.h ]
 then
 ( echo '/* automatically generated file - see ../scripts/reversion */'
   echo '#define EXIM_RELEASE_VERSION "'"$EXIM_RELEASE_VERSION"'"'
 ) >version.sh
 
 if [ ! -f version.h ]
 then
 ( echo '/* automatically generated file - see ../scripts/reversion */'
   echo '#define EXIM_RELEASE_VERSION "'"$EXIM_RELEASE_VERSION"'"'
+  test -n "$EXIM_VARIANT_VERSION" && \
   echo '#define EXIM_VARIANT_VERSION "'"$EXIM_VARIANT_VERSION"'"'
   echo '#define EXIM_VARIANT_VERSION "'"$EXIM_VARIANT_VERSION"'"'
-  echo '#define EXIM_VERSION_STR EXIM_RELEASE_VERSION EXIM_VARIANT_VERSION'
+  echo '#ifdef EXIM_VARIANT_VERSION'
+  echo '#define EXIM_VERSION_STR EXIM_RELEASE_VERSION "-" EXIM_VARIANT_VERSION'
+  echo '#else'
+  echo '#define EXIM_VERSION_STR EXIM_RELEASE_VERSION'
+  echo '#endif'
+  if [ ".${exim_build_date_override:-}" != "." ]; then
+    echo '#define EXIM_BUILD_DATE_OVERRIDE "'"${exim_build_date_override}"'"'
+  fi
 ) >version.h
 fi
 
 ) >version.h
 fi
 
-echo ">>> version $EXIM_RELEASE_VERSION$EXIM_VARIANT_VERSION #$EXIM_COMPILE_NUMBER"
+echo ">>> version $EXIM_RELEASE_VERSION $EXIM_VARIANT_VERSION #$EXIM_COMPILE_NUMBER"
 echo
 echo
index eac4b8d..918a6f8 100644 (file)
@@ -19,6 +19,7 @@ done <<-END
        globals.c       header_names
        globals.c       log_options
        expand.c        item_table
        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
        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
        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
 END
 
 # Tables with just string items
@@ -45,6 +48,5 @@ done <<-END
        expand.c        op_table_main
        expand.c        cond_table
        acl.c           verbs
        expand.c        op_table_main
        expand.c        cond_table
        acl.c           verbs
-       acl.c           conditions
 END
 
 END
 
index d576fd7..cbb0805 100644 (file)
@@ -192,6 +192,11 @@ SPOOL_DIRECTORY=/var/spool/exim
 # least one type of lookup. You should consider whether you want to build
 # the Exim monitor or not.
 
 # least one type of lookup. You should consider whether you want to build
 # the Exim monitor or not.
 
+# If you need to override how pkg-config finds configuration files for
+# installed software, then you can set that here; wildcards will be expanded.
+
+# PKG_CONFIG_PATH=/usr/local/opt/openssl/lib/pkgconfig : /opt/*/lib/pkgconfig
+
 
 #------------------------------------------------------------------------------
 # These settings determine which individual router drivers are included in the
 
 #------------------------------------------------------------------------------
 # These settings determine which individual router drivers are included in the
@@ -253,7 +258,7 @@ TRANSPORT_SMTP=yes
 # you perform upgrades and revert them. You should consider the benefit of
 # embedding the Exim version number into LOOKUP_MODULE_DIR, so that you can
 # maintain two concurrent sets of modules.
 # you perform upgrades and revert them. You should consider the benefit of
 # embedding the Exim version number into LOOKUP_MODULE_DIR, so that you can
 # maintain two concurrent sets of modules.
-# 
+#
 # *BEWARE*: ability to modify the files in LOOKUP_MODULE_DIR is equivalent to
 # the ability to modify the Exim binary, which is often setuid root!  The Exim
 # developers only intend this functionality be used by OS software packagers
 # *BEWARE*: ability to modify the files in LOOKUP_MODULE_DIR is equivalent to
 # the ability to modify the Exim binary, which is often setuid root!  The Exim
 # developers only intend this functionality be used by OS software packagers
@@ -288,6 +293,11 @@ TRANSPORT_SMTP=yes
 # library.
 # NOTE: LDAP cannot be built as a module!
 #
 # 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.
 # 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.
@@ -301,11 +311,13 @@ LOOKUP_DNSDB=yes
 # LOOKUP_IBASE=yes
 # LOOKUP_LDAP=yes
 # LOOKUP_MYSQL=yes
 # LOOKUP_IBASE=yes
 # LOOKUP_LDAP=yes
 # LOOKUP_MYSQL=yes
+# LOOKUP_MYSQL_PC=mariadb
 # LOOKUP_NIS=yes
 # LOOKUP_NISPLUS=yes
 # LOOKUP_ORACLE=yes
 # LOOKUP_PASSWD=yes
 # LOOKUP_PGSQL=yes
 # LOOKUP_NIS=yes
 # LOOKUP_NISPLUS=yes
 # LOOKUP_ORACLE=yes
 # LOOKUP_PASSWD=yes
 # LOOKUP_PGSQL=yes
+# LOOKUP_REDIS=yes
 # LOOKUP_SQLITE=yes
 # LOOKUP_SQLITE_PC=sqlite3
 # LOOKUP_WHOSON=yes
 # LOOKUP_SQLITE=yes
 # LOOKUP_SQLITE_PC=sqlite3
 # LOOKUP_WHOSON=yes
@@ -336,7 +348,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
 # 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
@@ -351,13 +363,20 @@ PCRE_CONFIG=yes
 # PCRE_LIBS=-lpcre
 
 
 # PCRE_LIBS=-lpcre
 
 
+#------------------------------------------------------------------------------
+# Uncomment the following line to add DANE support
+# Note: Enabling this unconditionally overrides DISABLE_DNSSEC
+# For DANE under GnuTLS we need an additional library.  See TLS_LIBS below.
+# SUPPORT_DANE=yes
+
 #------------------------------------------------------------------------------
 # Additional libraries and include directories may be required for some
 # lookup styles (e.g. LDAP, MYSQL or PGSQL). LOOKUP_LIBS is included only on
 # 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
 #------------------------------------------------------------------------------
 # Additional libraries and include directories may be required for some
 # lookup styles (e.g. LDAP, MYSQL or PGSQL). LOOKUP_LIBS is included only on
 # 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.
 
 #
 # You do not need to use this for any lookup information added via pkg-config.
 
@@ -385,24 +404,28 @@ EXIM_MONITOR=eximon.bin
 
 # 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.
+# If you have content scanning you may wish to only include some of the scanner
+# interfaces.  Uncomment any of these lines to remove that code.
 
 
-# WITH_OLD_DEMIME=yes
+# DISABLE_MAL_FFROTD=yes
+# DISABLE_MAL_FFROT6D=yes
+# DISABLE_MAL_DRWEB=yes
+# DISABLE_MAL_FSECURE=yes
+# DISABLE_MAL_SOPHIE=yes
+# DISABLE_MAL_CLAM=yes
+# DISABLE_MAL_AVAST=yes
+# DISABLE_MAL_SOCK=yes
+# DISABLE_MAL_CMDLINE=yes
+
+# These scanners are claimed to be no longer existent.
+
+DISABLE_MAL_AVE=yes
+DISABLE_MAL_KAV=yes
+DISABLE_MAL_MKS=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
-# zINSTREAM.  If you need to set this, please let the Exim developers know, as
-# if nobody reports a need for it, we'll remove this option and clean up the
-# code.  zINSTREAM was introduced with ClamAV 0.95.
-#
-# 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
 # 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 +449,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"
 # 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 SUPPORT_DANE unconditionally overrides this setting.
 
 # DISABLE_DNSSEC=yes
 
 
 # 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
 
 #------------------------------------------------------------------------------
 # Compiling Exim with experimental features. These are documented in
@@ -437,17 +465,10 @@ 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.
 
 # 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
 
 
 # EXPERIMENTAL_DCC=yes
 
-# Uncomment the following lines to add SPF support. You need to have libspf2
-# installed on your system (www.libspf2.org). Depending on where it is installed
-# you may have to edit the CFLAGS and LDFLAGS lines.
-
-# EXPERIMENTAL_SPF=yes
-# CFLAGS  += -I/usr/local/include
-# LDFLAGS += -lspf2
-
 # Uncomment the following lines to add SRS (Sender rewriting scheme) support.
 # You need to have libsrs_alt installed on your system (srs.mirtol.com).
 # Depending on where it is installed you may have to edit the CFLAGS and
 # Uncomment the following lines to add SRS (Sender rewriting scheme) support.
 # You need to have libsrs_alt installed on your system (srs.mirtol.com).
 # Depending on where it is installed you may have to edit the CFLAGS and
@@ -457,6 +478,17 @@ EXIM_MONITOR=eximon.bin
 # CFLAGS  += -I/usr/local/include
 # LDFLAGS += -lsrs_alt
 
 # 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 and DKIM support enabled also.
+# EXPERIMENTAL_DMARC=yes
+# DMARC_TLD_FILE= /etc/exim/opendmarc.tlds
+# CFLAGS += -I/usr/local/include
+# LDFLAGS += -lopendmarc
+
+# Uncomment the following line to add ARC (Authenticated Received Chain)
+# support.  You must have SPF and DKIM support enabled also.
+# EXPERIMENTAL_ARC=yes
+
 # 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
 # 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 +498,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
 
 # 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 support Transport post-delivery actions,
-# eg. for logging to a database.
-# EXPERIMENTAL_TPDA=yes
+# Uncomment the following to include extra information in fail DSN message (bounces)
+# EXPERIMENTAL_DSN_INFO=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.
 # 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
 # CFLAGS += -I/usr/local/include
-# LDFLAGS += -lhiredis
+# LDFLAGS += -llmdb
 
 
-# Uncomment the following line to enable Experimental Proxy Protocol
-# EXPERIMENTAL_PROXY=yes
+# Uncomment the following line to add queuefile transport support
+# EXPERIMENTAL_QUEUEFILE=yes
 
 
-# Uncomment the following line to enable support for checking certiticate
-# ownership
-# EXPERIMENTAL_CERTNAMES=yes
-
-# Uncomment the following line to add DSN support
-# EXPERIMENTAL_DSN=yes
+# Uncomment the following to add REQUIRETLS support.
+# You must also have SUPPORT_TLS enabled.
+# Ref: https://datatracker.ietf.org/doc/draft-fenton-smtp-require-tls
+# EXPERIMENTAL_REQUIRETLS=yes
 
 ###############################################################################
 #                 THESE ARE THINGS YOU MIGHT WANT TO SPECIFY                  #
 
 ###############################################################################
 #                 THESE ARE THINGS YOU MIGHT WANT TO SPECIFY                  #
@@ -593,7 +615,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
 # 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.
 # 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 +647,14 @@ FIXED_NEVER_USERS=root
 # AUTH_GSASL_PC=libgsasl
 # AUTH_HEIMDAL_GSSAPI=yes
 # AUTH_HEIMDAL_GSSAPI_PC=heimdal-gssapi
 # 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_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
 
 #------------------------------------------------------------------------------
 # If you specified AUTH_CYRUS_SASL above, you should ensure that you have the
@@ -680,6 +707,13 @@ HEADERS_CHARSET="ISO-8859-1"
 #
 # but of course there may need to be other things in CFLAGS and EXTRALIBS_EXIM
 # as well.
 #
 # 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 +775,10 @@ HEADERS_CHARSET="ISO-8859-1"
 # USE_GNUTLS_PC=gnutls
 # TLS_LIBS=-lgnutls -ltasn1 -lgcrypt
 
 # 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
 # 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
@@ -770,6 +808,9 @@ HEADERS_CHARSET="ISO-8859-1"
 # or
 # TLS_LIBS=-L/opt/gnu/lib -lgnutls -ltasn1 -lgcrypt
 
 # or
 # TLS_LIBS=-L/opt/gnu/lib -lgnutls -ltasn1 -lgcrypt
 
+# For DANE under GnuTLS we need an additional library.
+# TLS_LIBS += -lgnutls-dane
+
 # TLS_LIBS is included only on the command for linking Exim itself, not on any
 # auxiliary programs. If the include files are not in a standard place, you can
 # set TLS_INCLUDE to specify where they are, for example:
 # TLS_LIBS is included only on the command for linking Exim itself, not on any
 # auxiliary programs. If the include files are not in a standard place, you can
 # set TLS_INCLUDE to specify where they are, for example:
@@ -790,7 +831,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
 # 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
 # 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 +909,15 @@ COMPRESS_SUFFIX=gz
 # If the exigrep utility is fed compressed log files, it tries to uncompress
 # them using this command.
 
 # 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
 
 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
 #------------------------------------------------------------------------------
 # 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 +950,46 @@ ZCAT_COMMAND=/usr/bin/zcat
 # GNU/Linux -ldl is also needed.
 
 
 # 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
+
+
+#------------------------------------------------------------------------------
+# Uncomment the following lines to add SPF support. You need to have libspf2
+# installed on your system (www.libspf2.org). Depending on where it is installed
+# you may have to edit the CFLAGS and LDFLAGS lines.
+
+# SUPPORT_SPF=yes
+# CFLAGS  += -I/usr/local/include
+# LDFLAGS += -lspf2
+
+
 #------------------------------------------------------------------------------
 # Support for authentication via Radius is also available. The Exim support,
 # which is intended for use in conjunction with the SMTP AUTH facilities,
 #------------------------------------------------------------------------------
 # 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 +1032,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
 # 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
 # pwcheck, you should arrange for it to be started by root at boot time.
 
 # CYRUS_PWCHECK_SOCKET=/var/pwcheck/pwcheck
@@ -953,7 +1040,7 @@ ZCAT_COMMAND=/usr/bin/zcat
 
 #------------------------------------------------------------------------------
 # Support for authentication via the Cyrus SASL saslauthd daemon is available.
 
 #------------------------------------------------------------------------------
 # 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.
 #
 # facilities, is included only when requested by setting the following
 # parameter to the location of the saslauthd daemon's socket.
 #
@@ -961,7 +1048,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
 # ./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.
 
 # group. Once you have installed saslauthd, you should arrange for it to be
 # started by root at boot time.
 
@@ -1038,6 +1125,13 @@ SYSTEM_ALIASES_FILE=/etc/aliases
 # Note that this option adds to the size of the Exim binary, because the
 # dynamic loading library is not otherwise included.
 
 # Note that this option adds to the size of the Exim binary, because the
 # dynamic loading library is not otherwise included.
 
+# If libreadline is not in the normal library paths, then because Exim is
+# setuid you'll need to ensure that the correct directory is stamped into
+# the binary so that dlopen will find it.
+# Eg, on macOS/Darwin with a third-party install of libreadline, perhaps:
+
+# EXTRALIBS_EXIM+=-Wl,-rpath,/usr/local/opt/readline/lib
+
 
 #------------------------------------------------------------------------------
 # Uncomment this setting to include IPv6 support.
 
 #------------------------------------------------------------------------------
 # Uncomment this setting to include IPv6 support.
@@ -1088,9 +1182,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
 # 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 +1218,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
 # 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>.
 # 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 +1402,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
 # 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..f3b860e 100644 (file)
--- a/src/acl.c
+++ b/src/acl.c
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Code for handling Access Control Lists (ACLs) */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Code for handling Access Control Lists (ACLs) */
@@ -22,13 +22,14 @@ enum { ACL_ACCEPT, ACL_DEFER, ACL_DENY, ACL_DISCARD, ACL_DROP, ACL_REQUIRE,
 /* ACL verbs */
 
 static uschar *verbs[] = {
 /* ACL verbs */
 
 static uschar *verbs[] = {
-    US"accept",
-    US"defer",
-    US"deny",
-    US"discard",
-    US"drop",
-    US"require",
-    US"warn" };
+    [ACL_ACCEPT] =     US"accept",
+    [ACL_DEFER] =      US"defer",
+    [ACL_DENY] =       US"deny",
+    [ACL_DISCARD] =    US"discard",
+    [ACL_DROP] =       US"drop",
+    [ACL_REQUIRE] =    US"require",
+    [ACL_WARN] =       US"warn"
+};
 
 /* For each verb, the conditions for which "message" or "log_message" are used
 are held as a bitmap. This is to avoid expanding the strings unnecessarily. For
 
 /* For each verb, the conditions for which "message" or "log_message" are used
 are held as a bitmap. This is to avoid expanding the strings unnecessarily. For
@@ -36,17 +37,17 @@ are held as a bitmap. This is to avoid expanding the strings unnecessarily. For
 the code. */
 
 static int msgcond[] = {
 the code. */
 
 static int msgcond[] = {
-  (1<<OK) | (1<<FAIL) | (1<<FAIL_DROP),  /* accept */
-  (1<<OK),                               /* defer */
-  (1<<OK),                               /* deny */
-  (1<<OK) | (1<<FAIL) | (1<<FAIL_DROP),  /* discard */
-  (1<<OK),                               /* drop */
-  (1<<FAIL) | (1<<FAIL_DROP),            /* require */
-  (1<<OK)                                /* warn */
+  [ACL_ACCEPT] =       BIT(OK) | BIT(FAIL) | BIT(FAIL_DROP),
+  [ACL_DEFER] =                BIT(OK),
+  [ACL_DENY] =         BIT(OK),
+  [ACL_DISCARD] =      BIT(OK) | BIT(FAIL) | BIT(FAIL_DROP),
+  [ACL_DROP] =         BIT(OK),
+  [ACL_REQUIRE] =      BIT(FAIL) | BIT(FAIL_DROP),
+  [ACL_WARN] =         BIT(OK)
   };
 
 /* ACL condition and modifier codes - keep in step with the table that
   };
 
 /* 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,
 down. */
 
 enum { ACLC_ACL,
@@ -65,9 +66,6 @@ enum { ACLC_ACL,
        ACLC_DECODE,
 #endif
        ACLC_DELAY,
        ACLC_DECODE,
 #endif
        ACLC_DELAY,
-#ifdef WITH_OLD_DEMIME
-       ACLC_DEMIME,
-#endif
 #ifndef DISABLE_DKIM
        ACLC_DKIM_SIGNER,
        ACLC_DKIM_STATUS,
 #ifndef DISABLE_DKIM
        ACLC_DKIM_SIGNER,
        ACLC_DKIM_STATUS,
@@ -91,6 +89,7 @@ enum { ACLC_ACL,
 #ifdef WITH_CONTENT_SCAN
        ACLC_MIME_REGEX,
 #endif
 #ifdef WITH_CONTENT_SCAN
        ACLC_MIME_REGEX,
 #endif
+       ACLC_QUEUE,
        ACLC_RATELIMIT,
        ACLC_RECIPIENTS,
 #ifdef WITH_CONTENT_SCAN
        ACLC_RATELIMIT,
        ACLC_RECIPIENTS,
 #ifdef WITH_CONTENT_SCAN
@@ -103,7 +102,7 @@ enum { ACLC_ACL,
 #ifdef WITH_CONTENT_SCAN
        ACLC_SPAM,
 #endif
 #ifdef WITH_CONTENT_SCAN
        ACLC_SPAM,
 #endif
-#ifdef EXPERIMENTAL_SPF
+#ifdef SUPPORT_SPF
        ACLC_SPF,
        ACLC_SPF_GUESS,
 #endif
        ACLC_SPF,
        ACLC_SPF_GUESS,
 #endif
@@ -111,636 +110,438 @@ enum { ACLC_ACL,
        ACLC_VERIFY };
 
 /* ACL conditions/modifiers: "delay", "control", "continue", "endpass",
        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. */
 
 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[] = {
+  [ACLC_ACL] =                 { US"acl",              FALSE, FALSE,   0 },
+
+  [ACLC_ADD_HEADER] =          { US"add_header",       TRUE, TRUE,
+                                 (unsigned int)
+                                 ~(ACL_BIT_MAIL | ACL_BIT_RCPT |
+                                   ACL_BIT_PREDATA | ACL_BIT_DATA |
+#ifndef DISABLE_PRDR
+                                   ACL_BIT_PRDR |
+#endif
+                                   ACL_BIT_MIME | ACL_BIT_NOTSMTP |
+                                   ACL_BIT_DKIM |
+                                   ACL_BIT_NOTSMTP_START),
+  },
+
+  [ACLC_AUTHENTICATED] =       { US"authenticated",    FALSE, FALSE,
+                                 ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START |
+                                   ACL_BIT_CONNECT | ACL_BIT_HELO,
+  },
 #ifdef EXPERIMENTAL_BRIGHTMAIL
 #ifdef EXPERIMENTAL_BRIGHTMAIL
-  US"bmi_optin",
+  [ACLC_BMI_OPTIN] =           { US"bmi_optin",        TRUE, TRUE,
+                                 ACL_BIT_AUTH |
+                                   ACL_BIT_CONNECT | ACL_BIT_HELO |
+                                   ACL_BIT_DATA | ACL_BIT_MIME |
+# ifndef DISABLE_PRDR
+                                   ACL_BIT_PRDR |
+# endif
+                                   ACL_BIT_ETRN | ACL_BIT_EXPN |
+                                   ACL_BIT_MAILAUTH |
+                                   ACL_BIT_MAIL | ACL_BIT_STARTTLS |
+                                   ACL_BIT_VRFY | ACL_BIT_PREDATA |
+                                   ACL_BIT_NOTSMTP_START,
+  },
 #endif
 #endif
-  US"condition",
-  US"continue",
-  US"control",
+  [ACLC_CONDITION] =           { US"condition",        TRUE, FALSE,    0 },
+  [ACLC_CONTINUE] =            { 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. */
+  [ACLC_CONTROL] =             { US"control",  TRUE, TRUE,     0 },
+
 #ifdef EXPERIMENTAL_DCC
 #ifdef EXPERIMENTAL_DCC
-  US"dcc",
+  [ACLC_DCC] =                 { US"dcc",              TRUE, FALSE,
+                                 (unsigned int)
+                                 ~(ACL_BIT_DATA |
+# ifndef DISABLE_PRDR
+                                 ACL_BIT_PRDR |
+# endif
+                                 ACL_BIT_NOTSMTP),
+  },
 #endif
 #ifdef WITH_CONTENT_SCAN
 #endif
 #ifdef WITH_CONTENT_SCAN
-  US"decode",
-#endif
-  US"delay",
-#ifdef WITH_OLD_DEMIME
-  US"demime",
+  [ACLC_DECODE] =              { US"decode",           TRUE, FALSE, (unsigned int) ~ACL_BIT_MIME },
+
 #endif
 #endif
+  [ACLC_DELAY] =               { US"delay",            TRUE, TRUE, ACL_BIT_NOTQUIT },
 #ifndef DISABLE_DKIM
 #ifndef DISABLE_DKIM
-  US"dkim_signers",
-  US"dkim_status",
+  [ACLC_DKIM_SIGNER] =         { US"dkim_signers",     TRUE, FALSE, (unsigned int) ~ACL_BIT_DKIM },
+  [ACLC_DKIM_STATUS] =         { US"dkim_status",      TRUE, FALSE, (unsigned int) ~ACL_BIT_DKIM },
 #endif
 #ifdef EXPERIMENTAL_DMARC
 #endif
 #ifdef EXPERIMENTAL_DMARC
-  US"dmarc_status",
+  [ACLC_DMARC_STATUS] =                { US"dmarc_status",     TRUE, FALSE, (unsigned int) ~ACL_BIT_DATA },
+#endif
+
+  /* Explicit key lookups can be made in non-smtp ACLs so pass
+  always and check in the verify processing itself. */
+  [ACLC_DNSLISTS] =            { US"dnslists", TRUE, FALSE,    0 },
+
+  [ACLC_DOMAINS] =             { US"domains",  FALSE, FALSE,
+                                 (unsigned int)
+                                 ~(ACL_BIT_RCPT | ACL_BIT_VRFY
+#ifndef DISABLE_PRDR
+                                 |ACL_BIT_PRDR
+#endif
+      ),
+  },
+  [ACLC_ENCRYPTED] =           { US"encrypted",        FALSE, FALSE,
+                                 ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START |
+                                   ACL_BIT_HELO,
+  },
+
+  [ACLC_ENDPASS] =             { US"endpass",  TRUE, TRUE,     0 },
+
+  [ACLC_HOSTS] =               { US"hosts",            FALSE, FALSE,
+                                 ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START,
+  },
+  [ACLC_LOCAL_PARTS] =         { US"local_parts",      FALSE, FALSE,
+                                 (unsigned int)
+                                 ~(ACL_BIT_RCPT | ACL_BIT_VRFY
+#ifndef DISABLE_PRDR
+                                 | ACL_BIT_PRDR
 #endif
 #endif
-  US"dnslists",
-  US"domains",
-  US"encrypted",
-  US"endpass",
-  US"hosts",
-  US"local_parts",
-  US"log_message",
-  US"log_reject_target",
-  US"logwrite",
+      ),
+  },
+
+  [ACLC_LOG_MESSAGE] =         { US"log_message",      TRUE, TRUE,     0 },
+  [ACLC_LOG_REJECT_TARGET] =   { US"log_reject_target", TRUE, TRUE,    0 },
+  [ACLC_LOGWRITE] =            { US"logwrite", TRUE, TRUE,     0 },
+
 #ifdef WITH_CONTENT_SCAN
 #ifdef WITH_CONTENT_SCAN
-  US"malware",
+  [ACLC_MALWARE] =             { US"malware",  TRUE, FALSE,
+                                 (unsigned int)
+                                   ~(ACL_BIT_DATA |
+# ifndef DISABLE_PRDR
+                                   ACL_BIT_PRDR |
+# endif
+                                   ACL_BIT_NOTSMTP),
+  },
 #endif
 #endif
-  US"message",
+
+  [ACLC_MESSAGE] =             { US"message",  TRUE, TRUE,     0 },
 #ifdef WITH_CONTENT_SCAN
 #ifdef WITH_CONTENT_SCAN
-  US"mime_regex",
+  [ACLC_MIME_REGEX] =          { US"mime_regex",       TRUE, FALSE, (unsigned int) ~ACL_BIT_MIME },
+#endif
+
+  [ACLC_QUEUE] =               { US"queue",            TRUE, TRUE,
+                                 ACL_BIT_NOTSMTP |
+#ifndef DISABLE_PRDR
+                                 ACL_BIT_PRDR |
 #endif
 #endif
-  US"ratelimit",
-  US"recipients",
+                                 ACL_BIT_DATA,
+  },
+
+  [ACLC_RATELIMIT] =           { US"ratelimit",        TRUE, FALSE,    0 },
+  [ACLC_RECIPIENTS] =          { US"recipients",       FALSE, FALSE, (unsigned int) ~ACL_BIT_RCPT },
+
 #ifdef WITH_CONTENT_SCAN
 #ifdef WITH_CONTENT_SCAN
-  US"regex",
+  [ACLC_REGEX] =               { US"regex",            TRUE, FALSE,
+                                 (unsigned int)
+                                 ~(ACL_BIT_DATA |
+# ifndef DISABLE_PRDR
+                                   ACL_BIT_PRDR |
+# endif
+                                   ACL_BIT_NOTSMTP |
+                                   ACL_BIT_MIME),
+  },
+
+#endif
+  [ACLC_REMOVE_HEADER] =       { US"remove_header",    TRUE, TRUE,
+                                 (unsigned int)
+                                 ~(ACL_BIT_MAIL|ACL_BIT_RCPT |
+                                   ACL_BIT_PREDATA | ACL_BIT_DATA |
+#ifndef DISABLE_PRDR
+                                   ACL_BIT_PRDR |
 #endif
 #endif
-  US"remove_header",
-  US"sender_domains", US"senders", US"set",
+                                   ACL_BIT_MIME | ACL_BIT_NOTSMTP |
+                                   ACL_BIT_NOTSMTP_START),
+  },
+  [ACLC_SENDER_DOMAINS] =      { US"sender_domains",   FALSE, FALSE,
+                                 ACL_BIT_AUTH | ACL_BIT_CONNECT |
+                                   ACL_BIT_HELO |
+                                   ACL_BIT_MAILAUTH | ACL_BIT_QUIT |
+                                   ACL_BIT_ETRN | ACL_BIT_EXPN |
+                                   ACL_BIT_STARTTLS | ACL_BIT_VRFY,
+  },
+  [ACLC_SENDERS] =             { US"senders",  FALSE, FALSE,
+                                 ACL_BIT_AUTH | ACL_BIT_CONNECT |
+                                   ACL_BIT_HELO |
+                                   ACL_BIT_MAILAUTH | ACL_BIT_QUIT |
+                                   ACL_BIT_ETRN | ACL_BIT_EXPN |
+                                   ACL_BIT_STARTTLS | ACL_BIT_VRFY,
+  },
+
+  [ACLC_SET] =                 { US"set",              TRUE, TRUE,     0 },
+
 #ifdef WITH_CONTENT_SCAN
 #ifdef WITH_CONTENT_SCAN
-  US"spam",
+  [ACLC_SPAM] =                        { US"spam",             TRUE, FALSE,
+                                 (unsigned int) ~(ACL_BIT_DATA |
+# ifndef DISABLE_PRDR
+                                 ACL_BIT_PRDR |
+# endif
+                                 ACL_BIT_NOTSMTP),
+  },
 #endif
 #endif
-#ifdef EXPERIMENTAL_SPF
-  US"spf",
-  US"spf_guess",
+#ifdef SUPPORT_SPF
+  [ACLC_SPF] =                 { US"spf",              TRUE, FALSE,
+                                 ACL_BIT_AUTH | ACL_BIT_CONNECT |
+                                   ACL_BIT_HELO | ACL_BIT_MAILAUTH |
+                                   ACL_BIT_ETRN | ACL_BIT_EXPN |
+                                   ACL_BIT_STARTTLS | ACL_BIT_VRFY |
+                                   ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START,
+  },
+  [ACLC_SPF_GUESS] =           { US"spf_guess",        TRUE, FALSE,
+                                 ACL_BIT_AUTH | ACL_BIT_CONNECT |
+                                   ACL_BIT_HELO | ACL_BIT_MAILAUTH |
+                                   ACL_BIT_ETRN | ACL_BIT_EXPN |
+                                   ACL_BIT_STARTTLS | ACL_BIT_VRFY |
+                                   ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START,
+  },
 #endif
 #endif
-  US"udpsend",
-  US"verify" };
+  [ACLC_UDPSEND] =             { 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 */
+  [ACLC_VERIFY] =              { US"verify",           TRUE, FALSE, 0 },
+};
 
 
 
 
-/* Return values from decode_control(); keep in step with the table of names
-that follows! */
+
+/* Return values from decode_control(); used as index so keep in step
+with the controls_list table that follows! */
 
 enum {
   CONTROL_AUTH_UNADVERTISED,
 
 enum {
   CONTROL_AUTH_UNADVERTISED,
-  #ifdef EXPERIMENTAL_BRIGHTMAIL
+#ifdef EXPERIMENTAL_BRIGHTMAIL
   CONTROL_BMI_RUN,
   CONTROL_BMI_RUN,
-  #endif
+#endif
+  CONTROL_CASEFUL_LOCAL_PART,
+  CONTROL_CASELOWER_LOCAL_PART,
+  CONTROL_CUTTHROUGH_DELIVERY,
   CONTROL_DEBUG,
   CONTROL_DEBUG,
-  #ifndef DISABLE_DKIM
+#ifndef DISABLE_DKIM
   CONTROL_DKIM_VERIFY,
   CONTROL_DKIM_VERIFY,
-  #endif
-  #ifdef EXPERIMENTAL_DMARC
+#endif
+#ifdef EXPERIMENTAL_DMARC
   CONTROL_DMARC_VERIFY,
   CONTROL_DMARC_FORENSIC,
   CONTROL_DMARC_VERIFY,
   CONTROL_DMARC_FORENSIC,
-  #endif
+#endif
   CONTROL_DSCP,
   CONTROL_DSCP,
-  CONTROL_ERROR,
-  CONTROL_CASEFUL_LOCAL_PART,
-  CONTROL_CASELOWER_LOCAL_PART,
-  CONTROL_CUTTHROUGH_DELIVERY,
   CONTROL_ENFORCE_SYNC,
   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_ERROR,               /* pseudo-value for decode errors */
   CONTROL_FAKEDEFER,
   CONTROL_FAKEREJECT,
   CONTROL_FAKEDEFER,
   CONTROL_FAKEREJECT,
-  CONTROL_NO_MULTILINE,
-  CONTROL_NO_PIPELINING,
-  CONTROL_NO_DELAY_FLUSH,
-  CONTROL_NO_CALLOUT_FLUSH
-};
-
-/* 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"
-};
-
-/* 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. */
+  CONTROL_FREEZE,
 
 
-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 */
-#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 */
-#ifdef WITH_CONTENT_SCAN
-  TRUE,    /* malware */
-#endif
-  TRUE,    /* message */
-#ifdef WITH_CONTENT_SCAN
-  TRUE,    /* mime_regex */
-#endif
-  TRUE,    /* ratelimit */
-  FALSE,   /* recipients */
-#ifdef WITH_CONTENT_SCAN
-  TRUE,    /* regex */
-#endif
-  TRUE,    /* remove_header */
-  FALSE,   /* sender_domains */
-  FALSE,   /* senders */
-  TRUE,    /* set */
+  CONTROL_NO_CALLOUT_FLUSH,
+  CONTROL_NO_DELAY_FLUSH,
+  CONTROL_NO_ENFORCE_SYNC,
 #ifdef WITH_CONTENT_SCAN
 #ifdef WITH_CONTENT_SCAN
-  TRUE,    /* spam */
-#endif
-#ifdef EXPERIMENTAL_SPF
-  TRUE,    /* spf */
-  TRUE,    /* spf_guess */
+  CONTROL_NO_MBOX_UNSPOOL,
 #endif
 #endif
-  TRUE,    /* udpsend */
-  TRUE     /* verify */
-};
-
-/* Flags to identify the modifiers */
+  CONTROL_NO_MULTILINE,
+  CONTROL_NO_PIPELINING,
 
 
-static uschar cond_modifiers[] = {
-  FALSE,   /* acl */
-  TRUE,    /* add_header */
-  FALSE,   /* authenticated */
-#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 */
-#endif
-#ifndef DISABLE_DKIM
-  FALSE,   /* dkim_signers */
-  FALSE,   /* dkim_status */
-#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 */
-#endif
-  TRUE,    /* remove_header */
-  FALSE,   /* sender_domains */
-  FALSE,   /* senders */
-  TRUE,    /* set */
-#ifdef WITH_CONTENT_SCAN
-  FALSE,   /* spam */
+  CONTROL_QUEUE_ONLY,
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+  CONTROL_REQUIRETLS,
 #endif
 #endif
-#ifdef EXPERIMENTAL_SPF
-  FALSE,   /* spf */
-  FALSE,   /* spf_guess */
+  CONTROL_SUBMISSION,
+  CONTROL_SUPPRESS_LOCAL_FIXUPS,
+#ifdef SUPPORT_I18N
+  CONTROL_UTF8_DOWNCONVERT,
 #endif
 #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 */
-
-  0                                                /* verify */
 };
 
 
 };
 
 
-/* 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 {
 
 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[] = {
 } control_def;
 
 static control_def controls_list[] = {
-  { US"allow_auth_unadvertised", CONTROL_AUTH_UNADVERTISED, FALSE },
+  /*   name                    has_option      forbids */
+[CONTROL_AUTH_UNADVERTISED] =
+  { US"allow_auth_unadvertised", FALSE,
+                                 (unsigned)
+                                 ~(ACL_BIT_CONNECT | ACL_BIT_HELO)
+  },
 #ifdef EXPERIMENTAL_BRIGHTMAIL
 #ifdef EXPERIMENTAL_BRIGHTMAIL
-  { US"bmi_run",                 CONTROL_BMI_RUN, FALSE },
+[CONTROL_BMI_RUN] =
+  { US"bmi_run",                 FALSE,                0 },
 #endif
 #endif
-  { US"debug",                   CONTROL_DEBUG, TRUE },
+[CONTROL_CASEFUL_LOCAL_PART] =
+  { US"caseful_local_part",      FALSE, (unsigned) ~ACL_BIT_RCPT },
+[CONTROL_CASELOWER_LOCAL_PART] =
+  { US"caselower_local_part",    FALSE, (unsigned) ~ACL_BIT_RCPT },
+[CONTROL_CUTTHROUGH_DELIVERY] =
+  { US"cutthrough_delivery",     TRUE,         0 },
+[CONTROL_DEBUG] =
+  { US"debug",                   TRUE,         0 },
+
 #ifndef DISABLE_DKIM
 #ifndef DISABLE_DKIM
-  { US"dkim_disable_verify",     CONTROL_DKIM_VERIFY, FALSE },
+[CONTROL_DKIM_VERIFY] =
+  { US"dkim_disable_verify",     FALSE,
+                                 ACL_BIT_DATA | ACL_BIT_NOTSMTP |
+# ifndef DISABLE_PRDR
+                                 ACL_BIT_PRDR |
+# endif
+                                 ACL_BIT_NOTSMTP_START
+  },
 #endif
 #endif
+
 #ifdef EXPERIMENTAL_DMARC
 #ifdef EXPERIMENTAL_DMARC
-  { US"dmarc_disable_verify",    CONTROL_DMARC_VERIFY, FALSE },
-  { US"dmarc_enable_forensic",   CONTROL_DMARC_FORENSIC, FALSE },
+[CONTROL_DMARC_VERIFY] =
+  { US"dmarc_disable_verify",    FALSE,
+         ACL_BIT_DATA | ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START
+  },
+[CONTROL_DMARC_FORENSIC] =
+  { US"dmarc_enable_forensic",   FALSE,
+         ACL_BIT_DATA | ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START
+  },
+#endif
+
+[CONTROL_DSCP] =
+  { US"dscp",                    TRUE,
+         ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START | ACL_BIT_NOTQUIT
+  },
+[CONTROL_ENFORCE_SYNC] =
+  { US"enforce_sync",            FALSE,
+         ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START
+  },
+
+  /* Pseudo-value for decode errors */
+[CONTROL_ERROR] =
+  { US"error",                   FALSE, 0 },
+
+[CONTROL_FAKEDEFER] =
+  { US"fakedefer",               TRUE,
+         (unsigned)
+         ~(ACL_BIT_MAIL | ACL_BIT_RCPT |
+           ACL_BIT_PREDATA | ACL_BIT_DATA |
+#ifndef DISABLE_PRDR
+           ACL_BIT_PRDR |
 #endif
 #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 },
+           ACL_BIT_MIME)
+  },
+[CONTROL_FAKEREJECT] =
+  { US"fakereject",              TRUE,
+         (unsigned)
+         ~(ACL_BIT_MAIL | ACL_BIT_RCPT |
+           ACL_BIT_PREDATA | ACL_BIT_DATA |
+#ifndef DISABLE_PRDR
+         ACL_BIT_PRDR |
+#endif
+         ACL_BIT_MIME)
+  },
+[CONTROL_FREEZE] =
+  { US"freeze",                  TRUE,
+         (unsigned)
+         ~(ACL_BIT_MAIL | ACL_BIT_RCPT |
+           ACL_BIT_PREDATA | ACL_BIT_DATA |
+           // ACL_BIT_PRDR|    /* Not allow one user to freeze for all */
+           ACL_BIT_NOTSMTP | ACL_BIT_MIME)
+  },
+
+[CONTROL_NO_CALLOUT_FLUSH] =
+  { US"no_callout_flush",        FALSE,
+         ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START
+  },
+[CONTROL_NO_DELAY_FLUSH] =
+  { US"no_delay_flush",          FALSE,
+         ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START
+  },
+  
+[CONTROL_NO_ENFORCE_SYNC] =
+  { US"no_enforce_sync",         FALSE,
+         ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START
+  },
 #ifdef WITH_CONTENT_SCAN
 #ifdef WITH_CONTENT_SCAN
-  { US"no_mbox_unspool",         CONTROL_NO_MBOX_UNSPOOL, FALSE },
+[CONTROL_NO_MBOX_UNSPOOL] =
+  { US"no_mbox_unspool",         FALSE,
+       (unsigned)
+       ~(ACL_BIT_MAIL | ACL_BIT_RCPT |
+         ACL_BIT_PREDATA | ACL_BIT_DATA |
+         // ACL_BIT_PRDR|    /* Not allow one user to freeze for all */
+         ACL_BIT_MIME)
+  },
+#endif
+[CONTROL_NO_MULTILINE] =
+  { US"no_multiline_responses",  FALSE,
+         ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START
+  },
+[CONTROL_NO_PIPELINING] =
+  { US"no_pipelining",           FALSE,
+         ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START
+  },
+
+[CONTROL_QUEUE_ONLY] =
+  { US"queue_only",              FALSE,
+         (unsigned)
+         ~(ACL_BIT_MAIL | ACL_BIT_RCPT |
+           ACL_BIT_PREDATA | ACL_BIT_DATA |
+           // ACL_BIT_PRDR|    /* Not allow one user to freeze for all */
+           ACL_BIT_NOTSMTP | ACL_BIT_MIME)
+  },
+
+
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+[CONTROL_REQUIRETLS] =
+  { US"requiretls",             FALSE,
+         (unsigned)
+         ~(ACL_BIT_MAIL | ACL_BIT_RCPT | ACL_BIT_PREDATA |
+           ACL_BIT_DATA | ACL_BIT_MIME |
+           ACL_BIT_NOTSMTP)
+  },
 #endif
 #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 }
-  };
+
+[CONTROL_SUBMISSION] =
+  { US"submission",              TRUE,
+         (unsigned)
+         ~(ACL_BIT_MAIL | ACL_BIT_RCPT | ACL_BIT_PREDATA)
+  },
+[CONTROL_SUPPRESS_LOCAL_FIXUPS] =
+  { US"suppress_local_fixups",   FALSE,
+    (unsigned)
+    ~(ACL_BIT_MAIL | ACL_BIT_RCPT | ACL_BIT_PREDATA |
+      ACL_BIT_NOTSMTP_START)
+  },
+#ifdef SUPPORT_I18N
+[CONTROL_UTF8_DOWNCONVERT] =
+  { US"utf8_downconvert",        TRUE, (unsigned) ~(ACL_BIT_RCPT | ACL_BIT_VRFY)
+  }
+#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
 
 /* 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
@@ -761,24 +562,36 @@ further ACL conditions to distinguish ok, unknown, and defer if required, but
 the aim is to make the usual configuration simple. */
 
 static int csa_return_code[] = {
 the aim is to make the usual configuration simple. */
 
 static int csa_return_code[] = {
-  OK, OK, OK, OK,
-  FAIL, FAIL, FAIL, FAIL
+  [CSA_UNKNOWN] =      OK,
+  [CSA_OK] =           OK,
+  [CSA_DEFER_SRV] =    OK,
+  [CSA_DEFER_ADDR] =   OK,
+  [CSA_FAIL_EXPLICIT] =        FAIL,
+  [CSA_FAIL_DOMAIN] =  FAIL,
+  [CSA_FAIL_NOADDR] =  FAIL,
+  [CSA_FAIL_MISMATCH] =        FAIL
 };
 
 static uschar *csa_status_string[] = {
 };
 
 static uschar *csa_status_string[] = {
-  US"unknown", US"ok", US"defer", US"defer",
-  US"fail", US"fail", US"fail", US"fail"
+  [CSA_UNKNOWN] =      US"unknown",
+  [CSA_OK] =           US"ok",
+  [CSA_DEFER_SRV] =    US"defer",
+  [CSA_DEFER_ADDR] =   US"defer",
+  [CSA_FAIL_EXPLICIT] =        US"fail",
+  [CSA_FAIL_DOMAIN] =  US"fail",
+  [CSA_FAIL_NOADDR] =  US"fail",
+  [CSA_FAIL_MISMATCH] =        US"fail"
 };
 
 static uschar *csa_reason_string[] = {
 };
 
 static uschar *csa_reason_string[] = {
-  US"unknown",
-  US"ok",
-  US"deferred (SRV lookup failed)",
-  US"deferred (target address lookup failed)",
-  US"failed (explicit authorization required)",
-  US"failed (host name not authorized)",
-  US"failed (no authorized addresses)",
-  US"failed (client address mismatch)"
+  [CSA_UNKNOWN] =      US"unknown",
+  [CSA_OK] =           US"ok",
+  [CSA_DEFER_SRV] =    US"deferred (SRV lookup failed)",
+  [CSA_DEFER_ADDR] =   US"deferred (target address lookup failed)",
+  [CSA_FAIL_EXPLICIT] =        US"failed (explicit authorization required)",
+  [CSA_FAIL_DOMAIN] =  US"failed (host name not authorized)",
+  [CSA_FAIL_NOADDR] =  US"failed (no authorized addresses)",
+  [CSA_FAIL_MISMATCH] =        US"failed (client address mismatch)"
 };
 
 /* Options for the ratelimit condition. Note that there are two variants of
 };
 
 /* Options for the ratelimit condition. Note that there are two variants of
@@ -796,16 +609,85 @@ enum {
   (((var) == RATE_PER_WHAT) ? ((var) = RATE_##new) : ((var) = RATE_PER_CLASH))
 
 static uschar *ratelimit_option_string[] = {
   (((var) == RATE_PER_WHAT) ? ((var) = RATE_##new) : ((var) = RATE_PER_CLASH))
 
 static uschar *ratelimit_option_string[] = {
-  US"?", US"!", US"per_addr", US"per_byte", US"per_cmd",
-  US"per_conn", US"per_mail", US"per_rcpt", US"per_rcpt"
+  [RATE_PER_WHAT] =    US"?",
+  [RATE_PER_CLASH] =   US"!",
+  [RATE_PER_ADDR] =    US"per_addr",
+  [RATE_PER_BYTE] =    US"per_byte",
+  [RATE_PER_CMD] =     US"per_cmd",
+  [RATE_PER_CONN] =    US"per_conn",
+  [RATE_PER_MAIL] =    US"per_mail",
+  [RATE_PER_RCPT] =    US"per_rcpt",
+  [RATE_PER_ALLRCPTS] =        US"per_rcpt"
 };
 
 /* Enable recursion between acl_check_internal() and acl_check_condition() */
 
 };
 
 /* 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 **);
 
 
     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                *
 *************************************************/
 /*************************************************
 *         Pick out name from list                *
 *************************************************/
@@ -893,8 +775,7 @@ while ((s = (*func)()) != NULL)
   /* If a verb is unrecognized, it may be another condition or modifier that
   continues the previous verb. */
 
   /* 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)
       {
     {
     if (this == NULL)
       {
@@ -931,8 +812,7 @@ while ((s = (*func)()) != NULL)
 
   /* Handle a condition or modifier. */
 
 
   /* 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);
     {
     *error = string_sprintf("unknown ACL condition/modifier in \"%s\"",
       saveline);
@@ -941,10 +821,10 @@ while ((s = (*func)()) != NULL)
 
   /* The modifiers may not be negated */
 
 
   /* 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 "
     {
     *error = string_sprintf("ACL error: negation is not allowed with "
-      "\"%s\"", conditions[c]);
+      "\"%s\"", conditions[c].name);
     return NULL;
     }
 
     return NULL;
     }
 
@@ -955,7 +835,7 @@ while ((s = (*func)()) != NULL)
       this->verb != ACL_DISCARD)
     {
     *error = string_sprintf("ACL error: \"%s\" is not allowed with \"%s\"",
       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;
     }
 
     return NULL;
     }
 
@@ -978,6 +858,26 @@ while ((s = (*func)()) != NULL)
   compatibility. */
 
   if (c == ACLC_SET)
   compatibility. */
 
   if (c == ACLC_SET)
+#ifndef DISABLE_DKIM
+    if (  Ustrncmp(s, "dkim_verify_status", 18) == 0
+       || Ustrncmp(s, "dkim_verify_reason", 18) == 0)
+      {
+      uschar * endptr = s+18;
+
+      if (isalnum(*endptr))
+       {
+       *error = string_sprintf("invalid variable name after \"set\" in ACL "
+         "modifier \"set %s\" "
+         "(only \"dkim_verify_status\" or \"dkim_verify_reason\" permitted)",
+         s);
+       return NULL;
+       }
+      cond->u.varname = string_copyn(s, 18);
+      s = endptr;
+      while (isspace(*s)) s++;
+      }
+    else
+#endif
     {
     uschar *endptr;
 
     {
     uschar *endptr;
 
@@ -1022,7 +922,7 @@ while ((s = (*func)()) != NULL)
     if (*s++ != '=')
       {
       *error = string_sprintf("\"=\" missing after ACL \"%s\" %s", name,
     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++;
       return NULL;
       }
     while (isspace(*s)) s++;
@@ -1049,9 +949,9 @@ Returns:    nothing
 */
 
 static void
 */
 
 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 */
 int hlen = Ustrlen(hstring);
 
 /* Ignore any leading newlines */
@@ -1059,14 +959,24 @@ while (*hstring == '\n') hstring++, hlen--;
 
 /* An empty string does nothing; ensure exactly one final newline. */
 if (hlen <= 0) return;
 
 /* 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 */
 
 
 /* Loop for multiple header lines, taking care about continuations */
 
-for (p = q = hstring; *p != 0; )
+for (p = q; *p; p = q)
   {
   {
-  uschar *s;
+  const uschar *s;
+  uschar * hdr;
   int newtype = htype_add_bot;
   header_line **hptr = &acl_added_headers;
 
   int newtype = htype_add_bot;
   header_line **hptr = &acl_added_headers;
 
@@ -1074,8 +984,8 @@ for (p = q = hstring; *p != 0; )
 
   for (;;)
     {
 
   for (;;)
     {
-    q = Ustrchr(q, '\n');
-    if (*(++q) != ' ' && *q != '\t') break;
+    q = Ustrchr(q, '\n');              /* we know there was a newline */
+    if (*++q != ' ' && *q != '\t') break;
     }
 
   /* If the line starts with a colon, interpret the instruction for where to
     }
 
   /* If the line starts with a colon, interpret the instruction for where to
@@ -1110,37 +1020,31 @@ for (p = q = hstring; *p != 0; )
   to the front of it. */
 
   for (s = p; s < q - 1; s++)
   to the front of it. */
 
   for (s = p; s < q - 1; s++)
-    {
     if (*s == ':' || !isgraph(*s)) break;
     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 */
 
 
   /* See if this line has already been added */
 
-  while (*hptr != NULL)
+  while (*hptr)
     {
     {
-    if (Ustrncmp((*hptr)->text, s, hlen) == 0) break;
-    hptr = &((*hptr)->next);
+    if (Ustrncmp((*hptr)->text, hdr, hlen) == 0) break;
+    hptr = &(*hptr)->next;
     }
 
   /* Add if not previously present */
 
     }
 
   /* Add if not previously present */
 
-  if (*hptr == NULL)
+  if (!*hptr)
     {
     header_line *h = store_get(sizeof(header_line));
     {
     header_line *h = store_get(sizeof(header_line));
-    h->text = s;
+    h->text = hdr;
     h->next = NULL;
     h->type = newtype;
     h->slen = hlen;
     *hptr = h;
     h->next = NULL;
     h->type = newtype;
     h->slen = hlen;
     *hptr = h;
-    hptr = &(h->next);
+    hptr = &h->next;
     }
     }
-
-  /* Advance for next header line within the string */
-
-  p = q;
   }
 }
 
   }
 }
 
@@ -1152,35 +1056,17 @@ for (p = q = hstring; *p != 0; )
 uschar *
 fn_hdrs_added(void)
 {
 uschar *
 fn_hdrs_added(void)
 {
-uschar * ret = NULL;
-header_line * h = acl_added_headers;
-uschar * s;
-uschar * cp;
-int size = 0;
-int ptr = 0;
-
-if (!h) return NULL;
+gstring * g = NULL;
+header_line * h;
 
 
-do
+for (h = acl_added_headers; h; h = h->next)
   {
   {
-  s = h->text;
-  while ((cp = Ustrchr(s, '\n')) != NULL)
-    {
-    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);
-    s = cp+1;
-    }
-  /* last bit of header */
-
-  ret = string_cat(ret, &size, &ptr, s, cp-s+1);       /* newline-sep list */
+  int i = h->slen;
+  if (h->text[i-1] == '\n') i--;
+  g = string_append_listele_n(g, '\n', h->text, i);
   }
   }
-while((h = h->next));
 
 
-ret[ptr-1] = '\0';     /* overwrite last newline */
-return ret;
+return g ? g->s : NULL;
 }
 
 
 }
 
 
@@ -1197,15 +1083,12 @@ Returns:    nothing
 */
 
 static void
 */
 
 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);
-  }
+if (*hnames)
+  acl_removed_headers = acl_removed_headers
+    ? string_sprintf("%s : %s", acl_removed_headers, hnames)
+    : string_copy(hnames);
 }
 
 
 }
 
 
@@ -1259,7 +1142,7 @@ if (log_message != NULL && log_message != user_message)
     int length = Ustrlen(text) + 1;
     log_write(0, LOG_MAIN, "%s", text);
     logged = store_malloc(sizeof(string_item) + length);
     int length = Ustrlen(text) + 1;
     log_write(0, LOG_MAIN, "%s", text);
     logged = store_malloc(sizeof(string_item) + length);
-    logged->text = (uschar *)logged + sizeof(string_item);
+    logged->text = US logged + sizeof(string_item);
     memcpy(logged->text, text, length);
     logged->next = acl_warn_logged;
     acl_warn_logged = logged;
     memcpy(logged->text, text, length);
     logged->next = acl_warn_logged;
     acl_warn_logged = logged;
@@ -1329,7 +1212,7 @@ if (host_lookup_failed)
 /* Need to do a lookup */
 
 HDEBUG(D_acl)
 /* 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)
   {
 
 if ((rc = host_name_lookup()) != OK)
   {
@@ -1363,7 +1246,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
 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
   target     the target hostname to use for matching RR names
 
 Returns:     CSA_OK             successfully authorized
@@ -1389,9 +1272,6 @@ for (rr = dns_next_rr(dnsa, dnss, reset);
   if (rr->type != T_A
     #if HAVE_IPV6
       && rr->type != T_AAAA
   if (rr->type != T_A
     #if HAVE_IPV6
       && rr->type != T_AAAA
-      #ifdef SUPPORT_A6
-        && rr->type != T_A6
-      #endif
     #endif
   ) continue;
 
     #endif
   ) continue;
 
@@ -1406,7 +1286,7 @@ for (rr = dns_next_rr(dnsa, dnss, reset);
     {
     /* If the client IP address matches the target IP address, it's good! */
 
     {
     /* 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;
     }
 
     if (strcmpic(sender_host_address, da->address) == 0) return CSA_OK;
     }
@@ -1444,10 +1324,10 @@ Returns:    CSA_UNKNOWN    no valid CSA record found
 */
 
 static int
 */
 
 static int
-acl_verify_csa(uschar *domain)
+acl_verify_csa(const uschar *domain)
 {
 tree_node *t;
 {
 tree_node *t;
-uschar *found, *p;
+const uschar *found;
 int priority, weight, port;
 dns_answer dnsa;
 dns_scan dnss;
 int priority, weight, port;
 dns_answer dnsa;
 dns_scan dnss;
@@ -1470,7 +1350,7 @@ containing a keyword and a colon before the actual IP address. */
 
 if (domain[0] == '[')
   {
 
 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);
   }
   if (start == NULL) start = domain;
   domain = string_copyn(start + 1, Ustrlen(start) - 2);
   }
@@ -1525,20 +1405,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);
 /* 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) */
 
 
   /* Extract the numerical SRV fields (p is incremented) */
 
-  p = rr->data;
   GETSHORT(priority, p);
   GETSHORT(weight, p);
   GETSHORT(port, p);
 
   DEBUG(D_acl)
   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 */
 
 
   /* Check the CSA version number */
 
@@ -1550,13 +1429,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. */
 
   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
 
   /* 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 +1449,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));
 
   (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;
   }
 
   break;
   }
@@ -1610,24 +1484,20 @@ else
   type = T_A;
 
 
   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:
 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:
 
   /* 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)
 
   /* 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 +1505,7 @@ switch (dns_lookup(&dnsa, target, type, NULL))
 
   case DNS_NOMATCH:
   case DNS_NODATA:
 
   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;
   }
 }
 
   }
 }
 
@@ -1652,7 +1517,7 @@ switch (dns_lookup(&dnsa, target, type, NULL))
 
 enum { VERIFY_REV_HOST_LKUP, VERIFY_CERT, VERIFY_HELO, VERIFY_CSA, VERIFY_HDR_SYNTAX,
        VERIFY_NOT_BLIND, VERIFY_HDR_SNDR, VERIFY_SNDR, VERIFY_RCPT,
 
 enum { VERIFY_REV_HOST_LKUP, VERIFY_CERT, VERIFY_HELO, VERIFY_CSA, VERIFY_HDR_SYNTAX,
        VERIFY_NOT_BLIND, VERIFY_HDR_SNDR, VERIFY_SNDR, VERIFY_RCPT,
-       VERIFY_HDR_NAMES_ASCII
+       VERIFY_HDR_NAMES_ASCII, VERIFY_ARC
   };
 typedef struct {
   uschar * name;
   };
 typedef struct {
   uschar * name;
@@ -1662,25 +1527,29 @@ typedef struct {
   unsigned alt_opt_sep;                /* >0 Non-/ option separator (custom parser) */
   } verify_type_t;
 static verify_type_t verify_type_list[] = {
   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"certificate",         VERIFY_CERT,            ~0,     TRUE, 0 },
-    { US"helo",                        VERIFY_HELO,            ~0,     TRUE, 0 },
+    /* name                    value                   where   no-opt opt-sep */
+    { 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 },
     { US"csa",                 VERIFY_CSA,             ~0,     FALSE, 0 },
-    { US"header_syntax",       VERIFY_HDR_SYNTAX,      (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP), TRUE, 0 },
-    { US"not_blind",           VERIFY_NOT_BLIND,       (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP), TRUE, 0 },
-    { US"header_sender",       VERIFY_HDR_SNDR,        (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP), FALSE, 0 },
-    { US"sender",              VERIFY_SNDR,            (1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)
-                       |(1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP),
+    { US"header_syntax",       VERIFY_HDR_SYNTAX,      ACL_BIT_DATA | ACL_BIT_NOTSMTP, TRUE, 0 },
+    { US"not_blind",           VERIFY_NOT_BLIND,       ACL_BIT_DATA | ACL_BIT_NOTSMTP, TRUE, 0 },
+    { US"header_sender",       VERIFY_HDR_SNDR,        ACL_BIT_DATA | ACL_BIT_NOTSMTP, FALSE, 0 },
+    { US"sender",              VERIFY_SNDR,            ACL_BIT_MAIL | ACL_BIT_RCPT
+                       |ACL_BIT_PREDATA | ACL_BIT_DATA | ACL_BIT_NOTSMTP,
                                                                                FALSE, 6 },
                                                                                FALSE, 6 },
-    { US"recipient",           VERIFY_RCPT,            (1<<ACL_WHERE_RCPT),    FALSE, 0 },
-    { US"header_names_ascii",  VERIFY_HDR_NAMES_ASCII, (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP), TRUE, 0 }
+    { US"recipient",           VERIFY_RCPT,            ACL_BIT_RCPT,   FALSE, 0 },
+    { US"header_names_ascii",  VERIFY_HDR_NAMES_ASCII, ACL_BIT_DATA | ACL_BIT_NOTSMTP, TRUE, 0 },
+#ifdef EXPERIMENTAL_ARC
+    { US"arc",                 VERIFY_ARC,             ACL_BIT_DATA,   FALSE , 0 },
+#endif
   };
 
 
 enum { CALLOUT_DEFER_OK, CALLOUT_NOCACHE, CALLOUT_RANDOM, CALLOUT_USE_SENDER,
   CALLOUT_USE_POSTMASTER, CALLOUT_POSTMASTER, CALLOUT_FULLPOSTMASTER,
   CALLOUT_MAILFROM, CALLOUT_POSTMASTER_MAILFROM, CALLOUT_MAXWAIT, CALLOUT_CONNECT,
   };
 
 
 enum { CALLOUT_DEFER_OK, CALLOUT_NOCACHE, CALLOUT_RANDOM, CALLOUT_USE_SENDER,
   CALLOUT_USE_POSTMASTER, CALLOUT_POSTMASTER, CALLOUT_FULLPOSTMASTER,
   CALLOUT_MAILFROM, CALLOUT_POSTMASTER_MAILFROM, CALLOUT_MAXWAIT, CALLOUT_CONNECT,
-  CALLOUT_TIME
+  CALLOUT_HOLD, CALLOUT_TIME   /* TIME must be last */
   };
 typedef struct {
   uschar * name;
   };
 typedef struct {
   uschar * name;
@@ -1690,6 +1559,7 @@ typedef struct {
   BOOL     timeval;    /* Has a time value */
   } callout_opt_t;
 static callout_opt_t callout_opt_list[] = {
   BOOL     timeval;    /* Has a time value */
   } callout_opt_t;
 static callout_opt_t callout_opt_list[] = {
+    /* name                    value                   flag            has-opt         has-time */
     { US"defer_ok",      CALLOUT_DEFER_OK,      0,                             FALSE, FALSE },
     { US"no_cache",      CALLOUT_NOCACHE,       vopt_callout_no_cache,         FALSE, FALSE },
     { US"random",        CALLOUT_RANDOM,        vopt_callout_random,           FALSE, FALSE },
     { US"defer_ok",      CALLOUT_DEFER_OK,      0,                             FALSE, FALSE },
     { US"no_cache",      CALLOUT_NOCACHE,       vopt_callout_no_cache,         FALSE, FALSE },
     { US"random",        CALLOUT_RANDOM,        vopt_callout_random,           FALSE, FALSE },
@@ -1701,6 +1571,7 @@ static callout_opt_t callout_opt_list[] = {
     { US"mailfrom",      CALLOUT_MAILFROM,      0,                             TRUE,  FALSE },
     { US"maxwait",       CALLOUT_MAXWAIT,       0,                             TRUE,  TRUE },
     { US"connect",       CALLOUT_CONNECT,       0,                             TRUE,  TRUE },
     { US"mailfrom",      CALLOUT_MAILFROM,      0,                             TRUE,  FALSE },
     { US"maxwait",       CALLOUT_MAXWAIT,       0,                             TRUE,  TRUE },
     { US"connect",       CALLOUT_CONNECT,       0,                             TRUE,  TRUE },
+    { US"hold",                  CALLOUT_HOLD,          vopt_callout_hold,             FALSE, FALSE },
     { NULL,              CALLOUT_TIME,          0,                             FALSE, TRUE }
   };
 
     { NULL,              CALLOUT_TIME,          0,                             FALSE, TRUE }
   };
 
@@ -1726,7 +1597,7 @@ Returns:       OK        verification condition succeeded
 */
 
 static int
 */
 
 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 = '/';
   uschar **user_msgptr, uschar **log_msgptr, int *basic_errno)
 {
 int sep = '/';
@@ -1750,77 +1621,103 @@ an error if options are given for items that don't expect them.
 */
 
 uschar *slash = Ustrchr(arg, '/');
 */
 
 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;
 
 uschar *ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size);
 verify_type_t * vp;
 
-if (ss == NULL) goto BAD_VERIFY;
+if (!ss) goto BAD_VERIFY;
 
 /* Handle name/address consistency verification in a separate function. */
 
 for (vp= verify_type_list;
 
 /* Handle name/address consistency verification in a separate function. */
 
 for (vp= verify_type_list;
-     (char *)vp < (char *)verify_type_list + sizeof(verify_type_list);
+     CS vp < CS verify_type_list + sizeof(verify_type_list);
      vp++
     )
   if (vp->alt_opt_sep ? strncmpic(ss, vp->name, vp->alt_opt_sep) == 0
                       : strcmpic (ss, vp->name) == 0)
    break;
      vp++
     )
   if (vp->alt_opt_sep ? strncmpic(ss, vp->name, vp->alt_opt_sep) == 0
                       : strcmpic (ss, vp->name) == 0)
    break;
-if ((char *)vp >= (char *)verify_type_list + sizeof(verify_type_list))
+if (CS vp >= CS verify_type_list + sizeof(verify_type_list))
   goto BAD_VERIFY;
 
   goto BAD_VERIFY;
 
-if (vp->no_options && slash != NULL)
+if (vp->no_options && slash)
   {
   *log_msgptr = string_sprintf("unexpected '/' found in \"%s\" "
     "(this verify item has no options)", arg);
   return ERROR;
   }
   {
   *log_msgptr = string_sprintf("unexpected '/' found in \"%s\" "
     "(this verify item has no options)", arg);
   return ERROR;
   }
-if (!(vp->where_allowed & (1<<where)))
+if (!(vp->where_allowed & BIT(where)))
   {
   {
-  *log_msgptr = string_sprintf("cannot verify %s in ACL for %s", vp->name, acl_wherenames[where]);
+  *log_msgptr = string_sprintf("cannot verify %s in ACL for %s",
+                 vp->name, acl_wherenames[where]);
   return ERROR;
   }
 switch(vp->value)
   {
   case VERIFY_REV_HOST_LKUP:
   return ERROR;
   }
 switch(vp->value)
   {
   case VERIFY_REV_HOST_LKUP:
-    if (sender_host_address == NULL) return OK;
-    return acl_verify_reverse(user_msgptr, log_msgptr);
+    if (!sender_host_address) return OK;
+    if ((rc = acl_verify_reverse(user_msgptr, log_msgptr)) == DEFER)
+      while ((ss = string_nextinlist(&list, &sep, NULL, 0)))
+       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.) */
 
 
   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. */
 
 
   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 (!f.helo_verified && !f.helo_verify_failed) smtp_verify_helo();
+    return f.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. */
 
 
   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_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];
+
+#ifdef EXPERIMENTAL_ARC
+  case VERIFY_ARC:
+    {  /* Do Authenticated Received Chain checks in a separate function. */
+    const uschar * condlist = CUS string_nextinlist(&list, &sep, NULL, 0);
+    int csep = 0;
+    uschar * cond;
+
+    if (!(arc_state = acl_verify_arc())) return DEFER;
+    DEBUG(D_acl) debug_printf_indent("ARC verify result %s %s%s%s\n", arc_state,
+      arc_state_reason ? "(":"", arc_state_reason, arc_state_reason ? ")":"");
+
+    if (!condlist) condlist = US"none:pass";
+    while ((cond = string_nextinlist(&condlist, &csep, NULL, 0)))
+      if (Ustrcmp(arc_state, cond) == 0) return OK;
+    return FAIL;
+    }
+#endif
 
   case VERIFY_HDR_SYNTAX:
 
   case VERIFY_HDR_SYNTAX:
-    /* Check that all relevant header lines have the correct syntax. If there is
+    /* Check that all relevant header lines have the correct 5322-syntax. If there is
     a syntax error, we return details of the error to the sender if configured to
     send out full details. (But a "message" setting on the ACL can override, as
     always). */
 
     rc = verify_check_headers(log_msgptr);
     a syntax error, we return details of the error to the sender if configured to
     send out full details. (But a "message" setting on the ACL can override, as
     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:
     return rc;
 
   case VERIFY_HDR_NAMES_ASCII:
@@ -1828,7 +1725,7 @@ switch(vp->value)
     See RFC 5322, 2.2. and RFC 6532, 3. */
 
     rc = verify_check_header_names_ascii(log_msgptr);
     See RFC 5322, 2.2. and RFC 6532, 3. */
 
     rc = verify_check_header_names_ascii(log_msgptr);
-    if (rc != OK && smtp_return_error_details && *log_msgptr != NULL)
+    if (rc != OK && smtp_return_error_details && *log_msgptr)
       *user_msgptr = string_sprintf("Rejected after DATA: %s", *log_msgptr);
     return rc;
 
       *user_msgptr = string_sprintf("Rejected after DATA: %s", *log_msgptr);
     return rc;
 
@@ -1836,8 +1733,7 @@ switch(vp->value)
     /* Check that no recipient of this message is "blind", that is, every envelope
     recipient must be mentioned in either To: or Cc:. */
 
     /* Check that no recipient of this message is "blind", that is, every envelope
     recipient must be mentioned in either To: or Cc:. */
 
-    rc = verify_check_notblind();
-    if (rc != OK)
+    if ((rc = verify_check_notblind()) != OK)
       {
       *log_msgptr = string_sprintf("bcc recipient detected");
       if (smtp_return_error_details)
       {
       *log_msgptr = string_sprintf("bcc recipient detected");
       if (smtp_return_error_details)
@@ -1911,13 +1807,13 @@ while ((ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size))
       while (isspace(*ss)) ss++;
       if (*ss++ == '=')
         {
       while (isspace(*ss)) ss++;
       if (*ss++ == '=')
         {
+       const uschar * sublist = ss;
         int optsep = ',';
         uschar *opt;
         uschar buffer[256];
         int optsep = ',';
         uschar *opt;
         uschar buffer[256];
-        while (isspace(*ss)) ss++;
+        while (isspace(*sublist)) sublist++;
 
 
-        while ((opt = string_nextinlist(&ss, &optsep, buffer, sizeof(buffer)))
-              != NULL)
+        while ((opt = string_nextinlist(&sublist, &optsep, buffer, sizeof(buffer))))
           {
          callout_opt_t * op;
          double period = 1.0F;
           {
          callout_opt_t * op;
          double period = 1.0F;
@@ -1939,15 +1835,11 @@ while ((ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size))
               }
             while (isspace(*opt)) opt++;
            }
               }
             while (isspace(*opt)) opt++;
            }
-         if (op->timeval)
+         if (op->timeval && (period = readconf_readtime(opt, 0, FALSE)) < 0)
            {
            {
-            period = readconf_readtime(opt, 0, FALSE);
-            if (period < 0)
-              {
-              *log_msgptr = string_sprintf("bad time value in ACL condition "
-                "\"verify %s\"", arg);
-              return ERROR;
-              }
+           *log_msgptr = string_sprintf("bad time value in ACL condition "
+             "\"verify %s\"", arg);
+           return ERROR;
            }
 
          switch(op->value)
            }
 
          switch(op->value)
@@ -2005,17 +1897,17 @@ message if giving out verification details. */
 if (verify_header_sender)
   {
   int verrno;
 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,
     callout_overall, callout_connect, se_mailfrom, pm_mailfrom, verify_options,
-    &verrno);
-  if (rc != OK)
+    &verrno)) != OK)
     {
     *basic_errno = verrno;
     if (smtp_return_error_details)
       {
     {
     *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);
         *user_msgptr = string_sprintf("Rejected after DATA: %s", *log_msgptr);
-      if (rc == DEFER) acl_temp_details = TRUE;
+      if (rc == DEFER) f.acl_temp_details = TRUE;
       }
     }
   }
       }
     }
   }
@@ -2035,10 +1927,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. */
 
 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";
     {
     *log_msgptr = US"use_sender or use_postmaster cannot be used for a "
       "sender verify callout";
@@ -2054,12 +1945,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. */
 
     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;
       }
       {
       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
     }
 
   /* Do a new verification, and cache the result. The cache is used to avoid
@@ -2078,6 +1971,13 @@ else if (verify_sender_address != NULL)
     uschar *save_address_data = deliver_address_data;
 
     sender_vaddr = deliver_make_addr(verify_sender_address, TRUE);
     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)
       {
     if (no_details) setflag(sender_vaddr, af_sverify_told);
     if (verify_sender_address[0] != 0)
       {
@@ -2099,24 +1999,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);
 
       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 */
 
 
     /* Cache the result code */
 
@@ -2134,7 +2033,7 @@ else if (verify_sender_address != NULL)
 
   /* Put the sender address_data value into $sender_address_data */
 
 
   /* 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
   }
 
 /* A recipient address just gets a straightforward verify; again we must handle
@@ -2153,7 +2052,7 @@ else
   addr2 = *addr;
   rc = verify_address(&addr2, NULL, verify_options|vopt_is_recipient, callout,
     callout_overall, callout_connect, se_mailfrom, pm_mailfrom, NULL);
   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;
 
   *basic_errno = addr2.basic_errno;
   *log_msgptr = addr2.message;
@@ -2161,10 +2060,10 @@ else
     addr2.user_message : addr2.message;
 
   /* Allow details for temporary error if the address is so flagged. */
     addr2.user_message : addr2.message;
 
   /* Allow details for temporary error if the address is so flagged. */
-  if (testflag((&addr2), af_pass_message)) acl_temp_details = TRUE;
+  if (testflag((&addr2), af_pass_message)) f.acl_temp_details = TRUE;
 
   /* Make $address_data visible */
 
   /* 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. */
   }
 
 /* We have a result from the relevant test. Handle defer overrides first. */
@@ -2172,7 +2071,7 @@ else
 if (rc == DEFER && (defer_ok ||
    (callout_defer_ok && *basic_errno == ERRNO_CALLOUTDEFER)))
   {
 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;
   }
     defer_ok? "defer_ok" : "callout_defer_ok");
   rc = OK;
   }
@@ -2183,13 +2082,9 @@ sender_verified_failed to the address item that actually failed. */
 if (rc != OK && verify_sender_address != NULL)
   {
   if (rc != DEFER)
 if (rc != OK && verify_sender_address != NULL)
   {
   if (rc != DEFER)
-    {
     *log_msgptr = *user_msgptr = US"Sender verify failed";
     *log_msgptr = *user_msgptr = US"Sender verify failed";
-    }
   else if (*basic_errno != ERRNO_CALLOUTDEFER)
   else if (*basic_errno != ERRNO_CALLOUTDEFER)
-    {
     *log_msgptr = *user_msgptr = US"Could not complete sender verify";
     *log_msgptr = *user_msgptr = US"Could not complete sender verify";
-    }
   else
     {
     *log_msgptr = US"Could not complete sender verify callout";
   else
     {
     *log_msgptr = US"Could not complete sender verify callout";
@@ -2239,28 +2134,22 @@ Returns:      CONTROL_xxx value
 */
 
 static int
 */
 
 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;
   {
   *log_msgptr = string_sprintf("syntax error in \"control=%s\"", arg);
   return CONTROL_ERROR;
   }
 
 *pptr = arg + len;
-return d->value;
+return idx;
 }
 
 
 }
 
 
@@ -2276,8 +2165,6 @@ Arguments:
   log_msgptr  for error messages
   format      format string
   ...         supplementary arguments
   log_msgptr  for error messages
   format      format string
   ...         supplementary arguments
-  ss          ratelimit option name
-  where       ACL_WHERE_xxxx indicating which ACL this is
 
 Returns:      ERROR
 */
 
 Returns:      ERROR
 */
@@ -2286,14 +2173,15 @@ static int
 ratelimit_error(uschar **log_msgptr, const char *format, ...)
 {
 va_list ap;
 ratelimit_error(uschar **log_msgptr, const char *format, ...)
 {
 va_list ap;
-uschar buffer[STRING_SPRINTF_BUFFER_SIZE];
+gstring * g =
+  string_cat(NULL, US"error in arguments to \"ratelimit\" condition: ");
+
 va_start(ap, format);
 va_start(ap, format);
-if (!string_vformat(buffer, sizeof(buffer), format, ap))
-  log_write(0, LOG_MAIN|LOG_PANIC_DIE,
-    "string_sprintf expansion was longer than " SIZE_T_FMT, sizeof(buffer));
+g = string_vformat(g, TRUE, format, ap);
 va_end(ap);
 va_end(ap);
-*log_msgptr = string_sprintf(
-  "error in arguments to \"ratelimit\" condition: %s", buffer);
+
+gstring_reset_unused(g);
+*log_msgptr = string_from_gstring(g);
 return ERROR;
 }
 
 return ERROR;
 }
 
@@ -2324,7 +2212,7 @@ Returns:       OK        - Sender's rate is above limit
 */
 
 static int
 */
 
 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;
 {
 double limit, period, count;
 uschar *ss;
@@ -2350,16 +2238,14 @@ error messages based on rate limits obtained from a table lookup. */
 size, which must be greater than or equal to zero. Zero is useful for
 rate measurement as opposed to rate limiting. */
 
 size, which must be greater than or equal to zero. Zero is useful for
 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++; }
-  }
+if (!(sender_rate_limit = string_nextinlist(&arg, &sep, NULL, 0)))
+  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);
 if (limit < 0.0 || *ss != '\0')
   return ratelimit_error(log_msgptr,
     "\"%s\" is not a positive number", sender_rate_limit);
@@ -2368,9 +2254,8 @@ if (limit < 0.0 || *ss != '\0')
 constant. This must be strictly greater than zero, because zero leads to
 run-time division errors. */
 
 constant. This must be strictly greater than zero, because zero leads to
 run-time division errors. */
 
-sender_rate_period = string_nextinlist(&arg, &sep, NULL, 0);
-if (sender_rate_period == NULL) period = -1.0;
-else period = readconf_readtime(sender_rate_period, 0, FALSE);
+period = !(sender_rate_period = string_nextinlist(&arg, &sep, NULL, 0))
+  ? -1.0 : readconf_readtime(sender_rate_period, 0, FALSE);
 if (period <= 0.0)
   return ratelimit_error(log_msgptr,
     "\"%s\" is not a time value", sender_rate_period);
 if (period <= 0.0)
   return ratelimit_error(log_msgptr,
     "\"%s\" is not a time value", sender_rate_period);
@@ -2382,8 +2267,7 @@ count = 1.0;
 
 /* Parse the other options. */
 
 
 /* Parse the other options. */
 
-while ((ss = string_nextinlist(&arg, &sep, big_buffer, big_buffer_size))
-       != NULL)
+while ((ss = string_nextinlist(&arg, &sep, big_buffer, big_buffer_size)))
   {
   if (strcmpic(ss, US"leaky") == 0) leaky = TRUE;
   else if (strcmpic(ss, US"strict") == 0) strict = TRUE;
   {
   if (strcmpic(ss, US"leaky") == 0) leaky = TRUE;
   else if (strcmpic(ss, US"strict") == 0) strict = TRUE;
@@ -2416,29 +2300,28 @@ 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
   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;
     zero and let the recorded rate decay as if nothing happened. */
     RATE_SET(mode, PER_MAIL);
     if (where > ACL_WHERE_NOTSMTP) badacl = TRUE;
-      else count = message_size < 0 ? 0.0 : (double)message_size;
+    else count = message_size < 0 ? 0.0 : (double)message_size;
     }
   else if (strcmpic(ss, US"per_addr") == 0)
     {
     RATE_SET(mode, PER_RCPT);
     if (where != ACL_WHERE_RCPT) badacl = TRUE, unique = US"*";
     }
   else if (strcmpic(ss, US"per_addr") == 0)
     {
     RATE_SET(mode, PER_RCPT);
     if (where != ACL_WHERE_RCPT) badacl = TRUE, unique = US"*";
-      else unique = string_sprintf("%s@%s", deliver_localpart, deliver_domain);
+    else unique = string_sprintf("%s@%s", deliver_localpart, deliver_domain);
     }
   else if (strncmpic(ss, US"count=", 6) == 0)
     {
     uschar *e;
     count = Ustrtod(ss+6, &e);
     if (count < 0.0 || *e != '\0')
     }
   else if (strncmpic(ss, US"count=", 6) == 0)
     {
     uschar *e;
     count = Ustrtod(ss+6, &e);
     if (count < 0.0 || *e != '\0')
-      return ratelimit_error(log_msgptr,
-       "\"%s\" is not a positive number", ss);
+      return ratelimit_error(log_msgptr, "\"%s\" is not a positive number", ss);
     }
   else if (strncmpic(ss, US"unique=", 7) == 0)
     unique = string_copy(ss + 7);
     }
   else if (strncmpic(ss, US"unique=", 7) == 0)
     unique = string_copy(ss + 7);
-  else if (key == NULL)
+  else if (!key)
     key = string_copy(ss);
   else
     key = string_sprintf("%s/%s", key, ss);
     key = string_copy(ss);
   else
     key = string_sprintf("%s/%s", key, ss);
@@ -2454,7 +2337,7 @@ if (leaky + strict + readonly > 1)
   return ratelimit_error(log_msgptr, "conflicting update modes");
 if (badacl && (leaky || strict) && !noupdate)
   return ratelimit_error(log_msgptr,
   return ratelimit_error(log_msgptr, "conflicting update modes");
 if (badacl && (leaky || strict) && !noupdate)
   return ratelimit_error(log_msgptr,
-    "\"%s\" must not have /leaky or /strict option in %s ACL",
+    "\"%s\" must not have /leaky or /strict option, or cannot be used in %s ACL",
     ratelimit_option_string[mode], acl_wherenames[where]);
 
 /* Set the default values of any unset options. In readonly mode we
     ratelimit_option_string[mode], acl_wherenames[where]);
 
 /* Set the default values of any unset options. In readonly mode we
@@ -2472,8 +2355,8 @@ If there is no sender_host_address (e.g. -bs or acl_not_smtp) then we simply
 omit it. The smoothing constant (sender_rate_period) and the per_xxx options
 are added to the key because they alter the meaning of the stored data. */
 
 omit it. The smoothing constant (sender_rate_period) and the per_xxx options
 are added to the key because they alter the meaning of the stored data. */
 
-if (key == NULL)
-  key = (sender_host_address == NULL)? US"" : sender_host_address;
+if (!key)
+  key = !sender_host_address ? US"" : sender_host_address;
 
 key = string_sprintf("%s/%s/%s%s",
   sender_rate_period,
 
 key = string_sprintf("%s/%s/%s%s",
   sender_rate_period,
@@ -2482,7 +2365,7 @@ key = string_sprintf("%s/%s/%s%s",
   key);
 
 HDEBUG(D_acl)
   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
 
 /* 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
@@ -2494,30 +2377,30 @@ old_pool = store_pool;
 
 if (readonly)
   anchor = &ratelimiters_cmd;
 
 if (readonly)
   anchor = &ratelimiters_cmd;
-else switch(mode) {
-case RATE_PER_CONN:
-  anchor = &ratelimiters_conn;
-  store_pool = POOL_PERM;
-  break;
-case RATE_PER_BYTE:
-case RATE_PER_MAIL:
-case RATE_PER_ALLRCPTS:
-  anchor = &ratelimiters_mail;
-  break;
-case RATE_PER_ADDR:
-case RATE_PER_CMD:
-case RATE_PER_RCPT:
-  anchor = &ratelimiters_cmd;
-  break;
-default:
-  anchor = NULL; /* silence an "unused" complaint */
-  log_write(0, LOG_MAIN|LOG_PANIC_DIE,
-    "internal ACL error: unknown ratelimit mode %d", mode);
-  break;
-}
+else switch(mode)
+  {
+  case RATE_PER_CONN:
+    anchor = &ratelimiters_conn;
+    store_pool = POOL_PERM;
+    break;
+  case RATE_PER_BYTE:
+  case RATE_PER_MAIL:
+  case RATE_PER_ALLRCPTS:
+    anchor = &ratelimiters_mail;
+    break;
+  case RATE_PER_ADDR:
+  case RATE_PER_CMD:
+  case RATE_PER_RCPT:
+    anchor = &ratelimiters_cmd;
+    break;
+  default:
+    anchor = NULL; /* silence an "unused" complaint */
+    log_write(0, LOG_MAIN|LOG_PANIC_DIE,
+      "internal ACL error: unknown ratelimit mode %d", mode);
+    break;
+  }
 
 
-t = tree_search(*anchor, key);
-if (t != NULL)
+if ((t = tree_search(*anchor, key)))
   {
   dbd = t->data.ptr;
   /* The following few lines duplicate some of the code below. */
   {
   dbd = t->data.ptr;
   /* The following few lines duplicate some of the code below. */
@@ -2525,19 +2408,18 @@ if (t != NULL)
   store_pool = old_pool;
   sender_rate = string_sprintf("%.1f", dbd->rate);
   HDEBUG(D_acl)
   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;
   }
 
 /* We aren't using a pre-computed rate, so get a previously recorded rate
 from the database, which will be updated and written back if required. */
 
   return rc;
   }
 
 /* We aren't using a pre-computed rate, so get a previously recorded rate
 from the database, which will be updated and written back if required. */
 
-dbm = dbfn_open(US"ratelimit", O_RDWR, &dbblock, TRUE);
-if (dbm == NULL)
+if (!(dbm = dbfn_open(US"ratelimit", O_RDWR, &dbblock, TRUE)))
   {
   store_pool = old_pool;
   sender_rate = 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;
   }
   *log_msgptr = US"ratelimit database not available";
   return DEFER;
   }
@@ -2546,10 +2428,10 @@ dbd = NULL;
 
 gettimeofday(&tv, NULL);
 
 
 gettimeofday(&tv, NULL);
 
-if (dbdb != NULL)
+if (dbdb)
   {
   /* Locate the basic ratelimit block inside the DB data. */
   {
   /* 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
   dbd = &dbdb->dbd;
 
   /* Forget the old Bloom filter if it is too old, so that we count each
@@ -2557,17 +2439,17 @@ if (dbdb != NULL)
   filter because we want its size to change if the limit changes. Note that
   we keep the dbd pointer for copying the rate into the new data block. */
 
   filter because we want its size to change if the limit changes. Note that
   we keep the dbd pointer for copying the rate into the new data block. */
 
-  if(unique != NULL && tv.tv_sec > dbdb->bloom_epoch + period)
+  if(unique && 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;
     }
 
   /* Sanity check. */
 
     dbdb = NULL;
     }
 
   /* Sanity check. */
 
-  if(unique != NULL && dbdb_size < sizeof(*dbdb))
+  if(unique && 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;
     }
   }
     dbdb = NULL;
     }
   }
@@ -2575,19 +2457,19 @@ if (dbdb != NULL)
 /* Allocate a new data block if the database lookup failed
 or the Bloom filter passed its age limit. */
 
 /* Allocate a new data block if the database lookup failed
 or the Bloom filter passed its age limit. */
 
-if (dbdb == NULL)
+if (!dbdb)
   {
   {
-  if (unique == NULL)
+  if (!unique)
     {
     /* No Bloom filter. This basic ratelimit block is initialized below. */
     {
     /* 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;
     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;
 
     /* 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;
@@ -2604,7 +2486,7 @@ if (dbdb == NULL)
     /* Preserve any basic ratelimit data (which is our longer-term memory)
     by copying it from the discarded block. */
 
     /* Preserve any basic ratelimit data (which is our longer-term memory)
     by copying it from the discarded block. */
 
-    if (dbd != NULL)
+    if (dbd)
       {
       dbdb->dbd = *dbd;
       dbd = &dbdb->dbd;
       {
       dbdb->dbd = *dbd;
       dbd = &dbdb->dbd;
@@ -2618,7 +2500,7 @@ counted. We skip this code in readonly mode for efficiency, because any
 changes to the filter will be discarded and because count is already set to
 zero. */
 
 changes to the filter will be discarded and because count is already set to
 zero. */
 
-if (unique != NULL && !readonly)
+if (unique && !readonly)
   {
   /* We identify unique events using a Bloom filter. (You can find my
   notes on Bloom filters at http://fanf.livejournal.com/81696.html)
   {
   /* We identify unique events using a Bloom filter. (You can find my
   notes on Bloom filters at http://fanf.livejournal.com/81696.html)
@@ -2671,7 +2553,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. */
 
   /* 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)
 
   seen = TRUE;
   for (n = 0; n < 8; n++, hash += hinc)
@@ -2689,20 +2571,20 @@ if (unique != NULL && !readonly)
 
   if (seen)
     {
 
   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
     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
 the new one, otherwise update the block from the database. The initial rate
 is what would be computed by the code below for an infinite interval. */
 
   }
 
 /* If there was no previous ratelimit data block for this key, initialize
 the new one, otherwise update the block from the database. The initial rate
 is what would be computed by the code below for an infinite interval. */
 
-if (dbd == NULL)
+if (!dbd)
   {
   {
-  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;
   dbd = &dbdb->dbd;
   dbd->time_stamp = tv.tv_sec;
   dbd->time_usec = tv.tv_usec;
@@ -2782,7 +2664,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
   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;
   below is true if the interval is greater than the period. */
 
   if (dbd->rate < count) dbd->rate = count;
@@ -2792,7 +2674,7 @@ else
 This matters for edge cases such as a limit of zero, when the client
 should be completely blocked. */
 
 This matters for edge cases such as a limit of zero, when the client
 should be completely blocked. */
 
-rc = (dbd->rate < limit)? FAIL : OK;
+rc = dbd->rate < limit ? FAIL : OK;
 
 /* Update the state if the rate is low or if we are being strict. If we
 are in leaky mode and the sender's rate is too high, we do not update
 
 /* Update the state if the rate is low or if we are being strict. If we
 are in leaky mode and the sender's rate is too high, we do not update
@@ -2803,11 +2685,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);
 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
   {
   }
 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");
   }
 
     readonly? "readonly mode" : "over the limit, but leaky");
   }
 
@@ -2827,7 +2709,7 @@ store_pool = old_pool;
 sender_rate = string_sprintf("%.1f", dbd->rate);
 
 HDEBUG(D_acl)
 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;
 }
 
 return rc;
 }
@@ -2850,7 +2732,7 @@ Returns:       OK        - Completed.
 */
 
 static int
 */
 
 static int
-acl_udpsend(uschar *arg, uschar **log_msgptr)
+acl_udpsend(const uschar *arg, uschar **log_msgptr)
 {
 int sep = 0;
 uschar *hostname;
 {
 int sep = 0;
 uschar *hostname;
@@ -2865,17 +2747,17 @@ uschar * errstr;
 hostname = string_nextinlist(&arg, &sep, NULL, 0);
 portstr = string_nextinlist(&arg, &sep, NULL, 0);
 
 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;
   }
   {
   *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;
   }
   {
   *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;
   {
   *log_msgptr = US"missing datagram payload in \"udpsend\" modifier";
   return ERROR;
@@ -2905,10 +2787,11 @@ if (r == HOST_FIND_FAILED || r == HOST_FIND_AGAIN)
   }
 
 HDEBUG(D_acl)
   }
 
 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);
 
 
+/*XXX this could better use sendto */
 r = s = ip_connectedsocket(SOCK_DGRAM, h->address, portnum, portnum,
 r = s = ip_connectedsocket(SOCK_DGRAM, h->address, portnum, portnum,
-               1, NULL, &errstr);
+               1, NULL, &errstr, NULL);
 if (r < 0) goto defer;
 len = Ustrlen(arg);
 r = send(s, arg, len, 0);
 if (r < 0) goto defer;
 len = Ustrlen(arg);
 r = send(s, arg, len, 0);
@@ -2927,7 +2810,7 @@ if (r < len)
   }
 
 HDEBUG(D_acl)
   }
 
 HDEBUG(D_acl)
-  debug_printf("udpsend %d bytes\n", r);
+  debug_printf_indent("udpsend %d bytes\n", r);
 
 return OK;
 
 
 return OK;
 
@@ -2974,17 +2857,14 @@ acl_check_condition(int verb, acl_condition_block *cb, int where,
 {
 uschar *user_message = NULL;
 uschar *log_message = NULL;
 {
 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 rc = OK;
 #ifdef WITH_CONTENT_SCAN
-int sep = '/';
+int sep = -'/';
 #endif
 
 #endif
 
-for (; cb != NULL; cb = cb->next)
+for (; cb; cb = cb->next)
   {
   {
-  uschar *arg;
+  const uschar *arg;
   int control_type;
 
   /* The message and log_message items set up messages to be used in
   int control_type;
 
   /* The message and log_message items set up messages to be used in
@@ -2992,14 +2872,14 @@ for (; cb != NULL; cb = cb->next)
 
   if (cb->type == ACLC_MESSAGE)
     {
 
   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)
     {
     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;
     }
     log_message = cb->arg;
     continue;
     }
@@ -3017,32 +2897,40 @@ 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. */
 
   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 (f.expand_string_forcedfail) continue;
+    *log_msgptr = string_sprintf("failed to expand ACL string \"%s\": %s",
+      cb->arg, expand_string_message);
+    return f.search_find_defer ? DEFER : ERROR;
     }
     }
-  else arg = cb->arg;
 
   /* Show condition, and expanded condition if it's different */
 
   HDEBUG(D_acl)
     {
     int lhswidth = 0;
 
   /* 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)
       {
 
     if (cb->type == ACLC_SET)
       {
-      debug_printf("acl_%s ", cb->u.varname);
-      lhswidth += 5 + Ustrlen(cb->u.varname);
+#ifndef DISABLE_DKIM
+      if (  Ustrcmp(cb->u.varname, "dkim_verify_status") == 0
+        || Ustrcmp(cb->u.varname, "dkim_verify_reason") == 0)
+       {
+       debug_printf("%s ", cb->u.varname);
+       lhswidth += 19;
+       }
+      else
+#endif
+       {
+       debug_printf("acl_%s ", cb->u.varname);
+       lhswidth += 5 + Ustrlen(cb->u.varname);
+       }
       }
 
     debug_printf("= %s\n", cb->arg);
       }
 
     debug_printf("= %s\n", cb->arg);
@@ -3054,11 +2942,11 @@ for (; cb != NULL; cb = cb->next)
 
   /* Check that this condition makes sense at this time */
 
 
   /* 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",
     {
     *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;
     }
 
     return ERROR;
     }
 
@@ -3075,7 +2963,7 @@ for (; cb != NULL; cb = cb->next)
     "discard" verb. */
 
     case ACLC_ACL:
     "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 "
       if (rc == DISCARD && verb != ACL_ACCEPT && verb != ACL_DISCARD)
         {
         *log_msgptr = string_sprintf("nested ACL returned \"discard\" for "
@@ -3086,9 +2974,8 @@ for (; cb != NULL; cb = cb->next)
     break;
 
     case ACLC_AUTHENTICATED:
     break;
 
     case ACLC_AUTHENTICATED:
-    rc = (sender_host_authenticated == NULL)? FAIL :
-      match_isinlist(sender_host_authenticated, &arg, 0, NULL, NULL, MCL_STRING,
-        TRUE, NULL);
+      rc = sender_host_authenticated ? match_isinlist(sender_host_authenticated,
+             &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL) : FAIL;
     break;
 
     #ifdef EXPERIMENTAL_BRIGHTMAIL
     break;
 
     #ifdef EXPERIMENTAL_BRIGHTMAIL
@@ -3123,267 +3010,359 @@ for (; cb != NULL; cb = cb->next)
     break;
 
     case ACLC_CONTROL:
     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:
+       f.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:
+       f.dkim_disable_verify = TRUE;
+       #ifdef EXPERIMENTAL_DMARC
+       /* Since DKIM was blocked, skip DMARC too */
+       f.dmarc_disable_verify = TRUE;
+       f.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:
+       f.dmarc_disable_verify = TRUE;
+       break;
 
 
-      case CONTROL_DMARC_FORENSIC:
-      dmarc_enable_forensic = TRUE;
-      break;
-      #endif
+       case CONTROL_DMARC_FORENSIC:
+       f.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:
+       f.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:
+       f.no_multiline_responses = TRUE;
+       break;
 
 
-      case CONTROL_NO_MULTILINE:
-      no_multiline_responses = TRUE;
-      break;
+       case CONTROL_NO_PIPELINING:
+       f.pipelining_enable = FALSE;
+       break;
 
 
-      case CONTROL_NO_PIPELINING:
-      pipelining_enable = FALSE;
-      break;
+       case CONTROL_NO_DELAY_FLUSH:
+       f.disable_delay_flush = TRUE;
+       break;
 
 
-      case CONTROL_NO_DELAY_FLUSH:
-      disable_delay_flush = TRUE;
-      break;
+       case CONTROL_NO_CALLOUT_FLUSH:
+       f.disable_callout_flush = TRUE;
+       break;
 
 
-      case CONTROL_NO_CALLOUT_FLUSH:
-      disable_callout_flush = TRUE;
-      break;
+       case CONTROL_FAKEREJECT:
+       cancel_cutthrough_connection(TRUE, US"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:
+       f.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(TRUE, US"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:
+       f.queue_only_policy = TRUE;
+       cancel_cutthrough_connection(TRUE, US"queueing forced");
+       break;
 
 
-      case CONTROL_QUEUE_ONLY:
-      queue_only_policy = TRUE;
-      cancel_cutthrough_connection("queueing forced");
-      break;
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+       case CONTROL_REQUIRETLS:
+       tls_requiretls |= REQUIRETLS_MSG;
+       break;
+#endif
+       case CONTROL_SUBMISSION:
+       originator_name = US"";
+       f.submission_mode = TRUE;
+       while (*p == '/')
+         {
+         if (Ustrncmp(p, "/sender_retain", 14) == 0)
+           {
+           p += 14;
+           f.active_local_sender_retain = TRUE;
+           f.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
+       case CONTROL_SUPPRESS_LOCAL_FIXUPS:
+       f.suppress_local_fixups = TRUE;
+       break;
+
+       case CONTROL_CUTTHROUGH_DELIVERY:
        {
        {
-       cutthrough_delivery = TRUE;
+       uschar * ignored = NULL;
+#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" */
+         ignored = US"PRDR active";
+       else
+         {
+         if (f.deliver_freeze)
+           ignored = US"frozen";
+         else if (f.queue_only_policy)
+           ignored = US"queue-only";
+         else if (fake_response == FAIL)
+           ignored = US"fakereject";
+         else
+           {
+           if (rcpt_count == 1)
+             {
+             cutthrough.delivery = TRUE;       /* control accepted */
+             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;
+               }
+             }
+           else
+             ignored = US"nonfirst rcpt";
+           }
+         }
+       DEBUG(D_acl) if (ignored)
+         debug_printf(" cutthrough request ignored on %s item\n", ignored);
+       }
        break;
        break;
+
+#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:
       {
 
     #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);
       /* 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)
         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
       }
     break;
     #endif
@@ -3405,25 +3384,15 @@ for (; cb != NULL; cb = cb->next)
         }
       else
         {
         }
       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)
           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");
           }
 
           }
 
-        /* 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
-        that the client has closed its end of the connection. (The connection
-        is actually in a half-closed state, waiting for the server to close its
-        end.) It would be nice to be able to detect this state, so that the
-        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
+       /* 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.
         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.
@@ -3434,39 +3403,61 @@ for (; cb != NULL; cb = cb->next)
 
         else
           {
 
         else
           {
-          if (smtp_out != NULL && !disable_delay_flush) mac_smtp_fflush();
+          if (smtp_out && !f.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
+         /* Lacking POLLRDHUP 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 that the client has closed its end of the
+         connection. (The connection is actually in a half-closed state,
+         waiting for the server to close its end.) It would be nice to be able
+         to detect this state, so that the 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.  */
+
           while (delay > 0) delay = sleep(delay);
           while (delay > 0) delay = sleep(delay);
+#endif
           }
         }
       }
     break;
 
           }
         }
       }
     break;
 
-    #ifdef WITH_OLD_DEMIME
-    case ACLC_DEMIME:
-      rc = demime(&arg);
-    break;
-    #endif
-
     #ifndef DISABLE_DKIM
     case ACLC_DKIM_SIGNER:
     #ifndef DISABLE_DKIM
     case ACLC_DKIM_SIGNER:
-    if (dkim_cur_signer != NULL)
+    if (dkim_cur_signer)
       rc = match_isinlist(dkim_cur_signer,
                           &arg,0,NULL,NULL,MCL_STRING,TRUE,NULL);
     else
       rc = match_isinlist(dkim_cur_signer,
                           &arg,0,NULL,NULL,MCL_STRING,TRUE,NULL);
     else
-       rc = FAIL;
+      rc = FAIL;
     break;
 
     case ACLC_DKIM_STATUS:
     break;
 
     case ACLC_DKIM_STATUS:
-    rc = match_isinlist(dkim_exim_expand_query(DKIM_VERIFY_STATUS),
+    rc = match_isinlist(dkim_verify_status,
                         &arg,0,NULL,NULL,MCL_STRING,TRUE,NULL);
     break;
     #endif
 
     #ifdef EXPERIMENTAL_DMARC
     case ACLC_DMARC_STATUS:
                         &arg,0,NULL,NULL,MCL_STRING,TRUE,NULL);
     break;
     #endif
 
     #ifdef EXPERIMENTAL_DMARC
     case ACLC_DMARC_STATUS:
-    if (!dmarc_has_been_checked)
+    if (!f.dmarc_has_been_checked)
       dmarc_process();
       dmarc_process();
-    dmarc_has_been_checked = TRUE;
+    f.dmarc_has_been_checked = TRUE;
     /* used long way of dmarc_exim_expand_query() in case we need more
      * view into the process in the future. */
     rc = match_isinlist(dmarc_exim_expand_query(DMARC_VERIFY_STATUS),
     /* used long way of dmarc_exim_expand_query() in case we need more
      * view into the process in the future. */
     rc = match_isinlist(dmarc_exim_expand_query(DMARC_VERIFY_STATUS),
@@ -3475,12 +3466,12 @@ for (; cb != NULL; cb = cb->next)
     #endif
 
     case ACLC_DNSLISTS:
     #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,
     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,
     break;
 
     /* The value in tls_cipher is the full cipher name, for example,
@@ -3513,24 +3504,25 @@ for (; cb != NULL; cb = cb->next)
 
     case ACLC_HOSTS:
     rc = verify_check_this_host(&arg, sender_host_cache, NULL,
 
     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,
     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;
     break;
 
     case ACLC_LOG_REJECT_TARGET:
       {
       int logbits = 0;
       int sep = 0;
-      uschar *s = arg;
+      const uschar *s = arg;
       uschar *ss;
       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;
         {
         if (Ustrcmp(ss, "main") == 0) logbits |= LOG_MAIN;
         else if (Ustrcmp(ss, "panic") == 0) logbits |= LOG_PANIC;
@@ -3549,7 +3541,7 @@ for (; cb != NULL; cb = cb->next)
     case ACLC_LOGWRITE:
       {
       int logbits = 0;
     case ACLC_LOGWRITE:
       {
       int logbits = 0;
-      uschar *s = arg;
+      const uschar *s = arg;
       if (*s == ':')
         {
         s++;
       if (*s == ':')
         {
         s++;
@@ -3573,28 +3565,35 @@ for (; cb != NULL; cb = cb->next)
         }
       while (isspace(*s)) s++;
 
         }
       while (isspace(*s)) s++;
 
-
       if (logbits == 0) logbits = LOG_MAIN;
       log_write(0, logbits, "%s", string_printing(s));
       }
     break;
 
     #ifdef WITH_CONTENT_SCAN
       if (logbits == 0) logbits = LOG_MAIN;
       log_write(0, logbits, "%s", string_printing(s));
       }
     break;
 
     #ifdef WITH_CONTENT_SCAN
-    case ACLC_MALWARE:
+    case ACLC_MALWARE:                 /* Run the malware backend. */
       {
       /* Separate the regular expression and any optional parameters. */
       {
       /* 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;
 
       }
     break;
 
@@ -3603,13 +3602,23 @@ for (; cb != NULL; cb = cb->next)
     break;
     #endif
 
     break;
     #endif
 
+    case ACLC_QUEUE:
+    if (Ustrchr(arg, '/'))
+      {
+      *log_msgptr = string_sprintf(
+             "Directory separator not permitted in queue name: '%s'", arg);
+      return ERROR;
+      }
+    queue_name = string_copy_malloc(arg);
+    break;
+
     case ACLC_RATELIMIT:
     rc = acl_ratelimit(arg, where, log_msgptr);
     break;
 
     case ACLC_RECIPIENTS:
     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(CUS addr->address, TRUE, TRUE, &arg, NULL, -1, 0,
+      CUSS &recipient_data);
     break;
 
     #ifdef WITH_CONTENT_SCAN
     break;
 
     #ifdef WITH_CONTENT_SCAN
@@ -3626,15 +3635,15 @@ for (; cb != NULL; cb = cb->next)
       {
       uschar *sdomain;
       sdomain = Ustrrchr(sender_address, '@');
       {
       uschar *sdomain;
       sdomain = Ustrrchr(sender_address, '@');
-      sdomain = (sdomain == NULL)? US"" : sdomain + 1;
+      sdomain = sdomain ? sdomain + 1 : US"";
       rc = match_isinlist(sdomain, &arg, 0, &domainlist_anchor,
         sender_domain_cache, MCL_DOMAIN, TRUE, NULL);
       }
     break;
 
     case ACLC_SENDERS:
       rc = match_isinlist(sdomain, &arg, 0, &domainlist_anchor,
         sender_domain_cache, MCL_DOMAIN, TRUE, NULL);
       }
     break;
 
     case ACLC_SENDERS:
-    rc = match_address_list(sender_address, TRUE, TRUE, &arg,
-      sender_address_cache, -1, 0, &sender_data);
+    rc = match_address_list(CUS sender_address, TRUE, TRUE, &arg,
+      sender_address_cache, -1, 0, CUSS &sender_data);
     break;
 
     /* Connection variables must persist forever */
     break;
 
     /* Connection variables must persist forever */
@@ -3642,40 +3651,51 @@ for (; cb != NULL; cb = cb->next)
     case ACLC_SET:
       {
       int old_pool = store_pool;
     case ACLC_SET:
       {
       int old_pool = store_pool;
-      if (cb->u.varname[0] == 'c') store_pool = POOL_PERM;
-      acl_var_create(cb->u.varname)->data.ptr = string_copy(arg);
+      if (  cb->u.varname[0] == 'c'
+#ifndef DISABLE_DKIM
+         || cb->u.varname[0] == 'd'
+#endif
+#ifndef DISABLE_EVENT
+        || event_name          /* An event is being delivered */
+#endif
+        )
+        store_pool = POOL_PERM;
+#ifndef DISABLE_DKIM   /* Overwriteable dkim result variables */
+      if (Ustrcmp(cb->u.varname, "dkim_verify_status") == 0)
+       dkim_verify_status = string_copy(arg);
+      else if (Ustrcmp(cb->u.varname, "dkim_verify_reason") == 0)
+       dkim_verify_reason = string_copy(arg);
+      else
+#endif
+       acl_var_create(cb->u.varname)->data.ptr = string_copy(arg);
       store_pool = old_pool;
       }
     break;
 
       store_pool = old_pool;
       }
     break;
 
-    #ifdef WITH_CONTENT_SCAN
+#ifdef WITH_CONTENT_SCAN
     case ACLC_SPAM:
       {
     case ACLC_SPAM:
       {
-      /* Seperate the regular expression and any optional parameters. */
-      uschar *ss = string_nextinlist(&arg, &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))
-            != NULL) {
+      /* Separate the regular expression and any optional parameters. */
+      const uschar * list = arg;
+      uschar *ss = string_nextinlist(&list, &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)))
         if (strcmpic(ss, US"defer_ok") == 0 && rc == DEFER)
         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;
       }
     break;
-    #endif
+#endif
 
 
-    #ifdef EXPERIMENTAL_SPF
+#ifdef SUPPORT_SPF
     case ACLC_SPF:
       rc = spf_process(&arg, sender_address, SPF_PROCESS_NORMAL);
     break;
     case ACLC_SPF_GUESS:
       rc = spf_process(&arg, sender_address, SPF_PROCESS_GUESS);
     break;
     case ACLC_SPF:
       rc = spf_process(&arg, sender_address, SPF_PROCESS_NORMAL);
     break;
     case ACLC_SPF_GUESS:
       rc = spf_process(&arg, sender_address, SPF_PROCESS_GUESS);
     break;
-    #endif
+#endif
 
     case ACLC_UDPSEND:
     rc = acl_udpsend(arg, log_msgptr);
 
     case ACLC_UDPSEND:
     rc = acl_udpsend(arg, log_msgptr);
@@ -3689,7 +3709,8 @@ for (; cb != NULL; cb = cb->next)
 
     case ACLC_VERIFY:
     rc = acl_verify(where, addr, arg, user_msgptr, log_msgptr, basic_errno);
 
     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;
 
     if (verb == ACL_WARN) *user_msgptr = NULL;
     break;
 
@@ -3701,11 +3722,9 @@ for (; cb != NULL; cb = cb->next)
 
   /* If a condition was negated, invert OK/FAIL. */
 
 
   /* 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;
     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 */
   }
 
   if (rc != OK) break;   /* Conditions loop */
   }
@@ -3729,7 +3748,7 @@ present. */
 
 if (*epp && rc == OK) user_message = NULL;
 
 
 if (*epp && rc == OK) user_message = NULL;
 
-if (((1<<rc) & msgcond[verb]) != 0)
+if ((BIT(rc) & msgcond[verb]) != 0)
   {
   uschar *expmessage;
   uschar *old_user_msgptr = *user_msgptr;
   {
   uschar *expmessage;
   uschar *old_user_msgptr = *user_msgptr;
@@ -3745,26 +3764,26 @@ if (((1<<rc) & msgcond[verb]) != 0)
       (rc == OK && (verb == ACL_ACCEPT || verb == ACL_DISCARD)))
     *log_msgptr = *user_msgptr = NULL;
 
       (rc == OK && (verb == ACL_ACCEPT || verb == ACL_DISCARD)))
     *log_msgptr = *user_msgptr = NULL;
 
-  if (user_message != NULL)
+  if (user_message)
     {
     acl_verify_message = old_user_msgptr;
     expmessage = expand_string(user_message);
     {
     acl_verify_message = old_user_msgptr;
     expmessage = expand_string(user_message);
-    if (expmessage == NULL)
+    if (!expmessage)
       {
       {
-      if (!expand_string_forcedfail)
+      if (!f.expand_string_forcedfail)
         log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand ACL message \"%s\": %s",
           user_message, expand_string_message);
       }
     else if (expmessage[0] != 0) *user_msgptr = expmessage;
     }
 
         log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand ACL message \"%s\": %s",
           user_message, expand_string_message);
       }
     else if (expmessage[0] != 0) *user_msgptr = expmessage;
     }
 
-  if (log_message != NULL)
+  if (log_message)
     {
     acl_verify_message = old_log_msgptr;
     expmessage = expand_string(log_message);
     {
     acl_verify_message = old_log_msgptr;
     expmessage = expand_string(log_message);
-    if (expmessage == NULL)
+    if (!expmessage)
       {
       {
-      if (!expand_string_forcedfail)
+      if (!f.expand_string_forcedfail)
         log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand ACL message \"%s\": %s",
           log_message, expand_string_message);
       }
         log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand ACL message \"%s\": %s",
           log_message, expand_string_message);
       }
@@ -3777,7 +3796,7 @@ if (((1<<rc) & msgcond[verb]) != 0)
 
   /* If no log message, default it to the user message */
 
 
   /* If no log message, default it to the user message */
 
-  if (*log_msgptr == NULL) *log_msgptr = *user_msgptr;
+  if (!*log_msgptr) *log_msgptr = *user_msgptr;
   }
 
 acl_verify_message = NULL;
   }
 
 acl_verify_message = NULL;
@@ -3911,7 +3930,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
   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)
 
   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 +3942,7 @@ Returns:       OK         access is granted
 */
 
 static int
 */
 
 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;
   uschar **user_msgptr, uschar **log_msgptr)
 {
 int fd = -1;
@@ -3934,27 +3952,26 @@ uschar *ss;
 
 /* Catch configuration loops */
 
 
 /* Catch configuration loops */
 
-if (level > 20)
+if (acl_level > 20)
   {
   *log_msgptr = US"ACL nested too deep: possible loop";
   return ERROR;
   }
 
   {
   *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. */
 
   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;
+    if (f.expand_string_forcedfail) return OK;
     *log_msgptr = string_sprintf("failed to expand ACL string \"%s\": %s", s,
       expand_string_message);
     return ERROR;
     *log_msgptr = string_sprintf("failed to expand ACL string \"%s\": %s", s,
       expand_string_message);
     return ERROR;
@@ -3962,7 +3979,7 @@ if (level == 0)
   }
 else ss = s;
 
   }
 else ss = s;
 
-while (isspace(*ss))ss++;
+while (isspace(*ss)) ss++;
 
 /* If we can't find a named ACL, the default is to parse it as an inline one.
 (Unless it begins with a slash; non-existent files give rise to an error.) */
 
 /* If we can't find a named ACL, the default is to parse it as an inline one.
 (Unless it begins with a slash; non-existent files give rise to an error.) */
@@ -3983,11 +4000,11 @@ if (Ustrchr(ss, ' ') == NULL)
     acl = (acl_block *)(t->data.ptr);
     if (acl == 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);
       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 == '/')
     }
 
   else if (*ss == '/')
@@ -4021,7 +4038,7 @@ if (Ustrchr(ss, ' ') == NULL)
     (void)close(fd);
 
     acl_name = string_sprintf("ACL \"%s\"", ss);
     (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 +4069,19 @@ while (acl != NULL)
   int cond;
   int basic_errno = 0;
   BOOL endpass_seen = FALSE;
   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;
 
   *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;
-    }
+  f.acl_temp_details = FALSE;
 
 
-  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;
 
   /* 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;
     &endpass_seen, user_msgptr, log_msgptr, &basic_errno);
 
   /* Handle special returns: DEFER causes a return except on a WARN verb;
@@ -4080,44 +4090,42 @@ while (acl != NULL)
   switch (cond)
     {
     case DEFER:
   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)
         *log_msgptr = search_error_message;
     if (basic_errno != ERRNO_CALLOUTDEFER)
       {
       if (search_error_message != NULL && *search_error_message != 0)
         *log_msgptr = search_error_message;
-      if (smtp_return_error_details) acl_temp_details = TRUE;
+      if (smtp_return_error_details) f.acl_temp_details = TRUE;
       }
     else
       }
     else
-      {
-      acl_temp_details = TRUE;
-      }
+      f.acl_temp_details = TRUE;
     if (acl->verb != ACL_WARN) return DEFER;
     break;
 
     default:      /* Paranoia */
     case ERROR:
     if (acl->verb != ACL_WARN) return DEFER;
     break;
 
     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:
     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:
       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:
     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:
       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;
     }
       verbs[acl->verb], acl_name);
     break;
     }
@@ -4129,10 +4137,14 @@ while (acl != NULL)
   switch(acl->verb)
     {
     case ACL_ACCEPT:
   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)
       {
     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;
       return cond;
       }
     break;
@@ -4140,36 +4152,58 @@ while (acl != NULL)
     case ACL_DEFER:
     if (cond == OK)
       {
     case ACL_DEFER:
     if (cond == OK)
       {
-      acl_temp_details = TRUE;
+      HDEBUG(D_acl) debug_printf_indent("end of %s: DEFER\n", acl_name);
+      if (acl_quit_check) goto badquit;
+      f.acl_temp_details = TRUE;
       return DEFER;
       }
     break;
 
     case ACL_DENY:
       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:
     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)
       {
     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:
       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:
     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);
     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": ",
       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 +4224,13 @@ while (acl != NULL)
 
 /* We have reached the end of the ACL. This is an implicit DENY. */
 
 
 /* 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;
 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 +4240,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
 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;
   uschar **user_msgptr, uschar **log_msgptr)
 {
 uschar * tmp;
@@ -4239,17 +4278,19 @@ while (i < 9)
   acl_arg[i++] = NULL;
   }
 
   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];
 return ret;
 
 bad:
 
 acl_narg = sav_narg;
 for (i = 0; i < 9; i++) acl_arg[i] = sav_arg[i];
 return ret;
 
 bad:
-if (expand_string_forcedfail) return ERROR;
+if (f.expand_string_forcedfail) return ERROR;
 *log_msgptr = string_sprintf("failed to expand ACL string \"%s\": %s",
   tmp, expand_string_message);
 *log_msgptr = string_sprintf("failed to expand ACL string \"%s\": %s",
   tmp, expand_string_message);
-return search_find_defer?DEFER:ERROR;
+return f.search_find_defer ? DEFER : ERROR;
 }
 
 
 }
 
 
@@ -4264,6 +4305,7 @@ acl_eval(int where, uschar *s, uschar **user_msgptr, uschar **log_msgptr)
 {
 address_item adb;
 address_item *addr = NULL;
 {
 address_item adb;
 address_item *addr = NULL;
+int rc;
 
 *user_msgptr = *log_msgptr = NULL;
 sender_verified_failed = NULL;
 
 *user_msgptr = *log_msgptr = NULL;
 sender_verified_failed = NULL;
@@ -4281,7 +4323,10 @@ if (where == ACL_WHERE_RCPT)
   addr->lc_local_part = deliver_localpart;
   }
 
   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 +4365,9 @@ ratelimiters_cmd = NULL;
 log_reject_target = LOG_MAIN|LOG_REJECT;
 
 #ifndef DISABLE_PRDR
 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
 #else
-if (where == ACL_WHERE_RCPT )
+if (where==ACL_WHERE_RCPT || where==ACL_WHERE_VRFY)
 #endif
   {
   adb = address_defaults;
 #endif
   {
   adb = address_defaults;
@@ -4333,12 +4378,21 @@ if (where == ACL_WHERE_RCPT )
     *log_msgptr = US"defer in percent_hack_domains check";
     return DEFER;
     }
     *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;
   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,
 acl_where = ACL_WHERE_UNKNOWN;
 
 /* Cutthrough - if requested,
@@ -4346,8 +4400,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.
 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.
 
 Initial implementation:  dual-write to spool.
 Assume the rxd datastream is now being copied byte-for-byte to an open cutthrough connection.
 
@@ -4361,35 +4415,68 @@ If temp-reject, close the conn (and keep the spooled copy).
 If conn-failure, no action (and keep the spooled copy).
 */
 switch (where)
 If conn-failure, no action (and keep the spooled copy).
 */
 switch (where)
-{
-case ACL_WHERE_RCPT:
+  {
+  case ACL_WHERE_RCPT:
 #ifndef DISABLE_PRDR
 #ifndef DISABLE_PRDR
-case ACL_WHERE_PRDR:
+  case ACL_WHERE_PRDR:
 #endif
 #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;
 
 
-case ACL_WHERE_PREDATA:
-  if( rc == OK )
-    cutthrough_predata();
-  else
-    cancel_cutthrough_connection("predata acl not ok");
-  break;
+    if (f.host_checking_callout)       /* -bhc mode */
+      cancel_cutthrough_connection(TRUE, US"host-checking mode");
 
 
-case ACL_WHERE_QUIT:
-case ACL_WHERE_NOTQUIT:
-  cancel_cutthrough_connection("quit or notquit");
-  break;
+    else if (  rc == OK
+           && cutthrough.delivery
+           && rcpt_count > cutthrough.nrcpt
+           )
+      {
+      if ((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;
+         f.acl_temp_details = TRUE;
+         }
+       else
+         {
+         HDEBUG(D_acl) debug_printf_indent("cutthrough defer; will spool\n");
+         rc = OK;
+         }
+      }
+    else HDEBUG(D_acl) if (cutthrough.delivery)
+      if (rcpt_count <= cutthrough.nrcpt)
+       debug_printf_indent("ignore cutthrough request; nonfirst message\n");
+      else if (rc != OK)
+       debug_printf_indent("ignore cutthrough request; ACL did not accept\n");
+    break;
 
 
-default:
-  break;
-}
+  case ACL_WHERE_PREDATA:
+    if (rc == OK)
+      cutthrough_predata();
+    else
+      cancel_cutthrough_connection(TRUE, US"predata acl not ok");
+    break;
+
+  case ACL_WHERE_QUIT:
+  case ACL_WHERE_NOTQUIT:
+    /* Drop cutthrough conns, and drop heldopen verify conns if
+    the previous was not DATA */
+    {
+    uschar prev = smtp_connection_had[smtp_ch_index-2];
+    BOOL dropverify = !(prev == SCH_DATA || prev == SCH_BDAT);
+
+    cancel_cutthrough_connection(dropverify, US"quit or conndrop");
+    break;
+    }
+
+  default:
+    break;
+  }
 
 deliver_domain = deliver_localpart = deliver_address_data =
 
 deliver_domain = deliver_localpart = deliver_address_data =
-  sender_address_data = NULL;
+  deliver_domain_data = sender_address_data = NULL;
 
 /* A DISCARD response is permitted only for message ACLs, excluding the PREDATA
 ACL, which is really in the middle of an SMTP command. */
 
 /* A DISCARD response is permitted only for message ACLs, excluding the PREDATA
 ACL, which is really in the middle of an SMTP command. */
@@ -4439,12 +4526,10 @@ Returns   the pointer to variable's tree node
 */
 
 tree_node *
 */
 
 tree_node *
-acl_var_create(uschar *name)
+acl_var_create(uschar * name)
 {
 {
-tree_node *node, **root;
-root = (name[0] == 'c')? &acl_var_c : &acl_var_m;
-node = tree_search(*root, name);
-if (node == NULL)
+tree_node * node, ** root = name[0] == 'c' ? &acl_var_c : &acl_var_m;
+if (!(node = tree_search(*root, name)))
   {
   node = store_get(sizeof(tree_node) + Ustrlen(name));
   Ustrcpy(node->name, name);
   {
   node = store_get(sizeof(tree_node) + Ustrlen(name));
   Ustrcpy(node->name, name);
index ca48b3a..725d172 100644 (file)
@@ -35,6 +35,6 @@
 #
 # abuse:       the person dealing with network and mail abuse
 # hostmaster:  the person dealing with DNS problems
 #
 # abuse:       the person dealing with network and mail abuse
 # hostmaster:  the person dealing with DNS problems
-# webmaster:   the person dealing with your web site
+# webmaster:   the person dealing with your website
 
 ####
 
 ####
diff --git a/src/arc.c b/src/arc.c
new file mode 100644 (file)
index 0000000..6c4bcc6
--- /dev/null
+++ b/src/arc.c
@@ -0,0 +1,1851 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+/* Experimental ARC support for Exim
+   Copyright (c) Jeremy Harris 2018
+   License: GPL
+*/
+
+#include "exim.h"
+#ifdef EXPERIMENTAL_ARC
+# if !defined SUPPORT_SPF
+#  error SPF must also be enabled for ARC
+# elif defined DISABLE_DKIM
+#  error DKIM must also be enabled for ARC
+# else
+
+#  include "functions.h"
+#  include "pdkim/pdkim.h"
+#  include "pdkim/signing.h"
+
+extern pdkim_ctx * dkim_verify_ctx;
+extern pdkim_ctx dkim_sign_ctx;
+
+#define ARC_SIGN_OPT_TSTAMP    BIT(0)
+#define ARC_SIGN_OPT_EXPIRE    BIT(1)
+
+#define ARC_SIGN_DEFAULT_EXPIRE_DELTA (60 * 60 * 24 * 30)      /* one month */
+
+/******************************************************************************/
+
+typedef struct hdr_rlist {
+  struct hdr_rlist *   prev;
+  BOOL                 used;
+  header_line *                h;
+} hdr_rlist;
+
+typedef struct arc_line {
+  header_line *        complete;       /* including the header name; nul-term */
+  uschar *     relaxed;
+
+  /* identified tag contents */
+  /*XXX t= for AS? */
+  blob         i;
+  blob         cv;
+  blob         a;
+  blob         b;
+  blob         bh;
+  blob         d;
+  blob         h;
+  blob         s;
+  blob         c;
+  blob         l;
+
+  /* tag content sub-portions */
+  blob         a_algo;
+  blob         a_hash;
+
+  blob         c_head;
+  blob         c_body;
+
+  /* modified copy of b= field in line */
+  blob         rawsig_no_b_val;
+} arc_line;
+
+typedef struct arc_set {
+  struct arc_set *     next;
+  struct arc_set *     prev;
+
+  unsigned             instance;
+  arc_line *           hdr_aar;
+  arc_line *           hdr_ams;
+  arc_line *           hdr_as;
+
+  const uschar *       ams_verify_done;
+  BOOL                 ams_verify_passed;
+} arc_set;
+
+typedef struct arc_ctx {
+  arc_set *    arcset_chain;
+  arc_set *    arcset_chain_last;
+} arc_ctx;
+
+#define ARC_HDR_AAR    US"ARC-Authentication-Results:"
+#define ARC_HDRLEN_AAR 27
+#define ARC_HDR_AMS    US"ARC-Message-Signature:"
+#define ARC_HDRLEN_AMS 22
+#define ARC_HDR_AS     US"ARC-Seal:"
+#define ARC_HDRLEN_AS  9
+#define HDR_AR         US"Authentication-Results:"
+#define HDRLEN_AR      23
+
+static time_t now;
+static time_t expire;
+static hdr_rlist * headers_rlist;
+static arc_ctx arc_sign_ctx = { NULL };
+static arc_ctx arc_verify_ctx = { NULL };
+
+
+/******************************************************************************/
+
+
+/* Get the instance number from the header.
+Return 0 on error */
+static unsigned
+arc_instance_from_hdr(const arc_line * al)
+{
+const uschar * s = al->i.data;
+if (!s || !al->i.len) return 0;
+return (unsigned) atoi(CCS s);
+}
+
+
+static uschar *
+skip_fws(uschar * s)
+{
+uschar c = *s;
+while (c && (c == ' ' || c == '\t' || c == '\n' || c == '\r')) c = *++s;
+return s;
+}
+
+
+/* Locate instance struct on chain, inserting a new one if
+needed.  The chain is in increasing-instance-number order
+by the "next" link, and we have a "prev" link also.
+*/
+
+static arc_set *
+arc_find_set(arc_ctx * ctx, unsigned i)
+{
+arc_set ** pas, * as, * next, * prev;
+
+for (pas = &ctx->arcset_chain, prev = NULL, next = ctx->arcset_chain;
+     as = *pas; pas = &as->next)
+  {
+  if (as->instance > i) break;
+  if (as->instance == i)
+    {
+    DEBUG(D_acl) debug_printf("ARC: existing instance %u\n", i);
+    return as;
+    }
+  next = as->next;
+  prev = as;
+  }
+
+DEBUG(D_acl) debug_printf("ARC: new instance %u\n", i);
+*pas = as = store_get(sizeof(arc_set));
+memset(as, 0, sizeof(arc_set));
+as->next = next;
+as->prev = prev;
+as->instance = i;
+if (next)
+  next->prev = as;
+else
+  ctx->arcset_chain_last = as;
+return as;
+}
+
+
+
+/* Insert a tag content into the line structure.
+Note this is a reference to existing data, not a copy.
+Check for already-seen tag.
+The string-pointer is on the '=' for entry.  Update it past the
+content (to the ;) on return;
+*/
+
+static uschar *
+arc_insert_tagvalue(arc_line * al, unsigned loff, uschar ** ss)
+{
+uschar * s = *ss;
+uschar c = *++s;
+blob * b = (blob *)(US al + loff);
+size_t len = 0;
+
+/* [FWS] tag-value [FWS] */
+
+if (b->data) return US"fail";
+s = skip_fws(s);                                               /* FWS */
+
+b->data = s;
+while ((c = *s) && c != ';') { len++; s++; }
+*ss = s;
+while (len && ((c = s[-1]) == ' ' || c == '\t' || c == '\n' || c == '\r'))
+  { s--; len--; }                                              /* FWS */
+b->len = len;
+return NULL;
+}
+
+
+/* Inspect a header line, noting known tag fields.
+Check for duplicates. */
+
+static uschar *
+arc_parse_line(arc_line * al, header_line * h, unsigned off, BOOL instance_only)
+{
+uschar * s = h->text + off;
+uschar * r = NULL;     /* compiler-quietening */
+uschar c;
+
+al->complete = h;
+
+if (!instance_only)
+  {
+  al->rawsig_no_b_val.data = store_get(h->slen + 1);
+  memcpy(al->rawsig_no_b_val.data, h->text, off);      /* copy the header name blind */
+  r = al->rawsig_no_b_val.data + off;
+  al->rawsig_no_b_val.len = off;
+  }
+
+/* tag-list  =  tag-spec *( ";" tag-spec ) [ ";" ] */
+
+while ((c = *s))
+  {
+  char tagchar;
+  uschar * t;
+  unsigned i = 0;
+  uschar * fieldstart = s;
+  uschar * bstart = NULL, * bend;
+
+  /* tag-spec  =  [FWS] tag-name [FWS] "=" [FWS] tag-value [FWS] */
+
+  s = skip_fws(s);                                             /* FWS */
+  if (!*s) break;
+/* debug_printf("%s: consider '%s'\n", __FUNCTION__, s); */
+  tagchar = *s++;
+  s = skip_fws(s);                                             /* FWS */
+  if (!*s) break;
+
+  if (!instance_only || tagchar == 'i') switch (tagchar)
+    {
+    case 'a':                          /* a= AMS algorithm */
+      {
+      if (*s != '=') return US"no 'a' value";
+      if (arc_insert_tagvalue(al, offsetof(arc_line, a), &s)) return US"a tag dup";
+
+      /* substructure: algo-hash   (eg. rsa-sha256) */
+
+      t = al->a_algo.data = al->a.data;
+      while (*t != '-')
+       if (!*t++ || ++i > al->a.len) return US"no '-' in 'a' value";
+      al->a_algo.len = i;
+      if (*t++ != '-') return US"no '-' in 'a' value";
+      al->a_hash.data = t;
+      al->a_hash.len = al->a.len - i - 1;
+      }
+      break;
+    case 'b':
+      {
+      gstring * g = NULL;
+
+      switch (*s)
+       {
+       case '=':                       /* b= AMS signature */
+         if (al->b.data) return US"already b data";
+         bstart = s+1;
+
+         /* The signature can have FWS inserted in the content;
+         make a stripped copy */
+
+         while ((c = *++s) && c != ';')
+           if (c != ' ' && c != '\t' && c != '\n' && c != '\r')
+             g = string_catn(g, s, 1);
+         al->b.data = string_from_gstring(g);
+         al->b.len = g->ptr;
+         gstring_reset_unused(g);
+         bend = s;
+         break;
+       case 'h':                       /* bh= AMS body hash */
+         s = skip_fws(++s);                                    /* FWS */
+         if (*s != '=') return US"no bh value";
+         if (al->bh.data) return US"already bh data";
+
+         /* The bodyhash can have FWS inserted in the content;
+         make a stripped copy */
+
+         while ((c = *++s) && c != ';')
+           if (c != ' ' && c != '\t' && c != '\n' && c != '\r')
+             g = string_catn(g, s, 1);
+         al->bh.data = string_from_gstring(g);
+         al->bh.len = g->ptr;
+         gstring_reset_unused(g);
+         break;
+       default:
+         return US"b? tag";
+       }
+      }
+      break;
+    case 'c':
+      switch (*s)
+       {
+       case '=':                       /* c= AMS canonicalisation */
+         if (arc_insert_tagvalue(al, offsetof(arc_line, c), &s)) return US"c tag dup";
+
+         /* substructure: head/body   (eg. relaxed/simple)) */
+
+         t = al->c_head.data = al->c.data;
+         while (isalpha(*t))
+           if (!*t++ || ++i > al->a.len) break;
+         al->c_head.len = i;
+         if (*t++ == '/')              /* /body is optional */
+           {
+           al->c_body.data = t;
+           al->c_body.len = al->c.len - i - 1;
+           }
+         else
+           {
+           al->c_body.data = US"simple";
+           al->c_body.len = 6;
+           }
+         break;
+       case 'v':                       /* cv= AS validity */
+         if (*++s != '=') return US"cv tag val";
+         if (arc_insert_tagvalue(al, offsetof(arc_line, cv), &s)) return US"cv tag dup";
+         break;
+       default:
+         return US"c? tag";
+       }
+      break;
+    case 'd':                          /* d= AMS domain */
+      if (*s != '=') return US"d tag val";
+      if (arc_insert_tagvalue(al, offsetof(arc_line, d), &s)) return US"d tag dup";
+      break;
+    case 'h':                          /* h= AMS headers */
+      if (*s != '=') return US"h tag val";
+      if (arc_insert_tagvalue(al, offsetof(arc_line, h), &s)) return US"h tag dup";
+      break;
+    case 'i':                          /* i= ARC set instance */
+      if (*s != '=') return US"i tag val";
+      if (arc_insert_tagvalue(al, offsetof(arc_line, i), &s)) return US"i tag dup";
+      if (instance_only) goto done;
+      break;
+    case 'l':                          /* l= bodylength */
+      if (*s != '=') return US"l tag val";
+      if (arc_insert_tagvalue(al, offsetof(arc_line, l), &s)) return US"l tag dup";
+      break;
+    case 's':                          /* s= AMS selector */
+      if (*s != '=') return US"s tag val";
+      if (arc_insert_tagvalue(al, offsetof(arc_line, s), &s)) return US"s tag dup";
+      break;
+    }
+
+  while ((c = *s) && c != ';') s++;
+  if (c) s++;                          /* ; after tag-spec */
+
+  /* for all but the b= tag, copy the field including FWS.  For the b=,
+  drop the tag content. */
+
+  if (!instance_only)
+    if (bstart)
+      {
+      size_t n = bstart - fieldstart;
+      memcpy(r, fieldstart, n);                /* FWS "b=" */
+      r += n;
+      al->rawsig_no_b_val.len += n;
+      n = s - bend;
+      memcpy(r, bend, n);              /* FWS ";" */
+      r += n;
+      al->rawsig_no_b_val.len += n;
+      }
+    else
+      {
+      size_t n = s - fieldstart;
+      memcpy(r, fieldstart, n);
+      r += n;
+      al->rawsig_no_b_val.len += n;
+      }
+  }
+
+if (!instance_only)
+  *r = '\0';
+
+done:
+/* debug_printf("%s: finshed\n", __FUNCTION__); */
+return NULL;
+}
+
+
+/* Insert one header line in the correct set of the chain,
+adding instances as needed and checking for duplicate lines.
+*/
+
+static uschar *
+arc_insert_hdr(arc_ctx * ctx, header_line * h, unsigned off, unsigned hoff,
+  BOOL instance_only)
+{
+unsigned i;
+arc_set * as;
+arc_line * al = store_get(sizeof(arc_line)), ** alp;
+uschar * e;
+
+memset(al, 0, sizeof(arc_line));
+
+if ((e = arc_parse_line(al, h, off, instance_only)))
+  {
+  DEBUG(D_acl) if (e) debug_printf("ARC: %s\n", e);
+  return US"line parse";
+  }
+if (!(i = arc_instance_from_hdr(al)))  return US"instance find";
+if (i > 50)                            return US"overlarge instance number";
+if (!(as = arc_find_set(ctx, i)))      return US"set find";
+if (*(alp = (arc_line **)(US as + hoff))) return US"dup hdr";
+
+*alp = al;
+return NULL;
+}
+
+
+
+
+static const uschar *
+arc_try_header(arc_ctx * ctx, header_line * h, BOOL instance_only)
+{
+const uschar * e;
+
+/*debug_printf("consider hdr '%s'\n", h->text);*/
+if (strncmpic(ARC_HDR_AAR, h->text, ARC_HDRLEN_AAR) == 0)
+  {
+  DEBUG(D_acl)
+    {
+    int len = h->slen;
+    uschar * s;
+    for (s = h->text + h->slen; s[-1] == '\r' || s[-1] == '\n'; )
+      s--, len--;
+    debug_printf("ARC: found AAR: %.*s\n", len, h->text);
+    }
+  if ((e = arc_insert_hdr(ctx, h, ARC_HDRLEN_AAR, offsetof(arc_set, hdr_aar),
+                         TRUE)))
+    {
+    DEBUG(D_acl) debug_printf("inserting AAR: %s\n", e);
+    return US"inserting AAR";
+    }
+  }
+else if (strncmpic(ARC_HDR_AMS, h->text, ARC_HDRLEN_AMS) == 0)
+  {
+  arc_line * ams;
+
+  DEBUG(D_acl)
+    {
+    int len = h->slen;
+    uschar * s;
+    for (s = h->text + h->slen; s[-1] == '\r' || s[-1] == '\n'; )
+      s--, len--;
+    debug_printf("ARC: found AMS: %.*s\n", len, h->text);
+    }
+  if ((e = arc_insert_hdr(ctx, h, ARC_HDRLEN_AMS, offsetof(arc_set, hdr_ams),
+                         instance_only)))
+    {
+    DEBUG(D_acl) debug_printf("inserting AMS: %s\n", e);
+    return US"inserting AMS";
+    }
+
+  /* defaults */
+  /*XXX dubious selection of ams here */
+  ams = ctx->arcset_chain->hdr_ams;
+  if (!ams->c.data)
+    {
+    ams->c_head.data = US"simple"; ams->c_head.len = 6;
+    ams->c_body = ams->c_head;
+    }
+  }
+else if (strncmpic(ARC_HDR_AS, h->text, ARC_HDRLEN_AS) == 0)
+  {
+  DEBUG(D_acl)
+    {
+    int len = h->slen;
+    uschar * s;
+    for (s = h->text + h->slen; s[-1] == '\r' || s[-1] == '\n'; )
+      s--, len--;
+    debug_printf("ARC: found AS: %.*s\n", len, h->text);
+    }
+  if ((e = arc_insert_hdr(ctx, h, ARC_HDRLEN_AS, offsetof(arc_set, hdr_as),
+                         instance_only)))
+    {
+    DEBUG(D_acl) debug_printf("inserting AS: %s\n", e);
+    return US"inserting AS";
+    }
+  }
+return NULL;
+}
+
+
+
+/* Gather the chain of arc sets from the headers.
+Check for duplicates while that is done.  Also build the
+reverse-order headers list;
+
+Return: ARC state if determined, eg. by lack of any ARC chain.
+*/
+
+static const uschar *
+arc_vfy_collect_hdrs(arc_ctx * ctx)
+{
+header_line * h;
+hdr_rlist * r = NULL, * rprev = NULL;
+const uschar * e;
+
+DEBUG(D_acl) debug_printf("ARC: collecting arc sets\n");
+for (h = header_list; h; h = h->next)
+  {
+  r = store_get(sizeof(hdr_rlist));
+  r->prev = rprev;
+  r->used = FALSE;
+  r->h = h;
+  rprev = r;
+
+  if ((e = arc_try_header(ctx, h, FALSE)))
+    {
+    arc_state_reason = string_sprintf("collecting headers: %s", e);
+    return US"fail";
+    }
+  }
+headers_rlist = r;
+
+if (!ctx->arcset_chain) return US"none";
+return NULL;
+}
+
+
+static BOOL
+arc_cv_match(arc_line * al, const uschar * s)
+{
+return Ustrncmp(s, al->cv.data, al->cv.len) == 0;
+}
+
+/******************************************************************************/
+
+/* Return the hash of headers from the message that the AMS claims it
+signed.
+*/
+
+static void
+arc_get_verify_hhash(arc_ctx * ctx, arc_line * ams, blob * hhash)
+{
+const uschar * headernames = string_copyn(ams->h.data, ams->h.len);
+const uschar * hn;
+int sep = ':';
+hdr_rlist * r;
+BOOL relaxed = Ustrncmp(US"relaxed", ams->c_head.data, ams->c_head.len) == 0;
+int hashtype = pdkim_hashname_to_hashtype(
+                   ams->a_hash.data, ams->a_hash.len);
+hctx hhash_ctx;
+const uschar * s;
+int len;
+
+if (!exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod))
+  {
+  DEBUG(D_acl)
+      debug_printf("ARC: hash setup error, possibly nonhandled hashtype\n");
+  return;
+  }
+
+/* For each headername in the list from the AMS (walking in order)
+walk the message headers in reverse order, adding to the hash any
+found for the first time. For that last point, maintain used-marks
+on the list of message headers. */
+
+DEBUG(D_acl) debug_printf("ARC: AMS header data for verification:\n");
+
+for (r = headers_rlist; r; r = r->prev)
+  r->used = FALSE;
+while ((hn = string_nextinlist(&headernames, &sep, NULL, 0)))
+  for (r = headers_rlist; r; r = r->prev)
+    if (  !r->used
+       && strncasecmp(CCS (s = r->h->text), CCS hn, Ustrlen(hn)) == 0
+       )
+      {
+      if (relaxed) s = pdkim_relax_header_n(s, r->h->slen, TRUE);
+
+      len = Ustrlen(s);
+      DEBUG(D_acl) pdkim_quoteprint(s, len);
+      exim_sha_update(&hhash_ctx, s, Ustrlen(s));
+      r->used = TRUE;
+      break;
+      }
+
+/* Finally add in the signature header (with the b= tag stripped); no CRLF */
+
+s = ams->rawsig_no_b_val.data, len = ams->rawsig_no_b_val.len;
+if (relaxed)
+  len = Ustrlen(s = pdkim_relax_header_n(s, len, FALSE));
+DEBUG(D_acl) pdkim_quoteprint(s, len);
+exim_sha_update(&hhash_ctx, s, len);
+
+exim_sha_finish(&hhash_ctx, hhash);
+DEBUG(D_acl)
+  { debug_printf("ARC: header hash: "); pdkim_hexprint(hhash->data, hhash->len); }
+return;
+}
+
+
+
+
+static pdkim_pubkey *
+arc_line_to_pubkey(arc_line * al)
+{
+uschar * dns_txt;
+pdkim_pubkey * p;
+
+if (!(dns_txt = dkim_exim_query_dns_txt(string_sprintf("%.*s._domainkey.%.*s",
+         al->s.len, al->s.data, al->d.len, al->d.data))))
+  {
+  DEBUG(D_acl) debug_printf("pubkey dns lookup fail\n");
+  return NULL;
+  }
+
+if (  !(p = pdkim_parse_pubkey_record(dns_txt))
+   || (Ustrcmp(p->srvtype, "*") != 0 && Ustrcmp(p->srvtype, "email") != 0)
+   )
+  {
+  DEBUG(D_acl) debug_printf("pubkey dns lookup format error\n");
+  return NULL;
+  }
+
+/* If the pubkey limits use to specified hashes, reject unusable
+signatures. XXX should we have looked for multiple dns records? */
+
+if (p->hashes)
+  {
+  const uschar * list = p->hashes, * ele;
+  int sep = ':';
+
+  while ((ele = string_nextinlist(&list, &sep, NULL, 0)))
+    if (Ustrncmp(ele, al->a_hash.data, al->a_hash.len) == 0) break;
+  if (!ele)
+    {
+    DEBUG(D_acl) debug_printf("pubkey h=%s vs sig a=%.*s\n",
+                             p->hashes, (int)al->a.len, al->a.data);
+    return NULL;
+    }
+  }
+return p;
+}
+
+
+
+
+static pdkim_bodyhash *
+arc_ams_setup_vfy_bodyhash(arc_line * ams)
+{
+int canon_head, canon_body;
+long bodylen;
+
+if (!ams->c.data) ams->c.data = US"simple";    /* RFC 6376 (DKIM) default */
+pdkim_cstring_to_canons(ams->c.data, ams->c.len, &canon_head, &canon_body);
+bodylen = ams->l.data
+       ? strtol(CS string_copyn(ams->l.data, ams->l.len), NULL, 10) : -1;
+
+return pdkim_set_bodyhash(dkim_verify_ctx,
+       pdkim_hashname_to_hashtype(ams->a_hash.data, ams->a_hash.len),
+       canon_body,
+       bodylen);
+}
+
+
+
+/* Verify an AMS. This is a DKIM-sig header, but with an ARC i= tag
+and without a DKIM v= tag.
+*/
+
+static const uschar *
+arc_ams_verify(arc_ctx * ctx, arc_set * as)
+{
+arc_line * ams = as->hdr_ams;
+pdkim_bodyhash * b;
+pdkim_pubkey * p;
+blob sighash;
+blob hhash;
+ev_ctx vctx;
+int hashtype;
+const uschar * errstr;
+
+as->ams_verify_done = US"in-progress";
+
+/* Check the AMS has all the required tags:
+   "a="  algorithm
+   "b="  signature
+   "bh=" body hash
+   "d="  domain (for key lookup)
+   "h="  headers (included in signature)
+   "s="  key-selector (for key lookup)
+*/
+if (  !ams->a.data || !ams->b.data || !ams->bh.data || !ams->d.data
+   || !ams->h.data || !ams->s.data)
+  {
+  as->ams_verify_done = arc_state_reason = US"required tag missing";
+  return US"fail";
+  }
+
+
+/* The bodyhash should have been created earlier, and the dkim code should
+have managed calculating it during message input.  Find the reference to it. */
+
+if (!(b = arc_ams_setup_vfy_bodyhash(ams)))
+  {
+  as->ams_verify_done = arc_state_reason = US"internal hash setup error";
+  return US"fail";
+  }
+
+DEBUG(D_acl)
+  {
+  debug_printf("ARC i=%d AMS   Body bytes hashed: %lu\n"
+              "              Body %.*s computed: ",
+              as->instance, b->signed_body_bytes,
+              (int)ams->a_hash.len, ams->a_hash.data);
+  pdkim_hexprint(CUS b->bh.data, b->bh.len);
+  }
+
+/* We know the bh-tag blob is of a nul-term string, so safe as a string */
+
+if (  !ams->bh.data
+   || (pdkim_decode_base64(ams->bh.data, &sighash), sighash.len != b->bh.len)
+   || memcmp(sighash.data, b->bh.data, b->bh.len) != 0
+   )
+  {
+  DEBUG(D_acl)
+    {
+    debug_printf("ARC i=%d AMS Body hash from headers: ", as->instance);
+    pdkim_hexprint(sighash.data, sighash.len);
+    debug_printf("ARC i=%d AMS Body hash did NOT match\n", as->instance);
+    }
+  return as->ams_verify_done = arc_state_reason = US"AMS body hash miscompare";
+  }
+
+DEBUG(D_acl) debug_printf("ARC i=%d AMS Body hash compared OK\n", as->instance);
+
+/* Get the public key from DNS */
+
+if (!(p = arc_line_to_pubkey(ams)))
+  return as->ams_verify_done = arc_state_reason = US"pubkey problem";
+
+/* We know the b-tag blob is of a nul-term string, so safe as a string */
+pdkim_decode_base64(ams->b.data, &sighash);
+
+arc_get_verify_hhash(ctx, ams, &hhash);
+
+/* Setup the interface to the signing library */
+
+if ((errstr = exim_dkim_verify_init(&p->key, KEYFMT_DER, &vctx)))
+  {
+  DEBUG(D_acl) debug_printf("ARC verify init: %s\n", errstr);
+  as->ams_verify_done = arc_state_reason = US"internal sigverify init error";
+  return US"fail";
+  }
+
+hashtype = pdkim_hashname_to_hashtype(ams->a_hash.data, ams->a_hash.len);
+
+if ((errstr = exim_dkim_verify(&vctx,
+         pdkim_hashes[hashtype].exim_hashmethod, &hhash, &sighash)))
+  {
+  DEBUG(D_acl) debug_printf("ARC i=%d AMS verify %s\n", as->instance, errstr);
+  return as->ams_verify_done = arc_state_reason = US"AMS sig nonverify";
+  }
+
+DEBUG(D_acl) debug_printf("ARC i=%d AMS verify pass\n", as->instance);
+as->ams_verify_passed = TRUE;
+return NULL;
+}
+
+
+
+/* Check the sets are instance-continuous and that all
+members are present.  Check that no arc_seals are "fail".
+Set the highest instance number global.
+Verify the latest AMS.
+*/
+static uschar *
+arc_headers_check(arc_ctx * ctx)
+{
+arc_set * as;
+int inst;
+BOOL ams_fail_found = FALSE;
+
+if (!(as = ctx->arcset_chain_last))
+  return US"none";
+
+for(inst = as->instance; as; as = as->prev, inst--)
+  {
+  if (as->instance != inst)
+    arc_state_reason = string_sprintf("i=%d (sequence; expected %d)",
+      as->instance, inst);
+  else if (!as->hdr_aar || !as->hdr_ams || !as->hdr_as)
+    arc_state_reason = string_sprintf("i=%d (missing header)", as->instance);
+  else if (arc_cv_match(as->hdr_as, US"fail"))
+    arc_state_reason = string_sprintf("i=%d (cv)", as->instance);
+  else
+    goto good;
+
+  DEBUG(D_acl) debug_printf("ARC chain fail at %s\n", arc_state_reason);
+  return US"fail";
+
+  good:
+  /* Evaluate the oldest-pass AMS validation while we're here.
+  It does not affect the AS chain validation but is reported as
+  auxilary info. */
+
+  if (!ams_fail_found)
+    if (arc_ams_verify(ctx, as))
+      ams_fail_found = TRUE;
+    else
+      arc_oldest_pass = inst;
+  arc_state_reason = NULL;
+  }
+if (inst != 0)
+  {
+  arc_state_reason = string_sprintf("(sequence; expected i=%d)", inst);
+  DEBUG(D_acl) debug_printf("ARC chain fail %s\n", arc_state_reason);
+  return US"fail";
+  }
+
+arc_received = ctx->arcset_chain_last;
+arc_received_instance = arc_received->instance;
+
+/* We can skip the latest-AMS validation, if we already did it. */
+
+as = ctx->arcset_chain_last;
+if (!as->ams_verify_passed)
+  {
+  if (as->ams_verify_done)
+    {
+    arc_state_reason = as->ams_verify_done;
+    return US"fail";
+    }
+  if (!!arc_ams_verify(ctx, as))
+    return US"fail";
+  }
+return NULL;
+}
+
+
+/******************************************************************************/
+static const uschar *
+arc_seal_verify(arc_ctx * ctx, arc_set * as)
+{
+arc_line * hdr_as = as->hdr_as;
+arc_set * as2;
+int hashtype;
+hctx hhash_ctx;
+blob hhash_computed;
+blob sighash;
+ev_ctx vctx;
+pdkim_pubkey * p;
+const uschar * errstr;
+
+DEBUG(D_acl) debug_printf("ARC: AS vfy i=%d\n", as->instance);
+/*
+       1.  If the value of the "cv" tag on that seal is "fail", the
+           chain state is "fail" and the algorithm stops here.  (This
+           step SHOULD be skipped if the earlier step (2.1) was
+           performed) [it was]
+
+       2.  In Boolean nomenclature: if ((i == 1 && cv != "none") or (cv
+           == "none" && i != 1)) then the chain state is "fail" and the
+           algorithm stops here (note that the ordering of the logic is
+           structured for short-circuit evaluation).
+*/
+
+if (  as->instance == 1 && !arc_cv_match(hdr_as, US"none")
+   || arc_cv_match(hdr_as, US"none") && as->instance != 1
+   )
+  {
+  arc_state_reason = US"seal cv state";
+  return US"fail";
+  }
+
+/*
+       3.  Initialize a hash function corresponding to the "a" tag of
+           the ARC-Seal.
+*/
+
+hashtype = pdkim_hashname_to_hashtype(hdr_as->a_hash.data, hdr_as->a_hash.len);
+
+if (!exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod))
+  {
+  DEBUG(D_acl)
+      debug_printf("ARC: hash setup error, possibly nonhandled hashtype\n");
+  arc_state_reason = US"seal hash setup error";
+  return US"fail";
+  }
+
+/*
+       4.  Compute the canonicalized form of the ARC header fields, in
+           the order described in Section 5.4.2, using the "relaxed"
+           header canonicalization defined in Section 3.4.2 of
+           [RFC6376].  Pass the canonicalized result to the hash
+           function.
+
+Headers are CRLF-separated, but the last one is not crlf-terminated.
+*/
+
+DEBUG(D_acl) debug_printf("ARC: AS header data for verification:\n");
+for (as2 = ctx->arcset_chain;
+     as2 && as2->instance <= as->instance;
+     as2 = as2->next)
+  {
+  arc_line * al;
+  uschar * s;
+  int len;
+
+  al = as2->hdr_aar;
+  if (!(s = al->relaxed))
+    al->relaxed = s = pdkim_relax_header_n(al->complete->text,
+                                           al->complete->slen, TRUE);
+  len = Ustrlen(s);
+  DEBUG(D_acl) pdkim_quoteprint(s, len);
+  exim_sha_update(&hhash_ctx, s, len);
+
+  al = as2->hdr_ams;
+  if (!(s = al->relaxed))
+    al->relaxed = s = pdkim_relax_header_n(al->complete->text,
+                                           al->complete->slen, TRUE);
+  len = Ustrlen(s);
+  DEBUG(D_acl) pdkim_quoteprint(s, len);
+  exim_sha_update(&hhash_ctx, s, len);
+
+  al = as2->hdr_as;
+  if (as2->instance == as->instance)
+    s = pdkim_relax_header_n(al->rawsig_no_b_val.data,
+                                       al->rawsig_no_b_val.len, FALSE);
+  else if (!(s = al->relaxed))
+    al->relaxed = s = pdkim_relax_header_n(al->complete->text,
+                                           al->complete->slen, TRUE);
+  len = Ustrlen(s);
+  DEBUG(D_acl) pdkim_quoteprint(s, len);
+  exim_sha_update(&hhash_ctx, s, len);
+  }
+
+/*
+       5.  Retrieve the final digest from the hash function.
+*/
+
+exim_sha_finish(&hhash_ctx, &hhash_computed);
+DEBUG(D_acl)
+  {
+  debug_printf("ARC i=%d AS Header %.*s computed: ",
+    as->instance, (int)hdr_as->a_hash.len, hdr_as->a_hash.data);
+  pdkim_hexprint(hhash_computed.data, hhash_computed.len);
+  }
+
+
+/*
+       6.  Retrieve the public key identified by the "s" and "d" tags in
+           the ARC-Seal, as described in Section 4.1.6.
+*/
+
+if (!(p = arc_line_to_pubkey(hdr_as)))
+  return US"pubkey problem";
+
+/*
+       7.  Determine whether the signature portion ("b" tag) of the ARC-
+           Seal and the digest computed above are valid according to the
+           public key.  (See also Section Section 8.4 for failure case
+           handling)
+
+       8.  If the signature is not valid, the chain state is "fail" and
+           the algorithm stops here.
+*/
+
+/* We know the b-tag blob is of a nul-term string, so safe as a string */
+pdkim_decode_base64(hdr_as->b.data, &sighash);
+
+if ((errstr = exim_dkim_verify_init(&p->key, KEYFMT_DER, &vctx)))
+  {
+  DEBUG(D_acl) debug_printf("ARC verify init: %s\n", errstr);
+  return US"fail";
+  }
+
+hashtype = pdkim_hashname_to_hashtype(hdr_as->a_hash.data, hdr_as->a_hash.len);
+
+if ((errstr = exim_dkim_verify(&vctx,
+             pdkim_hashes[hashtype].exim_hashmethod,
+             &hhash_computed, &sighash)))
+  {
+  DEBUG(D_acl)
+    debug_printf("ARC i=%d AS headers verify: %s\n", as->instance, errstr);
+  arc_state_reason = US"seal sigverify error";
+  return US"fail";
+  }
+
+DEBUG(D_acl) debug_printf("ARC: AS vfy i=%d pass\n", as->instance);
+return NULL;
+}
+
+
+static const uschar *
+arc_verify_seals(arc_ctx * ctx)
+{
+arc_set * as = ctx->arcset_chain_last;
+
+if (!as)
+  return US"none";
+
+for ( ; as; as = as->prev) if (arc_seal_verify(ctx, as)) return US"fail";
+
+DEBUG(D_acl) debug_printf("ARC: AS vfy overall pass\n");
+return NULL;
+}
+/******************************************************************************/
+
+/* Do ARC verification.  Called from DATA ACL, on a verify = arc
+condition.  No arguments; we are checking globals.
+
+Return:  The ARC state, or NULL on error.
+*/
+
+const uschar *
+acl_verify_arc(void)
+{
+const uschar * res;
+
+memset(&arc_verify_ctx, 0, sizeof(arc_verify_ctx));
+
+if (!dkim_verify_ctx)
+  {
+  DEBUG(D_acl) debug_printf("ARC: no DKIM verify context\n");
+  return NULL;
+  }
+
+/* AS evaluation, per
+https://tools.ietf.org/html/draft-ietf-dmarc-arc-protocol-10#section-6
+*/
+/* 1.  Collect all ARC sets currently on the message.  If there were
+       none, the ARC state is "none" and the algorithm stops here.
+*/
+
+if ((res = arc_vfy_collect_hdrs(&arc_verify_ctx)))
+  goto out;
+
+/* 2.  If the form of any ARC set is invalid (e.g., does not contain
+       exactly one of each of the three ARC-specific header fields),
+       then the chain state is "fail" and the algorithm stops here.
+
+       1.  To avoid the overhead of unnecessary computation and delay
+           from crypto and DNS operations, the cv value for all ARC-
+           Seal(s) MAY be checked at this point.  If any of the values
+           are "fail", then the overall state of the chain is "fail" and
+           the algorithm stops here.
+
+   3.  Conduct verification of the ARC-Message-Signature header field
+       bearing the highest instance number.  If this verification fails,
+       then the chain state is "fail" and the algorithm stops here.
+*/
+
+if ((res = arc_headers_check(&arc_verify_ctx)))
+  goto out;
+
+/* 4.  For each ARC-Seal from the "N"th instance to the first, apply the
+       following logic:
+
+       1.  If the value of the "cv" tag on that seal is "fail", the
+           chain state is "fail" and the algorithm stops here.  (This
+           step SHOULD be skipped if the earlier step (2.1) was
+           performed)
+
+       2.  In Boolean nomenclature: if ((i == 1 && cv != "none") or (cv
+           == "none" && i != 1)) then the chain state is "fail" and the
+           algorithm stops here (note that the ordering of the logic is
+           structured for short-circuit evaluation).
+
+       3.  Initialize a hash function corresponding to the "a" tag of
+           the ARC-Seal.
+
+       4.  Compute the canonicalized form of the ARC header fields, in
+           the order described in Section 5.4.2, using the "relaxed"
+           header canonicalization defined in Section 3.4.2 of
+           [RFC6376].  Pass the canonicalized result to the hash
+           function.
+
+       5.  Retrieve the final digest from the hash function.
+
+       6.  Retrieve the public key identified by the "s" and "d" tags in
+           the ARC-Seal, as described in Section 4.1.6.
+
+       7.  Determine whether the signature portion ("b" tag) of the ARC-
+           Seal and the digest computed above are valid according to the
+           public key.  (See also Section Section 8.4 for failure case
+           handling)
+
+       8.  If the signature is not valid, the chain state is "fail" and
+           the algorithm stops here.
+
+   5.  If all seals pass validation, then the chain state is "pass", and
+       the algorithm is complete.
+*/
+
+if ((res = arc_verify_seals(&arc_verify_ctx)))
+  goto out;
+
+res = US"pass";
+
+out:
+  return res;
+}
+
+/******************************************************************************/
+
+/* Prepend the header to the rlist */
+
+static hdr_rlist *
+arc_rlist_entry(hdr_rlist * list, const uschar * s, int len)
+{
+hdr_rlist * r = store_get(sizeof(hdr_rlist) + sizeof(header_line));
+header_line * h = r->h = (header_line *)(r+1);
+
+r->prev = list;
+r->used = FALSE;
+h->next = NULL;
+h->type = 0;
+h->slen = len;
+h->text = US s;
+
+/* This works for either NL or CRLF lines; also nul-termination */
+while (*++s)
+  if (*s == '\n' && s[1] != '\t' && s[1] != ' ') break;
+s++;           /* move past end of line */
+
+return r;
+}
+
+
+/* Walk the given headers strings identifying each header, and construct
+a reverse-order list.
+*/
+
+static hdr_rlist *
+arc_sign_scan_headers(arc_ctx * ctx, gstring * sigheaders)
+{
+const uschar * s;
+hdr_rlist * rheaders = NULL;
+
+s = sigheaders ? sigheaders->s : NULL;
+if (s) while (*s)
+  {
+  const uschar * s2 = s;
+
+  /* This works for either NL or CRLF lines; also nul-termination */
+  while (*++s2)
+    if (*s2 == '\n' && s2[1] != '\t' && s2[1] != ' ') break;
+  s2++;                /* move past end of line */
+
+  rheaders = arc_rlist_entry(rheaders, s, s2 - s);
+  s = s2;
+  }
+return rheaders;
+}
+
+
+
+/* Return the A-R content, without identity, with line-ending and
+NUL termination. */
+
+static BOOL
+arc_sign_find_ar(header_line * headers, const uschar * identity, blob * ret)
+{
+header_line * h;
+int ilen = Ustrlen(identity);
+
+ret->data = NULL;
+for(h = headers; h; h = h->next)
+  {
+  uschar * s = h->text, c;
+  int len = h->slen;
+
+  if (Ustrncmp(s, HDR_AR, HDRLEN_AR) != 0) continue;
+  s += HDRLEN_AR, len -= HDRLEN_AR;            /* header name */
+  while (  len > 0
+       && (c = *s) && (c == ' ' || c == '\t' || c == '\r' || c == '\n'))
+    s++, len--;                                        /* FWS */
+  if (Ustrncmp(s, identity, ilen) != 0) continue;
+  s += ilen; len -= ilen;                      /* identity */
+  if (len <= 0) continue;
+  if ((c = *s) && c == ';') s++, len--;                /* identity terminator */
+  while (  len > 0
+       && (c = *s) && (c == ' ' || c == '\t' || c == '\r' || c == '\n'))
+    s++, len--;                                        /* FWS */
+  if (len <= 0) continue;
+  ret->data = s;
+  ret->len = len;
+  return TRUE;
+  }
+return FALSE;
+}
+
+
+
+/* Append a constructed AAR including CRLF.  Add it to the arc_ctx too.  */
+
+static gstring *
+arc_sign_append_aar(gstring * g, arc_ctx * ctx,
+  const uschar * identity, int instance, blob * ar)
+{
+int aar_off = g ? g->ptr : 0;
+arc_set * as = store_get(sizeof(arc_set) + sizeof(arc_line) + sizeof(header_line));
+arc_line * al = (arc_line *)(as+1);
+header_line * h = (header_line *)(al+1);
+
+g = string_catn(g, ARC_HDR_AAR, ARC_HDRLEN_AAR);
+g = string_fmt_append(g, " i=%d; %s;\r\n\t", instance, identity);
+g = string_catn(g, US ar->data, ar->len);
+
+h->slen = g->ptr - aar_off;
+h->text = g->s + aar_off;
+al->complete = h;
+as->next = NULL;
+as->prev = ctx->arcset_chain_last;
+as->instance = instance;
+as->hdr_aar = al;
+if (instance == 1)
+  ctx->arcset_chain = as;
+else
+  ctx->arcset_chain_last->next = as;
+ctx->arcset_chain_last = as;
+
+DEBUG(D_transport) debug_printf("ARC: AAR '%.*s'\n", h->slen - 2, h->text);
+return g;
+}
+
+
+
+static BOOL
+arc_sig_from_pseudoheader(gstring * hdata, int hashtype, const uschar * privkey,
+  blob * sig, const uschar * why)
+{
+hashmethod hm = /*sig->keytype == KEYTYPE_ED25519*/ FALSE
+  ? HASH_SHA2_512 : pdkim_hashes[hashtype].exim_hashmethod;
+blob hhash;
+es_ctx sctx;
+const uschar * errstr;
+
+DEBUG(D_transport)
+  {
+  hctx hhash_ctx;
+  debug_printf("ARC: %s header data for signing:\n", why);
+  pdkim_quoteprint(hdata->s, hdata->ptr);
+
+  (void) exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod);
+  exim_sha_update(&hhash_ctx, hdata->s, hdata->ptr);
+  exim_sha_finish(&hhash_ctx, &hhash);
+  debug_printf("ARC: header hash: "); pdkim_hexprint(hhash.data, hhash.len);
+  }
+
+if (FALSE /*need hash for Ed25519 or GCrypt signing*/ )
+  {
+  hctx hhash_ctx;
+  (void) exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod);
+  exim_sha_update(&hhash_ctx, hdata->s, hdata->ptr);
+  exim_sha_finish(&hhash_ctx, &hhash);
+  }
+else
+  {
+  hhash.data = hdata->s;
+  hhash.len = hdata->ptr;
+  }
+
+if (  (errstr = exim_dkim_signing_init(privkey, &sctx))
+   || (errstr = exim_dkim_sign(&sctx, hm, &hhash, sig)))
+  {
+  log_write(0, LOG_MAIN, "ARC: %s signing: %s\n", why, errstr);
+  DEBUG(D_transport)
+    debug_printf("private key, or private-key file content, was: '%s'\n",
+      privkey);
+  return FALSE;
+  }
+return TRUE;
+}
+
+
+
+static gstring *
+arc_sign_append_sig(gstring * g, blob * sig)
+{
+/*debug_printf("%s: raw sig ", __FUNCTION__); pdkim_hexprint(sig->data, sig->len);*/
+sig->data = pdkim_encode_base64(sig);
+sig->len = Ustrlen(sig->data);
+for (;;)
+  {
+  int len = MIN(sig->len, 74);
+  g = string_catn(g, sig->data, len);
+  if ((sig->len -= len) == 0) break;
+  sig->data += len;
+  g = string_catn(g, US"\r\n\t  ", 5);
+  }
+g = string_catn(g, US";\r\n", 3);
+gstring_reset_unused(g);
+string_from_gstring(g);
+return g;
+}
+
+
+/* Append a constructed AMS including CRLF.  Add it to the arc_ctx too. */
+
+static gstring *
+arc_sign_append_ams(gstring * g, arc_ctx * ctx, int instance,
+  const uschar * identity, const uschar * selector, blob * bodyhash,
+  hdr_rlist * rheaders, const uschar * privkey, unsigned options)
+{
+uschar * s;
+gstring * hdata = NULL;
+int col;
+int hashtype = pdkim_hashname_to_hashtype(US"sha256", 6);      /*XXX hardwired */
+blob sig;
+int ams_off;
+arc_line * al = store_get(sizeof(header_line) + sizeof(arc_line));
+header_line * h = (header_line *)(al+1);
+
+/* debug_printf("%s\n", __FUNCTION__); */
+
+/* Construct the to-be-signed AMS pseudo-header: everything but the sig. */
+
+ams_off = g->ptr;
+g = string_fmt_append(g, "%s i=%d; a=rsa-sha256; c=relaxed; d=%s; s=%s",
+      ARC_HDR_AMS, instance, identity, selector);      /*XXX hardwired a= */
+if (options & ARC_SIGN_OPT_TSTAMP)
+  g = string_fmt_append(g, "; t=%lu", (u_long)now);
+if (options & ARC_SIGN_OPT_EXPIRE)
+  g = string_fmt_append(g, "; x=%lu", (u_long)expire);
+g = string_fmt_append(g, ";\r\n\tbh=%s;\r\n\th=",
+      pdkim_encode_base64(bodyhash));
+
+for(col = 3; rheaders; rheaders = rheaders->prev)
+  {
+  const uschar * hnames = US"DKIM-Signature:" PDKIM_DEFAULT_SIGN_HEADERS;
+  uschar * name, * htext = rheaders->h->text;
+  int sep = ':';
+
+  /* Spot headers of interest */
+
+  while ((name = string_nextinlist(&hnames, &sep, NULL, 0)))
+    {
+    int len = Ustrlen(name);
+    if (strncasecmp(CCS htext, CCS name, len) == 0)
+      {
+      /* If too long, fold line in h= field */
+
+      if (col + len > 78) g = string_catn(g, US"\r\n\t  ", 5), col = 3;
+
+      /* Add name to h= list */
+
+      g = string_catn(g, name, len);
+      g = string_catn(g, US":", 1);
+      col += len + 1;
+
+      /* Accumulate header for hashing/signing */
+
+      hdata = string_cat(hdata,
+               pdkim_relax_header_n(htext, rheaders->h->slen, TRUE));  /*XXX hardwired */
+      break;
+      }
+    }
+  }
+
+/* Lose the last colon from the h= list */
+
+if (g->s[g->ptr - 1] == ':') g->ptr--;
+
+g = string_catn(g, US";\r\n\tb=;", 7);
+
+/* Include the pseudo-header in the accumulation */
+
+s = pdkim_relax_header_n(g->s + ams_off, g->ptr - ams_off, FALSE);
+hdata = string_cat(hdata, s);
+
+/* Calculate the signature from the accumulation */
+/*XXX does that need further relaxation? there are spaces embedded in the b= strings! */
+
+if (!arc_sig_from_pseudoheader(hdata, hashtype, privkey, &sig, US"AMS"))
+  return NULL;
+
+/* Lose the trailing semicolon from the psuedo-header, and append the signature
+(folded over lines) and termination to complete it. */
+
+g->ptr--;
+g = arc_sign_append_sig(g, &sig);
+
+h->slen = g->ptr - ams_off;
+h->text = g->s + ams_off;
+al->complete = h;
+ctx->arcset_chain_last->hdr_ams = al;
+
+DEBUG(D_transport) debug_printf("ARC: AMS '%.*s'\n", h->slen - 2, h->text);
+return g;
+}
+
+
+
+/* Look for an arc= result in an A-R header blob.  We know that its data
+happens to be a NUL-term string. */
+
+static uschar *
+arc_ar_cv_status(blob * ar)
+{
+const uschar * resinfo = ar->data;
+int sep = ';';
+uschar * methodspec, * s;
+
+while ((methodspec = string_nextinlist(&resinfo, &sep, NULL, 0)))
+  if (Ustrncmp(methodspec, US"arc=", 4) == 0)
+    {
+    uschar c;
+    for (s = methodspec += 4;
+         (c = *s) && c != ';' && c != ' ' && c != '\r' && c != '\n'; ) s++;
+    return string_copyn(methodspec, s - methodspec);
+    }
+return US"none";
+}
+
+
+
+/* Build the AS header and prepend it */
+
+static gstring *
+arc_sign_prepend_as(gstring * arcset_interim, arc_ctx * ctx,
+  int instance, const uschar * identity, const uschar * selector, blob * ar,
+  const uschar * privkey, unsigned options)
+{
+gstring * arcset;
+arc_set * as;
+uschar * status = arc_ar_cv_status(ar);
+arc_line * al = store_get(sizeof(header_line) + sizeof(arc_line));
+header_line * h = (header_line *)(al+1);
+
+gstring * hdata = NULL;
+int hashtype = pdkim_hashname_to_hashtype(US"sha256", 6);      /*XXX hardwired */
+blob sig;
+
+/*
+- Generate AS
+  - no body coverage
+  - no h= tag; implicit coverage
+  - arc status from A-R
+    - if fail:
+      - coverage is just the new ARC set
+        including self (but with an empty b= in self)
+    - if non-fail:
+      - all ARC set headers, set-number order, aar then ams then as,
+        including self (but with an empty b= in self)
+*/
+
+/* Construct the AS except for the signature */
+
+arcset = string_append(NULL, 9,
+         ARC_HDR_AS,
+         US" i=", string_sprintf("%d", instance),
+         US"; cv=", status,
+         US"; a=rsa-sha256; d=", identity,                     /*XXX hardwired */
+         US"; s=", selector);                                  /*XXX same as AMS */
+if (options & ARC_SIGN_OPT_TSTAMP)
+  arcset = string_append(arcset, 2,
+      US"; t=", string_sprintf("%lu", (u_long)now));
+arcset = string_cat(arcset,
+         US";\r\n\t b=;");
+
+h->slen = arcset->ptr;
+h->text = arcset->s;
+al->complete = h;
+ctx->arcset_chain_last->hdr_as = al;
+
+/* For any but "fail" chain-verify status, walk the entire chain in order by
+instance.  For fail, only the new arc-set.  Accumulate the elements walked. */
+
+for (as = Ustrcmp(status, US"fail") == 0
+       ? ctx->arcset_chain_last : ctx->arcset_chain;
+     as; as = as->next)
+  {
+  /* Accumulate AAR then AMS then AS.  Relaxed canonicalisation
+  is required per standard. */
+
+  h = as->hdr_aar->complete;
+  hdata = string_cat(hdata, pdkim_relax_header_n(h->text, h->slen, TRUE));
+  h = as->hdr_ams->complete;
+  hdata = string_cat(hdata, pdkim_relax_header_n(h->text, h->slen, TRUE));
+  h = as->hdr_as->complete;
+  hdata = string_cat(hdata, pdkim_relax_header_n(h->text, h->slen, !!as->next));
+  }
+
+/* Calculate the signature from the accumulation */
+
+if (!arc_sig_from_pseudoheader(hdata, hashtype, privkey, &sig, US"AS"))
+  return NULL;
+
+/* Lose the trailing semicolon */
+arcset->ptr--;
+arcset = arc_sign_append_sig(arcset, &sig);
+DEBUG(D_transport) debug_printf("ARC: AS  '%.*s'\n", arcset->ptr - 2, arcset->s);
+
+/* Finally, append the AMS and AAR to the new AS */
+
+return string_catn(arcset, arcset_interim->s, arcset_interim->ptr);
+}
+
+
+/**************************************/
+
+/* Return pointer to pdkim_bodyhash for given hash method, creating new
+method if needed.
+*/
+
+void *
+arc_ams_setup_sign_bodyhash(void)
+{
+int canon_head, canon_body;
+
+DEBUG(D_transport) debug_printf("ARC: requesting bodyhash\n");
+pdkim_cstring_to_canons(US"relaxed", 7, &canon_head, &canon_body);     /*XXX hardwired */
+return pdkim_set_bodyhash(&dkim_sign_ctx,
+       pdkim_hashname_to_hashtype(US"sha256", 6),                      /*XXX hardwired */
+       canon_body,
+       -1);
+}
+
+
+
+void
+arc_sign_init(void)
+{
+memset(&arc_sign_ctx, 0, sizeof(arc_sign_ctx));
+}
+
+
+
+/* A "normal" header line, identified by DKIM processing.  These arrive before
+the call to arc_sign(), which carries any newly-created DKIM headers - and
+those go textually before the normal ones in the message.
+
+We have to take the feed from DKIM as, in the transport-filter case, the
+headers are not in memory at the time of the call to arc_sign().
+
+Take a copy of the header and construct a reverse-order list.
+Also parse ARC-chain headers and build the chain struct, retaining pointers
+into the copies.
+*/
+
+static const uschar *
+arc_header_sign_feed(gstring * g)
+{
+uschar * s = string_copyn(g->s, g->ptr);
+headers_rlist = arc_rlist_entry(headers_rlist, s, g->ptr);
+return arc_try_header(&arc_sign_ctx, headers_rlist->h, TRUE);
+}
+
+
+
+/* ARC signing.  Called from the smtp transport, if the arc_sign option is set.
+The dkim_exim_sign() function has already been called, so will have hashed the
+message body for us so long as we requested a hash previously.
+
+Arguments:
+  signspec     Three-element colon-sep list: identity, selector, privkey.
+               Optional fourth element: comma-sep list of options.
+               Already expanded
+  sigheaders   Any signature headers already generated, eg. by DKIM, or NULL
+  errstr       Error string
+
+Return value
+  Set of headers to prepend to the message, including the supplied sigheaders
+  but not the plainheaders.
+*/
+
+gstring *
+arc_sign(const uschar * signspec, gstring * sigheaders, uschar ** errstr)
+{
+const uschar * identity, * selector, * privkey, * opts, * s;
+unsigned options = 0;
+int sep = 0;
+header_line * headers;
+hdr_rlist * rheaders;
+blob ar;
+int instance;
+gstring * g = NULL;
+pdkim_bodyhash * b;
+
+expire = now = 0;
+
+/* Parse the signing specification */
+
+identity = string_nextinlist(&signspec, &sep, NULL, 0);
+selector = string_nextinlist(&signspec, &sep, NULL, 0);
+if (  !*identity || !*selector
+   || !(privkey = string_nextinlist(&signspec, &sep, NULL, 0)) || !*privkey)
+  {
+  log_write(0, LOG_MAIN, "ARC: bad signing-specification (%s)",
+    !*identity ? "identity" : !*selector ? "selector" : "private-key");
+  return sigheaders ? sigheaders : string_get(0);
+  }
+if (*privkey == '/' && !(privkey = expand_file_big_buffer(privkey)))
+  return sigheaders ? sigheaders : string_get(0);
+
+if ((opts = string_nextinlist(&signspec, &sep, NULL, 0)))
+  {
+  int osep = ',';
+  while ((s = string_nextinlist(&opts, &osep, NULL, 0)))
+    if (Ustrcmp(s, "timestamps") == 0)
+      {
+      options |= ARC_SIGN_OPT_TSTAMP;
+      if (!now) now = time(NULL);
+      }
+    else if (Ustrncmp(s, "expire", 6) == 0)
+      {
+      options |= ARC_SIGN_OPT_EXPIRE;
+      if (*(s += 6) == '=')
+       if (*++s == '+')
+         {
+         if (!(expire = (time_t)atoi(CS ++s)))
+           expire = ARC_SIGN_DEFAULT_EXPIRE_DELTA;
+         if (!now) now = time(NULL);
+         expire += now;
+         }
+       else
+         expire = (time_t)atol(CS s);
+      else
+       {
+       if (!now) now = time(NULL);
+       expire = now + ARC_SIGN_DEFAULT_EXPIRE_DELTA;
+       }
+      }
+  }
+
+DEBUG(D_transport) debug_printf("ARC: sign for %s\n", identity);
+
+/* Make an rlist of any new DKIM headers, then add the "normals" rlist to it.
+Then scan the list for an A-R header. */
+
+string_from_gstring(sigheaders);
+if ((rheaders = arc_sign_scan_headers(&arc_sign_ctx, sigheaders)))
+  {
+  hdr_rlist ** rp;
+  for (rp = &headers_rlist; *rp; ) rp = &(*rp)->prev;
+  *rp = rheaders;
+  }
+
+/* Finally, build a normal-order headers list */
+/*XXX only needed for hunt-the-AR? */
+/*XXX also, we really should be accepting any number of ADMD-matching ARs */
+  {
+  header_line * hnext = NULL;
+  for (rheaders = headers_rlist; rheaders;
+       hnext = rheaders->h, rheaders = rheaders->prev)
+    rheaders->h->next = hnext;
+  headers = hnext;
+  }
+
+if (!(arc_sign_find_ar(headers, identity, &ar)))
+  {
+  log_write(0, LOG_MAIN, "ARC: no Authentication-Results header for signing");
+  return sigheaders ? sigheaders : string_get(0);
+  }
+
+/* We previously built the data-struct for the existing ARC chain, if any, using a headers
+feed from the DKIM module.  Use that to give the instance number for the ARC set we are
+about to build. */
+
+DEBUG(D_transport)
+  if (arc_sign_ctx.arcset_chain_last)
+    debug_printf("ARC: existing chain highest instance: %d\n",
+      arc_sign_ctx.arcset_chain_last->instance);
+  else
+    debug_printf("ARC: no existing chain\n");
+
+instance = arc_sign_ctx.arcset_chain_last ? arc_sign_ctx.arcset_chain_last->instance + 1 : 1;
+
+/*
+- Generate AAR
+  - copy the A-R; prepend i= & identity
+*/
+
+g = arc_sign_append_aar(g, &arc_sign_ctx, identity, instance, &ar);
+
+/*
+- Generate AMS
+  - Looks fairly like a DKIM sig
+  - Cover all DKIM sig headers as well as the usuals
+    - ? oversigning?
+  - Covers the data
+  - we must have requested a suitable bodyhash previously
+*/
+
+b = arc_ams_setup_sign_bodyhash();
+g = arc_sign_append_ams(g, &arc_sign_ctx, instance, identity, selector,
+      &b->bh, headers_rlist, privkey, options);
+
+/*
+- Generate AS
+  - no body coverage
+  - no h= tag; implicit coverage
+  - arc status from A-R
+    - if fail:
+      - coverage is just the new ARC set
+        including self (but with an empty b= in self)
+    - if non-fail:
+      - all ARC set headers, set-number order, aar then ams then as,
+        including self (but with an empty b= in self)
+*/
+
+if (g)
+  g = arc_sign_prepend_as(g, &arc_sign_ctx, instance, identity, selector, &ar,
+      privkey, options);
+
+/* Finally, append the dkim headers and return the lot. */
+
+if (sigheaders) g = string_catn(g, sigheaders->s, sigheaders->ptr);
+(void) string_from_gstring(g);
+gstring_reset_unused(g);
+return g;
+}
+
+
+/******************************************************************************/
+
+/* Check to see if the line is an AMS and if so, set up to validate it.
+Called from the DKIM input processing.  This must be done now as the message
+body data is hashed during input.
+
+We call the DKIM code to request a body-hash; it has the facility already
+and the hash parameters might be common with other requests.
+*/
+
+static const uschar *
+arc_header_vfy_feed(gstring * g)
+{
+header_line h;
+arc_line al;
+pdkim_bodyhash * b;
+uschar * errstr;
+
+if (!dkim_verify_ctx) return US"no dkim context";
+
+if (strncmpic(ARC_HDR_AMS, g->s, ARC_HDRLEN_AMS) != 0) return US"not AMS";
+
+DEBUG(D_receive) debug_printf("ARC: spotted AMS header\n");
+/* Parse the AMS header */
+
+h.next = NULL;
+h.slen = g->size;
+h.text = g->s;
+memset(&al, 0, sizeof(arc_line));
+if ((errstr = arc_parse_line(&al, &h, ARC_HDRLEN_AMS, FALSE)))
+  {
+  DEBUG(D_acl) if (errstr) debug_printf("ARC: %s\n", errstr);
+  return US"line parsing error";
+  }
+
+/* defaults */
+if (!al.c.data)
+  {
+  al.c_body.data = US"simple"; al.c_body.len = 6;
+  al.c_head = al.c_body;
+  }
+
+/* Ask the dkim code to calc a bodyhash with those specs */
+
+if (!(b = arc_ams_setup_vfy_bodyhash(&al)))
+  return US"dkim hash setup fail";
+
+/* Discard the reference; search again at verify time, knowing that one
+should have been created here. */
+
+return NULL;
+}
+
+
+
+/* A header line has been identified by DKIM processing.
+
+Arguments:
+  g            Header line
+  is_vfy       TRUE for verify mode or FALSE for signing mode
+
+Return:
+  NULL for success, or an error string (probably unused)
+*/
+
+const uschar *
+arc_header_feed(gstring * g, BOOL is_vfy)
+{
+return is_vfy ? arc_header_vfy_feed(g) : arc_header_sign_feed(g);
+}
+
+
+
+/******************************************************************************/
+
+/* Construct the list of domains from the ARC chain after validation */
+
+uschar *
+fn_arc_domains(void)
+{
+arc_set * as;
+unsigned inst;
+gstring * g = NULL;
+
+for (as = arc_verify_ctx.arcset_chain, inst = 1; as; as = as->next, inst++)
+  {
+  arc_line * hdr_as = as->hdr_as;
+  if (hdr_as)
+    {
+    blob * d = &hdr_as->d;
+
+    for (; inst < as->instance; inst++)
+      g = string_catn(g, US":", 1);
+
+    g = d->data && d->len
+      ? string_append_listele_n(g, ':', d->data, d->len)
+      : string_catn(g, US":", 1);
+    }
+  else
+    g = string_catn(g, US":", 1);
+  }
+return g ? g->s : US"";
+}
+
+
+/* Construct an Authentication-Results header portion, for the ARC module */
+
+gstring *
+authres_arc(gstring * g)
+{
+if (arc_state)
+  {
+  arc_line * highest_ams;
+  int start = 0;               /* Compiler quietening */
+  DEBUG(D_acl) start = g->ptr;
+
+  g = string_append(g, 2, US";\n\tarc=", arc_state);
+  if (arc_received_instance > 0)
+    {
+    g = string_fmt_append(g, " (i=%d)", arc_received_instance);
+    if (arc_state_reason)
+      g = string_append(g, 3, US"(", arc_state_reason, US")");
+    g = string_catn(g, US" header.s=", 10);
+    highest_ams = arc_received->hdr_ams;
+    g = string_catn(g, highest_ams->s.data, highest_ams->s.len);
+
+    g = string_fmt_append(g, " arc.oldest-pass=%d", arc_oldest_pass);
+
+    if (sender_host_address)
+      g = string_append(g, 2, US" smtp.remote-ip=", sender_host_address);
+    }
+  else if (arc_state_reason)
+    g = string_append(g, 3, US" (", arc_state_reason, US")");
+  DEBUG(D_acl) debug_printf("ARC:  authres '%.*s'\n",
+                 g->ptr - start - 3, g->s + start + 3);
+  }
+else
+  DEBUG(D_acl) debug_printf("ARC:  no authres\n");
+return g;
+}
+
+
+# endif /* SUPPORT_SPF */
+#endif /* EXPERIMENTAL_ARC */
+/* vi: aw ai sw=2
+ */
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.
 
 # 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 \
       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
 
 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
                 $(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
 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
 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
 
 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
 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
 
 # End
index 21b0cda..0e0c097 100644 (file)
@@ -68,7 +68,7 @@ CLIENT AUTHENTICATION
 The third function performs authentication as a client. It receives a pointer
 to the instance block, and four further arguments:
 
 The third function performs authentication as a client. It receives a pointer
 to the instance block, and four further arguments:
 
-  The smtp_inblock item for the connection to the remote host.
+  The smtp_context item for the connection to the remote host.
 
   The normal command-reading timeout value.
 
 
   The normal command-reading timeout value.
 
index 7ad5a1d..d2c95c3 100644 (file)
@@ -68,7 +68,7 @@ int main (int argc, char ** argv)
 
        spa_build_auth_request (&request, username, domain);
 
 
        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",
                spa_request_length(&request));
 
        printf ("SPA Login request for username=%s:\n   %s\n",
@@ -83,15 +83,15 @@ int main (int argc, char ** argv)
 
        challenge_str = argv [3];
 
 
        challenge_str = argv [3];
 
-       if (spa_base64_to_bits ((char *)&challenge, sizeof(challenge),
-                (const char *)(challenge_str))<0)
+       if (spa_base64_to_bits (CS &challenge, sizeof(challenge),
+                CCS (challenge_str))<0)
        {
                 printf("bad base64 data in challenge: %s\n", challenge_str);
                 exit (1);
        }
 
        spa_build_auth_response (&challenge, &response, username, password);
        {
                 printf("bad base64 data in challenge: %s\n", challenge_str);
                 exit (1);
        }
 
        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 " \
                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)
 */
 
    up with a different answer to the one above)
 */
 
-#define DEBUG(a,b) ;
+#define DEBUG_X(a,b) ;
 
 extern int DEBUGLEVEL;
 
 
 extern int DEBUGLEVEL;
 
-#include <sys/types.h>     /* For size_t */
+#include "../exim.h"
 #include "auth-spa.h"
 #include <assert.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
 
 #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); } }
 
        { 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); } } }
 
        { 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); } } }
 
        { 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); } }
 
        { 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); } } }
 
        { 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); } } }
 
        { 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 */
 
 /* 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 */
 
 /* 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 */
 */
 
 /* 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 *)(CS (buf) + (pos)))
+#  define IVAL(buf,pos) (*(uint32x *)(CS (buf) + (pos)))
+#  define SVALS(buf,pos) (*(int16x *)(CS (buf) + (pos)))
+#  define IVALS(buf,pos) (*(int32x *)(CS (buf) + (pos)))
 
 /* store single value in an SMB buffer */
 
 /* 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 */
 
 
 /* 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); }
 
 { 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 */
 { 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 */
 
 /* 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) */
 
 
 /* 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) */
 
 /* 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) */
 
 /* 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) \
        { RW_PCVAL(read,inbuf,outbuf,len) \
-       DEBUG(5,("%s%04x %s: ", \
+       DEBUG_X(5,("%s%04x %s: ", \
              tab_depth(depth), base,string)); \
              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) \
        { RW_PSVAL(read,big_endian,inbuf,outbuf,len) \
-       DEBUG(5,("%s%04x %s: ", \
+       DEBUG_X(5,("%s%04x %s: ", \
              tab_depth(depth), base,string)); \
              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) \
        { RW_PIVAL(read,big_endian,inbuf,outbuf,len) \
-       DEBUG(5,("%s%04x %s: ", \
+       DEBUG_X(5,("%s%04x %s: ", \
              tab_depth(depth), base,string)); \
              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) \
        { 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)); }
 
              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) \
        { 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)); }
 
              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) \
        { 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 */
 
              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+/";
 
 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,
 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
 #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)
 /* 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)
     {
     }
   if (inlen > 0)
     {
-      unsigned char fragment;
+      uschar fragment;
 
       *out++ = base64digits[in[0] >> 2];
       fragment = (in[0] << 4) & 0x30;
 
       *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;
 /* 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;
 
   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,
   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
 };
 
   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,
   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
 };
 
   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,
   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
 };
 
   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,
   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
 };
 
   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,
   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,
   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},
   {{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
 };
 
 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++)
 {
   int i;
   for (i = 0; i < n; i++)
@@ -696,7 +680,7 @@ dohash (char *out, char *in, char *key, int forw)
 }
 
 static void
 }
 
 static void
-str_to_key (unsigned char *str, unsigned char *key)
+str_to_key (uschar *str, uschar *key)
 {
   int i;
 
 {
   int i;
 
@@ -716,13 +700,13 @@ str_to_key (unsigned char *str, unsigned char *key)
 
 
 static void
 
 
 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];
 {
   int i;
   char outb[64];
   char inb[64];
   char keyb[64];
-  unsigned char key2[8];
+  uschar key2[8];
 
   str_to_key (key, key2);
 
 
   str_to_key (key, key2);
 
@@ -748,15 +732,15 @@ smbhash (unsigned char *out, unsigned char *in, unsigned char *key, int forw)
 }
 
 void
 }
 
 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
   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);
 {
   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
 }
 
 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);
 {
   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)
     {
 
   if (!dest)
     {
-      DEBUG (0, ("ERROR: NULL dest in safe_strcpy\n"));
+      DEBUG_X (0, ("ERROR: NULL dest in safe_strcpy\n"));
       return NULL;
     }
 
       return NULL;
     }
 
@@ -828,7 +812,7 @@ safe_strcpy (char *dest, const char *src, size_t maxlength)
 
   if (len > 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;
     }
                 (int) (len - maxlength), src));
       len = maxlength;
     }
@@ -850,7 +834,7 @@ strupper (char *s)
          s += skip;
        else
          {
          s += skip;
        else
          {
-           if (islower ((unsigned char)(*s)))
+           if (islower ((uschar)(*s)))
              *s = toupper (*s);
            s++;
          }
              *s = toupper (*s);
            s++;
          }
@@ -866,24 +850,24 @@ strupper (char *s)
  */
 
 void
  */
 
 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);
 
   memset (p21, '\0', 21);
   memset (p14, '\0', 14);
-  StrnCpy ((char *) p14, (char *) passwd, 14);
+  StrnCpy (CS  p14, CS  passwd, 14);
 
 
-  strupper ((char *) p14);
+  strupper (CS  p14);
   E_P16 (p14, p21);
 
   SMBOWFencrypt (p21, c8, p24);
 
 #ifdef DEBUG_PASSWORD
   E_P16 (p14, p21);
 
   SMBOWFencrypt (p21, c8, p24);
 
 #ifdef DEBUG_PASSWORD
-  DEBUG (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);
+  DEBUG_X (100, ("spa_smb_encrypt: lm#, challenge, response\n"));
+  dump_data (100, CS  p21, 16);
+  dump_data (100, CS  c8, 8);
+  dump_data (100, CS  p24, 24);
 #endif
 }
 
 #endif
 }
 
@@ -905,7 +889,7 @@ _my_wcslen (int16x * str)
  */
 
 static int
  */
 
 static int
-_my_mbstowcs (int16x * dst, uchar * src, int len)
+_my_mbstowcs (int16x * dst, uschar * src, int len)
 {
   int i;
   int16x val;
 {
   int i;
   int16x val;
@@ -927,13 +911,13 @@ _my_mbstowcs (int16x * dst, uchar * src, int len)
  */
 
 void
  */
 
 void
-E_md4hash (uchar * passwd, uchar * p16)
+E_md4hash (uschar * passwd, uschar * p16)
 {
   int len;
   int16x wpwd[129];
 
   /* Password cannot be longer than 128 characters */
 {
   int len;
   int16x wpwd[129];
 
   /* Password cannot be longer than 128 characters */
-  len = strlen ((char *) passwd);
+  len = strlen (CS  passwd);
   if (len > 128)
     len = 128;
   /* Password must be converted to NT unicode */
   if (len > 128)
     len = 128;
   /* Password must be converted to NT unicode */
@@ -942,12 +926,12 @@ E_md4hash (uchar * passwd, uchar * p16)
   /* Calculate length in bytes */
   len = _my_wcslen (wpwd) * sizeof (int16x);
 
   /* 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
 }
 
 /* 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];
 
 {
   char passwd[130];
 
@@ -956,12 +940,12 @@ 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);
 
   /* 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
 
 #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 (120, passwd, strlen (passwd));
-  dump_data (100, (char *) nt_p16, 16);
+  dump_data (100, CS  nt_p16, 16);
 #endif
 
   /* Mangle the passwords into Lanman format */
 #endif
 
   /* Mangle the passwords into Lanman format */
@@ -971,12 +955,12 @@ 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);
   /* 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
 
 #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 (120, passwd, strlen (passwd));
-  dump_data (100, (char *) p16, 16);
+  dump_data (100, CS  p16, 16);
 #endif
   /* clear out local copy of user's password (just being paranoid). */
   memset (passwd, '\0', sizeof (passwd));
 #endif
   /* clear out local copy of user's password (just being paranoid). */
   memset (passwd, '\0', sizeof (passwd));
@@ -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
 
 /* 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);
 
 
   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
 
 /* 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);
 
   memset (p21, '\0', 21);
   memcpy (p21, passwd, 8);
@@ -1006,10 +990,10 @@ NTLMSSPOWFencrypt (uchar passwd[8], uchar * ntlmchalresp, uchar p24[24])
 
   E_P24 (p21, ntlmchalresp, p24);
 #ifdef DEBUG_PASSWORD
 
   E_P24 (p21, ntlmchalresp, p24);
 #ifdef DEBUG_PASSWORD
-  DEBUG (100, ("NTLMSSPOWFencrypt: p21, c8, p24\n"));
-  dump_data (100, (char *) p21, 21);
-  dump_data (100, (char *) ntlmchalresp, 8);
-  dump_data (100, (char *) p24, 24);
+  DEBUG_X (100, ("NTLMSSPOWFencrypt: p21, c8, p24\n"));
+  dump_data (100, CS  p21, 21);
+  dump_data (100, CS  ntlmchalresp, 8);
+  dump_data (100, CS  p24, 24);
 #endif
 }
 
 #endif
 }
 
@@ -1017,9 +1001,9 @@ NTLMSSPOWFencrypt (uchar passwd[8], uchar * ntlmchalresp, uchar p24[24])
 /* Does the NT MD4 hash then des encryption. */
 
 void
 /* 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);
 
 
   memset (p21, '\0', 21);
 
@@ -1027,10 +1011,10 @@ spa_smb_nt_encrypt (uchar * passwd, uchar * c8, uchar * p24)
   SMBOWFencrypt (p21, c8, p24);
 
 #ifdef DEBUG_PASSWORD
   SMBOWFencrypt (p21, c8, p24);
 
 #ifdef DEBUG_PASSWORD
-  DEBUG (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);
+  DEBUG_X (100, ("spa_smb_nt_encrypt: nt#, challenge, response\n"));
+  dump_data (100, CS  p21, 16);
+  dump_data (100, CS  c8, 8);
+  dump_data (100, CS  p24, 24);
 #endif
 }
 
 #endif
 }
 
@@ -1147,7 +1131,7 @@ spa_mdfour64 (uint32x * M)
 }
 
 static void
 }
 
 static void
-copy64 (uint32x * M, unsigned char *in)
+copy64 (uint32x * M, uschar *in)
 {
   int i;
 
 {
   int i;
 
@@ -1157,7 +1141,7 @@ copy64 (uint32x * M, unsigned char *in)
 }
 
 static void
 }
 
 static void
-copy4 (unsigned char *out, uint32x x)
+copy4 (uschar *out, uint32x x)
 {
   out[0] = x & 0xFF;
   out[1] = (x >> 8) & 0xFF;
 {
   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
 
 /* 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;
   uint32x M[16];
   uint32x b = n * 8;
   int i;
@@ -1236,7 +1220,7 @@ char versionString[] = "libntlm version 0.21";
 
 #define spa_bytes_add(ptr, header, buf, count) \
 { \
 
 #define spa_bytes_add(ptr, header, buf, count) \
 { \
-if (buf != NULL  &&  count) \
+if (buf != NULL  &&  count != 0) /* we hate -Wint-in-bool-contex */ \
   { \
   SSVAL(&ptr->header.len,0,count); \
   SSVAL(&ptr->header.maxlen,0,count); \
   { \
   SSVAL(&ptr->header.len,0,count); \
   SSVAL(&ptr->header.maxlen,0,count); \
@@ -1257,13 +1241,13 @@ else \
 char *p = string; \
 int len = 0; \
 if (p) len = strlen(p); \
 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; \
 }
 
 #define spa_unicode_add_string(ptr, header, string) \
 { \
 char *p = string; \
-unsigned char *b = NULL; \
+uschar *b = NULL; \
 int len = 0; \
 if (p) \
   { \
 int len = 0; \
 if (p) \
   { \
@@ -1277,13 +1261,16 @@ spa_bytes_add(ptr, header, b, len*2); \
 #define GetUnicodeString(structPtr, header) \
 unicodeToString(((char*)structPtr) + IVAL(&structPtr->header.offset,0) , SVAL(&structPtr->header.len,0)/2)
 #define GetString(structPtr, header) \
 #define GetUnicodeString(structPtr, header) \
 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))
+toString(((CS structPtr) + IVAL(&structPtr->header.offset,0)), SVAL(&structPtr->header.len,0))
+
+#ifdef notdef
+
 #define DumpBuffer(fp, structPtr, header) \
 #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
 
 
 static void
-dumpRaw (FILE * fp, unsigned char *buf, size_t len)
+dumpRaw (FILE * fp, uschar *buf, size_t len)
 {
   int i;
 
 {
   int i;
 
@@ -1293,6 +1280,8 @@ dumpRaw (FILE * fp, unsigned char *buf, size_t len)
   fprintf (fp, "\n");
 }
 
   fprintf (fp, "\n");
 }
 
+#endif
+
 char *
 unicodeToString (char *p, size_t len)
 {
 char *
 unicodeToString (char *p, size_t len)
 {
@@ -1311,10 +1300,10 @@ unicodeToString (char *p, size_t len)
   return buf;
 }
 
   return buf;
 }
 
-static unsigned char *
+static uschar *
 strToUnicode (char *p)
 {
 strToUnicode (char *p)
 {
-  static unsigned char buf[1024];
+  static uschar buf[1024];
   size_t l = strlen (p);
   int i = 0;
 
   size_t l = strlen (p);
   int i = 0;
 
@@ -1329,10 +1318,10 @@ strToUnicode (char *p)
   return buf;
 }
 
   return buf;
 }
 
-static unsigned char *
+static uschar *
 toString (char *p, size_t len)
 {
 toString (char *p, size_t len)
 {
-  static unsigned char buf[1024];
+  static uschar buf[1024];
 
   assert (len + 1 < sizeof buf);
 
 
   assert (len + 1 < sizeof buf);
 
@@ -1341,6 +1330,8 @@ toString (char *p, size_t len)
   return buf;
 }
 
   return buf;
 }
 
+#ifdef notdef
+
 void
 dumpSmbNtlmAuthRequest (FILE * fp, SPAAuthRequest * request)
 {
 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));
 }
   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)
 
 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++) {
   /* 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;
   };
 
     random_seed = (1103515245 - (chalstr[i])) * random_seed + 12345;
   };
 
@@ -1467,8 +1459,8 @@ spa_build_auth_response (SPAAuthChallenge * challenge,
       *p = '\0';
     }
 
       *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);
 
   response->bufIndex = 0;
   memcpy (response->ident, "NTLMSSP\0\0\0", 8);
@@ -1511,11 +1503,11 @@ spa_build_auth_response (SPAAuthChallenge * challenge,
     }
 
   else domain = d = strdup((cf & 0x1)?
     }
 
   else domain = d = strdup((cf & 0x1)?
-    (const char *)GetUnicodeString(challenge, uDomain) :
-    (const char *)GetString(challenge, uDomain));
+    CCS GetUnicodeString(challenge, uDomain) :
+    CCS 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);
 
   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..f96348c 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "../exim.h"
 /* 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;
 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;
 
 
 static BOOL pam_arg_ended;
 
 
@@ -129,7 +129,7 @@ Returns:   OK if authentication succeeded
 */
 
 int
 */
 
 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;
 {
 pam_handle_t *pamh = NULL;
 struct pam_conv pamc;
@@ -189,7 +189,7 @@ if (pam_error == PAM_SUCCESS)
   return OK;
   }
 
   return OK;
   }
 
-*errptr = (uschar *)pam_strerror(pamh, pam_error);
+*errptr = US pam_strerror(pamh, pam_error);
 DEBUG(D_auth) debug_printf("PAM error: %s\n", *errptr);
 
 if (pam_error == PAM_USER_UNKNOWN ||
 DEBUG(D_auth) debug_printf("PAM error: %s\n", *errptr);
 
 if (pam_error == PAM_USER_UNKNOWN ||
index a4567c1..089f501 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     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
 /* 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
 */
 
 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;
 
 {
 uschar *reply = NULL;
 
index 2064ed2..c363743 100644 (file)
@@ -2,12 +2,22 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     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. */
 
 /* 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
 #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)
   #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
   #endif
-  #include <radiusclient.h>
 #endif
 
 
 #endif
 
 
@@ -60,10 +75,10 @@ Returns:   OK if authentication succeeded
 */
 
 int
 */
 
 int
-auth_call_radius(uschar *s, uschar **errptr)
+auth_call_radius(const uschar *s, uschar **errptr)
 {
 uschar *user;
 {
 uschar *user;
-uschar *radius_args = s;
+const uschar *radius_args = s;
 int result;
 int sep = 0;
 
 int result;
 int sep = 0;
 
@@ -150,6 +165,7 @@ switch (result)
   case OK_RC:
   return OK;
 
   case OK_RC:
   return OK;
 
+  case REJECT_RC:
   case ERROR_RC:
   return FAIL;
 
   case ERROR_RC:
   return FAIL;
 
index 96c4b56..dc299f1 100644 (file)
@@ -102,7 +102,7 @@ again later. */
 
 if (cond == NULL)
   {
 
 if (cond == NULL)
   {
-  if (expand_string_forcedfail) return FAIL;
+  if (f.expand_string_forcedfail) return FAIL;
   auth_defer_msg = expand_string_message;
   return DEFER;
   }
   auth_defer_msg = expand_string_message;
   return DEFER;
   }
index f744a89..8e4794c 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2012 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
 /* 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);
 
 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 */
 
 auth_cram_md5_options_block auth_cram_md5_option_defaults = {
   NULL,             /* server_secret */
@@ -47,6 +47,17 @@ auth_cram_md5_options_block auth_cram_md5_option_defaults = {
 };
 
 
 };
 
 
+#ifdef MACRO_PREDEF
+
+/* Dummy values */
+void auth_cram_md5_init(auth_instance *ablock) {}
+int auth_cram_md5_server(auth_instance *ablock, uschar *data) {return 0;}
+int auth_cram_md5_client(auth_instance *ablock, void *sx, int timeout,
+    uschar *buffer, int buffsize) {return 0;}
+
+#else  /*!MACRO_PREDEF*/
+
+
 /*************************************************
 *          Initialization entry point            *
 *************************************************/
 /*************************************************
 *          Initialization entry point            *
 *************************************************/
@@ -68,12 +79,14 @@ if (ob->client_secret != NULL)
   }
 }
 
   }
 }
 
+#endif /*!MACRO_PREDEF*/
 #endif  /* STAND_ALONE */
 
 
 
 #endif  /* STAND_ALONE */
 
 
 
+#ifndef MACRO_PREDEF
 /*************************************************
 /*************************************************
-*      Peform the CRAM-MD5 algorithm             *
+*      Perform the CRAM-MD5 algorithm            *
 *************************************************/
 
 /* The CRAM-MD5 algorithm is described in RFC 2195. It computes
 *************************************************/
 
 /* The CRAM-MD5 algorithm is described in RFC 2195. It computes
@@ -108,8 +121,8 @@ and use that. */
 if (len > 64)
   {
   md5_start(&base);
 if (len > 64)
   {
   md5_start(&base);
-  md5_end(&base, (uschar *)secret, len, md5secret);
-  secret = (uschar *)md5secret;
+  md5_end(&base, US secret, len, md5secret);
+  secret = US md5secret;
   len = 16;
   }
 
   len = 16;
   }
 
@@ -130,7 +143,7 @@ for (i = 0; i < 64; i++)
 
 md5_start(&base);
 md5_mid(&base, isecret);
 
 md5_start(&base);
 md5_mid(&base, isecret);
-md5_end(&base, (uschar *)challenge, Ustrlen(challenge), md5secret);
+md5_end(&base, US challenge, Ustrlen(challenge), md5secret);
 
 /* Compute the outer MD5 digest */
 
 
 /* Compute the outer MD5 digest */
 
@@ -162,7 +175,7 @@ int i, rc, len;
 /* If we are running in the test harness, always send the same challenge,
 an example string taken from the RFC. */
 
 /* If we are running in the test harness, always send the same challenge,
 an example string taken from the RFC. */
 
-if (running_in_test_harness)
+if (f.running_in_test_harness)
   challenge = US"<1896.697170952@postoffice.reston.mci.net>";
 
 /* No data should have been sent with the AUTH command */
   challenge = US"<1896.697170952@postoffice.reston.mci.net>";
 
 /* No data should have been sent with the AUTH command */
@@ -172,7 +185,7 @@ if (*data != 0) return UNEXPECTED;
 /* Send the challenge, read the return */
 
 if ((rc = auth_get_data(&data, challenge, Ustrlen(challenge))) != OK) return rc;
 /* 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.
 
 /* 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.
@@ -199,7 +212,7 @@ the given name. */
 
 if (secret == NULL)
   {
 
 if (secret == NULL)
   {
-  if (expand_string_forcedfail) return FAIL;
+  if (f.expand_string_forcedfail) return FAIL;
   auth_defer_msg = expand_string_message;
   return DEFER;
   }
   auth_defer_msg = expand_string_message;
   return DEFER;
   }
@@ -246,8 +259,7 @@ return auth_check_serv_cond(ablock);
 int
 auth_cram_md5_client(
   auth_instance *ablock,                 /* authenticator block */
 int
 auth_cram_md5_client(
   auth_instance *ablock,                 /* authenticator block */
-  smtp_inblock *inblock,                 /* input connection */
-  smtp_outblock *outblock,               /* output connection */
+  void * sx,                            /* smtp connextion */
   int timeout,                           /* command timeout */
   uschar *buffer,                        /* for reading response */
   int buffsize)                          /* size of buffer */
   int timeout,                           /* command timeout */
   uschar *buffer,                        /* for reading response */
   int buffsize)                          /* size of buffer */
@@ -261,18 +273,18 @@ int i;
 uschar digest[16];
 
 /* If expansion of either the secret or the user name failed, return CANCELLED
 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)
+  if (f.expand_string_forcedfail)
     {
     *buffer = 0;           /* No message */
     return CANCELLED;
     }
   string_format(buffer, buffsize, "expansion of \"%s\" failed in "
     "%s authenticator: %s",
     {
     *buffer = 0;           /* No message */
     return CANCELLED;
     }
   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;
   }
     ablock->name, expand_string_message);
   return ERROR;
   }
@@ -280,12 +292,12 @@ if (secret == NULL || name == NULL)
 /* Initiate the authentication exchange and read the challenge, which arrives
 in base 64. */
 
 /* Initiate the authentication exchange and read the challenge, which arrives
 in base 64. */
 
-if (smtp_write_command(outblock, FALSE, "AUTH %s\r\n", ablock->public_name) < 0)
+if (smtp_write_command(sx, SCMD_FLUSH, "AUTH %s\r\n", ablock->public_name) < 0)
   return FAIL_SEND;
   return FAIL_SEND;
-if (smtp_read_response(inblock, (uschar *)buffer, buffsize, '3', timeout) < 0)
+if (!smtp_read_response(sx, buffer, buffsize, '3', timeout))
   return FAIL;
 
   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);
   {
   string_format(buffer, buffsize, "bad base 64 string in challenge: %s",
     big_buffer + 4);
@@ -299,26 +311,22 @@ 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);
 /* 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++)
 *p++ = ' ';
 
 for (i = 0; i < 16; i++)
-  {
-  sprintf(CS p, "%02x", digest[i]);
-  p += 2;
-  }
+  p += sprintf(CS p, "%02x", digest[i]);
 
 /* Send the response, in base 64, and check the result. The response is
 
 /* 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;
 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(sx, SCMD_FLUSH, "%s\r\n", b64encode(big_buffer,
   p - big_buffer)) < 0) return FAIL_SEND;
 
   p - big_buffer)) < 0) return FAIL_SEND;
 
-return smtp_read_response(inblock, (uschar *)buffer, buffsize, '2', timeout)?
-  OK : FAIL;
+return smtp_read_response(sx, US buffer, buffsize, '2', timeout)
+  OK : FAIL;
 }
 #endif  /* STAND_ALONE */
 
 }
 #endif  /* STAND_ALONE */
 
@@ -348,4 +356,5 @@ return 0;
 
 #endif
 
 
 #endif
 
+#endif /*!MACRO_PREDEF*/
 /* End of cram_md5.c */
 /* End of cram_md5.c */
index e1363bf..95644db 100644 (file)
@@ -26,7 +26,6 @@ extern auth_cram_md5_options_block auth_cram_md5_option_defaults;
 
 extern void auth_cram_md5_init(auth_instance *);
 extern int auth_cram_md5_server(auth_instance *, uschar *);
 
 extern void auth_cram_md5_init(auth_instance *);
 extern int auth_cram_md5_server(auth_instance *, uschar *);
-extern int auth_cram_md5_client(auth_instance *, smtp_inblock *,
-                                smtp_outblock *, int, uschar *, int);
+extern int auth_cram_md5_client(auth_instance *, void *, int, uschar *, int);
 
 /* End of cram_md5.h */
 
 /* End of cram_md5.h */
index c7fb593..546c20b 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2012 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* This code was originally contributed by Matthew Byng-Maddick */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* This code was originally contributed by Matthew Byng-Maddick */
@@ -63,6 +63,20 @@ auth_cyrus_sasl_options_block auth_cyrus_sasl_option_defaults = {
 };
 
 
 };
 
 
+#ifdef MACRO_PREDEF
+
+/* Dummy values */
+void auth_cyrus_sasl_init(auth_instance *ablock) {}
+int auth_cyrus_sasl_server(auth_instance *ablock, uschar *data) {return 0;}
+int auth_cyrus_sasl_client(auth_instance *ablock, void * sx,
+  int timeout, uschar *buffer, int buffsize) {return 0;}
+void auth_cyrus_sasl_version_report(FILE *f) {}
+
+#else   /*!MACRO_PREDEF*/
+
+
+
+
 /*************************************************
 *          Initialization entry point            *
 *************************************************/
 /*************************************************
 *          Initialization entry point            *
 *************************************************/
@@ -97,20 +111,20 @@ auth_cyrus_sasl_init(auth_instance *ablock)
 {
 auth_cyrus_sasl_options_block *ob =
   (auth_cyrus_sasl_options_block *)(ablock->options_block);
 {
 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;
 char *realm_expanded;
 
 sasl_conn_t *conn;
 int rc, i;
 unsigned int len;
 uschar *rs_point, *expanded_hostname;
 char *realm_expanded;
 
 sasl_conn_t *conn;
-sasl_callback_t cbs[]={
+sasl_callback_t cbs[] = {
   {SASL_CB_GETOPT, NULL, NULL },
   {SASL_CB_LIST_END, NULL, NULL}};
 
 /* default the mechanism to our "public name" */
   {SASL_CB_GETOPT, NULL, NULL },
   {SASL_CB_LIST_END, NULL, NULL}};
 
 /* default the mechanism to our "public name" */
-if(ob->server_mech == NULL)
-  ob->server_mech=string_copy(ablock->public_name);
+if (ob->server_mech == NULL)
+  ob->server_mech = string_copy(ablock->public_name);
 
 expanded_hostname = expand_string(ob->server_hostname);
 if (expanded_hostname == NULL)
 
 expanded_hostname = expand_string(ob->server_hostname);
 if (expanded_hostname == NULL)
@@ -118,7 +132,7 @@ if (expanded_hostname == NULL)
       "couldn't expand server_hostname [%s]: %s",
       ablock->name, ob->server_hostname, expand_string_message);
 
       "couldn't expand server_hostname [%s]: %s",
       ablock->name, ob->server_hostname, expand_string_message);
 
-realm_expanded=NULL;
+realm_expanded = NULL;
 if (ob->server_realm != NULL) {
   realm_expanded = CS expand_string(ob->server_realm);
   if (realm_expanded == NULL)
 if (ob->server_realm != NULL) {
   realm_expanded = CS expand_string(ob->server_realm);
   if (realm_expanded == NULL)
@@ -134,45 +148,42 @@ if (ob->server_realm != NULL) {
 cbs[0].proc = (int(*)(void)) &mysasl_config;
 cbs[0].context = ob->server_mech;
 
 cbs[0].proc = (int(*)(void)) &mysasl_config;
 cbs[0].context = ob->server_mech;
 
-rc=sasl_server_init(cbs, "exim");
-
-if( rc != SASL_OK )
+if ((rc = sasl_server_init(cbs, "exim")) != SASL_OK )
   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:  "
       "couldn't initialise Cyrus SASL library.", ablock->name);
 
   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:  "
       "couldn't initialise Cyrus SASL library.", ablock->name);
 
-rc=sasl_server_new(CS ob->server_service, CS expanded_hostname,
-                   realm_expanded, NULL, NULL, NULL, 0, &conn);
-if( rc != SASL_OK )
+if ((rc = sasl_server_new(CS ob->server_service, CS expanded_hostname,
+                   realm_expanded, NULL, NULL, NULL, 0, &conn)) != SASL_OK )
   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:  "
       "couldn't initialise Cyrus SASL server connection.", ablock->name);
 
   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);
-if( rc != SASL_OK )
+if ((rc = sasl_listmech(conn, NULL, "", ":", "", (const char **)&list, &len, &i)) != SASL_OK )
   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:  "
       "couldn't get Cyrus SASL mechanism list.", ablock->name);
 
   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:  "
       "couldn't get Cyrus SASL mechanism list.", ablock->name);
 
-i=':';
-listptr=list;
+i = ':';
+listptr = list;
 
 
-HDEBUG(D_auth) {
+HDEBUG(D_auth)
+  {
   debug_printf("Initialised Cyrus SASL service=\"%s\" fqdn=\"%s\" realm=\"%s\"\n",
       ob->server_service, expanded_hostname, realm_expanded);
   debug_printf("Cyrus SASL knows mechanisms: %s\n", list);
   debug_printf("Initialised Cyrus SASL service=\"%s\" fqdn=\"%s\" realm=\"%s\"\n",
       ob->server_service, expanded_hostname, realm_expanded);
   debug_printf("Cyrus SASL knows mechanisms: %s\n", list);
-}
+  }
 
 /* the store_get / store_reset mechanism is hierarchical
  * the hierarchy is stored for us behind our back. This point
  * creates a hierarchy point for this function.
  */
 
 /* the store_get / store_reset mechanism is hierarchical
  * the hierarchy is stored for us behind our back. This point
  * creates a hierarchy point for this function.
  */
-rs_point=store_get(0);
+rs_point = store_get(0);
 
 /* loop until either we get to the end of the list, or we match the
  * public name of this authenticator
  */
 
 /* loop until either we get to the end of the list, or we match the
  * public name of this authenticator
  */
-while( ( buffer = string_nextinlist(&listptr, &i, NULL, 0) ) &&
+while ( ( buffer = string_nextinlist(&listptr, &i, NULL, 0) ) &&
        strcmpic(buffer,ob->server_mech) );
 
        strcmpic(buffer,ob->server_mech) );
 
-if(!buffer)
+if (!buffer)
   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:  "
       "Cyrus SASL doesn't know about mechanism %s.", ablock->name, ob->server_mech);
 
   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:  "
       "Cyrus SASL doesn't know about mechanism %s.", ablock->name, ob->server_mech);
 
@@ -204,54 +215,48 @@ auth_cyrus_sasl_options_block *ob =
   (auth_cyrus_sasl_options_block *)(ablock->options_block);
 uschar *output, *out2, *input, *clear, *hname;
 uschar *debug = NULL;   /* Stops compiler complaining */
   (auth_cyrus_sasl_options_block *)(ablock->options_block);
 uschar *output, *out2, *input, *clear, *hname;
 uschar *debug = NULL;   /* Stops compiler complaining */
-sasl_callback_t cbs[]={{SASL_CB_LIST_END, NULL, NULL}};
+sasl_callback_t cbs[] = {{SASL_CB_LIST_END, NULL, NULL}};
 sasl_conn_t *conn;
 sasl_conn_t *conn;
-char *realm_expanded;
-int rc, i, firsttime=1, clen, *negotiated_ssf_ptr=NULL, negotiated_ssf;
+char * realm_expanded = NULL;
+int rc, i, firsttime = 1, clen, *negotiated_ssf_ptr = NULL, negotiated_ssf;
 unsigned int inlen, outlen;
 
 unsigned int inlen, outlen;
 
-input=data;
-inlen=Ustrlen(data);
+input = data;
+inlen = Ustrlen(data);
 
 
-HDEBUG(D_auth) debug=string_copy(data);
+HDEBUG(D_auth) debug = string_copy(data);
 
 
-hname=expand_string(ob->server_hostname);
-realm_expanded=NULL;
+hname = expand_string(ob->server_hostname);
 if (hname && ob->server_realm)
 if (hname && ob->server_realm)
-  realm_expanded= CS expand_string(ob->server_realm);
-if((hname == NULL) ||
-   ((realm_expanded == NULL) && (ob->server_realm != NULL)))
+  realm_expanded = CS expand_string(ob->server_realm);
+if (!hname  ||  !realm_expanded  && ob->server_realm)
   {
   auth_defer_msg = expand_string_message;
   return DEFER;
   }
 
   {
   auth_defer_msg = expand_string_message;
   return DEFER;
   }
 
-if(inlen)
+if (inlen)
   {
   {
-  clen=auth_b64decode(input, &clear);
-  if(clen < 0)
-    {
+  if ((clen = b64decode(input, &clear)) < 0)
     return BAD64;
     return BAD64;
-    }
-  input=clear;
-  inlen=clen;
+  input = clear;
+  inlen = clen;
   }
 
   }
 
-rc=sasl_server_init(cbs, "exim");
-if (rc != SASL_OK)
+if ((rc = sasl_server_init(cbs, "exim")) != SASL_OK)
   {
   auth_defer_msg = US"couldn't initialise Cyrus SASL library";
   return DEFER;
   }
 
   {
   auth_defer_msg = US"couldn't initialise Cyrus SASL library";
   return DEFER;
   }
 
-rc=sasl_server_new(CS ob->server_service, CS hname, realm_expanded, NULL,
+rc = sasl_server_new(CS ob->server_service, CS hname, realm_expanded, NULL,
   NULL, NULL, 0, &conn);
 
 HDEBUG(D_auth)
   debug_printf("Initialised Cyrus SASL server connection; service=\"%s\" fqdn=\"%s\" realm=\"%s\"\n",
       ob->server_service, hname, realm_expanded);
 
   NULL, NULL, 0, &conn);
 
 HDEBUG(D_auth)
   debug_printf("Initialised Cyrus SASL server connection; service=\"%s\" fqdn=\"%s\" realm=\"%s\"\n",
       ob->server_service, hname, realm_expanded);
 
-ifrc != SASL_OK )
+if (rc != SASL_OK )
   {
   auth_defer_msg = US"couldn't initialise Cyrus SASL connection";
   sasl_done();
   {
   auth_defer_msg = US"couldn't initialise Cyrus SASL connection";
   sasl_done();
@@ -260,8 +265,7 @@ if( rc != SASL_OK )
 
 if (tls_in.cipher)
   {
 
 if (tls_in.cipher)
   {
-  rc = sasl_setprop(conn, SASL_SSF_EXTERNAL, (sasl_ssf_t *) &tls_in.bits);
-  if (rc != SASL_OK)
+  if ((rc = sasl_setprop(conn, SASL_SSF_EXTERNAL, (sasl_ssf_t *) &tls_in.bits)) != SASL_OK)
     {
     HDEBUG(D_auth) debug_printf("Cyrus SASL EXTERNAL SSF set %d failed: %s\n",
         tls_in.bits, sasl_errstring(rc, NULL, NULL));
     {
     HDEBUG(D_auth) debug_printf("Cyrus SASL EXTERNAL SSF set %d failed: %s\n",
         tls_in.bits, sasl_errstring(rc, NULL, NULL));
@@ -284,7 +288,7 @@ inet_ntop which we wrap in our host_ntoa() function.
 So the docs are too strict and we shouldn't worry about :: contractions. */
 
 /* Set properties for remote and local host-ip;port */
 So the docs are too strict and we shouldn't worry about :: contractions. */
 
 /* Set properties for remote and local host-ip;port */
-for (i=0; i < 2; ++i)
+for (i = 0; i < 2; ++i)
   {
   struct sockaddr_storage ss;
   int (*query)(int, struct sockaddr *, socklen_t *);
   {
   struct sockaddr_storage ss;
   int (*query)(int, struct sockaddr *, socklen_t *);
@@ -308,8 +312,7 @@ for (i=0; i < 2; ++i)
     }
 
   sslen = sizeof(ss);
     }
 
   sslen = sizeof(ss);
-  rc = query(fileno(smtp_in), (struct sockaddr *) &ss, &sslen);
-  if (rc < 0)
+  if ((rc = query(fileno(smtp_in), (struct sockaddr *) &ss, &sslen)) < 0)
     {
     HDEBUG(D_auth)
       debug_printf("Failed to get %s address information: %s\n",
     {
     HDEBUG(D_auth)
       debug_printf("Failed to get %s address information: %s\n",
@@ -320,8 +323,7 @@ for (i=0; i < 2; ++i)
   address = host_ntoa(-1, &ss, NULL, &port);
   address_port = string_sprintf("%s;%d", address, port);
 
   address = host_ntoa(-1, &ss, NULL, &port);
   address_port = string_sprintf("%s;%d", address, port);
 
-  rc = sasl_setprop(conn, propnum, address_port);
-  if (rc != SASL_OK)
+  if ((rc = sasl_setprop(conn, propnum, address_port)) != SASL_OK)
     {
     s_err = sasl_errdetail(conn);
     HDEBUG(D_auth)
     {
     s_err = sasl_errdetail(conn);
     HDEBUG(D_auth)
@@ -333,24 +335,21 @@ for (i=0; i < 2; ++i)
       label, address_port);
   }
 
       label, address_port);
   }
 
-rc=SASL_CONTINUE;
-
-while(rc==SASL_CONTINUE)
+for (rc = SASL_CONTINUE; rc == SASL_CONTINUE; )
   {
   {
-  if(firsttime)
+  if (firsttime)
     {
     {
-    firsttime=0;
+    firsttime = 0;
     HDEBUG(D_auth) debug_printf("Calling sasl_server_start(%s,\"%s\")\n", ob->server_mech, debug);
     HDEBUG(D_auth) debug_printf("Calling sasl_server_start(%s,\"%s\")\n", ob->server_mech, debug);
-    rc=sasl_server_start(conn, CS ob->server_mech, inlen?CS input:NULL, inlen,
+    rc = sasl_server_start(conn, CS ob->server_mech, inlen?CS input:NULL, inlen,
            (const char **)(&output), &outlen);
     }
   else
     {
     /* make sure that we have a null-terminated string */
            (const char **)(&output), &outlen);
     }
   else
     {
     /* make sure that we have a null-terminated string */
-    out2=store_get(outlen+1);
-    memcpy(out2,output,outlen);
-    out2[outlen]='\0';
-    if((rc=auth_get_data(&input, out2, outlen))!=OK)
+    out2 = string_copyn(output, outlen);
+
+    if ((rc = auth_get_data(&input, out2, outlen)) != OK)
       {
       /* we couldn't get the data, so free up the library before
        * returning whatever error we get */
       {
       /* we couldn't get the data, so free up the library before
        * returning whatever error we get */
@@ -358,133 +357,130 @@ while(rc==SASL_CONTINUE)
       sasl_done();
       return rc;
       }
       sasl_done();
       return rc;
       }
-    inlen=Ustrlen(input);
+    inlen = Ustrlen(input);
 
 
-    HDEBUG(D_auth) debug=string_copy(input);
-    if(inlen)
+    HDEBUG(D_auth) debug = string_copy(input);
+    if (inlen)
       {
       {
-      clen=auth_b64decode(input, &clear);
-      if(clen < 0)
+      if ((clen = b64decode(input, &clear)) < 0)
        {
        {
-        sasl_dispose(&conn);
-        sasl_done();
+       sasl_dispose(&conn);
+       sasl_done();
        return BAD64;
        }
        return BAD64;
        }
-      input=clear;
-      inlen=clen;
+      input = clear;
+      inlen = clen;
       }
 
     HDEBUG(D_auth) debug_printf("Calling sasl_server_step(\"%s\")\n", debug);
       }
 
     HDEBUG(D_auth) debug_printf("Calling sasl_server_step(\"%s\")\n", debug);
-    rc=sasl_server_step(conn, CS input, inlen, (const char **)(&output), &outlen);
+    rc = sasl_server_step(conn, CS input, inlen, (const char **)(&output), &outlen);
     }
     }
-  if(rc==SASL_BADPROT)
+
+  if (rc == SASL_BADPROT)
     {
     sasl_dispose(&conn);
     sasl_done();
     return UNEXPECTED;
     }
     {
     sasl_dispose(&conn);
     sasl_done();
     return UNEXPECTED;
     }
-  else if( rc==SASL_FAIL     || rc==SASL_BUFOVER
-       || rc==SASL_BADMAC   || rc==SASL_BADAUTH
-       || rc==SASL_NOAUTHZ  || rc==SASL_ENCRYPT
-       || rc==SASL_EXPIRED  || rc==SASL_DISABLED
-       || rc==SASL_NOUSER   )
+  if (rc == SASL_CONTINUE)
+    continue;
+
+  /* Get the username and copy it into $auth1 and $1. The former is now the
+  preferred variable; the latter is the original variable. */
+
+  if ((sasl_getprop(conn, SASL_USERNAME, (const void **)(&out2))) != SASL_OK)
     {
     {
-    /* these are considered permanent failure codes */
     HDEBUG(D_auth)
     HDEBUG(D_auth)
-      debug_printf("Cyrus SASL permanent failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
+      debug_printf("Cyrus SASL library will not tell us the username: %s\n",
+         sasl_errstring(rc, NULL, NULL));
     log_write(0, LOG_REJECT, "%s authenticator (%s):\n  "
     log_write(0, LOG_REJECT, "%s authenticator (%s):\n  "
-       "Cyrus SASL permanent failure: %s", ablock->name, ob->server_mech,
+       "Cyrus SASL username fetch problem: %s", ablock->name, ob->server_mech,
        sasl_errstring(rc, NULL, NULL));
     sasl_dispose(&conn);
     sasl_done();
     return FAIL;
     }
        sasl_errstring(rc, NULL, NULL));
     sasl_dispose(&conn);
     sasl_done();
     return FAIL;
     }
-  else if(rc==SASL_NOMECH)
-    {
-    /* this is a temporary failure, because the mechanism is not
-     * available for this user. If it wasn't available at all, we
-     * shouldn't have got here in the first place...
-     */
-    HDEBUG(D_auth)
-      debug_printf("Cyrus SASL temporary failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
-    auth_defer_msg =
-        string_sprintf("Cyrus SASL: mechanism %s not available", ob->server_mech);
-    sasl_dispose(&conn);
-    sasl_done();
-    return DEFER;
-    }
-  else if(!(rc==SASL_OK || rc==SASL_CONTINUE))
-    {
-    /* Anything else is a temporary failure, and we'll let SASL print out
-     * the error string for us
-     */
-    HDEBUG(D_auth)
-      debug_printf("Cyrus SASL temporary failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
-    auth_defer_msg =
-        string_sprintf("Cyrus SASL: %s", sasl_errstring(rc, NULL, NULL));
-    sasl_dispose(&conn);
-    sasl_done();
-    return DEFER;
-    }
-  else if(rc==SASL_OK)
+  auth_vars[0] = expand_nstring[1] = string_copy(out2);
+  expand_nlength[1] = Ustrlen(out2);
+  expand_nmax = 1;
+
+  switch (rc)
     {
     {
-    /* Get the username and copy it into $auth1 and $1. The former is now the
-    preferred variable; the latter is the original variable. */
-    rc = sasl_getprop(conn, SASL_USERNAME, (const void **)(&out2));
-    if (rc != SASL_OK)
-      {
+    case SASL_FAIL: case SASL_BUFOVER: case SASL_BADMAC: case SASL_BADAUTH:
+    case SASL_NOAUTHZ: case SASL_ENCRYPT: case SASL_EXPIRED:
+    case SASL_DISABLED: case SASL_NOUSER:
+      /* these are considered permanent failure codes */
       HDEBUG(D_auth)
       HDEBUG(D_auth)
-        debug_printf("Cyrus SASL library will not tell us the username: %s\n",
-            sasl_errstring(rc, NULL, NULL));
+       debug_printf("Cyrus SASL permanent failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
       log_write(0, LOG_REJECT, "%s authenticator (%s):\n  "
       log_write(0, LOG_REJECT, "%s authenticator (%s):\n  "
-         "Cyrus SASL username fetch problem: %s", ablock->name, ob->server_mech,
-         sasl_errstring(rc, NULL, NULL));
+        "Cyrus SASL permanent failure: %s", ablock->name, ob->server_mech,
+        sasl_errstring(rc, NULL, NULL));
       sasl_dispose(&conn);
       sasl_done();
       return FAIL;
       sasl_dispose(&conn);
       sasl_done();
       return FAIL;
-      }
-
-    auth_vars[0] = expand_nstring[1] = string_copy(out2);
-    expand_nlength[1] = Ustrlen(expand_nstring[1]);
-    expand_nmax = 1;
-
-    HDEBUG(D_auth)
-      debug_printf("Cyrus SASL %s authentication succeeded for %s\n",
-          ob->server_mech, auth_vars[0]);
 
 
-    rc = sasl_getprop(conn, SASL_SSF, (const void **)(&negotiated_ssf_ptr));
-    if (rc != SASL_OK)
-      {
+    case SASL_NOMECH:
+      /* this is a temporary failure, because the mechanism is not
+       * available for this user. If it wasn't available at all, we
+       * shouldn't have got here in the first place...
+       */
       HDEBUG(D_auth)
       HDEBUG(D_auth)
-        debug_printf("Cyrus SASL library will not tell us the SSF: %s\n",
-            sasl_errstring(rc, NULL, NULL));
-      log_write(0, LOG_REJECT, "%s authenticator (%s):\n  "
-          "Cyrus SASL SSF value not available: %s", ablock->name, ob->server_mech,
-          sasl_errstring(rc, NULL, NULL));
+       debug_printf("Cyrus SASL temporary failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
+      auth_defer_msg =
+         string_sprintf("Cyrus SASL: mechanism %s not available", ob->server_mech);
       sasl_dispose(&conn);
       sasl_done();
       sasl_dispose(&conn);
       sasl_done();
-      return FAIL;
-      }
-    negotiated_ssf = *negotiated_ssf_ptr;
-    HDEBUG(D_auth)
-      debug_printf("Cyrus SASL %s negotiated SSF: %d\n", ob->server_mech, negotiated_ssf);
-    if (negotiated_ssf > 0)
-      {
+      return DEFER;
+
+    case SASL_OK:
       HDEBUG(D_auth)
       HDEBUG(D_auth)
-        debug_printf("Exim does not implement SASL wrapping (needed for SSF %d).\n", negotiated_ssf);
-      log_write(0, LOG_REJECT, "%s authenticator (%s):\n  "
-          "Cyrus SASL SSF %d not supported by Exim", ablock->name, ob->server_mech, negotiated_ssf);
+       debug_printf("Cyrus SASL %s authentication succeeded for %s\n",
+           ob->server_mech, auth_vars[0]);
+
+      if ((rc = sasl_getprop(conn, SASL_SSF, (const void **)(&negotiated_ssf_ptr)))!= SASL_OK)
+       {
+       HDEBUG(D_auth)
+         debug_printf("Cyrus SASL library will not tell us the SSF: %s\n",
+             sasl_errstring(rc, NULL, NULL));
+       log_write(0, LOG_REJECT, "%s authenticator (%s):\n  "
+           "Cyrus SASL SSF value not available: %s", ablock->name, ob->server_mech,
+           sasl_errstring(rc, NULL, NULL));
+       sasl_dispose(&conn);
+       sasl_done();
+       return FAIL;
+       }
+      negotiated_ssf = *negotiated_ssf_ptr;
+      HDEBUG(D_auth)
+       debug_printf("Cyrus SASL %s negotiated SSF: %d\n", ob->server_mech, negotiated_ssf);
+      if (negotiated_ssf > 0)
+       {
+       HDEBUG(D_auth)
+         debug_printf("Exim does not implement SASL wrapping (needed for SSF %d).\n", negotiated_ssf);
+       log_write(0, LOG_REJECT, "%s authenticator (%s):\n  "
+           "Cyrus SASL SSF %d not supported by Exim", ablock->name, ob->server_mech, negotiated_ssf);
+       sasl_dispose(&conn);
+       sasl_done();
+       return FAIL;
+       }
+
+      /* close down the connection, freeing up library's memory */
       sasl_dispose(&conn);
       sasl_done();
       sasl_dispose(&conn);
       sasl_done();
-      return FAIL;
-      }
 
 
-    /* close down the connection, freeing up library's memory */
-    sasl_dispose(&conn);
-    sasl_done();
+      /* Expand server_condition as an authorization check */
+      return auth_check_serv_cond(ablock);
 
 
-    /* Expand server_condition as an authorization check */
-    return auth_check_serv_cond(ablock);
+    default:
+      /* Anything else is a temporary failure, and we'll let SASL print out
+       * the error string for us
+       */
+      HDEBUG(D_auth)
+       debug_printf("Cyrus SASL temporary failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
+      auth_defer_msg =
+         string_sprintf("Cyrus SASL: %s", sasl_errstring(rc, NULL, NULL));
+      sasl_dispose(&conn);
+      sasl_done();
+      return DEFER;
     }
   }
 /* NOTREACHED */
     }
   }
 /* NOTREACHED */
@@ -498,12 +494,12 @@ return 0;  /* Stop compiler complaints */
 void
 auth_cyrus_sasl_version_report(FILE *f)
 {
 void
 auth_cyrus_sasl_version_report(FILE *f)
 {
-  const char *implementation, *version;
-  sasl_version_info(&implementation, &version, NULL, NULL, NULL, NULL);
-  fprintf(f, "Library version: Cyrus SASL: Compile: %d.%d.%d\n"
-             "                             Runtime: %s [%s]\n",
-          SASL_VERSION_MAJOR, SASL_VERSION_MINOR, SASL_VERSION_STEP,
-          version, implementation);
+const char *implementation, *version;
+sasl_version_info(&implementation, &version, NULL, NULL, NULL, NULL);
+fprintf(f, "Library version: Cyrus SASL: Compile: %d.%d.%d\n"
+          "                             Runtime: %s [%s]\n",
+       SASL_VERSION_MAJOR, SASL_VERSION_MINOR, SASL_VERSION_STEP,
+       version, implementation);
 }
 
 /*************************************************
 }
 
 /*************************************************
@@ -515,8 +511,7 @@ auth_cyrus_sasl_version_report(FILE *f)
 int
 auth_cyrus_sasl_client(
   auth_instance *ablock,                 /* authenticator block */
 int
 auth_cyrus_sasl_client(
   auth_instance *ablock,                 /* authenticator block */
-  smtp_inblock *inblock,                 /* input connection */
-  smtp_outblock *outblock,               /* output connection */
+  void * sx,                            /* connexction */
   int timeout,                           /* command timeout */
   uschar *buffer,                          /* for reading response */
   int buffsize)                          /* size of buffer */
   int timeout,                           /* command timeout */
   uschar *buffer,                          /* for reading response */
   int buffsize)                          /* size of buffer */
@@ -525,6 +520,7 @@ auth_cyrus_sasl_client(
 return FAIL;
 }
 
 return FAIL;
 }
 
+#endif   /*!MACRO_PREDEF*/
 #endif  /* AUTH_CYRUS_SASL */
 
 /* End of cyrus_sasl.c */
 #endif  /* AUTH_CYRUS_SASL */
 
 /* End of cyrus_sasl.c */
index 8481054..da6f3cd 100644 (file)
@@ -29,8 +29,7 @@ extern auth_cyrus_sasl_options_block auth_cyrus_sasl_option_defaults;
 
 extern void auth_cyrus_sasl_init(auth_instance *);
 extern int auth_cyrus_sasl_server(auth_instance *, uschar *);
 
 extern void auth_cyrus_sasl_init(auth_instance *);
 extern int auth_cyrus_sasl_server(auth_instance *, uschar *);
-extern int auth_cyrus_sasl_client(auth_instance *, smtp_inblock *,
-                                smtp_outblock *, int, uschar *, int);
+extern int auth_cyrus_sasl_client(auth_instance *, void *, int, uschar *, int);
 extern void auth_cyrus_sasl_version_report(FILE *f);
 
 /* End of cyrus_sasl.h */
 extern void auth_cyrus_sasl_version_report(FILE *f);
 
 /* End of cyrus_sasl.h */
index 1874f32..b1dde06 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * Copyright (c) 2004 Andrey Panin <pazke@donpac.ru>
 /*
  * Copyright (c) 2004 Andrey Panin <pazke@donpac.ru>
- * Copyright (c) 2006-2014 The Exim Maintainers
+ * Copyright (c) 2006-2017 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
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published
@@ -71,6 +71,19 @@ auth_dovecot_options_block auth_dovecot_option_defaults = {
 };
 
 
 };
 
 
+
+
+#ifdef MACRO_PREDEF
+
+/* Dummy values */
+void auth_dovecot_init(auth_instance *ablock) {}
+int auth_dovecot_server(auth_instance *ablock, uschar *data) {return 0;}
+int auth_dovecot_client(auth_instance *ablock, void * sx,
+  int timeout, uschar *buffer, int buffsize) {return 0;}
+
+#else   /*!MACRO_PREDEF*/
+
+
 /* Static variables for reading from the socket */
 
 static uschar sbuffer[256];
 /* Static variables for reading from the socket */
 
 static uschar sbuffer[256];
@@ -228,232 +241,273 @@ return s;
 *              Server entry point                *
 *************************************************/
 
 *              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;
+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;
+  }
 
 
-       if (connect(fd, (struct sockaddr *) &sa, sizeof(sa)) < 0)
-               goto out;
+memset(&sa, 0, sizeof(sa));
+sa.sun_family = AF_UNIX;
 
 
-       auth_defer_msg = US"authentication socket protocol error";
+/* 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 */
 
 
-       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;
-               }
-       }
-
-       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 ? CS  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:
 
 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;
 }
 }
+
+
+#endif   /*!MACRO_PREDEF*/
index a121bde..7d974ab 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "../exim.h"
 /* 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;
 {
 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", FALSE, 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;
   {
   if (p >= big_buffer_size - 1) return BAD64;
   big_buffer[p++] = c;
index d3ffe08..a019756 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2012 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "../exim.h"
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "../exim.h"
@@ -31,8 +31,8 @@ auth_get_no64_data(uschar **aptr, uschar *challenge)
 {
 int c;
 int p = 0;
 {
 int c;
 int p = 0;
-smtp_printf("334 %s\r\n", challenge);
-while ((c = receive_getc()) != '\n' && c != EOF)
+smtp_printf("334 %s\r\n", FALSE, challenge);
+while ((c = receive_getc(GETC_BUFFER_UNLIMITED)) != '\n' && c != EOF)
   {
   if (p >= big_buffer_size - 1) return BAD64;
   big_buffer[p++] = c;
   {
   if (p >= big_buffer_size - 1) return BAD64;
   big_buffer[p++] = c;
index 87be9b5..da833d5 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2012 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Copyright (c) Twitter Inc 2012
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Copyright (c) Twitter Inc 2012
@@ -78,6 +78,20 @@ auth_gsasl_options_block auth_gsasl_option_defaults = {
   FALSE                     /* server_channelbinding */
 };
 
   FALSE                     /* server_channelbinding */
 };
 
+
+#ifdef MACRO_PREDEF
+
+/* Dummy values */
+void auth_gsasl_init(auth_instance *ablock) {}
+int auth_gsasl_server(auth_instance *ablock, uschar *data) {return 0;}
+int auth_gsasl_client(auth_instance *ablock, smtp_inblock * sx,
+  int timeout, uschar *buffer, int buffsize) {return 0;}
+void auth_gsasl_version_report(FILE *f) {}
+
+#else   /*!MACRO_PREDEF*/
+
+
+
 /* "Globals" for managing the gsasl interface. */
 
 static Gsasl *gsasl_ctx = NULL;
 /* "Globals" for managing the gsasl interface. */
 
 static Gsasl *gsasl_ctx = NULL;
@@ -143,7 +157,7 @@ auth_gsasl_init(auth_instance *ablock)
               ablock->name,  gsasl_strerror_name(rc), gsasl_strerror(rc));
   HDEBUG(D_auth) debug_printf("GNU SASL supports: %s\n", p);
 
               ablock->name,  gsasl_strerror_name(rc), gsasl_strerror(rc));
   HDEBUG(D_auth) debug_printf("GNU SASL supports: %s\n", p);
 
-  supported = gsasl_client_support_p(gsasl_ctx, (const char *)ob->server_mech);
+  supported = gsasl_client_support_p(gsasl_ctx, CCS ob->server_mech);
   if (!supported)
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:  "
               "GNU SASL does not support mechanism \"%s\"",
   if (!supported)
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:  "
               "GNU SASL does not support mechanism \"%s\"",
@@ -244,7 +258,7 @@ auth_gsasl_server(auth_instance *ablock, uschar *initial_data)
     debug_printf("GNU SASL: initialising session for %s, mechanism %s.\n",
         ablock->name, ob->server_mech);
 
     debug_printf("GNU SASL: initialising session for %s, mechanism %s.\n",
         ablock->name, ob->server_mech);
 
-  rc = gsasl_server_start(gsasl_ctx, (const char *)ob->server_mech, &sctx);
+  rc = gsasl_server_start(gsasl_ctx, CCS ob->server_mech, &sctx);
   if (rc != GSASL_OK) {
     auth_defer_msg = string_sprintf("GNU SASL: session start failure: %s (%s)",
         gsasl_strerror_name(rc), gsasl_strerror(rc));
   if (rc != GSASL_OK) {
     auth_defer_msg = string_sprintf("GNU SASL: session start failure: %s (%s)",
         gsasl_strerror_name(rc), gsasl_strerror(rc));
@@ -272,10 +286,10 @@ auth_gsasl_server(auth_instance *ablock, uschar *initial_data)
   gsasl_property_set(sctx, GSASL_QOPS, "qop-auth");
 #ifdef SUPPORT_TLS
   if (tls_channelbinding_b64) {
   gsasl_property_set(sctx, GSASL_QOPS, "qop-auth");
 #ifdef SUPPORT_TLS
   if (tls_channelbinding_b64) {
-    /* Some auth mechanisms can ensure that both sides are talking withing the
+    /* Some auth mechanisms can ensure that both sides are talking within 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
     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,
     proxying data on within a second, authentication will fail.
 
     We might not have this available, depending upon TLS implementation,
@@ -294,7 +308,7 @@ auth_gsasl_server(auth_instance *ablock, uschar *initial_data)
       HDEBUG(D_auth) debug_printf("Auth %s: Enabling channel-binding\n",
           ablock->name);
       gsasl_property_set(sctx, GSASL_CB_TLS_UNIQUE,
       HDEBUG(D_auth) debug_printf("Auth %s: Enabling channel-binding\n",
           ablock->name);
       gsasl_property_set(sctx, GSASL_CB_TLS_UNIQUE,
-          (const char *) tls_channelbinding_b64);
+          CCS  tls_channelbinding_b64);
     } else {
       HDEBUG(D_auth)
         debug_printf("Auth %s: Not enabling channel-binding (data available)\n",
     } else {
       HDEBUG(D_auth)
         debug_printf("Auth %s: Not enabling channel-binding (data available)\n",
@@ -355,7 +369,7 @@ auth_gsasl_server(auth_instance *ablock, uschar *initial_data)
     if ((rc == GSASL_NEEDS_MORE) ||
         (to_send && *to_send))
       exim_error =
     if ((rc == GSASL_NEEDS_MORE) ||
         (to_send && *to_send))
       exim_error =
-        auth_get_no64_data((uschar **)&received, (uschar *)to_send);
+        auth_get_no64_data((uschar **)&received, US to_send);
 
     if (to_send) {
       free(to_send);
 
     if (to_send) {
       free(to_send);
@@ -435,11 +449,11 @@ server_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_insta
   switch (prop) {
     case GSASL_VALIDATE_SIMPLE:
       /* GSASL_AUTHID, GSASL_AUTHZID, and GSASL_PASSWORD */
   switch (prop) {
     case GSASL_VALIDATE_SIMPLE:
       /* GSASL_AUTHID, GSASL_AUTHZID, and GSASL_PASSWORD */
-      propval = (uschar *) gsasl_property_fast(sctx, GSASL_AUTHID);
+      propval = US  gsasl_property_fast(sctx, GSASL_AUTHID);
       auth_vars[0] = expand_nstring[1] = propval ? propval : US"";
       auth_vars[0] = expand_nstring[1] = propval ? propval : US"";
-      propval = (uschar *) gsasl_property_fast(sctx, GSASL_AUTHZID);
+      propval = US  gsasl_property_fast(sctx, GSASL_AUTHZID);
       auth_vars[1] = expand_nstring[2] = propval ? propval : US"";
       auth_vars[1] = expand_nstring[2] = propval ? propval : US"";
-      propval = (uschar *) gsasl_property_fast(sctx, GSASL_PASSWORD);
+      propval = US  gsasl_property_fast(sctx, GSASL_PASSWORD);
       auth_vars[2] = expand_nstring[3] = propval ? propval : US"";
       expand_nmax = 3;
       for (i = 1; i <= 3; ++i)
       auth_vars[2] = expand_nstring[3] = propval ? propval : US"";
       expand_nmax = 3;
       for (i = 1; i <= 3; ++i)
@@ -455,7 +469,7 @@ server_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_insta
         cbrc = GSASL_AUTHENTICATION_ERROR;
         break;
       }
         cbrc = GSASL_AUTHENTICATION_ERROR;
         break;
       }
-      propval = (uschar *) gsasl_property_fast(sctx, GSASL_AUTHZID);
+      propval = US  gsasl_property_fast(sctx, GSASL_AUTHZID);
       /* We always set $auth1, even if only to empty string. */
       auth_vars[0] = expand_nstring[1] = propval ? propval : US"";
       expand_nlength[1] = Ustrlen(expand_nstring[1]);
       /* We always set $auth1, even if only to empty string. */
       auth_vars[0] = expand_nstring[1] = propval ? propval : US"";
       expand_nlength[1] = Ustrlen(expand_nstring[1]);
@@ -472,7 +486,7 @@ server_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_insta
         cbrc = GSASL_AUTHENTICATION_ERROR;
         break;
       }
         cbrc = GSASL_AUTHENTICATION_ERROR;
         break;
       }
-      propval = (uschar *) gsasl_property_fast(sctx, GSASL_ANONYMOUS_TOKEN);
+      propval = US  gsasl_property_fast(sctx, GSASL_ANONYMOUS_TOKEN);
       /* We always set $auth1, even if only to empty string. */
       auth_vars[0] = expand_nstring[1] = propval ? propval : US"";
       expand_nlength[1] = Ustrlen(expand_nstring[1]);
       /* We always set $auth1, even if only to empty string. */
       auth_vars[0] = expand_nstring[1] = propval ? propval : US"";
       expand_nlength[1] = Ustrlen(expand_nstring[1]);
@@ -493,9 +507,9 @@ server_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_insta
       First coding, we had these values swapped, but for consistency and prior
       to the first release of Exim with this authenticator, they've been
       switched to match the ordering of GSASL_VALIDATE_SIMPLE. */
       First coding, we had these values swapped, but for consistency and prior
       to the first release of Exim with this authenticator, they've been
       switched to match the ordering of GSASL_VALIDATE_SIMPLE. */
-      propval = (uschar *) gsasl_property_fast(sctx, GSASL_GSSAPI_DISPLAY_NAME);
+      propval = US  gsasl_property_fast(sctx, GSASL_GSSAPI_DISPLAY_NAME);
       auth_vars[0] = expand_nstring[1] = propval ? propval : US"";
       auth_vars[0] = expand_nstring[1] = propval ? propval : US"";
-      propval = (uschar *) gsasl_property_fast(sctx, GSASL_AUTHZID);
+      propval = US  gsasl_property_fast(sctx, GSASL_AUTHZID);
       auth_vars[1] = expand_nstring[2] = propval ? propval : US"";
       expand_nmax = 2;
       for (i = 1; i <= 2; ++i)
       auth_vars[1] = expand_nstring[2] = propval ? propval : US"";
       expand_nmax = 2;
       for (i = 1; i <= 2; ++i)
@@ -528,11 +542,11 @@ server_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_insta
       a new mechanism is added to the library.  It *shouldn't* result in us
       needing to add more glue, since avoiding that is a large part of the
       point of SASL. */
       a new mechanism is added to the library.  It *shouldn't* result in us
       needing to add more glue, since avoiding that is a large part of the
       point of SASL. */
-      propval = (uschar *) gsasl_property_fast(sctx, GSASL_AUTHID);
+      propval = US  gsasl_property_fast(sctx, GSASL_AUTHID);
       auth_vars[0] = expand_nstring[1] = propval ? propval : US"";
       auth_vars[0] = expand_nstring[1] = propval ? propval : US"";
-      propval = (uschar *) gsasl_property_fast(sctx, GSASL_AUTHZID);
+      propval = US  gsasl_property_fast(sctx, GSASL_AUTHZID);
       auth_vars[1] = expand_nstring[2] = propval ? propval : US"";
       auth_vars[1] = expand_nstring[2] = propval ? propval : US"";
-      propval = (uschar *) gsasl_property_fast(sctx, GSASL_REALM);
+      propval = US  gsasl_property_fast(sctx, GSASL_REALM);
       auth_vars[2] = expand_nstring[3] = propval ? propval : US"";
       expand_nmax = 3;
       for (i = 1; i <= 3; ++i)
       auth_vars[2] = expand_nstring[3] = propval ? propval : US"";
       expand_nmax = 3;
       for (i = 1; i <= 3; ++i)
@@ -540,7 +554,7 @@ server_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_insta
 
       tmps = CS expand_string(ob->server_password);
       if (tmps == NULL) {
 
       tmps = CS expand_string(ob->server_password);
       if (tmps == NULL) {
-        sasl_error_should_defer = expand_string_forcedfail ? FALSE : TRUE;
+        sasl_error_should_defer = f.expand_string_forcedfail ? FALSE : TRUE;
         HDEBUG(D_auth) debug_printf("server_password expansion failed, so "
             "can't tell GNU SASL library the password for %s\n", auth_vars[0]);
         return GSASL_AUTHENTICATION_ERROR;
         HDEBUG(D_auth) debug_printf("server_password expansion failed, so "
             "can't tell GNU SASL library the password for %s\n", auth_vars[0]);
         return GSASL_AUTHENTICATION_ERROR;
@@ -573,12 +587,11 @@ server_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_insta
 
 int
 auth_gsasl_client(
 
 int
 auth_gsasl_client(
-  auth_instance *ablock,                 /* authenticator block */
-  smtp_inblock *inblock,                 /* connection inblock */
-  smtp_outblock *outblock,               /* connection outblock */
-  int timeout,                           /* command timeout */
-  uschar *buffer,                        /* buffer for reading response */
-  int buffsize)                          /* size of buffer */
+  auth_instance *ablock,               /* authenticator block */
+  smtp_inblock * sx,                   /* connection */
+  int timeout,                         /* command timeout */
+  uschar *buffer,                      /* buffer for reading response */
+  int buffsize)                                /* size of buffer */
 {
   HDEBUG(D_auth)
     debug_printf("Client side NOT IMPLEMENTED: you should not see this!\n");
 {
   HDEBUG(D_auth)
     debug_printf("Client side NOT IMPLEMENTED: you should not see this!\n");
@@ -614,6 +627,7 @@ auth_gsasl_version_report(FILE *f)
           GSASL_VERSION, runtime);
 }
 
           GSASL_VERSION, runtime);
 }
 
+#endif   /*!MACRO_PREDEF*/
 #endif  /* AUTH_GSASL */
 
 /* End of gsasl_exim.c */
 #endif  /* AUTH_GSASL */
 
 /* End of gsasl_exim.c */
index 785b853..8842165 100644 (file)
@@ -36,7 +36,7 @@ extern auth_gsasl_options_block auth_gsasl_option_defaults;
 extern void auth_gsasl_init(auth_instance *);
 extern int auth_gsasl_server(auth_instance *, uschar *);
 extern int auth_gsasl_client(auth_instance *, smtp_inblock *,
 extern void auth_gsasl_init(auth_instance *);
 extern int auth_gsasl_server(auth_instance *, uschar *);
 extern int auth_gsasl_client(auth_instance *, smtp_inblock *,
-                                smtp_outblock *, int, uschar *, int);
+                               int, uschar *, int);
 extern void auth_gsasl_version_report(FILE *f);
 
 /* End of gsasl_exim.h */
 extern void auth_gsasl_version_report(FILE *f);
 
 /* End of gsasl_exim.h */
index 21ed75b..11a7d39 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2012 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Copyright (c) Twitter Inc 2012
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Copyright (c) Twitter Inc 2012
@@ -76,6 +76,20 @@ auth_heimdal_gssapi_options_block auth_heimdal_gssapi_option_defaults = {
   US"smtp",                 /* server_service */
 };
 
   US"smtp",                 /* server_service */
 };
 
+
+#ifdef MACRO_PREDEF
+
+/* Dummy values */
+void auth_heimdal_gssapi_init(auth_instance *ablock) {}
+int auth_heimdal_gssapi_server(auth_instance *ablock, uschar *data) {return 0;}
+int auth_heimdal_gssapi_client(auth_instance *ablock, void * sx,
+  int timeout, uschar *buffer, int buffsize) {return 0;}
+void auth_heimdal_gssapi_version_report(FILE *f) {}
+
+#else   /*!MACRO_PREDEF*/
+
+
+
 /* "Globals" for managing the heimdal_gssapi interface. */
 
 /* Utility functions */
 /* "Globals" for managing the heimdal_gssapi interface. */
 
 /* Utility functions */
@@ -320,7 +334,7 @@ auth_heimdal_gssapi_server(auth_instance *ablock, uschar *initial_data)
         break;
 
       case 1:
         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;
         if (gclient) {
           maj_stat = gss_release_name(&min_stat, &gclient);
           gclient = GSS_C_NO_NAME;
@@ -343,7 +357,7 @@ auth_heimdal_gssapi_server(auth_instance *ablock, uschar *initial_data)
           error_out = FAIL;
           goto ERROR_OUT;
         }
           error_out = FAIL;
           goto ERROR_OUT;
         }
-        if (&gbufdesc_out.length != 0) {
+        if (gbufdesc_out.length != 0) {
           error_out = auth_get_data(&from_client,
               gbufdesc_out.value, gbufdesc_out.length);
           if (error_out != OK)
           error_out = auth_get_data(&from_client,
               gbufdesc_out.value, gbufdesc_out.length);
           if (error_out != OK)
@@ -400,7 +414,7 @@ auth_heimdal_gssapi_server(auth_instance *ablock, uschar *initial_data)
         break;
 
       case 3:
         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 */
         maj_stat = gss_unwrap(&min_stat,
             gcontext,
             &gbufdesc_in,       /* data from client */
@@ -520,31 +534,30 @@ exim_gssapi_error_defer(uschar *store_reset_point,
     const char *format, ...)
 {
   va_list ap;
     const char *format, ...)
 {
   va_list ap;
-  uschar buffer[STRING_SPRINTF_BUFFER_SIZE];
   OM_uint32 maj_stat, min_stat;
   OM_uint32 msgcontext = 0;
   gss_buffer_desc status_string;
   OM_uint32 maj_stat, min_stat;
   OM_uint32 msgcontext = 0;
   gss_buffer_desc status_string;
+  gstring * g;
 
 
-  va_start(ap, format);
-  if (!string_vformat(buffer, sizeof(buffer), format, ap))
-    log_write(0, LOG_MAIN|LOG_PANIC_DIE,
-        "exim_gssapi_error_defer expansion larger than %lu",
-        sizeof(buffer));
-  va_end(ap);
+  HDEBUG(D_auth)
+    {
+    va_start(ap, format);
+    g = string_vformat(NULL, TRUE, format, ap);
+    va_end(ap);
+    }
 
   auth_defer_msg = NULL;
 
   do {
     maj_stat = gss_display_status(&min_stat,
 
   auth_defer_msg = NULL;
 
   do {
     maj_stat = gss_display_status(&min_stat,
-        major, GSS_C_GSS_CODE, GSS_C_NO_OID,
-        &msgcontext, &status_string);
+        major, GSS_C_GSS_CODE, GSS_C_NO_OID, &msgcontext, &status_string);
 
 
-    if (auth_defer_msg == NULL) {
+    if (!auth_defer_msg)
       auth_defer_msg = string_copy(US status_string.value);
       auth_defer_msg = string_copy(US status_string.value);
-    }
 
     HDEBUG(D_auth) debug_printf("heimdal %s: %.*s\n",
 
     HDEBUG(D_auth) debug_printf("heimdal %s: %.*s\n",
-        buffer, (int)status_string.length, CS status_string.value);
+        string_from_gstring(g), (int)status_string.length,
+       CS status_string.value);
     gss_release_buffer(&min_stat, &status_string);
 
   } while (msgcontext != 0);
     gss_release_buffer(&min_stat, &status_string);
 
   } while (msgcontext != 0);
@@ -564,8 +577,7 @@ exim_gssapi_error_defer(uschar *store_reset_point,
 int
 auth_heimdal_gssapi_client(
   auth_instance *ablock,                 /* authenticator block */
 int
 auth_heimdal_gssapi_client(
   auth_instance *ablock,                 /* authenticator block */
-  smtp_inblock *inblock,                 /* connection inblock */
-  smtp_outblock *outblock,               /* connection outblock */
+  void * sx,                            /* connection */
   int timeout,                           /* command timeout */
   uschar *buffer,                        /* buffer for reading response */
   int buffsize)                          /* size of buffer */
   int timeout,                           /* command timeout */
   uschar *buffer,                        /* buffer for reading response */
   int buffsize)                          /* size of buffer */
@@ -590,6 +602,7 @@ auth_heimdal_gssapi_version_report(FILE *f)
           heimdal_version, heimdal_long_version);
 }
 
           heimdal_version, heimdal_long_version);
 }
 
+#endif   /*!MACRO_PREDEF*/
 #endif  /* AUTH_HEIMDAL_GSSAPI */
 
 /* End of heimdal_gssapi.c */
 #endif  /* AUTH_HEIMDAL_GSSAPI */
 
 /* End of heimdal_gssapi.c */
index 25655e9..8accdb9 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 #ifndef STAND_ALONE
 /* See the file NOTICE for conditions of use and distribution. */
 
 #ifndef STAND_ALONE
@@ -334,7 +334,7 @@ int main(void)
 {
 md5 base;
 int i = 0x01020304;
 {
 md5 base;
 int i = 0x01020304;
-uschar *ctest = (uschar *)(&i);
+uschar *ctest = US (&i);
 uschar buffer[256];
 uschar digest[16];
 printf("Checking md5: %s-endian\n", (ctest[0] == 0x04)? "little" : "big");
 uschar buffer[256];
 uschar digest[16];
 printf("Checking md5: %s-endian\n", (ctest[0] == 0x04)? "little" : "big");
index ff449e5..7a0f788 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "../exim.h"
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "../exim.h"
@@ -35,6 +35,18 @@ auth_plaintext_options_block auth_plaintext_option_defaults = {
 };
 
 
 };
 
 
+#ifdef MACRO_PREDEF
+
+/* Dummy values */
+void auth_plaintext_init(auth_instance *ablock) {}
+int auth_plaintext_server(auth_instance *ablock, uschar *data) {return 0;}
+int auth_plaintext_client(auth_instance *ablock, void * sx, int timeout,
+    uschar *buffer, int buffsize) {return 0;}
+
+#else   /*!MACRO_PREDEF*/
+
+
+
 /*************************************************
 *          Initialization entry point            *
 *************************************************/
 /*************************************************
 *          Initialization entry point            *
 *************************************************/
@@ -66,7 +78,7 @@ auth_plaintext_server(auth_instance *ablock, uschar *data)
 {
 auth_plaintext_options_block *ob =
   (auth_plaintext_options_block *)(ablock->options_block);
 {
 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;
 uschar *clear, *end, *s;
 int number = 1;
 int len, rc;
@@ -76,7 +88,7 @@ int sep = 0;
 
 if (prompts != NULL)
   {
 
 if (prompts != NULL)
   {
-  prompts = expand_string(prompts);
+  prompts = expand_cstring(prompts);
   if (prompts == NULL)
     {
     auth_defer_msg = expand_string_message;
   if (prompts == NULL)
     {
     auth_defer_msg = expand_string_message;
@@ -99,7 +111,7 @@ if (*data != 0)
     }
   else
     {
     }
   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)
       {
     end = clear + len;
     while (clear < end && expand_nmax < EXPAND_MAXN)
       {
@@ -121,7 +133,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 (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 */
   end = clear + len;
 
   /* This loop must run at least once, in case the length is zero */
@@ -155,15 +167,14 @@ return auth_check_serv_cond(ablock);
 int
 auth_plaintext_client(
   auth_instance *ablock,                 /* authenticator block */
 int
 auth_plaintext_client(
   auth_instance *ablock,                 /* authenticator block */
-  smtp_inblock *inblock,                 /* connection inblock */
-  smtp_outblock *outblock,               /* connection outblock */
+  void * sx,                            /* smtp connextion */
   int timeout,                           /* command timeout */
   uschar *buffer,                        /* buffer for reading response */
   int buffsize)                          /* size of buffer */
 {
 auth_plaintext_options_block *ob =
   (auth_plaintext_options_block *)(ablock->options_block);
   int timeout,                           /* command timeout */
   uschar *buffer,                        /* buffer for reading response */
   int buffsize)                          /* size of buffer */
 {
 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;
 uschar *s;
 BOOL first = TRUE;
 int sep = 0;
@@ -173,7 +184,7 @@ int auth_var_idx = 0;
 sent one by one. The first one is sent with the AUTH command; the remainder are
 sent in response to subsequent prompts. Each is expanded before being sent. */
 
 sent one by one. The first one is sent with the AUTH command; the remainder are
 sent in response to subsequent prompts. Each is expanded before being sent. */
 
-while ((s = string_nextinlist(&text, &sep, big_buffer, big_buffer_size)) != NULL)
+while ((s = string_nextinlist(&text, &sep, big_buffer, big_buffer_size)))
   {
   int i, len, clear_len;
   uschar *ss = expand_string(s);
   {
   int i, len, clear_len;
   uschar *ss = expand_string(s);
@@ -184,15 +195,15 @@ while ((s = string_nextinlist(&text, &sep, big_buffer, big_buffer_size)) != NULL
   sending a line containing "*". Save the failed expansion string, because it
   is in big_buffer, and that gets used by the sending function. */
 
   sending a line containing "*". Save the failed expansion string, because it
   is in big_buffer, and that gets used by the sending function. */
 
-  if (ss == NULL)
+  if (!ss)
     {
     uschar *ssave = string_copy(s);
     if (!first)
       {
     {
     uschar *ssave = string_copy(s);
     if (!first)
       {
-      if (smtp_write_command(outblock, FALSE, "*\r\n") >= 0)
-        (void) smtp_read_response(inblock, US buffer, buffsize, '2', timeout);
+      if (smtp_write_command(sx, SCMD_FLUSH, "*\r\n") >= 0)
+        (void) smtp_read_response(sx, US buffer, buffsize, '2', timeout);
       }
       }
-    if (expand_string_forcedfail)
+    if (f.expand_string_forcedfail)
       {
       *buffer = 0;       /* No message */
       return CANCELLED;
       {
       *buffer = 0;       /* No message */
       return CANCELLED;
@@ -208,33 +219,29 @@ while ((s = string_nextinlist(&text, &sep, big_buffer, big_buffer_size)) != NULL
   needed for the PLAIN mechanism. It must be doubled if really needed. */
 
   for (i = 0; i < len; i++)
   needed for the PLAIN mechanism. It must be doubled if really needed. */
 
   for (i = 0; i < len; i++)
-    {
     if (ss[i] == '^')
     if (ss[i] == '^')
-      {
-      if (ss[i+1] != '^') ss[i] = 0; else
+      if (ss[i+1] != '^')
+       ss[i] = 0;
+      else
         {
         i++;
         len--;
         memmove(ss + i, ss + i + 1, len - i);
         }
         {
         i++;
         len--;
         memmove(ss + i, ss + i + 1, len - i);
         }
-      }
-    }
 
   /* The first string is attached to the AUTH command; others are sent
 
   /* The first string is attached to the AUTH command; others are sent
-  unembelished. */
+  unembellished. */
 
   if (first)
     {
     first = FALSE;
 
   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)
+    if (smtp_write_command(sx, SCMD_FLUSH, "AUTH %s%s%s\r\n",
+         ablock->public_name, len == 0 ? "" : " ", b64encode(ss, len)) < 0)
       return FAIL_SEND;
     }
   else
     {
       return FAIL_SEND;
     }
   else
     {
-    if (smtp_write_command(outblock, FALSE, "%s\r\n",
-          auth_b64encode(ss, len)) < 0)
+    if (smtp_write_command(sx, SCMD_FLUSH, "%s\r\n", b64encode(ss, len)) < 0)
       return FAIL_SEND;
     }
 
       return FAIL_SEND;
     }
 
@@ -242,7 +249,7 @@ while ((s = string_nextinlist(&text, &sep, big_buffer, big_buffer_size)) != NULL
   has succeeded. There may be more data to send, but is there any point
   in provoking an error here? */
 
   has succeeded. There may be more data to send, but is there any point
   in provoking an error here? */
 
-  if (smtp_read_response(inblock, US buffer, buffsize, '2', timeout)) return OK;
+  if (smtp_read_response(sx, US buffer, buffsize, '2', timeout)) return OK;
 
   /* Not a success response. If errno != 0 there is some kind of transmission
   error. Otherwise, check the response code in the buffer. If it starts with
 
   /* Not a success response. If errno != 0 there is some kind of transmission
   error. Otherwise, check the response code in the buffer. If it starts with
@@ -253,10 +260,10 @@ while ((s = string_nextinlist(&text, &sep, big_buffer, big_buffer_size)) != NULL
   /* If there is no more data to send, we have to cancel the authentication
   exchange and return ERROR. */
 
   /* If there is no more data to send, we have to cancel the authentication
   exchange and return ERROR. */
 
-  if (text == NULL)
+  if (!text)
     {
     {
-    if (smtp_write_command(outblock, FALSE, "*\r\n") >= 0)
-      (void)smtp_read_response(inblock, US buffer, buffsize, '2', timeout);
+    if (smtp_write_command(sx, SCMD_FLUSH, "*\r\n") >= 0)
+      (void)smtp_read_response(sx, US buffer, buffsize, '2', timeout);
     string_format(buffer, buffsize, "Too few items in client_send in %s "
       "authenticator", ablock->name);
     return ERROR;
     string_format(buffer, buffsize, "Too few items in client_send in %s "
       "authenticator", ablock->name);
     return ERROR;
@@ -265,7 +272,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. */
 
   /* 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_
 
   /* If decoding failed, the default is to terminate the authentication, and
   return FAIL, with the SMTP response still in the buffer. However, if client_
@@ -277,8 +284,8 @@ while ((s = string_nextinlist(&text, &sep, big_buffer, big_buffer_size)) != NULL
     uschar *save_bad = string_copy(buffer);
     if (!ob->client_ignore_invalid_base64)
       {
     uschar *save_bad = string_copy(buffer);
     if (!ob->client_ignore_invalid_base64)
       {
-      if (smtp_write_command(outblock, FALSE, "*\r\n") >= 0)
-        (void)smtp_read_response(inblock, US buffer, buffsize, '2', timeout);
+      if (smtp_write_command(sx, SCMD_FLUSH, "*\r\n") >= 0)
+        (void)smtp_read_response(sx, US buffer, buffsize, '2', timeout);
       string_format(buffer, buffsize, "Invalid base64 string in server "
         "response \"%s\"", save_bad);
       return CANCELLED;
       string_format(buffer, buffsize, "Invalid base64 string in server "
         "response \"%s\"", save_bad);
       return CANCELLED;
@@ -296,4 +303,5 @@ while ((s = string_nextinlist(&text, &sep, big_buffer, big_buffer_size)) != NULL
 return FAIL;
 }
 
 return FAIL;
 }
 
+#endif   /*!MACRO_PREDEF*/
 /* End of plaintext.c */
 /* End of plaintext.c */
index 6cf3522..4c6d011 100644 (file)
@@ -26,7 +26,6 @@ extern auth_plaintext_options_block auth_plaintext_option_defaults;
 
 extern void auth_plaintext_init(auth_instance *);
 extern int auth_plaintext_server(auth_instance *, uschar *);
 
 extern void auth_plaintext_init(auth_instance *);
 extern int auth_plaintext_server(auth_instance *, uschar *);
-extern int auth_plaintext_client(auth_instance *, smtp_inblock *,
-                                 smtp_outblock *, int, uschar *, int);
+extern int auth_plaintext_client(auth_instance *, void *, int, uschar *, int);
 
 /* End of plaintext.h */
 
 /* End of plaintext.h */
index 645265d..54ba80f 100644 (file)
@@ -113,7 +113,7 @@ return PWCHECK_FAIL;
      s = socket(AF_UNIX, SOCK_STREAM, 0);
      if (s == -1) { return PWCHECK_FAIL; }
 
      s = socket(AF_UNIX, SOCK_STREAM, 0);
      if (s == -1) { return PWCHECK_FAIL; }
 
-     memset((char *)&srvaddr, 0, sizeof(srvaddr));
+     memset(CS &srvaddr, 0, sizeof(srvaddr));
      srvaddr.sun_family = AF_UNIX;
      strncpy(srvaddr.sun_path, CYRUS_PWCHECK_SOCKET, sizeof(srvaddr.sun_path));
      r = connect(s, (struct sockaddr *)&srvaddr, sizeof(srvaddr));
      srvaddr.sun_family = AF_UNIX;
      strncpy(srvaddr.sun_path, CYRUS_PWCHECK_SOCKET, sizeof(srvaddr.sun_path));
      r = connect(s, (struct sockaddr *)&srvaddr, sizeof(srvaddr));
@@ -124,9 +124,9 @@ return PWCHECK_FAIL;
        return PWCHECK_FAIL;
      }
 
        return PWCHECK_FAIL;
      }
 
-     iov[0].iov_base = (char *)userid;
+     iov[0].iov_base = CS userid;
      iov[0].iov_len = strlen(userid)+1;
      iov[0].iov_len = strlen(userid)+1;
-     iov[1].iov_base = (char *)passwd;
+     iov[1].iov_base = CS passwd;
      iov[1].iov_len = strlen(passwd)+1;
 
      retry_writev(s, iov, 2);
      iov[1].iov_len = strlen(passwd)+1;
 
      retry_writev(s, iov, 2);
@@ -200,7 +200,7 @@ int saslauthd_verify_password(const uschar *userid,
        return PWCHECK_FAIL;
     }
 
        return PWCHECK_FAIL;
     }
 
-    memset((char *)&srvaddr, 0, sizeof(srvaddr));
+    memset(CS &srvaddr, 0, sizeof(srvaddr));
     srvaddr.sun_family = AF_UNIX;
     strncpy(srvaddr.sun_path, CYRUS_SASLAUTHD_SOCKET,
             sizeof(srvaddr.sun_path));
     srvaddr.sun_family = AF_UNIX;
     strncpy(srvaddr.sun_path, CYRUS_SASLAUTHD_SOCKET,
             sizeof(srvaddr.sun_path));
@@ -343,7 +343,7 @@ static int retry_read(int fd, void *inbuf, unsigned nbyte)
 {
     int n;
     int nread = 0;
 {
     int n;
     int nread = 0;
-    char *buf = (char *)inbuf;
+    char *buf = CS inbuf;
 
     if (nbyte == 0) return 0;
 
 
     if (nbyte == 0) return 0;
 
@@ -432,7 +432,7 @@ retry_writev (
 
        for (i = 0; i < iovcnt; i++) {
            if (iov[i].iov_len > (unsigned) n) {
 
        for (i = 0; i < iovcnt; i++) {
            if (iov[i].iov_len > (unsigned) n) {
-               iov[i].iov_base = (char *)iov[i].iov_base + n;
+               iov[i].iov_base = CS iov[i].iov_base + n;
                iov[i].iov_len -= n;
                break;
            }
                iov[i].iov_len -= n;
                break;
            }
index 0bf7b04..97e3b10 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* This file, which provides support for Microsoft's Secure Password
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* This file, which provides support for Microsoft's Secure Password
@@ -61,7 +61,7 @@ address can appear in the tables drtables.c. */
 int auth_spa_options_count =
   sizeof(auth_spa_options)/sizeof(optionlist);
 
 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 */
 
 auth_spa_options_block auth_spa_option_defaults = {
   NULL,              /* spa_password */
@@ -71,6 +71,19 @@ auth_spa_options_block auth_spa_option_defaults = {
 };
 
 
 };
 
 
+#ifdef MACRO_PREDEF
+
+/* Dummy values */
+void auth_spa_init(auth_instance *ablock) {}
+int auth_spa_server(auth_instance *ablock, uschar *data) {return 0;}
+int auth_spa_client(auth_instance *ablock, void * sx, int timeout,
+    uschar *buffer, int buffsize) {return 0;}
+
+#else   /*!MACRO_PREDEF*/
+
+
+
+
 /*************************************************
 *          Initialization entry point            *
 *************************************************/
 /*************************************************
 *          Initialization entry point            *
 *************************************************/
@@ -110,7 +123,7 @@ ablock->server = ob->spa_serverpassword != NULL;
 
 /* For interface, see auths/README */
 
 
 /* For interface, see auths/README */
 
-#define CVAL(buf,pos) (((unsigned char *)(buf))[pos])
+#define CVAL(buf,pos) ((US (buf))[pos])
 #define PVAL(buf,pos) ((unsigned)CVAL(buf,pos))
 #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 PVAL(buf,pos) ((unsigned)CVAL(buf,pos))
 #define SVAL(buf,pos) (PVAL(buf,pos)|PVAL(buf,(pos)+1)<<8)
 #define IVAL(buf,pos) (SVAL(buf,pos)|SVAL(buf,(pos)+2)<<16)
@@ -138,7 +151,7 @@ if ((*data == '\0') &&
   return FAIL;
   }
 
   return FAIL;
   }
 
-if (spa_base64_to_bits((char *)(&request), sizeof(request), (const char *)(data)) < 0)
+if (spa_base64_to_bits(CS (&request), sizeof(request), CCS (data)) < 0)
   {
   DEBUG(D_auth) debug_printf("auth_spa_server(): bad base64 data in "
   "request: %s\n", data);
   {
   DEBUG(D_auth) debug_printf("auth_spa_server(): bad base64 data in "
   "request: %s\n", data);
@@ -158,7 +171,7 @@ if (auth_get_no64_data(&data, msgbuf) != OK)
   }
 
 /* dump client response */
   }
 
 /* dump client response */
-if (spa_base64_to_bits((char *)(&response), sizeof(response), (const char *)(data)) < 0)
+if (spa_base64_to_bits(CS (&response), sizeof(response), CCS (data)) < 0)
   {
   DEBUG(D_auth) debug_printf("auth_spa_server(): bad base64 data in "
   "response: %s\n", data);
   {
   DEBUG(D_auth) debug_printf("auth_spa_server(): bad base64 data in "
   "response: %s\n", data);
@@ -211,7 +224,7 @@ debug_print_string(ablock->server_debug_string);    /* customized debug */
 clearpass = expand_string(ob->spa_serverpassword);
 if (clearpass == NULL)
   {
 clearpass = expand_string(ob->spa_serverpassword);
 if (clearpass == NULL)
   {
-  if (expand_string_forcedfail)
+  if (f.expand_string_forcedfail)
     {
     DEBUG(D_auth) debug_printf("auth_spa_server(): forced failure while "
       "expanding spa_serverpassword\n");
     {
     DEBUG(D_auth) debug_printf("auth_spa_server(): forced failure while "
       "expanding spa_serverpassword\n");
@@ -255,115 +268,106 @@ return FAIL;
 int
 auth_spa_client(
   auth_instance *ablock,                 /* authenticator block */
 int
 auth_spa_client(
   auth_instance *ablock,                 /* authenticator block */
-  smtp_inblock *inblock,                 /* connection inblock */
-  smtp_outblock *outblock,               /* connection outblock */
+  void * sx,                            /* connection */
   int timeout,                           /* command timeout */
   uschar *buffer,                        /* buffer for reading response */
   int buffsize)                          /* size of buffer */
 {
   int timeout,                           /* command timeout */
   uschar *buffer,                        /* buffer for reading response */
   int buffsize)                          /* size of buffer */
 {
-       auth_spa_options_block *ob =
-               (auth_spa_options_block *)(ablock->options_block);
-       SPAAuthRequest   request;
-       SPAAuthChallenge challenge;
-       SPAAuthResponse  response;
-       char msgbuf[2048];
-       char *domain = NULL;
-       char *username, *password;
-
-       /* Code added by PH to expand the options */
-
-       *buffer = 0;    /* Default no message when cancelled */
-
-       username = CS expand_string(ob->spa_username);
-       if (username == NULL)
-         {
-         if (expand_string_forcedfail) return CANCELLED;
-         string_format(buffer, buffsize, "expansion of \"%s\" failed in %s "
-           "authenticator: %s", ob->spa_username, ablock->name,
-           expand_string_message);
-         return ERROR;
-         }
-
-       password = CS expand_string(ob->spa_password);
-       if (password == NULL)
-         {
-         if (expand_string_forcedfail) return CANCELLED;
-         string_format(buffer, buffsize, "expansion of \"%s\" failed in %s "
-           "authenticator: %s", ob->spa_password, ablock->name,
-           expand_string_message);
-         return ERROR;
-         }
-
-       if (ob->spa_domain != NULL)
-         {
-         domain = CS expand_string(ob->spa_domain);
-         if (domain == NULL)
-           {
-           if (expand_string_forcedfail) return CANCELLED;
-           string_format(buffer, buffsize, "expansion of \"%s\" failed in %s "
-             "authenticator: %s", ob->spa_domain, ablock->name,
-             expand_string_message);
-           return ERROR;
-           }
-         }
-
-       /* Original code */
-
-    if (smtp_write_command(outblock, FALSE, "AUTH %s\r\n",
-         ablock->public_name) < 0)
-               return FAIL_SEND;
-
-       /* wait for the 3XX OK message */
-       if (!smtp_read_response(inblock, (uschar *)buffer, buffsize, '3', timeout))
-               return FAIL;
-
-       DSPA("\n\n%s authenticator: using domain %s\n\n",
-               ablock->name, domain);
-
-       spa_build_auth_request (&request, CS username, domain);
-       spa_bits_to_base64 (US msgbuf, (unsigned char*)&request,
-               spa_request_length(&request));
-
-       DSPA("\n\n%s authenticator: sending request (%s)\n\n", ablock->name,
-               msgbuf);
-
-       /* send the encrypted password */
-       if (smtp_write_command(outblock, FALSE, "%s\r\n", msgbuf) < 0)
-               return FAIL_SEND;
-
-       /* wait for the auth challenge */
-       if (!smtp_read_response(inblock, (uschar *)buffer, buffsize, '3', timeout))
-               return FAIL;
-
-       /* convert the challenge into the challenge struct */
-       DSPA("\n\n%s authenticator: challenge (%s)\n\n",
-               ablock->name, buffer + 4);
-       spa_base64_to_bits ((char *)(&challenge), sizeof(challenge), (const char *)(buffer + 4));
-
-       spa_build_auth_response (&challenge, &response,
-               CS username, CS password);
-       spa_bits_to_base64 (US msgbuf, (unsigned char*)&response,
-               spa_request_length(&response));
-       DSPA("\n\n%s authenticator: challenge response (%s)\n\n", ablock->name,
-               msgbuf);
-
-       /* send the challenge response */
-       if (smtp_write_command(outblock, FALSE, "%s\r\n", msgbuf) < 0)
-               return FAIL_SEND;
-
-       /* If we receive a success response from the server, authentication
-       has succeeded. There may be more data to send, but is there any point
-       in provoking an error here? */
-       if (smtp_read_response(inblock, US buffer, buffsize, '2', timeout))
-               return OK;
-
-       /* Not a success response. If errno != 0 there is some kind of transmission
-       error. Otherwise, check the response code in the buffer. If it starts with
-       '3', more data is expected. */
-       if (errno != 0 || buffer[0] != '3')
-               return FAIL;
-
-       return FAIL;
+auth_spa_options_block *ob =
+       (auth_spa_options_block *)(ablock->options_block);
+SPAAuthRequest   request;
+SPAAuthChallenge challenge;
+SPAAuthResponse  response;
+char msgbuf[2048];
+char *domain = NULL;
+char *username, *password;
+
+/* Code added by PH to expand the options */
+
+*buffer = 0;    /* Default no message when cancelled */
+
+if (!(username = CS expand_string(ob->spa_username)))
+  {
+  if (f.expand_string_forcedfail) return CANCELLED;
+  string_format(buffer, buffsize, "expansion of \"%s\" failed in %s "
+   "authenticator: %s", ob->spa_username, ablock->name,
+   expand_string_message);
+  return ERROR;
+  }
+
+if (!(password = CS expand_string(ob->spa_password)))
+  {
+  if (f.expand_string_forcedfail) return CANCELLED;
+  string_format(buffer, buffsize, "expansion of \"%s\" failed in %s "
+   "authenticator: %s", ob->spa_password, ablock->name,
+   expand_string_message);
+  return ERROR;
+  }
+
+if (ob->spa_domain)
+  if (!(domain = CS expand_string(ob->spa_domain)))
+    {
+    if (f.expand_string_forcedfail) return CANCELLED;
+    string_format(buffer, buffsize, "expansion of \"%s\" failed in %s "
+                 "authenticator: %s", ob->spa_domain, ablock->name,
+                 expand_string_message);
+    return ERROR;
+    }
+
+/* Original code */
+
+if (smtp_write_command(sx, SCMD_FLUSH, "AUTH %s\r\n", ablock->public_name) < 0)
+  return FAIL_SEND;
+
+/* wait for the 3XX OK message */
+if (!smtp_read_response(sx, US buffer, buffsize, '3', timeout))
+  return FAIL;
+
+DSPA("\n\n%s authenticator: using domain %s\n\n", ablock->name, domain);
+
+spa_build_auth_request (&request, CS username, domain);
+spa_bits_to_base64 (US msgbuf, (unsigned char*)&request,
+       spa_request_length(&request));
+
+DSPA("\n\n%s authenticator: sending request (%s)\n\n", ablock->name, msgbuf);
+
+/* send the encrypted password */
+if (smtp_write_command(sx, SCMD_FLUSH, "%s\r\n", msgbuf) < 0)
+  return FAIL_SEND;
+
+/* wait for the auth challenge */
+if (!smtp_read_response(sx, US buffer, buffsize, '3', timeout))
+  return FAIL;
+
+/* convert the challenge into the challenge struct */
+DSPA("\n\n%s authenticator: challenge (%s)\n\n", ablock->name, buffer + 4);
+spa_base64_to_bits (CS (&challenge), sizeof(challenge), CCS (buffer + 4));
+
+spa_build_auth_response (&challenge, &response, CS username, CS password);
+spa_bits_to_base64 (US msgbuf, (unsigned char*)&response,
+       spa_request_length(&response));
+DSPA("\n\n%s authenticator: challenge response (%s)\n\n", ablock->name, msgbuf);
+
+/* send the challenge response */
+if (smtp_write_command(sx, SCMD_FLUSH, "%s\r\n", msgbuf) < 0)
+       return FAIL_SEND;
+
+/* If we receive a success response from the server, authentication
+has succeeded. There may be more data to send, but is there any point
+in provoking an error here? */
+
+if (smtp_read_response(sx, US buffer, buffsize, '2', timeout))
+  return OK;
+
+/* Not a success response. If errno != 0 there is some kind of transmission
+error. Otherwise, check the response code in the buffer. If it starts with
+'3', more data is expected. */
+
+if (errno != 0 || buffer[0] != '3')
+  return FAIL;
+
+return FAIL;
 }
 
 }
 
+#endif   /*!MACRO_PREDEF*/
 /* End of spa.c */
 /* End of spa.c */
index c140fe5..ca93469 100644 (file)
@@ -33,7 +33,6 @@ extern auth_spa_options_block auth_spa_option_defaults;
 
 extern void auth_spa_init(auth_instance *);
 extern int auth_spa_server(auth_instance *, uschar *);
 
 extern void auth_spa_init(auth_instance *);
 extern int auth_spa_server(auth_instance *, uschar *);
-extern int auth_spa_client(auth_instance *, smtp_inblock *,
-                                 smtp_outblock *, int, uschar *, int);
+extern int auth_spa_client(auth_instance *, void *, int, uschar *, int);
 
 /* End of spa.h */
 
 /* End of spa.h */
diff --git a/src/auths/tls.c b/src/auths/tls.c
new file mode 100644 (file)
index 0000000..56f5f5e
--- /dev/null
@@ -0,0 +1,94 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) Jeremy Harris 1995 - 2018 */
+/* 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 */
+};
+
+
+#ifdef MACRO_PREDEF
+
+/* Dummy values */
+void auth_tls_init(auth_instance *ablock) {}
+int auth_tls_server(auth_instance *ablock, uschar *data) {return 0;}
+int auth_tls_client(auth_instance *ablock, void * sx,
+  int timeout, uschar *buffer, int buffsize) {return 0;}
+
+#else   /*!MACRO_PREDEF*/
+
+
+
+
+/*************************************************
+*          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);
+}
+
+
+#endif   /*!MACRO_PREDEF*/
+/* 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 */
index 7cdfe32..2c00c4a 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "../exim.h"
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "../exim.h"
@@ -28,7 +28,7 @@ uschar *
 auth_xtextencode(uschar *clear, int len)
 {
 uschar *code;
 auth_xtextencode(uschar *clear, int len)
 {
 uschar *code;
-uschar *p = (uschar *)clear;
+uschar *p = US clear;
 uschar *pp;
 int c = len;
 int count = 1;
 uschar *pp;
 int c = len;
 int count = 1;
@@ -42,17 +42,13 @@ while (c -- > 0)
 
 pp = code = store_get(count);
 
 
 pp = code = store_get(count);
 
-p = (uschar *)clear;
+p = US clear;
 c = len;
 while (c-- > 0)
 c = len;
 while (c-- > 0)
-  {
   if ((x = *p++) < 33 || x > 127 || x == '+' || x == '=')
   if ((x = *p++) < 33 || x > 127 || x == '+' || x == '=')
-    {
-    sprintf(CS pp, "+%.02x", x);   /* There's always room */
-    pp += 3;
-    }
-  else *pp++ = x;
-  }
+    pp += sprintf(CS pp, "+%.02x", x);   /* There's always room */
+  else
+    *pp++ = x;
 
 *pp = 0;
 return code;
 
 *pp = 0;
 return code;
diff --git a/src/base64.c b/src/base64.c
new file mode 100644 (file)
index 0000000..e63522e
--- /dev/null
@@ -0,0 +1,289 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2004, 2015 */
+/* License: GPL */
+
+/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* 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(const uschar *code, uschar **ptr)
+{
+
+int x, y;
+uschar *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. */
+
+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 > 127 || (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)
+  {
+  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 3785149..546ac1e 100644 (file)
@@ -27,7 +27,7 @@ uschar *bmi_process_message(header_line *header_list, int data_fd) {
   uschar *verdicts = NULL;
   int i,j;
 
   uschar *verdicts = NULL;
   int i,j;
 
-  err = bmiInitSystem(BMI_VERSION, (char *)bmi_config_file, &system);
+  err = bmiInitSystem(BMI_VERSION, CS bmi_config_file, &system);
   if (bmiErrorIsFatal(err) == BMI_TRUE) {
     err_loc = bmiErrorGetLocation(err);
     err_type = bmiErrorGetType(err);
   if (bmiErrorIsFatal(err) == BMI_TRUE) {
     err_loc = bmiErrorGetLocation(err);
     err_type = bmiErrorGetType(err);
@@ -51,24 +51,24 @@ uschar *bmi_process_message(header_line *header_list, int data_fd) {
     host_address = localhost;
   else
     host_address = sender_host_address;
     host_address = localhost;
   else
     host_address = sender_host_address;
-  err = bmiProcessConnection((char *)host_address, message);
+  err = bmiProcessConnection(CS host_address, message);
   if (bmiErrorIsFatal(err) == BMI_TRUE) {
     err_loc = bmiErrorGetLocation(err);
     err_type = bmiErrorGetType(err);
     log_write(0, LOG_PANIC,
   if (bmiErrorIsFatal(err) == BMI_TRUE) {
     err_loc = bmiErrorGetLocation(err);
     err_type = bmiErrorGetType(err);
     log_write(0, LOG_PANIC,
-               "bmi error [loc %d type %d]: bmiProcessConnection() failed (IP %s).", (int)err_loc, (int)err_type, (char *)host_address);
+               "bmi error [loc %d type %d]: bmiProcessConnection() failed (IP %s).", (int)err_loc, (int)err_type, CS host_address);
     bmiFreeMessage(message);
     bmiFreeSystem(system);
     return NULL;
   };
 
   /* Send envelope sender address */
     bmiFreeMessage(message);
     bmiFreeSystem(system);
     return NULL;
   };
 
   /* Send envelope sender address */
-  err = bmiProcessFROM((char *)sender_address, message);
+  err = bmiProcessFROM(CS sender_address, message);
   if (bmiErrorIsFatal(err) == BMI_TRUE) {
     err_loc = bmiErrorGetLocation(err);
     err_type = bmiErrorGetType(err);
     log_write(0, LOG_PANIC,
   if (bmiErrorIsFatal(err) == BMI_TRUE) {
     err_loc = bmiErrorGetLocation(err);
     err_type = bmiErrorGetType(err);
     log_write(0, LOG_PANIC,
-               "bmi error [loc %d type %d]: bmiProcessFROM() failed (address %s).", (int)err_loc, (int)err_type, (char *)sender_address);
+               "bmi error [loc %d type %d]: bmiProcessFROM() failed (address %s).", (int)err_loc, (int)err_type, CS sender_address);
     bmiFreeMessage(message);
     bmiFreeSystem(system);
     return NULL;
     bmiFreeMessage(message);
     bmiFreeSystem(system);
     return NULL;
@@ -86,14 +86,14 @@ uschar *bmi_process_message(header_line *header_list, int data_fd) {
       err = bmiOptinMset(optin, r->bmi_optin, ':');
       if (bmiErrorIsFatal(err) == BMI_TRUE) {
         log_write(0, LOG_PANIC|LOG_MAIN,
       err = bmiOptinMset(optin, r->bmi_optin, ':');
       if (bmiErrorIsFatal(err) == BMI_TRUE) {
         log_write(0, LOG_PANIC|LOG_MAIN,
-                   "bmi warning: [loc %d type %d]: bmiOptinMSet() failed (address '%s', string '%s').", (int)err_loc, (int)err_type, (char *)r->address, (char *)r->bmi_optin);
+                   "bmi warning: [loc %d type %d]: bmiOptinMSet() failed (address '%s', string '%s').", (int)err_loc, (int)err_type, CS r->address, CS r->bmi_optin);
         if (optin != NULL)
           bmiOptinFree(optin);
         optin = NULL;
       };
     };
 
         if (optin != NULL)
           bmiOptinFree(optin);
         optin = NULL;
       };
     };
 
-    err = bmiAccumulateTO((char *)r->address, optin, message);
+    err = bmiAccumulateTO(CS r->address, optin, message);
 
     if (optin != NULL)
       bmiOptinFree(optin);
 
     if (optin != NULL)
       bmiOptinFree(optin);
@@ -102,7 +102,7 @@ uschar *bmi_process_message(header_line *header_list, int data_fd) {
       err_loc = bmiErrorGetLocation(err);
       err_type = bmiErrorGetType(err);
       log_write(0, LOG_PANIC,
       err_loc = bmiErrorGetLocation(err);
       err_type = bmiErrorGetType(err);
       log_write(0, LOG_PANIC,
-                 "bmi error [loc %d type %d]: bmiAccumulateTO() failed (address %s).", (int)err_loc, (int)err_type, (char *)r->address);
+                 "bmi error [loc %d type %d]: bmiAccumulateTO() failed (address %s).", (int)err_loc, (int)err_type, CS r->address);
       bmiFreeMessage(message);
       bmiFreeSystem(system);
       return NULL;
       bmiFreeMessage(message);
       bmiFreeSystem(system);
       return NULL;
@@ -126,7 +126,7 @@ uschar *bmi_process_message(header_line *header_list, int data_fd) {
       header_list = header_list->next;
       continue;
     };
       header_list = header_list->next;
       continue;
     };
-    err = bmiAccumulateHeaders((const char *)header_list->text, header_list->slen, message);
+    err = bmiAccumulateHeaders(CCS header_list->text, header_list->slen, message);
     if (bmiErrorIsFatal(err) == BMI_TRUE) {
       err_loc = bmiErrorGetLocation(err);
       err_type = bmiErrorGetType(err);
     if (bmiErrorIsFatal(err) == BMI_TRUE) {
       err_loc = bmiErrorGetLocation(err);
       err_type = bmiErrorGetType(err);
@@ -154,7 +154,7 @@ uschar *bmi_process_message(header_line *header_list, int data_fd) {
   do {
     j = fread(data_buffer, 1, sizeof(data_buffer), data_file);
     if (j > 0) {
   do {
     j = fread(data_buffer, 1, sizeof(data_buffer), data_file);
     if (j > 0) {
-      err = bmiAccumulateBody((const char *)data_buffer, j, message);
+      err = bmiAccumulateBody(CCS data_buffer, j, message);
       if (bmiErrorIsFatal(err) == BMI_TRUE) {
         err_loc = bmiErrorGetLocation(err);
         err_type = bmiErrorGetType(err);
       if (bmiErrorIsFatal(err) == BMI_TRUE) {
         err_loc = bmiErrorGetLocation(err);
         err_type = bmiErrorGetType(err);
@@ -328,7 +328,7 @@ uschar *bmi_get_base64_verdict(uschar *bmi_local_part, uschar *bmi_domain) {
 
   /* loop through verdicts */
   verdict_ptr = bmi_verdicts;
 
   /* loop through verdicts */
   verdict_ptr = bmi_verdicts;
-  while ((verdict_str = (const char *)string_nextinlist(&verdict_ptr, &sep,
+  while ((verdict_str = CCS string_nextinlist(&verdict_ptr, &sep,
                                           verdict_buffer,
                                           Ustrlen(bmi_verdicts)+1)) != NULL) {
 
                                           verdict_buffer,
                                           Ustrlen(bmi_verdicts)+1)) != NULL) {
 
@@ -350,7 +350,7 @@ uschar *bmi_get_base64_verdict(uschar *bmi_local_part, uschar *bmi_domain) {
       uschar *rcpt_domain;
 
       /* compare address against our subject */
       uschar *rcpt_domain;
 
       /* compare address against our subject */
-      rcpt_local_part = (unsigned char *)bmiRecipientAccessAddress(recipient);
+      rcpt_local_part = US bmiRecipientAccessAddress(recipient);
       rcpt_domain = Ustrchr(rcpt_local_part,'@');
       if (rcpt_domain == NULL) {
         rcpt_domain = US"";
       rcpt_domain = Ustrchr(rcpt_local_part,'@');
       if (rcpt_domain == NULL) {
         rcpt_domain = US"";
@@ -364,7 +364,7 @@ uschar *bmi_get_base64_verdict(uschar *bmi_local_part, uschar *bmi_domain) {
            (strcmpic(rcpt_domain, bmi_domain) == 0) ) {
         /* found verdict */
         bmiFreeVerdict(verdict);
            (strcmpic(rcpt_domain, bmi_domain) == 0) ) {
         /* found verdict */
         bmiFreeVerdict(verdict);
-        return (uschar *)verdict_str;
+        return US verdict_str;
       };
     };
 
       };
     };
 
index f3390cb..3d404f1 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2012 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -36,6 +36,8 @@ normally called independently. */
 #include <stdlib.h>
 #include <string.h>
 #include <sys/types.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/types.h>
+#include <sys/time.h>
+#include <poll.h>
 #include <pwd.h>
 #include <grp.h>
 
 #include <pwd.h>
 #include <grp.h>
 
@@ -101,12 +103,15 @@ main(int argc, char **argv)
 {
 off_t test_off_t = 0;
 time_t test_time_t = 0;
 {
 off_t test_off_t = 0;
 time_t test_time_t = 0;
+ino_t test_ino_t;
 #if ! (__STDC_VERSION__ >= 199901L)
 size_t test_size_t = 0;
 ssize_t test_ssize_t = 0;
 unsigned long test_ulong_t = 0L;
 #if ! (__STDC_VERSION__ >= 199901L)
 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;
 #endif
 long test_long_t = 0;
+int test_int_t = 0;
 FILE *base;
 FILE *new;
 int last_initial = 'A';
 FILE *base;
 FILE *new;
 int last_initial = 'A';
@@ -151,13 +156,13 @@ This assumption is known to be OK for the common operating systems. */
 fprintf(new, "#ifndef OFF_T_FMT\n");
 if (sizeof(test_off_t) > sizeof(test_long_t))
   {
 fprintf(new, "#ifndef OFF_T_FMT\n");
 if (sizeof(test_off_t) > sizeof(test_long_t))
   {
-  fprintf(new, "#define OFF_T_FMT  \"%%lld\"\n");
-  fprintf(new, "#define LONGLONG_T long long int\n");
+  fprintf(new, "# define OFF_T_FMT  \"%%lld\"\n");
+  fprintf(new, "# define LONGLONG_T long long int\n");
   }
 else
   {
   }
 else
   {
-  fprintf(new, "#define OFF_T_FMT  \"%%ld\"\n");
-  fprintf(new, "#define LONGLONG_T long int\n");
+  fprintf(new, "# define OFF_T_FMT  \"%%ld\"\n");
+  fprintf(new, "# define LONGLONG_T long int\n");
   }
 fprintf(new, "#endif\n\n");
 
   }
 fprintf(new, "#endif\n\n");
 
@@ -169,14 +174,23 @@ off_t. */
 fprintf(new, "#ifndef TIME_T_FMT\n");
 if (sizeof(test_time_t) > sizeof(test_long_t))
   {
 fprintf(new, "#ifndef TIME_T_FMT\n");
 if (sizeof(test_time_t) > sizeof(test_long_t))
   {
-  fprintf(new, "#define TIME_T_FMT  \"%%lld\"\n");
-  fprintf(new, "#undef  LONGLONG_T\n");
-  fprintf(new, "#define LONGLONG_T long long int\n");
+  fprintf(new, "# define TIME_T_FMT  \"%%lld\"\n");
+  fprintf(new, "# undef  LONGLONG_T\n");
+  fprintf(new, "# define LONGLONG_T long long int\n");
   }
 else
   }
 else
-  {
-  fprintf(new, "#define TIME_T_FMT  \"%%ld\"\n");
-  }
+  fprintf(new, "# define TIME_T_FMT  \"%%ld\"\n");
+fprintf(new, "#endif\n\n");
+
+fprintf(new, "#ifndef INO_T_FMT\n");
+if (sizeof(test_ino_t) > sizeof(test_long_t))
+  fprintf(new, "# define INO_T_FMT  \"%%llu\"\n");
+else
+  fprintf(new, "# define INO_T_FMT  \"%%lu\"\n");
+fprintf(new, "#endif\n\n");
+
+fprintf(new, "#ifndef PID_T_FMT\n");
+fprintf(new, "# define PID_T_FMT  \"%%lu\"\n");
 fprintf(new, "#endif\n\n");
 
 /* And for sizeof() results, size_t, which should with C99 be just %zu, deal
 fprintf(new, "#endif\n\n");
 
 /* And for sizeof() results, size_t, which should with C99 be just %zu, deal
@@ -190,12 +204,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
 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");
   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");
 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");
   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 */
 #endif
 
 /* Now search the makefile for certain settings */
@@ -716,21 +735,36 @@ else if (isgroup)
       fprintf(new, "#define FIXED_NEVER_USERS     %d", j);
       for (i = 0; i < j; i++) fprintf(new, ", %d", (unsigned int)vector[i]);
       fprintf(new, "\n");
       fprintf(new, "#define FIXED_NEVER_USERS     %d", j);
       for (i = 0; i < j; i++) fprintf(new, ", %d", (unsigned int)vector[i]);
       fprintf(new, "\n");
+      free(vector);
       }
     continue;
     }
 
       }
     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");
 
   if (strcmp(name, "WITH_CONTENT_SCAN") == 0)
     {
     char *wcs = getenv("WITH_CONTENT_SCAN");
-    char *wod = getenv("WITH_OLD_DEMIME");
     char *dcc = getenv("EXPERIMENTAL_DCC");
     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;
     }
 
     continue;
     }
 
@@ -804,7 +838,11 @@ else if (isgroup)
             strncpy(buffer, ss, sss-ss);
             buffer[sss-ss] = 0;  /* For empty case */
             }
             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;
           pp = buffer + (int)strlen(buffer);
           while (pp > buffer && isspace((unsigned char)pp[-1])) pp--;
           *pp = 0;
@@ -920,6 +958,25 @@ if (have_auth)
     "#define SUPPORT_CRYPTEQ\n");
   }
 
     "#define SUPPORT_CRYPTEQ\n");
   }
 
+/* Check poll() for timer functionality.
+Some OS' have released with it broken. */
+
+  {
+  struct timeval before, after;
+  int rc;
+  size_t us;
+
+  gettimeofday(&before, NULL);
+  rc = poll(NULL, 0, 500);
+  gettimeofday(&after, NULL);
+
+  us = (after.tv_sec - before.tv_sec) * 1000000 +
+    (after.tv_usec - before.tv_usec);
+
+  if (us < 400000)
+    fprintf(new, "#define NO_POLL_H\n");
+  }
+
 /* End off */
 
 fprintf(new, "\n/* End of config.h */\n");
 /* End off */
 
 fprintf(new, "\n/* End of config.h */\n");
index 20083b4..2262678 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     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. */
 
 
 /* See the file NOTICE for conditions of use and distribution. */
 
 
 
 static void (*oldsignal)(int);
 
 
 static void (*oldsignal)(int);
 
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+static uschar tls_requiretls_copy = 0;
+#endif
+
 
 /*************************************************
 *          Ensure an fd has a given value        *
 
 /*************************************************
 *          Ensure an fd has a given value        *
@@ -72,9 +76,14 @@ child_exec_exim(int exec_type, BOOL kill_v, int *pcount, BOOL minimal,
 {
 int first_special = -1;
 int n = 0;
 {
 int first_special = -1;
 int n = 0;
-int extra = (pcount != NULL)? *pcount : 0;
-uschar **argv =
-  store_get((extra + acount + MAX_CLMACROS + 16) * sizeof(char *));
+int extra = pcount ? *pcount : 0;
+uschar **argv;
+
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+if (tls_requiretls) extra++;
+#endif
+
+argv = 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. */
 
 /* In all case, the list starts out with the path, any macros, and a changed
 config file. */
@@ -85,7 +94,7 @@ if (clmacro_count > 0)
   memcpy(argv + n, clmacros, clmacro_count * sizeof(uschar *));
   n += clmacro_count;
   }
   memcpy(argv + n, clmacros, clmacro_count * sizeof(uschar *));
   n += clmacro_count;
   }
-if (config_changed)
+if (f.config_changed)
   {
   argv[n++] = US"-C";
   argv[n++] = config_main_filename;
   {
   argv[n++] = US"-C";
   argv[n++] = config_main_filename;
@@ -108,13 +117,23 @@ if (!minimal)
     if (debug_selector != 0)
       argv[n++] = string_sprintf("-d=0x%x", debug_selector);
     }
     if (debug_selector != 0)
       argv[n++] = string_sprintf("-d=0x%x", debug_selector);
     }
-  if (dont_deliver) argv[n++] = US"-N";
-  if (queue_smtp) argv[n++] = US"-odqs";
-  if (synchronous_delivery) argv[n++] = US"-odi";
+  if (f.dont_deliver) argv[n++] = US"-N";
+  if (f.queue_smtp) argv[n++] = US"-odqs";
+  if (f.synchronous_delivery) argv[n++] = US"-odi";
   if (connection_max_messages >= 0)
     argv[n++] = string_sprintf("-oB%d", connection_max_messages);
   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;
+    }
   }
 
   }
 
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+if (tls_requiretls_copy & REQUIRETLS_MSG)
+  argv[n++] = US"-MS";
+#endif
+
 /* Now add in any others that are in the call. Remember which they were,
 for more helpful diagnosis on failure. */
 
 /* Now add in any others that are in the call. Remember which they were,
 for more helpful diagnosis on failure. */
 
@@ -141,7 +160,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. */
 
 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);
 
 exim_nullstd();                            /* Make sure std{in,out,err} exist */
 execv(CS argv[0], (char *const *)argv);
 
@@ -217,17 +236,20 @@ 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
 
 /* 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. */
 
 if (pid == 0)
   {
 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. */
 
 if (pid == 0)
   {
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+  tls_requiretls_copy = tls_requiretls;
+#endif
   force_fd(pfd[pipe_read], 0);
   (void)close(pfd[pipe_write]);
   if (debug_fd > 0) force_fd(debug_fd, 2);
   force_fd(pfd[pipe_read], 0);
   (void)close(pfd[pipe_write]);
   if (debug_fd > 0) force_fd(debug_fd, 2);
-  if (running_in_test_harness && !queue_only)
+  if (f.running_in_test_harness && !queue_only)
     {
     if (sender_authentication != NULL)
       child_exec_exim(CEE_EXEC_EXIT, FALSE, NULL, FALSE, 9,
     {
     if (sender_authentication != NULL)
       child_exec_exim(CEE_EXEC_EXIT, FALSE, NULL, FALSE, 9,
@@ -307,8 +329,9 @@ Returns:      the pid of the created process or -1 if anything has gone wrong
 */
 
 pid_t
 */
 
 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];
 {
 int save_errno;
 int inpfd[2], outpfd[2];
@@ -387,7 +410,7 @@ if (pid == 0)
   /* Now do the exec */
 
   if (envp == NULL) execv(CS argv[0], (char *const *)argv);
   /* 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
 
   /* 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 +473,8 @@ pid_t
 child_open(uschar **argv, uschar **envp, int newumask, int *infdptr,
   int *outfdptr, BOOL make_leader)
 {
 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);
 }
 
 
 }
 
 
@@ -484,7 +507,7 @@ int yield;
 if (timeout > 0)
   {
   sigalrm_seen = FALSE;
 if (timeout > 0)
   {
   sigalrm_seen = FALSE;
-  alarm(timeout);
+  ALARM(timeout);
   }
 
 for(;;)
   }
 
 for(;;)
@@ -494,18 +517,23 @@ for(;;)
   if (rc == pid)
     {
     int lowbyte = status & 255;
   if (rc == pid)
     {
     int lowbyte = status & 255;
-    if (lowbyte == 0) yield = (status >> 8) & 255;
-      else yield = -lowbyte;
+    yield = lowbyte == 0 ? (status >> 8) & 255 : -lowbyte;
     break;
     }
   if (rc < 0)
     {
     break;
     }
   if (rc < 0)
     {
-    yield = (errno == EINTR && sigalrm_seen)? -256 : -257;
+    /* This "shouldn't happen" test does happen on MacOS: for some reason
+    I do not understand we seems to get an alarm signal despite not having
+    an active alarm set. There seems to be only one, so just go round again. */
+
+    if (errno == EINTR && sigalrm_seen && timeout <= 0) continue;
+
+    yield = (errno == EINTR && sigalrm_seen) ? -256 : -257;
     break;
     }
   }
 
     break;
     }
   }
 
-if (timeout > 0) alarm(0);
+if (timeout > 0) ALARM_CLR(0);
 
 signal(SIGCHLD, oldsignal);   /* restore */
 return yield;
 
 signal(SIGCHLD, oldsignal);   /* restore */
 return yield;
diff --git a/src/cnumber.h b/src/cnumber.h
new file mode 100644 (file)
index 0000000..d00491f
--- /dev/null
@@ -0,0 +1 @@
+1
index ba4615c..7c2e534 100644 (file)
@@ -2,13 +2,17 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* Copyright (c) The Exim Maintainers 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* The default settings for Exim configuration variables. A #define without
 any data just defines the existence of the variable; it won't get included
 in config.h unless some value is defined in Local/Makefile. If there is data,
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* The default settings for Exim configuration variables. A #define without
 any data just defines the existence of the variable; it won't get included
 in config.h unless some value is defined in Local/Makefile. If there is data,
-it's a default value. */
+it's a default value.
+
+Do not put spaces between # and the 'define'.
+*/
 
 #define ALT_CONFIG_PREFIX
 #define TRUSTED_CONFIG_LIST
 
 #define ALT_CONFIG_PREFIX
 #define TRUSTED_CONFIG_LIST
@@ -24,6 +28,7 @@ it's a default value. */
 #define AUTH_HEIMDAL_GSSAPI
 #define AUTH_PLAINTEXT
 #define AUTH_SPA
 #define AUTH_HEIMDAL_GSSAPI
 #define AUTH_PLAINTEXT
 #define AUTH_SPA
+#define AUTH_TLS
 
 #define AUTH_VARS                     3
 
 
 #define AUTH_VARS                     3
 
@@ -40,10 +45,11 @@ it's a default value. */
 #define DEFAULT_CRYPT              crypt
 #define DELIVER_IN_BUFFER_SIZE     8192
 #define DELIVER_OUT_BUFFER_SIZE    8192
 #define DEFAULT_CRYPT              crypt
 #define DELIVER_IN_BUFFER_SIZE     8192
 #define DELIVER_OUT_BUFFER_SIZE    8192
+#define DISABLE_DNSSEC
 #define DISABLE_DKIM
 #define DISABLE_DKIM
+#define DISABLE_EVENT
 #define DISABLE_PRDR
 #define DISABLE_OCSP
 #define DISABLE_PRDR
 #define DISABLE_OCSP
-#define DISABLE_DNSSEC
 #define DISABLE_D_OPTION
 
 #define ENABLE_DISABLE_FSYNC
 #define DISABLE_D_OPTION
 
 #define ENABLE_DISABLE_FSYNC
@@ -65,6 +71,7 @@ it's a default value. */
 #define FIXED_NEVER_USERS         "root"
 
 #define HAVE_CRYPT16
 #define FIXED_NEVER_USERS         "root"
 
 #define HAVE_CRYPT16
+#define HAVE_LOCAL_SCAN
 #define HAVE_SA_LEN
 #define HEADERS_CHARSET           "ISO-8859-1"
 #define HEADER_ADD_BUFFER_SIZE    (8192 * 4)
 #define HAVE_SA_LEN
 #define HEADERS_CHARSET           "ISO-8859-1"
 #define HEADER_ADD_BUFFER_SIZE    (8192 * 4)
@@ -93,6 +100,7 @@ it's a default value. */
 #define LOOKUP_ORACLE
 #define LOOKUP_PASSWD
 #define LOOKUP_PGSQL
 #define LOOKUP_ORACLE
 #define LOOKUP_PASSWD
 #define LOOKUP_PGSQL
+#define LOOKUP_REDIS
 #define LOOKUP_SQLITE
 #define LOOKUP_TESTDB
 #define LOOKUP_WHOSON
 #define LOOKUP_SQLITE
 #define LOOKUP_TESTDB
 #define LOOKUP_WHOSON
@@ -115,6 +123,8 @@ it's a default value. */
 #define RADIUS_CONFIG_FILE
 #define RADIUS_LIB_TYPE
 
 #define RADIUS_CONFIG_FILE
 #define RADIUS_LIB_TYPE
 
+#define REGEX_VARS                 9
+
 #define ROUTER_ACCEPT
 #define ROUTER_DNSLOOKUP
 #define ROUTER_IPLITERAL
 #define ROUTER_ACCEPT
 #define ROUTER_DNSLOOKUP
 #define ROUTER_IPLITERAL
@@ -128,13 +138,18 @@ it's a default value. */
 #define SPOOL_MODE                 0640
 #define STRING_SPRINTF_BUFFER_SIZE (8192 * 4)
 
 #define SPOOL_MODE                 0640
 #define STRING_SPRINTF_BUFFER_SIZE (8192 * 4)
 
-#define SUPPORT_A6
 #define SUPPORT_CRYPTEQ
 #define SUPPORT_CRYPTEQ
+#define SUPPORT_DANE
+#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_MAILDIR
 #define SUPPORT_MAILSTORE
 #define SUPPORT_MBX
 #define SUPPORT_MOVE_FROZEN_MESSAGES
 #define SUPPORT_PAM
+#define SUPPORT_PROXY
+#define SUPPORT_SOCKS
+#define SUPPORT_SPF
 #define SUPPORT_TLS
 #define SUPPORT_TRANSLATE_IP_ADDRESS
 
 #define SUPPORT_TLS
 #define SUPPORT_TRANSLATE_IP_ADDRESS
 
@@ -143,7 +158,7 @@ it's a default value. */
 
 #define TCP_WRAPPERS_DAEMON_NAME "exim"
 #define TIMEZONE_DEFAULT
 
 #define TCP_WRAPPERS_DAEMON_NAME "exim"
 #define TIMEZONE_DEFAULT
-#define TMPDIR
+#define EXIM_TMPDIR
 
 #define TRANSPORT_APPENDFILE
 #define TRANSPORT_AUTOREPLY
 
 #define TRANSPORT_APPENDFILE
 #define TRANSPORT_AUTOREPLY
@@ -162,20 +177,32 @@ it's a default value. */
 #define WHITELIST_D_MACROS
 
 #define WITH_CONTENT_SCAN
 #define WHITELIST_D_MACROS
 
 #define WITH_CONTENT_SCAN
-#define WITH_OLD_DEMIME
-#define WITH_OLD_CLAMAV_STREAM
+#define DISABLE_MAL_FFROTD
+#define DISABLE_MAL_FFROT6D
+#define DISABLE_MAL_DRWEB
+#define DISABLE_MAL_AVE
+#define DISABLE_MAL_FSECURE
+#define DISABLE_MAL_KAV
+#define DISABLE_MAL_SOPHIE
+#define DISABLE_MAL_CLAM
+#define DISABLE_MAL_MKS
+#define DISABLE_MAL_AVAST
+#define DISABLE_MAL_SOCK
+#define DISABLE_MAL_CMDLINE
 
 /* EXPERIMENTAL features */
 
 /* EXPERIMENTAL features */
+#define EXPERIMENTAL_ARC
 #define EXPERIMENTAL_BRIGHTMAIL
 #define EXPERIMENTAL_BRIGHTMAIL
-#define EXPERIMENTAL_CERTNAMES
 #define EXPERIMENTAL_DCC
 #define EXPERIMENTAL_DCC
+#define EXPERIMENTAL_DSN_INFO
 #define EXPERIMENTAL_DMARC
 #define EXPERIMENTAL_DMARC
-#define EXPERIMENTAL_DSN
-#define EXPERIMENTAL_PROXY
-#define EXPERIMENTAL_REDIS
-#define EXPERIMENTAL_SPF
+    #define DMARC_TLD_FILE "/etc/exim/opendmarc.tlds"
+#define EXPERIMENTAL_LMDB
+#define EXPERIMENTAL_PIPE_CONNECT
+#define EXPERIMENTAL_REQUIRETLS
+#define EXPERIMENTAL_QUEUEFILE
 #define EXPERIMENTAL_SRS
 #define EXPERIMENTAL_SRS
-#define EXPERIMENTAL_TPDA
+
 
 /* For developers */
 #define WANT_DEEPER_PRINTF_CHECKS
 
 /* For developers */
 #define WANT_DEEPER_PRINTF_CHECKS
@@ -191,7 +218,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
 /* 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 */
 #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..555dec3 100644 (file)
@@ -9,7 +9,7 @@
 # configuration file. There are many more than are mentioned here. The
 # manual is in the file doc/spec.txt in the Exim distribution as a plain
 # ASCII file. Other formats (PostScript, Texinfo, HTML, PDF) are available
 # configuration file. There are many more than are mentioned here. The
 # manual is in the file doc/spec.txt in the Exim distribution as a plain
 # ASCII file. Other formats (PostScript, Texinfo, HTML, PDF) are available
-# from the Exim ftp sites. The manual is also online at the Exim web sites.
+# from the Exim ftp sites. The manual is also online at the Exim website.
 
 
 # This file is divided into several parts, all but the first of which are
 
 
 # This file is divided into several parts, all but the first of which are
 
 
 
 
 
 
+######################################################################
+#                               MACROS                               #
+######################################################################
+#
+
+# If you want to use a smarthost instead of sending directly to recipient
+# domains, uncomment this macro definition and set a real hostname.
+# An appropriately privileged user can then redirect email on the command-line
+# in emergencies, via -D.
+#
+# ROUTER_SMARTHOST=MAIL.HOSTNAME.FOR.CENTRAL.SERVER.EXAMPLE
+
 ######################################################################
 #                    MAIN CONFIGURATION SETTINGS                     #
 ######################################################################
 ######################################################################
 #                    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
 
 # 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
@@ -106,8 +119,11 @@ hostlist   relay_from_hosts = localhost
 # manual for details. The lists above are used in the access control lists for
 # checking incoming messages. The names of these ACLs are defined here:
 
 # manual for details. The lists above are used in the access control lists for
 # checking incoming messages. The names of these ACLs are defined here:
 
-acl_smtp_rcpt = acl_check_rcpt
-acl_smtp_data = acl_check_data
+acl_smtp_rcpt =         acl_check_rcpt
+.ifdef _HAVE_PRDR
+acl_smtp_data_prdr =    acl_check_prdr
+.endif
+acl_smtp_data =         acl_check_data
 
 # You should not change those settings until you understand how ACLs work.
 
 
 # You should not change those settings until you understand how ACLs work.
 
@@ -152,6 +168,9 @@ acl_smtp_data = acl_check_data
 # tls_certificate = /etc/ssl/exim.crt
 # tls_privatekey = /etc/ssl/exim.pem
 
 # tls_certificate = /etc/ssl/exim.crt
 # tls_privatekey = /etc/ssl/exim.pem
 
+# For OpenSSL, prefer EC- over RSA-authenticated ciphers
+# tls_require_ciphers = ECDSA:RSA:!COMPLEMENTOFDEFAULT
+
 # In order to support roaming users who wish to send email from anywhere,
 # you may want to make Exim listen on other ports as well as port 25, in
 # case these users need to send email from a network that blocks port 25.
 # In order to support roaming users who wish to send email from anywhere,
 # you may want to make Exim listen on other ports as well as port 25, in
 # case these users need to send email from a network that blocks port 25.
@@ -221,18 +240,35 @@ never_users = root
 host_lookup = *
 
 
 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 setting below causes Exim to try to initialize the system resolver
+# library with DNSSEC support.  It has no effect if your library lacks
+# DNSSEC support.
 
 
-rfc1413_hosts = *
-rfc1413_query_timeout = 5s
+dns_dnssec_ok = 1
+
+
+# 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.
+#
+.ifdef _HAVE_PRDR
+prdr_enable = true
+.endif
 
 
 # By default, Exim expects all envelope addresses to be fully qualified, that
 
 
 # By default, Exim expects all envelope addresses to be fully qualified, that
@@ -248,6 +284,13 @@ rfc1413_query_timeout = 5s
 # and/or qualify_recipient (see above).
 
 
 # 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
 # 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 +357,18 @@ timeout_frozen_after = 7d
 # accept_8bitmime = false
 
 
 # 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      #
 ######################################################################
 #                       ACL CONFIGURATION                            #
 #         Specifies access control lists for incoming SMTP mail      #
@@ -421,6 +476,11 @@ acl_check_rcpt:
           control       = submission
           control       = dkim_disable_verify
 
           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.
   # 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.
@@ -461,12 +521,45 @@ acl_check_rcpt:
   # require verify = csa
   #############################################################################
 
   # require verify = csa
   #############################################################################
 
+  #############################################################################
+  # If doing per-user content filtering then recipients with filters different
+  # to the first recipient must be deferred unless the sender talks PRDR.
+  #
+  # defer  !condition     = $prdr_requested
+  #        condition      = ${if > {0}{$receipients_count}}
+  #        condition      = ${if !eq {$acl_m_content_filter} \
+  #                                  {${lookup PER_RCPT_CONTENT_FILTER}}}
+  # warn   !condition     = $prdr_requested
+  #        condition      = ${if > {0}{$receipients_count}}
+  #        set acl_m_content_filter = ${lookup PER_RCPT_CONTENT_FILTER}
+  #############################################################################
+
   # At this point, the address has passed all the checks that have been
   # configured, so we accept it unconditionally.
 
   accept
 
 
   # At this point, the address has passed all the checks that have been
   # configured, so we accept it unconditionally.
 
   accept
 
 
+# This ACL is used once per recipient, for multi-recipient messages, if
+# we advertised PRDR.  It can be used to perform receipient-dependent
+# header- and body- based filtering and rejections.
+# We set a variable to record that PRDR was active used, so that checking
+# in the data ACL can be skipped.
+
+.ifdef _HAVE_PRDR
+acl_check_prdr:
+  warn  set acl_m_did_prdr = y
+.endif
+
+  #############################################################################
+  # do lookup on filtering, with $local_part@$domain, deny on filter match
+  #
+  # deny      set acl_m_content_filter = ${lookup PER_RCPT_CONTENT_FILTER}
+  #           condition    = ...
+  #############################################################################
+
+  accept
+
 # This ACL is used after the contents of a message have been received. This
 # is the ACL in which you can test a message's headers or body, and in
 # particular, this is where you can invoke external virus or spam scanners.
 # This ACL is used after the contents of a message have been received. This
 # is the ACL in which you can test a message's headers or body, and in
 # particular, this is where you can invoke external virus or spam scanners.
@@ -477,6 +570,19 @@ acl_check_rcpt:
 
 acl_check_data:
 
 
 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 headers contain badly-formed addresses.
+  #
+  deny    !verify =     header_syntax
+          message =     header syntax
+          log_message = header syntax ($acl_verify_message)
+
   # Deny if the message contains a virus. Before enabling this check, you
   # must install a virus scanner and set the av_scanner option above.
   #
   # Deny if the message contains a virus. Before enabling this check, you
   # must install a virus scanner and set the av_scanner option above.
   #
@@ -493,6 +599,19 @@ acl_check_data:
   #                      X-Spam_bar: $spam_bar\n\
   #                      X-Spam_report: $spam_report
 
   #                      X-Spam_bar: $spam_bar\n\
   #                      X-Spam_report: $spam_report
 
+  #############################################################################
+  # No more tests if PRDR was actively used.
+  # accept   condition  = ${if def:acl_m_did_prdr}
+  #
+  # To get here, all message recipients must have identical per-user
+  # content filtering (enforced by RCPT ACL).  Do lookup for filter
+  # and deny on match.
+  #
+  # deny      set acl_m_content_filter = ${lookup PER_RCPT_CONTENT_FILTER}
+  #           condition    = ...
+  #############################################################################
+
+
   # Accept the message.
 
   accept
   # Accept the message.
 
   accept
@@ -524,6 +643,25 @@ begin routers
 #   transport = remote_smtp
 
 
 #   transport = remote_smtp
 
 
+# This router can be used when you want to send all mail to a
+# server which handles DNS lookups for you; an ISP will typically run such
+# a server for their customers.  The hostname in route_data comes from the
+# macro defined at the top of the file.  If not defined, then we'll use the
+# dnslookup router below instead.
+# Beware that the hostname is specified again in the Transport.
+
+.ifdef ROUTER_SMARTHOST
+
+smarthost:
+  driver = manualroute
+  domains = ! +local_domains
+  transport = smarthost_smtp
+  route_data = ROUTER_SMARTHOST
+  ignore_target_hosts = <; 0.0.0.0 ; 127.0.0.0/8 ; ::1
+  no_more
+
+.else
+
 # This router routes addresses that are not in local domains by doing a DNS
 # lookup on the domain name. The exclamation mark that appears in "domains = !
 # +local_domains" is a negating operator, that is, it can be read as "not". The
 # This router routes addresses that are not in local domains by doing a DNS
 # lookup on the domain name. The exclamation mark that appears in "domains = !
 # +local_domains" is a negating operator, that is, it can be read as "not". The
@@ -544,22 +682,12 @@ dnslookup:
   ignore_target_hosts = 0.0.0.0 : 127.0.0.0/8
 # if ipv6-enabled then instead use:
 # ignore_target_hosts = <; 0.0.0.0 ; 127.0.0.0/8 ; ::1
   ignore_target_hosts = 0.0.0.0 : 127.0.0.0/8
 # if ipv6-enabled then instead use:
 # ignore_target_hosts = <; 0.0.0.0 ; 127.0.0.0/8 ; ::1
+  dnssec_request_domains = *
   no_more
 
   no_more
 
-
-# This alternative router can be used when you want to send all mail to a
-# server which handles DNS lookups for you; an ISP will typically run such
-# a server for their customers.  If you uncomment "smarthost" then you
-# should comment out "dnslookup" above.  Setting a real hostname in route_data
-# wouldn't hurt either.
-
-# smarthost:
-#   driver = manualroute
-#   domains = ! +local_domains
-#   transport = remote_smtp
-#   route_data = MAIL.HOSTNAME.FOR.CENTRAL.SERVER.EXAMPLE
-#   ignore_target_hosts = <; 0.0.0.0 ; 127.0.0.0/8 ; ::1
-#   no_more
+# This closes the ROUTER_SMARTHOST ifdef around the choice of routing for
+# off-site mail.
+.endif
 
 
 # The remaining routers handle addresses in the local domain(s), that is those
 
 
 # The remaining routers handle addresses in the local domain(s), that is those
@@ -669,9 +797,61 @@ begin transports
 
 
 # This transport is used for delivering messages over SMTP connections.
 
 
 # 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
 
 remote_smtp:
   driver = smtp
+  message_size_limit = ${if > {$max_received_linelength}{998} {1}{0}}
+.ifdef _HAVE_DANE
+  dnssec_request_domains = *
+  hosts_try_dane = *
+.endif
+.ifdef _HAVE_PRDR
+  hosts_try_prdr = *
+.endif
+
+
+# This transport is used for delivering messages to a smarthost, if the
+# smarthost router is enabled.  This starts from the same basis as
+# "remote_smtp" but then turns on various security options, because
+# we assume that if you're told "use smarthost.example.org as the smarthost"
+# then there will be TLS available, with a verifiable certificate for that
+# hostname, using decent TLS.
+
+smarthost_smtp:
+  driver = smtp
+  message_size_limit = ${if > {$max_received_linelength}{998} {1}{0}}
+  multi_domain
+  #
+.ifdef _HAVE_TLS
+  # Comment out any of these which you have to, then file a Support
+  # request with your smarthost provider to get things fixed:
+  hosts_require_tls = *
+  tls_verify_hosts = *
+  # As long as tls_verify_hosts is enabled, this won't matter, but if you
+  # have to comment it out then this will at least log whether you succeed
+  # or not:
+  tls_try_verify_hosts = *
+  #
+  # The SNI name should match the name which we'll expect to verify;
+  # many mail systems don't use SNI and this doesn't matter, but if it does,
+  # we need to send a name which the remote site will recognize.
+  # This _should_ be the name which the smarthost operators specified as
+  # the hostname for sending your mail to.
+  tls_sni = ROUTER_SMARTHOST
+  #
+.ifdef _HAVE_OPENSSL
+  tls_require_ciphers = HIGH:!aNULL:@STRENGTH
+.endif
+.ifdef _HAVE_GNUTLS
+  tls_require_ciphers = SECURE192:-VERS-SSL3.0:-VERS-TLS1.0:-VERS-TLS1.1
+.endif
+.endif
+.ifdef _HAVE_PRDR
+  hosts_try_prdr = *
+.endif
 
 
 # This transport is used for local delivery to user mailboxes in traditional
 
 
 # This transport is used for local delivery to user mailboxes in traditional
index 0ccaf6c..d0b94d1 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
 
 # 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,20 @@
 
 # It is assumed that the input is a valid Exim configuration file.
 
 
 # It is assumed that the input is a valid Exim configuration file.
 
+use warnings;
+BEGIN { pop @INC if $INC[-1] eq '.' };
+
+use Getopt::Long;
+use File::Basename;
+
+GetOptions(
+    'version' => sub {
+        print basename($0) . ": $0\n",
+            "build: EXIM_RELEASE_VERSIONEXIM_VARIANT_VERSION\n",
+            "perl(runtime): $^V\n";
+            exit 0;
+    },
+);
 
 ##################################################
 #             Analyse one line                   #
 
 ##################################################
 #             Analyse one line                   #
index 4455fb7..47987fc 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
 
 # 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,20 @@
 
 # It is assumed that the input is a valid Exim 3 configuration file.
 
 
 # It is assumed that the input is a valid Exim 3 configuration file.
 
+use warnings;
+BEGIN { pop @INC if $INC[-1] eq '.' };
+
+use Getopt::Long;
+use File::Basename;
+
+GetOptions(
+    'version' => sub {
+        print basename($0) . ": $0\n",
+            "build: EXIM_RELEASE_VERSIONEXIM_VARIANT_VERSION\n",
+            "perl(runtime): $^V\n";
+            exit 0;
+    },
+);
 
 # These are lists of main options which are abolished in Exim 4.
 # The first contains options that are used to construct new options.
 
 # 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
 
 #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
 
 }
 #endif
 
index 5c64205..a852192 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions concerned with running Exim as a daemon */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions concerned with running Exim as a daemon */
@@ -19,9 +19,9 @@ typedef struct smtp_slot {
 } smtp_slot;
 
 /* An empty slot for initializing (Standard C does not allow constructor
 } 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 };
+static smtp_slot empty_smtp_slot = { .pid = 0, .host_address = NULL };
 
 
 
 
 
 
@@ -106,10 +106,10 @@ Returns:         nothing
 static void
 never_error(uschar *log_msg, uschar *smtp_msg, int was_errno)
 {
 static void
 never_error(uschar *log_msg, uschar *smtp_msg, int was_errno)
 {
-uschar *emsg = (was_errno <= 0)? US"" :
-  string_sprintf(": %s", strerror(was_errno));
+uschar *emsg = was_errno <= 0
+  ? US"" : string_sprintf(": %s", strerror(was_errno));
 log_write(0, LOG_MAIN|LOG_PANIC, "%s%s", log_msg, emsg);
 log_write(0, LOG_MAIN|LOG_PANIC, "%s%s", log_msg, emsg);
-if (smtp_out != NULL) smtp_printf("421 %s\r\n", smtp_msg);
+if (smtp_out) smtp_printf("421 %s\r\n", FALSE, smtp_msg);
 }
 
 
 }
 
 
@@ -143,10 +143,8 @@ union sockaddr_46 interface_sockaddr;
 EXIM_SOCKLEN_T ifsize = sizeof(interface_sockaddr);
 int dup_accept_socket = -1;
 int max_for_this_host = 0;
 EXIM_SOCKLEN_T ifsize = sizeof(interface_sockaddr);
 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;
-uschar *whofrom = NULL;
+int save_log_selector = *log_selector;
+gstring * whofrom;
 
 void *reset_point = store_get(0);
 
 
 void *reset_point = store_get(0);
 
@@ -161,23 +159,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. */
 
 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;
   }
 
   {
   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;
   }
 
   {
   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);
   {
   never_error(US"daemon: fdopen() for smtp_in failed",
     US"Connection setup failed", errno);
@@ -192,7 +187,7 @@ if (getsockname(accept_socket, (struct sockaddr *)(&interface_sockaddr),
   {
   log_write(0, LOG_MAIN | ((errno == ECONNRESET)? 0 : LOG_PANIC),
     "getsockname() failed: %s", strerror(errno));
   {
   log_write(0, LOG_MAIN | ((errno == ECONNRESET)? 0 : LOG_PANIC),
     "getsockname() failed: %s", strerror(errno));
-  smtp_printf("421 Local problem: getsockname() failed; please try again later\r\n");
+  smtp_printf("421 Local problem: getsockname() failed; please try again later\r\n", FALSE);
   goto ERROR_RETURN;
   }
 
   goto ERROR_RETURN;
   }
 
@@ -204,17 +199,16 @@ DEBUG(D_interface) debug_printf("interface address=%s port=%d\n",
 the local interface data. This is for logging; at the end of this function the
 memory is reclaimed. */
 
 the local interface data. This is for logging; at the end of this function the
 memory is reclaimed. */
 
-whofrom = string_append(whofrom, &wfsize, &wfptr, 3, "[", sender_host_address, "]");
+whofrom = string_append(NULL, 3, "[", sender_host_address, "]");
 
 
-if ((log_extra_selector & LX_incoming_port) != 0)
-  whofrom = string_append(whofrom, &wfsize, &wfptr, 2, ":", string_sprintf("%d",
-    sender_host_port));
+if (LOGGING(incoming_port))
+  whofrom = string_fmt_append(whofrom, ":%d", sender_host_port);
 
 
-if ((log_extra_selector & LX_incoming_interface) != 0)
-  whofrom = string_append(whofrom, &wfsize, &wfptr, 4, " I=[",
-    interface_address, "]:", string_sprintf("%d", interface_port));
+if (LOGGING(incoming_interface))
+  whofrom = string_fmt_append(whofrom, " I=[%s]:%d",
+    interface_address, interface_port);
 
 
-whofrom[wfptr] = 0;    /* Terminate the newly-built string */
+(void) string_from_gstring(whofrom);    /* Terminate the newly-built string */
 
 /* Check maximum number of connections. We do not check for reserved
 connections or unacceptable hosts here. That is done in the subprocess because
 
 /* Check maximum number of connections. We do not check for reserved
 connections or unacceptable hosts here. That is done in the subprocess because
@@ -225,10 +219,10 @@ if (smtp_accept_max > 0 && smtp_accept_count >= smtp_accept_max)
   DEBUG(D_any) debug_printf("rejecting SMTP connection: count=%d max=%d\n",
     smtp_accept_count, smtp_accept_max);
   smtp_printf("421 Too many concurrent SMTP connections; "
   DEBUG(D_any) debug_printf("rejecting SMTP connection: count=%d max=%d\n",
     smtp_accept_count, smtp_accept_max);
   smtp_printf("421 Too many concurrent SMTP connections; "
-    "please try again later.\r\n");
+    "please try again later.\r\n", FALSE);
   log_write(L_connection_reject,
             LOG_MAIN, "Connection from %s refused: too many connections",
   log_write(L_connection_reject,
             LOG_MAIN, "Connection from %s refused: too many connections",
-    whofrom);
+    whofrom->s);
   goto ERROR_RETURN;
   }
 
   goto ERROR_RETURN;
   }
 
@@ -244,10 +238,10 @@ if (smtp_load_reserve >= 0)
     {
     DEBUG(D_any) debug_printf("rejecting SMTP connection: load average = %.2f\n",
       (double)load_average/1000.0);
     {
     DEBUG(D_any) debug_printf("rejecting SMTP connection: load average = %.2f\n",
       (double)load_average/1000.0);
-    smtp_printf("421 Too much load; please try again later.\r\n");
+    smtp_printf("421 Too much load; please try again later.\r\n", FALSE);
     log_write(L_connection_reject,
               LOG_MAIN, "Connection from %s refused: load average = %.2f",
     log_write(L_connection_reject,
               LOG_MAIN, "Connection from %s refused: load average = %.2f",
-      whofrom, (double)load_average/1000.0);
+      whofrom->s, (double)load_average/1000.0);
     goto ERROR_RETURN;
     }
   }
     goto ERROR_RETURN;
     }
   }
@@ -265,9 +259,9 @@ if (smtp_accept_max_per_host != NULL)
   uschar *expanded = expand_string(smtp_accept_max_per_host);
   if (expanded == NULL)
     {
   uschar *expanded = expand_string(smtp_accept_max_per_host);
   if (expanded == NULL)
     {
-    if (!expand_string_forcedfail)
+    if (!f.expand_string_forcedfail)
       log_write(0, LOG_MAIN|LOG_PANIC, "expansion of smtp_accept_max_per_host "
       log_write(0, LOG_MAIN|LOG_PANIC, "expansion of smtp_accept_max_per_host "
-        "failed for %s: %s", whofrom, expand_string_message);
+        "failed for %s: %s", whofrom->s, expand_string_message);
     }
   /* For speed, interpret a decimal number inline here */
   else
     }
   /* For speed, interpret a decimal number inline here */
   else
@@ -277,7 +271,7 @@ if (smtp_accept_max_per_host != NULL)
       max_for_this_host = max_for_this_host * 10 + *s++ - '0';
     if (*s != 0)
       log_write(0, LOG_MAIN|LOG_PANIC, "expansion of smtp_accept_max_per_host "
       max_for_this_host = max_for_this_host * 10 + *s++ - '0';
     if (*s != 0)
       log_write(0, LOG_MAIN|LOG_PANIC, "expansion of smtp_accept_max_per_host "
-        "for %s contains non-digit: %s", whofrom, expanded);
+        "for %s contains non-digit: %s", whofrom->s, expanded);
     }
   }
 
     }
   }
 
@@ -293,8 +287,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)
   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++;
       {
       if (Ustrcmp(sender_host_address, smtp_slots[i].host_address) == 0)
        host_accept_count++;
@@ -309,7 +302,6 @@ if ((max_for_this_host > 0) &&
          ((smtp_accept_count - other_host_count) < max_for_this_host))
        break;
       }
          ((smtp_accept_count - other_host_count) < max_for_this_host))
        break;
       }
-    }
 
   if (host_accept_count >= max_for_this_host)
     {
 
   if (host_accept_count >= max_for_this_host)
     {
@@ -317,10 +309,10 @@ if ((max_for_this_host > 0) &&
       "IP address: count=%d max=%d\n",
       host_accept_count, max_for_this_host);
     smtp_printf("421 Too many concurrent SMTP connections "
       "IP address: count=%d max=%d\n",
       host_accept_count, max_for_this_host);
     smtp_printf("421 Too many concurrent SMTP connections "
-      "from this IP address; please try again later.\r\n");
+      "from this IP address; please try again later.\r\n", FALSE);
     log_write(L_connection_reject,
               LOG_MAIN, "Connection from %s refused: too many connections "
     log_write(L_connection_reject,
               LOG_MAIN, "Connection from %s refused: too many connections "
-      "from that IP address", whofrom);
+      "from that IP address", whofrom->s);
     goto ERROR_RETURN;
     }
   }
     goto ERROR_RETURN;
     }
   }
@@ -338,14 +330,15 @@ 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. */
 
 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;
   {
   uschar *list = hosts_connection_nolog;
+  memset(sender_host_cache, 0, sizeof(sender_host_cache));
   if (list != NULL && verify_check_host(&list) == OK)
   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 "
   else
     log_write(L_smtp_connection, LOG_MAIN, "SMTP connection from %s "
-      "(TCP/IP connection count = %d)", whofrom, smtp_accept_count + 1);
+      "(TCP/IP connection count = %d)", whofrom->s, smtp_accept_count + 1);
   }
 
 /* Now we can fork the accepting process; do a lookup tidy, just in case any
   }
 
 /* Now we can fork the accepting process; do a lookup tidy, just in case any
@@ -372,7 +365,7 @@ if (pid == 0)
 
   /* May have been modified for the subprocess */
 
 
   /* 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 */
 
 
   /* Get the local interface address into permanent store */
 
@@ -389,24 +382,24 @@ if (pid == 0)
   likely what it depends on.) */
 
   smtp_active_hostname = primary_hostname;
   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)
+      if (!f.expand_string_forcedfail)
         {
         log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand \"%s\" "
           "(smtp_active_hostname): %s", raw_active_hostname,
           expand_string_message);
         smtp_printf("421 Local configuration error; "
         {
         log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand \"%s\" "
           "(smtp_active_hostname): %s", raw_active_hostname,
           expand_string_message);
         smtp_printf("421 Local configuration error; "
-          "please try again later.\r\n");
+          "please try again later.\r\n", FALSE);
         mac_smtp_fflush();
         search_tidyup();
         _exit(EXIT_FAILURE);
         }
       }
         mac_smtp_fflush();
         search_tidyup();
         _exit(EXIT_FAILURE);
         }
       }
-    else if (nah[0] != 0) smtp_active_hostname = nah;
+    else if (*nah) smtp_active_hostname = nah;
     }
 
   /* Initialize the queueing flags */
     }
 
   /* Initialize the queueing flags */
@@ -448,7 +441,7 @@ if (pid == 0)
   finding the id, but turn it on again afterwards so that information about the
   incoming connection is output. */
 
   finding the id, but turn it on again afterwards so that information about the
   incoming connection is output. */
 
-  if (debug_daemon) debug_selector = 0;
+  if (f.debug_daemon) debug_selector = 0;
   verify_get_ident(IDENT_PORT);
   host_build_sender_fullhost();
   debug_selector = save_debug_selector;
   verify_get_ident(IDENT_PORT);
   host_build_sender_fullhost();
   debug_selector = save_debug_selector;
@@ -460,7 +453,7 @@ if (pid == 0)
   /* Now disable debugging permanently if it's required only for the daemon
   process. */
 
   /* Now disable debugging permanently if it's required only for the daemon
   process. */
 
-  if (debug_daemon) debug_selector = 0;
+  if (f.debug_daemon) debug_selector = 0;
 
   /* If there are too many child processes for immediate delivery,
   set the session_local_queue_only flag, which is initialized from the
 
   /* If there are too many child processes for immediate delivery,
   set the session_local_queue_only flag, which is initialized from the
@@ -514,6 +507,7 @@ if (pid == 0)
       search_tidyup();                    /* Close cached databases */
       if (!ok)                            /* Connection was dropped */
         {
       search_tidyup();                    /* Close cached databases */
       if (!ok)                            /* Connection was dropped */
         {
+       cancel_cutthrough_connection(TRUE, US"receive dropped");
         mac_smtp_fflush();
         smtp_log_no_mail();               /* Log no mail if configured */
         _exit(EXIT_SUCCESS);
         mac_smtp_fflush();
         smtp_log_no_mail();               /* Log no mail if configured */
         _exit(EXIT_SUCCESS);
@@ -522,10 +516,24 @@ if (pid == 0)
       }
     else
       {
       }
     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--;
+       }
+      cancel_cutthrough_connection(TRUE, US"message setup dropped");
       search_tidyup();
       smtp_log_no_mail();                 /* Log no mail if configured */
       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 */
       }
 
     /* Show the recipients when debugging */
@@ -533,9 +541,9 @@ if (pid == 0)
     DEBUG(D_receive)
       {
       int i;
     DEBUG(D_receive)
       {
       int i;
-      if (sender_address != NULL)
+      if (sender_address)
         debug_printf("Sender: %s\n", sender_address);
         debug_printf("Sender: %s\n", sender_address);
-      if (recipients_list != NULL)
+      if (recipients_list)
         {
         debug_printf("Recipients:\n");
         for (i = 0; i < recipients_count; i++)
         {
         debug_printf("Recipients:\n");
         for (i = 0; i < recipients_count; i++)
@@ -555,7 +563,13 @@ if (pid == 0)
 
     /* Reclaim up the store used in accepting this message */
 
 
     /* Reclaim up the store used in accepting this message */
 
-    store_reset(reset_point);
+      {
+      int r = receive_messagecount;
+      BOOL q = f.queue_only_policy;
+      smtp_reset(reset_point);
+      f.queue_only_policy = q;
+      receive_messagecount = r;
+      }
 
     /* If queue_only is set or if there are too many incoming connections in
     existence, session_local_queue_only will be TRUE. If it is not, check
 
     /* If queue_only is set or if there are too many incoming connections in
     existence, session_local_queue_only will be TRUE. If it is not, check
@@ -581,15 +595,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. */
 
     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
       }
 
     /* Log the queueing here, when it will get a message id attached, but
@@ -597,30 +609,27 @@ if (pid == 0)
 
     if (local_queue_only) switch(queue_only_reason)
       {
 
     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);
                 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);
                 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);
                 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.
     If we are not root, we have to re-exec exim unless deliveries are being
     done unprivileged. */
 
       }
 
     /* If a delivery attempt is required, spin off a new process to handle it.
     If we are not root, we have to re-exec exim unless deliveries are being
     done unprivileged. */
 
-    else if (!queue_only_policy && !deliver_freeze)
+    else if (!f.queue_only_policy && !f.deliver_freeze)
       {
       pid_t dpid;
 
       {
       pid_t dpid;
 
@@ -638,9 +647,9 @@ if (pid == 0)
         /* Don't ever molest the parent's SSL connection, but do clean up
         the data structures if necessary. */
 
         /* Don't ever molest the parent's SSL connection, but do clean up
         the data structures if necessary. */
 
-        #ifdef SUPPORT_TLS
-        tls_close(TRUE, FALSE);
-        #endif
+#ifdef SUPPORT_TLS
+        tls_close(NULL, TLS_NO_SHUTDOWN);
+#endif
 
         /* Reset SIGHUP and SIGCHLD in the child in both cases. */
 
 
         /* Reset SIGHUP and SIGCHLD in the child in both cases. */
 
@@ -650,27 +659,28 @@ if (pid == 0)
         if (geteuid() != root_uid && !deliver_drop_privilege)
           {
           signal(SIGALRM, SIG_DFL);
         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);
+         delivery_re_exec(CEE_EXEC_PANIC);
           /* Control does not return here. */
           }
 
         /* No need to re-exec; SIGALRM remains set to the default handler */
 
           /* Control does not return here. */
           }
 
         /* No need to re-exec; SIGALRM remains set to the default handler */
 
-        (void)deliver_message(message_id, FALSE, FALSE);
+        (void) deliver_message(message_id, FALSE, FALSE);
         search_tidyup();
         _exit(EXIT_SUCCESS);
         }
 
       if (dpid > 0)
         {
         search_tidyup();
         _exit(EXIT_SUCCESS);
         }
 
       if (dpid > 0)
         {
+       release_cutthrough_connection(US"passed for delivery");
         DEBUG(D_any) debug_printf("forked delivery process %d\n", (int)dpid);
         }
       else
         DEBUG(D_any) debug_printf("forked delivery process %d\n", (int)dpid);
         }
       else
-        {
+       {
+       cancel_cutthrough_connection(TRUE, US"delivery fork failed");
         log_write(0, LOG_MAIN|LOG_PANIC, "daemon: delivery process fork "
           "failed: %s", strerror(errno));
         log_write(0, LOG_MAIN|LOG_PANIC, "daemon: delivery process fork "
           "failed: %s", strerror(errno));
-        }
+       }
       }
     }
   }
       }
     }
   }
@@ -681,14 +691,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)
 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);
   never_error(US"daemon: accept process fork failed", US"Fork failed", errno);
-  }
 else
   {
   int i;
   for (i = 0; i < smtp_accept_max; ++i)
 else
   {
   int i;
   for (i = 0; i < smtp_accept_max; ++i)
-    {
     if (smtp_slots[i].pid <= 0)
       {
       smtp_slots[i].pid = pid;
     if (smtp_slots[i].pid <= 0)
       {
       smtp_slots[i].pid = pid;
@@ -697,7 +704,6 @@ else
       smtp_accept_count++;
       break;
       }
       smtp_accept_count++;
       break;
       }
-    }
   DEBUG(D_any) debug_printf("%d SMTP accept process%s running\n",
     smtp_accept_count, (smtp_accept_count == 1)? "" : "es");
   }
   DEBUG(D_any) debug_printf("%d SMTP accept process%s running\n",
     smtp_accept_count, (smtp_accept_count == 1)? "" : "es");
   }
@@ -714,7 +720,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. */
 
 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",
   {
   if (fclose(smtp_out) != 0 && errno != ECONNRESET && errno != EPIPE)
     log_write(0, LOG_MAIN|LOG_PANIC, "daemon: fclose(smtp_out) failed: %s",
@@ -723,7 +729,7 @@ if (smtp_out != NULL)
   }
 else (void)close(accept_socket);
 
   }
 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",
   {
   if (fclose(smtp_in) != 0 && errno != ECONNRESET && errno != EPIPE)
     log_write(0, LOG_MAIN|LOG_PANIC, "daemon: fclose(smtp_in) failed: %s",
@@ -735,6 +741,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. */
 
 /* 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;
 }
 store_reset(reset_point);
 sender_host_address = NULL;
 }
@@ -843,13 +852,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 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++)
     {
     for (i = 0; i < smtp_accept_max; i++)
-      {
       if (smtp_slots[i].pid == pid)
         {
       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;
           store_free(smtp_slots[i].host_address);
         smtp_slots[i] = empty_smtp_slot;
         if (--smtp_accept_count < 0) smtp_accept_count = 0;
@@ -857,17 +865,16 @@ while ((pid = waitpid(-1, &status, WNOHANG)) > 0)
           smtp_accept_count, (smtp_accept_count == 1)? "" : "es");
         break;
         }
           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 (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;
       if (queue_pid_slots[i] == pid)
         {
         queue_pid_slots[i] = 0;
@@ -876,7 +883,6 @@ while ((pid = waitpid(-1, &status, WNOHANG)) > 0)
           queue_run_count, (queue_run_count == 1)? "" : "es");
         break;
         }
           queue_run_count, (queue_run_count == 1)? "" : "es");
         break;
         }
-      }
     }
   }
 }
     }
   }
 }
@@ -914,24 +920,22 @@ int *listen_sockets = NULL;
 int listen_socket_count = 0;
 ip_address_item *addresses = NULL;
 time_t last_connection_time = (time_t)0;
 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. */
 
 DEBUG(D_any|D_v) debug_selector |= D_pid;
 
 
 /* If any debugging options are set, turn on the D_pid bit so that all
 debugging lines get the pid added. */
 
 DEBUG(D_any|D_v) debug_selector |= D_pid;
 
-if (inetd_wait_mode)
+if (f.inetd_wait_mode)
   {
   {
-  int on = 1;
-
   listen_socket_count = 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)
   (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));
     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);
   listen_sockets[0] = 3;
   (void) close(0);
   (void) close(1);
@@ -955,12 +959,14 @@ 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. */
 
   /* 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));
   }
 
 
   }
 
 
-if (inetd_wait_mode || daemon_listen)
+if (f.inetd_wait_mode || f.daemon_listen)
   {
   /* If any option requiring a load average to be available during the
   reception of a message is set, call os_getloadavg() while we are root
   {
   /* If any option requiring a load average to be available during the
   reception of a message is set, call os_getloadavg() while we are root
@@ -1042,13 +1048,13 @@ The preparation code decodes options and sets up the relevant data. We do this
 first, so that we can return non-zero if there are any syntax errors, and also
 write to stderr. */
 
 first, so that we can return non-zero if there are any syntax errors, and also
 write to stderr. */
 
-if (daemon_listen && !inetd_wait_mode)
+if (f.daemon_listen && !f.inetd_wait_mode)
   {
   int *default_smtp_port;
   int sep;
   int pct = 0;
   uschar *s;
   {
   int *default_smtp_port;
   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;
   uschar *local_iface_source = US"local_interfaces";
   ip_address_item *ipa;
   ip_address_item **pipa;
@@ -1058,63 +1064,46 @@ if (daemon_listen && !inetd_wait_mode)
   that contain neither a dot nor a colon are used to override daemon_smtp_port.
   Any other items are used to override local_interfaces. */
 
   that contain neither a dot nor a colon are used to override daemon_smtp_port.
   Any other items are used to override local_interfaces. */
 
-  if (override_local_interfaces != NULL)
+  if (override_local_interfaces)
     {
     {
-    uschar *new_smtp_port = NULL;
-    uschar *new_local_interfaces = NULL;
-    int portsize = 0;
-    int portptr = 0;
-    int ifacesize = 0;
-    int ifaceptr = 0;
+    gstring * new_smtp_port = NULL;
+    gstring * new_local_interfaces = NULL;
 
     if (override_pid_file_path == NULL) write_pid = FALSE;
 
     list = override_local_interfaces;
     sep = 0;
 
     if (override_pid_file_path == NULL) write_pid = FALSE;
 
     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 joinstr[4];
-      uschar **ptr;
-      int *sizeptr;
-      int *ptrptr;
+      gstring ** gp;
 
       if (Ustrpbrk(s, ".:") == NULL)
 
       if (Ustrpbrk(s, ".:") == NULL)
-        {
-        ptr = &new_smtp_port;
-        sizeptr = &portsize;
-        ptrptr = &portptr;
-        }
+        gp = &new_smtp_port;
       else
       else
-        {
-        ptr = &new_local_interfaces;
-        sizeptr = &ifacesize;
-        ptrptr = &ifaceptr;
-        }
+        gp = &new_local_interfaces;
 
 
-      if (*ptr == NULL)
+      if (!*gp)
         {
         joinstr[0] = sep;
         joinstr[1] = ' ';
         {
         joinstr[0] = sep;
         joinstr[1] = ' ';
-        *ptr = string_cat(*ptr, sizeptr, ptrptr, US"<", 1);
+        *gp = string_catn(*gp, US"<", 1);
         }
 
         }
 
-      *ptr = string_cat(*ptr, sizeptr, ptrptr, joinstr, 2);
-      *ptr = string_cat(*ptr, sizeptr, ptrptr, s, Ustrlen(s));
+      *gp = string_catn(*gp, joinstr, 2);
+      *gp = string_cat (*gp, s);
       }
 
       }
 
-    if (new_smtp_port != NULL)
+    if (new_smtp_port)
       {
       {
-      new_smtp_port[portptr] = 0;
-      daemon_smtp_port = new_smtp_port;
+      daemon_smtp_port = string_from_gstring(new_smtp_port);
       DEBUG(D_any) debug_printf("daemon_smtp_port overridden by -oX:\n  %s\n",
         daemon_smtp_port);
       }
 
       DEBUG(D_any) debug_printf("daemon_smtp_port overridden by -oX:\n  %s\n",
         daemon_smtp_port);
       }
 
-    if (new_local_interfaces != NULL)
+    if (new_local_interfaces)
       {
       {
-      new_local_interfaces[ifaceptr] = 0;
-      local_interfaces = new_local_interfaces;
+      local_interfaces = string_from_gstring(new_local_interfaces);
       local_iface_source = US"-oX data";
       DEBUG(D_any) debug_printf("local_interfaces overridden by -oX:\n  %s\n",
         local_interfaces);
       local_iface_source = US"-oX data";
       DEBUG(D_any) debug_printf("local_interfaces overridden by -oX:\n  %s\n",
         local_interfaces);
@@ -1122,18 +1111,18 @@ if (daemon_listen && !inetd_wait_mode)
     }
 
   /* Create a list of default SMTP ports, to be used if local_interfaces
     }
 
   /* 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;
   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;
     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))
        pct++)
     {
     if (isdigit(*s))
@@ -1160,6 +1149,8 @@ if (daemon_listen && !inetd_wait_mode)
   while ((s = string_nextinlist(&list, &sep, big_buffer, big_buffer_size)))
     if (!isdigit(*s))
       {
   while ((s = string_nextinlist(&list, &sep, big_buffer, big_buffer_size)))
     if (!isdigit(*s))
       {
+      gstring * g = NULL;
+
       list = tls_in.on_connect_ports;
       tls_in.on_connect_ports = NULL;
       sep = 0;
       list = tls_in.on_connect_ports;
       tls_in.on_connect_ports = NULL;
       sep = 0;
@@ -1167,14 +1158,15 @@ if (daemon_listen && !inetd_wait_mode)
        {
         if (!isdigit(*s))
          {
        {
         if (!isdigit(*s))
          {
-         struct servent *smtp_service = getservbyname(CS s, "tcp");
+         struct servent * smtp_service = getservbyname(CS s, "tcp");
          if (!smtp_service)
            log_write(0, LOG_PANIC_DIE|LOG_CONFIG, "TCP port \"%s\" not found", s);
          if (!smtp_service)
            log_write(0, LOG_PANIC_DIE|LOG_CONFIG, "TCP port \"%s\" not found", s);
-         s= string_sprintf("%d", (int)ntohs(smtp_service->s_port));
+         s = string_sprintf("%d", (int)ntohs(smtp_service->s_port));
          }
          }
-       tls_in.on_connect_ports = string_append_listele(tls_in.on_connect_ports,
-           ':', s);
+       g = string_append_listele(g, ':', s);
        }
        }
+      if (g)
+       tls_in.on_connect_ports = g->s;
       break;
       }
 
       break;
       }
 
@@ -1193,11 +1185,12 @@ if (daemon_listen && !inetd_wait_mode)
   In the same scan, fill in missing port numbers from the default list. When
   there is more than one item in the list, extra items are created. */
 
   In the same scan, fill in missing port numbers from the default list. When
   there is more than one item in the list, extra items are created. */
 
-  for (ipa = addresses; ipa != NULL; ipa = ipa->next)
+  for (ipa = addresses; ipa; ipa = ipa->next)
     {
     int i;
 
     {
     int i;
 
-    if (Ustrcmp(ipa->address, "0.0.0.0") == 0) ipa->address[0] = 0;
+    if (Ustrcmp(ipa->address, "0.0.0.0") == 0)
+      ipa->address[0] = 0;
     else if (Ustrcmp(ipa->address, "::0") == 0)
       {
       ipa->address[0] = ':';
     else if (Ustrcmp(ipa->address, "::0") == 0)
       {
       ipa->address[0] = ':';
@@ -1209,12 +1202,14 @@ if (daemon_listen && !inetd_wait_mode)
     if (daemon_smtp_port[0] <= 0)
       log_write(0, LOG_MAIN|LOG_PANIC_DIE, "no port specified for interface "
         "%s and daemon_smtp_port is unset; cannot start daemon",
     if (daemon_smtp_port[0] <= 0)
       log_write(0, LOG_MAIN|LOG_PANIC_DIE, "no port specified for interface "
         "%s and daemon_smtp_port is unset; cannot start daemon",
-        (ipa->address[0] == 0)? US"\"all IPv4\"" :
-        (ipa->address[1] == 0)? US"\"all IPv6\"" : ipa->address);
+        ipa->address[0] == 0 ? US"\"all IPv4\"" :
+        ipa->address[1] == 0 ? US"\"all IPv6\"" : ipa->address);
+
     ipa->port = default_smtp_port[0];
     for (i = 1; default_smtp_port[i] > 0; i++)
       {
       ip_address_item *new = store_get(sizeof(ip_address_item));
     ipa->port = default_smtp_port[0];
     for (i = 1; default_smtp_port[i] > 0; i++)
       {
       ip_address_item *new = store_get(sizeof(ip_address_item));
+
       memcpy(new->address, ipa->address, Ustrlen(ipa->address) + 1);
       new->port = default_smtp_port[i];
       new->next = ipa->next;
       memcpy(new->address, ipa->address, Ustrlen(ipa->address) + 1);
       new->port = default_smtp_port[i];
       new->next = ipa->next;
@@ -1229,15 +1224,14 @@ if (daemon_listen && !inetd_wait_mode)
   also simplifies the construction of the "daemon started" log line. */
 
   pipa = &addresses;
   also simplifies the construction of the "daemon started" log line. */
 
   pipa = &addresses;
-  for (ipa = addresses; ipa != NULL; pipa = &(ipa->next), ipa = ipa->next)
+  for (ipa = addresses; ipa; pipa = &ipa->next, ipa = ipa->next)
     {
     ip_address_item *ipa2;
 
     /* Handle an IPv4 wildcard */
 
     if (ipa->address[0] == 0)
     {
     ip_address_item *ipa2;
 
     /* Handle an IPv4 wildcard */
 
     if (ipa->address[0] == 0)
-      {
-      for (ipa2 = ipa; ipa2->next != NULL; ipa2 = ipa2->next)
+      for (ipa2 = ipa; ipa2->next; ipa2 = ipa2->next)
         {
         ip_address_item *ipa3 = ipa2->next;
         if (ipa3->address[0] == ':' &&
         {
         ip_address_item *ipa3 = ipa2->next;
         if (ipa3->address[0] == ':' &&
@@ -1250,13 +1244,11 @@ if (daemon_listen && !inetd_wait_mode)
           break;
           }
         }
           break;
           }
         }
-      }
 
     /* Handle an IPv6 wildcard. */
 
     else if (ipa->address[0] == ':' && ipa->address[1] == 0)
 
     /* Handle an IPv6 wildcard. */
 
     else if (ipa->address[0] == ':' && ipa->address[1] == 0)
-      {
-      for (ipa2 = ipa; ipa2->next != NULL; ipa2 = ipa2->next)
+      for (ipa2 = ipa; ipa2->next; ipa2 = ipa2->next)
         {
         ip_address_item *ipa3 = ipa2->next;
         if (ipa3->address[0] == 0 && ipa3->port == ipa->port)
         {
         ip_address_item *ipa3 = ipa2->next;
         if (ipa3->address[0] == 0 && ipa3->port == ipa->port)
@@ -1268,18 +1260,17 @@ if (daemon_listen && !inetd_wait_mode)
           break;
           }
         }
           break;
           }
         }
-      }
     }
 
   /* Get a vector to remember all the sockets in */
 
     }
 
   /* Get a vector to remember all the sockets in */
 
-  for (ipa = addresses; ipa != NULL; ipa = ipa->next)
+  for (ipa = addresses; ipa; ipa = ipa->next)
     listen_socket_count++;
     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 */
 
 
   } /* daemon_listen but not inetd_wait_mode */
 
-if (daemon_listen)
+if (f.daemon_listen)
   {
 
   /* Do a sanity check on the max connects value just to save us from getting
   {
 
   /* Do a sanity check on the max connects value just to save us from getting
@@ -1319,7 +1310,7 @@ Then disconnect from the controlling terminal, Most modern Unixes seem to have
 setsid() for getting rid of the controlling terminal. For any OS that doesn't,
 setsid() can be #defined as a no-op, or as something else. */
 
 setsid() for getting rid of the controlling terminal. For any OS that doesn't,
 setsid() can be #defined as a no-op, or as something else. */
 
-if (background_daemon || inetd_wait_mode)
+if (f.background_daemon || f.inetd_wait_mode)
   {
   log_close_all();    /* Just in case anything was logged earlier */
   search_tidyup();    /* Just in case any were used in reading the config. */
   {
   log_close_all();    /* Just in case anything was logged earlier */
   search_tidyup();    /* Just in case any were used in reading the config. */
@@ -1330,7 +1321,7 @@ if (background_daemon || inetd_wait_mode)
   log_stderr = NULL;  /* So no attempt to copy paniclog output */
   }
 
   log_stderr = NULL;  /* So no attempt to copy paniclog output */
   }
 
-if (background_daemon)
+if (f.background_daemon)
   {
   /* If the parent process of this one has pid == 1, we are re-initializing the
   daemon as the result of a SIGHUP. In this case, there is no need to do
   {
   /* If the parent process of this one has pid == 1, we are re-initializing the
   daemon as the result of a SIGHUP. In this case, there is no need to do
@@ -1351,10 +1342,9 @@ if (background_daemon)
 /* We are now in the disconnected, daemon process (unless debugging). Set up
 the listening sockets if required. */
 
 /* We are now in the disconnected, daemon process (unless debugging). Set up
 the listening sockets if required. */
 
-if (daemon_listen && !inetd_wait_mode)
+if (f.daemon_listen && !f.inetd_wait_mode)
   {
   int sk;
   {
   int sk;
-  int on = 1;
   ip_address_item *ipa;
 
   /* For each IP address, create a socket, bind it to the appropriate port, and
   ip_address_item *ipa;
 
   /* For each IP address, create a socket, bind it to the appropriate port, and
@@ -1380,8 +1370,7 @@ if (daemon_listen && !inetd_wait_mode)
       wildcard = ipa->address[0] == 0;
       }
 
       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))
         {
       {
       if (check_special_case(0, addresses, ipa, FALSE))
         {
@@ -1397,20 +1386,20 @@ if (daemon_listen && !inetd_wait_mode)
     available. Just log failure (can get protocol not available, just like
     socket creation can). */
 
     available. Just log failure (can get protocol not available, just like
     socket creation can). */
 
-    #ifdef IPV6_V6ONLY
+#ifdef IPV6_V6ONLY
     if (af == AF_INET6 && wildcard &&
     if (af == AF_INET6 && wildcard &&
-        setsockopt(listen_sockets[sk], IPPROTO_IPV6, IPV6_V6ONLY, (char *)(&on),
+        setsockopt(listen_sockets[sk], IPPROTO_IPV6, IPV6_V6ONLY, CS (&on),
           sizeof(on)) < 0)
       log_write(0, LOG_MAIN, "Setting IPV6_V6ONLY on daemon's IPv6 wildcard "
         "socket failed (%s): carrying on without it", strerror(errno));
           sizeof(on)) < 0)
       log_write(0, LOG_MAIN, "Setting IPV6_V6ONLY on daemon's IPv6 wildcard "
         "socket failed (%s): carrying on without it", strerror(errno));
-    #endif  /* IPV6_V6ONLY */
+#endif  /* IPV6_V6ONLY */
 
     /* Set SO_REUSEADDR so that the daemon can be restarted while a connection
     is being handled.  Without this, a connection will prevent reuse of the
     smtp port for listening. */
 
     if (setsockopt(listen_sockets[sk], SOL_SOCKET, SO_REUSEADDR,
 
     /* Set SO_REUSEADDR so that the daemon can be restarted while a connection
     is being handled.  Without this, a connection will prevent reuse of the
     smtp port for listening. */
 
     if (setsockopt(listen_sockets[sk], SOL_SOCKET, SO_REUSEADDR,
-                   (uschar *)(&on), sizeof(on)) < 0)
+                   US (&on), sizeof(on)) < 0)
       log_write(0, LOG_MAIN|LOG_PANIC_DIE, "setting SO_REUSEADDR on socket "
         "failed when starting daemon: %s", strerror(errno));
 
       log_write(0, LOG_MAIN|LOG_PANIC_DIE, "setting SO_REUSEADDR on socket "
         "failed when starting daemon: %s", strerror(errno));
 
@@ -1418,7 +1407,7 @@ if (daemon_listen && !inetd_wait_mode)
     disable this because it breaks some broken clients. */
 
     if (tcp_nodelay) setsockopt(listen_sockets[sk], IPPROTO_TCP, TCP_NODELAY,
     disable this because it breaks some broken clients. */
 
     if (tcp_nodelay) setsockopt(listen_sockets[sk], IPPROTO_TCP, TCP_NODELAY,
-      (uschar *)(&on), sizeof(on));
+      US (&on), sizeof(on));
 
     /* Now bind the socket to the required port; if Exim is being restarted
     it may not always be possible to bind immediately, even with SO_REUSEADDR
 
     /* Now bind the socket to the required port; if Exim is being restarted
     it may not always be possible to bind immediately, even with SO_REUSEADDR
@@ -1430,6 +1419,9 @@ if (daemon_listen && !inetd_wait_mode)
     necessary for (some release of) USAGI Linux; other IP stacks fail at the
     listen() stage instead. */
 
     necessary for (some release of) USAGI Linux; other IP stacks fail at the
     listen() stage instead. */
 
+#ifdef TCP_FASTOPEN
+    f.tcp_fastopen_ok = TRUE;
+#endif
     for(;;)
       {
       uschar *msg, *addr;
     for(;;)
       {
       uschar *msg, *addr;
@@ -1442,8 +1434,11 @@ if (daemon_listen && !inetd_wait_mode)
         goto SKIP_SOCKET;
         }
       msg = US strerror(errno);
         goto SKIP_SOCKET;
         }
       msg = US strerror(errno);
-      addr = wildcard? ((af == AF_INET6)? US"(any IPv6)" : US"(any IPv4)") :
-        ipa->address;
+      addr = wildcard
+        ? af == AF_INET6
+       ? US"(any IPv6)"
+       : US"(any IPv4)"
+       : ipa->address;
       if (daemon_startup_retries <= 0)
         log_write(0, LOG_MAIN|LOG_PANIC_DIE,
           "socket bind() to port %d for address %s failed: %s: "
       if (daemon_startup_retries <= 0)
         log_write(0, LOG_MAIN|LOG_PANIC_DIE,
           "socket bind() to port %d for address %s failed: %s: "
@@ -1457,18 +1452,38 @@ if (daemon_listen && !inetd_wait_mode)
       }
 
     DEBUG(D_any)
       }
 
     DEBUG(D_any)
-      {
       if (wildcard)
         debug_printf("listening on all interfaces (IPv%c) port %d\n",
       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);
       else
         debug_printf("listening on %s port %d\n", ipa->address, ipa->port);
+
+#if defined(TCP_FASTOPEN) && !defined(__APPLE__)
+    if (  f.tcp_fastopen_ok
+       && 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));
+      f.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. */
 
 
     /* Start listening on the bound socket, establishing the maximum backlog of
     connections that is allowed. On success, continue to the next address. */
 
-    if (listen(listen_sockets[sk], smtp_connect_backlog) >= 0) continue;
+    if (listen(listen_sockets[sk], smtp_connect_backlog) >= 0)
+      {
+#if defined(TCP_FASTOPEN) && defined(__APPLE__)
+      if (  f.tcp_fastopen_ok
+        && setsockopt(listen_sockets[sk], IPPROTO_TCP, TCP_FASTOPEN,
+                     &on, sizeof(on)))
+       {
+       DEBUG(D_any) debug_printf("setsockopt FASTOPEN: %s\n", strerror(errno));
+       f.tcp_fastopen_ok = FALSE;
+       }
+#endif
+      continue;
+      }
 
     /* Listening has failed. In an IPv6 environment, as for bind(), if listen()
     fails with the error EADDRINUSE and we are doing IPv4 wildcard listening
 
     /* Listening has failed. In an IPv6 environment, as for bind(), if listen()
     fails with the error EADDRINUSE and we are doing IPv4 wildcard listening
@@ -1478,8 +1493,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",
 
     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 "
         strerror(errno));
 
     DEBUG(D_any) debug_printf("wildcard IPv4 listen() failed after IPv6 "
@@ -1490,7 +1505,7 @@ if (daemon_listen && !inetd_wait_mode)
     are going to ignore. We remove the address from the chain, and back up the
     counts. */
 
     are going to ignore. We remove the address from the chain, and back up the
     counts. */
 
-    SKIP_SOCKET:
+  SKIP_SOCKET:
     sk--;                          /* Back up the count */
     listen_socket_count--;         /* Reduce the total */
     if (ipa == addresses) addresses = ipa->next; else
     sk--;                          /* Back up the count */
     listen_socket_count--;         /* Reduce the total */
     if (ipa == addresses) addresses = ipa->next; else
@@ -1506,7 +1521,8 @@ if (daemon_listen && !inetd_wait_mode)
 /* If we are not listening, we want to write a pid file only if -oP was
 explicitly given. */
 
 /* If we are not listening, we want to write a pid file only if -oP was
 explicitly given. */
 
-else if (override_pid_file_path == NULL) write_pid = FALSE;
+else if (!override_pid_file_path)
+  write_pid = FALSE;
 
 /* Write the pid to a known file for assistance in identification, if required.
 We do this before giving up root privilege, because on some systems it is
 
 /* Write the pid to a known file for assistance in identification, if required.
 We do this before giving up root privilege, because on some systems it is
@@ -1522,29 +1538,26 @@ automatically. Consequently, Exim 4 writes a pid file only
 
 The variable daemon_write_pid is used to control this. */
 
 
 The variable daemon_write_pid is used to control this. */
 
-if (running_in_test_harness || write_pid)
+if (f.running_in_test_harness || write_pid)
   {
   FILE *f;
 
   {
   FILE *f;
 
-  if (override_pid_file_path != NULL)
+  if (override_pid_file_path)
     pid_file_path = override_pid_file_path;
 
   if (pid_file_path[0] == 0)
     pid_file_path = string_sprintf("%s/exim-daemon.pid", spool_directory);
 
     pid_file_path = override_pid_file_path;
 
   if (pid_file_path[0] == 0)
     pid_file_path = string_sprintf("%s/exim-daemon.pid", spool_directory);
 
-  f = modefopen(pid_file_path, "wb", 0644);
-  if (f != NULL)
+  if ((f = modefopen(pid_file_path, "wb", 0644)))
     {
     (void)fprintf(f, "%d\n", (int)getpid());
     (void)fclose(f);
     DEBUG(D_any) debug_printf("pid written to %s\n", pid_file_path);
     }
   else
     {
     (void)fprintf(f, "%d\n", (int)getpid());
     (void)fclose(f);
     DEBUG(D_any) debug_printf("pid written to %s\n", pid_file_path);
     }
   else
-    {
     DEBUG(D_any)
       debug_printf("%s\n", string_open_failed(errno, "pid file %s",
         pid_file_path));
     DEBUG(D_any)
       debug_printf("%s\n", string_open_failed(errno, "pid file %s",
         pid_file_path));
-    }
   }
 
 /* Set up the handler for SIGHUP, which causes a restart of the daemon. */
   }
 
 /* Set up the handler for SIGHUP, which causes a restart of the daemon. */
@@ -1571,11 +1584,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). */
 
 /* 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;
   {
   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. */
   }
 
 /* Set up the handler for termination of child processes. */
@@ -1591,7 +1604,7 @@ sigalrm_seen = (queue_interval > 0);
 /* Log the start up of a daemon - at least one of listening or queue running
 must be set up. */
 
 /* Log the start up of a daemon - at least one of listening or queue running
 must be set up. */
 
-if (inetd_wait_mode)
+if (f.inetd_wait_mode)
   {
   uschar *p = big_buffer;
 
   {
   uschar *p = big_buffer;
 
@@ -1603,23 +1616,22 @@ 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);
   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;
   }
 
 
   /* set up the timeout logic */
   sigalrm_seen = 1;
   }
 
-else if (daemon_listen)
+else if (f.daemon_listen)
   {
   int i, j;
   int smtp_ports = 0;
   int smtps_ports = 0;
   {
   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, * i2;
+  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.
 
   /* Build a list of listening addresses in big_buffer, but limit it to 10
   items. The style is for backwards compatibility.
@@ -1630,55 +1642,63 @@ else if (daemon_listen)
 
   for (j = 0; j < 2; j++)
     {
 
   for (j = 0; j < 2; j++)
     {
-    for (i = 0, ipa = addresses; i < 10 && ipa != NULL; 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)
-             {
-             memcpy(p, "SMTP on", 8);
-             p += 7;
-             }
-           }
-         else
-           {
-           if (smtps_ports++ == 0)
-             {
-             (void)sprintf(CS p, "%sSMTPS on",
-               (smtp_ports == 0)? "":" and for ");
-             while (*p != 0) p++;
-             }
-           }
-
-         /* Now the information about the port (and sometimes interface) */
-
-         if (ipa->address[0] == ':' && ipa->address[1] == 0)
-           {
-           if (ipa->next != NULL && ipa->next->address[0] == 0 &&
-               ipa->next->port == ipa->port)
-             {
-             (void)sprintf(CS p, " port %d (IPv6 and IPv4)", ipa->port);
-             ipa = ipa->next;
-             }
-           else if (ipa->v6_include_v4)
-             (void)sprintf(CS p, " port %d (IPv6 with IPv4)", ipa->port);
-           else
-             (void)sprintf(CS p, " port %d (IPv6)", ipa->port);
-           }
-         else if (ipa->address[0] == 0)
-           (void)sprintf(CS p, " port %d (IPv4)", ipa->port);
-         else
-           (void)sprintf(CS p, " [%s]:%d", ipa->address, ipa->port);
-         while (*p != 0) p++;
-         }
-       }
-
-    if (ipa != NULL)
+    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)
+           {
+           memcpy(p, "SMTP on", 8);
+           p += 7;
+           }
+         }
+       else
+         if (smtps_ports++ == 0)
+           p += sprintf(CS p, "%sSMTPS on",
+             smtp_ports == 0 ? "" : " and for ");
+
+       /* Now the information about the port (and sometimes interface) */
+
+       if (ipa->address[0] == ':' && ipa->address[1] == 0)
+         {                                             /* v6 wildcard */
+         if (ipa->next && ipa->next->address[0] == 0 &&
+             ipa->next->port == ipa->port)
+           {
+           p += sprintf(CS p, " port %d (IPv6 and IPv4)", ipa->port);
+           ipa = ipa->next;
+           }
+         else if (ipa->v6_include_v4)
+           p += sprintf(CS p, " port %d (IPv6 with IPv4)", ipa->port);
+         else
+           p += sprintf(CS p, " port %d (IPv6)", ipa->port);
+         }
+       else if (ipa->address[0] == 0)                  /* v4 wildcard */
+         p += sprintf(CS p, " port %d (IPv4)", ipa->port);
+       else                            /* check for previously-seen IP */
+         {
+         for (i2 = addresses; i2 != ipa; i2 = i2->next)
+           if (  host_is_tls_on_connect_port(i2->port) == (j > 0)
+              && Ustrcmp(ipa->address, i2->address) == 0
+              )
+             {                         /* found; append port to list */
+             if (p[-1] == '}') p--;
+             while (isdigit(*--p)) ;
+             p +=  1 + sprintf(CS p+1, "%s%d,%d}", *p == ',' ? "" : "{",
+               i2->port, ipa->port);
+             break;
+             }
+         if (i2 == ipa)                /* first-time IP */
+           p += sprintf(CS p, " [%s]:%d", ipa->address, ipa->port);
+         }
+       }
+      }
+
+    if (ipa)
       {
       memcpy(p, " ...", 5);
       p += 4;
       {
       memcpy(p, " ...", 5);
       p += 4;
@@ -1688,18 +1708,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);
   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
   {
   }
 
 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,
   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
 
 /* 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
@@ -1769,7 +1800,7 @@ for (;;)
         }
 
       sigalrm_seen = FALSE;
         }
 
       sigalrm_seen = FALSE;
-      alarm(resignal_interval);
+      ALARM(resignal_interval);
       }
 
     else
       }
 
     else
@@ -1781,7 +1812,7 @@ for (;;)
       re-exec is required. */
 
       if (queue_interval > 0 &&
       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)
           {
         {
         if ((pid = fork()) == 0)
           {
@@ -1794,7 +1825,7 @@ for (;;)
           leave the above message, because it ties up with the "child ended"
           debugging messages. */
 
           leave the above message, because it ties up with the "child ended"
           debugging messages. */
 
-          if (debug_daemon) debug_selector = 0;
+          if (f.debug_daemon) debug_selector = 0;
 
           /* Close any open listening sockets in the child */
 
 
           /* Close any open listening sockets in the child */
 
@@ -1819,27 +1850,28 @@ for (;;)
             signal(SIGALRM, SIG_DFL);
             *p++ = '-';
             *p++ = 'q';
             signal(SIGALRM, SIG_DFL);
             *p++ = '-';
             *p++ = 'q';
-            if (queue_2stage) *p++ = 'q';
-            if (queue_run_first_delivery) *p++ = 'i';
-            if (queue_run_force) *p++ = 'f';
-            if (deliver_force_thaw) *p++ = 'f';
-            if (queue_run_local) *p++ = 'l';
+            if (f.queue_2stage) *p++ = 'q';
+            if (f.queue_run_first_delivery) *p++ = 'i';
+            if (f.queue_run_force) *p++ = 'f';
+            if (f.deliver_force_thaw) *p++ = 'f';
+            if (f.queue_run_local) *p++ = 'l';
             *p = 0;
             *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 -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++] = f.deliver_selectstring_regex ? US"-Rr" : US"-R";
               extra[extracount++] = deliver_selectstring;
               }
 
               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++] = f.deliver_selectstring_sender_regex
+               ? US"-Sr" : US"-S";
               extra[extracount++] = deliver_selectstring_sender;
               }
 
               extra[extracount++] = deliver_selectstring_sender;
               }
 
@@ -1866,15 +1898,13 @@ for (;;)
         else
           {
           int i;
         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;
               }
             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");
           }
           DEBUG(D_any) debug_printf("%d queue-runner process%s running\n",
             queue_run_count, (queue_run_count == 1)? "" : "es");
           }
@@ -1883,7 +1913,7 @@ for (;;)
       /* Reset the alarm clock */
 
       sigalrm_seen = FALSE;
       /* Reset the alarm clock */
 
       sigalrm_seen = FALSE;
-      alarm(queue_interval);
+      ALARM(queue_interval);
       }
 
     } /* sigalrm_seen */
       }
 
     } /* sigalrm_seen */
@@ -1898,7 +1928,7 @@ for (;;)
   new OS. In fact, the later addition of listening on specific interfaces only
   requires this way of working anyway. */
 
   new OS. In fact, the later addition of listening on specific interfaces only
   requires this way of working anyway. */
 
-  if (daemon_listen)
+  if (f.daemon_listen)
     {
     int sk, lcount, select_errno;
     int max_socket = 0;
     {
     int sk, lcount, select_errno;
     int max_socket = 0;
@@ -1927,10 +1957,8 @@ for (;;)
       errno = EINTR;
       }
     else
       errno = EINTR;
       }
     else
-      {
       lcount = select(max_socket + 1, (SELECT_ARG2_TYPE *)&select_listen,
         NULL, NULL, NULL);
       lcount = select(max_socket + 1, (SELECT_ARG2_TYPE *)&select_listen,
         NULL, NULL, NULL);
-      }
 
     if (lcount < 0)
       {
 
     if (lcount < 0)
       {
@@ -1956,10 +1984,9 @@ for (;;)
     while (lcount-- > 0)
       {
       int accept_socket = -1;
     while (lcount-- > 0)
       {
       int accept_socket = -1;
+
       if (!select_failed)
       if (!select_failed)
-        {
         for (sk = 0; sk < listen_socket_count; sk++)
         for (sk = 0; sk < listen_socket_count; sk++)
-          {
           if (FD_ISSET(listen_sockets[sk], &select_listen))
             {
             len = sizeof(accepted);
           if (FD_ISSET(listen_sockets[sk], &select_listen))
             {
             len = sizeof(accepted);
@@ -1968,8 +1995,6 @@ for (;;)
             FD_CLR(listen_sockets[sk], &select_listen);
             break;
             }
             FD_CLR(listen_sockets[sk], &select_listen);
             break;
             }
-          }
-        }
 
       /* If select or accept has failed and this was not caused by an
       interruption, log the incident and try again. With asymmetric TCP/IP
 
       /* If select or accept has failed and this was not caused by an
       interruption, log the incident and try again. With asymmetric TCP/IP
@@ -2075,7 +2100,7 @@ for (;;)
       getpid());
     for (sk = 0; sk < listen_socket_count; sk++)
       (void)close(listen_sockets[sk]);
       getpid());
     for (sk = 0; sk < listen_socket_count; sk++)
       (void)close(listen_sockets[sk]);
-    alarm(0);
+    ALARM_CLR(0);
     signal(SIGHUP, SIG_IGN);
     sighup_argv[0] = exim_path;
     exim_nullstd();
     signal(SIGHUP, SIG_IGN);
     sighup_argv[0] = exim_path;
     exim_nullstd();
diff --git a/src/dane-openssl.c b/src/dane-openssl.c
new file mode 100644 (file)
index 0000000..f7ccbd7
--- /dev/null
@@ -0,0 +1,1721 @@
+/*
+ *  Author: Viktor Dukhovni
+ *  License: THIS CODE IS IN THE PUBLIC DOMAIN.
+ *
+ * Copyright (c) The Exim Maintainers 2014 - 2018
+ */
+#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[] = {
+    /* error                           string */
+    {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[] = {
+    /* error                           string */
+    {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 = US  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 = US  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 *subj)
+{
+X509_NAME *name = akid_issuer_name(akid);
+
+/*
+ * If subject's akid specifies an authority key identifier issuer name, we
+ * must use that.
+ */
+return X509_set_issuer_name(cert,
+                           name ? name : subj);
+}
+
+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, name)
+   || !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(CCS  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(CS  namebuf, len) == 0)
+  {
+  OPENSSL_free(namebuf);
+  return 0;
+  }
+return CS  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)))
+  {
+  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..541e9cb
--- /dev/null
@@ -0,0 +1,48 @@
+/*************************************************
+*     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 SUPPORT_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
+
+# ifndef USE_GNUTLS
+#  include "dane-openssl.c"
+# endif
+
+
+#endif  /* SUPPORT_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..336cfe7 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -91,7 +91,9 @@ int rc, save_errno;
 BOOL read_only = flags == O_RDONLY;
 BOOL created = FALSE;
 flock_t lock_data;
 BOOL read_only = flags == O_RDONLY;
 BOOL created = FALSE;
 flock_t lock_data;
-uschar buffer[256];
+uschar dirname[256], filename[256];
+
+DEBUG(D_hints_lookup) acl_level++;
 
 /* The first thing to do is to open a separate file on which to lock. This
 ensures that Exim has exclusive use of the database before it even tries to
 
 /* The first thing to do is to open a separate file on which to lock. This
 ensures that Exim has exclusive use of the database before it even tries to
@@ -106,20 +108,22 @@ make the directory as well, just in case. We won't be doing this many times
 unnecessarily, because usually the lock file will be there. If the directory
 exists, there is no error. */
 
 unnecessarily, because usually the lock file will be there. If the directory
 exists, there is no error. */
 
-sprintf(CS buffer, "%s/db/%s.lockfile", spool_directory, name);
+snprintf(CS dirname, sizeof(dirname), "%s/db", spool_directory);
+snprintf(CS filename, sizeof(filename), "%s/%s.lockfile", dirname, name);
 
 
-if ((dbblock->lockfd = Uopen(buffer, O_RDWR, EXIMDB_LOCKFILE_MODE)) < 0)
+if ((dbblock->lockfd = Uopen(filename, O_RDWR, EXIMDB_LOCKFILE_MODE)) < 0)
   {
   created = TRUE;
   (void)directory_make(spool_directory, US"db", EXIMDB_DIRECTORY_MODE, TRUE);
   {
   created = TRUE;
   (void)directory_make(spool_directory, US"db", EXIMDB_DIRECTORY_MODE, TRUE);
-  dbblock->lockfd = Uopen(buffer, O_RDWR|O_CREAT, EXIMDB_LOCKFILE_MODE);
+  dbblock->lockfd = Uopen(filename, O_RDWR|O_CREAT, EXIMDB_LOCKFILE_MODE);
   }
 
 if (dbblock->lockfd < 0)
   {
   log_write(0, LOG_MAIN, "%s",
   }
 
 if (dbblock->lockfd < 0)
   {
   log_write(0, LOG_MAIN, "%s",
-    string_open_failed(errno, "database lock file %s", buffer));
+    string_open_failed(errno, "database lock file %s", filename));
   errno = 0;      /* Indicates locking failure */
   errno = 0;      /* Indicates locking failure */
+  DEBUG(D_hints_lookup) acl_level--;
   return NULL;
   }
 
   return NULL;
   }
 
@@ -130,25 +134,26 @@ lock_data.l_type = read_only? F_RDLCK : F_WRLCK;
 lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
 
 DEBUG(D_hints_lookup|D_retry|D_route|D_deliver)
 lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
 
 DEBUG(D_hints_lookup|D_retry|D_route|D_deliver)
-  debug_printf("locking %s\n", buffer);
+  debug_printf_indent("locking %s\n", filename);
 
 sigalrm_seen = FALSE;
 
 sigalrm_seen = FALSE;
-alarm(EXIMDB_LOCK_TIMEOUT);
+ALARM(EXIMDB_LOCK_TIMEOUT);
 rc = fcntl(dbblock->lockfd, F_SETLKW, &lock_data);
 rc = fcntl(dbblock->lockfd, F_SETLKW, &lock_data);
-alarm(0);
+ALARM_CLR(0);
 
 if (sigalrm_seen) errno = ETIMEDOUT;
 if (rc < 0)
   {
   log_write(0, LOG_MAIN|LOG_PANIC, "Failed to get %s lock for %s: %s",
 
 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", filename,
+    errno == ETIMEDOUT ? "timed out" : strerror(errno));
   (void)close(dbblock->lockfd);
   errno = 0;       /* Indicates locking failure */
   (void)close(dbblock->lockfd);
   errno = 0;       /* Indicates locking failure */
+  DEBUG(D_hints_lookup) acl_level--;
   return NULL;
   }
 
   return NULL;
   }
 
-DEBUG(D_hints_lookup) debug_printf("locked %s\n", buffer);
+DEBUG(D_hints_lookup) debug_printf_indent("locked  %s\n", filename);
 
 /* 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
 
 /* 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
@@ -159,18 +164,15 @@ databases - often this is caused by non-matching db.h and the library. To make
 it easy to pin this down, there are now debug statements on either side of the
 open call. */
 
 it easy to pin this down, there are now debug statements on either side of the
 open call. */
 
-sprintf(CS buffer, "%s/db/%s", spool_directory, name);
-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");
+snprintf(CS filename, sizeof(filename), "%s/%s", dirname, name);
+EXIM_DBOPEN(filename, dirname, flags, EXIMDB_MODE, &(dbblock->dbptr));
 
 
-if (dbblock->dbptr == NULL && errno == ENOENT && flags == O_RDWR)
+if (!dbblock->dbptr && errno == ENOENT && flags == O_RDWR)
   {
   DEBUG(D_hints_lookup)
   {
   DEBUG(D_hints_lookup)
-    debug_printf("%s appears not to exist: trying to create\n", buffer);
+    debug_printf_indent("%s appears not to exist: trying to create\n", filename);
   created = TRUE;
   created = TRUE;
-  EXIM_DBOPEN(buffer, flags|O_CREAT, EXIMDB_MODE, &(dbblock->dbptr));
-  DEBUG(D_hints_lookup) debug_printf("returned from EXIM_DBOPEN\n");
+  EXIM_DBOPEN(filename, dirname, flags|O_CREAT, EXIMDB_MODE, &(dbblock->dbptr));
   }
 
 save_errno = errno;
   }
 
 save_errno = errno;
@@ -193,54 +195,53 @@ if (created && geteuid() == root_uid)
   {
   DIR *dd;
   struct dirent *ent;
   {
   DIR *dd;
   struct dirent *ent;
-  uschar *lastname = Ustrrchr(buffer, '/') + 1;
+  uschar *lastname = Ustrrchr(filename, '/') + 1;
   int namelen = Ustrlen(name);
 
   *lastname = 0;
   int namelen = Ustrlen(name);
 
   *lastname = 0;
-  dd = opendir(CS buffer);
+  dd = opendir(CS filename);
 
 
-  while ((ent = readdir(dd)) != NULL)
-    {
+  while ((ent = readdir(dd)))
     if (Ustrncmp(ent->d_name, name, namelen) == 0)
       {
       struct stat statbuf;
       Ustrcpy(lastname, ent->d_name);
     if (Ustrncmp(ent->d_name, name, namelen) == 0)
       {
       struct stat statbuf;
       Ustrcpy(lastname, ent->d_name);
-      if (Ustat(buffer, &statbuf) >= 0 && statbuf.st_uid != exim_uid)
+      if (Ustat(filename, &statbuf) >= 0 && statbuf.st_uid != exim_uid)
         {
         {
-        DEBUG(D_hints_lookup) debug_printf("ensuring %s is owned by exim\n", buffer);
-        if (Uchown(buffer, exim_uid, exim_gid))
-          DEBUG(D_hints_lookup) debug_printf("failed setting %s to owned by exim\n", buffer);
+        DEBUG(D_hints_lookup) debug_printf_indent("ensuring %s is owned by exim\n", filename);
+        if (Uchown(filename, exim_uid, exim_gid))
+          DEBUG(D_hints_lookup) debug_printf_indent("failed setting %s to owned by exim\n", filename);
         }
       }
         }
       }
-    }
 
   closedir(dd);
   }
 
 /* If the open has failed, return NULL, leaving errno set. If lof is TRUE,
 
   closedir(dd);
   }
 
 /* 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. */
+log the event - also for debugging - but debug only 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));
-    else
-      DEBUG(D_hints_lookup)
-        debug_printf("%s", CS string_open_failed(save_errno, "DB file %s\n",
-          buffer));
-    }
+  if (lof && save_errno != ENOENT)
+    log_write(0, LOG_MAIN, "%s", string_open_failed(save_errno, "DB file %s",
+        filename));
+  else
+    DEBUG(D_hints_lookup)
+      debug_printf_indent("%s\n", CS string_open_failed(save_errno, "DB file %s",
+          filename));
   (void)close(dbblock->lockfd);
   errno = save_errno;
   (void)close(dbblock->lockfd);
   errno = save_errno;
+  DEBUG(D_hints_lookup) acl_level--;
   return NULL;
   }
 
 DEBUG(D_hints_lookup)
   return 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" : "??");
+  debug_printf_indent("opened hints database %s: flags=%s\n", filename,
+    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. */
 
 /* Pass back the block containing the opened database handle and the open fd
 for the lock. */
@@ -267,6 +268,8 @@ dbfn_close(open_db *dbblock)
 {
 EXIM_DBCLOSE(dbblock->dbptr);
 (void)close(dbblock->lockfd);
 {
 EXIM_DBCLOSE(dbblock->dbptr);
 (void)close(dbblock->lockfd);
+DEBUG(D_hints_lookup)
+  { debug_printf_indent("closed hints database and lockfile\n"); acl_level--; }
 }
 
 
 }
 
 
@@ -294,17 +297,21 @@ Returns: a pointer to the retrieved record, or
 */
 
 void *
 */
 
 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;
 {
 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);
+DEBUG(D_hints_lookup) debug_printf_indent("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_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;
 
 
 if (!EXIM_DBGET(dbblock->dbptr, key_datum, result_datum)) return NULL;
 
@@ -334,18 +341,22 @@ Returns:    the yield of the underlying dbm or db "write" function. If this
 */
 
 int
 */
 
 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;
 {
 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);
 
 gptr->time_stamp = time(NULL);
 
-DEBUG(D_hints_lookup) debug_printf("dbfn_write: key=%s\n", key);
+DEBUG(D_hints_lookup) debug_printf_indent("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_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);
 EXIM_DATUM_DATA(value_datum) = CS ptr;
 EXIM_DATUM_SIZE(value_datum) = length;
 return EXIM_DBPUT(dbblock->dbptr, key_datum, value_datum);
@@ -366,12 +377,18 @@ Returns: the yield of the underlying dbm or db "delete" function.
 */
 
 int
 */
 
 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);
+
+DEBUG(D_hints_lookup) debug_printf_indent("dbfn_delete: key=%s\n", key);
+
+memcpy(key_copy, key, klen);
 EXIM_DATUM key_datum;
 EXIM_DATUM_INIT(key_datum);         /* Some DBM libraries require clearing */
 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);
 }
 
 return EXIM_DBDEL(dbblock->dbptr, key_datum);
 }
 
@@ -400,6 +417,8 @@ EXIM_DATUM key_datum, value_datum;
 uschar *yield;
 value_datum = value_datum;    /* dummy; not all db libraries use this */
 
 uschar *yield;
 value_datum = value_datum;    /* dummy; not all db libraries use this */
 
+DEBUG(D_hints_lookup) debug_printf_indent("dbfn_scan\n");
+
 /* Some dbm require an initialization */
 
 if (start) EXIM_DBCREATE_CURSOR(dbblock->dbptr, cursor);
 /* Some dbm require an initialization */
 
 if (start) EXIM_DBCREATE_CURSOR(dbblock->dbptr, cursor);
@@ -520,7 +539,7 @@ while (Ufgets(buffer, 256, stdin) != NULL)
     odb = dbfn_open(s, O_RDWR, dbblock + i, TRUE);
     stop = clock();
 
     odb = dbfn_open(s, O_RDWR, dbblock + i, TRUE);
     stop = clock();
 
-    if (odb != NULL)
+    if (odb)
       {
       current = i;
       printf("opened %d\n", current);
       {
       current = i;
       printf("opened %d\n", current);
index 1963fa9..93d12ef 100644 (file)
@@ -2,18 +2,18 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     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 *);
 /* 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);
 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 **);
 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. */
 
 
 /* Macro for the common call to read without wanting to know the length. */
 
index ce81f1e..02cfa14 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* This header file contains macro definitions so that a variety of DBM
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* This header file contains macro definitions so that a variety of DBM
@@ -39,7 +39,7 @@ tdb_traverse to be called) */
 /* Access functions */
 
 /* EXIM_DBOPEN - sets *dbpp to point to an EXIM_DB, NULL if failed */
 /* Access functions */
 
 /* EXIM_DBOPEN - sets *dbpp to point to an EXIM_DB, NULL if failed */
-#define EXIM_DBOPEN(name, flags, mode, dbpp) \
+#define EXIM_DBOPEN__(name, dirname, flags, mode, dbpp) \
        *(dbpp) = tdb_open(CS name, 0, TDB_DEFAULT, flags, mode)
 
 /* EXIM_DBGET - returns TRUE if successful, FALSE otherwise */
        *(dbpp) = tdb_open(CS name, 0, TDB_DEFAULT, flags, mode)
 
 /* EXIM_DBGET - returns TRUE if successful, FALSE otherwise */
@@ -64,7 +64,7 @@ tdb_traverse to be called) */
 
 /* EXIM_DBCREATE_CURSOR - initialize for scanning operation */
 #define EXIM_DBCREATE_CURSOR(db, cursor) { \
 
 /* 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 */
 
 /* EXIM_DBSCAN - This is complicated because we have to free the last datum
 free() must not die when passed NULL */
@@ -77,7 +77,7 @@ free() must not die when passed NULL */
 #define EXIM_DBDELETE_CURSOR(cursor) free(cursor)
 
 /* EXIM_DBCLOSE */
 #define EXIM_DBDELETE_CURSOR(cursor) free(cursor)
 
 /* EXIM_DBCLOSE */
-#define EXIM_DBCLOSE(db)        tdb_close(db)
+#define EXIM_DBCLOSE__(db)        tdb_close(db)
 
 /* Datum access types - these are intended to be assignable */
 
 
 /* Datum access types - these are intended to be assignable */
 
@@ -106,6 +106,10 @@ definition of DB_VERSION_STRING, which is present in versions 2.x onwards. */
 
 #ifdef DB_VERSION_STRING
 
 
 #ifdef DB_VERSION_STRING
 
+# if DB_VERSION_MAJOR >= 6
+#  error Version 6 and later BDB API is not supported
+# endif
+
 /* The API changed (again!) between the 2.x and 3.x versions */
 
 #if DB_VERSION_MAJOR >= 3
 /* The API changed (again!) between the 2.x and 3.x versions */
 
 #if DB_VERSION_MAJOR >= 3
@@ -113,87 +117,176 @@ definition of DB_VERSION_STRING, which is present in versions 2.x onwards. */
 /***************** Berkeley db 3.x/4.x native definitions ******************/
 
 /* Basic DB type */
 /***************** Berkeley db 3.x/4.x native definitions ******************/
 
 /* Basic DB type */
-#define EXIM_DB       DB
-
+# if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 1)
+#  define EXIM_DB       DB_ENV
 /* Cursor type, for scanning */
 /* Cursor type, for scanning */
-#define EXIM_CURSOR   DBC
+#  define EXIM_CURSOR   DBC
 
 /* The datum type used for queries */
 
 /* The datum type used for queries */
-#define EXIM_DATUM    DBT
+#  define EXIM_DATUM    DBT
 
 /* Some text for messages */
 
 /* Some text for messages */
-#define EXIM_DBTYPE   "db (v3/4)"
+#  define EXIM_DBTYPE   "db (v4.1+)"
+
+/* Only more-recent versions.  5+ ? */
+#  ifndef DB_FORCESYNC
+#   define DB_FORCESYNC 0
+#  endif
+
 
 /* Access functions */
 
 /* EXIM_DBOPEN - sets *dbpp to point to an EXIM_DB, NULL if failed. The
 
 /* Access functions */
 
 /* EXIM_DBOPEN - sets *dbpp to point to an EXIM_DB, NULL if failed. The
-API changed for DB 4.1. */
+API changed for DB 4.1. - and we also starting using the "env" with a
+specified working dir, to avoid the DBCONFIG file trap. */
+
+#  define ENV_TO_DB(env) ((DB *)((env)->app_private))
+
+#  define EXIM_DBOPEN__(name, dirname, flags, mode, dbpp) \
+  if (  db_env_create(dbpp, 0) != 0                                            \
+     || ((*dbpp)->set_errcall(*dbpp, dbfn_bdb_error_callback), 0)              \
+     || (*dbpp)->open(*dbpp, CS dirname, DB_CREATE|DB_INIT_MPOOL|DB_PRIVATE, 0) != 0\
+     )                                                                         \
+    *dbpp = NULL;                                      \
+  else if (db_create((DB **) &((*dbpp)->app_private), *dbpp, 0) != 0)          \
+    {                                                  \
+    ((DB_ENV *)(*dbpp))->close((DB_ENV *)(*dbpp), 0);  \
+    *dbpp = NULL;                                      \
+    }                                                  \
+  else if (ENV_TO_DB(*dbpp)->open(ENV_TO_DB(*dbpp), NULL, CS name, NULL,       \
+             (flags) == O_RDONLY ? DB_UNKNOWN : DB_HASH,                       \
+             (flags) == O_RDONLY ? DB_RDONLY : DB_CREATE,                      \
+             mode) != 0                                                        \
+         )                                                                     \
+    {                                                  \
+    ENV_TO_DB(*dbpp)->close(ENV_TO_DB(*dbpp), 0);      \
+    ((DB_ENV *)(*dbpp))->close((DB_ENV *)(*dbpp), 0);  \
+    *dbpp = NULL;                                      \
+    }
 
 
-#if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 1)
-#define EXIM_DBOPEN(name, flags, mode, dbpp) \
-       if (db_create(dbpp, NULL, 0) != 0 || \
-         ((*dbpp)->set_errcall(*dbpp, dbfn_bdb_error_callback), \
-         ((*dbpp)->open)(*dbpp, NULL, CS name, NULL, \
-         ((flags) == O_RDONLY)? DB_UNKNOWN : DB_HASH, \
-         ((flags) == O_RDONLY)? DB_RDONLY : DB_CREATE, \
-         mode)) != 0) *(dbpp) = NULL
-#else
-#define EXIM_DBOPEN(name, flags, mode, dbpp) \
+/* EXIM_DBGET - returns TRUE if successful, FALSE otherwise */
+#  define EXIM_DBGET(db, key, data)      \
+       (ENV_TO_DB(db)->get(ENV_TO_DB(db), NULL, &key, &data, 0) == 0)
+
+/* EXIM_DBPUT - returns nothing useful, assumes replace mode */
+#  define EXIM_DBPUT(db, key, data)      \
+       ENV_TO_DB(db)->put(ENV_TO_DB(db), NULL, &key, &data, 0)
+
+/* EXIM_DBPUTB - non-overwriting for use by dbmbuild */
+#  define EXIM_DBPUTB(db, key, data)      \
+       ENV_TO_DB(db)->put(ENV_TO_DB(db), NULL, &key, &data, DB_NOOVERWRITE)
+
+/* Return values from EXIM_DBPUTB */
+
+#  define EXIM_DBPUTB_OK  0
+#  define EXIM_DBPUTB_DUP DB_KEYEXIST
+
+/* EXIM_DBDEL */
+#  define EXIM_DBDEL(db, key)     ENV_TO_DB(db)->del(ENV_TO_DB(db), NULL, &key, 0)
+
+/* EXIM_DBCREATE_CURSOR - initialize for scanning operation */
+
+#  define EXIM_DBCREATE_CURSOR(db, cursor) \
+       ENV_TO_DB(db)->cursor(ENV_TO_DB(db), NULL, cursor, 0)
+
+/* EXIM_DBSCAN - returns TRUE if data is returned, FALSE at end */
+#  define EXIM_DBSCAN(db, key, data, first, cursor)      \
+       ((cursor)->c_get(cursor, &key, &data,         \
+         (first? DB_FIRST : DB_NEXT)) == 0)
+
+/* EXIM_DBDELETE_CURSOR - terminate scanning operation */
+#  define EXIM_DBDELETE_CURSOR(cursor) \
+       (cursor)->c_close(cursor)
+
+/* EXIM_DBCLOSE */
+#  define EXIM_DBCLOSE__(db) \
+       (ENV_TO_DB(db)->close(ENV_TO_DB(db), 0) , ((DB_ENV *)(db))->close((DB_ENV *)(db), DB_FORCESYNC))
+
+/* Datum access types - these are intended to be assignable. */
+
+#  define EXIM_DATUM_SIZE(datum)  (datum).size
+#  define EXIM_DATUM_DATA(datum)  (datum).data
+
+/* The whole datum structure contains other fields that must be cleared
+before use, but we don't have to free anything after reading data. */
+
+#  define EXIM_DATUM_INIT(datum)   memset(&datum, 0, sizeof(datum))
+#  define EXIM_DATUM_FREE(datum)
+
+# else /* pre- 4.1 */
+
+#  define EXIM_DB       DB
+
+/* Cursor type, for scanning */
+#  define EXIM_CURSOR   DBC
+
+/* The datum type used for queries */
+#  define EXIM_DATUM    DBT
+
+/* Some text for messages */
+#  define EXIM_DBTYPE   "db (v3/4)"
+
+/* Access functions */
+
+/* EXIM_DBOPEN - sets *dbpp to point to an EXIM_DB, NULL if failed. */
+
+#  define EXIM_DBOPEN__(name, dirname, flags, mode, dbpp) \
        if (db_create(dbpp, NULL, 0) != 0 || \
          ((*dbpp)->set_errcall(*dbpp, dbfn_bdb_error_callback), \
          ((*dbpp)->open)(*dbpp, CS name, NULL, \
          ((flags) == O_RDONLY)? DB_UNKNOWN : DB_HASH, \
          ((flags) == O_RDONLY)? DB_RDONLY : DB_CREATE, \
          mode)) != 0) *(dbpp) = NULL
        if (db_create(dbpp, NULL, 0) != 0 || \
          ((*dbpp)->set_errcall(*dbpp, dbfn_bdb_error_callback), \
          ((*dbpp)->open)(*dbpp, CS name, NULL, \
          ((flags) == O_RDONLY)? DB_UNKNOWN : DB_HASH, \
          ((flags) == O_RDONLY)? DB_RDONLY : DB_CREATE, \
          mode)) != 0) *(dbpp) = NULL
-#endif
 
 /* EXIM_DBGET - returns TRUE if successful, FALSE otherwise */
 
 /* EXIM_DBGET - returns TRUE if successful, FALSE otherwise */
-#define EXIM_DBGET(db, key, data)      \
+#  define EXIM_DBGET(db, key, data)      \
        ((db)->get(db, NULL, &key, &data, 0) == 0)
 
 /* EXIM_DBPUT - returns nothing useful, assumes replace mode */
        ((db)->get(db, NULL, &key, &data, 0) == 0)
 
 /* EXIM_DBPUT - returns nothing useful, assumes replace mode */
-#define EXIM_DBPUT(db, key, data)      \
+#  define EXIM_DBPUT(db, key, data)      \
        (db)->put(db, NULL, &key, &data, 0)
 
 /* EXIM_DBPUTB - non-overwriting for use by dbmbuild */
        (db)->put(db, NULL, &key, &data, 0)
 
 /* EXIM_DBPUTB - non-overwriting for use by dbmbuild */
-#define EXIM_DBPUTB(db, key, data)      \
+#  define EXIM_DBPUTB(db, key, data)      \
        (db)->put(db, NULL, &key, &data, DB_NOOVERWRITE)
 
 /* Return values from EXIM_DBPUTB */
 
        (db)->put(db, NULL, &key, &data, DB_NOOVERWRITE)
 
 /* Return values from EXIM_DBPUTB */
 
-#define EXIM_DBPUTB_OK  0
-#define EXIM_DBPUTB_DUP DB_KEYEXIST
+#  define EXIM_DBPUTB_OK  0
+#  define EXIM_DBPUTB_DUP DB_KEYEXIST
 
 /* EXIM_DBDEL */
 
 /* EXIM_DBDEL */
-#define EXIM_DBDEL(db, key)     (db)->del(db, NULL, &key, 0)
+#  define EXIM_DBDEL(db, key)     (db)->del(db, NULL, &key, 0)
 
 /* EXIM_DBCREATE_CURSOR - initialize for scanning operation */
 
 
 /* EXIM_DBCREATE_CURSOR - initialize for scanning operation */
 
-#define EXIM_DBCREATE_CURSOR(db, cursor) \
+#  define EXIM_DBCREATE_CURSOR(db, cursor) \
        (db)->cursor(db, NULL, cursor, 0)
 
 /* EXIM_DBSCAN - returns TRUE if data is returned, FALSE at end */
        (db)->cursor(db, NULL, cursor, 0)
 
 /* EXIM_DBSCAN - returns TRUE if data is returned, FALSE at end */
-#define EXIM_DBSCAN(db, key, data, first, cursor)      \
+#  define EXIM_DBSCAN(db, key, data, first, cursor)      \
        ((cursor)->c_get(cursor, &key, &data,         \
          (first? DB_FIRST : DB_NEXT)) == 0)
 
 /* EXIM_DBDELETE_CURSOR - terminate scanning operation */
        ((cursor)->c_get(cursor, &key, &data,         \
          (first? DB_FIRST : DB_NEXT)) == 0)
 
 /* EXIM_DBDELETE_CURSOR - terminate scanning operation */
-#define EXIM_DBDELETE_CURSOR(cursor) \
+#  define EXIM_DBDELETE_CURSOR(cursor) \
        (cursor)->c_close(cursor)
 
 /* EXIM_DBCLOSE */
        (cursor)->c_close(cursor)
 
 /* EXIM_DBCLOSE */
-#define EXIM_DBCLOSE(db)        (db)->close(db, 0)
+#  define EXIM_DBCLOSE__(db)        (db)->close(db, 0)
 
 /* Datum access types - these are intended to be assignable. */
 
 
 /* Datum access types - these are intended to be assignable. */
 
-#define EXIM_DATUM_SIZE(datum)  (datum).size
-#define EXIM_DATUM_DATA(datum)  (datum).data
+#  define EXIM_DATUM_SIZE(datum)  (datum).size
+#  define EXIM_DATUM_DATA(datum)  (datum).data
 
 /* The whole datum structure contains other fields that must be cleared
 before use, but we don't have to free anything after reading data. */
 
 
 /* The whole datum structure contains other fields that must be cleared
 before use, but we don't have to free anything after reading data. */
 
-#define EXIM_DATUM_INIT(datum)   memset(&datum, 0, sizeof(datum))
-#define EXIM_DATUM_FREE(datum)
+#  define EXIM_DATUM_INIT(datum)   memset(&datum, 0, sizeof(datum))
+#  define EXIM_DATUM_FREE(datum)
+
+# endif
 
 
 #else /* DB_VERSION_MAJOR >= 3 */
 
 
 #else /* DB_VERSION_MAJOR >= 3 */
@@ -215,7 +308,7 @@ before use, but we don't have to free anything after reading data. */
 /* Access functions */
 
 /* EXIM_DBOPEN - sets *dbpp to point to an EXIM_DB, NULL if failed */
 /* Access functions */
 
 /* EXIM_DBOPEN - sets *dbpp to point to an EXIM_DB, NULL if failed */
-#define EXIM_DBOPEN(name, flags, mode, dbpp)         \
+#define EXIM_DBOPEN__(name, dirname, flags, mode, dbpp)         \
        if ((errno = db_open(CS name, DB_HASH,           \
          ((flags) == O_RDONLY)? DB_RDONLY : DB_CREATE, \
          mode, NULL, NULL, dbpp)) != 0) *(dbpp) = NULL
        if ((errno = db_open(CS name, DB_HASH,           \
          ((flags) == O_RDONLY)? DB_RDONLY : DB_CREATE, \
          mode, NULL, NULL, dbpp)) != 0) *(dbpp) = NULL
@@ -264,7 +357,7 @@ the new option that is available, so I guess that it happened at 2.5.x. */
        (cursor)->c_close(cursor)
 
 /* EXIM_DBCLOSE */
        (cursor)->c_close(cursor)
 
 /* EXIM_DBCLOSE */
-#define EXIM_DBCLOSE(db)        (db)->close(db, 0)
+#define EXIM_DBCLOSE__(db)        (db)->close(db, 0)
 
 /* Datum access types - these are intended to be assignable. */
 
 
 /* Datum access types - these are intended to be assignable. */
 
@@ -312,7 +405,7 @@ before been able to pass successfully. */
 /* Access functions */
 
 /* EXIM_DBOPEN - sets *dbpp to point to an EXIM_DB, NULL if failed */
 /* Access functions */
 
 /* EXIM_DBOPEN - sets *dbpp to point to an EXIM_DB, NULL if failed */
-#define EXIM_DBOPEN(name, flags, mode, dbpp) \
+#define EXIM_DBOPEN__(name, dirname, flags, mode, dbpp) \
        *(dbpp) = dbopen(CS name, flags, mode, DB_HASH, NULL)
 
 /* EXIM_DBGET - returns TRUE if successful, FALSE otherwise */
        *(dbpp) = dbopen(CS name, flags, mode, DB_HASH, NULL)
 
 /* EXIM_DBGET - returns TRUE if successful, FALSE otherwise */
@@ -347,7 +440,7 @@ refer to cursor, to keep picky compilers happy. */
 #define EXIM_DBDELETE_CURSOR(cursor) { cursor = cursor; }
 
 /* EXIM_DBCLOSE */
 #define EXIM_DBDELETE_CURSOR(cursor) { cursor = cursor; }
 
 /* EXIM_DBCLOSE */
-#define EXIM_DBCLOSE(db)        (db)->close(db)
+#define EXIM_DBCLOSE__(db)        (db)->close(db)
 
 /* Datum access types - these are intended to be assignable */
 
 
 /* Datum access types - these are intended to be assignable */
 
@@ -389,7 +482,7 @@ typedef struct {
 /* Access functions */
 
 /* EXIM_DBOPEN - returns a EXIM_DB *, NULL if failed */
 /* Access functions */
 
 /* EXIM_DBOPEN - returns a EXIM_DB *, NULL if failed */
-#define EXIM_DBOPEN(name, flags, mode, dbpp) \
+#define EXIM_DBOPEN__(name, dirname, flags, mode, dbpp) \
      { (*(dbpp)) = (EXIM_DB *) malloc(sizeof(EXIM_DB));\
        if (*(dbpp) != NULL) { \
          (*(dbpp))->lkey.dptr = NULL;\
      { (*(dbpp)) = (EXIM_DB *) malloc(sizeof(EXIM_DB));\
        if (*(dbpp) != NULL) { \
          (*(dbpp))->lkey.dptr = NULL;\
@@ -435,7 +528,7 @@ refer to cursor, to keep picky compilers happy. */
 #define EXIM_DBDELETE_CURSOR(cursor) { cursor = cursor; }
 
 /* EXIM_DBCLOSE */
 #define EXIM_DBDELETE_CURSOR(cursor) { cursor = cursor; }
 
 /* EXIM_DBCLOSE */
-#define EXIM_DBCLOSE(db) \
+#define EXIM_DBCLOSE__(db) \
 { gdbm_close((db)->gdbm);\
   if ((db)->lkey.dptr != NULL) free((db)->lkey.dptr);\
   free(db); }
 { gdbm_close((db)->gdbm);\
   if ((db)->lkey.dptr != NULL) free((db)->lkey.dptr);\
   free(db); }
@@ -478,7 +571,7 @@ interface */
 /* Access functions */
 
 /* EXIM_DBOPEN - returns a EXIM_DB *, NULL if failed */
 /* Access functions */
 
 /* EXIM_DBOPEN - returns a EXIM_DB *, NULL if failed */
-#define EXIM_DBOPEN(name, flags, mode, dbpp) \
+#define EXIM_DBOPEN__(name, dirname, flags, mode, dbpp) \
        *(dbpp) = dbm_open(CS name, flags, mode)
 
 /* EXIM_DBGET - returns TRUE if successful, FALSE otherwise */
        *(dbpp) = dbm_open(CS name, flags, mode)
 
 /* EXIM_DBGET - returns TRUE if successful, FALSE otherwise */
@@ -513,7 +606,7 @@ refer to cursor, to keep picky compilers happy. */
 #define EXIM_DBDELETE_CURSOR(cursor) { cursor = cursor; }
 
 /* EXIM_DBCLOSE */
 #define EXIM_DBDELETE_CURSOR(cursor) { cursor = cursor; }
 
 /* EXIM_DBCLOSE */
-#define EXIM_DBCLOSE(db) dbm_close(db)
+#define EXIM_DBCLOSE__(db) dbm_close(db)
 
 /* Datum access types - these are intended to be assignable */
 
 
 /* Datum access types - these are intended to be assignable */
 
@@ -528,6 +621,38 @@ after reading data. */
 
 #endif /* USE_GDBM */
 
 
 #endif /* USE_GDBM */
 
+
+
+
+
+# ifdef COMPILE_UTILITY
+
+#  define EXIM_DBOPEN(name, dirname, flags, mode, dbpp) \
+  EXIM_DBOPEN__(name, dirname, flags, mode, dbpp)
+#  define EXIM_DBCLOSE(db) EXIM_DBCLOSE__(db)
+
+# else
+
+#  define EXIM_DBOPEN(name, dirname, flags, mode, dbpp) \
+  do { \
+  DEBUG(D_hints_lookup) \
+    debug_printf_indent("EXIM_DBOPEN: file <%s> dir <%s> flags=%s\n", \
+      (name), (dirname),               \
+      (flags) == O_RDONLY ? "O_RDONLY" \
+      : (flags) == O_RDWR ? "O_RDWR"   \
+      : (flags) == (O_RDWR|O_CREAT) ? "O_RDWR|O_CREAT" \
+      : "??"); \
+  EXIM_DBOPEN__(name, dirname, flags, mode, dbpp); \
+  DEBUG(D_hints_lookup) debug_printf_indent("returned from EXIM_DBOPEN: %p\n", *dbpp); \
+  } while(0)
+#  define EXIM_DBCLOSE(db) \
+  do { \
+  DEBUG(D_hints_lookup) debug_printf_indent("EXIM_DBCLOSE(%p)\n", db); \
+  EXIM_DBCLOSE__(db); \
+  } while(0)
+
+#  endif
+
 /********************* End of dbm library definitions **********************/
 
 
 /********************* End of dbm library definitions **********************/
 
 
@@ -582,7 +707,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
 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;
 
 typedef struct {
   time_t time_stamp;
@@ -661,5 +786,23 @@ typedef struct {
   uschar   bloom[40];     /* Bloom filter which may be larger than this */
 } dbdata_ratelimit_unique;
 
   uschar   bloom[40];     /* Bloom filter which may be larger than this */
 } dbdata_ratelimit_unique;
 
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+/* This structure records the EHLO responses, cleartext and crypted,
+for an IP, as bitmasks (cf. OPTION_TLS) */
+
+typedef struct {
+  unsigned short cleartext_features;
+  unsigned short crypted_features;
+  unsigned short cleartext_auths;
+  unsigned short crypted_auths;
+} ehlo_resp_precis;
+
+typedef struct {
+  time_t time_stamp;
+  /*************/
+  ehlo_resp_precis data;
+} dbdata_ehlo_resp;
+#endif
+
 
 /* End of dbstuff.h */
 
 /* End of dbstuff.h */
index 44c0c00..5aa2b17 100644 (file)
--- a/src/dcc.c
+++ b/src/dcc.c
@@ -2,10 +2,12 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     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.
  * Vienna University Computer Center
  * wbreyha@gmx.net
  * See the file NOTICE for conditions of use and distribution.
+ *
+ * Copyright (c) The Exim Maintainers 2015 - 2018
  */
 
 /* This patch is based on code from Tom Kistners exiscan (ACL integration) and
  */
 
 /* 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;
 }
 
   return retval;
 }
 
-int dcc_process(uschar **listptr) {
+int
+dcc_process(uschar **listptr)
+{
   int sep = 0;
   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";
   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 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;
   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,
 
   /* 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 */
     /* 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 */
 
   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';
 
   /* 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;
       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;
     /* error while spooling */
     log_write(0, LOG_MAIN|LOG_PANIC,
            "dcc acl condition: error while opening spool file");
     return DEFER;
-  };
+    }
 
   /* Initialize the variables */
 
 
   /* 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 */
   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);
        (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 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);
   else {
     /* sender_host_address is NULL which means it comes from localhost */
     Ustrncpy(client_ip, dcc_default_ip_option, sizeof(client_ip)-1);
@@ -190,10 +185,10 @@ int dcc_process(uschar **listptr) {
 
   /* If sockip contains an ip, we use a tcp socket, otherwise a UNIX socket */
   if(Ustrcmp(sockip, "")){
 
   /* If sockip contains an ip, we use a tcp socket, otherwise a UNIX socket */
   if(Ustrcmp(sockip, "")){
-    ipaddress = gethostbyname((char *)sockip);
-    bzero((char *) &serv_addr_in, sizeof(serv_addr_in));
+    ipaddress = gethostbyname(CS sockip);
+    bzero(CS  &serv_addr_in, sizeof(serv_addr_in));
     serv_addr_in.sin_family = AF_INET;
     serv_addr_in.sin_family = AF_INET;
-    bcopy((char *)ipaddress->h_addr, (char *)&serv_addr_in.sin_addr.s_addr, ipaddress->h_length);
+    bcopy(CS ipaddress->h_addr, CS &serv_addr_in.sin_addr.s_addr, ipaddress->h_length);
     serv_addr_in.sin_port = htons(portnr);
     if ((sockfd = socket(AF_INET, SOCK_STREAM,0)) < 0){
       DEBUG(D_acl)
     serv_addr_in.sin_port = htons(portnr);
     if ((sockfd = socket(AF_INET, SOCK_STREAM,0)) < 0){
       DEBUG(D_acl)
@@ -214,9 +209,9 @@ int dcc_process(uschar **listptr) {
     }
   } else {
     /* connecting to the dccifd UNIX socket */
     }
   } 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;
     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));
     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)
   Ustrncat(sendbuf, "\n", sizeof(sendbuf)-Ustrlen(sendbuf)-1);
   flushbuffer(sockfd, sendbuf);
   DEBUG(D_acl)
@@ -432,11 +427,11 @@ int dcc_process(uschar **listptr) {
             }
           }
           else {
             }
           }
           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) {
           }
         }
         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 */
           /* 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 {
             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 {
           }
         }
         else {
index ebd932f..2423a33 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -30,7 +30,7 @@ static uschar tree_printline[tree_printlinesize];
 
 Arguments:
   p          tree node
 
 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
   barswitch  if TRUE print | at the pos value
 
 Returns:     nothing
@@ -40,11 +40,11 @@ static void
 tree_printsub(tree_node *p, int pos, int barswitch)
 {
 int i;
 tree_printsub(tree_node *p, int pos, int barswitch)
 {
 int i;
-if (p->right != NULL) tree_printsub(p->right, pos+2, 1);
+if (p->right) tree_printsub(p->right, pos+2, 1);
 for (i = 0; i <= pos-1; i++) debug_printf("%c", tree_printline[i]);
 debug_printf("-->%s [%d]\n", p->name, p->balance);
 tree_printline[pos] = barswitch? '|' : ' ';
 for (i = 0; i <= pos-1; i++) debug_printf("%c", tree_printline[i]);
 debug_printf("-->%s [%d]\n", p->name, p->balance);
 tree_printline[pos] = barswitch? '|' : ' ';
-if (p->left != NULL)
+if (p->left)
   {
   tree_printline[pos+2] = '|';
   tree_printsub(p->left, pos+2, 0);
   {
   tree_printline[pos+2] = '|';
   tree_printsub(p->left, pos+2, 0);
@@ -58,7 +58,7 @@ debug_print_tree(tree_node *p)
 {
 int i;
 for (i = 0; i < tree_printlinesize; i++) tree_printline[i] = ' ';
 {
 int i;
 for (i = 0; i < tree_printlinesize; i++) tree_printline[i] = ' ';
-if (p == NULL) debug_printf("Empty Tree\n"); else tree_printsub(p, 0, 0);
+if (!p) debug_printf("Empty Tree\n"); else tree_printsub(p, 0, 0);
 debug_printf("---- End of tree ----\n");
 }
 
 debug_printf("---- End of tree ----\n");
 }
 
@@ -75,10 +75,10 @@ Returns:     nothing
 */
 
 void
 */
 
 void
-debug_print_argv(uschar **argv)
+debug_print_argv(const uschar ** argv)
 {
 debug_printf("exec");
 {
 debug_printf("exec");
-while (*argv != NULL) debug_printf(" %.256s", *argv++);
+while (*argv) debug_printf(" %.256s", *argv++);
 debug_printf("\n");
 }
 
 debug_printf("\n");
 }
 
@@ -98,11 +98,11 @@ Returns:     nothing
 void
 debug_print_string(uschar *debug_string)
 {
 void
 debug_print_string(uschar *debug_string)
 {
-if (debug_string == NULL) return;
+if (!debug_string) return;
 HDEBUG(D_any|D_v)
   {
   uschar *s = expand_string(debug_string);
 HDEBUG(D_any|D_v)
   {
   uschar *s = expand_string(debug_string);
-  if (s == NULL)
+  if (!s)
     debug_printf("failed to expand debug_output \"%s\": %s\n", debug_string,
       expand_string_message);
   else if (s[0] != 0)
     debug_printf("failed to expand debug_output \"%s\": %s\n", debug_string,
       expand_string_message);
   else if (s[0] != 0)
@@ -137,27 +137,41 @@ 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
 *************************************************/
 
 /* 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,
 
 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;
+va_start(ap, format);
+debug_vprintf(acl_level + expand_level, format, ap);
+va_end(ap);
+}
 
 void
 debug_printf(const char *format, ...)
 {
 va_list ap;
 va_start(ap, format);
 
 void
 debug_printf(const char *format, ...)
 {
 va_list ap;
 va_start(ap, format);
-debug_vprintf(format, ap);
+debug_vprintf(0, format, ap);
 va_end(ap);
 }
 
 void
 va_end(ap);
 }
 
 void
-debug_vprintf(const char *format, va_list ap)
+debug_vprintf(int indent, 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
 
 /* 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
@@ -168,18 +182,20 @@ if (debug_ptr == debug_buffer)
   {
   DEBUG(D_timestamp)
     {
   {
   DEBUG(D_timestamp)
     {
-    time_t now = time(NULL);
-    struct tm *t = timestamps_utc? gmtime(&now) : localtime(&now);
-    (void) sprintf(CS debug_ptr, "%02d:%02d:%02d ", t->tm_hour, t->tm_min,
-      t->tm_sec);
-    while(*debug_ptr != 0) debug_ptr++;
+    struct timeval now;
+    time_t tmp;
+    struct tm * t;
+
+    gettimeofday(&now, NULL);
+    tmp = now.tv_sec;
+    t = f.timestamps_utc ? gmtime(&tmp) : localtime(&tmp);
+    debug_ptr += sprintf(CS debug_ptr,
+      LOGGING(millisec) ? "%02d:%02d:%02d.%03d " : "%02d:%02d:%02d ",
+      t->tm_hour, t->tm_min, t->tm_sec, (int)(now.tv_usec/1000));
     }
 
   DEBUG(D_pid)
     }
 
   DEBUG(D_pid)
-    {
-    sprintf(CS debug_ptr, "%5d ", (int)getpid());
-    while(*debug_ptr != 0) debug_ptr++;
-    }
+    debug_ptr += sprintf(CS debug_ptr, "%5d ", (int)getpid());
 
   /* Set up prefix if outputting for host checking and not debugging */
 
 
   /* Set up prefix if outputting for host checking and not debugging */
 
@@ -192,22 +208,52 @@ if (debug_ptr == debug_buffer)
   debug_prefix_length = debug_ptr - debug_buffer;
   }
 
   debug_prefix_length = debug_ptr - debug_buffer;
   }
 
+if (indent > 0)
+  {
+  int i;
+  for (i = indent >> 2; i > 0; i--)
+    DEBUG(D_noutf8)
+      {
+      Ustrcpy(debug_ptr, "   !");
+      debug_ptr += 4;  /* 3 spaces + shriek */
+      debug_prefix_length += 4;
+      }
+    else
+      {
+      Ustrcpy(debug_ptr, "   " UTF8_VERT_2DASH);
+      debug_ptr += 6;  /* 3 spaces + 3 UTF-8 octets */
+      debug_prefix_length += 6;
+      }
+
+  Ustrncpy(debug_ptr, "   ", indent &= 3);
+  debug_ptr += indent;
+  debug_prefix_length += indent;
+  }
+
 /* Use the checked formatting routine to ensure that the buffer
 does not overflow. Ensure there's space for a newline at the end. */
 
 /* Use the checked formatting routine to ensure that the buffer
 does not overflow. Ensure there's space for a newline at the end. */
 
-if (!string_vformat(debug_ptr,
-     sizeof(debug_buffer) - (debug_ptr - debug_buffer) - 1, format, ap))
   {
   {
-  uschar *s = US"**** debug string too long - truncated ****\n";
-  uschar *p = debug_buffer + Ustrlen(debug_buffer);
-  int maxlen = sizeof(debug_buffer) - Ustrlen(s) - 3;
-  if (p > debug_buffer + maxlen) p = debug_buffer + maxlen;
-  if (p > debug_buffer && p[-1] != '\n') *p++ = '\n';
-  Ustrcpy(p, s);
+  gstring gs = { .size = (int)sizeof(debug_buffer) - 1,
+               .ptr = debug_ptr - debug_buffer,
+               .s = debug_buffer };
+  if (!string_vformat(&gs, FALSE, format, ap))
+    {
+    uschar * s = US"**** debug string too long - truncated ****\n";
+    uschar * p = gs.s + gs.ptr;
+    int maxlen = gs.size - Ustrlen(s) - 2;
+    if (p > gs.s + maxlen) p = gs.s + maxlen;
+    if (p > gs.s && p[-1] != '\n') *p++ = '\n';
+    Ustrcpy(p, s);
+    while(*debug_ptr) debug_ptr++;
+    }
+  else
+    {
+    string_from_gstring(&gs);
+    debug_ptr = gs.s + gs.ptr;
+    }
   }
 
   }
 
-while(*debug_ptr != 0) debug_ptr++;
-
 /* Output the line if it is complete. If we added any prefix data and there
 are internal newlines, make sure the prefix is on the continuation lines,
 as long as there is room in the buffer. We want to do just a single fprintf()
 /* Output the line if it is complete. If we added any prefix data and there
 are internal newlines, make sure the prefix is on the continuation lines,
 as long as there is room in the buffer. We want to do just a single fprintf()
@@ -235,6 +281,7 @@ if (debug_ptr[-1] == '\n')
   debug_ptr = debug_buffer;
   debug_prefix_length = 0;
   }
   debug_ptr = debug_buffer;
   debug_prefix_length = 0;
   }
+errno = save_errno;
 }
 
 /* End of debug.c */
 }
 
 /* End of debug.c */
index 87b54d8..664d004 100644 (file)
@@ -2,13 +2,16 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* The main code for delivering a message. */
 
 
 #include "exim.h"
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* The main code for delivering a message. */
 
 
 #include "exim.h"
+#include "transports/smtp.h"
+#include <sys/uio.h>
+#include <assert.h>
 
 
 /* Data block for keeping track of subprocesses for parallel remote
 
 
 /* Data block for keeping track of subprocesses for parallel remote
@@ -63,10 +66,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;
 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;
 static address_item *addr_dsntmp = NULL;
 static address_item *addr_senddsn = NULL;
-#endif
 
 static FILE *message_log = NULL;
 static BOOL update_spool;
 
 static FILE *message_log = NULL;
 static BOOL update_spool;
@@ -77,9 +78,53 @@ static int  return_count;
 static uschar *frozen_info = US"";
 static uschar *used_return_path = NULL;
 
 static uschar *frozen_info = US"";
 static uschar *used_return_path = NULL;
 
-static uschar spoolname[PATH_MAX];
 
 
 
 
+/*************************************************
+*          read as much as requested             *
+*************************************************/
+
+/* The syscall read(2) doesn't always returns as much as we want. For
+several reasons it might get less. (Not talking about signals, as syscalls
+are restartable). When reading from a network or pipe connection the sender
+might send in smaller chunks, with delays between these chunks. The read(2)
+may return such a chunk.
+
+The more the writer writes and the smaller the pipe between write and read is,
+the more we get the chance of reading leass than requested. (See bug 2130)
+
+This function read(2)s until we got all the data we *requested*.
+
+Note: This function may block. Use it only if you're sure about the
+amount of data you will get.
+
+Argument:
+  fd          the file descriptor to read from
+  buffer      pointer to a buffer of size len
+  len         the requested(!) amount of bytes
+
+Returns:      the amount of bytes read
+*/
+static ssize_t
+readn(int fd, void * buffer, size_t len)
+{
+  void * next = buffer;
+  void * end = buffer + len;
+
+  while (next < end)
+    {
+    ssize_t got = read(fd, next, end - next);
+
+    /* I'm not sure if there are signals that can interrupt us,
+    for now I assume the worst */
+    if (got == -1 && errno == EINTR) continue;
+    if (got <= 0) return next - buffer;
+    next += got;
+    }
+
+  return len;
+}
+
 
 /*************************************************
 *             Make a new address item            *
 
 /*************************************************
 *             Make a new address item            *
@@ -127,10 +172,10 @@ Returns:        nothing
 void
 deliver_set_expansions(address_item *addr)
 {
 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;
   }
 
   return;
   }
 
@@ -138,20 +183,22 @@ if (addr == NULL)
 what they contain. These first ones are always set, taking their values from
 the first address. */
 
 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 = deliver_host_address = US"";
+  deliver_host_port = 0;
   }
 else
   {
   deliver_host = addr->host_list->name;
   deliver_host_address = addr->host_list->address;
   }
 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_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 */
 
 
 /* These may be unset for multiple addresses */
 
@@ -167,7 +214,7 @@ bmi_base64_tracker_verdict = NULL;
 
 /* If there's only one address we can set everything. */
 
 
 /* If there's only one address we can set everything. */
 
-if (addr->next == NULL)
+if (!addr->next)
   {
   address_item *addr_orig;
 
   {
   address_item *addr_orig;
 
@@ -175,8 +222,7 @@ if (addr->next == NULL)
   deliver_localpart_prefix = addr->prefix;
   deliver_localpart_suffix = addr->suffix;
 
   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
   deliver_domain_orig = addr_orig->domain;
 
   /* Re-instate any prefix and suffix in the original local part. In all
@@ -185,30 +231,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. */
 
   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 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_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))
       {
 
     /* 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;
       deliver_localpart = addr->parent->local_part;
       deliver_localpart_prefix = addr->parent->prefix;
       deliver_localpart_suffix = addr->parent->suffix;
@@ -222,9 +271,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 */
     /* 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);
       bmi_alt_location = bmi_get_alt_location(bmi_base64_verdict);
-    };
 #endif
 
   }
 #endif
 
   }
@@ -239,18 +287,19 @@ else
   address_item *addr2;
   if (testflag(addr, af_pfr))
     {
   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;
       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;
       self_hostname = NULL;
-    if (deliver_domain == NULL && self_hostname == NULL) break;
+    if (!deliver_domain && !self_hostname) break;
     }
   }
 }
     }
   }
 }
@@ -267,6 +316,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.
 
 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
 Argument:
   filename  the file name
   mode      the mode required
@@ -278,38 +329,49 @@ Returns:    a file descriptor, or -1 (with errno set)
 static int
 open_msglog_file(uschar *filename, int mode, uschar **error)
 {
 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 +423,16 @@ static void
 replicate_status(address_item *addr)
 {
 address_item *addr2;
 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->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->delivery_usec =    addr->delivery_usec;
+  addr2->special_action =   addr->special_action;
+  addr2->message =         addr->message;
+  addr2->user_message =            addr->user_message;
   }
 }
 
   }
 }
 
@@ -402,7 +465,7 @@ Returns:    TRUE if the lists refer to the same host set
 static BOOL
 same_hosts(host_item *one, host_item *two)
 {
 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)
     {
   {
   if (Ustrcmp(one->name, two->name) != 0)
     {
@@ -416,8 +479,8 @@ while (one != NULL && two != NULL)
 
     /* Find the ends of the shortest sequence of identical MX values */
 
 
     /* 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;
       {
       end_one = end_one->next;
       end_two = end_two->next;
@@ -446,6 +509,10 @@ while (one != NULL && two != NULL)
     two = end_two;
     }
 
     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;
   /* Hosts matched */
 
   one = one->next;
@@ -476,13 +543,11 @@ Returns:    TRUE if the lists refer to the same header set
 static BOOL
 same_headers(header_line *one, header_line *two)
 {
 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 == 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;
   if (Ustrcmp(one->text, two->text) != 0) return FALSE;
-  one = one->next;
-  two = two->next;
   }
 }
 
   }
 }
 
@@ -506,7 +571,7 @@ static BOOL
 same_strings(uschar *one, uschar *two)
 {
 if (one == two) return TRUE;   /* Includes the case where both NULL */
 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);
 }
 
 return (Ustrcmp(one, two) == 0);
 }
 
@@ -531,21 +596,21 @@ Returns:        TRUE or FALSE
 static BOOL
 same_ugid(transport_instance *tp, address_item *addr1, address_item *addr2)
 {
 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;
 }
 
 return TRUE;
 }
@@ -598,7 +663,7 @@ update_spool = TRUE;        /* Ensure spool gets updated */
 
 /* Top-level address */
 
 
 /* Top-level address */
 
-if (addr->parent == NULL)
+if (!addr->parent)
   {
   tree_add_nonrecipient(addr->unique);
   tree_add_nonrecipient(addr->address);
   {
   tree_add_nonrecipient(addr->unique);
   tree_add_nonrecipient(addr->address);
@@ -608,11 +673,9 @@ if (addr->parent == NULL)
 
 else if (testflag(addr, af_homonym))
   {
 
 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));
     tree_add_nonrecipient(
       string_sprintf("%s/%s", addr->unique + 3, addr->transport->name));
-    }
   }
 
 /* Non-homonymous child address */
   }
 
 /* Non-homonymous child address */
@@ -622,14 +685,12 @@ else tree_add_nonrecipient(addr->unique);
 /* Check the list of duplicate addresses and ensure they are now marked
 done as well. */
 
 /* 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);
     }
   if (Ustrcmp(addr->unique, dup->unique) == 0)
     {
     tree_add_nonrecipient(dup->unique);
     child_done(dup, now);
     }
-  }
 }
 
 
 }
 
 
@@ -656,18 +717,18 @@ static void
 child_done(address_item *addr, uschar *now)
 {
 address_item *aa;
 child_done(address_item *addr, uschar *now)
 {
 address_item *aa;
-while (addr->parent != NULL)
+while (addr->parent)
   {
   addr = 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. */
 
   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 (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);
 
   deliver_msglog("%s %s: children all complete\n", now, addr->address);
   DEBUG(D_deliver) debug_printf("%s: children all complete\n", addr->address);
@@ -676,35 +737,394 @@ 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.
 
 
-static uschar *
-d_hostlog(uschar * s, int * sizep, int * ptrp, address_item * addr)
+Arguments:
+  g         The log line
+  addr      The address to be logged
+
+Returns:    New value for s
+*/
+
+static gstring *
+d_log_interface(gstring * g)
+{
+if (LOGGING(incoming_interface) && LOGGING(outgoing_interface)
+    && sending_ip_address)
+  {
+  g = string_fmt_append(g, " I=[%s]", sending_ip_address);
+  if (LOGGING(outgoing_port))
+    g = string_fmt_append(g, "%d", sending_port);
+  }
+return g;
+}
+
+
+
+static gstring *
+d_hostlog(gstring * g, address_item * addr)
 {
 {
-  s = string_append(s, sizep, ptrp, 5, US" H=", addr->host_used->name,
-    US" [", addr->host_used->address, US"]");
-  if ((log_extra_selector & LX_outgoing_port) != 0)
-    s = string_append(s, sizep, ptrp, 2, US":", string_sprintf("%d",
-      addr->host_used->port));
-  return s;
+host_item * h = addr->host_used;
+
+g = string_append(g, 2, US" H=", h->name);
+
+if (LOGGING(dnssec) && h->dnssec == DS_YES)
+  g = string_catn(g, US" DS", 3);
+
+g = string_append(g, 3, US" [", h->address, US"]");
+
+if (LOGGING(outgoing_port))
+  g = string_fmt_append(g, ":%d", h->port);
+
+#ifdef SUPPORT_SOCKS
+if (LOGGING(proxy) && proxy_local_address)
+  {
+  g = string_append(g, 3, US" PRX=[", proxy_local_address, US"]");
+  if (LOGGING(outgoing_port))
+    g = string_fmt_append(g, ":%d", proxy_local_port);
+  }
+#endif
+
+g = d_log_interface(g);
+
+if (testflag(addr, af_tcp_fastopen))
+  g = string_catn(g, US" TFO*", testflag(addr, af_tcp_fastopen_data) ? 5 : 4);
+
+return g;
 }
 
 }
 
+
+
+
+
 #ifdef SUPPORT_TLS
 #ifdef SUPPORT_TLS
-static uschar *
-d_tlslog(uschar * s, int * sizep, int * ptrp, address_item * addr)
+static gstring *
+d_tlslog(gstring * s, 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, 2, US" X=", addr->cipher);
+if (LOGGING(tls_certificate_verified) && addr->cipher)
+  s = string_append(s, 2, US" CV=",
+    testflag(addr, af_cert_verified)
+    ?
+#ifdef SUPPORT_DANE
+      testflag(addr, af_dane_verified)
+    ? "dane"
+    :
+#endif
+      "yes"
+    : "no");
+if (LOGGING(tls_peerdn) && addr->peerdn)
+  s = string_append(s, 3, US" DN=\"", string_printing(addr->peerdn), US"\"");
+return s;
 }
 #endif
 
 }
 #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 ? transport_name : US"main", 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;
+
+router_name =    addr->router ? addr->router->name : NULL;
+deliver_domain = addr->domain;
+deliver_localpart = addr->local_part;
+deliver_host =   addr->host_used ? addr->host_used->name : NULL;
+
+if (!addr->transport)
+  {
+  if (Ustrcmp(event, "msg:fail:delivery") == 0)
+    {
+     /* An address failed with no transport involved. This happens when
+     a filter was used which triggered a fail command (in such a case
+     a transport isn't needed).  Convert it to an internal fail event. */
+
+    (void) event_raise(event_action, US"msg:fail:internal", addr->message);
+    }
+  }
+else
+  {
+  transport_name = addr->transport->name;
+
+  (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
+           || Ustrcmp(addr->transport->driver_name, "autoreply") == 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
+
+Returns:      the new value of the buffer pointer
+*/
+
+static gstring *
+string_get_localpart(address_item * addr, gstring * yield)
+{
+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, 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, 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, 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:
+  g             points to growing-string struct
+  addr          bottom (ultimate) address
+  all_parents   if TRUE, include all parents
+  success       TRUE for successful delivery
+
+Returns:        a growable string in dynamic store
+*/
+
+static gstring *
+string_log_address(gstring * g,
+  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] != '/')
+    g = string_catn(g, CUS"save ", 5);
+  g = string_get_localpart(addr, g);
+  }
+
+/* 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 = g->s + g->ptr;
+
+  if (addr->local_part)
+    {
+    const uschar * s;
+    g = string_get_localpart(addr, g);
+    g = string_catn(g, US"@", 1);
+    s = addr->domain;
+#ifdef SUPPORT_I18N
+    if (testflag(addr, af_utf8_downcvt))
+      s = string_localpart_utf8_to_alabel(s, NULL);
+#endif
+    g = string_cat(g, s);
+    }
+  else
+    g = string_cat(g, 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. */
+
+  string_from_gstring(g);      /* ensure nul-terminated */
+  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)
+    {
+    g = string_catn(g, s, 2);
+    g = string_cat (g, addr2->address);
+    if (!all_parents) break;
+    s = US", ";
+    }
+  g = string_catn(g, US")", 1);
+  }
+
+/* Add the top address if it is required */
+
+if (add_topaddr)
+  g = string_append(g, 3,
+    US" <",
+    addr->onetime_parent ? addr->onetime_parent : topaddr->address,
+    US">");
+
+return g;
+}
+
+
+
+void
+timesince(struct timeval * diff, struct timeval * then)
+{
+gettimeofday(diff, NULL);
+diff->tv_sec -= then->tv_sec;
+if ((diff->tv_usec -= then->tv_usec) < 0)
+  {
+  diff->tv_sec--;
+  diff->tv_usec += 1000*1000;
+  }
+}
+
+
+
+uschar *
+string_timediff(struct timeval * diff)
+{
+static uschar buf[sizeof("0.000s")];
+
+if (diff->tv_sec >= 5 || !LOGGING(millisec))
+  return readconf_printtime((int)diff->tv_sec);
+
+sprintf(CS buf, "%u.%03us", (uint)diff->tv_sec, (uint)diff->tv_usec/1000);
+return buf;
+}
+
+
+uschar *
+string_timesince(struct timeval * then)
+{
+struct timeval diff;
+
+timesince(&diff, then);
+return string_timediff(&diff);
+}
+
+/******************************************************************************/
+
+
+
 /* 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
 but sender-host and sender are prefixed and "msg" is inserted in the log line.
 /* 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
 but sender-host and sender are prefixed and "msg" is inserted in the log line.
@@ -715,45 +1135,46 @@ Arguments:
 void
 delivery_log(int flags, address_item * addr, int logchar, uschar * msg)
 {
 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.  */
-
+gstring * g; /* Used for a temporary, expanding buffer, for 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. */
 
 
 /* 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
 
   lookup_dnssec_authenticated = NULL;
 #endif
 
-s = reset_point = store_get(size);
+g = reset_point = string_get(256);
 
 
-log_address = string_log_address(addr, (log_write_selector & L_all_parents) != 0, TRUE);
 if (msg)
 if (msg)
-  s = string_append(s, &size, &ptr, 3, host_and_ident(TRUE), US" ", log_address);
+  g = string_append(g, 2, host_and_ident(TRUE), US" ");
 else
   {
 else
   {
-  s[ptr++] = logchar;
-  s = string_append(s, &size, &ptr, 2, US"> ", log_address);
+  g->s[0] = logchar; g->ptr = 1;
+  g = string_catn(g, US"> ", 2);
   }
   }
+g = string_log_address(g, addr, LOGGING(all_parents), TRUE);
+
+if (LOGGING(sender_on_delivery) || msg)
+  g = string_append(g, 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)
+  g = string_append(g, 2, US" Q=", queue_name);
 
 #ifdef EXPERIMENTAL_SRS
 
 #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)
+  g = string_append(g, 3, US" SRS=<", addr->prop.srs_sender, US">");
 #endif
 
 /* You might think that the return path must always be set for a successful
 #endif
 
 /* You might think that the return path must always be set for a successful
@@ -761,37 +1182,30 @@ 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. */
 
 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)
-  s = string_append(s, &size, &ptr, 3, US" P=<", used_return_path, US">");
+if (used_return_path && LOGGING(return_path_on_delivery))
+  g = string_append(g, 3, US" P=<", used_return_path, US">");
 
 if (msg)
 
 if (msg)
-  s = string_append(s, &size, &ptr, 2, US" ", msg);
+  g = string_append(g, 2, US" ", msg);
 
 /* For a delivery from a system filter, there may not be a router */
 
 /* For a delivery from a system filter, there may not be a router */
-if (addr->router != NULL)
-  s = string_append(s, &size, &ptr, 2, US" R=", addr->router->name);
+if (addr->router)
+  g = string_append(g, 2, US" R=", addr->router->name);
 
 
-s = string_append(s, &size, &ptr, 2, US" T=", addr->transport->name);
+g = string_append(g, 2, US" T=", addr->transport->name);
 
 
-if ((log_extra_selector & LX_delivery_size) != 0)
-  s = string_append(s, &size, &ptr, 2, US" S=",
-    string_sprintf("%d", transport_count));
+if (LOGGING(delivery_size))
+  g = string_fmt_append(g, " S=%d", transport_count);
 
 /* Local delivery */
 
 if (addr->transport->info->local)
   {
 
 /* Local delivery */
 
 if (addr->transport->info->local)
   {
-  if (addr->host_list != NULL)
-    {
-    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));
+  if (addr->host_list)
+    g = string_append(g, 2, US" H=", addr->host_list->name);
+  g = d_log_interface(g);
+  if (addr->shadow_message)
+    g = string_cat(g, addr->shadow_message);
   }
 
 /* Remote delivery */
   }
 
 /* Remote delivery */
@@ -800,98 +1214,249 @@ else
   {
   if (addr->host_used)
     {
   {
   if (addr->host_used)
     {
-    s = d_hostlog(s, &size, &ptr, addr);
+    g = d_hostlog(g, addr);
     if (continue_sequence > 1)
     if (continue_sequence > 1)
-      s = string_cat(s, &size, &ptr, US"*", 1);
+      g = string_catn(g, 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;
 
     /* 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
-  s = d_tlslog(s, &size, &ptr, addr);
-  #endif
+#ifdef SUPPORT_TLS
+  g = d_tlslog(g, addr);
+#endif
 
   if (addr->authenticator)
     {
 
   if (addr->authenticator)
     {
-    s = string_append(s, &size, &ptr, 2, US" A=", addr->authenticator);
+    g = string_append(g, 2, US" A=", addr->authenticator);
     if (addr->auth_id)
       {
     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)
-        s = string_append(s, &size, &ptr, 2, US":", addr->auth_sndr);
+      g = string_append(g, 2, US":", addr->auth_id);
+      if (LOGGING(smtp_mailauth) && addr->auth_sndr)
+        g = string_append(g, 2, US":", addr->auth_sndr);
       }
     }
 
       }
     }
 
-  #ifndef DISABLE_PRDR
-  if (addr->flags & af_prdr_used)
-    s = string_append(s, &size, &ptr, 1, US" PRDR");
-  #endif
+  if (LOGGING(pipelining))
+    {
+    if (testflag(addr, af_pipelining))
+      g = string_catn(g, US" L", 2);
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+    if (testflag(addr, af_early_pipe))
+      g = string_catn(g, US"*", 1);
+#endif
+    }
+
+#ifndef DISABLE_PRDR
+  if (testflag(addr, af_prdr_used))
+    g = string_catn(g, US" PRDR", 5);
+#endif
+
+  if (testflag(addr, af_chunking_used))
+    g = string_catn(g, US" K", 2);
   }
 
 /* confirmation message (SMTP (host_used) and LMTP (driver_name)) */
 
   }
 
 /* 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++ = '\"';
   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];
     }
   *p++ = '\"';
   *p = 0;
     {
     if (ss[i] == '\"' || ss[i] == '\\') *p++ = '\\'; /* quote \ and " */
     *p++ = ss[i];
     }
   *p++ = '\"';
   *p = 0;
-  s = string_append(s, &size, &ptr, 2, US" C=", big_buffer);
+  g = string_append(g, 2, US" C=", big_buffer);
   }
 
 /* Time on queue and actual time taken to deliver */
 
   }
 
 /* Time on queue and actual time taken to deliver */
 
-if ((log_extra_selector & LX_queue_time) != 0)
-  s = string_append(s, &size, &ptr, 2, US" QT=",
-    readconf_printtime( (int) ((long)time(NULL) - (long)received_time)) );
+if (LOGGING(queue_time))
+  g = string_append(g, 2, US" QT=",
+    string_timesince(&received_time));
 
 
-if ((log_extra_selector & LX_deliver_time) != 0)
-  s = string_append(s, &size, &ptr, 2, US" DT=",
-    readconf_printtime(addr->more_errno));
+if (LOGGING(deliver_time))
+  {
+  struct timeval diff = {.tv_sec = addr->more_errno, .tv_usec = addr->delivery_usec};
+  g = string_append(g, 2, US" DT=", string_timediff(&diff));
+  }
 
 /* string_cat() always leaves room for the terminator. Release the
 store we used to build the line after writing it. */
 
 
 /* string_cat() always leaves room for the terminator. Release the
 store we used to build the line after writing it. */
 
-s[ptr] = 0;
-log_write(0, flags, "%s", s);
+log_write(0, flags, "%s", string_from_gstring(g));
+
+#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)
+{
+gstring * g;
+void * reset_point;
+
+/* Build up the line that is used for both the message log and the main
+log. */
 
 
-#ifdef EXPERIMENTAL_TPDA
-if (addr->transport->tpda_delivery_action)
+g = reset_point = string_get(256);
+
+/* 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. */
+
+g = string_log_address(g, addr, LOGGING(all_parents), FALSE);
+
+if (*queue_name)
+  g = string_append(g, 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);
+  if (driver_kind[1] == 't' && addr->router)
+    g = string_append(g, 2, US" R=", addr->router->name);
+  g = string_fmt_append(g, " %c=%s", toupper(driver_kind[1]), driver_name);
+  }
+else if (driver_kind)
+  g = string_append(g, 2, US" ", driver_kind);
 
 
-  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;
+g = string_fmt_append(g, " defer (%d)", addr->basic_errno);
+
+if (addr->basic_errno > 0)
+  g = string_append(g, 2, US": ",
+    US strerror(addr->basic_errno));
+
+if (addr->host_used)
+  {
+  g = string_append(g, 5,
+                   US" H=", addr->host_used->name,
+                   US" [",  addr->host_used->address, US"]");
+  if (LOGGING(outgoing_port))
+    {
+    int port = addr->host_used->port;
+    g = string_fmt_append(g, ":%d", port == PORT_NONE ? 25 : port);
+    }
   }
   }
+
+if (addr->message)
+  g = string_append(g, 2, US": ", addr->message);
+
+(void) string_from_gstring(g);
+
+/* Log the deferment in the message log, but don't clutter it
+up with retry-time defers after the first delivery attempt. */
+
+if (f.deliver_firsttime || addr->basic_errno > ERRNO_RETRY_BASE)
+  deliver_msglog("%s %s\n", now, g->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", g->s);
+
+store_reset(reset_point);
+return;
+}
+
+
+
+static void
+failure_log(address_item * addr, uschar * driver_kind, uschar * now)
+{
+void * reset_point;
+gstring * g = reset_point = string_get(256);
+
+#ifndef DISABLE_EVENT
+/* Message failures for which we will send a DSN get their event raised
+later so avoid doing it here. */
+
+if (  !addr->prop.ignore_error
+   && !(addr->dsn_flags & (rf_dsnflags & ~rf_notify_failure))
+   )
+  msg_event_raise(US"msg:fail:delivery", addr);
+#endif
+
+/* Build up the log line for the message and main logs */
+
+/* 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. */
+
+g = string_log_address(g, addr, LOGGING(all_parents), FALSE);
+
+if (LOGGING(sender_on_delivery))
+  g = string_append(g, 3, US" F=<", sender_address, US">");
+
+if (*queue_name)
+  g = string_append(g, 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))
+  g = string_append(g, 3, US" P=<", used_return_path, US">");
+
+if (addr->router)
+  g = string_append(g, 2, US" R=", addr->router->name);
+if (addr->transport)
+  g = string_append(g, 2, US" T=", addr->transport->name);
+
+if (addr->host_used)
+  g = d_hostlog(g, addr);
+
+#ifdef SUPPORT_TLS
+g = d_tlslog(g, addr);
 #endif
 #endif
+
+if (addr->basic_errno > 0)
+  g = string_append(g, 2, US": ", US strerror(addr->basic_errno));
+
+if (addr->message)
+  g = string_append(g, 2, US": ", addr->message);
+
+(void) string_from_gstring(g);
+
+/* 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, g->s);
+else
+  deliver_msglog("%s %s\n", now, g->s);
+
+log_write(0, LOG_MAIN, "** %s", g->s);
+
 store_reset(reset_point);
 return;
 }
 store_reset(reset_point);
 return;
 }
@@ -923,36 +1488,29 @@ 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 *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);
 
 /* Set up driver kind and name for logging. Disable logging if the router or
 transport has disabled it. */
 
 
 DEBUG(D_deliver) debug_printf("post-process %s (%d)\n", addr->address, result);
 
 /* Set up driver kind and name for logging. Disable logging if the router or
 transport has disabled it. */
 
-if (driver_type == DTYPE_TRANSPORT)
+if (driver_type == EXIM_DTYPE_TRANSPORT)
   {
   {
-  if (addr->transport != NULL)
+  if (addr->transport)
     {
     driver_name = addr->transport->name;
     driver_kind = US" transport";
     {
     driver_name = addr->transport->name;
     driver_kind = US" transport";
-    disable_logging = addr->transport->disable_logging;
+    f.disable_logging = addr->transport->disable_logging;
     }
   else driver_kind = US"transporting";
   }
     }
   else driver_kind = US"transporting";
   }
-else if (driver_type == DTYPE_ROUTER)
+else if (driver_type == EXIM_DTYPE_ROUTER)
   {
   {
-  if (addr->router != NULL)
+  if (addr->router)
     {
     driver_name = addr->router->name;
     driver_kind = US" router";
     {
     driver_name = addr->router->name;
     driver_kind = US" router";
-    disable_logging = addr->router->disable_logging;
+    f.disable_logging = addr->router->disable_logging;
     }
   else driver_kind = US"routing";
   }
     }
   else driver_kind = US"routing";
   }
@@ -964,22 +1522,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.) */
 
 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
   }
 
 /* If we used a transport that has one of the "return_output" options set, and
@@ -994,7 +1542,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. */
 
 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;
   {
   BOOL return_output = FALSE;
   struct stat statbuf;
@@ -1008,46 +1556,44 @@ if (addr->return_file >= 0 && addr->return_filename != NULL)
 
     /* Handle logging options */
 
 
     /* 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");
       {
       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
         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);
           {
           uschar *p = big_buffer + Ustrlen(big_buffer);
+         const uschar * sp;
           while (p > big_buffer && isspace(p[-1])) p--;
           *p = 0;
           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",
           log_write(0, LOG_MAIN, "<%s>: %s transport output: %s",
-            addr->address, tb->name, s);
+            addr->address, tb->name, sp);
           }
           }
-        (void)fclose(f);
-        }
+      (void)fclose(f);
       }
 
     /* Handle returning options, but only if there is an address to return
     the text to. */
 
       }
 
     /* 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 (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;
           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
     }
 
   /* Get rid of the file unless it might be returned, but close it in
@@ -1078,7 +1624,7 @@ if (result == OK)
   address_done(addr, now);
   DEBUG(D_deliver) debug_printf("%s delivered\n", addr->address);
 
   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
     deliver_msglog("%s %s: %s%s succeeded\n", now, addr->address,
       driver_name, driver_kind);
   else
@@ -1088,8 +1634,8 @@ if (result == OK)
     child_done(addr, now);
     }
 
     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;
   tls_out.ourcert = addr->ourcert;
   addr->ourcert = NULL;
   tls_out.peercert = addr->peercert;
@@ -1098,25 +1644,23 @@ if (result == OK)
   tls_out.cipher = addr->cipher;
   tls_out.peerdn = addr->peerdn;
   tls_out.ocsp = addr->ocsp;
   tls_out.cipher = addr->cipher;
   tls_out.peerdn = addr->peerdn;
   tls_out.ocsp = addr->ocsp;
-  #endif
+# ifdef SUPPORT_DANE
+  tls_out.dane_verified = testflag(addr, af_dane_verified);
+# endif
+#endif
 
   delivery_log(LOG_MAIN, addr, logchar, NULL);
 
 
   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;
   tls_out.cipher = NULL;
   tls_out.peerdn = NULL;
   tls_out.ocsp = OCSP_NOT_REQ;
-  #endif
+# ifdef SUPPORT_DANE
+  tls_out.dane_verified = FALSE;
+# endif
+#endif
   }
 
 
   }
 
 
@@ -1127,95 +1671,29 @@ else if (result == DEFER || result == PANIC)
   {
   if (result == PANIC) logflags |= LOG_PANIC;
 
   {
   if (result == PANIC) logflags |= LOG_PANIC;
 
-  /* This puts them on the chain in reverse order. Do not change this, because
-  the code for handling retries assumes that the one with the retry
-  information is last. */
-
-  addr->next = addr_defer;
-  addr_defer = addr;
-
-  /* The only currently implemented special action is to freeze the
-  message. Logging of this is done later, just before the -H file is
-  updated. */
-
-  if (addr->special_action == SPECIAL_FREEZE)
-    {
-    deliver_freeze = TRUE;
-    deliver_frozen_at = time(NULL);
-    update_spool = TRUE;
-    }
-
-  /* If doing a 2-stage queue run, we skip writing to either the message
-  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);
+  /* This puts them on the chain in reverse order. Do not change this, because
+  the code for handling retries assumes that the one with the retry
+  information is last. */
 
 
-    s[ptr] = 0;
+  addr->next = addr_defer;
+  addr_defer = addr;
 
 
-    /* Log the deferment in the message log, but don't clutter it
-    up with retry-time defers after the first delivery attempt. */
+  /* The only currently implemented special action is to freeze the
+  message. Logging of this is done later, just before the -H file is
+  updated. */
 
 
-    if (deliver_firsttime || addr->basic_errno > ERRNO_RETRY_BASE)
-      deliver_msglog("%s %s\n", now, s);
+  if (addr->special_action == SPECIAL_FREEZE)
+    {
+    f.deliver_freeze = TRUE;
+    deliver_frozen_at = time(NULL);
+    update_spool = TRUE;
+    }
 
 
-    /* Write the main log and reset the store */
+  /* If doing a 2-stage queue run, we skip writing to either the message
+  log or the main log for SMTP defers. */
 
 
-    log_write(use_log_selector, logflags, "== %s", s);
-    store_reset(reset_point);
-    }
+  if (!f.queue_2stage || addr->basic_errno != 0)
+    deferral_log(addr, now, logflags, driver_name, driver_kind);
   }
 
 
   }
 
 
@@ -1231,8 +1709,8 @@ else
   force the af_ignore_error flag. This will cause the address to be discarded
   later (with a log entry). */
 
   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)
-    setflag(addr, af_ignore_error);
+  if (!*sender_address && message_age >= ignore_bounce_errors_after)
+    addr->prop.ignore_error = TRUE;
 
   /* Freeze the message if requested, or if this is a bounce message (or other
   message with null sender) and this address does not have its own errors
 
   /* Freeze the message if requested, or if this is a bounce message (or other
   message with null sender) and this address does not have its own errors
@@ -1240,15 +1718,17 @@ else
   to ignore occurs later, instead of sending a message. Logging of freezing
   occurs later, just before writing the -H file. */
 
   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)
-      ))
-    {
-    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;
+  if (  !addr->prop.ignore_error
+     && (  addr->special_action == SPECIAL_FREEZE
+        || (sender_address[0] == 0 && !addr->prop.errors_address)
+     )  )
+    {
+    frozen_info = addr->special_action == SPECIAL_FREEZE
+      ? US""
+      : f.sender_local && !f.local_error_message
+      ? US" (message created with -f <>)"
+      : US" (delivery error message)";
+    f.deliver_freeze = TRUE;
     deliver_frozen_at = time(NULL);
     update_spool = TRUE;
 
     deliver_frozen_at = time(NULL);
     update_spool = TRUE;
 
@@ -1268,63 +1748,12 @@ else
     addr_failed = addr;
     }
 
     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 */
 
   }
 
 /* Ensure logging is turned on again in all cases */
 
-disable_logging = FALSE;
+f.disable_logging = FALSE;
 }
 
 
 }
 
 
@@ -1356,19 +1785,18 @@ common_error(BOOL logit, address_item *addr, int code, uschar *format, ...)
 address_item *addr2;
 addr->basic_errno = code;
 
 address_item *addr2;
 addr->basic_errno = code;
 
-if (format != NULL)
+if (format)
   {
   va_list ap;
   {
   va_list ap;
-  uschar buffer[512];
+  gstring * g;
+
   va_start(ap, format);
   va_start(ap, format);
-  if (!string_vformat(buffer, sizeof(buffer), CS format, ap))
-    log_write(0, LOG_MAIN|LOG_PANIC_DIE,
-      "common_error expansion was longer than " SIZE_T_FMT, sizeof(buffer));
+  g = string_vformat(NULL, TRUE, CS format, ap);
   va_end(ap);
   va_end(ap);
-  addr->message = string_copy(buffer);
+  addr->message = string_from_gstring(g);
   }
 
   }
 
-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;
   {
   addr2->basic_errno = code;
   addr2->message = addr->message;
@@ -1399,7 +1827,7 @@ static BOOL
 check_never_users(uid_t uid, uid_t *nusers)
 {
 int i;
 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;
 }
 for (i = 1; i <= (int)(nusers[0]); i++) if (nusers[i] == uid) return TRUE;
 return FALSE;
 }
@@ -1432,7 +1860,7 @@ static BOOL
 findugid(address_item *addr, transport_instance *tp, uid_t *uidp, gid_t *gidp,
   BOOL *igfp)
 {
 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 */
 BOOL gid_set = FALSE;
 
 /* Default initgroups flag comes from the transport */
@@ -1447,15 +1875,15 @@ if (tp->gid_set)
   *gidp = tp->gid;
   gid_set = TRUE;
   }
   *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;
     }
     {
     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. */
   }
 
 /* If the transport did not set a group, see if the router did. */
@@ -1473,7 +1901,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. */
 
 /* 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,
   {
   struct passwd *pw;
   if (!route_find_expanded_user(tp->expand_uid, tp->name, US"transport", &pw,
@@ -1482,7 +1910,7 @@ else if (tp->expand_uid != NULL)
     common_error(FALSE, addr, ERRNO_UIDFAIL, NULL);
     return FALSE;
     }
     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;
     {
     *gidp = pw->pw_gid;
     gid_set = TRUE;
@@ -1537,12 +1965,12 @@ if (!gid_set)
 /* Check that the uid is not on the lists of banned uids that may not be used
 for delivery processes. */
 
 /* 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);
   {
   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 +2011,13 @@ deliver_set_expansions(addr);
 size_limit = expand_string_integer(tp->message_size_limit, TRUE);
 deliver_set_expansions(NULL);
 
 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;
   {
   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)
       "in %s transport: %s", tp->name, expand_string_message);
   }
 else if (size_limit > 0 && message_size > size_limit)
@@ -1737,21 +2164,21 @@ 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. */
 
 /* 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
 #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;
 
 #endif
 else
   return_path = sender_address;
 
-if (tp->return_path != NULL)
+if (tp->return_path)
   {
   uschar *new_return_path = expand_string(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)
+    if (!f.expand_string_forcedfail)
       {
       common_error(TRUE, addr, ERRNO_EXPANDFAIL,
         US"Failed to expand return path \"%s\" in %s transport: %s",
       {
       common_error(TRUE, addr, ERRNO_EXPANDFAIL,
         US"Failed to expand return path \"%s\" in %s transport: %s",
@@ -1777,14 +2204,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. */
 
 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 */
   {
   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,
     {
     common_error(TRUE, addr, ERRNO_EXPANDFAIL, US"home directory \"%s\" failed "
       "to expand for %s transport: %s", rawhome, tp->name,
@@ -1806,14 +2233,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. */
 
 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;
   {
   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,
     {
     common_error(TRUE, addr, ERRNO_EXPANDFAIL, US"current directory \"%s\" "
       "failed to expand for %s transport: %s", raw, tp->name,
@@ -1827,22 +2251,25 @@ if (working_directory != NULL)
     return;
     }
   }
     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 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 =
   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));
     {
     common_error(TRUE, addr, errno, US"Unable to %s file for %s transport "
       "to return message: %s", error, tp->name, strerror(errno));
@@ -1888,19 +2315,19 @@ if ((pid = fork()) == 0)
   diagnosis that it's reasonable to make them something that has to be explicitly requested.
   */
 
   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)
     {
   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)
     if (errno != ENOSYS && errno != ENOTSUP)
-    #endif
+endif
       log_write(0, LOG_MAIN|LOG_PANIC, "setrlimit(RLIMIT_CORE) failed: %s",
         strerror(errno));
     }
       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. */
 
   /* Reset the random number generator, so different processes don't all
   have the same sequence. */
@@ -1911,20 +2338,18 @@ if ((pid = fork()) == 0)
   privileged. (Appendfile uses this to expand quota, for example, while
   able to read private files.) */
 
   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:
     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:
 
       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
 
   /* 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 +2375,7 @@ if ((pid = fork()) == 0)
     {
     address_item *batched;
     debug_printf("  home=%s current=%s\n", deliver_home, working_directory);
     {
     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);
     }
 
       debug_printf("additional batched address: %s\n", batched->address);
     }
 
@@ -1977,7 +2402,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 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,
       {
       ok = transport_set_up_command(&transport_filter_argv,
         addr->transport->filter_command,
@@ -2002,20 +2427,21 @@ if ((pid = fork()) == 0)
   PASS_BACK:
 
   if (replicate) replicate_status(addr);
   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;
 
     {
     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->delivery_usec,  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
         sizeof(transport_instance *))) != sizeof(transport_instance *)
 
     /* For a file delivery, pass back the local part, in case the original
@@ -2023,23 +2449,23 @@ if ((pid = fork()) == 0)
     logging. */
 
       || (testflag(addr2, af_file)
     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
             )
         )
       )
              || (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)
       {
        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");
       }
     }
          ret == -1 ? strerror(errno) : "short write");
       }
     }
@@ -2068,41 +2494,55 @@ will remain. Afterwards, close the reading end. */
 
 (void)close(pfd[pipe_write]);
 
 
 (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;
     {
     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));
       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->delivery_usec,  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))
       {
       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);
       }
 
       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;
       {
       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)
         {
       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);
         }
       }
         if (len > 0) *sptr = string_copy(big_buffer);
         }
       }
@@ -2126,26 +2566,25 @@ in order to record the delivery. */
 
 if (!shadowing)
   {
 
 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 (f.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. */
 
 
   /* Ensure the journal file is pushed out to disk. */
 
@@ -2163,7 +2602,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)
 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",
   if (rc < 0 && errno == ECHILD)      /* Process has vanished */
     {
     log_write(0, LOG_MAIN, "%s transport process vanished unexpectedly",
@@ -2171,7 +2609,6 @@ while ((rc = wait(&status)) != pid)
     status = 0;
     break;
     }
     status = 0;
     break;
     }
-  }
 
 if ((status & 0xffff) != 0)
   {
 
 if ((status & 0xffff) != 0)
   {
@@ -2184,43 +2621,39 @@ if ((status & 0xffff) != 0)
     "status 0x%04x: %s %d",
     addr->transport->driver_name,
     status,
     "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. */
 
     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;
   {
   int fd;
   uschar *warn_message;
+  pid_t pid;
 
   DEBUG(D_deliver) debug_printf("Warning message requested by transport\n");
 
 
   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);
     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;
     }
 
   addr->special_action = SPECIAL_NONE;
@@ -2229,6 +2662,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, EXIM_DTYPE_TRANSPORT, 0);
+      } while ((addr = next));
+    return TRUE;
+    }
+  *key = serialize_key;
+  }
+return FALSE;
+}
+
+
+
 /*************************************************
 *              Do local deliveries               *
 *************************************************/
 /*************************************************
 *              Do local deliveries               *
 *************************************************/
@@ -2252,14 +2731,15 @@ time_t now = time(NULL);
 
 /* Loop until we have exhausted the supply of local deliveries */
 
 
 /* Loop until we have exhausted the supply of local deliveries */
 
-while (addr_local != NULL)
+while (addr_local)
   {
   {
-  time_t delivery_start;
-  int deliver_time;
+  struct timeval delivery_start;
+  struct timeval deliver_time;
   address_item *addr2, *addr3, *nextaddr;
   int logflags = LOG_MAIN;
   address_item *addr2, *addr3, *nextaddr;
   int logflags = LOG_MAIN;
-  int logchar = dont_deliver? '*' : '=';
+  int logchar = f.dont_deliver? '*' : '=';
   transport_instance *tp;
   transport_instance *tp;
+  uschar * serialize_key = NULL;
 
   /* Pick the first undelivered address off the chain */
 
 
   /* Pick the first undelivered address off the chain */
 
@@ -2272,16 +2752,14 @@ while (addr_local != NULL)
 
   /* An internal disaster if there is no transport. Should not occur! */
 
 
   /* An internal disaster if there is no transport. Should not occur! */
 
-  if ((tp = addr->transport) == NULL)
+  if (!(tp = addr->transport))
     {
     logflags |= LOG_PANIC;
     {
     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");
-    post_process_one(addr, DEFER, logflags, DTYPE_TRANSPORT, 0);
+    f.disable_logging = FALSE;  /* Jic */
+    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, EXIM_DTYPE_TRANSPORT, 0);
     continue;
     }
 
     continue;
     }
 
@@ -2295,19 +2773,20 @@ while (addr_local != NULL)
 
   /* There are weird cases where logging is disabled */
 
 
   /* There are weird cases where logging is disabled */
 
-  disable_logging = tp->disable_logging;
+  f.disable_logging = tp->disable_logging;
 
   /* Check for batched addresses and possible amalgamation. Skip all the work
   if either batch_max <= 1 or there aren't any other addresses for local
   delivery. */
 
 
   /* Check for batched addresses and possible amalgamation. Skip all the work
   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");
     {
     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;
     uschar *batch_id = NULL;
     address_item **anchor = &addr_local;
     address_item *last = addr;
@@ -2316,12 +2795,12 @@ while (addr_local != NULL)
     /* Expand the batch_id string for comparison with other addresses.
     Expansion failure suppresses batching. */
 
     /* 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);
       {
       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,
         {
         log_write(0, LOG_MAIN|LOG_PANIC, "Failed to expand batch_id option "
           "in %s transport (%s): %s", tp->name, addr->address,
@@ -2345,27 +2824,30 @@ while (addr_local != NULL)
       same first host if a host list is set
     */
 
       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 =
       {
       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)
+       && testflag(addr, af_pfr) == testflag(next, af_pfr)
+       && testflag(addr, af_file) == testflag(next, 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 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;
         {
         uschar *bid;
         address_item *save_nextnext = next->next;
@@ -2374,7 +2856,7 @@ while (addr_local != NULL)
         next->next = save_nextnext;
         bid = expand_string(tp->batch_id);
         deliver_set_expansions(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,
           {
           log_write(0, LOG_MAIN|LOG_PANIC, "Failed to expand batch_id option "
             "in %s transport (%s): %s", tp->name, next->address,
@@ -2394,7 +2876,7 @@ while (addr_local != NULL)
         last = next;
         batch_count++;
         }
         last = next;
         batch_count++;
         }
-      else anchor = &(next->next);      /* Skip the address */
+      else anchor = &next->next;        /* Skip the address */
       }
     }
 
       }
     }
 
@@ -2403,16 +2885,16 @@ while (addr_local != NULL)
   fail them all forthwith. If the expansion fails, or does not yield an
   integer, defer delivery. */
 
   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);
     {
     int rc = check_message_size(tp, addr);
     if (rc != OK)
       {
       replicate_status(addr);
-      while (addr != NULL)
+      while (addr)
         {
         addr2 = addr->next;
         {
         addr2 = addr->next;
-        post_process_one(addr, rc, logflags, DTYPE_TRANSPORT, 0);
+        post_process_one(addr, rc, logflags, EXIM_DTYPE_TRANSPORT, 0);
         addr = addr2;
         }
       continue;    /* With next batch of addresses */
         addr = addr2;
         }
       continue;    /* With next batch of addresses */
@@ -2427,8 +2909,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. */
 
   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");
     {
     DEBUG(D_deliver|D_retry|D_hints_lookup)
       debug_printf("no retry data available\n");
@@ -2436,7 +2917,7 @@ while (addr_local != NULL)
 
   addr2 = addr;
   addr3 = NULL;
 
   addr2 = addr;
   addr3 = NULL;
-  while (addr2 != NULL)
+  while (addr2)
     {
     BOOL ok = TRUE;   /* to deliver this address */
     uschar *retry_key;
     {
     BOOL ok = TRUE;   /* to deliver this address */
     uschar *retry_key;
@@ -2447,20 +2928,20 @@ while (addr_local != NULL)
     a routing delay. */
 
     retry_key = string_copy(
     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. */
 
         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. */
 
       {
       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);
 
         {
         setflag(addr2, af_lt_retry_exists);
 
@@ -2479,11 +2960,11 @@ while (addr_local != NULL)
             retry_record->expired);
           }
 
             retry_record->expired);
           }
 
-        if (queue_running && !deliver_force)
+        if (f.queue_running && !f.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. */
 
           /* If we haven't reached the retry time, there is one more check
           to do, which is for the ultimate address timeout. */
@@ -2513,27 +2994,47 @@ while (addr_local != NULL)
       address_item *this = addr2;
       this->message = US"Retry time not yet reached";
       this->basic_errno = ERRNO_LRETRY;
       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;
-      post_process_one(this, DEFER, logflags, DTYPE_TRANSPORT, 0);
+      addr2 = addr3 ? (addr3->next = addr2->next)
+                   : (addr = addr2->next);
+      post_process_one(this, DEFER, logflags, EXIM_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 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, EXIM_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
   single delivery. */
 
   deliver_set_expansions(addr);
 
   /* 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
   single delivery. */
 
   deliver_set_expansions(addr);
-  delivery_start = time(NULL);
+
+  gettimeofday(&delivery_start, NULL);
   deliver_local(addr, FALSE);
   deliver_local(addr, FALSE);
-  deliver_time = (int)(time(NULL) - delivery_start);
+  timesince(&deliver_time, &delivery_start);
 
   /* If a shadow transport (which must perforce be another local transport), is
   defined, and its condition is met, we must pass the message to the shadow
 
   /* If a shadow transport (which must perforce be another local transport), is
   defined, and its condition is met, we must pass the message to the shadow
@@ -2545,18 +3046,19 @@ while (addr_local != NULL)
   NOTE: if the condition fails because of a lookup defer, there is nothing we
   can do! */
 
   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;
 
     {
     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 (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);
 
       log_write(0, LOG_MAIN|LOG_PANIC, "shadow transport \"%s\" not found ",
         tp->shadow);
 
@@ -2564,25 +3066,25 @@ while (addr_local != NULL)
     the shadow_message field a pointer to the shadow_message field of the real
     address. */
 
     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 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;
 
       {
       int save_count = transport_count;
 
@@ -2590,26 +3092,32 @@ while (addr_local != NULL)
         debug_printf(">>>>>>>>>>>>>>>> Shadow delivery >>>>>>>>>>>>>>>>\n");
       deliver_local(shadow_addr, TRUE);
 
         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;
         {
         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,
 
         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);
         }
 
             shadow_addr->address);
         }
 
@@ -2624,11 +3132,15 @@ while (addr_local != NULL)
 
   deliver_set_expansions(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. */
 
   /* 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;
     {
     int result = addr2->transport_return;
     nextaddr = addr2->next;
@@ -2636,10 +3148,10 @@ while (addr_local != NULL)
     DEBUG(D_deliver|D_transport)
       debug_printf("%s transport returned %s for %s\n",
         tp->name,
     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
         addr2->address);
 
     /* If there is a retry_record, or if delivery is deferred, build a retry
@@ -2650,17 +3162,21 @@ while (addr_local != NULL)
 
     if (result == DEFER || testflag(addr2, af_lt_retry_exists))
       {
 
     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);
       }
 
     /* Done with this address */
 
       *retry_key = 'T';
       retry_add_item(addr2, retry_key, flags);
       }
 
     /* Done with this address */
 
-    if (result == OK) addr2->more_errno = deliver_time;
-    post_process_one(addr2, result, logflags, DTYPE_TRANSPORT, logchar);
+    if (result == OK)
+      {
+      addr2->more_errno = deliver_time.tv_sec;
+      addr2->delivery_usec = deliver_time.tv_usec;
+      }
+    post_process_one(addr2, result, logflags, EXIM_DTYPE_TRANSPORT, logchar);
 
     /* If a pipe delivery generated text to be sent back, the result may be
     changed to FAIL, and we must copy this for subsequent addresses in the
 
     /* If a pipe delivery generated text to be sent back, the result may be
     changed to FAIL, and we must copy this for subsequent addresses in the
@@ -2668,7 +3184,7 @@ while (addr_local != NULL)
 
     if (addr2->transport_return != result)
       {
 
     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;
         {
         addr3->transport_return = addr2->transport_return;
         addr3->basic_errno = addr2->basic_errno;
@@ -2711,40 +3227,41 @@ sort_remote_deliveries(void)
 {
 int sep = 0;
 address_item **aptr = &addr_remote;
 {
 int sep = 0;
 address_item **aptr = &addr_remote;
-uschar *listptr = remote_sort_domains;
+const uschar *listptr = remote_sort_domains;
 uschar *pattern;
 uschar patbuf[256];
 
 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;
 
   {
   address_item *moved = NULL;
   address_item **bptr = &moved;
 
-  while (*aptr != NULL)
+  while (*aptr)
     {
     address_item **next;
     deliver_domain = (*aptr)->domain;   /* set $domain */
     {
     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)
       {
           &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL) == OK)
       {
-      aptr = &((*aptr)->next);
+      aptr = &(*aptr)->next;
       continue;
       }
 
       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 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;
       {
       *next = moved;
       break;
@@ -2754,7 +3271,7 @@ while (*aptr != NULL &&
     *aptr = *next;
     *next = NULL;
     bptr = next;
     *aptr = *next;
     *next = NULL;
     bptr = next;
-    aptr = &((*aptr)->next);
+    aptr = &(*aptr)->next;
     }
 
   /* If the loop ended because the final address matched, *aptr will
     }
 
   /* If the loop ended because the final address matched, *aptr will
@@ -2763,14 +3280,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. */
 
   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");
   }
 
 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);
   }
 }
     debug_printf("  %s\n", addr->address);
   }
 }
@@ -2800,6 +3317,9 @@ small items (less than PIPE_BUF, which seems to be at least 512 in any Unix and
 often bigger) so even if we are reading while the subprocess is still going, we
 should never have only a partial item in the buffer.
 
 often bigger) so even if we are reading while the subprocess is still going, we
 should never have only a partial item in the buffer.
 
+hs12: This assumption is not true anymore, since we get quite large items (certificate
+information and such).
+
 Argument:
   poffset     the offset of the parlist item
   eop         TRUE if the process has completed
 Argument:
   poffset     the offset of the parlist item
   eop         TRUE if the process has completed
@@ -2818,96 +3338,111 @@ address_item *addrlist = p->addrlist;
 address_item *addr = p->addr;
 pid_t pid = p->pid;
 int fd = p->fd;
 address_item *addr = p->addr;
 pid_t pid = p->pid;
 int fd = p->fd;
-uschar *endptr = big_buffer;
-uschar *ptr = endptr;
+
 uschar *msg = p->msg;
 BOOL done = p->done;
 uschar *msg = p->msg;
 BOOL done = p->done;
-BOOL unfinished = TRUE;
 
 /* Loop through all items, reading from the pipe when necessary. The pipe
 
 /* 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
-use. Exim uses O_NONBLOCK if it is defined. This returns 0 for end of file,
-and EAGAIN for no more data. If O_NONBLOCK is not defined, Exim uses O_NDELAY,
-which returns 0 for both end of file and no more data. We distinguish the
-two cases by taking 0 as end of file only when we know the process has
-completed.
-
-Each separate item is written to the pipe in a single write(), and as they are
-all short items, the writes will all be atomic and we should never find
-ourselves in the position of having read an incomplete item. "Short" in this
-case can mean up to about 1K in the case when there is a long error message
-associated with an address. */
+used to be non-blocking. But I do not see a reason for using non-blocking I/O
+here, as the preceding select() tells us, if data is available for reading.
 
 
-DEBUG(D_deliver) debug_printf("reading pipe for subprocess %d (%s)\n",
-  (int)p->pid, eop? "ended" : "not ended");
-
-while (!done)
-  {
-  retry_item *r, **rp;
-  int remaining = endptr - ptr;
-
-  /* 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)
-    {
-    int len;
-    int available = big_buffer_size - remaining;
+A read() on a "selected" handle should never block, but(!) it may return
+less data then we expected. (The buffer size we pass to read() shouldn't be
+understood as a "request", but as a "limit".)
 
 
-    if (remaining > 0) memmove(big_buffer, ptr, remaining);
+Each separate item is written to the pipe in a timely manner. But, especially for
+larger items, the read(2) may already return partial data from the write(2).
 
 
-    ptr = big_buffer;
-    endptr = big_buffer + remaining;
-    len = read(fd, endptr, available);
+The write is atomic mostly (depending on the amount written), but atomic does
+not imply "all or noting", it just is "not intermixed" with other writes on the
+same channel (pipe).
 
 
-    DEBUG(D_deliver) debug_printf("read() yielded %d\n", len);
+*/
 
 
-    /* If the result is EAGAIN and the process is not complete, just
-    stop reading any more and process what we have already. */
+DEBUG(D_deliver) debug_printf("reading pipe for subprocess %d (%s)\n",
+  (int)p->pid, eop? "ended" : "not ended yet");
 
 
-    if (len < 0)
-      {
-      if (!eop && errno == EAGAIN) len = 0; else
-        {
-        msg = string_sprintf("failed to read pipe from transport process "
-          "%d for transport %s: %s", pid, addr->transport->driver_name,
-          strerror(errno));
-        break;
-        }
-      }
+while (!done)
+  {
+  retry_item *r, **rp;
+  uschar pipeheader[PIPE_HEADER_SIZE+1];
+  uschar *id = &pipeheader[0];
+  uschar *subid = &pipeheader[1];
+  uschar *ptr = big_buffer;
+  size_t required = PIPE_HEADER_SIZE; /* first the pipehaeder, later the data */
+  ssize_t got;
+
+  DEBUG(D_deliver) debug_printf(
+    "expect %lu bytes (pipeheader) from tpt process %d\n", (u_long)required, pid);
+
+  /* We require(!) all the PIPE_HEADER_SIZE bytes here, as we know,
+  they're written in a timely manner, so waiting for the write shouldn't hurt a lot.
+  If we get less, we can assume the subprocess do be done and do not expect any further
+  information from it. */
+
+  if ((got = readn(fd, pipeheader, required)) != required)
+    {
+    msg = string_sprintf("got " SSIZE_T_FMT " of %d bytes (pipeheader) "
+      "from transport process %d for transport %s",
+      got, PIPE_HEADER_SIZE, pid, addr->transport->driver_name);
+    done = TRUE;
+    break;
+    }
 
 
-    /* If the length is zero (eof or no-more-data), just process what we
-    already have. Note that if the process is still running and we have
-    read all the data in the pipe (but less that "available") then we
-    won't read any more, as "unfinished" will get set FALSE. */
+  pipeheader[PIPE_HEADER_SIZE] = '\0';
+  DEBUG(D_deliver)
+    debug_printf("got %ld bytes (pipeheader) from transport process %d\n",
+      (long) got, pid);
 
 
-    endptr += len;
-    unfinished = len == available;
+  {
+  /* If we can't decode the pipeheader, the subprocess seems to have a
+  problem, we do not expect any furher information from it. */
+  char *endc;
+  required = Ustrtol(pipeheader+2, &endc, 10);
+  if (*endc)
+    {
+    msg = string_sprintf("failed to read pipe "
+      "from transport process %d for transport %s: error decoding size from header",
+      pid, addr->transport->driver_name);
+    done = TRUE;
+    break;
     }
     }
+  }
 
 
-  /* If we are at the end of the available data, exit the loop. */
+  DEBUG(D_deliver)
+    debug_printf("expect %lu bytes (pipedata) from transport process %d\n",
+      (u_long)required, pid);
 
 
-  if (ptr >= endptr) break;
+  /* Same as above, the transport process will write the bytes announced
+  in a timely manner, so we can just wait for the bytes, getting less than expected
+  is considered a problem of the subprocess, we do not expect anything else from it. */
+  if ((got = readn(fd, big_buffer, required)) != required)
+    {
+    msg = string_sprintf("got only " SSIZE_T_FMT " of " SIZE_T_FMT
+      " bytes (pipedata) from transport process %d for transport %s",
+      got, required, pid, addr->transport->driver_name);
+    done = TRUE;
+    break;
+    }
 
   /* Handle each possible type of item, assuming the complete item is
   available in store. */
 
 
   /* 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':
     {
     /* 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)
-      {
-      if (h->address == NULL || Ustrcmp(h->address, ptr+2) != 0) continue;
-      h->status = ptr[0];
-      h->why = ptr[1];
-      }
-    ptr += 2;
-    while (*ptr++);
-    break;
+      for (h = addrlist->host_list; h; h = h->next)
+       {
+       if (!h->address || Ustrcmp(h->address, ptr+2) != 0) continue;
+       h->status = ptr[0];
+       h->why = ptr[1];
+       }
+      ptr += 2;
+      while (*ptr++);
+      break;
 
     /* Retry items are sent in a preceding R item for each address. This is
     kept separate to keep each message short enough to guarantee it won't
 
     /* Retry items are sent in a preceding R item for each address. This is
     kept separate to keep each message short enough to guarantee it won't
@@ -2921,64 +3456,61 @@ while (!done)
     that a "delete" item is dropped in favour of an "add" item. */
 
     case 'R':
     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",
-        ptr+1);
+      DEBUG(D_deliver|D_retry)
+       debug_printf("reading retry information for %s from subprocess\n",
+         ptr+1);
 
 
-    /* Cut out any "delete" items on the list. */
+      /* Cut out any "delete" items on the list. */
 
 
-    for (rp = &(addr->retries); (r = *rp) != NULL; 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" */
-        *rp = r->next;                           /* Excise a delete item */
-        DEBUG(D_deliver|D_retry)
-          debug_printf("  existing delete item dropped\n");
-        }
-      }
+      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)) break;      /* It was not "delete" */
+         *rp = r->next;                           /* Excise a delete item */
+         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. */
+      /* 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)
-      {
-      r = store_get(sizeof(retry_item));
-      r->next = addr->retries;
-      addr->retries = r;
-      r->flags = *ptr++;
-      r->key = string_copy(ptr);
-      while (*ptr++);
-      memcpy(&(r->basic_errno), ptr, sizeof(r->basic_errno));
-      ptr += sizeof(r->basic_errno);
-      memcpy(&(r->more_errno), ptr, sizeof(r->more_errno));
-      ptr += sizeof(r->more_errno);
-      r->message = (*ptr)? string_copy(ptr) : NULL;
-      DEBUG(D_deliver|D_retry)
-        debug_printf("  added %s item\n",
-          ((r->flags & rf_delete) == 0)? "retry" : "delete");
-      }
+      if (!r || !(*ptr & rf_delete))
+       {
+       r = store_get(sizeof(retry_item));
+       r->next = addr->retries;
+       addr->retries = r;
+       r->flags = *ptr++;
+       r->key = string_copy(ptr);
+       while (*ptr++);
+       memcpy(&r->basic_errno, ptr, sizeof(r->basic_errno));
+       ptr += sizeof(r->basic_errno);
+       memcpy(&r->more_errno, ptr, sizeof(r->more_errno));
+       ptr += sizeof(r->more_errno);
+       r->message = *ptr ? string_copy(ptr) : NULL;
+       DEBUG(D_deliver|D_retry) debug_printf("  added %s item\n",
+           r->flags & rf_delete ? "delete" : "retry");
+       }
 
 
-    else
-      {
-      DEBUG(D_deliver|D_retry)
-        debug_printf("  delete item not added: non-delete item exists\n");
-      ptr++;
-      while(*ptr++);
-      ptr += sizeof(r->basic_errno) + sizeof(r->more_errno);
-      }
+      else
+       {
+       DEBUG(D_deliver|D_retry)
+         debug_printf("  delete item not added: non-delete item exists\n");
+       ptr++;
+       while(*ptr++);
+       ptr += sizeof(r->basic_errno) + sizeof(r->more_errno);
+       }
 
 
-    while(*ptr++);
-    break;
+      while(*ptr++);
+      break;
 
     /* Put the amount of data written into the parlist block */
 
     case 'S':
 
     /* Put the amount of data written into the parlist block */
 
     case 'S':
-    memcpy(&(p->transport_count), ptr, sizeof(transport_count));
-    ptr += sizeof(transport_count);
-    break;
+      memcpy(&(p->transport_count), ptr, sizeof(transport_count));
+      ptr += sizeof(transport_count);
+      break;
 
     /* Address items are in the order of items on the address chain. We
     remember the current address value in case this function is called
 
     /* Address items are in the order of items on the address chain. We
     remember the current address value in case this function is called
@@ -2987,124 +3519,177 @@ 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. */
 
     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':
     case 'X':
-    if (addr == NULL) goto ADDR_MISMATCH;          /* Below, in 'A' handler */
-    switch (*ptr++)
-      {
-      case '1':
-      addr->cipher = NULL;
-      addr->peerdn = NULL;
-
-      if (*ptr)
-       addr->cipher = string_copy(ptr);
+      if (!addr) goto ADDR_MISMATCH;          /* Below, in 'A' handler */
+      switch (*subid)
+       {
+       case '1':
+         addr->cipher = NULL;
+         addr->peerdn = NULL;
+
+         if (*ptr)
+           addr->cipher = string_copy(ptr);
+         while (*ptr++);
+         if (*ptr)
+           addr->peerdn = string_copy(ptr);
+         break;
+
+       case '2':
+         if (*ptr)
+           (void) tls_import_cert(ptr, &addr->peercert);
+         else
+           addr->peercert = NULL;
+         break;
+
+       case '3':
+         if (*ptr)
+           (void) tls_import_cert(ptr, &addr->ourcert);
+         else
+           addr->ourcert = NULL;
+         break;
+
+# ifndef DISABLE_OCSP
+       case '4':
+         addr->ocsp = *ptr ? *ptr - '0' : OCSP_NOT_REQ;
+         break;
+# endif
+       }
       while (*ptr++);
       while (*ptr++);
-      if (*ptr)
-       addr->peerdn = string_copy(ptr);
       break;
       break;
+#endif /*SUPPORT_TLS*/
 
 
-      case '2':
-      addr->peercert = NULL;
-      if (*ptr)
-       (void) tls_import_cert(ptr, &addr->peercert);
+    case 'C':  /* client authenticator information */
+      switch (*subid)
+       {
+       case '1': addr->authenticator = *ptr ? string_copy(ptr) : NULL; break;
+       case '2': addr->auth_id = *ptr ? string_copy(ptr) : NULL;       break;
+       case '3': addr->auth_sndr = *ptr ? string_copy(ptr) : NULL;     break;
+       }
+      while (*ptr++);
       break;
 
       break;
 
-      case '3':
-      addr->ourcert = NULL;
-      if (*ptr)
-       (void) tls_import_cert(ptr, &addr->ourcert);
+#ifndef DISABLE_PRDR
+    case 'P':
+      setflag(addr, af_prdr_used);
       break;
       break;
+#endif
 
 
-      #ifndef DISABLE_OCSP
-      case '4':
-      addr->ocsp = OCSP_NOT_REQ;
-      if (*ptr)
-       addr->ocsp = *ptr - '0';
+    case 'L':
+      switch (*subid)
+       {
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+       case 2: setflag(addr, af_early_pipe);   /*FALLTHROUGH*/
+#endif
+       case 1: setflag(addr, af_pipelining); break;
+       }
       break;
       break;
-      #endif
-      }
-    while (*ptr++);
-    break;
-    #endif     /*SUPPORT_TLS*/
 
 
-    case 'C':  /* client authenticator information */
-    switch (*ptr++)
-      {
-      case '1':
-       addr->authenticator = (*ptr)? string_copy(ptr) : NULL;
-       break;
-      case '2':
-       addr->auth_id = (*ptr)? string_copy(ptr) : NULL;
-       break;
-      case '3':
-       addr->auth_sndr = (*ptr)? string_copy(ptr) : NULL;
-       break;
-      }
-    while (*ptr++);
-    break;
+    case 'K':
+      setflag(addr, af_chunking_used);
+      break;
 
 
-#ifndef DISABLE_PRDR
-    case 'P':
-    addr->flags |= af_prdr_used;
-    break;
-#endif
+    case 'T':
+      setflag(addr, af_tcp_fastopen_conn);
+      if (*subid > '0') setflag(addr, af_tcp_fastopen);
+      if (*subid > '1') setflag(addr, af_tcp_fastopen_data);
+      break;
 
 
-    #ifdef EXPERIMENTAL_DSN
     case 'D':
     case 'D':
-    if (addr == NULL) break;
-    memcpy(&(addr->dsn_aware), ptr, sizeof(addr->dsn_aware));
-    ptr += sizeof(addr->dsn_aware);
-    DEBUG(D_deliver) debug_printf("DSN read: addr->dsn_aware = %d\n", addr->dsn_aware);
-    break;
-    #endif
+      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;
 
     case 'A':
 
     case 'A':
-    if (addr == NULL)
-      {
-      ADDR_MISMATCH:
-      msg = string_sprintf("address count mismatch for data read from pipe "
-        "for transport process %d for transport %s", pid,
-          addrlist->transport->driver_name);
-      done = TRUE;
-      break;
-      }
+      if (!addr)
+       {
+       ADDR_MISMATCH:
+       msg = string_sprintf("address count mismatch for data read from pipe "
+         "for transport process %d for transport %s", pid,
+           addrlist->transport->driver_name);
+       done = TRUE;
+       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++);
-
-    /* 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++;
+      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
+
+  #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
 
 
-    /* Finished with this address */
+       case '0':
+         DEBUG(D_deliver) debug_printf("A0 %s tret %d\n", addr->address, *ptr);
+         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->delivery_usec, ptr, sizeof(addr->delivery_usec));
+         ptr += sizeof(addr->delivery_usec);
+         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++);
 
 
-    addr = addr->next;
-    break;
+         /* Always two strings for host information, followed by the port number and DNSSEC mark */
+
+         if (*ptr)
+           {
+           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;
+
+    /* 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
     continue_transport was NULL at the end of transporting, otherwise '1'.
 
     /* Z marks the logical end of the data. It is followed by '0' if
     continue_transport was NULL at the end of transporting, otherwise '1'.
@@ -3113,23 +3698,23 @@ while (!done)
     most normal messages it will remain NULL all the time. */
 
     case 'Z':
     most normal messages it will remain NULL all the time. */
 
     case 'Z':
-    if (*ptr == '0')
-      {
-      continue_transport = NULL;
-      continue_hostname = NULL;
-      }
-    done = TRUE;
-    DEBUG(D_deliver) debug_printf("Z%c item read\n", *ptr);
-    break;
+      if (*ptr == '0')
+       {
+       continue_transport = NULL;
+       continue_hostname = NULL;
+       }
+      done = TRUE;
+      DEBUG(D_deliver) debug_printf("Z0%c item read\n", *ptr);
+      break;
 
     /* Anything else is a disaster. */
 
     default:
 
     /* Anything else is a disaster. */
 
     default:
-    msg = string_sprintf("malformed data (%d) read from pipe for transport "
-      "process %d for transport %s", ptr[-1], pid,
-        addr->transport->driver_name);
-    done = TRUE;
-    break;
+      msg = string_sprintf("malformed data (%d) read from pipe for transport "
+       "process %d for transport %s", ptr[-1], pid,
+         addr->transport->driver_name);
+      done = TRUE;
+      break;
     }
   }
 
     }
   }
 
@@ -3139,7 +3724,7 @@ call the function again when the process finishes. */
 p->done = done;
 
 /* If the process hadn't finished, and we haven't seen the end of the data
 p->done = done;
 
 /* If the process hadn't finished, and we haven't seen the end of the data
-or suffered a disaster, update the rest of the state, and return FALSE to
+or if we suffered a disaster, update the rest of the state, and return FALSE to
 indicate "not finished". */
 
 if (!eop && !done)
 indicate "not finished". */
 
 if (!eop && !done)
@@ -3158,7 +3743,7 @@ p->fd = -1;
 /* If we have finished without error, but haven't had data for every address,
 something is wrong. */
 
 /* 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);
   msg = string_sprintf("insufficient address data read from pipe "
     "for transport process %d for transport %s", pid,
       addr->transport->driver_name);
@@ -3166,15 +3751,14 @@ 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 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;
     {
     addr->transport_return = DEFER;
     addr->special_action = SPECIAL_FREEZE;
     addr->message = msg;
+    log_write(0, LOG_MAIN|LOG_PANIC, "Delivery status for %s: %s\n", addr->address, addr->message);
     }
     }
-  }
 
 /* Return TRUE to indicate we have got all we need from this process, even
 if it hasn't actually finished yet. */
 
 /* Return TRUE to indicate we have got all we need from this process, even
 if it hasn't actually finished yet. */
@@ -3213,16 +3797,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. */
 
 /* 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. */
 
 
 /* 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;
 
   {
   address_item *next = addr->next;
 
@@ -3230,10 +3812,11 @@ while (addr != NULL)
   processing the main hosts and there are fallback hosts available, put the
   address on the list for fallback delivery. */
 
   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;
     {
     addr->host_list = addr->fallback_hosts;
     addr->next = addr_fallback;
@@ -3246,13 +3829,13 @@ while (addr != NULL)
 
   else
     {
 
   else
     {
-    if (msg != NULL)
+    if (msg)
       {
       addr->message = msg;
       addr->transport_return = DEFER;
       }
     (void)post_process_one(addr, addr->transport_return, logflags,
       {
       addr->message = msg;
       addr->transport_return = DEFER;
       }
     (void)post_process_one(addr, addr->transport_return, logflags,
-      DTYPE_TRANSPORT, addr->special_action);
+      EXIM_DTYPE_TRANSPORT, addr->special_action);
     }
 
   /* Next address */
     }
 
   /* Next address */
@@ -3265,7 +3848,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. */
 
 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;
 }
 
 
 }
 
 
@@ -3394,14 +3977,12 @@ for (;;)   /* Normally we do not repeat this loop */
     maxpipe = 0;
     FD_ZERO(&select_pipes);
     for (poffset = 0; poffset < remote_max_parallel; poffset++)
     maxpipe = 0;
     FD_ZERO(&select_pipes);
     for (poffset = 0; poffset < remote_max_parallel; poffset++)
-      {
       if (parlist[poffset].pid != 0)
         {
         int fd = parlist[poffset].fd;
         FD_SET(fd, &select_pipes);
         if (fd > maxpipe) maxpipe = fd;
         }
       if (parlist[poffset].pid != 0)
         {
         int fd = parlist[poffset].fd;
         FD_SET(fd, &select_pipes);
         if (fd > maxpipe) maxpipe = fd;
         }
-      }
 
     /* Stick in a 60-second timeout, just in case. */
 
 
     /* Stick in a 60-second timeout, just in case. */
 
@@ -3428,12 +4009,12 @@ for (;;)   /* Normally we do not repeat this loop */
          readycount > 0 && poffset < remote_max_parallel;
          poffset++)
       {
          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 */
         {
         readycount--;
         if (par_read_pipe(poffset, FALSE))    /* Finished with this pipe */
-          {
           for (;;)                            /* Loop for signals */
             {
             pid_t endedpid = waitpid(pid, &status, 0);
           for (;;)                            /* Loop for signals */
             {
             pid_t endedpid = waitpid(pid, &status, 0);
@@ -3443,7 +4024,6 @@ for (;;)   /* Normally we do not repeat this loop */
                 "%d (errno = %d) from waitpid() for process %d",
                 (int)endedpid, errno, (int)pid);
             }
                 "%d (errno = %d) from waitpid() for process %d",
                 (int)endedpid, errno, (int)pid);
             }
-          }
         }
       }
 
         }
       }
 
@@ -3509,7 +4089,7 @@ if ((status & 0xffff) != 0)
   if (msb != 0 || (code != SIGTERM && code != SIGKILL && code != SIGQUIT))
     addrlist->special_action = SPECIAL_FREEZE;
 
   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;
     {
     addr->transport_return = DEFER;
     addr->message = msg;
@@ -3558,26 +4138,62 @@ par_reduce(int max, BOOL fallback)
 while (parcount > max)
   {
   address_item *doneaddr = par_wait();
 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;
     }
     {
     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);
+    }
   }
 }
 
   }
 }
 
+static void
+rmt_dlv_checked_write(int fd, char id, char subid, void * buf, ssize_t size)
+{
+uschar pipe_header[PIPE_HEADER_SIZE+1];
+size_t total_len = PIPE_HEADER_SIZE + size;
 
 
+struct iovec iov[2] = {
+  { pipe_header, PIPE_HEADER_SIZE },  /* indication about the data to expect */
+  { buf, size }                       /* *the* data */
+};
 
 
+ssize_t ret;
 
 
-static void
-rmt_dlv_checked_write(int fd, void * buf, int size)
-{
-int ret = write(fd, buf, size);
-if(ret != size)
-  log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Failed writing transport result to pipe: %s\n",
-    ret == -1 ? strerror(errno) : "short write");
+/* 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 > BIG_BUFFER_SIZE-1)
+  {
+  log_write(0, LOG_MAIN|LOG_PANIC_DIE,
+    "Failed writing transport result to pipe: can't handle buffers > %d bytes. truncating!\n",
+      BIG_BUFFER_SIZE-1);
+  size = BIG_BUFFER_SIZE;
+  }
+
+/* Should we check that we do not write more than PIPE_BUF? What would
+that help? */
+
+/* convert size to human readable string prepended by id and subid */
+if (PIPE_HEADER_SIZE != snprintf(CS pipe_header, PIPE_HEADER_SIZE+1, "%c%c%05ld",
+    id, subid, (long)size))
+  log_write(0, LOG_MAIN|LOG_PANIC_DIE, "header snprintf failed\n");
+
+DEBUG(D_deliver) debug_printf("header write id:%c,subid:%c,size:%ld,final:%s\n",
+                                 id, subid, (long)size, pipe_header);
+
+if ((ret = writev(fd, iov, 2)) != total_len)
+  log_write(0, LOG_MAIN|LOG_PANIC_DIE,
+    "Failed writing transport result to pipe (%ld of %ld bytes): %s",
+    (long)ret, (long)total_len, ret == -1 ? strerror(errno) : "short write");
 }
 
 /*************************************************
 }
 
 /*************************************************
@@ -3624,13 +4240,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. */
 
 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. */
 
 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++)
   {
   parlist = store_get(remote_max_parallel * sizeof(pardata));
   for (poffset = 0; poffset < remote_max_parallel; poffset++)
@@ -3639,7 +4255,7 @@ if (parlist == NULL)
 
 /* Now loop for each remote delivery */
 
 
 /* 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;
   {
   pid_t pid;
   uid_t uid;
@@ -3655,6 +4271,8 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
   address_item *addr = addr_remote;
   address_item *last = addr;
   address_item *next;
   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. */
 
 
   /* Pull the first address right off the list. */
 
@@ -3666,12 +4284,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 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;
+    f.disable_logging = FALSE;  /* Jic */
+    panicmsg = US"No transport set by router";
+    goto panic_continue;
     }
 
   /* Check that this base address hasn't previously been delivered to this
     }
 
   /* Check that this base address hasn't previously been delivered to this
@@ -3684,7 +4301,7 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
 
   /* Force failure if the message is too big. */
 
 
   /* 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)
     {
     int rc = check_message_size(tp, addr);
     if (rc != OK)
@@ -3696,9 +4313,20 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
     }
 
   /* Get the flag which specifies whether the transport can handle different
     }
 
   /* 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. */
 
   /* Get the maximum it can handle in one envelope, with zero meaning
   unlimited, which is forced for the MUA wrapper case. */
@@ -3746,8 +4374,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. */
 
   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;
     {
     int new_max = remote_delivery_count/remote_max_parallel;
     int message_max = tp->connection_max_messages;
@@ -3767,26 +4396,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
   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;
       {
       *anchor = next->next;
       next->next = NULL;
@@ -3796,18 +4434,29 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
       address_count++;
       }
     else anchor = &(next->next);
       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 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;
     }
 
     {
     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);
   /* Set up the expansion variables for this set of addresses */
 
   deliver_set_expansions(addr);
@@ -3818,29 +4467,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. */
 
   /* 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
 #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;
 
 #endif
   else
     return_path = sender_address;
 
-  if (tp->return_path != NULL)
+  if (tp->return_path)
     {
     uschar *new_return_path = expand_string(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 (!f.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
     }
 
   /* Find the uid, gid, and use_initgroups setting for this transport. Failure
@@ -3849,8 +4495,8 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
 
   if (!findugid(addr, tp, &uid, &gid, &use_initgroups))
     {
 
   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
     }
 
   /* If this transport has a setup function, call it now so that it gets
@@ -3860,28 +4506,61 @@ 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. */
 
   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));
 
     (void)((tp->setup)(addr->transport, addr, NULL, uid, gid, NULL));
 
+  /* If we have a connection still open from a verify stage (lazy-close)
+  treat it as if it is a continued connection (apart from the counter used
+  for the log line mark). */
+
+  if (cutthrough.cctx.sock >= 0 && cutthrough.callout_hold_only)
+    {
+    DEBUG(D_deliver)
+      debug_printf("lazy-callout-close: have conn still open from verification\n");
+    continue_transport = cutthrough.transport;
+    continue_hostname = string_copy(cutthrough.host.name);
+    continue_host_address = string_copy(cutthrough.host.address);
+    continue_sequence = 1;
+    sending_ip_address = cutthrough.snd_ip;
+    sending_port = cutthrough.snd_port;
+    smtp_peer_options = cutthrough.peer_options;
+    }
+
   /* If this is a run to continue delivery down an already-established
   channel, check that this set of addresses matches the transport and
   the channel. If it does not, defer the addresses. If a host list exists,
   we must check that the continue host is on the list. Otherwise, the
   host is set in the transport. */
 
   /* If this is a run to continue delivery down an already-established
   channel, check that this set of addresses matches the transport and
   the channel. If it does not, defer the addresses. If a host list exists,
   we must check that the continue host is on the list. Otherwise, the
   host is set in the transport. */
 
-  continue_more = FALSE;           /* In case got set for the last lot */
-  if (continue_transport != NULL)
+  f.continue_more = FALSE;           /* In case got set for the last lot */
+  if (continue_transport)
     {
     BOOL ok = Ustrcmp(continue_transport, tp->name) == 0;
     {
     BOOL ok = Ustrcmp(continue_transport, tp->name) == 0;
-    if (ok && addr->host_list != NULL)
+
+    /* If the transport is about to override the host list do not check
+    it here but take the cost of running the transport process to discover
+    if the continued_hostname connection is suitable.  This is a layering
+    violation which is unfortunate as it requires we haul in the smtp
+    include file. */
+
+    if (ok)
       {
       {
-      host_item *h;
-      ok = FALSE;
-      for (h = addr->host_list; h != NULL; h = h->next)
-        {
-        if (Ustrcmp(h->name, continue_hostname) == 0)
-          { ok = TRUE; break; }
-        }
+      smtp_transport_options_block * ob;
+
+      if (  !(  Ustrcmp(tp->info->driver_name, "smtp") == 0
+            && (ob = (smtp_transport_options_block *)tp->options_block)
+            && ob->hosts_override && ob->hosts
+            )
+        && addr->host_list
+        )
+       {
+       host_item * h;
+       ok = FALSE;
+       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
       }
 
     /* Addresses not suitable; defer or queue for fallback hosts (which
@@ -3889,44 +4568,51 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
 
     if (!ok)
       {
 
     if (!ok)
       {
-      DEBUG(D_deliver) debug_printf("not suitable for continue_transport\n");
-      next = addr;
+      DEBUG(D_deliver) debug_printf("not suitable for continue_transport (%s)\n",
+       Ustrcmp(continue_transport, tp->name) != 0
+       ? string_sprintf("tpt %s vs %s", continue_transport, tp->name)
+       : string_sprintf("no host matching %s", continue_hostname));
+      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);
           {
           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
           }
         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;
       }
 
     /* Set a flag indicating whether there are further addresses that list
     the continued host. This tells the transport to leave the channel open,
 
       continue;
       }
 
     /* Set a flag indicating whether there are further addresses that list
     the continued host. This tells the transport to leave the channel open,
-    but not to pass it to another delivery process. */
+    but not to pass it to another delivery process. We'd like to do that
+    for non-continue_transport cases too but the knowlege of which host is
+    connected to is too hard to manage.  Perhaps we need a finer-grain
+    interface to the transport. */
 
 
-    for (next = addr_remote; next != NULL; next = next->next)
+    for (next = addr_remote; next && !f.continue_more; next = next->next)
       {
       host_item *h;
       {
       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)
         if (Ustrcmp(h->name, continue_hostname) == 0)
-          { continue_more = TRUE; break; }
-        }
+          { f.continue_more = TRUE; break; }
       }
     }
 
       }
     }
 
@@ -3954,11 +4640,15 @@ 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. */
 
     that it can use either of them, though it prefers O_NONBLOCK, which
     distinguishes between EOF and no-more-data. */
 
-    #ifdef O_NONBLOCK
+/* The data appears in a timely manner and we already did a select on
+all pipes, so I do not see a reason to use non-blocking IO here
+
+#ifdef O_NONBLOCK
     (void)fcntl(pfd[pipe_read], F_SETFL, O_NONBLOCK);
     (void)fcntl(pfd[pipe_read], F_SETFL, O_NONBLOCK);
-    #else
+#else
     (void)fcntl(pfd[pipe_read], F_SETFL, O_NDELAY);
     (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
 
     /* 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 +4663,8 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
 
   if (!pipe_done)
     {
 
   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
     }
 
   /* Find a free slot in the pardata list. Must do this after the possible
@@ -3983,7 +4672,8 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
   up a slot. */
 
   for (poffset = 0; poffset < remote_max_parallel; poffset++)
   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. */
 
 
   /* If there isn't one, there has been a horrible disaster. */
 
@@ -3991,13 +4681,12 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
     {
     (void)close(pfd[pipe_write]);
     (void)close(pfd[pipe_read]);
     {
     (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,
     }
 
   /* 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();
   what happens in the subprocess. */
 
   search_tidyup();
@@ -4011,11 +4700,11 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
     transport_name = tp->name;
 
     /* There are weird circumstances in which logging is disabled */
     transport_name = tp->name;
 
     /* There are weird circumstances in which logging is disabled */
-    disable_logging = tp->disable_logging;
+    f.disable_logging = tp->disable_logging;
 
     /* Show pids on debug output if parallelism possible */
 
 
     /* 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");
       {
       DEBUG(D_any|D_v) debug_selector |= D_pid;
       DEBUG(D_deliver) debug_printf("Remote delivery process started\n");
@@ -4026,7 +4715,7 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
     predictable settings for each delivery process, so do something explicit
     here rather they rely on the fixed reset in the random number function. */
 
     predictable settings for each delivery process, so do something explicit
     here rather they rely on the fixed reset in the random number function. */
 
-    random_seed = running_in_test_harness? 42 + 2*delivery_count : 0;
+    random_seed = f.running_in_test_harness ? 42 + 2*delivery_count : 0;
 
     /* Set close-on-exec on the pipe so that it doesn't get passed on to
     a new process that may be forked to do another delivery down the same
 
     /* Set close-on-exec on the pipe so that it doesn't get passed on to
     a new process that may be forked to do another delivery down the same
@@ -4049,18 +4738,23 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
     a dup-with-new-file-pointer. */
 
     (void)close(deliver_datafile);
     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 "
       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 */
 
     /* Set the close-on-exec flag */
-
+#ifndef O_CLOEXEC
     (void)fcntl(deliver_datafile, F_SETFD, fcntl(deliver_datafile, F_GETFD) |
       FD_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. */
 
 
     /* Set the uid/gid of this process; bombs out on failure. */
 
@@ -4078,7 +4772,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)",
     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 */
 
 
     /* Ensure any cached resources that we used are now released */
 
@@ -4097,11 +4791,11 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
     /* Host unusability information: for most success cases this will
     be null. */
 
     /* 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
       }
 
     /* The number of bytes written. This is the same for each address. Even
@@ -4109,155 +4803,189 @@ 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(). */
 
     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. */
 
 
     /* 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);
       {
       uschar *ptr;
       retry_item *r;
 
       /* The certificate verification status goes into the flags */
       if (tls_out.certificate_verified) setflag(addr, af_cert_verified);
+#ifdef SUPPORT_DANE
+      if (tls_out.dane_verified)        setflag(addr, af_dane_verified);
+#endif
 
       /* Use an X item only if there's something to send */
 
       /* Use an X item only if there's something to send */
-      #ifdef SUPPORT_TLS
+#ifdef SUPPORT_TLS
       if (addr->cipher)
         {
       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
         if (!addr->peerdn)
          *ptr++ = 0;
        else
-          {
-          sprintf(CS ptr, "%.512s", addr->peerdn);
-          while(*ptr++);
-          }
+          ptr += sprintf(CS ptr, "%.512s", addr->peerdn) + 1;
 
 
-        rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer);
+        rmt_dlv_checked_write(fd, 'X', '1', big_buffer, ptr - big_buffer);
         }
         }
+      else if (continue_proxy_cipher)
+       {
+        ptr = big_buffer + sprintf(CS big_buffer, "%.128s", continue_proxy_cipher) + 1;
+       *ptr++ = 0;
+        rmt_dlv_checked_write(fd, 'X', '1', big_buffer, ptr - big_buffer);
+       }
+
       if (addr->peercert)
        {
         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;
        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;
        }
       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;
        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)
        {
       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)
         {
 
       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)
         {
        }
       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)
         {
        }
       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
-      if (addr->flags & af_prdr_used)
-       rmt_dlv_checked_write(fd, "P", 1);
-      #endif
+#ifndef DISABLE_PRDR
+      if (testflag(addr, af_prdr_used))
+       rmt_dlv_checked_write(fd, 'P', '0', NULL, 0);
+#endif
+
+      if (testflag(addr, af_pipelining))
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+       if (testflag(addr, af_early_pipe))
+         rmt_dlv_checked_write(fd, 'L', '2', NULL, 0);
+       else
+#endif
+         rmt_dlv_checked_write(fd, 'L', '1', NULL, 0);
+
+      if (testflag(addr, af_chunking_used))
+       rmt_dlv_checked_write(fd, 'K', '0', NULL, 0);
+
+      if (testflag(addr, af_tcp_fastopen_conn))
+       rmt_dlv_checked_write(fd, 'T',
+         testflag(addr, af_tcp_fastopen) ? testflag(addr, af_tcp_fastopen_data)
+         ? '2' : '1' : '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);
-      DEBUG(D_deliver) debug_printf("DSN write: addr->dsn_aware = %d\n", addr->dsn_aware);
-      #endif
+      memcpy(big_buffer, &addr->dsn_aware, sizeof(addr->dsn_aware));
+      rmt_dlv_checked_write(fd, 'D', '0', big_buffer, sizeof(addr->dsn_aware));
 
       /* Retry information: for most success cases this will be null. */
 
 
       /* 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;
         ptr = big_buffer + Ustrlen(big_buffer+2) + 3;
-        memcpy(ptr, &(r->basic_errno), sizeof(r->basic_errno));
+        memcpy(ptr, &r->basic_errno, sizeof(r->basic_errno));
         ptr += sizeof(r->basic_errno);
         ptr += sizeof(r->basic_errno);
-        memcpy(ptr, &(r->more_errno), sizeof(r->more_errno));
+        memcpy(ptr, &r->more_errno, sizeof(r->more_errno));
         ptr += 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++);
           }
           {
           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);
-      memcpy(ptr, &(addr->basic_errno), sizeof(addr->basic_errno));
+      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);
       ptr += sizeof(addr->basic_errno);
-      memcpy(ptr, &(addr->more_errno), sizeof(addr->more_errno));
+      memcpy(ptr, &addr->more_errno, sizeof(addr->more_errno));
       ptr += sizeof(addr->more_errno);
       ptr += sizeof(addr->more_errno);
-      memcpy(ptr, &(addr->flags), sizeof(addr->flags));
+      memcpy(ptr, &addr->delivery_usec, sizeof(addr->delivery_usec));
+      ptr += sizeof(addr->delivery_usec);
+      memcpy(ptr, &addr->flags, sizeof(addr->flags));
       ptr += 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++);
-        memcpy(ptr, &(addr->host_used->port), sizeof(addr->host_used->port));
+        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);
 
         /* DNS lookup status */
         ptr += sizeof(addr->host_used->port);
 
         /* DNS lookup status */
@@ -4265,7 +4993,20 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
               : addr->host_used->dnssec==DS_NO ? '1' : '0';
 
         }
               : 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
       }
 
     /* Add termination flag, close the pipe, and that's it. The character
@@ -4273,9 +5014,8 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
     A change from non-NULL to NULL indicates a problem with a continuing
     connection. */
 
     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);
     }
     (void)close(fd);
     exit(EXIT_SUCCESS);
     }
@@ -4284,15 +5024,28 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
 
   (void)close(pfd[pipe_write]);
 
 
   (void)close(pfd[pipe_write]);
 
+  /* If we have a connection still open from a verify stage (lazy-close)
+  release its TLS library context (if any) as responsibility was passed to
+  the delivery child process. */
+
+  if (cutthrough.cctx.sock >= 0 && cutthrough.callout_hold_only)
+    {
+#ifdef SUPPORT_TLS
+    if (cutthrough.is_tls)
+      tls_close(cutthrough.cctx.tls_ctx, TLS_NO_SHUTDOWN);
+#endif
+    (void) close(cutthrough.cctx.sock);
+    release_cutthrough_connection(US"passed to transport proc");
+    }
+
   /* Fork failed; defer with error message */
 
   /* Fork failed; defer with error message */
 
-  if (pid < 0)
+  if (pid == -1)
     {
     (void)close(pfd[pipe_read]);
     {
     (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
     }
 
   /* Fork succeeded; increment the count, and remember relevant data for
@@ -4317,13 +5070,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. */
 
   (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. */
 
 
   /* 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);
+  else if (f.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
   }
 
 /* Reached the end of the list of addresses. Wait for all the subprocesses that
@@ -4355,13 +5116,17 @@ Returns:    OK
 */
 
 int
 */
 
 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
 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 +5138,7 @@ removing quoting backslashes and any unquoted doublequotes. */
 t = addr->cc_local_part = store_get(len+1);
 while(len-- > 0)
   {
 t = addr->cc_local_part = store_get(len+1);
 while(len-- > 0)
   {
-  register int c = *address++;
+  int c = *address++;
   if (c == '\"') continue;
   if (c == '\\')
     {
   if (c == '\"') continue;
   if (c == '\\')
     {
@@ -4387,7 +5152,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. */
 
 /* 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;
   {
   int rc;
   uschar *new_address = NULL;
@@ -4395,10 +5160,11 @@ if (percent_hack_domains != NULL)
 
   deliver_domain = addr->domain;  /* set $domain */
 
 
   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] = '@';
     {
     new_address = string_copy(local_part);
     new_address[t - local_part] = '@';
@@ -4410,11 +5176,12 @@ if (percent_hack_domains != NULL)
 
   /* If hackery happened, set up new parent and alter the current address. */
 
 
   /* 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;
     {
     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;
     addr->address = new_address;
     addr->unique = string_copy(new_address);
     addr->domain = deliver_domain;
@@ -4451,27 +5218,24 @@ Returns:     NULL or an expanded string
 static uschar *
 next_emf(FILE *f, uschar *which)
 {
 static uschar *
 next_emf(FILE *f, uschar *which)
 {
-int size = 256;
-int ptr = 0;
-uschar *para, *yield;
+uschar *yield;
+gstring * para;
 uschar buffer[256];
 
 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);
+para = string_get(256);
 for (;;)
   {
 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, 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(string_from_gstring(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,
 
 log_write(0, LOG_MAIN|LOG_PANIC, "Failed to expand string from "
   "bounce_message_file or warn_message_file (%s): %s", which,
@@ -4497,17 +5261,15 @@ Returns:    DELIVER_NOT_ATTEMPTED
 static int
 continue_closedown(void)
 {
 static int
 continue_closedown(void)
 {
-if (continue_transport != NULL)
+if (continue_transport)
   {
   transport_instance *t;
   {
   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 (Ustrcmp(t->name, continue_transport) == 0)
       {
-      if (t->info->closedown != NULL) (t->info->closedown)(t);
+      if (t->info->closedown) (t->info->closedown)(t);
       break;
       }
       break;
       }
-    }
   }
 return DELIVER_NOT_ATTEMPTED;
 }
   }
 return DELIVER_NOT_ATTEMPTED;
 }
@@ -4540,16 +5302,16 @@ print_address_information(address_item *addr, FILE *f, uschar *si, uschar *sc,
 BOOL yield = TRUE;
 uschar *printed = US"";
 address_item *ancestor = addr;
 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);
 
 
 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;
   }
   {
   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
   printed = addr->address;
 
 else
@@ -4569,14 +5331,18 @@ fprintf(f, "%s", CS string_printing(printed));
 
 if (ancestor != addr)
   {
 
 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,
   if (strcmpic(original, printed) != 0)
     fprintf(f, "%s(%sgenerated from %s)", sc,
-      (ancestor != addr->parent)? "ultimately " : "",
+      ancestor != addr->parent ? "ultimately " : "",
       string_printing(original));
   }
 
       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;
 }
 fprintf(f, "%s", CS se);
 return yield;
 }
@@ -4612,17 +5378,14 @@ static void
 print_address_error(address_item *addr, FILE *f, uschar *t)
 {
 int count = Ustrlen(t);
 print_address_error(address_item *addr, FILE *f, uschar *t)
 {
 int count = Ustrlen(t);
-uschar *s = testflag(addr, af_pass_message)? addr->message : NULL;
+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);
 
 
 fprintf(f, "\n    %s", t);
 
-while (*s != 0)
-  {
+while (*s)
   if (*s == '\\' && s[1] == 'n')
     {
     fprintf(f, "\n    ");
   if (*s == '\\' && s[1] == 'n')
     {
     fprintf(f, "\n    ");
@@ -4639,12 +5402,59 @@ while (*s != 0)
       count = 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 +5480,14 @@ static void
 do_duplicate_check(address_item **anchor)
 {
 address_item *addr;
 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);
     }
   {
   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);
     {
     DEBUG(D_deliver|D_route)
       debug_printf("%s is a duplicate address: discarded\n", addr->unique);
@@ -4718,6 +5528,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.
 
 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
 Arguments:
   id          the id of the message to be delivered
   forced      TRUE if delivery was forced by an administrator; this overrides
@@ -4742,15 +5554,14 @@ int final_yield = DELIVER_ATTEMPTED_NORMAL;
 time_t now = time(NULL);
 address_item *addr_last = NULL;
 uschar *filter_message = NULL;
 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;
 
 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
 
 /* 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 +5569,9 @@ D_queue_run is set or in verbose mode. */
 
 set_process_info("%s", info);
 
 
 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
   debug_printf("%s\n", info);
 
 /* Ensure that we catch any subprocesses that are created. Although Exim
@@ -4787,8 +5599,9 @@ message size. This use of strcpy() is OK because the length id is checked when
 it is obtained from a command line (the -M or -q options), and otherwise it is
 known to be a valid message id. */
 
 it is obtained from a command line (the -M or -q options), and otherwise it is
 known to be a valid message id. */
 
-Ustrcpy(message_id, id);
-deliver_force = forced;
+if (id != message_id)
+  Ustrcpy(message_id, id);
+f.deliver_force = forced;
 return_count = 0;
 message_size = 0;
 
 return_count = 0;
 message_size = 0;
 
@@ -4812,7 +5625,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. */
 
 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,
   return continue_closedown();  /* yields DELIVER_NOT_ATTEMPTED */
 
 /* The value of message_size at this point has been set to the data length,
@@ -4823,53 +5636,52 @@ 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. */
 
 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.tv_sec = received_time.tv_usec = 0;
+      /*XXX subsec precision?*/
+      for (i = 0; i < 6; i++)
+       received_time.tv_sec = received_time.tv_sec * 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.tv_sec > 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
   }
 
 /* The spool header file has been read. Look to see if there is an existing
@@ -4881,37 +5693,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. */
 
 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"))
+     )
     {
     {
-    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);
+    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)
+    {
+    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 */
+    }
   }
 
 
   }
 
 
@@ -4919,17 +5749,18 @@ if (recipients_list == NULL)
 can happen, but in the default situation, unless forced, no delivery is
 attempted. */
 
 can happen, but in the default situation, unless forced, no delivery is
 attempted. */
 
-if (deliver_freeze)
+if (f.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. */
 
   /* 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 */
     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
 
   /* 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 +5777,8 @@ if (deliver_freeze)
   ignore timer is exceeded. The message will be discarded if this delivery
   fails. */
 
   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");
     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
 
   /* 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 +5788,13 @@ if (deliver_freeze)
 
   else
     {
 
   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 || !f.deliver_force_thaw
+         || !f.admin_user || continue_hostname
+       )  )
       {
       (void)close(deliver_datafile);
       deliver_datafile = -1;
       {
       (void)close(deliver_datafile);
       deliver_datafile = -1;
@@ -4979,7 +5807,7 @@ if (deliver_freeze)
 
     if (forced)
       {
 
     if (forced)
       {
-      deliver_manual_thaw = TRUE;
+      f.deliver_manual_thaw = TRUE;
       log_write(0, LOG_MAIN, "Unfrozen by forced delivery");
       }
     else log_write(0, LOG_MAIN, "Unfrozen by auto-thaw");
       log_write(0, LOG_MAIN, "Unfrozen by forced delivery");
       }
     else log_write(0, LOG_MAIN, "Unfrozen by auto-thaw");
@@ -4987,7 +5815,7 @@ if (deliver_freeze)
 
   /* We get here if any of the rules for unfreezing have triggered. */
 
 
   /* We get here if any of the rules for unfreezing have triggered. */
 
-  deliver_freeze = FALSE;
+  f.deliver_freeze = FALSE;
   update_spool = TRUE;
   }
 
   update_spool = TRUE;
   }
 
@@ -4999,26 +5827,23 @@ done by rewriting the header spool file. */
 
 if (message_logs)
   {
 
 if (message_logs)
   {
-  uschar *error;
+  uschar * fname = spool_fname(US"msglog", message_subdir, id, US"");
+  uschar * error;
   int fd;
 
   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,
     {
     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. */
 
     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",
     {
     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 */
     }
   }
     return continue_closedown();   /* yields DELIVER_NOT_ATTEMPTED */
     }
   }
@@ -5030,8 +5855,8 @@ the addresses. */
 if (give_up)
   {
   struct passwd *pw = getpwuid(real_uid);
 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;
   }
 
   process_recipients = RECIP_FAIL;
   }
 
@@ -5046,7 +5871,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. */
 
 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;
   {
   int rc;
   int filtertype;
@@ -5060,13 +5885,11 @@ else if (system_filter != NULL && process_recipients != RECIP_FAIL_TIMEOUT)
     ugid.uid_set = ugid.gid_set = TRUE;
     }
   else
     ugid.uid_set = ugid.gid_set = TRUE;
     }
   else
-    {
     ugid.uid_set = ugid.gid_set = FALSE;
     ugid.uid_set = ugid.gid_set = FALSE;
-    }
 
   return_path = sender_address;
 
   return_path = sender_address;
-  enable_dollar_recipients = TRUE;   /* Permit $recipients in system filter */
-  system_filtering = TRUE;
+  f.enable_dollar_recipients = TRUE;   /* Permit $recipients in system filter */
+  f.system_filtering = TRUE;
 
   /* Any error in the filter file causes a delivery to be abandoned. */
 
 
   /* Any error in the filter file causes a delivery to be abandoned. */
 
@@ -5114,9 +5937,9 @@ else if (system_filter != NULL && process_recipients != RECIP_FAIL_TIMEOUT)
   /* Reset things. If the filter message is an empty string, which can happen
   for a filter "fail" or "freeze" command with no text, reset it to NULL. */
 
   /* Reset things. If the filter message is an empty string, which can happen
   for a filter "fail" or "freeze" command with no text, reset it to NULL. */
 
-  system_filtering = FALSE;
-  enable_dollar_recipients = FALSE;
-  if (filter_message != NULL && filter_message[0] == 0) filter_message = NULL;
+  f.system_filtering = FALSE;
+  f.enable_dollar_recipients = FALSE;
+  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. */
 
   /* Save the values of the system filter variables so that user filters
   can use them. */
@@ -5138,14 +5961,14 @@ else if (system_filter != NULL && process_recipients != RECIP_FAIL_TIMEOUT)
   unset "delivered", which is forced by the "freeze" command to make -bF
   work properly. */
 
   unset "delivered", which is forced by the "freeze" command to make -bF
   work properly. */
 
-  else if (rc == FF_FREEZE && !deliver_manual_thaw)
+  else if (rc == FF_FREEZE && !f.deliver_manual_thaw)
     {
     {
-    deliver_freeze = TRUE;
+    f.deliver_freeze = TRUE;
     deliver_frozen_at = time(NULL);
     process_recipients = RECIP_DEFER;
     frozen_info = string_sprintf(" by the system filter%s%s",
     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
     }
 
   /* The filter can request that a message be failed. The error message may be
@@ -5162,12 +5985,14 @@ else if (system_filter != NULL && process_recipients != RECIP_FAIL_TIMEOUT)
 
     process_recipients = RECIP_FAIL_FILTER;
 
 
     process_recipients = RECIP_FAIL_FILTER;
 
-    if (filter_message != NULL)
+    if (filter_message)
       {
       uschar *logend;
       colon = US": ";
       {
       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;
         {
         logmsg = filter_message + 2;
         loglen = logend - logmsg;
@@ -5191,10 +6016,10 @@ else if (system_filter != NULL && process_recipients != RECIP_FAIL_TIMEOUT)
   else if (rc == FF_DELIVERED)
     {
     process_recipients = RECIP_IGNORE;
   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)");
       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"
     }
 
   /* If any new addresses were created by the filter, fake up a "parent"
@@ -5203,7 +6028,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. */
 
   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();
     {
     int uid = (system_filter_uid_set)? system_filter_uid : geteuid();
     int gid = (system_filter_gid_set)? system_filter_gid : getegid();
@@ -5222,11 +6047,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. */
 
     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 "
         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;
 
       parent->child_count++;
       p->parent = parent;
 
@@ -5236,11 +6061,11 @@ else if (system_filter != NULL && process_recipients != RECIP_FAIL_TIMEOUT)
         uschar *type;
         p->uid = uid;
         p->gid = gid;
         uschar *type;
         p->uid = uid;
         p->gid = gid;
-        setflag(p, af_uid_set |
-                   af_gid_set |
-                   af_allow_file |
-                   af_allow_pipe |
-                   af_allow_reply);
+        setflag(p, af_uid_set);
+        setflag(p, af_gid_set);
+        setflag(p, af_allow_file);
+        setflag(p, af_allow_pipe);
+        setflag(p, af_allow_reply);
 
         /* Find the name of the system filter's appropriate pfr transport */
 
 
         /* Find the name of the system filter's appropriate pfr transport */
 
@@ -5273,33 +6098,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. */
 
         /* 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;
           {
           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("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);
           p->message = string_sprintf("system_filter_%s_transport is unset",
             type);
-          }
 
 
-        if (tpname != NULL)
+        if (tpname)
           {
           transport_instance *tp;
           {
           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 (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);
           }
             p->message = string_sprintf("failed to find \"%s\" transport "
               "for system filter delivery", tpname);
           }
@@ -5307,13 +6128,13 @@ 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 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;
           {
           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 */
           badp->local_part = badp->address;   /* Needed for log line */
-          post_process_one(badp, DEFER, LOG_MAIN|LOG_PANIC, DTYPE_ROUTER, 0);
+          post_process_one(badp, DEFER, LOG_MAIN|LOG_PANIC, EXIM_DTYPE_ROUTER, 0);
           continue;
           }
         }    /* End of pfr handling */
           continue;
           }
         }    /* End of pfr handling */
@@ -5346,44 +6167,52 @@ spool if the message is deferred, and in any case there are casing
 complications for local addresses. */
 
 if (process_recipients != RECIP_IGNORE)
 complications for local addresses. */
 
 if (process_recipients != RECIP_IGNORE)
-  {
   for (i = 0; i < recipients_count; i++)
   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);
       {
       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;
 
 
       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;
          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_orcpt : US"", new->dsn_flags);
 
       switch (process_recipients)
         {
         /* RECIP_DEFER is set when a system filter freezes a message. */
 
         case RECIP_DEFER:
 
       switch (process_recipients)
         {
         /* RECIP_DEFER is set when a system filter freezes a message. */
 
         case RECIP_DEFER:
-        new->next = addr_defer;
-        addr_defer = new;
-        break;
+         new->next = addr_defer;
+         addr_defer = new;
+         break;
 
 
         /* RECIP_FAIL_FILTER is set when a system filter has obeyed a "fail"
         command. */
 
         case RECIP_FAIL_FILTER:
 
 
         /* RECIP_FAIL_FILTER is set when a system filter has obeyed a "fail"
         command. */
 
         case RECIP_FAIL_FILTER:
-        new->message =
-          (filter_message == NULL)? US"delivery cancelled" : filter_message;
-        setflag(new, af_pass_message);
-        goto RECIP_QUEUE_FAILED;   /* below */
+         new->message =
+           filter_message ? filter_message : US"delivery cancelled";
+         setflag(new, af_pass_message);
+         goto RECIP_QUEUE_FAILED;   /* below */
 
 
         /* RECIP_FAIL_TIMEOUT is set when a message is frozen, but is older
 
 
         /* RECIP_FAIL_TIMEOUT is set when a message is frozen, but is older
@@ -5393,15 +6222,15 @@ if (process_recipients != RECIP_IGNORE)
         been logged. */
 
         case RECIP_FAIL_TIMEOUT:
         been logged. */
 
         case RECIP_FAIL_TIMEOUT:
-        new->message  = US"delivery cancelled; message timed out";
-        goto RECIP_QUEUE_FAILED;   /* below */
+         new->message  = US"delivery cancelled; message timed out";
+         goto RECIP_QUEUE_FAILED;   /* below */
 
 
         /* RECIP_FAIL is set when -Mg has been used. */
 
         case RECIP_FAIL:
 
 
         /* RECIP_FAIL is set when -Mg has been used. */
 
         case RECIP_FAIL:
-        new->message  = US"delivery cancelled by administrator";
-        /* Fall through */
+         new->message  = US"delivery cancelled by administrator";
+         /* Fall through */
 
         /* Common code for the failure cases above. If this is not a bounce
         message, put the address on the failed list so that it is used to
 
         /* Common code for the failure cases above. If this is not a bounce
         message, put the address on the failed list so that it is used to
@@ -5409,11 +6238,11 @@ if (process_recipients != RECIP_IGNORE)
         The incident has already been logged. */
 
         RECIP_QUEUE_FAILED:
         The incident has already been logged. */
 
         RECIP_QUEUE_FAILED:
-        if (sender_address[0] != 0)
-          {
-          new->next = addr_failed;
-          addr_failed = new;
-          }
+         if (sender_address[0])
+           {
+           new->next = addr_failed;
+           addr_failed = new;
+           }
         break;
 
 
         break;
 
 
@@ -5422,32 +6251,52 @@ if (process_recipients != RECIP_IGNORE)
         is a bounce message, it will get frozen. */
 
         case RECIP_FAIL_LOOP:
         is a bounce message, it will get frozen. */
 
         case RECIP_FAIL_LOOP:
-        new->message = US"Too many \"Received\" headers - suspected mail loop";
-        post_process_one(new, FAIL, LOG_MAIN, DTYPE_ROUTER, 0);
-        break;
+         new->message = US"Too many \"Received\" headers - suspected mail loop";
+         post_process_one(new, FAIL, LOG_MAIN, EXIM_DTYPE_ROUTER, 0);
+         break;
 
 
         /* Value should be RECIP_ACCEPT; take this as the safe default. */
 
         default:
 
 
         /* Value should be RECIP_ACCEPT; take this as the safe default. */
 
         default:
-        if (addr_new == NULL) addr_new = new; else addr_last->next = new;
-        addr_last = new;
-        break;
+         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;
+       uschar * addr = new->address, * errmsg = NULL;
+       int start, end, dom;
+
+       if (!parse_extract_address(addr, &errmsg, &start, &end, &dom, TRUE))
+         log_write(0, LOG_MAIN|LOG_PANIC,
+                "failed to parse address '%.100s': %s\n", addr, errmsg);
+       else
+         {
+         deliver_localpart =
+           string_copyn(addr+start, dom ? (dom-1) - start : end - start);
+         deliver_domain = dom ? CUS string_copyn(addr+dom, end - dom) : CUS"";
+
+         event_raise(event_action, US"msg:fail:internal", new->message);
+
+         deliver_localpart = save_local;
+         deliver_domain = save_domain;
+         }
+       }
+#endif
       }
       }
-    }
-  }
 
 DEBUG(D_deliver)
   {
 
 DEBUG(D_deliver)
   {
-  address_item *p = addr_new;
+  address_item *p;
   debug_printf("Delivery address list:\n");
   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. */
   }
 
 /* Set up the buffers used for copying over the file when delivering. */
@@ -5495,25 +6344,22 @@ deliver_out_buffer = store_malloc(DELIVER_OUT_BUFFER_SIZE);
  . If new addresses have been generated by the routers, da capo.
 */
 
  . If new addresses have been generated by the routers, da capo.
 */
 
-header_rewritten = FALSE;          /* No headers rewritten yet */
-while (addr_new != NULL)           /* Loop until all addresses dealt with */
+f.header_rewritten = FALSE;          /* No headers rewritten yet */
+while (addr_new)           /* Loop until all addresses dealt with */
   {
   address_item *addr, *parent;
   {
   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. */
 
 
   /* 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");
     DEBUG(D_deliver|D_retry|D_route|D_hints_lookup)
       debug_printf("no retry data available\n");
-    }
 
   /* Scan the current batch of new addresses, to handle pipes, files and
   autoreplies, and determine which others are ready for routing. */
 
 
   /* 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;
     {
     int rc;
     uschar *p;
@@ -5544,8 +6390,8 @@ while (addr_new != NULL)           /* Loop until all addresses dealt with */
         addr->local_part = addr->address;
         addr->message =
           US"filter autoreply generated syntactically invalid recipient";
         addr->local_part = addr->address;
         addr->message =
           US"filter autoreply generated syntactically invalid recipient";
-        setflag(addr, af_ignore_error);
-        (void)post_process_one(addr, FAIL, LOG_MAIN, DTYPE_ROUTER, 0);
+        addr->prop.ignore_error = TRUE;
+        (void) post_process_one(addr, FAIL, LOG_MAIN, EXIM_DTYPE_ROUTER, 0);
         continue;   /* with the next new address */
         }
 
         continue;   /* with the next new address */
         }
 
@@ -5569,11 +6415,11 @@ while (addr_new != NULL)           /* Loop until all addresses dealt with */
 
       if (addr->address[0] == '>')
         {
 
       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);
         }
 
           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);
         {
         DEBUG(D_deliver|D_route)
           debug_printf("%s is a duplicate address: discarded\n", addr->address);
@@ -5587,7 +6433,7 @@ while (addr_new != NULL)           /* Loop until all addresses dealt with */
 
       /* Check for previous delivery */
 
 
       /* 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);
         {
         DEBUG(D_deliver|D_route)
           debug_printf("%s was previously delivered: discarded\n", addr->address);
@@ -5612,7 +6458,7 @@ while (addr_new != NULL)           /* Loop until all addresses dealt with */
           {
           addr->basic_errno = ERRNO_FORBIDFILE;
           addr->message = US"delivery to file forbidden";
           {
           addr->basic_errno = ERRNO_FORBIDFILE;
           addr->message = US"delivery to file forbidden";
-          (void)post_process_one(addr, FAIL, LOG_MAIN, DTYPE_ROUTER, 0);
+          (void)post_process_one(addr, FAIL, LOG_MAIN, EXIM_DTYPE_ROUTER, 0);
           continue;   /* with the next new address */
           }
         }
           continue;   /* with the next new address */
           }
         }
@@ -5622,7 +6468,7 @@ while (addr_new != NULL)           /* Loop until all addresses dealt with */
           {
           addr->basic_errno = ERRNO_FORBIDPIPE;
           addr->message = US"delivery to pipe forbidden";
           {
           addr->basic_errno = ERRNO_FORBIDPIPE;
           addr->message = US"delivery to pipe forbidden";
-          (void)post_process_one(addr, FAIL, LOG_MAIN, DTYPE_ROUTER, 0);
+          (void)post_process_one(addr, FAIL, LOG_MAIN, EXIM_DTYPE_ROUTER, 0);
           continue;   /* with the next new address */
           }
         }
           continue;   /* with the next new address */
           }
         }
@@ -5630,7 +6476,7 @@ while (addr_new != NULL)           /* Loop until all addresses dealt with */
         {
         addr->basic_errno = ERRNO_FORBIDREPLY;
         addr->message = US"autoreply forbidden";
         {
         addr->basic_errno = ERRNO_FORBIDREPLY;
         addr->message = US"autoreply forbidden";
-        (void)post_process_one(addr, FAIL, LOG_MAIN, DTYPE_ROUTER, 0);
+        (void)post_process_one(addr, FAIL, LOG_MAIN, EXIM_DTYPE_ROUTER, 0);
         continue;     /* with the next new address */
         }
 
         continue;     /* with the next new address */
         }
 
@@ -5641,7 +6487,7 @@ while (addr_new != NULL)           /* Loop until all addresses dealt with */
 
       if (addr->basic_errno == ERRNO_BADTRANSPORT)
         {
 
       if (addr->basic_errno == ERRNO_BADTRANSPORT)
         {
-        (void)post_process_one(addr, DEFER, LOG_MAIN, DTYPE_ROUTER, 0);
+        (void)post_process_one(addr, DEFER, LOG_MAIN, EXIM_DTYPE_ROUTER, 0);
         continue;
         }
 
         continue;
         }
 
@@ -5653,7 +6499,7 @@ while (addr_new != NULL)           /* Loop until all addresses dealt with */
         {
         uschar *save = addr->transport->name;
         addr->transport->name = US"**bypassed**";
         {
         uschar *save = addr->transport->name;
         addr->transport->name = US"**bypassed**";
-        (void)post_process_one(addr, OK, LOG_MAIN, DTYPE_TRANSPORT, '=');
+        (void)post_process_one(addr, OK, LOG_MAIN, EXIM_DTYPE_TRANSPORT, '=');
         addr->transport->name = save;
         continue;   /* with the next new address */
         }
         addr->transport->name = save;
         continue;   /* with the next new address */
         }
@@ -5676,7 +6522,7 @@ while (addr_new != NULL)           /* Loop until all addresses dealt with */
       {
       addr->message = US"cannot check percent_hack_domains";
       addr->basic_errno = ERRNO_LISTDEFER;
       {
       addr->message = US"cannot check percent_hack_domains";
       addr->basic_errno = ERRNO_LISTDEFER;
-      (void)post_process_one(addr, DEFER, LOG_MAIN, DTYPE_NONE, 0);
+      (void)post_process_one(addr, DEFER, LOG_MAIN, EXIM_DTYPE_NONE, 0);
       continue;
       }
 
       continue;
       }
 
@@ -5684,10 +6530,11 @@ while (addr_new != NULL)           /* Loop until all addresses dealt with */
     delivery was forced by hand. */
 
     deliver_domain = addr->domain;  /* set $domain */
     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,
            &domainlist_anchor, addr->domain_cache, MCL_DOMAIN, TRUE,
-           NULL)) != FAIL)
+           NULL)) != FAIL
+       )
       {
       if (rc == DEFER)
         {
       {
       if (rc == DEFER)
         {
@@ -5699,7 +6546,7 @@ while (addr_new != NULL)           /* Loop until all addresses dealt with */
         addr->message = US"domain is held";
         addr->basic_errno = ERRNO_HELD;
         }
         addr->message = US"domain is held";
         addr->basic_errno = ERRNO_HELD;
         }
-      (void)post_process_one(addr, DEFER, LOG_MAIN, DTYPE_NONE, 0);
+      (void)post_process_one(addr, DEFER, LOG_MAIN, EXIM_DTYPE_NONE, 0);
       continue;
       }
 
       continue;
       }
 
@@ -5709,7 +6556,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. */
 
     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
       if (strcmpic(addr->address, parent->address) == 0) break;
 
     /* If there's an ancestor with the same name, set the homonym flag. This
@@ -5719,7 +6566,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. */
 
     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] != '\\')
       {
       setflag(addr, af_homonym);
       if (parent->unique[0] != '\\')
@@ -5737,7 +6584,7 @@ while (addr_new != NULL)           /* Loop until all addresses dealt with */
 
     DEBUG(D_deliver|D_route) debug_printf("unique = %s\n", addr->unique);
 
 
     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);
       {
       DEBUG(D_deliver|D_route)
         debug_printf("%s was previously delivered: discarded\n", addr->unique);
@@ -5755,37 +6602,60 @@ while (addr_new != NULL)           /* Loop until all addresses dealt with */
     addr->address_retry_key = string_sprintf("R:%s@%s", addr->local_part,
       addr->domain);
 
     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);
       {
       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
+        )
+       {
+       DEBUG(D_deliver|D_retry)
+         debug_printf("domain retry record present but expired\n");
         domain_retry_record = NULL;    /* Ignore if too old */
         domain_retry_record = NULL;    /* Ignore if too old */
+       }
 
       address_retry_record = dbfn_read(dbm_file, addr->address_retry_key);
 
       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
+        )
+       {
+       DEBUG(D_deliver|D_retry)
+         debug_printf("address retry record present but expired\n");
         address_retry_record = NULL;   /* Ignore if too old */
         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);
         {
         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)
+         {
+         DEBUG(D_deliver|D_retry)
+           debug_printf("address<sender> retry record present but expired\n");
           address_retry_record = NULL;   /* Ignore if too old */
           address_retry_record = NULL;   /* Ignore if too old */
+         }
         }
       }
         }
       }
+    else
+      domain_retry_record = address_retry_record = NULL;
 
     DEBUG(D_deliver|D_retry)
       {
 
     DEBUG(D_deliver|D_retry)
       {
-      if (domain_retry_record == NULL)
-        debug_printf("no domain retry record\n");
-      if (address_retry_record == NULL)
-        debug_printf("no address retry record\n");
+      if (!domain_retry_record)
+       debug_printf("no   domain  retry record\n");
+      else
+       debug_printf("have domain  retry record; next_try = now%+d\n",
+                     f.running_in_test_harness ? 0 :
+                     (int)(domain_retry_record->next_try - now));
+
+      if (!address_retry_record)
+       debug_printf("no   address retry record\n");
+      else
+       debug_printf("have address retry record; next_try = now%+d\n",
+                     f.running_in_test_harness ? 0 :
+                     (int)(address_retry_record->next_try - now));
       }
 
     /* If we are sending a message down an existing SMTP connection, we must
       }
 
     /* If we are sending a message down an existing SMTP connection, we must
@@ -5802,11 +6672,14 @@ 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. */
 
     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;
       {
       addr->message = US"reusing SMTP connection skips previous routing defer";
       addr->basic_errno = ERRNO_RRETRY;
-      (void)post_process_one(addr, DEFER, LOG_MAIN, DTYPE_ROUTER, 0);
+      (void)post_process_one(addr, DEFER, LOG_MAIN, EXIM_DTYPE_ROUTER, 0);
+
+      addr->message = domain_retry_record->text;
+      setflag(addr, af_pass_message);
       }
 
     /* If we are in a queue run, defer routing unless there is no retry data or
       }
 
     /* If we are in a queue run, defer routing unless there is no retry data or
@@ -5841,23 +6714,35 @@ while (addr_new != NULL)           /* Loop until all addresses dealt with */
     which keep the retry record fresh, which can lead to us perpetually
     deferring messages. */
 
     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 (  (  f.queue_running && !f.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;
       {
       addr->message = US"retry time not reached";
       addr->basic_errno = ERRNO_RRETRY;
-      (void)post_process_one(addr, DEFER, LOG_MAIN, DTYPE_ROUTER, 0);
+      (void)post_process_one(addr, DEFER, LOG_MAIN, EXIM_DTYPE_ROUTER, 0);
+
+      /* For remote-retry errors (here and just above) that we've not yet
+      hit the rery time, use the error recorded in the retry database
+      as info in the warning message.  This lets us send a message even
+      when we're not failing on a fresh attempt.  We assume that this
+      info is not sensitive. */
+
+      addr->message = domain_retry_record
+       ? domain_retry_record->text : address_retry_record->text;
+      setflag(addr, af_pass_message);
       }
 
     /* The domain is OK for routing. Remember if retry data exists so it
       }
 
     /* The domain is OK for routing. Remember if retry data exists so it
@@ -5865,7 +6750,7 @@ while (addr_new != NULL)           /* Loop until all addresses dealt with */
 
     else
       {
 
     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;
         setflag(addr, af_dr_retry_exists);
       addr->next = addr_route;
       addr_route = addr;
@@ -5877,42 +6762,40 @@ 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. */
 
   /* 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 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 (!f.deliver_force && queue_domains)
     {
     address_item *okaddr = NULL;
     {
     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 */
       {
       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)
             &domainlist_anchor, addr->domain_cache, MCL_DOMAIN, TRUE, NULL))
               != OK)
-        {
         if (rc == DEFER)
           {
           addr->basic_errno = ERRNO_LISTDEFER;
           addr->message = US"queue_domains lookup deferred";
         if (rc == DEFER)
           {
           addr->basic_errno = ERRNO_LISTDEFER;
           addr->message = US"queue_domains lookup deferred";
-          (void)post_process_one(addr, DEFER, LOG_MAIN, DTYPE_ROUTER, 0);
+          (void)post_process_one(addr, DEFER, LOG_MAIN, EXIM_DTYPE_ROUTER, 0);
           }
         else
           {
           addr->next = okaddr;
           okaddr = addr;
           }
           }
         else
           {
           addr->next = okaddr;
           okaddr = addr;
           }
-        }
       else
         {
         addr->basic_errno = ERRNO_QUEUE_DOMAIN;
         addr->message = US"domain is in queue_domains";
       else
         {
         addr->basic_errno = ERRNO_QUEUE_DOMAIN;
         addr->message = US"domain is in queue_domains";
-        (void)post_process_one(addr, DEFER, LOG_MAIN, DTYPE_ROUTER, 0);
+        (void)post_process_one(addr, DEFER, LOG_MAIN, EXIM_DTYPE_ROUTER, 0);
         }
       }
 
         }
       }
 
@@ -5921,28 +6804,30 @@ while (addr_new != NULL)           /* Loop until all addresses dealt with */
 
   /* Now route those addresses that are not deferred. */
 
 
   /* Now route those addresses that are not deferred. */
 
-  while (addr_route != NULL)
+  while (addr_route)
     {
     int rc;
     address_item *addr = 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. */
 
     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)
 
     /* 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
 
     /* 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
@@ -5975,7 +6860,7 @@ while (addr_new != NULL)           /* Loop until all addresses dealt with */
 
     if (rc != OK)
       {
 
     if (rc != OK)
       {
-      (void)post_process_one(addr, rc, LOG_MAIN, DTYPE_ROUTER, 0);
+      (void)post_process_one(addr, rc, LOG_MAIN, EXIM_DTYPE_ROUTER, 0);
       continue;  /* route next address */
       }
 
       continue;  /* route next address */
       }
 
@@ -5984,8 +6869,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. */
 
     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);
       {
       DEBUG(D_deliver|D_route) debug_printf("%s was previously delivered: "
         "discarded\n", addr->address);
@@ -6001,14 +6887,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. */
 
     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;
       {
       address_item **chain = &addr_route;
-      while (*chain != NULL)
+      while (*chain)
         {
         address_item *addr2 = *chain;
         if (Ustrcmp(addr2->domain, addr->domain) != 0)
         {
         address_item *addr2 = *chain;
         if (Ustrcmp(addr2->domain, addr->domain) != 0)
@@ -6031,16 +6918,15 @@ 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->transport = addr->transport;
         addr2->host_list = addr->host_list;
         addr2->fallback_hosts = addr->fallback_hosts;
-        addr2->p.errors_address = addr->p.errors_address;
-        copyflag(addr2, addr, af_hide_child | af_local_host_removed);
+        addr2->prop.errors_address = addr->prop.errors_address;
+        copyflag(addr2, addr, af_hide_child);
+        copyflag(addr2, addr, af_local_host_removed);
 
         DEBUG(D_deliver|D_route)
 
         DEBUG(D_deliver|D_route)
-          {
           debug_printf(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"
                        "routing %s\n"
                        "Routing for %s copied from %s\n",
             addr2->address, addr2->address, addr->address);
           debug_printf(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"
                        "routing %s\n"
                        "Routing for %s copied from %s\n",
             addr2->address, addr2->address, addr->address);
-          }
         }
       }
     }  /* Continue with routing the next address. */
         }
       }
     }  /* Continue with routing the next address. */
@@ -6052,38 +6938,23 @@ while (addr_new != NULL)           /* Loop until all addresses dealt with */
 
 DEBUG(D_deliver|D_retry|D_route)
   {
 
 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");
   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);
     debug_printf("    %s\n", p->address);
-    p = p->next;
-    }
 
 
-  p = addr_remote;
   debug_printf("  Remote deliveries:\n");
   debug_printf("  Remote deliveries:\n");
-  while (p != NULL)
-    {
+  for (p = addr_remote; p; p = p->next)
     debug_printf("    %s\n", p->address);
     debug_printf("    %s\n", p->address);
-    p = p->next;
-    }
 
 
-  p = addr_failed;
   debug_printf("  Failed addresses:\n");
   debug_printf("  Failed addresses:\n");
-  while (p != NULL)
-    {
+  for (p = addr_failed; p; p = p->next)
     debug_printf("    %s\n", p->address);
     debug_printf("    %s\n", p->address);
-    p = p->next;
-    }
 
 
-  p = addr_defer;
   debug_printf("  Deferred addresses:\n");
   debug_printf("  Deferred addresses:\n");
-  while (p != NULL)
-    {
+  for (p = addr_defer; p; p = p->next)
     debug_printf("    %s\n", p->address);
     debug_printf("    %s\n", p->address);
-    p = p->next;
-    }
   }
 
 /* Free any resources that were cached during routing. */
   }
 
 /* Free any resources that were cached during routing. */
@@ -6110,18 +6981,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. */
 
 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;
 
   {
   address_item *addr;
   uschar *which, *colon, *msg;
 
-  if (addr_local != NULL)
+  if (addr_local)
     {
     addr = addr_local;
     which = US"local";
     }
     {
     addr = addr_local;
     which = US"local";
     }
-  else if (addr_defer != NULL)
+  else if (addr_defer)
     {
     addr = addr_defer;
     which = US"deferred";
     {
     addr = addr_defer;
     which = US"deferred";
@@ -6132,9 +7004,9 @@ if (mua_wrapper && (addr_local != NULL || addr_failed != NULL ||
     which = US"failed";
     }
 
     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;
     {
     colon = US": ";
     msg = addr->message;
@@ -6163,14 +7035,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 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;
     {
     address_item *addr = addr_defer;
-    while (addr->next != NULL) addr = addr->next;
+    while (addr->next) addr = addr->next;
     addr->next = addr_local;
     }
     addr->next = addr_local;
     }
+  else
+    addr_defer = addr_local;
   addr_local = NULL;
   }
 
   addr_local = NULL;
   }
 
@@ -6188,57 +7062,70 @@ 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. */
 
 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 (  f.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);
   {
   /* Panic-dies on error */
   (void)spool_write_header(message_id, SW_DELIVERING, NULL);
-  header_rewritten = FALSE;
+  f.header_rewritten = FALSE;
   }
 
 
   }
 
 
-/* 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 do 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. */
 
 
 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)
     {
   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,38 +7138,37 @@ 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. */
 
 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 */
 
 
 /* Handle local deliveries */
 
-if (addr_local != NULL)
+if (addr_local)
   {
   DEBUG(D_deliver|D_transport)
     debug_printf(">>>>>>>>>>>>>>>> Local deliveries >>>>>>>>>>>>>>>>\n");
   do_local_deliveries();
   {
   DEBUG(D_deliver|D_transport)
     debug_printf(">>>>>>>>>>>>>>>> Local deliveries >>>>>>>>>>>>>>>>\n");
   do_local_deliveries();
-  disable_logging = FALSE;
+  f.disable_logging = FALSE;
   }
 
 /* If queue_run_local is set, we do not want to attempt any remote deliveries,
 so just queue them all. */
 
   }
 
 /* If queue_run_local is set, we do not want to attempt any remote deliveries,
 so just queue them all. */
 
-if (queue_run_local)
-  {
-  while (addr_remote != NULL)
+if (f.queue_run_local)
+  while (addr_remote)
     {
     address_item *addr = addr_remote;
     addr_remote = addr->next;
     addr->next = NULL;
     addr->basic_errno = ERRNO_LOCAL_ONLY;
     addr->message = US"remote deliveries suppressed";
     {
     address_item *addr = addr_remote;
     addr_remote = addr->next;
     addr->next = NULL;
     addr->basic_errno = ERRNO_LOCAL_ONLY;
     addr->message = US"remote deliveries suppressed";
-    (void)post_process_one(addr, DEFER, LOG_MAIN, DTYPE_TRANSPORT, 0);
+    (void)post_process_one(addr, DEFER, LOG_MAIN, EXIM_DTYPE_TRANSPORT, 0);
     }
     }
-  }
 
 /* Handle remote deliveries */
 
 
 /* Handle remote deliveries */
 
-if (addr_remote != NULL)
+if (addr_remote)
   {
   DEBUG(D_deliver|D_transport)
     debug_printf(">>>>>>>>>>>>>>>> Remote deliveries >>>>>>>>>>>>>>>>\n");
   {
   DEBUG(D_deliver|D_transport)
     debug_printf(">>>>>>>>>>>>>>>> Remote deliveries >>>>>>>>>>>>>>>>\n");
@@ -6290,37 +7176,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. */
 
   /* 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. */
 
 
   /* 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 "
   if (!do_remote_deliveries(FALSE))
     {
     log_write(0, LOG_MAIN, "** mua_wrapper is set but recipients cannot all "
@@ -6337,15 +7199,15 @@ if (addr_remote != NULL)
   host is used for many domains, so all can be sent in a single transaction
   (if appropriately configured). */
 
   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;
     {
     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);
     }
     do_remote_deliveries(TRUE);
     }
-  disable_logging = FALSE;
+  f.disable_logging = FALSE;
   }
 
 
   }
 
 
@@ -6354,6 +7216,7 @@ phase, to minimize cases of half-done things. */
 
 DEBUG(D_deliver)
   debug_printf(">>>>>>>>>>>>>>>> deliveries are done >>>>>>>>>>>>>>>>\n");
 
 DEBUG(D_deliver)
   debug_printf(">>>>>>>>>>>>>>>> deliveries are done >>>>>>>>>>>>>>>>\n");
+cancel_cutthrough_connection(TRUE, US"deliveries are done");
 
 /* Root privilege is no longer needed */
 
 
 /* Root privilege is no longer needed */
 
@@ -6370,10 +7233,10 @@ do not ever want to retry, nor do we want to send a bounce message. */
 
 if (mua_wrapper)
   {
 
 if (mua_wrapper)
   {
-  if (addr_defer != NULL)
+  if (addr_defer)
     {
     address_item *addr, *nextaddr;
     {
     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);
       {
       log_write(0, LOG_MAIN, "** %s mua_wrapper forced failure for deferred "
         "delivery", addr->address);
@@ -6386,22 +7249,27 @@ if (mua_wrapper)
 
   /* Now all should either have succeeded or failed. */
 
 
   /* 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));
 
     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 == NULL)
-      {
-      if (addr_failed->basic_errno <= 0) fprintf(stderr, "unknown error");
+      if (s) fprintf(stderr, ": ");
       }
       }
-    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;
     fprintf(stderr, "\n");
 
     final_yield = DELIVER_MUA_FAILED;
@@ -6418,88 +7286,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. */
 
 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 (!f.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;
 
 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 */
   /* 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 ? addr_dsntmp->router->name : US"(unknown)",
+      addr_dsntmp->address,
+      sender_address,
+      addr_dsntmp->dsn_orcpt ? addr_dsntmp->dsn_orcpt : US"NULL",
+      addr_dsntmp->dsn_flags,
+      dsn_envid ? dsn_envid : US"NULL", 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 */
 
   /* 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_notify_success
+     )
     {
     /* copy and relink address_item and send report with all of them at once later */
     {
     /* 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));
     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
     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;
 
   {
   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);
   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));
 
   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 */
     {
   else  /* Creation of child succeeded */
     {
-    FILE *f = fdopen(fd, "wb");
+    FILE * f = fdopen(fd, "wb");
     /* header only as required by RFC. only failure DSN needs to honor RET=FULL */
     /* 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 */
     /* 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);
     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"
     fprintf(f, "Auto-Submitted: auto-generated\n"
        "From: Mail Delivery System <Mailer-Daemon@%s>\n"
        "To: %s\n"
@@ -6509,36 +7374,32 @@ if (addr_senddsn != NULL)
 
        "--%s\n"
        "Content-type: text/plain; charset=us-ascii\n\n"
 
        "--%s\n"
        "Content-type: text/plain; charset=us-ascii\n\n"
-   
+
        "This message was created automatically by mail delivery software.\n"
        " ----- The following addresses had successful delivery notifications -----\n",
        "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,
       fprintf(f, "<%s> (relayed %s)\n\n",
        addr_dsntmp->address,
-       (addr_dsntmp->dsn_flags & rf_dsnlasthop) == 1
-         ? "via non DSN router"
-         : addr_dsntmp->dsn_aware == dsn_support_no
-         ? "to non-DSN-aware mailer"
-         : "via non \"Remote SMTP\" router"
+       addr_dsntmp->dsn_flags & rf_dsnlasthop ? "via non DSN router"
+       : addr_dsntmp->dsn_aware == dsn_support_no ? "to non-DSN-aware mailer"
+       : "via non \"Remote SMTP\" router"
        );
        );
-      addr_dsntmp = addr_dsntmp->next;
-      }
+
     fprintf(f, "--%s\n"
        "Content-type: message/delivery-status\n\n"
        "Reporting-MTA: dns; %s\n",
     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
       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);
 
       }
     fputc('\n', f);
 
@@ -6555,40 +7416,42 @@ if (addr_senddsn != NULL)
        addr_dsntmp->address);
 
       if (addr_dsntmp->host_used && addr_dsntmp->host_used->name)
        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
          addr_dsntmp->host_used->name);
       else
-       fprintf(f,"Diagnostic-Code: X-Exim; relayed via non %s router\n",
-         (addr_dsntmp->dsn_flags & rf_dsnlasthop) == 1 ? "DSN" : "SMTP");
-      fputc('\n', f);
+       fprintf(f, "Diagnostic-Code: X-Exim; relayed via non %s router\n\n",
+         addr_dsntmp->dsn_flags & rf_dsnlasthop ? "DSN" : "SMTP");
       }
 
       }
 
-    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 */
     fflush(f);
     transport_filter_argv = NULL;   /* Just in case */
     return_path = sender_address;   /* In case not previously set */
-           
+
     /* Write the original email out */
     /* Write the original email out */
-    transport_write_message(NULL, fileno(f), topt, 0, NULL, NULL, NULL, NULL, NULL, 0);
+
+    tctx.u.fd = fd;
+    tctx.options = topt_add_return_path | topt_no_body;
+    /*XXX hmm, retval ignored.
+    Could error for any number of reasons, and they are not handled. */
+    transport_write_message(&tctx, 0);
     fflush(f);
 
     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 */
     }
   }
 
     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. */
 
 
 /* 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;
   {
   pid_t pid;
   int fd;
@@ -6602,9 +7465,9 @@ while (addr_failed != NULL)
   /* There are weird cases when logging is disabled in the transport. However,
   there may not be a transport (address failed by a router). */
 
   /* There are weird cases when logging is disabled in the transport. However,
   there may not be a transport (address failed by a router). */
 
-  disable_logging = FALSE;
-  if (addr_failed->transport != NULL)
-    disable_logging = addr_failed->transport->disable_logging;
+  f.disable_logging = FALSE;
+  if (addr_failed->transport)
+    f.disable_logging = addr_failed->transport->disable_logging;
 
   DEBUG(D_deliver)
     debug_printf("processing failed address %s\n", addr_failed->address);
 
   DEBUG(D_deliver)
     debug_printf("processing failed address %s\n", addr_failed->address);
@@ -6625,37 +7488,36 @@ while (addr_failed != NULL)
   If neither of these cases obtains, something has gone wrong. Log the
   incident, but then ignore the error. */
 
   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)
+       && !addr_failed->prop.ignore_error)
       log_write(0, LOG_MAIN|LOG_PANIC, "internal error: bounce message "
         "failure is neither frozen nor ignored (it's been ignored)");
       log_write(0, LOG_MAIN|LOG_PANIC, "internal error: bounce message "
         "failure is neither frozen nor ignored (it's been ignored)");
-      }
-    setflag(addr_failed, af_ignore_error);
+
+    addr_failed->prop.ignore_error = TRUE;
     }
 
   /* If the first address on the list has af_ignore_error set, just remove
   it from the list, throw away any saved message file, log it, and
   mark the recipient done. */
 
     }
 
   /* If the first address on the list has af_ignore_error set, just remove
   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 (  addr_failed->prop.ignore_error
+     || addr_failed->dsn_flags & (rf_dsnflags & ~rf_notify_failure)
      )
      )
-  {
+    {
     addr = addr_failed;
     addr_failed = addr->next;
     addr = addr_failed;
     addr_failed = addr->next;
-    if (addr->return_filename != NULL) Uunlink(addr->return_filename);
+    if (addr->return_filename) Uunlink(addr->return_filename);
 
 
+#ifndef DISABLE_EVENT
+    msg_event_raise(US"msg:fail:delivery", addr);
+#endif
     log_write(0, LOG_MAIN, "%s%s%s%s: error ignored",
       addr->address,
     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);
 
     address_done(addr, logtod);
     child_done(addr, logtod);
@@ -6666,21 +7528,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
   /* 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
     {
   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 */
 
 
     /* 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));
       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));
@@ -6693,17 +7551,15 @@ while (addr_failed != NULL)
       int filecount = 0;
       int rcount = 0;
       uschar *bcc, *emf_text;
       int filecount = 0;
       int rcount = 0;
       uschar *bcc, *emf_text;
-      FILE *f = fdopen(fd, "wb");
-      FILE *emf = NULL;
+      FILE * fp = fdopen(fd, "wb");
+      FILE * emf = 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;
       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;
       uschar *dsnlimitmsg;
       uschar *dsnnotifyhdr;
       int topt;
-#endif
 
       DEBUG(D_deliver)
         debug_printf("sending error message to: %s\n", bounce_recipient);
 
       DEBUG(D_deliver)
         debug_printf("sending error message to: %s\n", bounce_recipient);
@@ -6712,61 +7568,56 @@ while (addr_failed != NULL)
       them from the addr_failed chain, and putting them on msgchain. */
 
       paddr = &addr_failed;
       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);
           }
           *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. */
 
 
       /* 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)
           {
         {
         if (testflag(addr, af_hide_child)) continue;
         if (rcount >= 50)
           {
-          fprintf(f, "\n");
+          fprintf(fp, "\n");
           rcount = 0;
           }
           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));
+        fprintf(fp, "%s%s",
+          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");
+      if (rcount > 0) fprintf(fp, "\n");
 
       /* Output the standard headers */
 
 
       /* Output the standard headers */
 
-      if (errors_reply_to != NULL)
-        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);
+      if (errors_reply_to)
+        fprintf(fp, "Reply-To: %s\n", errors_reply_to);
+      fprintf(fp, "Auto-Submitted: auto-replied\n");
+      moan_write_from(fp);
+      fprintf(fp, "To: %s\n", bounce_recipient);
 
 
-#ifdef EXPERIMENTAL_DSN
       /* generate boundary string and output MIME-Headers */
       /* 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;"
+      fprintf(fp, "Content-Type: multipart/report;"
            " report-type=delivery-status; boundary=%s\n"
          "MIME-Version: 1.0\n",
            " 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. */
 
       /* Open a template file if one is provided. Log failure to open, but
       carry on - default texts will be used. */
@@ -6779,48 +7630,46 @@ while (addr_failed != NULL)
       /* Quietly copy to configured additional addresses if required. */
 
       if ((bcc = moan_check_errorcopy(bounce_recipient)))
       /* Quietly copy to configured additional addresses if required. */
 
       if ((bcc = moan_check_errorcopy(bounce_recipient)))
-       fprintf(f, "Bcc: %s\n", bcc);
+       fprintf(fp, "Bcc: %s\n", bcc);
 
       /* The texts for the message can be read from a template file; if there
       isn't one, or if it is too short, built-in texts are used. The first
       emf text is a Subject: and any other headers. */
 
       if ((emf_text = next_emf(emf, US"header")))
 
       /* The texts for the message can be read from a template file; if there
       isn't one, or if it is too short, built-in texts are used. The first
       emf text is a Subject: and any other headers. */
 
       if ((emf_text = next_emf(emf, US"header")))
-       fprintf(f, "%s\n", emf_text);
+       fprintf(fp, "%s\n", emf_text);
       else
       else
-        fprintf(f, "Subject: Mail delivery failed%s\n\n",
+        fprintf(fp, "Subject: Mail delivery failed%s\n\n",
           to_sender? ": returning message to sender" : "");
 
           to_sender? ": returning message to sender" : "");
 
-#ifdef EXPERIMENTAL_DSN
       /* output human readable part as text/plain section */
       /* output human readable part as text/plain section */
-      fprintf(f, "--%s\n"
+      fprintf(fp, "--%s\n"
          "Content-type: text/plain; charset=us-ascii\n\n",
          "Content-type: text/plain; charset=us-ascii\n\n",
-       boundaryStr);
-#endif
+       bound);
 
       if ((emf_text = next_emf(emf, US"intro")))
 
       if ((emf_text = next_emf(emf, US"intro")))
-       fprintf(f, "%s", CS emf_text);
+       fprintf(fp, "%s", CS emf_text);
       else
         {
       else
         {
-        fprintf(f,
+        fprintf(fp,
 /* This message has been reworded several times. It seems to be confusing to
 somebody, however it is worded. I have retreated to the original, simple
 wording. */
 "This message was created automatically by mail delivery software.\n");
 
         if (bounce_message_text)
 /* This message has been reworded several times. It seems to be confusing to
 somebody, however it is worded. I have retreated to the original, simple
 wording. */
 "This message was created automatically by mail delivery software.\n");
 
         if (bounce_message_text)
-         fprintf(f, "%s", CS bounce_message_text);
+         fprintf(fp, "%s", CS bounce_message_text);
         if (to_sender)
         if (to_sender)
-          fprintf(f,
+          fprintf(fp,
 "\nA message that you sent could not be delivered to one or more of its\n"
 "recipients. This is a permanent error. The following address(es) failed:\n");
         else
 "\nA message that you sent could not be delivered to one or more of its\n"
 "recipients. This is a permanent error. The following address(es) failed:\n");
         else
-          fprintf(f,
+          fprintf(fp,
 "\nA message sent by\n\n  <%s>\n\n"
 "could not be delivered to one or more of its recipients. The following\n"
 "address(es) failed:\n", sender_address);
         }
 "\nA message sent by\n\n  <%s>\n\n"
 "could not be delivered to one or more of its recipients. The following\n"
 "address(es) failed:\n", sender_address);
         }
-      fputc('\n', f);
+      fputc('\n', fp);
 
       /* Process the addresses, leaving them on the msgchain if they have a
       file name for a return message. (There has already been a check in
 
       /* Process the addresses, leaving them on the msgchain if they have a
       file name for a return message. (There has already been a check in
@@ -6829,14 +7678,14 @@ wording. */
       hidden. */
 
       paddr = &msgchain;
       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"");
+        if (print_address_information(addr, fp, US"  ", US"\n    ", US""))
+          print_address_error(addr, fp, US"");
 
         /* End the final line for the address */
 
 
         /* End the final line for the address */
 
-        fputc('\n', f);
+        fputc('\n', fp);
 
         /* Leave on msgchain if there's a return file. */
 
 
         /* Leave on msgchain if there's a return file. */
 
@@ -6857,7 +7706,7 @@ wording. */
           }
         }
 
           }
         }
 
-      fputc('\n', f);
+      fputc('\n', fp);
 
       /* Get the next text, whether we need it or not, so as to be
       positioned for the one after. */
 
       /* Get the next text, whether we need it or not, so as to be
       positioned for the one after. */
@@ -6876,39 +7725,37 @@ wording. */
         address_item *nextaddr;
 
         if (emf_text)
         address_item *nextaddr;
 
         if (emf_text)
-         fprintf(f, "%s", CS emf_text);
+         fprintf(fp, "%s", CS emf_text);
        else
        else
-          fprintf(f,
+          fprintf(fp,
             "The following text was generated during the delivery "
             "attempt%s:\n", (filecount > 1)? "s" : "");
 
             "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;
 
           /* List all the addresses that relate to this file */
 
           {
           FILE *fm;
           address_item *topaddr = addr;
 
           /* List all the addresses that relate to this file */
 
-         fputc('\n', f);
+         fputc('\n', fp);
           while(addr)                   /* Insurance */
             {
           while(addr)                   /* Insurance */
             {
-            print_address_information(addr, f, US"------ ",  US"\n       ",
+            print_address_information(addr, fp, US"------ ",  US"\n       ",
               US" ------\n");
             if (addr->return_filename) break;
             addr = addr->next;
             }
               US" ------\n");
             if (addr->return_filename) break;
             addr = addr->next;
             }
-         fputc('\n', f);
+         fputc('\n', fp);
 
           /* Now copy the file */
 
 
           /* Now copy the file */
 
-          fm = Ufopen(addr->return_filename, "rb");
-
-          if (fm == NULL)
-            fprintf(f, "    +++ Exim error... failed to open text file: %s\n",
+          if (!(fm = Ufopen(addr->return_filename, "rb")))
+            fprintf(fp, "    +++ Exim error... failed to open text file: %s\n",
               strerror(errno));
           else
             {
               strerror(errno));
           else
             {
-            while ((ch = fgetc(fm)) != EOF) fputc(ch, f);
+            while ((ch = fgetc(fm)) != EOF) fputc(ch, fp);
             (void)fclose(fm);
             }
           Uunlink(addr->return_filename);
             (void)fclose(fm);
             }
           Uunlink(addr->return_filename);
@@ -6920,38 +7767,65 @@ wording. */
           addr->next = handled_addr;
           handled_addr = topaddr;
           }
           addr->next = handled_addr;
           handled_addr = topaddr;
           }
-       fputc('\n', f);
+       fputc('\n', fp);
         }
 
         }
 
-#ifdef EXPERIMENTAL_DSN
       /* output machine readable part */
       /* 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(fp, "--%s\n"
+           "Content-type: message/global-delivery-status\n\n"
+           "Reporting-MTA: dns; %s\n",
+         bound, smtp_active_hostname);
+      else
+#endif
+       fprintf(fp, "--%s\n"
+           "Content-type: message/delivery-status\n\n"
+           "Reporting-MTA: dns; %s\n",
+         bound, smtp_active_hostname);
 
       if (dsn_envid)
        {
         /* must be decoded from xtext: see RFC 3461:6.3a */
         uschar *xdec_envid;
         if (auth_xtextdecode(dsn_envid, &xdec_envid) > 0)
 
       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);
+          fprintf(fp, "Original-Envelope-ID: %s\n", dsn_envid);
         else
         else
-          fprintf(f, "X-Original-Envelope-ID: error decoding xtext formated ENVID\n");
+          fprintf(fp, "X-Original-Envelope-ID: error decoding xtext formatted ENVID\n");
         }
         }
-      fputc('\n', f);
+      fputc('\n', fp);
+
       for (addr = handled_addr; addr; addr = addr->next)
         {
       for (addr = handled_addr; addr; addr = addr->next)
         {
-        fprintf(f, "Action: failed\n"
+       host_item * hu;
+        fprintf(fp, "Action: failed\n"
            "Final-Recipient: rfc822;%s\n"
            "Status: 5.0.0\n",
            addr->address);
            "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)
+         {
+         fprintf(fp, "Remote-MTA: dns; %s\n", hu->name);
+#ifdef EXPERIMENTAL_DSN_INFO
+         {
+         const uschar * s;
+         if (hu->address)
+           {
+           uschar * p = hu->port == 25
+             ? US"" : string_sprintf(":%d", hu->port);
+           fprintf(fp, "Remote-MTA: X-ip; [%s]%s\n", hu->address, p);
+           }
+         if ((s = addr->smtp_greeting) && *s)
+           fprintf(fp, "X-Remote-MTA-smtp-greeting: X-str; %s\n", s);
+         if ((s = addr->helo_response) && *s)
+           fprintf(fp, "X-Remote-MTA-helo-response: X-str; %s\n", s);
+         if ((s = addr->message) && *s)
+           fprintf(fp, "X-Exim-Diagnostic: X-str; %s\n", s);
+         }
 #endif
 #endif
+         print_dsn_diagnostic_code(addr, fp);
+         }
+       fputc('\n', fp);
+        }
 
       /* 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
 
       /* 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 +7834,18 @@ wording. */
 
       emf_text = next_emf(emf, US"copy");
 
 
       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
       /* 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.
          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_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.
       */
          bounce_return_size_limit is always honored.
       */
-  
-      fprintf(f, "\n--%s\n", boundaryStr);
+
+      fprintf(fp, "--%s\n", bound);
 
       dsnlimitmsg = US"X-Exim-DSN-Information: Due to administrative limits only headers are returned";
       dsnnotifyhdr = NULL;
 
       dsnlimitmsg = US"X-Exim-DSN-Information: Due to administrative limits only headers are returned";
       dsnnotifyhdr = NULL;
@@ -7038,6 +7855,9 @@ wording. */
       if (dsn_ret == dsn_ret_hdrs)
         topt |= topt_no_body;
       else
       if (dsn_ret == dsn_ret_hdrs)
         topt |= topt_no_body;
       else
+       {
+       struct stat statbuf;
+
         /* no full body return at all? */
         if (!bounce_return_body)
           {
         /* no full body return at all? */
         if (!bounce_return_body)
           {
@@ -7046,45 +7866,62 @@ wording. */
           if (dsn_ret == dsn_ret_full)
             dsnnotifyhdr = dsnlimitmsg;
           }
           if (dsn_ret == dsn_ret_full)
             dsnnotifyhdr = dsnlimitmsg;
           }
+       /* line length limited... return headers only if oversize */
         /* size limited ... return headers only if limit reached */
         /* 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",
+             fp);
       else
       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",
+             fp);
 
 
-      fflush(f);
+      fflush(fp);
       transport_filter_argv = NULL;   /* Just in case */
       return_path = sender_address;   /* In case not previously set */
       transport_filter_argv = NULL;   /* Just in case */
       return_path = sender_address;   /* In case not previously set */
-      transport_write_message(NULL, fileno(f), topt,
-        0, dsnnotifyhdr, NULL, NULL, NULL, NULL, 0);
-      fflush(f);
+       {                             /* Dummy transport for headers add */
+       transport_ctx tctx = {{0}};
+       transport_instance tb = {0};
+
+       tctx.u.fd = fileno(fp);
+       tctx.tblock = &tb;
+       tctx.options = topt;
+       tb.add_headers = dsnnotifyhdr;
+
+       /*XXX no checking for failure!  buggy! */
+       transport_write_message(&tctx, 0);
+       }
+      fflush(fp);
+
       /* we never add the final text. close the file */
       if (emf)
         (void)fclose(emf);
       /* we never add the final text. close the file */
       if (emf)
         (void)fclose(emf);
-      fprintf(f, "\n--%s--\n", boundaryStr);
-#endif /*EXPERIMENTAL_DSN*/
+
+      fprintf(fp, "\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. */
 
 
       /* Close the file, which should send an EOF to the child process
       that is receiving the message. Wait for it to finish. */
 
-      (void)fclose(f);
+      (void)fclose(fp);
       rc = child_close(pid, 0);     /* Waits for child to close, no timeout */
 
       /* In the test harness, let the child do it's thing first. */
 
       rc = child_close(pid, 0);     /* Waits for child to close, no timeout */
 
       /* In the test harness, let the child do it's thing first. */
 
-      if (running_in_test_harness) millisleep(500);
+      if (f.running_in_test_harness) millisleep(500);
 
       /* If the process failed, there was some disaster in setting up the
       error message. Unless the message is very old, ensure that addr_defer
 
       /* If the process failed, there was some disaster in setting up the
       error message. Unless the message is very old, ensure that addr_defer
@@ -7098,10 +7935,10 @@ wording. */
       if (rc != 0)
         {
         uschar *s = US"";
       if (rc != 0)
         {
         uschar *s = US"";
-        if (now - received_time < retry_maximum_timeout && addr_defer == NULL)
+        if (now - received_time.tv_sec < retry_maximum_timeout && !addr_defer)
           {
           addr_defer = (address_item *)(+1);
           {
           addr_defer = (address_item *)(+1);
-          deliver_freeze = TRUE;
+          f.deliver_freeze = TRUE;
           deliver_frozen_at = time(NULL);
           /* Panic-dies on error */
           (void)spool_write_header(message_id, SW_DELIVERING, NULL);
           deliver_frozen_at = time(NULL);
           /* Panic-dies on error */
           (void)spool_write_header(message_id, SW_DELIVERING, NULL);
@@ -7118,7 +7955,7 @@ wording. */
 
       else
         {
 
       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);
           {
           address_done(addr, logtod);
           child_done(addr, logtod);
@@ -7130,7 +7967,7 @@ wording. */
     }
   }
 
     }
   }
 
-disable_logging = FALSE;  /* In case left set */
+f.disable_logging = FALSE;  /* In case left set */
 
 /* Come here from the mua_wrapper case if routing goes wrong */
 
 
 /* Come here from the mua_wrapper case if routing goes wrong */
 
@@ -7140,55 +7977,59 @@ DELIVERY_TIDYUP:
 message log if so configured, and we are using them. Otherwise, sling it.
 Then delete the message itself. */
 
 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)
     {
   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;
     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 "
         }
       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
       }
     else
-      {
-      if (Uunlink(spoolname) < 0)
+      if (Uunlink(fname) < 0)
         log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to unlink %s: %s",
         log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to unlink %s: %s",
-                 spoolname, strerror(errno));
-      }
+                 fname, strerror(errno));
     }
 
   /* Remove the two message files. */
 
     }
 
   /* 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",
     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",
     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. */
 
 
   /* Log the end of this message, with queue time if requested. */
 
-  if ((log_extra_selector & LX_queue_time_overall) != 0)
-    log_write(0, LOG_MAIN, "Completed QT=%s",
-      readconf_printtime( (int) ((long)time(NULL) - (long)received_time)) );
+  if (LOGGING(queue_time_overall))
+    log_write(0, LOG_MAIN, "Completed QT=%s", string_timesince(&received_time));
   else
     log_write(0, LOG_MAIN, "Completed");
 
   /* Unset deliver_freeze so that we won't try to move the spool files further down */
   else
     log_write(0, LOG_MAIN, "Completed");
 
   /* Unset deliver_freeze so that we won't try to move the spool files further down */
-  deliver_freeze = FALSE;
+  f.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
   }
 
 /* If there are deferred addresses, we are keeping this message because it is
@@ -7203,6 +8044,8 @@ the parent's domain.
 If all the deferred addresses have an error number that indicates "retry time
 not reached", skip sending the warning message, because it won't contain the
 reason for the delay. It will get sent at the next real delivery attempt.
 If all the deferred addresses have an error number that indicates "retry time
 not reached", skip sending the warning message, because it won't contain the
 reason for the delay. It will get sent at the next real delivery attempt.
+  Exception: for retries caused by a remote peer we use the error message
+  store in the retry DB as the reason.
 However, if at least one address has tried, we'd better include all of them in
 the message.
 
 However, if at least one address has tried, we'd better include all of them in
 the message.
 
@@ -7222,37 +8065,39 @@ else if (addr_defer != (address_item *)(+1))
   {
   address_item *addr;
   uschar *recipients = US"";
   {
   address_item *addr;
   uschar *recipients = US"";
-  BOOL delivery_attempted = FALSE;
+  BOOL want_warning_msg = 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;
 
     {
     address_item *otaddr;
 
-    if (addr->basic_errno > ERRNO_RETRY_BASE) delivery_attempted = TRUE;
+    if (addr->basic_errno > ERRNO_WARN_BASE) want_warning_msg = 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. */
 
 
       /* 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. */
 
 
     /* 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;
       {
       int i;
       int t = recipients_count;
@@ -7265,15 +8110,17 @@ else if (addr_defer != (address_item *)(+1))
         }
 
       /* Didn't find the address already in the list, and did find the
         }
 
       /* 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. */
 
       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);
         {
         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;
         }
         tree_add_nonrecipient(otaddr->parent->address);
         update_spool = TRUE;
         }
@@ -7283,20 +8130,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. */
 
     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,26 +8145,29 @@ else if (addr_defer != (address_item *)(+1))
   is not sent. Another attempt will be made at the next delivery attempt (if
   it also defers). */
 
   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 (  !f.queue_2stage
+     && want_warning_msg
+     && (  !(addr_defer->dsn_flags & rf_dsnflags)
+        || addr_defer->dsn_flags & 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;
     {
     int count;
     int show_time;
-    int queue_time = time(NULL) - received_time;
+    int queue_time = time(NULL) - received_time.tv_sec;
 
     /* When running in the test harness, there's an option that allows us to
     fudge this time so as to get repeatability of the tests. Take the first
     time off the list. In queue runs, the list pointer gets updated in the
     calling process. */
 
 
     /* When running in the test harness, there's an option that allows us to
     fudge this time so as to get repeatability of the tests. Take the first
     time off the list. In queue runs, the list pointer gets updated in the
     calling process. */
 
-    if (running_in_test_harness && fudged_queue_times[0] != 0)
+    if (f.running_in_test_harness && fudged_queue_times[0] != 0)
       {
       int qt = readconf_readtime(fudged_queue_times, '/', FALSE);
       if (qt >= 0)
       {
       int qt = readconf_readtime(fudged_queue_times, '/', FALSE);
       if (qt >= 0)
@@ -7354,7 +8197,7 @@ else if (addr_defer != (address_item *)(+1))
 
     DEBUG(D_deliver)
       {
 
     DEBUG(D_deliver)
       {
-      debug_printf("time on queue = %s\n", readconf_printtime(queue_time));
+      debug_printf("time on queue = %s  id %s  addr %s\n", readconf_printtime(queue_time), message_id, addr_defer->address);
       debug_printf("warning counts: required %d done %d\n", count,
         warning_count);
       }
       debug_printf("warning counts: required %d done %d\n", count,
         warning_count);
       }
@@ -7374,22 +8217,18 @@ else if (addr_defer != (address_item *)(+1))
         uschar *wmf_text;
         FILE *wmf = NULL;
         FILE *f = fdopen(fd, "wb");
         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)
 
         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));
             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_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);
 
         if (errors_reply_to)
           fprintf(f, "Reply-To: %s\n", errors_reply_to);
@@ -7397,16 +8236,13 @@ else if (addr_defer != (address_item *)(+1))
         moan_write_from(f);
         fprintf(f, "To: %s\n", recipients);
 
         moan_write_from(f);
         fprintf(f, "To: %s\n", recipients);
 
-#ifdef EXPERIMENTAL_DSN
         /* generated boundary string and output MIME-Headers */
         /* 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",
 
         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);
 
         if ((wmf_text = next_emf(wmf, US"header")))
           fprintf(f, "%s\n", wmf_text);
@@ -7414,12 +8250,10 @@ else if (addr_defer != (address_item *)(+1))
           fprintf(f, "Subject: Warning: message %s delayed %s\n\n",
             message_id, warnmsg_delay);
 
           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",
         /* 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);
 
         if ((wmf_text = next_emf(wmf, US"intro")))
          fprintf(f, "%s", CS wmf_text);
@@ -7443,7 +8277,7 @@ else if (addr_defer != (address_item *)(+1))
              "The message identifier is:     %s\n",
            warnmsg_delay, primary_hostname, message_id);
 
              "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)
             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 +8292,8 @@ else if (addr_defer != (address_item *)(+1))
 
         /* List the addresses, with error information if allowed */
 
 
         /* List the addresses, with error information if allowed */
 
-#ifdef EXPERIMENTAL_DSN
         /* store addr_defer for machine readable part */
         address_item *addr_dsndefer = addr_defer;
         /* store addr_defer for machine readable part */
         address_item *addr_dsndefer = addr_defer;
-#endif
         fputc('\n', f);
         while (addr_defer)
           {
         fputc('\n', f);
         while (addr_defer)
           {
@@ -7490,14 +8322,13 @@ else if (addr_defer != (address_item *)(+1))
 "and when that happens, the message will be returned to you.\n");
           }
 
 "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",
         /* 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);
          smtp_active_hostname);
+
 
         if (dsn_envid)
          {
 
         if (dsn_envid)
          {
@@ -7506,41 +8337,47 @@ 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
           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);
 
           }
         fputc('\n', f);
 
-        while (addr_dsndefer)
+        for ( ; addr_dsndefer; addr_dsndefer = addr_dsndefer->next)
           {
           if (addr_dsndefer->dsn_orcpt)
           {
           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)
           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",
            "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 */
 
         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.u.fd = fileno(f);
+        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 */
         transport_filter_argv = NULL;   /* Just in case */
         return_path = sender_address;   /* In case not previously set */
+
         /* Write the original email out */
         /* Write the original email out */
-        transport_write_message(NULL, fileno(f), topt, 0, NULL, NULL, NULL, NULL, NULL, 0);
+       /*XXX no checking for failure!  buggy! */
+        transport_write_message(&tctx, 0);
         fflush(f);
 
         fflush(f);
 
-        fprintf(f,"\n--%s--\n", boundaryStr);
+        fprintf(f,"\n--%s--\n", bound);
 
         fflush(f);
 
         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. */
 
         /* Close and wait for child process to complete, without a timeout.
         If there's an error, don't update the count. */
@@ -7562,9 +8399,9 @@ else if (addr_defer != (address_item *)(+1))
   /* If this was a first delivery attempt, unset the first time flag, and
   ensure that the spool gets updated. */
 
   /* If this was a first delivery attempt, unset the first time flag, and
   ensure that the spool gets updated. */
 
-  if (deliver_firsttime)
+  if (f.deliver_firsttime)
     {
     {
-    deliver_firsttime = FALSE;
+    f.deliver_firsttime = FALSE;
     update_spool = TRUE;
     }
 
     update_spool = TRUE;
     }
 
@@ -7575,9 +8412,9 @@ else if (addr_defer != (address_item *)(+1))
   For the "tell" message, we turn \n back into newline. Also, insert a newline
   near the start instead of the ": " string. */
 
   For the "tell" message, we turn \n back into newline. Also, insert a newline
   near the start instead of the ": " string. */
 
-  if (deliver_freeze)
+  if (f.deliver_freeze)
     {
     {
-    if (freeze_tell != NULL && freeze_tell[0] != 0 && !local_error_message)
+    if (freeze_tell && freeze_tell[0] != 0 && !f.local_error_message)
       {
       uschar *s = string_copy(frozen_info);
       uschar *ss = Ustrstr(s, " by the system filter: ");
       {
       uschar *s = string_copy(frozen_info);
       uschar *ss = Ustrstr(s, " by the system filter: ");
@@ -7618,9 +8455,9 @@ else if (addr_defer != (address_item *)(+1))
 
   DEBUG(D_deliver)
     debug_printf("delivery deferred: update_spool=%d header_rewritten=%d\n",
 
   DEBUG(D_deliver)
     debug_printf("delivery deferred: update_spool=%d header_rewritten=%d\n",
-      update_spool, header_rewritten);
+      update_spool, f.header_rewritten);
 
 
-  if (update_spool || header_rewritten)
+  if (update_spool || f.header_rewritten)
     /* Panic-dies on error */
     (void)spool_write_header(message_id, SW_DELIVERING, NULL);
   }
     /* Panic-dies on error */
     (void)spool_write_header(message_id, SW_DELIVERING, NULL);
   }
@@ -7646,17 +8483,18 @@ if (journal_fd >= 0) (void)close(journal_fd);
 
 if (remove_journal)
   {
 
 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));
 
       strerror(errno));
 
-  /* Move the message off the spool if reqested */
+  /* Move the message off the spool if requested */
 
 
-  #ifdef SUPPORT_MOVE_FROZEN_MESSAGES
-  if (deliver_freeze && move_frozen_messages)
+#ifdef SUPPORT_MOVE_FROZEN_MESSAGES
+  if (f.deliver_freeze && move_frozen_messages)
     (void)spool_move_message(id, message_subdir, US"", US"F");
     (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
   }
 
 /* Closing the data file frees the lock; if the file has been unlinked it
@@ -7678,6 +8516,178 @@ acl_where = ACL_WHERE_UNKNOWN;
 return final_yield;
 }
 
 return final_yield;
 }
 
+
+
+void
+deliver_init(void)
+{
+#ifdef EXIM_TFO_PROBE
+tfo_probe();
+#else
+f.tcp_fastopen_ok = TRUE;
+#endif
+
+
+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(AUTHS_REGEX, FALSE, TRUE);
+
+#ifdef SUPPORT_TLS
+if (!regex_STARTTLS) regex_STARTTLS =
+  regex_must_compile(US"\\n250[\\s\\-]STARTTLS(\\s|\\n|$)", FALSE, TRUE);
+
+# ifdef EXPERIMENTAL_REQUIRETLS
+if (!regex_REQUIRETLS) regex_REQUIRETLS =
+  regex_must_compile(US"\\n250[\\s\\-]REQUIRETLS(\\s|\\n|$)", FALSE, TRUE);
+# endif
+#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);
+
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+if (!regex_EARLY_PIPE) regex_EARLY_PIPE =
+  regex_must_compile(US"\\n250[\\s\\-]" EARLY_PIPE_FEATURE_NAME "(\\s|\\n|$)", FALSE, TRUE);
+#endif
+}
+
+
+uschar *
+deliver_get_sender_address (uschar * id)
+{
+int rc;
+uschar * new_sender_address,
+       * save_sender_address;
+BOOL save_qr = f.queue_running;
+uschar * spoolname;
+
+/* make spool_open_datafile non-noisy on fail */
+
+f.queue_running = TRUE;
+
+/* Side effect: message_subdir is set for the (possibly split) spool directory */
+
+deliver_datafile = spool_open_datafile(id);
+f.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;
+}
+
+
+
+void
+delivery_re_exec(int exec_type)
+{
+uschar * where;
+
+if (cutthrough.cctx.sock >= 0 && cutthrough.callout_hold_only)
+  {
+  int channel_fd = cutthrough.cctx.sock;
+
+  smtp_peer_options = cutthrough.peer_options;
+  continue_sequence = 0;
+
+#ifdef SUPPORT_TLS
+  if (cutthrough.is_tls)
+    {
+    int pfd[2], pid;
+
+    smtp_peer_options |= OPTION_TLS;
+    sending_ip_address = cutthrough.snd_ip;
+    sending_port = cutthrough.snd_port;
+
+    where = US"socketpair";
+    if (socketpair(AF_UNIX, SOCK_STREAM, 0, pfd) != 0)
+      goto fail;
+
+    where = US"fork";
+    if ((pid = fork()) < 0)
+      goto fail;
+
+    else if (pid == 0)         /* child: fork again to totally disconnect */
+      {
+      if (f.running_in_test_harness) millisleep(100); /* let parent debug out */
+      /* does not return */
+      smtp_proxy_tls(cutthrough.cctx.tls_ctx, big_buffer, big_buffer_size,
+                     pfd, 5*60);
+      }
+
+    DEBUG(D_transport) debug_printf("proxy-proc inter-pid %d\n", pid);
+    close(pfd[0]);
+    waitpid(pid, NULL, 0);
+    (void) close(channel_fd);  /* release the client socket */
+    channel_fd = pfd[1];
+    }
+#endif
+
+  transport_do_pass_socket(cutthrough.transport, cutthrough.host.name,
+    cutthrough.host.address, message_id, channel_fd);
+  }
+else
+  {
+  cancel_cutthrough_connection(TRUE, US"non-continued delivery");
+  (void) child_exec_exim(exec_type, FALSE, NULL, FALSE, 2, US"-Mc", message_id);
+  }
+return;                /* compiler quietening; control does not reach here. */
+
+#ifdef SUPPORT_TLS
+fail:
+  log_write(0,
+    LOG_MAIN | (exec_type == CEE_EXEC_EXIT ? LOG_PANIC : LOG_PANIC_DIE),
+    "delivery re-exec %s failed: %s", where, strerror(errno));
+
+  /* Get here if exec_type == CEE_EXEC_EXIT.
+  Note: this must be _exit(), not exit(). */
+
+  _exit(EX_EXECFAILED);
+#endif
+}
+
 /* vi: aw ai sw=2
 */
 /* End of deliver.c */
 /* 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
index 5c55a45..e5b6551 100644 (file)
@@ -3,6 +3,7 @@
 *************************************************/
 
 /* Copyright (c) University of Cambridge 1995 - 2009 */
 *************************************************/
 
 /* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) The Exim Maintainers 2010 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "exim.h"
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "exim.h"
@@ -38,59 +39,54 @@ directory_make(const uschar *parent, const uschar *name,
                int mode, BOOL panic)
 {
 BOOL use_chown = parent == spool_directory && geteuid() == root_uid;
                int mode, BOOL panic)
 {
 BOOL use_chown = parent == spool_directory && geteuid() == root_uid;
-uschar *p;
-const uschar *slash;
-int c = 1;
+uschar * p;
+uschar c = 1;
 struct stat statbuf;
 struct stat statbuf;
-uschar buffer[256];
+uschar * path;
 
 
-if (parent == NULL)
+if (parent)
   {
   {
-  p = buffer + 1;
-  slash = parent = CUS"";
+  path = string_sprintf("%s%s%s", parent, US"/", name);
+  p = path + Ustrlen(parent);
   }
 else
   {
   }
 else
   {
-  p = buffer + Ustrlen(parent);
-  slash = US"/";
+  path = string_copy(name);
+  p = path + 1;
   }
 
   }
 
-if (!string_format(buffer, sizeof(buffer), "%s%s%s", parent, slash, name))
-  log_write(0, LOG_MAIN|LOG_PANIC_DIE, "name too long in directory_make");
+/* Walk the path creating any missing directories */
 
 
-while (c != 0 && *p != 0)
+while (c && *p)
   {
   {
-  while (*p != 0 && *p != '/') p++;
+  while (*p && *p != '/') p++;
   c = *p;
   c = *p;
-  *p = 0;
-  if (Ustat(buffer, &statbuf) != 0)
+  *p = '\0';
+  if (Ustat(path, &statbuf) != 0)
     {
     {
-    if (mkdir(CS buffer, mode) < 0 && errno != EEXIST)
-      {
-      if (!panic) return FALSE;
-      log_write(0, LOG_MAIN|LOG_PANIC_DIE,
-        "Failed to create directory \"%s\": %s\n", buffer, strerror(errno));
-      }
+    if (mkdir(CS path, mode) < 0 && errno != EEXIST)
+      { p = US"create"; goto bad; }
 
     /* Set the ownership if necessary. */
 
 
     /* Set the ownership if necessary. */
 
-    if (use_chown && Uchown(buffer, exim_uid, exim_gid))
-      {
-      if (!panic) return FALSE;
-      log_write(0, LOG_MAIN|LOG_PANIC_DIE,
-        "Failed to set owner on directory \"%s\": %s\n", buffer, strerror(errno));
-      }
+    if (use_chown && Uchown(path, exim_uid, exim_gid))
+      { p = US"set owner on"; goto bad; }
 
     /* It appears that any mode bits greater than 0777 are ignored by
     mkdir(), at least on some operating systems. Therefore, if the mode
     contains any such bits, do an explicit mode setting. */
 
 
     /* It appears that any mode bits greater than 0777 are ignored by
     mkdir(), at least on some operating systems. Therefore, if the mode
     contains any such bits, do an explicit mode setting. */
 
-    if ((mode & 0777000) != 0) (void)Uchmod(buffer, mode);
+    if (mode & 0777000) (void) Uchmod(path, mode);
     }
   *p++ = c;
   }
 
 return TRUE;
     }
   *p++ = c;
   }
 
 return TRUE;
+
+bad:
+  if (panic) log_write(0, LOG_MAIN|LOG_PANIC_DIE,
+    "Failed to %s directory \"%s\": %s\n", p, path, strerror(errno));
+  return FALSE;
 }
 
 /* End of directory.c */
 }
 
 /* End of directory.c */
dissimilarity index 92%
index 171fccc..5209cd9 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 - 2018 */
+/* 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"
+
+# ifdef MACRO_PREDEF
+#  include "macro_predef.h"
+
+void
+params_dkim(void)
+{
+builtin_macro_create_var(US"_DKIM_SIGN_HEADERS", US PDKIM_DEFAULT_SIGN_HEADERS);
+}
+# else /*!MACRO_PREDEF*/
+
+
+
+pdkim_ctx dkim_sign_ctx;
+
+int dkim_verify_oldpool;
+pdkim_ctx *dkim_verify_ctx = NULL;
+pdkim_signature *dkim_cur_sig = NULL;
+static const uschar * dkim_collect_error = NULL;
+
+#define DKIM_MAX_SIGNATURES 20
+
+
+
+/*XXX the caller only uses the first record if we return multiple.
+*/
+
+uschar *
+dkim_exim_query_dns_txt(uschar * name)
+{
+dns_answer dnsa;
+dns_scan dnss;
+dns_record *rr;
+gstring * g = NULL;
+
+lookup_dnssec_authenticated = NULL;
+if (dns_lookup(&dnsa, name, T_TXT, NULL) != DNS_SUCCEED)
+  return NULL; /*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;
+
+    /* Copy record content to the answer buffer */
+
+    while (rr_offset < rr->size)
+      {
+      uschar len = rr->data[rr_offset++];
+
+      g = string_catn(g, US(rr->data + rr_offset), len);
+      if (g->ptr >= PDKIM_DNS_TXT_MAX_RECLEN)
+       goto bad;
+
+      rr_offset += len;
+      }
+
+    /* check if this looks like a DKIM record */
+    if (Ustrncmp(g->s, "v=", 2) != 0 || strncasecmp(CS g->s, "v=dkim", 6) == 0)
+      {
+      gstring_reset_unused(g);
+      return string_from_gstring(g);
+      }
+
+    if (g) g->ptr = 0;         /* overwrite previous record */
+    }
+
+bad:
+if (g) store_reset(g);
+return NULL;   /*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_MAX_SIGNATURES : 0;
+dkim_collect_error = NULL;
+
+/* 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, data, len)) != PDKIM_OK)
+  {
+  dkim_collect_error = pdkim_errstr(rc);
+  log_write(0, LOG_MAIN,
+            "DKIM: validation error: %.100s", dkim_collect_error);
+  dkim_collect_input = 0;
+  }
+store_pool = dkim_verify_oldpool;
+}
+
+
+/* Log the result for the given signature */
+static void
+dkim_exim_verify_log_sig(pdkim_signature * sig)
+{
+gstring * logmsg;
+uschar * s;
+
+if (!sig) return;
+
+/* Remember the domain for the first pass result */
+
+if (  !dkim_verify_overall
+   && dkim_verify_status
+      ? Ustrcmp(dkim_verify_status, US"pass") == 0
+      : sig->verify_status == PDKIM_VERIFY_PASS
+   )
+  dkim_verify_overall = string_copy(sig->domain);
+
+/* Rewrite the sig result if the ACL overrode it.  This is only
+needed because the DMARC code (sigh) peeks at the dkim sigs.
+Mark the sig for this having been done. */
+
+if (  dkim_verify_status
+   && (  dkim_verify_status != dkim_exim_expand_query(DKIM_VERIFY_STATUS)
+      || dkim_verify_reason != dkim_exim_expand_query(DKIM_VERIFY_REASON)
+   )  )
+  {                    /* overridden by ACL */
+  sig->verify_ext_status = -1;
+  if (Ustrcmp(dkim_verify_status, US"fail") == 0)
+    sig->verify_status = PDKIM_VERIFY_POLICY | PDKIM_VERIFY_FAIL;
+  else if (Ustrcmp(dkim_verify_status, US"invalid") == 0)
+    sig->verify_status = PDKIM_VERIFY_POLICY | PDKIM_VERIFY_INVALID;
+  else if (Ustrcmp(dkim_verify_status, US"none") == 0)
+    sig->verify_status = PDKIM_VERIFY_POLICY | PDKIM_VERIFY_NONE;
+  else if (Ustrcmp(dkim_verify_status, US"pass") == 0)
+    sig->verify_status = PDKIM_VERIFY_POLICY | PDKIM_VERIFY_PASS;
+  else
+    sig->verify_status = -1;
+  }
+
+if (!LOGGING(dkim_verbose)) return;
+
+
+logmsg = string_catn(NULL, US"DKIM: ", 6);
+if (!(s = sig->domain)) s = US"<UNSET>";
+logmsg = string_append(logmsg, 2, "d=", s);
+if (!(s = sig->selector)) s = US"<UNSET>";
+logmsg = string_append(logmsg, 2, " s=", s);
+logmsg = string_fmt_append(logmsg, " c=%s/%s a=%s b=" SIZE_T_FMT,
+         sig->canon_headers == PDKIM_CANON_SIMPLE ? "simple" : "relaxed",
+         sig->canon_body    == PDKIM_CANON_SIMPLE ? "simple" : "relaxed",
+         dkim_sig_to_a_tag(sig),
+         (int)sig->sighash.len > -1 ? sig->sighash.len * 8 : (size_t)0);
+if ((s= sig->identity)) logmsg = string_append(logmsg, 2, " i=", s);
+if (sig->created > 0) logmsg = string_fmt_append(logmsg, " t=%lu",
+                                   sig->created);
+if (sig->expires > 0) logmsg = string_fmt_append(logmsg, " x=%lu",
+                                   sig->expires);
+if (sig->bodylength > -1) logmsg = string_fmt_append(logmsg, " l=%lu",
+                                   sig->bodylength);
+
+if (sig->verify_status & PDKIM_VERIFY_POLICY)
+  logmsg = string_append(logmsg, 5,
+           US" [", dkim_verify_status, US" - ", dkim_verify_reason, US"]");
+else
+  switch (sig->verify_status)
+    {
+    case PDKIM_VERIFY_NONE:
+      logmsg = string_cat(logmsg, US" [not verified]");
+      break;
+
+    case PDKIM_VERIFY_INVALID:
+      logmsg = string_cat(logmsg, US" [invalid - ");
+      switch (sig->verify_ext_status)
+       {
+       case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE:
+         logmsg = string_cat(logmsg,
+                       US"public key record (currently?) unavailable]");
+         break;
+
+       case PDKIM_VERIFY_INVALID_BUFFER_SIZE:
+         logmsg = string_cat(logmsg, US"overlong public key record]");
+         break;
+
+       case PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD:
+       case PDKIM_VERIFY_INVALID_PUBKEY_IMPORT:
+         logmsg = string_cat(logmsg, US"syntax error in public key record]");
+         break;
+
+       case PDKIM_VERIFY_INVALID_SIGNATURE_ERROR:
+         logmsg = string_cat(logmsg, US"signature tag missing or invalid]");
+         break;
+
+       case PDKIM_VERIFY_INVALID_DKIM_VERSION:
+         logmsg = string_cat(logmsg, US"unsupported DKIM version]");
+         break;
+
+       default:
+         logmsg = string_cat(logmsg, US"unspecified problem]");
+       }
+      break;
+
+    case PDKIM_VERIFY_FAIL:
+      logmsg = string_cat(logmsg, US" [verification failed - ");
+      switch (sig->verify_ext_status)
+       {
+       case PDKIM_VERIFY_FAIL_BODY:
+         logmsg = string_cat(logmsg,
+              US"body hash mismatch (body probably modified in transit)]");
+         break;
+
+       case PDKIM_VERIFY_FAIL_MESSAGE:
+         logmsg = string_cat(logmsg,
+               US"signature did not verify "
+               "(headers probably modified in transit)]");
+         break;
+
+       default:
+         logmsg = string_cat(logmsg, US"unspecified reason]");
+       }
+      break;
+
+    case PDKIM_VERIFY_PASS:
+      logmsg = string_cat(logmsg, US" [verification succeeded]");
+      break;
+    }
+
+log_write(0, LOG_MAIN, "%s", string_from_gstring(logmsg));
+return;
+}
+
+
+/* Log a line for each signature */
+void
+dkim_exim_verify_log_all(void)
+{
+pdkim_signature * sig;
+for (sig = dkim_signatures; sig; sig = sig->next) dkim_exim_verify_log_sig(sig);
+}
+
+
+void
+dkim_exim_verify_finish(void)
+{
+pdkim_signature * sig;
+int rc;
+gstring * g = NULL;
+const uschar * errstr = NULL;
+
+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 during validation, disabling signature verification: %.100s",
+      dkim_collect_error);
+  f.dkim_disable_verify = TRUE;
+  goto out;
+  }
+
+dkim_collect_input = 0;
+
+/* Finish DKIM operation and fetch link to signatures chain */
+
+rc = pdkim_feed_finish(dkim_verify_ctx, (pdkim_signature **)&dkim_signatures,
+                       &errstr);
+if (rc != PDKIM_OK && errstr)
+  log_write(0, LOG_MAIN, "DKIM: validation error: %s", errstr);
+
+/* Build a colon-separated list of signing domains (and identities, if present) in dkim_signers */
+
+for (sig = dkim_signatures; sig; sig = sig->next)
+  {
+  if (sig->domain)   g = string_append_listele(g, ':', sig->domain);
+  if (sig->identity) g = string_append_listele(g, ':', sig->identity);
+  }
+
+if (g) dkim_signers = g->s;
+
+out:
+store_pool = dkim_verify_oldpool;
+}
+
+
+
+/* Args as per dkim_exim_acl_run() below */
+static int
+dkim_acl_call(uschar * id, gstring ** res_ptr,
+  uschar ** user_msgptr, uschar ** log_msgptr)
+{
+int rc;
+DEBUG(D_receive)
+  debug_printf("calling acl_smtp_dkim for dkim_cur_signer='%s'\n", id);
+
+rc = acl_check(ACL_WHERE_DKIM, NULL, acl_smtp_dkim, user_msgptr, log_msgptr);
+dkim_exim_verify_log_sig(dkim_cur_sig);
+*res_ptr = string_append_listele(*res_ptr, ':', dkim_verify_status);
+return rc;
+}
+
+
+
+/* For the given identity, run the DKIM ACL once for each matching signature.
+
+Arguments
+ id            Identity to look for in dkim signatures
+ res_ptr       ptr to growable string-list of status results,
+               appended to per ACL run
+ user_msgptr   where to put a user error (for SMTP response)
+ log_msgptr    where to put a logging message (not for SMTP response)
+
+Returns:       OK         access is granted by an ACCEPT verb
+               DISCARD    access is granted by a DISCARD verb
+               FAIL       access is denied
+               FAIL_DROP  access is denied; drop the connection
+               DEFER      can't tell at the moment
+               ERROR      disaster
+*/
+
+int
+dkim_exim_acl_run(uschar * id, gstring ** res_ptr,
+  uschar ** user_msgptr, uschar ** log_msgptr)
+{
+pdkim_signature * sig;
+uschar * cmp_val;
+int rc = -1;
+
+dkim_verify_status = US"none";
+dkim_verify_reason = US"";
+dkim_cur_signer = id;
+
+if (f.dkim_disable_verify || !id || !dkim_verify_ctx)
+  return OK;
+
+/* Find signatures 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
+     )
+    {
+    /* 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
+    dkim_exim_expand_query() below). */
+
+    dkim_cur_sig = sig;
+    dkim_signing_domain = US sig->domain;
+    dkim_signing_selector = US sig->selector;
+    dkim_key_length = sig->sighash.len * 8;
+
+    /* These two return static strings, so we can compare the addr
+    later to see if the ACL overwrote them.  Check that when logging */
+
+    dkim_verify_status = dkim_exim_expand_query(DKIM_VERIFY_STATUS);
+    dkim_verify_reason = dkim_exim_expand_query(DKIM_VERIFY_REASON);
+
+    if ((rc = dkim_acl_call(id, res_ptr, user_msgptr, log_msgptr)) != OK)
+      return rc;
+    }
+
+if (rc != -1)
+  return rc;
+
+/* No matching sig found.  Call ACL once anyway. */
+
+dkim_cur_sig = NULL;
+return dkim_acl_call(id, res_ptr, user_msgptr, log_msgptr);
+}
+
+
+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 || f.dkim_disable_verify || !dkim_cur_sig)
+  return dkim_exim_expand_defaults(what);
+
+switch (what)
+  {
+  case DKIM_ALGO:
+    return dkim_sig_to_a_tag(dkim_cur_sig);
+
+  case DKIM_BODYLENGTH:
+    return dkim_cur_sig->bodylength >= 0
+      ? string_sprintf("%ld", 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("%lu", dkim_cur_sig->created)
+      : dkim_exim_expand_defaults(what);
+
+  case DKIM_EXPIRES:
+    return dkim_cur_sig->expires > 0
+      ? string_sprintf("%lu", 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"";
+  }
+}
+
+
+void
+dkim_exim_sign_init(void)
+{
+int old_pool = store_pool;
+store_pool = POOL_MAIN;
+pdkim_init_context(&dkim_sign_ctx, FALSE, &dkim_exim_query_dns_txt);
+store_pool = old_pool;
+}
+
+
+/* Generate signatures for the given file.
+If a prefix is given, prepend it to the file for the calculations.
+
+Return:
+  NULL:                error; error string written
+  string:      signature header(s), or a zero-length string (not an error)
+*/
+
+gstring *
+dkim_exim_sign(int fd, off_t off, uschar * prefix,
+  struct ob_dkim * dkim, const uschar ** errstr)
+{
+const uschar * dkim_domain = NULL;
+int sep = 0;
+gstring * seen_doms = NULL;
+pdkim_signature * sig;
+gstring * sigbuf;
+int pdkim_rc;
+int sread;
+uschar buf[4096];
+int save_errno = 0;
+int old_pool = store_pool;
+uschar * errwhen;
+const uschar * s;
+
+if (dkim->dot_stuffed)
+  dkim_sign_ctx.flags |= PDKIM_DOT_TERM;
+
+store_pool = POOL_MAIN;
+
+if ((s = dkim->dkim_domain) && !(dkim_domain = expand_cstring(s)))
+  /* expansion error, do not send message. */
+  { errwhen = US"dkim_domain"; goto expand_bad; }
+
+/* Set $dkim_domain expansion variable to each unique domain in list. */
+
+if (dkim_domain)
+  while ((dkim_signing_domain = string_nextinlist(&dkim_domain, &sep, NULL, 0)))
+  {
+  const uschar * dkim_sel;
+  int sel_sep = 0;
+
+  if (dkim_signing_domain[0] == '\0')
+    continue;
+
+  /* Only sign once for each domain, no matter how often it
+  appears in the expanded list. */
+
+  if (match_isinlist(dkim_signing_domain, CUSS &seen_doms,
+      0, NULL, NULL, MCL_STRING, TRUE, NULL) == OK)
+    continue;
+
+  seen_doms = string_append_listele(seen_doms, ':', dkim_signing_domain);
+
+  /* Set $dkim_selector expansion variable to each selector in list,
+  for this domain. */
+
+  if (!(dkim_sel = expand_string(dkim->dkim_selector)))
+    { errwhen = US"dkim_selector"; goto expand_bad; }
+
+  while ((dkim_signing_selector = string_nextinlist(&dkim_sel, &sel_sep,
+         NULL, 0)))
+    {
+    uschar * dkim_canon_expanded;
+    int pdkim_canon;
+    uschar * dkim_sign_headers_expanded = NULL;
+    uschar * dkim_private_key_expanded;
+    uschar * dkim_hash_expanded;
+    uschar * dkim_identity_expanded = NULL;
+    uschar * dkim_timestamps_expanded = NULL;
+    unsigned long tval = 0, xval = 0;
+
+    /* 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. */
+      { errwhen = US"dkim_canon"; goto expand_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;
+      }
+
+    if (  dkim->dkim_sign_headers
+       && !(dkim_sign_headers_expanded = expand_string(dkim->dkim_sign_headers)))
+      { errwhen = US"dkim_sign_header"; goto expand_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)))
+      { errwhen = US"dkim_private_key"; goto expand_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] == '/'
+       && !(dkim_private_key_expanded =
+            expand_file_big_buffer(dkim_private_key_expanded)))
+      goto bad;
+
+    if (!(dkim_hash_expanded = expand_string(dkim->dkim_hash)))
+      { errwhen = US"dkim_hash"; goto expand_bad; }
+
+    if (dkim->dkim_identity)
+      if (!(dkim_identity_expanded = expand_string(dkim->dkim_identity)))
+       { errwhen = US"dkim_identity"; goto expand_bad; }
+      else if (!*dkim_identity_expanded)
+       dkim_identity_expanded = NULL;
+
+    if (dkim->dkim_timestamps)
+      if (!(dkim_timestamps_expanded = expand_string(dkim->dkim_timestamps)))
+       { errwhen = US"dkim_timestamps"; goto expand_bad; }
+      else
+       xval = (tval = (unsigned long) time(NULL))
+             + strtoul(CCS dkim_timestamps_expanded, NULL, 10);
+
+    if (!(sig = pdkim_init_sign(&dkim_sign_ctx, dkim_signing_domain,
+                         dkim_signing_selector,
+                         dkim_private_key_expanded,
+                         dkim_hash_expanded,
+                         errstr
+                         )))
+      goto bad;
+    dkim_private_key_expanded[0] = '\0';
+
+    pdkim_set_optional(sig,
+                       CS dkim_sign_headers_expanded,
+                       CS dkim_identity_expanded,
+                       pdkim_canon,
+                       pdkim_canon, -1, tval, xval);
+
+    if (!pdkim_set_sig_bodyhash(&dkim_sign_ctx, sig))
+      goto bad;
+
+    if (!dkim_sign_ctx.sig)            /* link sig to context chain */
+      dkim_sign_ctx.sig = sig;
+    else
+      {
+      pdkim_signature * n = dkim_sign_ctx.sig;
+      while (n->next) n = n->next;
+      n->next = sig;
+      }
+    }
+  }
+
+/* We may need to carry on with the data-feed even if there are no DKIM sigs to
+produce, if some other package (eg. ARC) is signing. */
+
+if (!dkim_sign_ctx.sig && !dkim->force_bodyhash)
+  {
+  DEBUG(D_transport) debug_printf("DKIM: no viable signatures to use\n");
+  sigbuf = string_get(1);      /* return a zero-len string */
+  }
+else
+  {
+  if (prefix && (pdkim_rc = pdkim_feed(&dkim_sign_ctx, prefix, Ustrlen(prefix))) != PDKIM_OK)
+    goto pk_bad;
+
+  if (lseek(fd, off, SEEK_SET) < 0)
+    sread = -1;
+  else
+    while ((sread = read(fd, &buf, sizeof(buf))) > 0)
+      if ((pdkim_rc = pdkim_feed(&dkim_sign_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;
+    }
+
+  /* Build string of headers, one per signature */
+
+  if ((pdkim_rc = pdkim_feed_finish(&dkim_sign_ctx, &sig, errstr)) != PDKIM_OK)
+    goto pk_bad;
+
+  if (!sig)
+    {
+    DEBUG(D_transport) debug_printf("DKIM: no signatures to use\n");
+    sigbuf = string_get(1);    /* return a zero-len string */
+    }
+  else for (sigbuf = NULL; sig; sig = sig->next)
+    sigbuf = string_append(sigbuf, 2, US sig->signature_header, US"\r\n");
+  }
+
+CLEANUP:
+  (void) string_from_gstring(sigbuf);
+  store_pool = old_pool;
+  errno = save_errno;
+  return sigbuf;
+
+pk_bad:
+  log_write(0, LOG_MAIN|LOG_PANIC,
+               "DKIM: signing failed: %.100s", pdkim_errstr(pdkim_rc));
+bad:
+  sigbuf = NULL;
+  goto CLEANUP;
+
+expand_bad:
+  log_write(0, LOG_MAIN | LOG_PANIC, "failed to expand %s: %s",
+             errwhen, expand_string_message);
+  goto bad;
+}
+
+
+
+
+gstring *
+authres_dkim(gstring * g)
+{
+pdkim_signature * sig;
+int start = 0;         /* compiler quietening */
+
+DEBUG(D_acl) start = g->ptr;
+
+for (sig = dkim_signatures; sig; sig = sig->next)
+  {
+  g = string_catn(g, US";\n\tdkim=", 8);
+
+  if (sig->verify_status & PDKIM_VERIFY_POLICY)
+    g = string_append(g, 5,
+      US"policy (", dkim_verify_status, US" - ", dkim_verify_reason, US")");
+  else switch(sig->verify_status)
+    {
+    case PDKIM_VERIFY_NONE:    g = string_cat(g, US"none"); break;
+    case PDKIM_VERIFY_INVALID:
+      switch (sig->verify_ext_status)
+       {
+       case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE:
+          g = string_cat(g, US"tmperror (pubkey unavailable)\n\t\t"); break;
+        case PDKIM_VERIFY_INVALID_BUFFER_SIZE:
+          g = string_cat(g, US"permerror (overlong public key record)\n\t\t"); break;
+        case PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD:
+        case PDKIM_VERIFY_INVALID_PUBKEY_IMPORT:
+          g = string_cat(g, US"neutral (public key record import problem)\n\t\t");
+          break;
+        case PDKIM_VERIFY_INVALID_SIGNATURE_ERROR:
+          g = string_cat(g, US"neutral (signature tag missing or invalid)\n\t\t");
+          break;
+        case PDKIM_VERIFY_INVALID_DKIM_VERSION:
+          g = string_cat(g, US"neutral (unsupported DKIM version)\n\t\t");
+          break;
+        default:
+          g = string_cat(g, US"permerror (unspecified problem)\n\t\t"); break;
+       }
+      break;
+    case PDKIM_VERIFY_FAIL:
+      switch (sig->verify_ext_status)
+       {
+       case PDKIM_VERIFY_FAIL_BODY:
+          g = string_cat(g,
+           US"fail (body hash mismatch; body probably modified in transit)\n\t\t");
+         break;
+        case PDKIM_VERIFY_FAIL_MESSAGE:
+          g = string_cat(g,
+           US"fail (signature did not verify; headers probably modified in transit)\n\t\t");
+         break;
+        default:
+          g = string_cat(g, US"fail (unspecified reason)\n\t\t");
+         break;
+       }
+      break;
+    case PDKIM_VERIFY_PASS:    g = string_cat(g, US"pass"); break;
+    default:                   g = string_cat(g, US"permerror"); break;
+    }
+  if (sig->domain)   g = string_append(g, 2, US" header.d=", sig->domain);
+  if (sig->identity) g = string_append(g, 2, US" header.i=", sig->identity);
+  if (sig->selector) g = string_append(g, 2, US" header.s=", sig->selector);
+  g = string_append(g, 2, US" header.a=", dkim_sig_to_a_tag(sig));
+  }
+
+DEBUG(D_acl)
+  if (g->ptr == start)
+    debug_printf("DKIM: no authres\n");
+  else
+    debug_printf("DKIM: authres '%.*s'\n", g->ptr - start - 3, g->s + start + 3);
+return g;
+}
+
+
+# endif        /*!MACRO_PREDEF*/
+#endif /*!DISABLE_DKIM*/
index 15a7e60..7b94f22 100644 (file)
@@ -2,16 +2,17 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge, 1995 - 2007 */
+/* Copyright (c) University of Cambridge, 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* 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);
+gstring * dkim_exim_sign(int, off_t, uschar *, struct ob_dkim *, const uschar **);
+void    dkim_exim_verify_init(BOOL);
 void    dkim_exim_verify_feed(uschar *, int);
 void    dkim_exim_verify_finish(void);
 void    dkim_exim_verify_feed(uschar *, int);
 void    dkim_exim_verify_finish(void);
-void    dkim_exim_acl_setup(uschar *);
+void    dkim_exim_verify_log_all(void);
+int     dkim_exim_acl_run(uschar *, gstring **, uschar **, uschar **);
 uschar *dkim_exim_expand_query(int);
 uschar *dkim_exim_expand_query(int);
-uschar *dkim_exim_expand_defaults(int);
 
 #define DKIM_ALGO               1
 #define DKIM_BODYLENGTH         2
 
 #define DKIM_ALGO               1
 #define DKIM_BODYLENGTH         2
diff --git a/src/dkim_transport.c b/src/dkim_transport.c
new file mode 100644 (file)
index 0000000..8ce18c8
--- /dev/null
@@ -0,0 +1,411 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+/* Transport shim for dkim signing */
+
+
+#include "exim.h"
+
+#ifndef DISABLE_DKIM   /* rest of file */
+
+
+static BOOL
+dkt_sign_fail(struct ob_dkim * dkim, int * errp)
+{
+if (dkim->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) )
+      {
+      /* Set errno to something halfway meaningful */
+      *errp = EACCES;
+      log_write(0, LOG_MAIN, "DKIM: message could not be signed,"
+       " and dkim_strict is set. Deferring message delivery.");
+      return FALSE;
+      }
+  }
+return TRUE;
+}
+
+/* Send the file at in_fd down the output fd */
+
+static BOOL
+dkt_send_file(int out_fd, int in_fd, off_t off
+#ifdef OS_SENDFILE
+  , size_t size
+#endif
+  )
+{
+#ifdef OS_SENDFILE
+DEBUG(D_transport) debug_printf("send file fd=%d size=%u\n", out_fd, (unsigned)(size - off));
+#else
+DEBUG(D_transport) debug_printf("send file fd=%d\n", out_fd);
+#endif
+
+/*XXX should implement timeout, like transport_write_block_fd() ? */
+
+#ifdef OS_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.sock != out_fd)
+  {
+  ssize_t copied = 0;
+
+  while(copied >= 0 && off < size)
+    copied = os_sendfile(out_fd, in_fd, &off, size - off);
+  if (copied < 0)
+    return FALSE;
+  }
+else
+
+#endif
+
+  {
+  int sread, wwritten;
+
+  /* Rewind file */
+  if (lseek(in_fd, off, SEEK_SET) < 0) return FALSE;
+
+  /* Send file down the original fd */
+  while((sread = read(in_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.sock == out_fd
+       ? tls_write(tls_out.active.tls_ctx, p, sread, FALSE)
+       : write(out_fd, CS p, sread);
+#else
+      wwritten = write(out_fd, CS p, sread);
+#endif
+      if (wwritten == -1)
+       return FALSE;
+      p += wwritten;
+      sread -= wwritten;
+      }
+    }
+
+  if (sread == -1)
+    return FALSE;
+  }
+
+return TRUE;
+}
+
+
+
+
+/* This function is a wrapper around transport_write_message().
+   It is only called from the smtp transport if DKIM or Domainkeys support
+   is active and no transport filter is to be used.
+
+Arguments:
+  As for transport_write_message() in transort.c, with additional arguments
+  for DKIM.
+
+Returns:       TRUE on success; FALSE (with errno) for any failure
+*/
+
+static BOOL
+dkt_direct(transport_ctx * tctx, struct ob_dkim * dkim,
+  const uschar ** err)
+{
+int save_fd = tctx->u.fd;
+int save_options = tctx->options;
+BOOL save_wireformat = f.spool_file_wireformat;
+uschar * hdrs;
+gstring * dkim_signature;
+int hsize;
+const uschar * errstr;
+BOOL rc;
+
+DEBUG(D_transport) debug_printf("dkim signing direct-mode\n");
+
+/* Get headers in string for signing and transmission.  Do CRLF
+and dotstuffing (but no body nor dot-termination) */
+
+tctx->u.msg = NULL;
+tctx->options = tctx->options & ~(topt_end_dot | topt_use_bdat)
+  | topt_output_string | topt_no_body;
+
+rc = transport_write_message(tctx, 0);
+hdrs = string_from_gstring(tctx->u.msg);
+hsize = tctx->u.msg->ptr;
+
+tctx->u.fd = save_fd;
+tctx->options = save_options;
+if (!rc) return FALSE;
+
+/* Get signatures for headers plus spool data file */
+
+#ifdef EXPERIMENTAL_ARC
+arc_sign_init();
+#endif
+
+/* The dotstuffed status of the datafile depends on whether it was stored
+in wireformat. */
+
+dkim->dot_stuffed = f.spool_file_wireformat;
+if (!(dkim_signature = dkim_exim_sign(deliver_datafile, SPOOL_DATA_START_OFFSET,
+                                   hdrs, dkim, &errstr)))
+  if (!(rc = dkt_sign_fail(dkim, &errno)))
+    {
+    *err = errstr;
+    return FALSE;
+    }
+
+#ifdef EXPERIMENTAL_ARC
+if (dkim->arc_signspec)                        /* Prepend ARC headers */
+  {
+  uschar * e;
+  if (!(dkim_signature = arc_sign(dkim->arc_signspec, dkim_signature, &e)))
+    {
+    *err = e;
+    return FALSE;
+    }
+  }
+#endif
+
+/* Write the signature and headers into the deliver-out-buffer.  This should
+mean they go out in the same packet as the MAIL, RCPT and (first) BDAT commands
+(transport_write_message() sizes the BDAT for the buffered amount) - for short
+messages, the BDAT LAST command.  We want no dotstuffing expansion here, it
+having already been done - but we have to say we want CRLF output format, and
+temporarily set the marker for possible already-CRLF input. */
+
+tctx->options &= ~topt_escape_headers;
+f.spool_file_wireformat = TRUE;
+transport_write_reset(0);
+if (  (  dkim_signature
+      && dkim_signature->ptr > 0
+      && !write_chunk(tctx, dkim_signature->s, dkim_signature->ptr)
+      )
+   || !write_chunk(tctx, hdrs, hsize)
+   )
+  return FALSE;
+
+f.spool_file_wireformat = save_wireformat;
+tctx->options = save_options | topt_no_headers | topt_continuation;
+
+if (!(transport_write_message(tctx, 0)))
+  return FALSE;
+
+tctx->options = save_options;
+return TRUE;
+}
+
+
+/* This function is a wrapper around transport_write_message().
+   It is only called from the smtp transport if DKIM or Domainkeys support
+   is active and a transport filter is to be used.  The function sets up a
+   replacement fd into a -K file, then calls the normal function. This way, the
+   exact bits that exim would have put "on the wire" will end up in the file
+   (except for TLS encapsulation, which is the very very last thing). When we
+   are done signing the file, send the signed message down the original fd (or
+   TLS fd).
+
+Arguments:
+  As for transport_write_message() in transort.c, with additional arguments
+  for DKIM.
+
+Returns:       TRUE on success; FALSE (with errno) for any failure
+*/
+
+static BOOL
+dkt_via_kfile(transport_ctx * tctx, struct ob_dkim * dkim, const uschar ** err)
+{
+int dkim_fd;
+int save_errno = 0;
+BOOL rc;
+uschar * dkim_spool_name;
+gstring * dkim_signature;
+int options, dlen;
+off_t k_file_size;
+const uschar * errstr;
+
+dkim_spool_name = spool_fname(US"input", message_subdir, message_id,
+                   string_sprintf("-%d-K", (int)getpid()));
+
+DEBUG(D_transport) debug_printf("dkim signing via file %s\n", dkim_spool_name);
+
+if ((dkim_fd = Uopen(dkim_spool_name, O_RDWR|O_CREAT|O_TRUNC, SPOOL_MODE)) < 0)
+  {
+  /* Can't create spool file. Ugh. */
+  rc = FALSE;
+  save_errno = errno;
+  *err = string_sprintf("dkim spoolfile create: %s", strerror(errno));
+  goto CLEANUP;
+  }
+
+/* Call transport utility function to write the -K file; does the CRLF expansion
+(but, in the CHUNKING case, neither dot-stuffing nor dot-termination). */
+
+  {
+  int save_fd = tctx->u.fd;
+  tctx->u.fd = dkim_fd;
+  options = tctx->options;
+  tctx->options &= ~topt_use_bdat;
+
+  rc = transport_write_message(tctx, 0);
+
+  tctx->u.fd = save_fd;
+  tctx->options = options;
+  }
+
+/* Save error state. We must clean up before returning. */
+if (!rc)
+  {
+  save_errno = errno;
+  goto CLEANUP;
+  }
+
+#ifdef EXPERIMENTAL_ARC
+arc_sign_init();
+#endif
+
+/* Feed the file to the goats^W DKIM lib.  At this point the dotstuffed
+status of the file depends on the output of transport_write_message() just
+above, which should be the result of the end_dot flag in tctx->options. */
+
+dkim->dot_stuffed = !!(options & topt_end_dot);
+if (!(dkim_signature = dkim_exim_sign(dkim_fd, 0, NULL, dkim, &errstr)))
+  {
+  dlen = 0;
+  if (!(rc = dkt_sign_fail(dkim, &save_errno)))
+    {
+    *err = errstr;
+    goto CLEANUP;
+    }
+  }
+else
+  dlen = dkim_signature->ptr;
+
+#ifdef EXPERIMENTAL_ARC
+if (dkim->arc_signspec)                                /* Prepend ARC headers */
+  {
+  if (!(dkim_signature = arc_sign(dkim->arc_signspec, dkim_signature, USS err)))
+    goto CLEANUP;
+  dlen = dkim_signature->ptr;
+  }
+#endif
+
+#ifndef OS_SENDFILE
+if (options & topt_use_bdat)
+#endif
+  if ((k_file_size = lseek(dkim_fd, 0, SEEK_END)) < 0)
+    {
+    *err = string_sprintf("dkim spoolfile seek: %s", strerror(errno));
+    goto CLEANUP;
+    }
+
+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 (  dlen + k_file_size > DELIVER_OUT_BUFFER_SIZE
+     && dlen > 0)
+    {
+    if (  tctx->chunk_cb(tctx, dlen, 0) != OK
+       || !transport_write_block(tctx,
+                   dkim_signature->s, dlen, FALSE)
+       || tctx->chunk_cb(tctx, 0, tc_reap_prev) != OK
+       )
+      goto err;
+    dlen = 0;
+    }
+
+  /* Send the BDAT command for the entire message, as a single LAST-marked
+  chunk. */
+
+  if (tctx->chunk_cb(tctx, dlen + k_file_size, tc_chunk_last) != OK)
+    goto err;
+  }
+
+if(dlen > 0 && !transport_write_block(tctx, dkim_signature->s, dlen, TRUE))
+  goto err;
+
+if (!dkt_send_file(tctx->u.fd, dkim_fd, 0
+#ifdef OS_SENDFILE
+  , k_file_size
+#endif
+  ))
+  {
+  save_errno = errno;
+  rc = FALSE;
+  }
+
+CLEANUP:
+  /* unlink -K file */
+  if (dkim_fd >= 0) (void)close(dkim_fd);
+  Uunlink(dkim_spool_name);
+  errno = save_errno;
+  return rc;
+
+err:
+  save_errno = errno;
+  rc = FALSE;
+  goto CLEANUP;
+}
+
+
+
+/***************************************************************************************************
+*    External interface to write the message, while signing it with DKIM and/or Domainkeys         *
+***************************************************************************************************/
+
+/* This function is a wrapper around transport_write_message().
+   It is only called from the smtp transport if DKIM or Domainkeys support
+   is compiled in.
+
+Arguments:
+  As for transport_write_message() in transort.c, with additional arguments
+  for DKIM.
+
+Returns:       TRUE on success; FALSE (with errno) for any failure
+*/
+
+BOOL
+dkim_transport_write_message(transport_ctx * tctx,
+  struct ob_dkim * dkim, const uschar ** err)
+{
+/* If we can't sign, just call the original function. */
+
+if (  !(dkim->dkim_private_key && dkim->dkim_domain && dkim->dkim_selector)
+   && !dkim->force_bodyhash)
+  return transport_write_message(tctx, 0);
+
+/* If there is no filter command set up, construct the message and calculate
+a dkim signature of it, send the signature and a reconstructed message. This
+avoids using a temprary file. */
+
+if (  !transport_filter_argv
+   || !*transport_filter_argv
+   || !**transport_filter_argv
+   )
+  return dkt_direct(tctx, dkim, err);
+
+/* Use the transport path to write a file, calculate a dkim signature,
+send the signature and then send the file. */
+
+return dkt_via_kfile(tctx, dkim, err);
+}
+
+#endif /* whole file */
+
+/* vi: aw ai sw=2
+*/
+/* End of dkim_transport.c */
index 769e4d4..f29f7eb 100644 (file)
@@ -12,7 +12,7 @@
 
 #include "exim.h"
 #ifdef EXPERIMENTAL_DMARC
 
 #include "exim.h"
 #ifdef EXPERIMENTAL_DMARC
-# if !defined EXPERIMENTAL_SPF
+# if !defined SUPPORT_SPF
 #  error SPF must also be enabled for DMARC
 # elif defined DISABLE_DKIM
 #  error DKIM must also be enabled for DMARC
 #  error SPF must also be enabled for DMARC
 # elif defined DISABLE_DKIM
 #  error DKIM must also be enabled for DMARC
@@ -28,7 +28,6 @@ OPENDMARC_STATUS_T  libdm_status, action, dmarc_policy;
 OPENDMARC_STATUS_T  da, sa, action;
 BOOL dmarc_abort  = FALSE;
 uschar *dmarc_pass_fail = US"skipped";
 OPENDMARC_STATUS_T  da, sa, action;
 BOOL dmarc_abort  = FALSE;
 uschar *dmarc_pass_fail = US"skipped";
-extern pdkim_signature  *dkim_signatures;
 header_line *from_header   = NULL;
 extern SPF_response_t   *spf_response;
 int dmarc_spf_ares_result  = 0;
 header_line *from_header   = NULL;
 extern SPF_response_t   *spf_response;
 int dmarc_spf_ares_result  = 0;
@@ -44,6 +43,7 @@ typedef struct dmarc_exim_p {
 } dmarc_exim_p;
 
 static dmarc_exim_p dmarc_policy_description[] = {
 } dmarc_exim_p;
 
 static dmarc_exim_p dmarc_policy_description[] = {
+  /* name              value */
   { US"",           DMARC_RECORD_P_UNSPECIFIED },
   { US"none",       DMARC_RECORD_P_NONE },
   { US"quarantine", DMARC_RECORD_P_QUARANTINE },
   { US"",           DMARC_RECORD_P_UNSPECIFIED },
   { US"none",       DMARC_RECORD_P_NONE },
   { US"quarantine", DMARC_RECORD_P_QUARANTINE },
@@ -57,7 +57,7 @@ static dmarc_exim_p dmarc_policy_description[] = {
 static error_block *
 add_to_eblock(error_block *eblock, uschar *t1, uschar *t2)
 {
 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
 if (eblock == NULL)
   eblock = eb;
 else
@@ -78,13 +78,11 @@ return eblock;
    messages on the same SMTP connection (that come from the
    same host with the same HELO string) */
 
    messages on the same SMTP connection (that come from the
    same host with the same HELO string) */
 
-int dmarc_init()
+int
+dmarc_init()
 {
 int *netmask   = NULL;   /* Ignored */
 int is_ipv6    = 0;
 {
 int *netmask   = NULL;   /* Ignored */
 int is_ipv6    = 0;
-char *tld_file = (dmarc_tld_file == NULL) ?
-                "/etc/exim/opendmarc.tlds" :
-                (char *)dmarc_tld_file;
 
 /* Set some sane defaults.  Also clears previous results when
  * multiple messages in one connection. */
 
 /* Set some sane defaults.  Also clears previous results when
  * multiple messages in one connection. */
@@ -93,14 +91,13 @@ dmarc_status       = US"none";
 dmarc_abort        = FALSE;
 dmarc_pass_fail    = US"skipped";
 dmarc_used_domain  = US"";
 dmarc_abort        = FALSE;
 dmarc_pass_fail    = US"skipped";
 dmarc_used_domain  = US"";
-dmarc_ar_header    = NULL;
-dmarc_has_been_checked = FALSE;
+f.dmarc_has_been_checked = FALSE;
 header_from_sender = NULL;
 spf_sender_domain  = NULL;
 spf_human_readable = NULL;
 
 /* ACLs have "control=dmarc_disable_verify" */
 header_from_sender = NULL;
 spf_sender_domain  = NULL;
 spf_human_readable = NULL;
 
 /* ACLs have "control=dmarc_disable_verify" */
-if (dmarc_disable_verify == TRUE)
+if (f.dmarc_disable_verify == TRUE)
   return OK;
 
 (void) memset(&dmarc_ctx, '\0', sizeof dmarc_ctx);
   return OK;
 
 (void) memset(&dmarc_ctx, '\0', sizeof dmarc_ctx);
@@ -112,22 +109,27 @@ if (libdm_status != DMARC_PARSE_OKAY)
                       opendmarc_policy_status_to_str(libdm_status));
   dmarc_abort = TRUE;
   }
                       opendmarc_policy_status_to_str(libdm_status));
   dmarc_abort = TRUE;
   }
-if (dmarc_tld_file == NULL)
+if (!dmarc_tld_file)
+  {
+  DEBUG(D_receive) debug_printf("DMARC: no dmarc_tld_file\n");
   dmarc_abort = TRUE;
   dmarc_abort = TRUE;
-else if (opendmarc_tld_read_file(tld_file, NULL, NULL, NULL))
+  }
+else if (opendmarc_tld_read_file(CS dmarc_tld_file, NULL, NULL, NULL))
   {
   log_write(0, LOG_MAIN|LOG_PANIC, "DMARC failure to load tld list %s: %d",
   {
   log_write(0, LOG_MAIN|LOG_PANIC, "DMARC failure to load tld list %s: %d",
-                      tld_file, errno);
+                      dmarc_tld_file, errno);
   dmarc_abort = TRUE;
   }
   dmarc_abort = TRUE;
   }
-if (sender_host_address == NULL)
+if (!sender_host_address)
+  {
+  DEBUG(D_receive) debug_printf("DMARC: no sender_host_address\n");
   dmarc_abort = TRUE;
   dmarc_abort = TRUE;
+  }
 /* This catches locally originated email and startup errors above. */
 if (!dmarc_abort)
   {
   is_ipv6 = string_is_ip_address(sender_host_address, netmask) == 6;
 /* This catches locally originated email and startup errors above. */
 if (!dmarc_abort)
   {
   is_ipv6 = string_is_ip_address(sender_host_address, netmask) == 6;
-  dmarc_pctx = opendmarc_policy_connect_init(sender_host_address, is_ipv6);
-  if (dmarc_pctx == NULL)
+  if (!(dmarc_pctx = opendmarc_policy_connect_init(sender_host_address, is_ipv6)))
     {
     log_write(0, LOG_MAIN|LOG_PANIC,
       "DMARC failure creating policy context: ip=%s", sender_host_address);
     {
     log_write(0, LOG_MAIN|LOG_PANIC,
       "DMARC failure creating policy context: ip=%s", sender_host_address);
@@ -140,19 +142,71 @@ return OK;
 
 
 /* dmarc_store_data stores the header data so that subsequent
 
 
 /* dmarc_store_data stores the header data so that subsequent
- * dmarc_process can access the data */
+dmarc_process can access the data */
 
 
-int dmarc_store_data(header_line *hdr) {
-  /* No debug output because would change every test debug output */
-  if (dmarc_disable_verify != TRUE)
-    from_header = hdr;
-  return OK;
+int
+dmarc_store_data(header_line *hdr)
+{
+/* No debug output because would change every test debug output */
+if (!f.dmarc_disable_verify)
+  from_header = hdr;
+return OK;
 }
 
 
 }
 
 
+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 (!f.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);
+
+    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 || f.running_in_test_harness) ? " (not really)" : "");
+      if (host_checking || f.running_in_test_harness)
+       continue;
+
+      if (!moan_send_message(recipient, ERRMESS_DMARC_FORENSIC, eblock,
+                           header_list, message_file, NULL))
+       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
 /* 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. */
+context (if any), retrieves the result, sets up expansion
+strings and evaluates the condition outcome. */
 
 int
 dmarc_process()
 
 int
 dmarc_process()
@@ -160,16 +214,13 @@ dmarc_process()
 int sr, origin;             /* used in SPF section */
 int dmarc_spf_result  = 0;  /* stores spf into dmarc conn ctx */
 int tmp_ans, c;
 int sr, origin;             /* used in SPF section */
 int dmarc_spf_result  = 0;  /* stores spf into dmarc conn ctx */
 int tmp_ans, c;
-pdkim_signature *sig  = NULL;
+pdkim_signature * sig = dkim_signatures;
 BOOL has_dmarc_record = TRUE;
 u_char **ruf; /* forensic report addressees, if called for */
 
 /* ACLs have "control=dmarc_disable_verify" */
 BOOL has_dmarc_record = TRUE;
 u_char **ruf; /* forensic report addressees, if called for */
 
 /* ACLs have "control=dmarc_disable_verify" */
-if (dmarc_disable_verify)
-  {
-  dmarc_ar_header = dmarc_auth_results_header(from_header, NULL);
+if (f.dmarc_disable_verify)
   return OK;
   return OK;
-  }
 
 /* Store the header From: sender domain for this part of DMARC.
  * If there is no from_header struct, then it's likely this message
 
 /* Store the header From: sender domain for this part of DMARC.
  * If there is no from_header struct, then it's likely this message
@@ -177,31 +228,34 @@ if (dmarc_disable_verify)
  * the entire DMARC system if we can't find a From: header....or if
  * there was a previous error.
  */
  * the entire DMARC system if we can't find a From: header....or if
  * there was a previous error.
  */
-if (!from_header || dmarc_abort)
+if (!from_header)
+  {
+  DEBUG(D_receive) debug_printf("DMARC: no From: header\n");
   dmarc_abort = TRUE;
   dmarc_abort = TRUE;
-else
+  }
+else if (!dmarc_abort)
   {
   {
-    uschar * errormsg;
-    int dummy, domain;
-    uschar * p;
-    uschar saveend;
-
-    parse_allow_group = TRUE;
-    p = parse_find_address_end(from_header->text, FALSE);
-    saveend = *p; *p = '\0';
-    if ((header_from_sender = parse_extract_address(from_header->text, &errormsg,
-                                &dummy, &dummy, &domain, FALSE)))
-      header_from_sender += domain;
-    *p = saveend;
-
-    /* The opendmarc library extracts the domain from the email address, but
-     * only try to store it if it's not empty.  Otherwise, skip out of DMARC. */
-    if (!header_from_sender || (strcmp( CCS header_from_sender, "") == 0))
-      dmarc_abort = TRUE;
-    libdm_status = dmarc_abort ?
-      DMARC_PARSE_OKAY :
-      opendmarc_policy_store_from_domain(dmarc_pctx, header_from_sender);
-    if (libdm_status != DMARC_PARSE_OKAY)
+  uschar * errormsg;
+  int dummy, domain;
+  uschar * p;
+  uschar saveend;
+
+  f.parse_allow_group = TRUE;
+  p = parse_find_address_end(from_header->text, FALSE);
+  saveend = *p; *p = '\0';
+  if ((header_from_sender = parse_extract_address(from_header->text, &errormsg,
+                             &dummy, &dummy, &domain, FALSE)))
+    header_from_sender += domain;
+  *p = saveend;
+
+  /* The opendmarc library extracts the domain from the email address, but
+   * only try to store it if it's not empty.  Otherwise, skip out of DMARC. */
+  if (!header_from_sender || (strcmp( CCS header_from_sender, "") == 0))
+    dmarc_abort = TRUE;
+  libdm_status = dmarc_abort
+    ? DMARC_PARSE_OKAY
+    : opendmarc_policy_store_from_domain(dmarc_pctx, header_from_sender);
+  if (libdm_status != DMARC_PARSE_OKAY)
     {
     log_write(0, LOG_MAIN|LOG_PANIC,
              "failure to store header From: in DMARC: %s, header was '%s'",
     {
     log_write(0, LOG_MAIN|LOG_PANIC,
              "failure to store header From: in DMARC: %s, header was '%s'",
@@ -214,6 +268,8 @@ else
  * instead do this in the ACLs.  */
 if (!dmarc_abort && !sender_host_authenticated)
   {
  * instead do this in the ACLs.  */
 if (!dmarc_abort && !sender_host_authenticated)
   {
+  uschar * dmarc_domain;
+
   /* Use the envelope sender domain for this part of DMARC */
   spf_sender_domain = expand_string(US"$sender_address_domain");
   if (!spf_response)
   /* Use the envelope sender domain for this part of DMARC */
   spf_sender_domain = expand_string(US"$sender_address_domain");
   if (!spf_response)
@@ -251,7 +307,7 @@ if (!dmarc_abort && !sender_host_authenticated)
                            sr == SPF_RESULT_PERMERROR ? ARES_RESULT_PERMERROR :
                            ARES_RESULT_UNKNOWN;
     origin = DMARC_POLICY_SPF_ORIGIN_MAILFROM;
                            sr == SPF_RESULT_PERMERROR ? ARES_RESULT_PERMERROR :
                            ARES_RESULT_UNKNOWN;
     origin = DMARC_POLICY_SPF_ORIGIN_MAILFROM;
-    spf_human_readable = (uschar *)spf_response->header_comment;
+    spf_human_readable = US spf_response->header_comment;
     DEBUG(D_receive)
       debug_printf("DMARC using SPF sender domain = %s\n", spf_sender_domain);
     }
     DEBUG(D_receive)
       debug_printf("DMARC using SPF sender domain = %s\n", spf_sender_domain);
     }
@@ -268,18 +324,18 @@ if (!dmarc_abort && !sender_host_authenticated)
 
   /* Now we cycle through the dkim signature results and put into
    * the opendmarc context, further building the DMARC reply.  */
 
   /* Now we cycle through the dkim signature results and put into
    * the opendmarc context, further building the DMARC reply.  */
-  sig = dkim_signatures;
   dkim_history_buffer = US"";
   while (sig)
     {
     int dkim_result, dkim_ares_result, vs, ves;
   dkim_history_buffer = US"";
   while (sig)
     {
     int dkim_result, dkim_ares_result, vs, ves;
-    vs  = sig->verify_status;
+
+    vs  = sig->verify_status & ~PDKIM_VERIFY_POLICY;
     ves = sig->verify_ext_status;
     dkim_result = vs == PDKIM_VERIFY_PASS ? DMARC_POLICY_DKIM_OUTCOME_PASS :
                  vs == PDKIM_VERIFY_FAIL ? DMARC_POLICY_DKIM_OUTCOME_FAIL :
                  vs == PDKIM_VERIFY_INVALID ? DMARC_POLICY_DKIM_OUTCOME_TMPFAIL :
                  DMARC_POLICY_DKIM_OUTCOME_NONE;
     ves = sig->verify_ext_status;
     dkim_result = vs == PDKIM_VERIFY_PASS ? DMARC_POLICY_DKIM_OUTCOME_PASS :
                  vs == PDKIM_VERIFY_FAIL ? DMARC_POLICY_DKIM_OUTCOME_FAIL :
                  vs == PDKIM_VERIFY_INVALID ? DMARC_POLICY_DKIM_OUTCOME_TMPFAIL :
                  DMARC_POLICY_DKIM_OUTCOME_NONE;
-    libdm_status = opendmarc_policy_store_dkim(dmarc_pctx, (uschar *)sig->domain,
+    libdm_status = opendmarc_policy_store_dkim(dmarc_pctx, US sig->domain,
                                               dkim_result, US"");
     DEBUG(D_receive)
       debug_printf("DMARC adding DKIM sender domain = %s\n", sig->domain);
                                               dkim_result, US"");
     DEBUG(D_receive)
       debug_printf("DMARC adding DKIM sender domain = %s\n", sig->domain);
@@ -295,7 +351,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 :
       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,
        ARES_RESULT_UNKNOWN :
       ARES_RESULT_UNKNOWN;
     dkim_history_buffer = string_sprintf("%sdkim %s %d\n", dkim_history_buffer,
@@ -340,9 +397,9 @@ if (!dmarc_abort && !sender_host_authenticated)
       }
 
   /* Can't use exim's string manipulation functions so allocate memory
       }
 
   /* Can't use exim's string manipulation functions so allocate memory
-   * for libopendmarc using its max hostname length definition. */
+  for libopendmarc using its max hostname length definition. */
 
 
-  uschar *dmarc_domain = (uschar *)calloc(DMARC_MAXHOSTNAMELEN, sizeof(uschar));
+  dmarc_domain = US calloc(DMARC_MAXHOSTNAMELEN, sizeof(uschar));
   libdm_status = opendmarc_policy_fetch_utilized_domain(dmarc_pctx,
     dmarc_domain, DMARC_MAXHOSTNAMELEN-1);
   dmarc_used_domain = string_copy(dmarc_domain);
   libdm_status = opendmarc_policy_fetch_utilized_domain(dmarc_pctx,
     dmarc_domain, DMARC_MAXHOSTNAMELEN-1);
   dmarc_used_domain = string_copy(dmarc_domain);
@@ -353,8 +410,7 @@ if (!dmarc_abort && !sender_host_authenticated)
       "failure to read domainname used for DMARC lookup: %s",
       opendmarc_policy_status_to_str(libdm_status));
 
       "failure to read domainname used for DMARC lookup: %s",
       opendmarc_policy_status_to_str(libdm_status));
 
-  libdm_status = opendmarc_get_policy_to_enforce(dmarc_pctx);
-  dmarc_policy = libdm_status;
+  dmarc_policy = libdm_status = opendmarc_get_policy_to_enforce(dmarc_pctx);
   switch(libdm_status)
     {
     case DMARC_POLICY_ABSENT:     /* No DMARC record found */
   switch(libdm_status)
     {
     case DMARC_POLICY_ABSENT:     /* No DMARC record found */
@@ -406,13 +462,13 @@ if (!dmarc_abort && !sender_host_authenticated)
     log_write(0, LOG_MAIN|LOG_PANIC, "failure to read DMARC alignment: %s",
                             opendmarc_policy_status_to_str(libdm_status));
 
     log_write(0, LOG_MAIN|LOG_PANIC, "failure to read DMARC alignment: %s",
                             opendmarc_policy_status_to_str(libdm_status));
 
-  if (has_dmarc_record == TRUE)
+  if (has_dmarc_record)
     {
     log_write(0, LOG_MAIN, "DMARC results: spf_domain=%s dmarc_domain=%s "
                           "spf_align=%s dkim_align=%s enforcement='%s'",
                           spf_sender_domain, dmarc_used_domain,
     {
     log_write(0, LOG_MAIN, "DMARC results: spf_domain=%s dmarc_domain=%s "
                           "spf_align=%s dkim_align=%s enforcement='%s'",
                           spf_sender_domain, dmarc_used_domain,
-                          (sa==DMARC_POLICY_SPF_ALIGNMENT_PASS) ?"yes":"no",
-                          (da==DMARC_POLICY_DKIM_ALIGNMENT_PASS)?"yes":"no",
+                          sa==DMARC_POLICY_SPF_ALIGNMENT_PASS  ?"yes":"no",
+                          da==DMARC_POLICY_DKIM_ALIGNMENT_PASS ?"yes":"no",
                           dmarc_status_text);
     history_file_status = dmarc_write_history_file();
     /* Now get the forensic reporting addresses, if any */
                           dmarc_status_text);
     history_file_status = dmarc_write_history_file();
     /* Now get the forensic reporting addresses, if any */
@@ -421,19 +477,16 @@ if (!dmarc_abort && !sender_host_authenticated)
     }
   }
 
     }
   }
 
-/* set some global variables here */
-dmarc_ar_header = dmarc_auth_results_header(from_header, NULL);
-
 /* shut down libopendmarc */
 /* shut down libopendmarc */
-if ( dmarc_pctx != NULL )
+if (dmarc_pctx)
   (void) opendmarc_policy_connect_shutdown(dmarc_pctx);
   (void) opendmarc_policy_connect_shutdown(dmarc_pctx);
-if ( dmarc_disable_verify == FALSE )
+if (!f.dmarc_disable_verify)
   (void) opendmarc_policy_library_shutdown(&dmarc_ctx);
 
 return OK;
 }
 
   (void) opendmarc_policy_library_shutdown(&dmarc_ctx);
 
 return OK;
 }
 
-int
+static int
 dmarc_write_history_file()
 {
 int history_file_fd;
 dmarc_write_history_file()
 {
 int history_file_fd;
@@ -443,15 +496,18 @@ u_char **rua; /* aggregate report addressees */
 uschar *history_buffer = NULL;
 
 if (!dmarc_history_file)
 uschar *history_buffer = NULL;
 
 if (!dmarc_history_file)
+  {
+  DEBUG(D_receive) debug_printf("DMARC history file not set\n");
   return DMARC_HIST_DISABLED;
   return DMARC_HIST_DISABLED;
+  }
 history_file_fd = log_create(dmarc_history_file);
 
 if (history_file_fd < 0)
 history_file_fd = log_create(dmarc_history_file);
 
 if (history_file_fd < 0)
-{
+  {
   log_write(0, LOG_MAIN|LOG_PANIC, "failure to create DMARC history file: %s",
                           dmarc_history_file);
   return DMARC_HIST_FILE_ERR;
   log_write(0, LOG_MAIN|LOG_PANIC, "failure to create DMARC history file: %s",
                           dmarc_history_file);
   return DMARC_HIST_FILE_ERR;
-}
+  }
 
 /* Generate the contents of the history file */
 history_buffer = string_sprintf(
 
 /* Generate the contents of the history file */
 history_buffer = string_sprintf(
@@ -495,8 +551,8 @@ history_buffer = string_sprintf(
 /* Write the contents to the history file */
 DEBUG(D_receive)
   debug_printf("DMARC logging history data for opendmarc reporting%s\n",
 /* Write the contents to the history file */
 DEBUG(D_receive)
   debug_printf("DMARC logging history data for opendmarc reporting%s\n",
-            (host_checking || running_in_test_harness) ? " (not really)" : "");
-if (host_checking || running_in_test_harness)
+            (host_checking || f.running_in_test_harness) ? " (not really)" : "");
+if (host_checking || f.running_in_test_harness)
   {
   DEBUG(D_receive)
     debug_printf("DMARC history data for debugging:\n%s", history_buffer);
   {
   DEBUG(D_receive)
     debug_printf("DMARC history data for debugging:\n%s", history_buffer);
@@ -517,125 +573,40 @@ else
 return DMARC_HIST_OK;
 }
 
 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)
 {
 
 uschar *
 dmarc_exim_expand_query(int what)
 {
-if (dmarc_disable_verify || !dmarc_pctx)
+if (f.dmarc_disable_verify || !dmarc_pctx)
   return dmarc_exim_expand_defaults(what);
 
   return dmarc_exim_expand_defaults(what);
 
-switch(what)
-  {
-  case DMARC_VERIFY_STATUS:
-    return(dmarc_status);
-  default:
-    return US"";
-  }
+if (what == DMARC_VERIFY_STATUS)
+  return dmarc_status;
+return US"";
 }
 
 uschar *
 dmarc_exim_expand_defaults(int what)
 {
 }
 
 uschar *
 dmarc_exim_expand_defaults(int what)
 {
-switch(what)
-  {
-  case DMARC_VERIFY_STATUS:
-    return dmarc_disable_verify ?  US"off" : US"none";
-  default:
-    return US"";
-  }
+if (what == DMARC_VERIFY_STATUS)
+  return f.dmarc_disable_verify ?  US"off" : US"none";
+return US"";
 }
 
 }
 
-uschar *
-dmarc_auth_results_header(header_line *from_header, uschar *hostname)
-{
-uschar *hdr_tmp    = US"";
-
-/* Allow a server hostname to be passed to this function, but is
- * currently unused */
-if (!hostname)
-  hostname = primary_hostname;
-hdr_tmp = string_sprintf("%s %s;", DMARC_AR_HEADER, hostname);
-
-#if 0
-/* I don't think this belongs here, but left it here commented out
- * because it was a lot of work to get working right. */
-if (spf_response != NULL) {
-  uschar *dmarc_ar_spf = US"";
-  int sr               = 0;
-  sr = spf_response->result;
-  dmarc_ar_spf = (sr == SPF_RESULT_NEUTRAL)  ? US"neutral" :
-                (sr == SPF_RESULT_PASS)     ? US"pass" :
-                (sr == SPF_RESULT_FAIL)     ? US"fail" :
-                (sr == SPF_RESULT_SOFTFAIL) ? US"softfail" :
-                US"none";
-  hdr_tmp = string_sprintf("%s spf=%s (%s) smtp.mail=%s;",
-                          hdr_tmp, dmarc_ar_spf_result,
-                          spf_response->header_comment,
-                          expand_string(US"$sender_address") );
-}
-#endif
 
 
-hdr_tmp = string_sprintf("%s dmarc=%s", hdr_tmp, dmarc_pass_fail);
-if (header_from_sender)
-  hdr_tmp = string_sprintf("%s header.from=%s",
-                          hdr_tmp, header_from_sender);
-return hdr_tmp;
+gstring *
+authres_dmarc(gstring * g)
+{
+if (f.dmarc_has_been_checked)
+  {
+  g = string_append(g, 2, US";\n\tdmarc=", dmarc_pass_fail);
+  if (header_from_sender)
+    g = string_append(g, 2, US" header.from=", header_from_sender);
+  }
+return g;
 }
 
 }
 
-# endif /* EXPERIMENTAL_SPF */
+# endif /* SUPPORT_SPF */
 #endif /* EXPERIMENTAL_DMARC */
 /* vi: aw ai sw=2
  */
 #endif /* EXPERIMENTAL_DMARC */
 /* vi: aw ai sw=2
  */
index 63d451c..3a3bc6d 100644 (file)
@@ -12,9 +12,9 @@
 #ifdef EXPERIMENTAL_DMARC
 
 # include "opendmarc/dmarc.h"
 #ifdef EXPERIMENTAL_DMARC
 
 # include "opendmarc/dmarc.h"
-# ifdef EXPERIMENTAL_SPF
+# ifdef SUPPORT_SPF
 #  include "spf2/spf.h"
 #  include "spf2/spf.h"
-# endif /* EXPERIMENTAL_SPF */
+# endif /* SUPPORT_SPF */
 
 /* prototypes */
 int dmarc_init();
 
 /* prototypes */
 int dmarc_init();
@@ -23,8 +23,7 @@ int dmarc_process();
 uschar *dmarc_exim_expand_query(int);
 uschar *dmarc_exim_expand_defaults(int);
 uschar *dmarc_auth_results_header(header_line *,uschar *);
 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 **);
+static int dmarc_write_history_file();
 
 #define DMARC_AR_HEADER        US"Authentication-Results:"
 #define DMARC_VERIFY_STATUS    1
 
 #define DMARC_AR_HEADER        US"Authentication-Results:"
 #define DMARC_VERIFY_STATUS    1
index 6efb88d..0f0b435 100644 (file)
--- a/src/dns.c
+++ b/src/dns.c
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for interfacing with the DNS. */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for interfacing with the DNS. */
 #include "exim.h"
 
 
 #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                *
 
 /*************************************************
 *               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
 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:
 code supports both.
 
 Arguments:
@@ -46,11 +36,10 @@ Returns:      length of returned data, or -1 on error (h_errno set)
 */
 
 static int
 */
 
 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 */
 {
 int len = Ustrlen(domain);
 int asize = size;                  /* Locally modified */
-uschar *endname;
 uschar name[256];
 uschar utilname[256];
 uschar *aptr = answerptr;          /* Locally modified */
 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;
 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. */
 
 
 /* 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)
   {
 
 if (stat(CS utilname, &statbuf) >= 0)
   {
@@ -100,11 +62,10 @@ if (stat(CS utilname, &statbuf) >= 0)
   int infd, outfd, rc;
   uschar *argv[5];
 
   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[0] = utilname;
-  argv[1] = spool_directory;
+  argv[1] = config_main_directory;
   argv[2] = name;
   argv[3] = dns_text_type(type);
   argv[4] = NULL;
   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(). */
     }
 
     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));
   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");
     }
   }
     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" */
 
 
 /* fakens utility not found, or it returned "pass on" */
 
@@ -257,9 +229,9 @@ Returns:     nothing
 */
 
 void
 */
 
 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 */
 uschar *pp = buffer;
 
 /* Handle IPv4 address */
@@ -271,7 +243,7 @@ if (Ustrchr(string, ':') == NULL)
   int i;
   for (i = 0; i < 4; i++)
     {
   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;
     while (ppp > string && ppp[-1] != '.') ppp--;
     Ustrncpy(pp, ppp, p - ppp);
     pp += p - ppp;
@@ -299,10 +271,7 @@ else
     {
     int j;
     for (j = 0; j < 32; j += 4)
     {
     int j;
     for (j = 0; j < 32; j += 4)
-      {
-      sprintf(CS pp, "%x.", (v6[i] >> j) & 15);
-      pp += 2;
-      }
+      pp += sprintf(CS pp, "%x.", (v6[i] >> j) & 15);
     }
   Ustrcpy(pp, "ip6.arpa.");
 
     }
   Ustrcpy(pp, "ip6.arpa.");
 
@@ -333,6 +302,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    *
 *************************************************/
 /*************************************************
 *       Get next DNS record from answer block    *
 *************************************************/
@@ -345,59 +323,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
 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 *
 
 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;
 
 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);
 /* 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)
     {
   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,
     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);
     }
 
   /* 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. */
 
 
   /* 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)
     {
 
   if (reset == RESET_AUTHORITY || reset == RESET_ADDITIONAL)
     {
+    TRACE if (reset == RESET_AUTHORITY)
+      debug_printf("%s: authority\n", __FUNCTION__);
     while (dnss->rrcount-- > 0)
       {
     while (dnss->rrcount-- > 0)
       {
+      TRACE trace = "A-namelen";
       namelen = dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen,
       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 */
       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);
       ? 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. */
 
 /* The variable dnss->aptr is now pointing at the next RR, and dnss->rrcount
 contains the number of RR records left. */
@@ -407,24 +415,67 @@ if (dnss->rrcount-- <= 0) return NULL;
 /* If expanding the RR domain name fails, behave as if no more records
 (something safe). */
 
 /* 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,
 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. */
 
 
 /* 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 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 +487,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.
 
 /* 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
 
 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
 {
 #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
 }
 
 #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
+}
 
 
 
 
 
 
@@ -496,6 +596,15 @@ switch(t)
 *        Cache a failed DNS lookup result        *
 *************************************************/
 
 *        Cache a failed DNS lookup result        *
 *************************************************/
 
+static void
+dns_fail_tag(uschar * buf, const uschar * name, int dns_type)
+{
+res_state resp = os_get_dns_resolver_res();
+sprintf(CS buf, "%.255s-%s-%lx", name, dns_text_type(dns_type),
+  (unsigned long) resp->options);
+}
+
+
 /* We cache failed lookup results so as not to experience timeouts many
 times for the same domain. We need to retain the resolver options because they
 may change. For successful lookups, we rely on resolver and/or name server
 /* We cache failed lookup results so as not to experience timeouts many
 times for the same domain. We need to retain the resolver options because they
 may change. For successful lookups, we rely on resolver and/or name server
@@ -510,30 +619,30 @@ Returns:     the return code
 */
 
 static int
 */
 
 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);
 tree_node *node = store_get_perm(sizeof(tree_node) + 290);
-sprintf(CS node->name, "%.255s-%s-%lx", name, dns_text_type(type),
-  resp->options);
+dns_fail_tag(node->name, name, type);
 node->data.val = rc;
 (void)tree_insertnode(&tree_dns_fails, node);
 return rc;
 }
 
 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-
 /*************************************************
 *              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.
 
 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.
 
+The dns_answer structure is pretty big; enough to hold a max-sized DNS message
+- so best allocated from fast-release memory.  As of writing, all our callers
+use a stack-auto variable.
+
 Arguments:
   dnsa      pointer to dns_answer structure
   name      name to look up
 Arguments:
   dnsa      pointer to dns_answer structure
   name      name to look up
@@ -549,13 +658,12 @@ Returns:    DNS_SUCCEED   successful lookup
 */
 
 int
 */
 
 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;
 {
 #ifndef STAND_ALONE
 int rc = -1;
-uschar *save;
+const uschar *save_domain;
 #endif
 #endif
-res_state resp = os_get_dns_resolver_res();
 
 tree_node *previous;
 uschar node_name[290];
 
 tree_node *previous;
 uschar node_name[290];
@@ -565,21 +673,38 @@ a timeout on one domain doesn't happen time and time again for messages that
 have many addresses in the same domain. We rely on the resolver and name server
 caching for successful lookups. */
 
 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)
+dns_fail_tag(node_name, name, type);
+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),
   {
   DEBUG(D_dns) debug_printf("DNS lookup of %.255s-%s: using cached value %s\n",
     name, dns_text_type(type),
-      (previous->data.val == DNS_NOMATCH)? "DNS_NOMATCH" :
-      (previous->data.val == DNS_NODATA)? "DNS_NODATA" :
-      (previous->data.val == DNS_AGAIN)? "DNS_AGAIN" :
-      (previous->data.val == DNS_FAIL)? "DNS_FAIL" : "??");
+      previous->data.val == DNS_NOMATCH ? "DNS_NOMATCH" :
+      previous->data.val == DNS_NODATA ? "DNS_NODATA" :
+      previous->data.val == DNS_AGAIN ? "DNS_AGAIN" :
+      previous->data.val == DNS_FAIL ? "DNS_FAIL" : "??");
   return previous->data.val;
   }
 
   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);
+    f.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;
 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,29 +722,27 @@ 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)
   {
 
 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)];
 
   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). */
 
 
   /* 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++ != '.');
     }
 
     {
     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,
         dns_text_type(type));
     {
     DEBUG(D_dns)
       debug_printf("DNS name syntax check failed: %s (%s)\n", name,
         dns_text_type(type));
-    host_find_failed_syntax = TRUE;
+    f.host_find_failed_syntax = TRUE;
     return DNS_NOMATCH;
     }
   }
     return DNS_NOMATCH;
     }
   }
@@ -635,75 +758,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. */
 
 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. */
 
   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 = f.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:
   }
 
 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:
 
   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));
+
+    /* 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
+#else   /* For stand-alone tests */
+    return dns_return(name, type, DNS_AGAIN);
+#endif
 
   case NO_RECOVERY:
 
   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:
 
   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:
 
   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",
   }
 
 DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) succeeded\n",
@@ -722,6 +840,8 @@ return DNS_SUCCEED;
 /* Look up the given domain name, using the given type. Follow CNAMEs if
 necessary, but only so many times. There aren't supposed to be CNAME chains in
 the DNS, but you are supposed to cope with them if you find them.
 /* Look up the given domain name, using the given type. Follow CNAMEs if
 necessary, but only so many times. There aren't supposed to be CNAME chains in
 the DNS, but you are supposed to cope with them if you find them.
+By default, follow one CNAME since a resolver has been seen, faced with
+an MX request and a CNAME (to an A) but no MX present, returning the CNAME.
 
 The assumption is made that if the resolver gives back records of the
 requested type *and* a CNAME, we don't need to make another call to look up
 
 The assumption is made that if the resolver gives back records of the
 requested type *and* a CNAME, we don't need to make another call to look up
@@ -732,7 +852,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
 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
 
 Arguments:
   dnsa                  pointer to dns_answer structure
@@ -749,23 +870,31 @@ Returns:                DNS_SUCCEED   successful lookup
 */
 
 int
 */
 
 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;
 {
 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... */
+/* By default, assume the resolver follows CNAME chains (and returns NODATA for
+an unterminated one). If it also does that for a CNAME loop, fine; if it returns
+a CNAME (maybe the last?) whine about it.  However, retain the coding for dumb
+resolvers hiding behind a config variable. Loop to follow CNAME chains so far,
+but no further...  The testsuite tests the latter case, mostly assuming that the
+former will work. */
 
 
-for (i = 0; i < 10; i++)
+for (i = 0; i <= dns_cname_loops; i++)
   {
   {
-  uschar data[256];
+  uschar * data;
   dns_record *rr, cname_rr, type_rr;
   dns_scan dnss;
   dns_record *rr, cname_rr, type_rr;
   dns_scan dnss;
-  int datalen, rc;
+  int rc;
 
   /* DNS lookup failures get passed straight back. */
 
 
   /* 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
 
   /* 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 +904,61 @@ for (i = 0; i < 10; i++)
 
   cname_rr.data = type_rr.data = NULL;
   for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS);
 
   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;
       }
     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. */
 
 
   /* 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 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 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 (dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen,
+      cname_rr.data, (DN_EXPAND_ARG4_TYPE)data, 256) < 0)
+    return DNS_FAIL;
   name = data;
 
   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 */
 
   DEBUG(D_dns) debug_printf("CNAME found: change to %s\n", name);
   }       /* Loop back to do another lookup */
 
@@ -839,7 +978,7 @@ return DNS_FAIL;
 *    Do a DNS lookup and handle virtual types   *
 ************************************************/
 
 *    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().
 
 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 +997,178 @@ Returns:                DNS_SUCCEED   successful lookup
 */
 
 int
 */
 
 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;
+    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 = sizeof(dnsa->answer);
+
+      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 */
   }
 
 /* Control should never reach here */
@@ -1037,224 +1178,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       *
 *************************************************/
 
 
 
 /*************************************************
 *          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
 
 
 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 *
 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)
   {
 
 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
 
   }
 
 #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
   {
 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;
 }
 
   }
 #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 */
 /* vi: aw ai sw=2
 */
 /* End of dns.c */
index c2d8668..b1bd836 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -19,9 +19,7 @@ all described in src/EDITME. */
 lookup_info **lookup_list;
 int lookup_list_count = 0;
 
 lookup_info **lookup_list;
 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. */
 
 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,111 +51,139 @@ set to NULL for those that are not compiled into the binary. */
 #include "auths/spa.h"
 #endif
 
 #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 */
 
 #ifdef AUTH_CRAM_MD5
   {
 auth_info auths_available[] = {
 
 /* Checking by an expansion condition on plain text */
 
 #ifdef AUTH_CRAM_MD5
   {
-  US"cram_md5",                              /* lookup name */
-  auth_cram_md5_options,
-  &auth_cram_md5_options_count,
-  &auth_cram_md5_option_defaults,
-  sizeof(auth_cram_md5_options_block),
-  auth_cram_md5_init,                        /* init function */
-  auth_cram_md5_server,                      /* server function */
-  auth_cram_md5_client,                      /* client function */
-  NULL                                       /* diagnostic function */
+  .driver_name =       US"cram_md5",                              /* lookup name */
+  .options =           auth_cram_md5_options,
+  .options_count =     &auth_cram_md5_options_count,
+  .options_block =     &auth_cram_md5_option_defaults,
+  .options_len =       sizeof(auth_cram_md5_options_block),
+  .init =              auth_cram_md5_init,
+  .servercode =                auth_cram_md5_server,
+  .clientcode =                auth_cram_md5_client,
+  .version_report =    NULL
   },
 #endif
 
 #ifdef AUTH_CYRUS_SASL
   {
   },
 #endif
 
 #ifdef AUTH_CYRUS_SASL
   {
-  US"cyrus_sasl",           /* lookup name */
-  auth_cyrus_sasl_options,
-  &auth_cyrus_sasl_options_count,
-  &auth_cyrus_sasl_option_defaults,
-  sizeof(auth_cyrus_sasl_options_block),
-  auth_cyrus_sasl_init,                      /* init function */
-  auth_cyrus_sasl_server,                    /* server function */
-  NULL,                                      /* client function */
-  auth_cyrus_sasl_version_report             /* diagnostic function */
+  .driver_name =       US"cyrus_sasl",
+  .options =           auth_cyrus_sasl_options,
+  .options_count =     &auth_cyrus_sasl_options_count,
+  .options_block =     &auth_cyrus_sasl_option_defaults,
+  .options_len =       sizeof(auth_cyrus_sasl_options_block),
+  .init =              auth_cyrus_sasl_init,
+  .servercode =                auth_cyrus_sasl_server,
+  .clientcode =                NULL,
+  .version_report =    auth_cyrus_sasl_version_report
   },
 #endif
 
 #ifdef AUTH_DOVECOT
   {
   },
 #endif
 
 #ifdef AUTH_DOVECOT
   {
-  US"dovecot",                                /* lookup name */
-  auth_dovecot_options,
-  &auth_dovecot_options_count,
-  &auth_dovecot_option_defaults,
-  sizeof(auth_dovecot_options_block),
-  auth_dovecot_init,                          /* init function */
-  auth_dovecot_server,                        /* server function */
-  NULL,                                       /* client function */
-  NULL                                        /* diagnostic function */
+  .driver_name =       US"dovecot",
+  .options =           auth_dovecot_options,
+  .options_count =     &auth_dovecot_options_count,
+  .options_block =     &auth_dovecot_option_defaults,
+  .options_len =       sizeof(auth_dovecot_options_block),
+  .init =              auth_dovecot_init,
+  .servercode =                auth_dovecot_server,
+  .clientcode =                NULL,
+  .version_report =    NULL
   },
 #endif
 
 #ifdef AUTH_GSASL
   {
   },
 #endif
 
 #ifdef AUTH_GSASL
   {
-  US"gsasl",                                  /* lookup name */
-  auth_gsasl_options,
-  &auth_gsasl_options_count,
-  &auth_gsasl_option_defaults,
-  sizeof(auth_gsasl_options_block),
-  auth_gsasl_init,                            /* init function */
-  auth_gsasl_server,                          /* server function */
-  NULL,                                       /* client function */
-  auth_gsasl_version_report                   /* diagnostic function */
+  .driver_name =       US"gsasl",
+  .options =           auth_gsasl_options,
+  .options_count =     &auth_gsasl_options_count,
+  .options_block =     &auth_gsasl_option_defaults,
+  .options_len =       sizeof(auth_gsasl_options_block),
+  .init =              auth_gsasl_init,
+  .servercode =                auth_gsasl_server,
+  .clientcode =                NULL,
+  .version_report =    auth_gsasl_version_report
   },
 #endif
 
 #ifdef AUTH_HEIMDAL_GSSAPI
   {
   },
 #endif
 
 #ifdef AUTH_HEIMDAL_GSSAPI
   {
-  US"heimdal_gssapi",                         /* lookup name */
-  auth_heimdal_gssapi_options,
-  &auth_heimdal_gssapi_options_count,
-  &auth_heimdal_gssapi_option_defaults,
-  sizeof(auth_heimdal_gssapi_options_block),
-  auth_heimdal_gssapi_init,                   /* init function */
-  auth_heimdal_gssapi_server,                 /* server function */
-  NULL,                                       /* client function */
-  auth_heimdal_gssapi_version_report          /* diagnostic function */
+  .driver_name =       US"heimdal_gssapi",
+  .options =           auth_heimdal_gssapi_options,
+  .options_count =     &auth_heimdal_gssapi_options_count,
+  .options_block =     &auth_heimdal_gssapi_option_defaults,
+  .options_len =       sizeof(auth_heimdal_gssapi_options_block),
+  .init =              auth_heimdal_gssapi_init,
+  .servercode =                auth_heimdal_gssapi_server,
+  .clientcode =                NULL,
+  .version_report =    auth_heimdal_gssapi_version_report
   },
 #endif
 
 #ifdef AUTH_PLAINTEXT
   {
   },
 #endif
 
 #ifdef AUTH_PLAINTEXT
   {
-  US"plaintext",                             /* lookup name */
-  auth_plaintext_options,
-  &auth_plaintext_options_count,
-  &auth_plaintext_option_defaults,
-  sizeof(auth_plaintext_options_block),
-  auth_plaintext_init,                       /* init function */
-  auth_plaintext_server,                     /* server function */
-  auth_plaintext_client,                     /* client function */
-  NULL                                       /* diagnostic function */
+  .driver_name =       US"plaintext",
+  .options =           auth_plaintext_options,
+  .options_count =     &auth_plaintext_options_count,
+  .options_block =     &auth_plaintext_option_defaults,
+  .options_len =       sizeof(auth_plaintext_options_block),
+  .init =              auth_plaintext_init,
+  .servercode =                auth_plaintext_server,
+  .clientcode =                auth_plaintext_client,
+  .version_report =    NULL
   },
 #endif
 
 #ifdef AUTH_SPA
   {
   },
 #endif
 
 #ifdef AUTH_SPA
   {
-  US"spa",                                   /* lookup name */
-  auth_spa_options,
-  &auth_spa_options_count,
-  &auth_spa_option_defaults,
-  sizeof(auth_spa_options_block),
-  auth_spa_init,                             /* init function */
-  auth_spa_server,                           /* server function */
-  auth_spa_client,                           /* client function */
-  NULL                                       /* diagnostic function */
+  .driver_name =       US"spa",
+  .options =           auth_spa_options,
+  .options_count =     &auth_spa_options_count,
+  .options_block =     &auth_spa_option_defaults,
+  .options_len =       sizeof(auth_spa_options_block),
+  .init =              auth_spa_init,
+  .servercode =                auth_spa_server,
+  .clientcode =                auth_spa_client,
+  .version_report =    NULL
+  },
+#endif
+
+#ifdef AUTH_TLS
+  {
+  .driver_name =       US"tls",
+  .options =           auth_tls_options,
+  .options_count =     &auth_tls_options_count,
+  .options_block =     &auth_tls_option_defaults,
+  .options_len =       sizeof(auth_tls_options_block),
+  .init =              auth_tls_init,
+  .servercode =                auth_tls_server,
+  .clientcode =                NULL,
+  .version_report =    NULL
   },
 #endif
 
   },
 #endif
 
-{ US"", NULL, NULL, NULL, 0, NULL, NULL, NULL, NULL  }
+  { .driver_name = US"" }              /* end marker */
 };
 
 };
 
+void
+auth_show_supported(FILE * f)
+{
+auth_info * ai;
+fprintf(f, "Authenticators:");
+for (ai = auths_available; ai->driver_name[0]; ai++)
+               fprintf(f, " %s", ai->driver_name);
+fprintf(f, "\n");
+}
+
 
 /* Tables of information about which routers and transports are included in the
 exim binary. */
 
 /* Tables of information about which routers and transports are included in the
 exim binary. */
@@ -210,6 +236,10 @@ exim binary. */
 #include "transports/pipe.h"
 #endif
 
 #include "transports/pipe.h"
 #endif
 
+#ifdef EXPERIMENTAL_QUEUEFILE
+#include "transports/queuefile.h"
+#endif
+
 #ifdef TRANSPORT_SMTP
 #include "transports/smtp.h"
 #endif
 #ifdef TRANSPORT_SMTP
 #include "transports/smtp.h"
 #endif
@@ -220,174 +250,237 @@ exim binary. */
 router_info routers_available[] = {
 #ifdef ROUTER_ACCEPT
   {
 router_info routers_available[] = {
 #ifdef ROUTER_ACCEPT
   {
-  US"accept",
-  accept_router_options,
-  &accept_router_options_count,
-  &accept_router_option_defaults,
-  sizeof(accept_router_options_block),
-  accept_router_init,
-  accept_router_entry,
-  NULL,     /* no tidyup entry */
-  ri_yestransport
+  .driver_name =       US"accept",
+  .options =           accept_router_options,
+  .options_count =     &accept_router_options_count,
+  .options_block =     &accept_router_option_defaults,
+  .options_len =       sizeof(accept_router_options_block),
+  .init =              accept_router_init,
+  .code =              accept_router_entry,
+  .tidyup =            NULL,     /* no tidyup entry */
+  .ri_flags =          ri_yestransport
   },
 #endif
 #ifdef ROUTER_DNSLOOKUP
   {
   },
 #endif
 #ifdef ROUTER_DNSLOOKUP
   {
-  US"dnslookup",
-  dnslookup_router_options,
-  &dnslookup_router_options_count,
-  &dnslookup_router_option_defaults,
-  sizeof(dnslookup_router_options_block),
-  dnslookup_router_init,
-  dnslookup_router_entry,
-  NULL,     /* no tidyup entry */
-  ri_yestransport
+  .driver_name =       US"dnslookup",
+  .options =           dnslookup_router_options,
+  .options_count =     &dnslookup_router_options_count,
+  .options_block =     &dnslookup_router_option_defaults,
+  .options_len =       sizeof(dnslookup_router_options_block),
+  .init =              dnslookup_router_init,
+  .code =              dnslookup_router_entry,
+  .tidyup =            NULL,     /* no tidyup entry */
+  .ri_flags =          ri_yestransport
   },
 #endif
 #ifdef ROUTER_IPLITERAL
   {
   },
 #endif
 #ifdef ROUTER_IPLITERAL
   {
-  US"ipliteral",
-  ipliteral_router_options,
-  &ipliteral_router_options_count,
-  &ipliteral_router_option_defaults,
-  sizeof(ipliteral_router_options_block),
-  ipliteral_router_init,
-  ipliteral_router_entry,
-  NULL,     /* no tidyup entry */
-  ri_yestransport
+  .driver_name =       US"ipliteral",
+  .options =           ipliteral_router_options,
+  .options_count =     &ipliteral_router_options_count,
+  .options_block =     &ipliteral_router_option_defaults,
+  .options_len =       sizeof(ipliteral_router_options_block),
+  .init =              ipliteral_router_init,
+  .code =              ipliteral_router_entry,
+  .tidyup =            NULL,     /* no tidyup entry */
+  .ri_flags =          ri_yestransport
   },
 #endif
 #ifdef ROUTER_IPLOOKUP
   {
   },
 #endif
 #ifdef ROUTER_IPLOOKUP
   {
-  US"iplookup",
-  iplookup_router_options,
-  &iplookup_router_options_count,
-  &iplookup_router_option_defaults,
-  sizeof(iplookup_router_options_block),
-  iplookup_router_init,
-  iplookup_router_entry,
-  NULL,     /* no tidyup entry */
-  ri_notransport
+  .driver_name =       US"iplookup",
+  .options =           iplookup_router_options,
+  .options_count =     &iplookup_router_options_count,
+  .options_block =     &iplookup_router_option_defaults,
+  .options_len =       sizeof(iplookup_router_options_block),
+  .init =              iplookup_router_init,
+  .code =              iplookup_router_entry,
+  .tidyup =            NULL,     /* no tidyup entry */
+  .ri_flags =          ri_notransport
   },
 #endif
 #ifdef ROUTER_MANUALROUTE
   {
   },
 #endif
 #ifdef ROUTER_MANUALROUTE
   {
-  US"manualroute",
-  manualroute_router_options,
-  &manualroute_router_options_count,
-  &manualroute_router_option_defaults,
-  sizeof(manualroute_router_options_block),
-  manualroute_router_init,
-  manualroute_router_entry,
-  NULL,     /* no tidyup entry */
-  0
+  .driver_name =       US"manualroute",
+  .options =           manualroute_router_options,
+  .options_count =     &manualroute_router_options_count,
+  .options_block =     &manualroute_router_option_defaults,
+  .options_len =       sizeof(manualroute_router_options_block),
+  .init =              manualroute_router_init,
+  .code =              manualroute_router_entry,
+  .tidyup =            NULL,     /* no tidyup entry */
+  .ri_flags =          0
   },
 #endif
 #ifdef ROUTER_QUERYPROGRAM
   {
   },
 #endif
 #ifdef ROUTER_QUERYPROGRAM
   {
-  US"queryprogram",
-  queryprogram_router_options,
-  &queryprogram_router_options_count,
-  &queryprogram_router_option_defaults,
-  sizeof(queryprogram_router_options_block),
-  queryprogram_router_init,
-  queryprogram_router_entry,
-  NULL,     /* no tidyup entry */
-  0
+  .driver_name =       US"queryprogram",
+  .options =           queryprogram_router_options,
+  .options_count =     &queryprogram_router_options_count,
+  .options_block =     &queryprogram_router_option_defaults,
+  .options_len =       sizeof(queryprogram_router_options_block),
+  .init =              queryprogram_router_init,
+  .code =              queryprogram_router_entry,
+  .tidyup =            NULL,     /* no tidyup entry */
+  .ri_flags =          0
   },
 #endif
 #ifdef ROUTER_REDIRECT
   {
   },
 #endif
 #ifdef ROUTER_REDIRECT
   {
-  US"redirect",
-  redirect_router_options,
-  &redirect_router_options_count,
-  &redirect_router_option_defaults,
-  sizeof(redirect_router_options_block),
-  redirect_router_init,
-  redirect_router_entry,
-  NULL,     /* no tidyup entry */
-  ri_notransport
+  .driver_name =       US"redirect",
+  .options =           redirect_router_options,
+  .options_count =     &redirect_router_options_count,
+  .options_block =     &redirect_router_option_defaults,
+  .options_len =       sizeof(redirect_router_options_block),
+  .init =              redirect_router_init,
+  .code =              redirect_router_entry,
+  .tidyup =            NULL,     /* no tidyup entry */
+  .ri_flags =          ri_notransport
   },
 #endif
   },
 #endif
-{ US"", NULL, NULL, NULL, 0, NULL, NULL, NULL, 0 }
+  { US"" }
 };
 
 
 };
 
 
+void
+route_show_supported(FILE * f)
+{
+router_info * rr;
+fprintf(f, "Routers:");
+for (rr = routers_available; rr->driver_name[0]; rr++)
+               fprintf(f, " %s", rr->driver_name);
+fprintf(f, "\n");
+}
+
+
+
 
 transport_info transports_available[] = {
 #ifdef TRANSPORT_APPENDFILE
   {
 
 transport_info transports_available[] = {
 #ifdef TRANSPORT_APPENDFILE
   {
-  US"appendfile",                              /* driver name */
-  appendfile_transport_options,                /* local options table */
-  &appendfile_transport_options_count,         /* number of entries */
-  &appendfile_transport_option_defaults,       /* private options defaults */
-  sizeof(appendfile_transport_options_block),  /* size of private block */
-  appendfile_transport_init,                   /* init entry point */
-  appendfile_transport_entry,                  /* main entry point */
-  NULL,                                        /* no tidyup entry */
-  NULL,                                        /* no closedown entry */
-  TRUE,                                        /* local flag */
+  .driver_name =       US"appendfile",
+  .options =           appendfile_transport_options,
+  .options_count =     &appendfile_transport_options_count,
+  .options_block =     &appendfile_transport_option_defaults,       /* private options defaults */
+  .options_len =       sizeof(appendfile_transport_options_block),
+  .init =              appendfile_transport_init,
+  .code =              appendfile_transport_entry,
+  .tidyup =            NULL,
+  .closedown =         NULL,
+  .local =             TRUE
   },
 #endif
 #ifdef TRANSPORT_AUTOREPLY
   {
   },
 #endif
 #ifdef TRANSPORT_AUTOREPLY
   {
-  US"autoreply",                               /* driver name */
-  autoreply_transport_options,                 /* local options table */
-  &autoreply_transport_options_count,          /* number of entries */
-  &autoreply_transport_option_defaults,        /* private options defaults */
-  sizeof(autoreply_transport_options_block),   /* size of private block */
-  autoreply_transport_init,                    /* init entry point */
-  autoreply_transport_entry,                   /* main entry point */
-  NULL,                                        /* no tidyup entry */
-  NULL,                                        /* no closedown entry */
-  TRUE                                         /* local flag */
+  .driver_name =       US"autoreply",
+  .options =           autoreply_transport_options,
+  .options_count =     &autoreply_transport_options_count,
+  .options_block =     &autoreply_transport_option_defaults,
+  .options_len =       sizeof(autoreply_transport_options_block),
+  .init =              autoreply_transport_init,
+  .code =              autoreply_transport_entry,
+  .tidyup =            NULL,
+  .closedown =         NULL,
+  .local =             TRUE
   },
 #endif
 #ifdef TRANSPORT_LMTP
   {
   },
 #endif
 #ifdef TRANSPORT_LMTP
   {
-  US"lmtp",                                    /* driver name */
-  lmtp_transport_options,                      /* local options table */
-  &lmtp_transport_options_count,               /* number of entries */
-  &lmtp_transport_option_defaults,             /* private options defaults */
-  sizeof(lmtp_transport_options_block),        /* size of private block */
-  lmtp_transport_init,                         /* init entry point */
-  lmtp_transport_entry,                        /* main entry point */
-  NULL,                                        /* no tidyup entry */
-  NULL,                                        /* no closedown entry */
-  TRUE                                         /* local flag */
+  .driver_name =       US"lmtp",
+  .options =           lmtp_transport_options,
+  .options_count =     &lmtp_transport_options_count,
+  .options_block =     &lmtp_transport_option_defaults,
+  .options_len =       sizeof(lmtp_transport_options_block),
+  .init =              lmtp_transport_init,
+  .code =              lmtp_transport_entry,
+  .tidyup =            NULL,
+  .closedown =         NULL,
+  .local =             TRUE
   },
 #endif
 #ifdef TRANSPORT_PIPE
   {
   },
 #endif
 #ifdef TRANSPORT_PIPE
   {
-  US"pipe",                                    /* driver name */
-  pipe_transport_options,                      /* local options table */
-  &pipe_transport_options_count,               /* number of entries */
-  &pipe_transport_option_defaults,             /* private options defaults */
-  sizeof(pipe_transport_options_block),        /* size of private block */
-  pipe_transport_init,                         /* init entry point */
-  pipe_transport_entry,                        /* main entry point */
-  NULL,                                        /* no tidyup entry */
-  NULL,                                        /* no closedown entry */
-  TRUE                                         /* local flag */
+  .driver_name =       US"pipe",
+  .options =           pipe_transport_options,
+  .options_count =     &pipe_transport_options_count,
+  .options_block =     &pipe_transport_option_defaults,
+  .options_len =       sizeof(pipe_transport_options_block),
+  .init =              pipe_transport_init,
+  .code =              pipe_transport_entry,
+  .tidyup =            NULL,
+  .closedown =         NULL,
+  .local =             TRUE
+  },
+#endif
+#ifdef EXPERIMENTAL_QUEUEFILE
+  {
+  .driver_name =       US"queuefile",
+  .options =           queuefile_transport_options,
+  .options_count =     &queuefile_transport_options_count,
+  .options_block =     &queuefile_transport_option_defaults,
+  .options_len =       sizeof(queuefile_transport_options_block),
+  .init =              queuefile_transport_init,
+  .code =              queuefile_transport_entry,
+  .tidyup =            NULL,
+  .closedown =         NULL,
+  .local =             TRUE
   },
 #endif
 #ifdef TRANSPORT_SMTP
   {
   },
 #endif
 #ifdef TRANSPORT_SMTP
   {
-  US"smtp",                                    /* driver name */
-  smtp_transport_options,                      /* local options table */
-  &smtp_transport_options_count,               /* number of entries */
-  &smtp_transport_option_defaults,             /* private options defaults */
-  sizeof(smtp_transport_options_block),        /* size of private block */
-  smtp_transport_init,                         /* init entry point */
-  smtp_transport_entry,                        /* main entry point */
-  NULL,                                        /* no tidyup entry */
-  smtp_transport_closedown,                    /* close down passed channel */
-  FALSE                                        /* local flag */
+  .driver_name =       US"smtp",
+  .options =           smtp_transport_options,
+  .options_count =     &smtp_transport_options_count,
+  .options_block =     &smtp_transport_option_defaults,
+  .options_len =       sizeof(smtp_transport_options_block),
+  .init =              smtp_transport_init,
+  .code =              smtp_transport_entry,
+  .tidyup =            NULL,
+  .closedown =         smtp_transport_closedown,
+  .local =             FALSE
   },
 #endif
   },
 #endif
-{ US"", NULL, NULL, NULL, 0, NULL, NULL, NULL, NULL, FALSE }
+  { US"" }
 };
 
 };
 
+void
+transport_show_supported(FILE * f)
+{
+fprintf(f, "Transports:");
+#ifdef TRANSPORT_APPENDFILE
+  fprintf(f, " appendfile");
+  #ifdef SUPPORT_MAILDIR
+    fprintf(f, "/maildir");    /* damn these subclasses */
+  #endif
+  #ifdef SUPPORT_MAILSTORE
+    fprintf(f, "/mailstore");
+  #endif
+  #ifdef SUPPORT_MBX
+    fprintf(f, "/mbx");
+  #endif
+#endif
+#ifdef TRANSPORT_AUTOREPLY
+  fprintf(f, " autoreply");
+#endif
+#ifdef TRANSPORT_LMTP
+  fprintf(f, " lmtp");
+#endif
+#ifdef TRANSPORT_PIPE
+  fprintf(f, " pipe");
+#endif
+#ifdef EXPERIMENTAL_QUEUEFILE
+  fprintf(f, " queuefile");
+#endif
+#ifdef TRANSPORT_SMTP
+  fprintf(f, " smtp");
+#endif
+fprintf(f, "\n");
+}
+
+
+#ifndef MACRO_PREDEF
+
 struct lookupmodulestr
 {
   void *dl;
 struct lookupmodulestr
 {
   void *dl;
@@ -397,37 +490,42 @@ struct lookupmodulestr
 
 static struct lookupmodulestr *lookupmodules = NULL;
 
 
 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 */
 }
 
 /* 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 +533,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  */
 
  * 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
 #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
 #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
 #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
 #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
 #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
 #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
 #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
 #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
 #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
 #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
 #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
 #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
 #endif
-#if defined(LOOKUP_DSEARCH) && LOOKUP_DSEARCH!=2
-extern lookup_module_info dsearch_lookup_module_info;
+#if defined(SUPPORT_SPF)
+extern lookup_module_info spf_lookup_module_info;
 #endif
 #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
 #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
 #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
 
 #endif
 
-void init_lookup_list(void)
+
+void
+init_lookup_list(void)
 {
 #ifdef LOOKUP_MODULE_DIR
   DIR *dd;
 {
 #ifdef LOOKUP_MODULE_DIR
   DIR *dd;
@@ -499,12 +602,12 @@ void init_lookup_list(void)
   int moduleerrors = 0;
 #endif
   struct lookupmodulestr *p;
   int moduleerrors = 0;
 #endif
   struct lookupmodulestr *p;
-  const pcre *regex_islookupmod = regex_must_compile(
-      US"\\." DYNLIB_FN_EXT "$", FALSE, TRUE);
+  static BOOL lookup_list_init_done = FALSE;
+
 
   if (lookup_list_init_done)
     return;
 
   if (lookup_list_init_done)
     return;
-  lookup_list_init_done = 1;
+  lookup_list_init_done = TRUE;
 
 #if defined(LOOKUP_CDB) && LOOKUP_CDB!=2
   addlookupmodule(NULL, &cdb_lookup_module_info);
 
 #if defined(LOOKUP_CDB) && LOOKUP_CDB!=2
   addlookupmodule(NULL, &cdb_lookup_module_info);
@@ -558,11 +661,15 @@ void init_lookup_list(void)
   addlookupmodule(NULL, &pgsql_lookup_module_info);
 #endif
 
   addlookupmodule(NULL, &pgsql_lookup_module_info);
 #endif
 
-#ifdef EXPERIMENTAL_REDIS
+#if defined(LOOKUP_REDIS) && LOOKUP_REDIS!=2
   addlookupmodule(NULL, &redis_lookup_module_info);
 #endif
 
   addlookupmodule(NULL, &redis_lookup_module_info);
 #endif
 
-#ifdef EXPERIMENTAL_SPF
+#ifdef EXPERIMENTAL_LMDB
+  addlookupmodule(NULL, &lmdb_lookup_module_info);
+#endif
+
+#ifdef SUPPORT_SPF
   addlookupmodule(NULL, &spf_lookup_module_info);
 #endif
 
   addlookupmodule(NULL, &spf_lookup_module_info);
 #endif
 
@@ -585,6 +692,9 @@ void init_lookup_list(void)
     log_write(0, LOG_MAIN, "Couldn't open %s: not loading lookup modules\n", LOOKUP_MODULE_DIR);
   }
   else {
     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;
     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 +751,13 @@ void init_lookup_list(void)
         countmodules++;
       }
     }
         countmodules++;
       }
     }
+    store_free((void*)regex_islookupmod);
     closedir(dd);
   }
 
   DEBUG(D_lookup) debug_printf("Loaded %d lookup modules\n", countmodules);
 #endif
 
     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);
   DEBUG(D_lookup) debug_printf("Total %d lookups\n", lookup_list_count);
 
   lookup_list = store_malloc(sizeof(lookup_info *) * lookup_list_count);
@@ -671,4 +780,5 @@ void init_lookup_list(void)
   lookupmodules = NULL;
 }
 
   lookupmodules = NULL;
 }
 
+#endif /*!MACRO_PREDEF*/
 /* End of drtables.c */
 /* End of drtables.c */
index d176bef..2e1ad11 100644 (file)
@@ -20,7 +20,7 @@ alternates. */
 /* We don't have the full Exim headers dragged in, but this function
 is used for debugging output. */
 
 /* We don't have the full Exim headers dragged in, but this function
 is used for debugging output. */
 
-extern int  string_vformat(char *, int, char *, va_list);
+extern gstring * string_vformat(gstring *, BOOL, const char *, va_list);
 
 
 /*************************************************
 
 
 /*************************************************
@@ -69,22 +69,24 @@ void
 debug_printf(char *format, ...)
 {
 va_list ap;
 debug_printf(char *format, ...)
 {
 va_list ap;
-char buffer[1024];
+gstring * g = string_get(1024);
+void * reset_point = g;
 
 va_start(ap, format);
 
 
 va_start(ap, format);
 
-if (!string_vformat(buffer, sizeof(buffer), format, ap))
+if (!string_vformat(g, FALSE, format, ap))
   {
   {
-  char *s = "**** debug string overflowed buffer ****\n";
-  char *p = buffer + (int)strlen(buffer);
-  int maxlen = sizeof(buffer) - (int)strlen(s) - 3;
-  if (p > buffer + maxlen) p = buffer + maxlen;
-  if (p > buffer && p[-1] != '\n') *p++ = '\n';
+  char * s = "**** debug string overflowed buffer ****\n";
+  char * p = CS g->s + g->ptr;
+  int maxlen = g->size - (int)strlen(s) - 3;
+  if (p > g->s + maxlen) p = g->s + maxlen;
+  if (p > g->s && p[-1] != '\n') *p++ = '\n';
   strcpy(p, s);
   }
 
   strcpy(p, s);
   }
 
-fprintf(stderr, "%s", buffer);
+fprintf(stderr, "%s", string_from_gstring(g));
 fflush(stderr);
 fflush(stderr);
+store_reset(reset_point);
 va_end(ap);
 }
 
 va_end(ap);
 }
 
@@ -100,7 +102,7 @@ void
 sigalrm_handler(int sig)
 {
 sig = sig;            /* Keep picky compilers happy */
 sigalrm_handler(int sig)
 {
 sig = sig;            /* Keep picky compilers happy */
-sigalrm_seen = 1;
+sigalrm_seen = TRUE;
 }
 
 
 }
 
 
index 6cd243c..573fc00 100644 (file)
--- a/src/enq.c
+++ b/src/enq.c
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     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. */
 /* 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
 /* 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
 
 Arguments:
   key            string on which to serialize
+  lim            parallelism limit
 
 Returns:         TRUE if OK to proceed; FALSE otherwise
 */
 
 
 BOOL
 
 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;
 {
 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. */
 
 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);
 
 /* 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;
 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;
 {
 open_db dbblock;
 open_db *dbm_file;
+dbdata_serialize *serial_record;
 
 DEBUG(D_transport) debug_printf("end serialized: %s\n", key);
 
 
 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);
 }
 
 dbfn_close(dbm_file);
 }
 
index 3af82a6..c394eb7 100644 (file)
@@ -44,14 +44,15 @@ else if (Ustrcmp(keep_environment, "*") != 0)
     /* 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. */
     /* 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, '=');
+    uschar * eqp = Ustrchr(*p, '=');
 
     if (eqp)
       {
 
     if (eqp)
       {
-      uschar *name = string_copyn(*p, eqp - *p);
-      if (OK != match_isinlist(name, USS &keep_environment,
+      uschar * name = string_copyn(*p, eqp - *p);
+
+      if (OK != match_isinlist(name, CUSS &keep_environment,
           0, NULL, NULL, MCL_NOEXPAND, FALSE, NULL))
           0, NULL, NULL, MCL_NOEXPAND, FALSE, NULL))
-        if (unsetenv(CS name) < 0) return FALSE;
+        if (os_unsetenv(name) < 0) return FALSE;
         else p = USS environ; /* RESTART from the beginning */
       else p++;
       store_reset(name);
         else p = USS environ; /* RESTART from the beginning */
       else p++;
       store_reset(name);
@@ -60,11 +61,11 @@ else if (Ustrcmp(keep_environment, "*") != 0)
   }
 if (add_environment)
   {
   }
 if (add_environment)
   {
-    uschar *p;
+    uschar * p;
     int sep = 0;
     int sep = 0;
-    uschar* envlist = add_environment;
-    while ((p = string_nextinlist(&envlist, &sep, NULL, 0)))
-        putenv(CS p);
+    const uschar * envlist = add_environment;
+
+    while ((p = string_nextinlist(&envlist, &sep, NULL, 0))) putenv(CS p);
   }
 
   return TRUE;
   }
 
   return TRUE;
index 01d1f2f..20bf9fc 100644 (file)
@@ -1,6 +1,6 @@
 #! /bin/sh
 
 #! /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:
 # See the file NOTICE for conditions of use and distribution.
 
 # This script takes the following command line arguments:
@@ -72,6 +72,11 @@ while [ $# -gt 0 ] ; do
   -k) keep=$2
       shift
       ;;
   -k) keep=$2
       shift
       ;;
+   --version)
+      echo "`basename $0`: $0"
+      echo "build: EXIM_RELEASE_VERSIONEXIM_VARIANT_VERSION"
+      exit 0
+      ;;
    *) echo "** exicyclog: unknown option $1"
       exit 1
       ;;
    *) echo "** exicyclog: unknown option $1"
       exit 1
       ;;
@@ -232,16 +237,16 @@ $b
 # Now do the job. First remove the files that have "fallen off the bottom".
 # Look for both the compressed and uncompressed forms.
 
 # 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.
 
 # 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 +287,34 @@ done
 
 if [ $keep -gt 99 ]; then first=001; else first=01; fi
 
 
 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
 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
 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
 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
 fi
 
 # Now scan the (0)02 and later files, compressing where necessary, and
index 2d3b40c..5db01fe 100644 (file)
@@ -1,8 +1,14 @@
-#! PERL_COMMAND -w
+#! PERL_COMMAND
 
 
+use warnings;
 use strict;
 use strict;
+BEGIN { pop @INC if $INC[-1] eq '.' };
 
 
-# Copyright (c) 2007-2014 University of Cambridge.
+use Pod::Usage;
+use Getopt::Long;
+use File::Basename;
+
+# Copyright (c) 2007-2017 University of Cambridge.
 # See the file NOTICE for conditions of use and distribution.
 
 # Except when they appear in comments, the following placeholders in this
 # See the file NOTICE for conditions of use and distribution.
 
 # Except when they appear in comments, the following placeholders in this
@@ -31,7 +37,6 @@ use strict;
 # Typical run time acceleration: 4 times
 
 
 # Typical run time acceleration: 4 times
 
 
-use Getopt::Std qw(getopts);
 use POSIX qw(mktime);
 
 
 use POSIX qw(mktime);
 
 
@@ -41,7 +46,7 @@ use POSIX qw(mktime);
 
 sub seconds {
 my($year,$month,$day,$hour,$min,$sec,$tzs,$tzh,$tzm) =
 
 sub seconds {
 my($year,$month,$day,$hour,$min,$sec,$tzs,$tzh,$tzm) =
-  $_[0] =~ /^(\d{4})-(\d\d)-(\d\d)\s(\d\d):(\d\d):(\d\d)(?>\s([+-])(\d\d)(\d\d))?/o;
+  $_[0] =~ /^(\d{4})-(\d\d)-(\d\d)\s(\d\d):(\d\d):(\d\d)(?:.\d+)?(?>\s([+-])(\d\d)(\d\d))?/o;
 
 my $seconds = mktime $sec, $min, $hour, $day, $month - 1, $year - 1900;
 
 
 my $seconds = mktime $sec, $min, $hour, $day, $month - 1, $year - 1900;
 
@@ -58,7 +63,19 @@ return $seconds;
 # This subroutine processes a single line (in $_) from a log file. Program
 # defensively against short lines finding their way into the log.
 
 # This subroutine processes a single line (in $_) from a log file. Program
 # defensively against short lines finding their way into the log.
 
-my (%saved, %id_list, $pattern, $queue_time, $insensitive, $invert);
+my (%saved, %id_list, $pattern);
+
+my $queue_time  = -1;
+my $insensitive = 1;
+my $invert      = 0;
+my $related     = 0;
+my $use_pager   = 1;
+my $literal     = 0;
+
+
+# If using "related" option, have to track extra message IDs
+my $related_re='';
+my @Mids = ();
 
 sub do_line {
 
 
 sub do_line {
 
@@ -67,7 +84,7 @@ sub do_line {
 if (!/^\d{4}-/o) { $_ =~ s/^.*? exim\b.*?: //o; }
 
 return unless
 if (!/^\d{4}-/o) { $_ =~ s/^.*? exim\b.*?: //o; }
 
 return unless
-  my($date,$id) = /^(\d{4}-\d\d-\d\d \d\d:\d\d:\d\d (?:[+-]\d{4} )?)(?:\[\d+\] )?(\w{6}\-\w{6}\-\w{2})?/o;
+  my($date,$id) = /^(\d{4}-\d\d-\d\d \d\d:\d\d:\d\d(?:\.\d+)? (?:[+-]\d{4} )?)(?:\[\d+\] )?(\w{6}\-\w{6}\-\w{2})?/o;
 
 # Handle the case when the log line belongs to a specific message. We save
 # lines for specific messages until the message is complete. Then either print
 
 # Handle the case when the log line belongs to a specific message. We save
 # lines for specific messages until the message is complete. Then either print
@@ -90,8 +107,16 @@ if (defined $id)
     }
   else
     {
     }
   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,
     }
 
   # See if this is a completion for some message. If it is interesting,
@@ -100,7 +125,7 @@ if (defined $id)
   if (index($_, 'Completed') != -1 ||
       index($_, 'SMTP data timeout') != -1 ||
         (index($_, 'rejected') != -1 &&
   if (index($_, 'Completed') != -1 ||
       index($_, 'SMTP data timeout') != -1 ||
         (index($_, 'rejected') != -1 &&
-          /^(\d{4}-\d\d-\d\d \d\d:\d\d:\d\d (?:[+-]\d{4} )?)(?:\[\d+\] )?\w{6}\-\w{6}\-\w{2} rejected/o))
+          /^(\d{4}-\d\d-\d\d \d\d:\d\d:\d\d(?:\.\d+)? (?:[+-]\d{4} )?)(?:\[\d+\] )?\w{6}\-\w{6}\-\w{2} rejected/o))
     {
     if ($queue_time != -1 &&
         $saved{$id} =~ /^(\d{4}-\d\d-\d\d \d\d:\d\d:\d\d ([+-]\d{4} )?)/o)
     {
     if ($queue_time != -1 &&
         $saved{$id} =~ /^(\d{4}-\d\d-\d\d \d\d:\d\d:\d\d ([+-]\d{4} )?)/o)
@@ -161,7 +186,7 @@ sub detect_compressor_capable
     {
     if ($filename =~ /\.(?:$ext)$/)
       {
     {
     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 '');
       # 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,21 +198,61 @@ sub detect_compressor_capable
   return $cmdline;
   }
 
   return $cmdline;
   }
 
-# The main program. Extract the pattern and make sure any relevant characters
-# are quoted if the -l flag is given. The -t flag gives a time-on-queue value
-# which is an additional condition.
+sub grep_for_related {
+  my ($line,$id) = @_;
+  $id_list{$id} = 1 if $line =~ m/$related_re/;
+}
 
 
-getopts('Ilvt:',\my %args);
-$queue_time  = $args{'t'}? $args{'t'} : -1;
-$insensitive = $args{'I'}? 0 : 1;
-$invert      = $args{'v'}? 1 : 0;
+sub get_related_ids {
+  my ($id) = @_;
+  push @Mids, $id unless grep /\b$id\b/, @Mids;
+  my $re = join '|', @Mids;
+  $related_re = qr/$re/;
+}
 
 
-die "usage: exigrep [-I] [-l] [-t <seconds>] [-v] <pattern> [<log file>]...\n"
-  if ($#ARGV < 0);
+# 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. The -M flag will also display "related"
+# loglines (msgid from matched lines is searched in following lines).
+
+GetOptions(
+    'I|sensitive' => sub { $insensitive = 0 },
+      'l|literal' => \$literal,
+      'M|related' => \$related,
+      't|queue-time=i' => \$queue_time,
+      'pager!'         => \$use_pager,
+      'v|invert'       => \$invert,
+      'h|help'         => sub { pod2usage(-exit => 0, -verbose => 1) },
+      'm|man'          => sub {
+        pod2usage(
+            -exit      => 0,
+            -verbose   => 2,
+            -noperldoc => system('perldoc -V 2>/dev/null >&2')
+        );
+      },
+      'version'        => sub {
+            print basename($0) . ": $0\n",
+                "build: EXIM_RELEASE_VERSIONEXIM_VARIANT_VERSION\n",
+                "perl(runtime): $]\n";
+            exit 0;
+      },
+) and @ARGV or pod2usage;
 
 $pattern = shift @ARGV;
 
 $pattern = shift @ARGV;
-$pattern = quotemeta $pattern if $args{l};
+$pattern = quotemeta $pattern if $literal;
 
 
+# Start a pager if output goes to a terminal
+if (-t 1 and $use_pager)
+  {
+  # for perl >= v5.10.x: foreach ($ENV{PAGER}//(), 'less', 'more')
+  foreach (defined $ENV{PAGER} ? $ENV{PAGER} : (), 'less', 'more')
+    {
+    local $ENV{LESS} .= ' --no-init --quit-if-one-screen';
+    open(my $pager, '|-', $_) or next;
+    select $pager;
+    last;
+    }
+  }
 
 # If file arguments are given, open each one and process according as it is
 # is compressed or not.
 
 # If file arguments are given, open each one and process according as it is
 # is compressed or not.
@@ -197,7 +262,7 @@ if (@ARGV)
   foreach (@ARGV)
     {
     my $filename = $_;
   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";
       {
       open(LOG, "ZCAT_COMMAND $filename |") ||
         die "Unable to zcat $filename: $!\n";
@@ -227,4 +292,83 @@ for (keys %id_list)
   print "+++ $_ has not completed +++\n$saved{$_}\n";
   }
 
   print "+++ $_ has not completed +++\n$saved{$_}\n";
   }
 
-# End of exigrep
+__END__
+
+=head1 NAME
+
+exigrep - search Exim's main log
+
+=head1 SYNOPSIS
+
+B<exigrep> [options] pattern [log] ...
+
+=head1 DESCRIPTION
+
+The B<exigrep> utility is a Perl script that searches one or more main log
+files for entries that match a given pattern.  When it finds  a  match,
+it  extracts  all  the  log  entries for the relevant message, not just
+those that match the pattern.  Thus, B<exigrep> can extract  complete  log
+entries  for  a  given  message, or all mail for a given user, or for a
+given host, for example.
+
+If no file names are given on the command line, the standard input is read.
+
+For known file extensions indicating compression (F<.gz>, F<.bz2>, F<.xz>, and F<.lzma>)
+a suitable de-compressor is used, if available.
+
+The output is sent through a pager if a terminal is connected to STDOUT. As
+pager are considered: C<$ENV{PAGER}>, C<less>, C<more>.
+
+=head1 OPTIONS
+
+=over
+
+=item B<-l>|B<--literal>
+
+This means 'literal', that is, treat all characters in the
+pattern  as standing for themselves.  Otherwise the pattern must be a
+Perl regular expression.  The pattern match is case-insensitive.
+
+=item B<-t>|B<--queue-time> I<seconds>
+
+Limit the output to messages that spent at least I<seconds> in the
+queue.
+
+=item B<-I>|B<--sensitive>
+
+Do a case sensitive search.
+
+=item B<-v>|B<--invert>
+
+Invert the meaning of the search pattern. That is, print message log
+entries that are not related to that pattern.
+
+=item B<-M>|B<--related>
+
+Search for related messages too.
+
+=item B<--no-pager>
+
+Do not use a pager, even if STDOUT is connected to a terminal.
+
+=item B<-h>|B<--help>
+
+Print a short reference help. For more detailed help try L<exigrep(8)>,
+or C<exigrep -m>.
+
+=item B<-m>|B<--man>
+
+Print this manual page of B<exigrep>.
+
+=back
+
+=head1 SEE ALSO
+
+L<exim(8)>, L<perlre(1)>, L<Exim|http://exim.org/>
+
+=head1 AUTHOR
+
+This  manual  page  was stitched together from spec.txt by Andreas Metzler L<ametzler at downhill.at.eu.org>
+and updated by Heiko Schlittermann L<hs@schlittermann.de>.
+
+=cut
index 3dc5214..f6f15f4 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
 /* 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"
 
 
 #include "exim.h"
 
+#if defined(__GLIBC__) && !defined(__UCLIBC__)
+# 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);
 
 
 extern void init_lookup_list(void);
 
 
@@ -81,7 +92,7 @@ Returns:      pointer to the compiled pattern
 */
 
 const pcre *
 */
 
 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;
 {
 int offset;
 int options = PCRE_COPT;
@@ -93,7 +104,7 @@ if (use_malloc)
   pcre_free = function_store_free;
   }
 if (caseless) options |= PCRE_CASELESS;
   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)
 pcre_malloc = function_store_get;
 pcre_free = function_dummy_free;
 if (yield == NULL)
@@ -124,20 +135,21 @@ Returns:      TRUE or FALSE
 */
 
 BOOL
 */
 
 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 ovector[3*(EXPAND_MAXN+1)];
-int n = pcre_exec(re, NULL, CS subject, Ustrlen(subject), 0,
-  PCRE_EOPT | options, ovector, sizeof(ovector)/sizeof(int));
+uschar * s = string_copy(subject);     /* de-constifying */
+int n = pcre_exec(re, NULL, CS s, Ustrlen(s), 0,
+  PCRE_EOPT | options, ovector, nelem(ovector));
 BOOL yield = n >= 0;
 if (n == 0) n = EXPAND_MAXN + 1;
 if (yield)
   {
   int nn;
 BOOL yield = n >= 0;
 if (n == 0) n = EXPAND_MAXN + 1;
 if (yield)
   {
   int nn;
-  expand_nmax = (setup < 0)? 0 : setup + 1;
-  for (nn = (setup < 0)? 0 : 2; nn < n*2; nn += 2)
+  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--;
     expand_nlength[expand_nmax++] = ovector[nn+1] - ovector[nn];
     }
   expand_nmax--;
@@ -162,22 +174,35 @@ Returns:   nothing
 void
 set_process_info(const char *format, ...)
 {
 void
 set_process_info(const char *format, ...)
 {
+gstring gs = { .size = PROCESS_INFO_SIZE - 2, .ptr = 0, .s = process_info };
+gstring * g;
 int len;
 va_list ap;
 int len;
 va_list ap;
-sprintf(CS process_info, "%5d ", (int)getpid());
-len = Ustrlen(process_info);
+
+g = string_fmt_append(&gs, "%5d ", (int)getpid());
+len = g->ptr;
 va_start(ap, format);
 va_start(ap, format);
-if (!string_vformat(process_info + len, PROCESS_INFO_SIZE - len - 2, format, ap))
-  Ustrcpy(process_info + len, "**** string overflowed buffer ****");
-len = Ustrlen(process_info);
-process_info[len+0] = '\n';
-process_info[len+1] = '\0';
-process_info_len = len + 1;
+if (!string_vformat(g, FALSE, format, ap))
+  {
+  gs.ptr = len;
+  g = string_cat(&gs, US"**** string overflowed buffer ****");
+  }
+g = string_catn(g, US"\n", 1);
+string_from_gstring(g);
+process_info_len = g->ptr;
 DEBUG(D_process_info) debug_printf("set_process_info: %s", process_info);
 va_end(ap);
 }
 
 DEBUG(D_process_info) debug_printf("set_process_info: %s", process_info);
 va_end(ap);
 }
 
+/***********************************************
+*            Handler for SIGTERM               *
+***********************************************/
 
 
+static void
+term_handler(int sig)
+{
+  exit(1);
+}
 
 
 /*************************************************
 
 
 /*************************************************
@@ -202,8 +227,7 @@ int fd;
 
 os_restarting_signal(sig, usr1_handler);
 
 
 os_restarting_signal(sig, usr1_handler);
 
-fd = Uopen(process_log_path, O_APPEND|O_WRONLY, LOG_MODE);
-if (fd < 0)
+if ((fd = Uopen(process_log_path, O_APPEND|O_WRONLY, LOG_MODE)) < 0)
   {
   /* If we are already running as the Exim user, try to create it in the
   current process (assuming spool_directory exists). Otherwise, if we are
   {
   /* If we are already running as the Exim user, try to create it in the
   current process (assuming spool_directory exists). Otherwise, if we are
@@ -222,7 +246,7 @@ to disrupt whatever is going on outside the signal handler. */
 
 if (fd < 0) return;
 
 
 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);
 }
 
 (void)close(fd);
 }
 
@@ -267,6 +291,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. :-)
 
 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
 */
 Argument:  an itimerval structure containing the interval
 Returns:   nothing
 */
@@ -276,6 +304,9 @@ milliwait(struct itimerval *itval)
 {
 sigset_t sigmask;
 sigset_t old_sigmask;
 {
 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 */
 (void)sigemptyset(&sigmask);                           /* Empty mask */
 (void)sigaddset(&sigmask, SIGALRM);                    /* Add SIGALRM */
 (void)sigprocmask(SIG_BLOCK, &sigmask, &old_sigmask);  /* Block SIGALRM */
@@ -328,7 +359,7 @@ Arguments:
 Returns:      -1, 0, or +1
 */
 
 Returns:      -1, 0, or +1
 */
 
-int
+static int
 exim_tvcmp(struct timeval *t1, struct timeval *t2)
 {
 if (t1->tv_sec > t2->tv_sec) return +1;
 exim_tvcmp(struct timeval *t1, struct timeval *t2)
 {
 if (t1->tv_sec > t2->tv_sec) return +1;
@@ -348,7 +379,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.
 /* 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
 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
@@ -396,13 +427,13 @@ if (exim_tvcmp(&now_tv, then_tv) <= 0)
 
   DEBUG(D_transport|D_receive)
     {
 
   DEBUG(D_transport|D_receive)
     {
-    if (!running_in_test_harness)
+    if (!f.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);
         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);
       }
     }
 
       }
     }
 
@@ -526,9 +557,9 @@ close_unwanted(void)
 {
 if (smtp_input)
   {
 {
 if (smtp_input)
   {
-  #ifdef SUPPORT_TLS
-  tls_close(TRUE, FALSE);      /* Shut down the TLS library */
-  #endif
+#ifdef SUPPORT_TLS
+  tls_close(NULL, TLS_NO_SHUTDOWN);      /* Shut down the TLS library */
+#endif
   (void)close(fileno(smtp_in));
   (void)close(fileno(smtp_out));
   smtp_in = NULL;
   (void)close(fileno(smtp_in));
   (void)close(fileno(smtp_out));
   smtp_in = NULL;
@@ -539,7 +570,7 @@ else
   if ((debug_selector & D_resolver) == 0) (void)close(1);  /* stdout */
   if (debug_selector == 0)                                 /* stderr */
     {
   if ((debug_selector & D_resolver) == 0) (void)close(1);  /* stdout */
   if (debug_selector == 0)                                 /* stderr */
     {
-    if (!synchronous_delivery)
+    if (!f.synchronous_delivery)
       {
       (void)close(2);
       log_stderr = NULL;
       {
       (void)close(2);
       log_stderr = NULL;
@@ -585,21 +616,18 @@ if (euid == root_uid || euid != uid || egid != gid || igflag)
   if (igflag)
     {
     struct passwd *pw = getpwuid(uid);
   if (igflag)
     {
     struct passwd *pw = getpwuid(uid);
-    if (pw != NULL)
-      {
-      if (initgroups(pw->pw_name, gid) != 0)
-        log_write(0,LOG_MAIN|LOG_PANIC_DIE,"initgroups failed for uid=%ld: %s",
-          (long int)uid, strerror(errno));
-      }
-    else log_write(0, LOG_MAIN|LOG_PANIC_DIE, "cannot run initgroups(): "
-      "no passwd entry for uid=%ld", (long int)uid);
+    if (!pw)
+      log_write(0, LOG_MAIN|LOG_PANIC_DIE, "cannot run initgroups(): "
+       "no passwd entry for uid=%ld", (long int)uid);
+
+    if (initgroups(pw->pw_name, gid) != 0)
+      log_write(0,LOG_MAIN|LOG_PANIC_DIE,"initgroups failed for uid=%ld: %s",
+       (long int)uid, strerror(errno));
     }
 
   if (setgid(gid) < 0 || setuid(uid) < 0)
     }
 
   if (setgid(gid) < 0 || setuid(uid) < 0)
-    {
     log_write(0, LOG_MAIN|LOG_PANIC_DIE, "unable to set gid=%ld or uid=%ld "
       "(euid=%ld): %s", (long int)gid, (long int)uid, (long int)euid, msg);
     log_write(0, LOG_MAIN|LOG_PANIC_DIE, "unable to set gid=%ld or uid=%ld "
       "(euid=%ld): %s", (long int)gid, (long int)uid, (long int)euid, msg);
-    }
   }
 
 /* Debugging output included uid/gid and all groups */
   }
 
 /* Debugging output included uid/gid and all groups */
@@ -607,10 +635,10 @@ if (euid == root_uid || euid != uid || egid != gid || igflag)
 DEBUG(D_uid)
   {
   int group_count, save_errno;
 DEBUG(D_uid)
   {
   int group_count, save_errno;
-  gid_t group_list[NGROUPS_MAX];
+  gid_t group_list[EXIM_GROUPLIST_SIZE];
   debug_printf("changed uid/gid: %s\n  uid=%ld gid=%ld pid=%ld\n", msg,
     (long int)geteuid(), (long int)getegid(), (long int)getpid());
   debug_printf("changed uid/gid: %s\n  uid=%ld gid=%ld pid=%ld\n", msg,
     (long int)geteuid(), (long int)getegid(), (long int)getpid());
-  group_count = getgroups(NGROUPS_MAX, group_list);
+  group_count = getgroups(nelem(group_list), group_list);
   save_errno = errno;
   debug_printf("  auxiliary group list:");
   if (group_count > 0)
   save_errno = errno;
   debug_printf("  auxiliary group list:");
   if (group_count > 0)
@@ -642,17 +670,29 @@ Returns:     does not return
 */
 
 void
 */
 
 void
-exim_exit(int rc)
+exim_exit(int rc, const uschar * process)
 {
 search_tidyup();
 DEBUG(D_any)
 {
 search_tidyup();
 DEBUG(D_any)
-  debug_printf(">>>>>>>>>>>>>>>> Exim pid=%d terminating with rc=%d "
-    ">>>>>>>>>>>>>>>>\n", (int)getpid(), rc);
+  debug_printf(">>>>>>>>>>>>>>>> Exim pid=%d %s%s%sterminating with rc=%d "
+    ">>>>>>>>>>>>>>>>\n", (int)getpid(),
+    process ? "(" : "", process, process ? ") " : "", rc);
 exit(rc);
 }
 
 
 
 exit(rc);
 }
 
 
 
+/* Print error string, then die */
+static void
+exim_fail(const char * fmt, ...)
+{
+va_list ap;
+va_start(ap, fmt);
+vfprintf(stderr, fmt, ap);
+exit(EXIT_FAILURE);
+}
+
+
 
 /*************************************************
 *         Extract port from host address         *
 
 /*************************************************
 *         Extract port from host address         *
@@ -674,10 +714,7 @@ check_port(uschar *address)
 {
 int port = host_address_extract_port(address);
 if (string_is_ip_address(address, NULL) == 0)
 {
 int port = host_address_extract_port(address);
 if (string_is_ip_address(address, NULL) == 0)
-  {
-  fprintf(stderr, "exim abandoned: \"%s\" is not an IP address\n", address);
-  exit(EXIT_FAILURE);
-  }
+  exim_fail("exim abandoned: \"%s\" is not an IP address\n", address);
 return port;
 }
 
 return port;
 }
 
@@ -726,26 +763,26 @@ else
 *          Show supported features               *
 *************************************************/
 
 *          Show supported features               *
 *************************************************/
 
-/* This function is called for -bV/--version and for -d to output the optional
-features of the current Exim binary.
-
-Arguments:  a FILE for printing
-Returns:    nothing
-*/
-
 static void
 static void
-show_whats_supported(FILE *f)
+show_db_version(FILE * f)
 {
 {
-  auth_info *authi;
-
 #ifdef DB_VERSION_STRING
 #ifdef DB_VERSION_STRING
-fprintf(f, "Berkeley DB: %s\n", DB_VERSION_STRING);
+DEBUG(D_any)
+  {
+  fprintf(f, "Library version: BDB: Compile: %s\n", DB_VERSION_STRING);
+  fprintf(f, "                      Runtime: %s\n",
+    db_version(NULL, NULL, NULL));
+  }
+else
+  fprintf(f, "Berkeley DB: %s\n", DB_VERSION_STRING);
+
 #elif defined(BTREEVERSION) && defined(HASHVERSION)
   #ifdef USE_DB
   fprintf(f, "Probably Berkeley DB version 1.8x (native mode)\n");
   #else
   fprintf(f, "Probably Berkeley DB version 1.8x (compatibility mode)\n");
   #endif
 #elif defined(BTREEVERSION) && defined(HASHVERSION)
   #ifdef USE_DB
   fprintf(f, "Probably Berkeley DB version 1.8x (native mode)\n");
   #else
   fprintf(f, "Probably Berkeley DB version 1.8x (compatibility mode)\n");
   #endif
+
 #elif defined(_DBM_RDONLY) || defined(dbm_dirfno)
 fprintf(f, "Probably ndbm\n");
 #elif defined(USE_TDB)
 #elif defined(_DBM_RDONLY) || defined(dbm_dirfno)
 fprintf(f, "Probably ndbm\n");
 #elif defined(USE_TDB)
@@ -757,228 +794,207 @@ fprintf(f, "Using tdb\n");
   fprintf(f, "Probably GDBM (compatibility mode)\n");
   #endif
 #endif
   fprintf(f, "Probably GDBM (compatibility mode)\n");
   #endif
 #endif
+}
 
 
-fprintf(f, "Support for:");
+
+/* This function is called for -bV/--version and for -d to output the optional
+features of the current Exim binary.
+
+Arguments:  a FILE for printing
+Returns:    nothing
+*/
+
+static void
+show_whats_supported(FILE * fp)
+{
+auth_info * authi;
+
+DEBUG(D_any) {} else show_db_version(fp);
+
+fprintf(fp, "Support for:");
 #ifdef SUPPORT_CRYPTEQ
 #ifdef SUPPORT_CRYPTEQ
-  fprintf(f, " crypteq");
+  fprintf(fp, " crypteq");
 #endif
 #if HAVE_ICONV
 #endif
 #if HAVE_ICONV
-  fprintf(f, " iconv()");
+  fprintf(fp, " iconv()");
 #endif
 #if HAVE_IPV6
 #endif
 #if HAVE_IPV6
-  fprintf(f, " IPv6");
+  fprintf(fp, " IPv6");
 #endif
 #ifdef HAVE_SETCLASSRESOURCES
 #endif
 #ifdef HAVE_SETCLASSRESOURCES
-  fprintf(f, " use_setclassresources");
+  fprintf(fp, " use_setclassresources");
 #endif
 #ifdef SUPPORT_PAM
 #endif
 #ifdef SUPPORT_PAM
-  fprintf(f, " PAM");
+  fprintf(fp, " PAM");
 #endif
 #ifdef EXIM_PERL
 #endif
 #ifdef EXIM_PERL
-  fprintf(f, " Perl");
+  fprintf(fp, " Perl");
 #endif
 #ifdef EXPAND_DLFUNC
 #endif
 #ifdef EXPAND_DLFUNC
-  fprintf(f, " Expand_dlfunc");
+  fprintf(fp, " Expand_dlfunc");
 #endif
 #ifdef USE_TCP_WRAPPERS
 #endif
 #ifdef USE_TCP_WRAPPERS
-  fprintf(f, " TCPwrappers");
+  fprintf(fp, " TCPwrappers");
 #endif
 #ifdef SUPPORT_TLS
 #endif
 #ifdef SUPPORT_TLS
-  #ifdef USE_GNUTLS
-  fprintf(f, " GnuTLS");
-  #else
-  fprintf(f, " OpenSSL");
-  #endif
+ifdef USE_GNUTLS
+  fprintf(fp, " GnuTLS");
+else
+  fprintf(fp, " OpenSSL");
+endif
 #endif
 #ifdef SUPPORT_TRANSLATE_IP_ADDRESS
 #endif
 #ifdef SUPPORT_TRANSLATE_IP_ADDRESS
-  fprintf(f, " translate_ip_address");
+  fprintf(fp, " translate_ip_address");
 #endif
 #ifdef SUPPORT_MOVE_FROZEN_MESSAGES
 #endif
 #ifdef SUPPORT_MOVE_FROZEN_MESSAGES
-  fprintf(f, " move_frozen_messages");
+  fprintf(fp, " move_frozen_messages");
 #endif
 #ifdef WITH_CONTENT_SCAN
 #endif
 #ifdef WITH_CONTENT_SCAN
-  fprintf(f, " Content_Scanning");
+  fprintf(fp, " Content_Scanning");
+#endif
+#ifdef SUPPORT_DANE
+  fprintf(fp, " DANE");
 #endif
 #ifndef DISABLE_DKIM
 #endif
 #ifndef DISABLE_DKIM
-  fprintf(f, " DKIM");
+  fprintf(fp, " DKIM");
 #endif
 #endif
-#ifdef WITH_OLD_DEMIME
-  fprintf(f, " Old_Demime");
+#ifndef DISABLE_DNSSEC
+  fprintf(fp, " DNSSEC");
 #endif
 #endif
-#ifndef DISABLE_PRDR
-  fprintf(f, " PRDR");
+#ifndef DISABLE_EVENT
+  fprintf(fp, " Event");
+#endif
+#ifdef SUPPORT_I18N
+  fprintf(fp, " I18N");
 #endif
 #ifndef DISABLE_OCSP
 #endif
 #ifndef DISABLE_OCSP
-  fprintf(f, " OCSP");
+  fprintf(fp, " OCSP");
+#endif
+#ifndef DISABLE_PRDR
+  fprintf(fp, " PRDR");
+#endif
+#ifdef SUPPORT_PROXY
+  fprintf(fp, " PROXY");
+#endif
+#ifdef SUPPORT_SOCKS
+  fprintf(fp, " SOCKS");
+#endif
+#ifdef SUPPORT_SPF
+  fprintf(fp, " SPF");
 #endif
 #endif
-#ifdef EXPERIMENTAL_SPF
-  fprintf(f, " Experimental_SPF");
+#ifdef TCP_FASTOPEN
+  deliver_init();
+  if (f.tcp_fastopen_ok) fprintf(fp, " TCP_Fast_Open");
+#endif
+#ifdef EXPERIMENTAL_LMDB
+  fprintf(fp, " Experimental_LMDB");
+#endif
+#ifdef EXPERIMENTAL_QUEUEFILE
+  fprintf(fp, " Experimental_QUEUEFILE");
 #endif
 #ifdef EXPERIMENTAL_SRS
 #endif
 #ifdef EXPERIMENTAL_SRS
-  fprintf(f, " Experimental_SRS");
+  fprintf(fp, " Experimental_SRS");
+#endif
+#ifdef EXPERIMENTAL_ARC
+  fprintf(fp, " Experimental_ARC");
 #endif
 #ifdef EXPERIMENTAL_BRIGHTMAIL
 #endif
 #ifdef EXPERIMENTAL_BRIGHTMAIL
-  fprintf(f, " Experimental_Brightmail");
+  fprintf(fp, " Experimental_Brightmail");
 #endif
 #ifdef EXPERIMENTAL_DCC
 #endif
 #ifdef EXPERIMENTAL_DCC
-  fprintf(f, " Experimental_DCC");
+  fprintf(fp, " Experimental_DCC");
 #endif
 #ifdef EXPERIMENTAL_DMARC
 #endif
 #ifdef EXPERIMENTAL_DMARC
-  fprintf(f, " Experimental_DMARC");
-#endif
-#ifdef EXPERIMENTAL_PROXY
-  fprintf(f, " Experimental_Proxy");
+  fprintf(fp, " Experimental_DMARC");
 #endif
 #endif
-#ifdef EXPERIMENTAL_TPDA
-  fprintf(f, " Experimental_TPDA");
+#ifdef EXPERIMENTAL_DSN_INFO
+  fprintf(fp, " Experimental_DSN_info");
 #endif
 #endif
-#ifdef EXPERIMENTAL_REDIS
-  fprintf(f, " Experimental_Redis");
+#ifdef EXPERIMENTAL_REQUIRETLS
+  fprintf(fp, " Experimental_REQUIRETLS");
 #endif
 #endif
-#ifdef EXPERIMENTAL_CERTNAMES
-  fprintf(f, " Experimental_Certnames");
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+  fprintf(fp, " Experimental_PIPE_CONNECT");
 #endif
 #endif
-#ifdef EXPERIMENTAL_DSN
-  fprintf(f, " Experimental_DSN");
-#endif
-fprintf(f, "\n");
+fprintf(fp, "\n");
 
 
-fprintf(f, "Lookups (built-in):");
+fprintf(fp, "Lookups (built-in):");
 #if defined(LOOKUP_LSEARCH) && LOOKUP_LSEARCH!=2
 #if defined(LOOKUP_LSEARCH) && LOOKUP_LSEARCH!=2
-  fprintf(f, " lsearch wildlsearch nwildlsearch iplsearch");
+  fprintf(fp, " lsearch wildlsearch nwildlsearch iplsearch");
 #endif
 #if defined(LOOKUP_CDB) && LOOKUP_CDB!=2
 #endif
 #if defined(LOOKUP_CDB) && LOOKUP_CDB!=2
-  fprintf(f, " cdb");
+  fprintf(fp, " cdb");
 #endif
 #if defined(LOOKUP_DBM) && LOOKUP_DBM!=2
 #endif
 #if defined(LOOKUP_DBM) && LOOKUP_DBM!=2
-  fprintf(f, " dbm dbmjz dbmnz");
+  fprintf(fp, " dbm dbmjz dbmnz");
 #endif
 #if defined(LOOKUP_DNSDB) && LOOKUP_DNSDB!=2
 #endif
 #if defined(LOOKUP_DNSDB) && LOOKUP_DNSDB!=2
-  fprintf(f, " dnsdb");
+  fprintf(fp, " dnsdb");
 #endif
 #if defined(LOOKUP_DSEARCH) && LOOKUP_DSEARCH!=2
 #endif
 #if defined(LOOKUP_DSEARCH) && LOOKUP_DSEARCH!=2
-  fprintf(f, " dsearch");
+  fprintf(fp, " dsearch");
 #endif
 #if defined(LOOKUP_IBASE) && LOOKUP_IBASE!=2
 #endif
 #if defined(LOOKUP_IBASE) && LOOKUP_IBASE!=2
-  fprintf(f, " ibase");
+  fprintf(fp, " ibase");
 #endif
 #if defined(LOOKUP_LDAP) && LOOKUP_LDAP!=2
 #endif
 #if defined(LOOKUP_LDAP) && LOOKUP_LDAP!=2
-  fprintf(f, " ldap ldapdn ldapm");
+  fprintf(fp, " ldap ldapdn ldapm");
+#endif
+#ifdef EXPERIMENTAL_LMDB
+  fprintf(fp, " lmdb");
 #endif
 #if defined(LOOKUP_MYSQL) && LOOKUP_MYSQL!=2
 #endif
 #if defined(LOOKUP_MYSQL) && LOOKUP_MYSQL!=2
-  fprintf(f, " mysql");
+  fprintf(fp, " mysql");
 #endif
 #if defined(LOOKUP_NIS) && LOOKUP_NIS!=2
 #endif
 #if defined(LOOKUP_NIS) && LOOKUP_NIS!=2
-  fprintf(f, " nis nis0");
+  fprintf(fp, " nis nis0");
 #endif
 #if defined(LOOKUP_NISPLUS) && LOOKUP_NISPLUS!=2
 #endif
 #if defined(LOOKUP_NISPLUS) && LOOKUP_NISPLUS!=2
-  fprintf(f, " nisplus");
+  fprintf(fp, " nisplus");
 #endif
 #if defined(LOOKUP_ORACLE) && LOOKUP_ORACLE!=2
 #endif
 #if defined(LOOKUP_ORACLE) && LOOKUP_ORACLE!=2
-  fprintf(f, " oracle");
+  fprintf(fp, " oracle");
 #endif
 #if defined(LOOKUP_PASSWD) && LOOKUP_PASSWD!=2
 #endif
 #if defined(LOOKUP_PASSWD) && LOOKUP_PASSWD!=2
-  fprintf(f, " passwd");
+  fprintf(fp, " passwd");
 #endif
 #if defined(LOOKUP_PGSQL) && LOOKUP_PGSQL!=2
 #endif
 #if defined(LOOKUP_PGSQL) && LOOKUP_PGSQL!=2
-  fprintf(f, " pgsql");
+  fprintf(fp, " pgsql");
+#endif
+#if defined(LOOKUP_REDIS) && LOOKUP_REDIS!=2
+  fprintf(fp, " redis");
 #endif
 #if defined(LOOKUP_SQLITE) && LOOKUP_SQLITE!=2
 #endif
 #if defined(LOOKUP_SQLITE) && LOOKUP_SQLITE!=2
-  fprintf(f, " sqlite");
+  fprintf(fp, " sqlite");
 #endif
 #if defined(LOOKUP_TESTDB) && LOOKUP_TESTDB!=2
 #endif
 #if defined(LOOKUP_TESTDB) && LOOKUP_TESTDB!=2
-  fprintf(f, " testdb");
+  fprintf(fp, " testdb");
 #endif
 #if defined(LOOKUP_WHOSON) && LOOKUP_WHOSON!=2
 #endif
 #if defined(LOOKUP_WHOSON) && LOOKUP_WHOSON!=2
-  fprintf(f, " whoson");
-#endif
-fprintf(f, "\n");
-
-fprintf(f, "Authenticators:");
-#ifdef AUTH_CRAM_MD5
-  fprintf(f, " cram_md5");
-#endif
-#ifdef AUTH_CYRUS_SASL
-  fprintf(f, " cyrus_sasl");
-#endif
-#ifdef AUTH_DOVECOT
-  fprintf(f, " dovecot");
-#endif
-#ifdef AUTH_GSASL
-  fprintf(f, " gsasl");
+  fprintf(fp, " whoson");
 #endif
 #endif
-#ifdef AUTH_HEIMDAL_GSSAPI
-  fprintf(f, " heimdal_gssapi");
-#endif
-#ifdef AUTH_PLAINTEXT
-  fprintf(f, " plaintext");
-#endif
-#ifdef AUTH_SPA
-  fprintf(f, " spa");
-#endif
-fprintf(f, "\n");
+fprintf(fp, "\n");
 
 
-fprintf(f, "Routers:");
-#ifdef ROUTER_ACCEPT
-  fprintf(f, " accept");
-#endif
-#ifdef ROUTER_DNSLOOKUP
-  fprintf(f, " dnslookup");
-#endif
-#ifdef ROUTER_IPLITERAL
-  fprintf(f, " ipliteral");
-#endif
-#ifdef ROUTER_IPLOOKUP
-  fprintf(f, " iplookup");
-#endif
-#ifdef ROUTER_MANUALROUTE
-  fprintf(f, " manualroute");
-#endif
-#ifdef ROUTER_QUERYPROGRAM
-  fprintf(f, " queryprogram");
-#endif
-#ifdef ROUTER_REDIRECT
-  fprintf(f, " redirect");
-#endif
-fprintf(f, "\n");
+auth_show_supported(fp);
+route_show_supported(fp);
+transport_show_supported(fp);
 
 
-fprintf(f, "Transports:");
-#ifdef TRANSPORT_APPENDFILE
-  fprintf(f, " appendfile");
-  #ifdef SUPPORT_MAILDIR
-    fprintf(f, "/maildir");
-  #endif
-  #ifdef SUPPORT_MAILSTORE
-    fprintf(f, "/mailstore");
-  #endif
-  #ifdef SUPPORT_MBX
-    fprintf(f, "/mbx");
-  #endif
-#endif
-#ifdef TRANSPORT_AUTOREPLY
-  fprintf(f, " autoreply");
-#endif
-#ifdef TRANSPORT_LMTP
-  fprintf(f, " lmtp");
-#endif
-#ifdef TRANSPORT_PIPE
-  fprintf(f, " pipe");
-#endif
-#ifdef TRANSPORT_SMTP
-  fprintf(f, " smtp");
+#ifdef WITH_CONTENT_SCAN
+malware_show_supported(fp);
 #endif
 #endif
-fprintf(f, "\n");
 
 if (fixed_never_users[0] > 0)
   {
   int i;
 
 if (fixed_never_users[0] > 0)
   {
   int i;
-  fprintf(f, "Fixed never_users: ");
+  fprintf(fp, "Fixed never_users: ");
   for (i = 1; i <= (int)fixed_never_users[0] - 1; i++)
   for (i = 1; i <= (int)fixed_never_users[0] - 1; i++)
-    fprintf(f, "%d:", (unsigned int)fixed_never_users[i]);
-  fprintf(f, "%d\n", (unsigned int)fixed_never_users[i]);
+    fprintf(fp, "%d:", (unsigned int)fixed_never_users[i]);
+  fprintf(fp, "%d\n", (unsigned int)fixed_never_users[i]);
   }
 
   }
 
-fprintf(f, "Size of off_t: " SIZE_T_FMT "\n", sizeof(off_t));
+fprintf(fp, "Configure owner: %d:%d\n", config_uid, config_gid);
+
+fprintf(fp, "Size of off_t: " SIZE_T_FMT "\n", sizeof(off_t));
 
 /* Everything else is details which are only worth reporting when debugging.
 Perhaps the tls_version_report should move into this too. */
 
 /* Everything else is details which are only worth reporting when debugging.
 Perhaps the tls_version_report should move into this too. */
@@ -988,9 +1004,9 @@ DEBUG(D_any) do {
 
 /* clang defines __GNUC__ (at least, for me) so test for it first */
 #if defined(__clang__)
 
 /* clang defines __GNUC__ (at least, for me) so test for it first */
 #if defined(__clang__)
-  fprintf(f, "Compiler: CLang [%s]\n", __clang_version__);
+  fprintf(fp, "Compiler: CLang [%s]\n", __clang_version__);
 #elif defined(__GNUC__)
 #elif defined(__GNUC__)
-  fprintf(f, "Compiler: GCC [%s]\n",
+  fprintf(fp, "Compiler: GCC [%s]\n",
 # ifdef __VERSION__
       __VERSION__
 # else
 # ifdef __VERSION__
       __VERSION__
 # else
@@ -998,28 +1014,39 @@ DEBUG(D_any) do {
 # endif
       );
 #else
 # endif
       );
 #else
-  fprintf(f, "Compiler: <unknown>\n");
+  fprintf(fp, "Compiler: <unknown>\n");
+#endif
+
+#if defined(__GLIBC__) && !defined(__UCLIBC__)
+  fprintf(fp, "Library version: Glibc: Compile: %d.%d\n",
+               __GLIBC__, __GLIBC_MINOR__);
+  if (__GLIBC_PREREQ(2, 1))
+    fprintf(fp, "                        Runtime: %s\n",
+               gnu_get_libc_version());
 #endif
 
 #endif
 
+show_db_version(fp);
+
 #ifdef SUPPORT_TLS
 #ifdef SUPPORT_TLS
-  tls_version_report(f);
+  tls_version_report(fp);
+#endif
+#ifdef SUPPORT_I18N
+  utf8_version_report(fp);
 #endif
 
 #endif
 
-  for (authi = auths_available; *authi->driver_name != '\0'; ++authi) {
-    if (authi->version_report) {
-      (*authi->version_report)(f);
-    }
-  }
+  for (authi = auths_available; *authi->driver_name != '\0'; ++authi)
+    if (authi->version_report)
+      (*authi->version_report)(fp);
 
   /* 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
 
   /* 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)
 #endif
 #define QUOTE(X) #X
 #define EXPAND_AND_QUOTE(X) QUOTE(X)
-  fprintf(f, "Library version: PCRE: Compile: %d.%d%s\n"
+  fprintf(fp, "Library version: PCRE: Compile: %d.%d%s\n"
              "                       Runtime: %s\n",
           PCRE_MAJOR, PCRE_MINOR,
           EXPAND_AND_QUOTE(PCRE_PRERELEASE) "",
              "                       Runtime: %s\n",
           PCRE_MAJOR, PCRE_MINOR,
           EXPAND_AND_QUOTE(PCRE_PRERELEASE) "",
@@ -1029,20 +1056,18 @@ DEBUG(D_any) do {
 
   init_lookup_list();
   for (i = 0; i < lookup_list_count; i++)
 
   init_lookup_list();
   for (i = 0; i < lookup_list_count; i++)
-    {
     if (lookup_list[i]->version_report)
     if (lookup_list[i]->version_report)
-      lookup_list[i]->version_report(f);
-    }
+      lookup_list[i]->version_report(fp);
 
 #ifdef WHITELIST_D_MACROS
 
 #ifdef WHITELIST_D_MACROS
-  fprintf(f, "WHITELIST_D_MACROS: \"%s\"\n", WHITELIST_D_MACROS);
+  fprintf(fp, "WHITELIST_D_MACROS: \"%s\"\n", WHITELIST_D_MACROS);
 #else
 #else
-  fprintf(f, "WHITELIST_D_MACROS unset\n");
+  fprintf(fp, "WHITELIST_D_MACROS unset\n");
 #endif
 #ifdef TRUSTED_CONFIG_LIST
 #endif
 #ifdef TRUSTED_CONFIG_LIST
-  fprintf(f, "TRUSTED_CONFIG_LIST: \"%s\"\n", TRUSTED_CONFIG_LIST);
+  fprintf(fp, "TRUSTED_CONFIG_LIST: \"%s\"\n", TRUSTED_CONFIG_LIST);
 #else
 #else
-  fprintf(f, "TRUSTED_CONFIG_LIST unset\n");
+  fprintf(fp, "TRUSTED_CONFIG_LIST unset\n");
 #endif
 
 } while (0);
 #endif
 
 } while (0);
@@ -1069,8 +1094,8 @@ switch(request)
 "If the string is not recognised, you'll get this help (on stderr).\n"
 "\n"
 "  exim -bI:help    this information\n"
 "If the string is not recognised, you'll get this help (on stderr).\n"
 "\n"
 "  exim -bI:help    this information\n"
-"  exim -bI:dscp    dscp value keywords known\n"
-"  exim -bI:sieve   list of supported sieve extensions, one per line.\n"
+"  exim -bI:dscp    list of known dscp value keywords\n"
+"  exim -bI:sieve   list of supported sieve extensions\n"
 );
     return;
   case CMDINFO_SIEVE:
 );
     return;
   case CMDINFO_SIEVE:
@@ -1100,8 +1125,7 @@ uschar *
 local_part_quote(uschar *lpart)
 {
 BOOL needs_quote = FALSE;
 local_part_quote(uschar *lpart)
 {
 BOOL needs_quote = FALSE;
-int size, ptr;
-uschar *yield;
+gstring * g;
 uschar *t;
 
 for (t = lpart; !needs_quote && *t != 0; t++)
 uschar *t;
 
 for (t = lpart; !needs_quote && *t != 0; t++)
@@ -1112,26 +1136,24 @@ for (t = lpart; !needs_quote && *t != 0; t++)
 
 if (!needs_quote) return lpart;
 
 
 if (!needs_quote) return lpart;
 
-size = ptr = 0;
-yield = string_cat(NULL, &size, &ptr, US"\"", 1);
+g = string_catn(NULL, US"\"", 1);
 
 for (;;)
   {
   uschar *nq = US Ustrpbrk(lpart, "\\\"");
   if (nq == NULL)
     {
 
 for (;;)
   {
   uschar *nq = US Ustrpbrk(lpart, "\\\"");
   if (nq == NULL)
     {
-    yield = string_cat(yield, &size, &ptr, lpart, Ustrlen(lpart));
+    g = string_cat(g, lpart);
     break;
     }
     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);
+  g = string_catn(g, lpart, nq - lpart);
+  g = string_catn(g, US"\\", 1);
+  g = string_catn(g, nq, 1);
   lpart = nq + 1;
   }
 
   lpart = nq + 1;
   }
 
-yield = string_cat(yield, &size, &ptr, US"\"", 1);
-yield[ptr] = 0;
-return yield;
+g = string_catn(g, US"\"", 1);
+return string_from_gstring(g);
 }
 
 
 }
 
 
@@ -1204,11 +1226,9 @@ static uschar *
 get_stdinput(char *(*fn_readline)(const char *), void(*fn_addhist)(const char *))
 {
 int i;
 get_stdinput(char *(*fn_readline)(const char *), void(*fn_addhist)(const char *))
 {
 int i;
-int size = 0;
-int ptr = 0;
-uschar *yield = NULL;
+gstring * g = NULL;
 
 
-if (fn_readline == NULL) { printf("> "); fflush(stdout); }
+if (!fn_readline) { printf("> "); fflush(stdout); }
 
 for (i = 0;; i++)
   {
 
 for (i = 0;; i++)
   {
@@ -1243,22 +1263,22 @@ for (i = 0;; i++)
     while (p < ss && isspace(*p)) p++;   /* leading space after cont */
     }
 
     while (p < ss && isspace(*p)) p++;   /* leading space after cont */
     }
 
-  yield = string_cat(yield, &size, &ptr, p, ss - p);
+  g = string_catn(g, p, ss - p);
 
   #ifdef USE_READLINE
 
   #ifdef USE_READLINE
-  if (fn_readline != NULL) free(readline_line);
+  if (fn_readline) free(readline_line);
   #endif
 
   #endif
 
-  if (ss == p || yield[ptr-1] != '\\')
-    {
-    yield[ptr] = 0;
+  /* g can only be NULL if ss==p */
+  if (ss == p || g->s[g->ptr-1] != '\\')
     break;
     break;
-    }
-  yield[--ptr] = 0;
+
+  --g->ptr;
+  (void) string_from_gstring(g);
   }
 
   }
 
-if (yield == NULL) printf("\n");
-return yield;
+if (!g) printf("\n");
+return string_from_gstring(g);
 }
 
 
 }
 
 
@@ -1280,22 +1300,17 @@ static void
 exim_usage(uschar *progname)
 {
 
 exim_usage(uschar *progname)
 {
 
-/* Handle specific program invocation varients */
+/* Handle specific program invocation variants */
 if (Ustrcmp(progname, US"-mailq") == 0)
 if (Ustrcmp(progname, US"-mailq") == 0)
-  {
-  fprintf(stderr,
+  exim_fail(
     "mailq - list the contents of the mail queue\n\n"
     "For a list of options, see the Exim documentation.\n");
     "mailq - list the contents of the mail queue\n\n"
     "For a list of options, see the Exim documentation.\n");
-  exit(EXIT_FAILURE);
-  }
 
 /* Generic usage - we output this whatever happens */
 
 /* Generic usage - we output this whatever happens */
-fprintf(stderr,
+exim_fail(
   "Exim is a Mail Transfer Agent. It is normally called by Mail User Agents,\n"
   "not directly from a shell command line. Options and/or arguments control\n"
   "what it does when called. For a list of options, see the Exim documentation.\n");
   "Exim is a Mail Transfer Agent. It is normally called by Mail User Agents,\n"
   "not directly from a shell command line. Options and/or arguments control\n"
   "what it does when called. For a list of options, see the Exim documentation.\n");
-
-exit(EXIT_FAILURE);
 }
 
 
 }
 
 
@@ -1307,12 +1322,12 @@ exit(EXIT_FAILURE);
 /* Typically, Exim will drop privileges if macros are supplied.  In some
 cases, we want to not do so.
 
 /* 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
 Returns:      true if trusted, false otherwise
 */
 
 static BOOL
-macros_trusted(void)
+macros_trusted(BOOL opt_D_used)
 {
 #ifdef WHITELIST_D_MACROS
 macro_item *m;
 {
 #ifdef WHITELIST_D_MACROS
 macro_item *m;
@@ -1322,7 +1337,7 @@ size_t len;
 BOOL prev_char_item, found;
 #endif
 
 BOOL prev_char_item, found;
 #endif
 
-if (macros == NULL)
+if (!opt_D_used)
   return TRUE;
 #ifndef WHITELIST_D_MACROS
 return FALSE;
   return TRUE;
 #ifndef WHITELIST_D_MACROS
 return FALSE;
@@ -1379,8 +1394,9 @@ for (p = whitelisted, i = 0; (p != end) && (i < white_count); ++p)
   }
 whites[i] = NULL;
 
   }
 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_user; m; m = m->next) if (m->command_line)
   {
   found = FALSE;
   for (w = whites; *w; ++w)
   {
   found = FALSE;
   for (w = whites; *w; ++w)
@@ -1391,10 +1407,9 @@ for (m = macros; m != NULL; m = m->next)
       }
   if (!found)
     return FALSE;
       }
   if (!found)
     return FALSE;
-  if (m->replacement == NULL)
+  if (!m->replacement)
     continue;
     continue;
-  len = Ustrlen(m->replacement);
-  if (len == 0)
+  if ((len = m->replen) == 0)
     continue;
   n = pcre_exec(regex_whitelisted_macro, NULL, CS m->replacement, len,
    0, PCRE_EOPT, NULL, 0);
     continue;
   n = pcre_exec(regex_whitelisted_macro, NULL, CS m->replacement, len,
    0, PCRE_EOPT, NULL, 0);
@@ -1411,6 +1426,40 @@ return TRUE;
 }
 
 
 }
 
 
+/*************************************************
+*          Expansion testing                    *
+*************************************************/
+
+/* Expand and print one item, doing macro-processing.
+
+Arguments:
+  item         line for expansion
+*/
+
+static void
+expansion_test_line(uschar * line)
+{
+int len;
+BOOL dummy_macexp;
+
+Ustrncpy(big_buffer, line, big_buffer_size);
+big_buffer[big_buffer_size-1] = '\0';
+len = Ustrlen(big_buffer);
+
+(void) macros_expand(0, &len, &dummy_macexp);
+
+if (isupper(big_buffer[0]))
+  {
+  if (macro_read_assignment(big_buffer))
+    printf("Defined macro '%s'\n", mlast->name);
+  }
+else
+  if ((line = expand_string(big_buffer))) printf("%s\n", CS line);
+  else printf("Failed: %s\n", expand_string_message);
+}
+
+
+
 /*************************************************
 *          Entry point and high-level code       *
 *************************************************/
 /*************************************************
 *          Entry point and high-level code       *
 *************************************************/
@@ -1453,6 +1502,7 @@ int  recipients_arg = argc;
 int  sender_address_domain = 0;
 int  test_retry_arg = -1;
 int  test_rewrite_arg = -1;
 int  sender_address_domain = 0;
 int  test_retry_arg = -1;
 int  test_rewrite_arg = -1;
+gid_t original_egid;
 BOOL arg_queue_only = FALSE;
 BOOL bi_option = FALSE;
 BOOL checking = FALSE;
 BOOL arg_queue_only = FALSE;
 BOOL bi_option = FALSE;
 BOOL checking = FALSE;
@@ -1466,9 +1516,11 @@ BOOL f_end_dot = FALSE;
 BOOL deliver_give_up = FALSE;
 BOOL list_queue = FALSE;
 BOOL list_options = 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 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;
 BOOL queue_only_set = FALSE;
 BOOL receiving_message = TRUE;
 BOOL sender_ident_set = FALSE;
@@ -1489,6 +1541,7 @@ uschar *ftest_domain = NULL;
 uschar *ftest_localpart = NULL;
 uschar *ftest_prefix = NULL;
 uschar *ftest_suffix = 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"/";
 uschar *malware_test_file = NULL;
 uschar *real_sender_address;
 uschar *originator_home = US"/";
@@ -1499,7 +1552,7 @@ struct passwd *pw;
 struct stat statbuf;
 pid_t passed_qr_pid = (pid_t)0;
 int passed_qr_pipe = -1;
 struct stat statbuf;
 pid_t passed_qr_pid = (pid_t)0;
 int passed_qr_pipe = -1;
-gid_t group_list[NGROUPS_MAX];
+gid_t group_list[EXIM_GROUPLIST_SIZE];
 
 /* For the -bI: flag */
 enum commandline_info info_flag = CMDINFO_NONE;
 
 /* For the -bI: flag */
 enum commandline_info info_flag = CMDINFO_NONE;
@@ -1523,49 +1576,32 @@ This is a feature to make the lives of binary distributors easier. */
 if (route_finduser(US EXIM_USERNAME, &pw, &exim_uid))
   {
   if (exim_uid == 0)
 if (route_finduser(US EXIM_USERNAME, &pw, &exim_uid))
   {
   if (exim_uid == 0)
-    {
-    fprintf(stderr, "exim: refusing to run with uid 0 for \"%s\"\n",
-      EXIM_USERNAME);
-    exit(EXIT_FAILURE);
-    }
+    exim_fail("exim: refusing to run with uid 0 for \"%s\"\n", EXIM_USERNAME);
+
   /* If ref:name uses a number as the name, route_finduser() returns
   TRUE with exim_uid set and pw coerced to NULL. */
   if (pw)
     exim_gid = pw->pw_gid;
 #ifndef EXIM_GROUPNAME
   else
   /* If ref:name uses a number as the name, route_finduser() returns
   TRUE with exim_uid set and pw coerced to NULL. */
   if (pw)
     exim_gid = pw->pw_gid;
 #ifndef EXIM_GROUPNAME
   else
-    {
-    fprintf(stderr,
+    exim_fail(
         "exim: ref:name should specify a usercode, not a group.\n"
         "exim: can't let you get away with it unless you also specify a group.\n");
         "exim: ref:name should specify a usercode, not a group.\n"
         "exim: can't let you get away with it unless you also specify a group.\n");
-    exit(EXIT_FAILURE);
-    }
 #endif
   }
 else
 #endif
   }
 else
-  {
-  fprintf(stderr, "exim: failed to find uid for user name \"%s\"\n",
-    EXIM_USERNAME);
-  exit(EXIT_FAILURE);
-  }
+  exim_fail("exim: failed to find uid for user name \"%s\"\n", EXIM_USERNAME);
 #endif
 
 #ifdef EXIM_GROUPNAME
 if (!route_findgroup(US EXIM_GROUPNAME, &exim_gid))
 #endif
 
 #ifdef EXIM_GROUPNAME
 if (!route_findgroup(US EXIM_GROUPNAME, &exim_gid))
-  {
-  fprintf(stderr, "exim: failed to find gid for group name \"%s\"\n",
-    EXIM_GROUPNAME);
-  exit(EXIT_FAILURE);
-  }
+  exim_fail("exim: failed to find gid for group name \"%s\"\n", EXIM_GROUPNAME);
 #endif
 
 #ifdef CONFIGURE_OWNERNAME
 if (!route_finduser(US CONFIGURE_OWNERNAME, NULL, &config_uid))
 #endif
 
 #ifdef CONFIGURE_OWNERNAME
 if (!route_finduser(US CONFIGURE_OWNERNAME, NULL, &config_uid))
-  {
-  fprintf(stderr, "exim: failed to find uid for user name \"%s\"\n",
+  exim_fail("exim: failed to find uid for user name \"%s\"\n",
     CONFIGURE_OWNERNAME);
     CONFIGURE_OWNERNAME);
-  exit(EXIT_FAILURE);
-  }
 #endif
 
 /* We default the system_filter_user to be the Exim run-time user, as a
 #endif
 
 /* We default the system_filter_user to be the Exim run-time user, as a
@@ -1574,15 +1610,13 @@ system_filter_uid = exim_uid;
 
 #ifdef CONFIGURE_GROUPNAME
 if (!route_findgroup(US CONFIGURE_GROUPNAME, &config_gid))
 
 #ifdef CONFIGURE_GROUPNAME
 if (!route_findgroup(US CONFIGURE_GROUPNAME, &config_gid))
-  {
-  fprintf(stderr, "exim: failed to find gid for group name \"%s\"\n",
+  exim_fail("exim: failed to find gid for group name \"%s\"\n",
     CONFIGURE_GROUPNAME);
     CONFIGURE_GROUPNAME);
-  exit(EXIT_FAILURE);
-  }
 #endif
 
 #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
 
 #ifdef OS_INIT
 OS_INIT
@@ -1591,8 +1625,10 @@ OS_INIT
 /* Check a field which is patched when we are running Exim within its
 testing harness; do a fast initial check, and then the whole thing. */
 
 /* Check a field which is patched when we are running Exim within its
 testing harness; do a fast initial check, and then the whole thing. */
 
-running_in_test_harness =
+f.running_in_test_harness =
   *running_status == '<' && Ustrcmp(running_status, "<<<testing>>>") == 0;
   *running_status == '<' && Ustrcmp(running_status, "<<<testing>>>") == 0;
+if (f.running_in_test_harness)
+  debug_store = TRUE;
 
 /* The C standard says that the equivalent of setlocale(LC_ALL, "C") is obeyed
 at the start of a program; however, it seems that some environments do not
 
 /* The C standard says that the equivalent of setlocale(LC_ALL, "C") is obeyed
 at the start of a program; however, it seems that some environments do not
@@ -1608,12 +1644,12 @@ 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. */
 
 /* 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)
-  {
-  fprintf(stderr, "exim: failed to get store for log buffer\n");
-  exit(EXIT_FAILURE);
-  }
+if (!(log_buffer = US malloc(LOG_BUFFER_SIZE)))
+  exim_fail("exim: failed to get store for log buffer\n");
+
+/* 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
 
 /* 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
@@ -1642,6 +1678,10 @@ descriptive text. */
 set_process_info("initializing");
 os_restarting_signal(SIGUSR1, usr1_handler);
 
 set_process_info("initializing");
 os_restarting_signal(SIGUSR1, usr1_handler);
 
+/* If running in a dockerized environment, the TERM signal is only
+delegated to the PID 1 if we request it by setting an signal handler */
+if (getpid() == 1) signal(SIGTERM, term_handler);
+
 /* SIGHUP is used to get the daemon to reconfigure. It gets set as appropriate
 in the daemon code. For the rest of Exim's uses, we ignore it. */
 
 /* SIGHUP is used to get the daemon to reconfigure. It gets set as appropriate
 in the daemon code. For the rest of Exim's uses, we ignore it. */
 
@@ -1725,6 +1765,7 @@ regex_whitelisted_macro =
   regex_must_compile(US"^[A-Za-z0-9_/.-]*$", FALSE, TRUE);
 #endif
 
   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
 
 /* 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
@@ -1747,7 +1788,7 @@ message has been sent). */
 if ((namelen == 5 && Ustrcmp(argv[0], "rmail") == 0) ||
     (namelen  > 5 && Ustrncmp(argv[0] + namelen - 6, "/rmail", 6) == 0))
   {
 if ((namelen == 5 && Ustrcmp(argv[0], "rmail") == 0) ||
     (namelen  > 5 && Ustrncmp(argv[0] + namelen - 6, "/rmail", 6) == 0))
   {
-  dot_ends = FALSE;
+  f.dot_ends = FALSE;
   called_as = US"-rmail";
   errors_sender_rc = EXIT_SUCCESS;
   }
   called_as = US"-rmail";
   errors_sender_rc = EXIT_SUCCESS;
   }
@@ -1788,6 +1829,7 @@ if ((namelen == 10 && Ustrcmp(argv[0], "newaliases") == 0) ||
 normally be root, but in some esoteric environments it may not be. */
 
 original_euid = geteuid();
 normally be root, but in some esoteric environments it may not be. */
 
 original_euid = geteuid();
+original_egid = getegid();
 
 /* Get the real uid and gid. If the caller is root, force the effective uid/gid
 to be the same as the real ones. This makes a difference only if Exim is setuid
 
 /* Get the real uid and gid. If the caller is root, force the effective uid/gid
 to be the same as the real ones. This makes a difference only if Exim is setuid
@@ -1799,20 +1841,12 @@ real_gid = getgid();
 
 if (real_uid == root_uid)
   {
 
 if (real_uid == root_uid)
   {
-  rv = setgid(real_gid);
-  if (rv)
-    {
-    fprintf(stderr, "exim: setgid(%ld) failed: %s\n",
+  if ((rv = setgid(real_gid)))
+    exim_fail("exim: setgid(%ld) failed: %s\n",
         (long int)real_gid, strerror(errno));
         (long int)real_gid, strerror(errno));
-    exit(EXIT_FAILURE);
-    }
-  rv = setuid(real_uid);
-  if (rv)
-    {
-    fprintf(stderr, "exim: setuid(%ld) failed: %s\n",
+  if ((rv = setuid(real_uid)))
+    exim_fail("exim: setuid(%ld) failed: %s\n",
         (long int)real_uid, strerror(errno));
         (long int)real_uid, strerror(errno));
-    exit(EXIT_FAILURE);
-    }
   }
 
 /* If neither the original real uid nor the original euid was root, Exim is
   }
 
 /* If neither the original real uid nor the original euid was root, Exim is
@@ -1840,7 +1874,7 @@ for (i = 1; i < argc; i++)
     break;
     }
 
     break;
     }
 
-  /* An option consistion of -- terminates the options */
+  /* An option consisting of -- terminates the options */
 
   if (Ustrcmp(arg, "--") == 0)
     {
 
   if (Ustrcmp(arg, "--") == 0)
     {
@@ -1869,7 +1903,7 @@ for (i = 1; i < argc; i++)
     {
     switchchar = arg[3];
     argrest += 2;
     {
     switchchar = arg[3];
     argrest += 2;
-    queue_2stage = TRUE;
+    f.queue_2stage = TRUE;
     }
 
   /* Make -r synonymous with -f, since it is a documented alias */
     }
 
   /* Make -r synonymous with -f, since it is a documented alias */
@@ -1940,8 +1974,8 @@ for (i = 1; i < argc; i++)
 
     if (*argrest == 'd')
       {
 
     if (*argrest == 'd')
       {
-      daemon_listen = TRUE;
-      if (*(++argrest) == 'f') background_daemon = FALSE;
+      f.daemon_listen = TRUE;
+      if (*(++argrest) == 'f') f.background_daemon = FALSE;
         else if (*argrest != 0) { badarg = TRUE; break; }
       }
 
         else if (*argrest != 0) { badarg = TRUE; break; }
       }
 
@@ -1965,13 +1999,10 @@ for (i = 1; i < argc; i++)
 
     else if (*argrest == 'F')
       {
 
     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
       if (*(++argrest) != 0) { badarg = TRUE; break; }
       if (++i < argc) filter_test_sfile = argv[i]; else
-        {
-        fprintf(stderr, "exim: file name expected after %s\n", argv[i-1]);
-        exit(EXIT_FAILURE);
-        }
+        exim_fail("exim: file name expected after %s\n", argv[i-1]);
       }
 
     /* -bf:  Run user filter test
       }
 
     /* -bf:  Run user filter test
@@ -1985,20 +2016,14 @@ for (i = 1; i < argc; i++)
       {
       if (*(++argrest) == 0)
         {
       {
       if (*(++argrest) == 0)
         {
-        filter_test |= FTEST_USER;
+        filter_test |= checking = FTEST_USER;
         if (++i < argc) filter_test_ufile = argv[i]; else
         if (++i < argc) filter_test_ufile = argv[i]; else
-          {
-          fprintf(stderr, "exim: file name expected after %s\n", argv[i-1]);
-          exit(EXIT_FAILURE);
-          }
+          exim_fail("exim: file name expected after %s\n", argv[i-1]);
         }
       else
         {
         if (++i >= argc)
         }
       else
         {
         if (++i >= argc)
-          {
-          fprintf(stderr, "exim: string expected after %s\n", arg);
-          exit(EXIT_FAILURE);
-          }
+          exim_fail("exim: string expected after %s\n", arg);
         if (Ustrcmp(argrest, "d") == 0) ftest_domain = argv[i];
         else if (Ustrcmp(argrest, "l") == 0) ftest_localpart = argv[i];
         else if (Ustrcmp(argrest, "p") == 0) ftest_prefix = argv[i];
         if (Ustrcmp(argrest, "d") == 0) ftest_domain = argv[i];
         else if (Ustrcmp(argrest, "l") == 0) ftest_localpart = argv[i];
         else if (Ustrcmp(argrest, "p") == 0) ftest_prefix = argv[i];
@@ -2013,8 +2038,9 @@ for (i = 1; i < argc; i++)
       {
       if (++i >= argc) { badarg = TRUE; break; }
       sender_host_address = argv[i];
       {
       if (++i >= argc) { badarg = TRUE; break; }
       sender_host_address = argv[i];
-      host_checking = checking = log_testing_mode = TRUE;
-      host_checking_callout = argrest[1] == 'c';
+      host_checking = checking = f.log_testing_mode = TRUE;
+      f.host_checking_callout = argrest[1] == 'c';
+      message_logs = FALSE;
       }
 
     /* -bi: This option is used by sendmail to initialize *the* alias file,
       }
 
     /* -bi: This option is used by sendmail to initialize *the* alias file,
@@ -2060,6 +2086,7 @@ for (i = 1; i < argc; i++)
     else if (Ustrcmp(argrest, "malware") == 0)
       {
       if (++i >= argc) { badarg = TRUE; break; }
     else if (Ustrcmp(argrest, "malware") == 0)
       {
       if (++i >= argc) { badarg = TRUE; break; }
+      checking = TRUE;
       malware_test_file = argv[i];
       }
 
       malware_test_file = argv[i];
       }
 
@@ -2069,8 +2096,8 @@ for (i = 1; i < argc; i++)
 
     else if (Ustrcmp(argrest, "nq") == 0)
       {
 
     else if (Ustrcmp(argrest, "nq") == 0)
       {
-      allow_unqualified_sender = FALSE;
-      allow_unqualified_recipient = FALSE;
+      f.allow_unqualified_sender = FALSE;
+      f.allow_unqualified_recipient = FALSE;
       }
 
     /* -bpxx: List the contents of the mail queue, in various forms. If
       }
 
     /* -bpxx: List the contents of the mail queue, in various forms. If
@@ -2122,15 +2149,26 @@ for (i = 1; i < argc; i++)
 
     else if (Ustrcmp(argrest, "P") == 0)
       {
 
     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)
       {
       }
 
     /* -brt: Test retry configuration lookup */
 
     else if (Ustrcmp(argrest, "rt") == 0)
       {
+      checking = TRUE;
       test_retry_arg = i + 1;
       goto END_ARG;
       }
       test_retry_arg = i + 1;
       goto END_ARG;
       }
@@ -2139,6 +2177,7 @@ for (i = 1; i < argc; i++)
 
     else if (Ustrcmp(argrest, "rw") == 0)
       {
 
     else if (Ustrcmp(argrest, "rw") == 0)
       {
+      checking = TRUE;
       test_rewrite_arg = i + 1;
       goto END_ARG;
       }
       test_rewrite_arg = i + 1;
       goto END_ARG;
       }
@@ -2157,18 +2196,18 @@ for (i = 1; i < argc; i++)
     /* -bt: address testing mode */
 
     else if (Ustrcmp(argrest, "t") == 0)
     /* -bt: address testing mode */
 
     else if (Ustrcmp(argrest, "t") == 0)
-      address_test_mode = checking = log_testing_mode = TRUE;
+      f.address_test_mode = checking = f.log_testing_mode = TRUE;
 
     /* -bv: verify addresses */
 
     else if (Ustrcmp(argrest, "v") == 0)
 
     /* -bv: verify addresses */
 
     else if (Ustrcmp(argrest, "v") == 0)
-      verify_address_mode = checking = log_testing_mode = TRUE;
+      verify_address_mode = checking = f.log_testing_mode = TRUE;
 
     /* -bvs: verify sender addresses */
 
     else if (Ustrcmp(argrest, "vs") == 0)
       {
 
     /* -bvs: verify sender addresses */
 
     else if (Ustrcmp(argrest, "vs") == 0)
       {
-      verify_address_mode = checking = log_testing_mode = TRUE;
+      verify_address_mode = checking = f.log_testing_mode = TRUE;
       verify_as_sender = TRUE;
       }
 
       verify_as_sender = TRUE;
       }
 
@@ -2181,24 +2220,19 @@ for (i = 1; i < argc; i++)
       printf("%s\n", CS version_copyright);
       version_printed = TRUE;
       show_whats_supported(stdout);
       printf("%s\n", CS version_copyright);
       version_printed = TRUE;
       show_whats_supported(stdout);
+      f.log_testing_mode = TRUE;
       }
 
     /* -bw: inetd wait mode, accept a listening socket as stdin */
 
     else if (*argrest == 'w')
       {
       }
 
     /* -bw: inetd wait mode, accept a listening socket as stdin */
 
     else if (*argrest == 'w')
       {
-      inetd_wait_mode = TRUE;
-      background_daemon = FALSE;
-      daemon_listen = TRUE;
+      f.inetd_wait_mode = TRUE;
+      f.background_daemon = FALSE;
+      f.daemon_listen = TRUE;
       if (*(++argrest) != '\0')
       if (*(++argrest) != '\0')
-        {
-        inetd_wait_timeout = readconf_readtime(argrest, 0, FALSE);
-        if (inetd_wait_timeout <= 0)
-          {
-          fprintf(stderr, "exim: bad time value %s: abandoned\n", argv[i]);
-          exit(EXIT_FAILURE);
-          }
-        }
+        if ((inetd_wait_timeout = readconf_readtime(argrest, 0, FALSE)) <= 0)
+          exim_fail("exim: bad time value %s: abandoned\n", argv[i]);
       }
 
     else badarg = TRUE;
       }
 
     else badarg = TRUE;
@@ -2219,7 +2253,7 @@ for (i = 1; i < argc; i++)
       #ifdef ALT_CONFIG_PREFIX
       int sep = 0;
       int len = Ustrlen(ALT_CONFIG_PREFIX);
       #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)
       uschar *filename;
       while((filename = string_nextinlist(&list, &sep, big_buffer,
              big_buffer_size)) != NULL)
@@ -2228,10 +2262,7 @@ for (i = 1; i < argc; i++)
              Ustrncmp(filename, ALT_CONFIG_PREFIX, len) != 0 ||
              Ustrstr(filename, "/../") != NULL) &&
              (Ustrcmp(filename, "/dev/null") != 0 || real_uid != root_uid))
              Ustrncmp(filename, ALT_CONFIG_PREFIX, len) != 0 ||
              Ustrstr(filename, "/../") != NULL) &&
              (Ustrcmp(filename, "/dev/null") != 0 || real_uid != root_uid))
-          {
-          fprintf(stderr, "-C Permission denied\n");
-          exit(EXIT_FAILURE);
-          }
+          exim_fail("-C Permission denied\n");
         }
       #endif
       if (real_uid != root_uid)
         }
       #endif
       if (real_uid != root_uid)
@@ -2243,7 +2274,7 @@ for (i = 1; i < argc; i++)
             && real_uid != config_uid
             #endif
             )
             && real_uid != config_uid
             #endif
             )
-          trusted_config = FALSE;
+          f.trusted_config = FALSE;
         else
           {
           FILE *trust_list = Ufopen(TRUSTED_CONFIG_LIST, "rb");
         else
           {
           FILE *trust_list = Ufopen(TRUSTED_CONFIG_LIST, "rb");
@@ -2265,7 +2296,7 @@ for (i = 1; i < argc; i++)
                    ) ||                            /* or */
                 (statbuf.st_mode & 2) != 0)        /* world writeable */
               {
                    ) ||                            /* or */
                 (statbuf.st_mode & 2) != 0)        /* world writeable */
               {
-              trusted_config = FALSE;
+              f.trusted_config = FALSE;
               fclose(trust_list);
               }
            else
               fclose(trust_list);
               }
            else
@@ -2295,9 +2326,9 @@ for (i = 1; i < argc; i++)
               if (nr_configs)
                 {
                 int sep = 0;
               if (nr_configs)
                 {
                 int sep = 0;
-                uschar *list = argrest;
+                const uschar *list = argrest;
                 uschar *filename;
                 uschar *filename;
-                while (trusted_config && (filename = string_nextinlist(&list,
+                while (f.trusted_config && (filename = string_nextinlist(&list,
                         &sep, big_buffer, big_buffer_size)) != NULL)
                   {
                   for (i=0; i < nr_configs; i++)
                         &sep, big_buffer, big_buffer_size)) != NULL)
                   {
                   for (i=0; i < nr_configs; i++)
@@ -2307,7 +2338,7 @@ for (i = 1; i < argc; i++)
                     }
                   if (i == nr_configs)
                     {
                     }
                   if (i == nr_configs)
                     {
-                    trusted_config = FALSE;
+                    f.trusted_config = FALSE;
                     break;
                     }
                   }
                     break;
                     }
                   }
@@ -2316,24 +2347,24 @@ for (i = 1; i < argc; i++)
               else
                 {
                 /* No valid prefixes found in trust_list file. */
               else
                 {
                 /* No valid prefixes found in trust_list file. */
-                trusted_config = FALSE;
+                f.trusted_config = FALSE;
                 }
               }
            }
           else
             {
             /* Could not open trust_list file. */
                 }
               }
            }
           else
             {
             /* Could not open trust_list file. */
-            trusted_config = FALSE;
+            f.trusted_config = FALSE;
             }
           }
       #else
         /* Not root; don't trust config */
             }
           }
       #else
         /* Not root; don't trust config */
-        trusted_config = FALSE;
+        f.trusted_config = FALSE;
       #endif
         }
 
       config_main_filelist = argrest;
       #endif
         }
 
       config_main_filelist = argrest;
-      config_changed = TRUE;
+      f.config_changed = TRUE;
       }
     break;
 
       }
     break;
 
@@ -2341,25 +2372,21 @@ for (i = 1; i < argc; i++)
     /* -D: set up a macro definition */
 
     case 'D':
     /* -D: set up a macro definition */
 
     case 'D':
-    #ifdef DISABLE_D_OPTION
-    fprintf(stderr, "exim: -D is not available in this Exim binary\n");
-    exit(EXIT_FAILURE);
-    #else
+#ifdef DISABLE_D_OPTION
+      exim_fail("exim: -D is not available in this Exim binary\n");
+#else
       {
       int ptr = 0;
       {
       int ptr = 0;
-      macro_item *mlast = NULL;
       macro_item *m;
       uschar name[24];
       uschar *s = argrest;
 
       macro_item *m;
       uschar name[24];
       uschar *s = argrest;
 
+      opt_D_used = TRUE;
       while (isspace(*s)) s++;
 
       if (*s < 'A' || *s > 'Z')
       while (isspace(*s)) s++;
 
       if (*s < 'A' || *s > 'Z')
-        {
-        fprintf(stderr, "exim: macro name set by -D must start with "
+        exim_fail("exim: macro name set by -D must start with "
           "an upper case letter\n");
           "an upper case letter\n");
-        exit(EXIT_FAILURE);
-        }
 
       while (isalnum(*s) || *s == '_')
         {
 
       while (isalnum(*s) || *s == '_')
         {
@@ -2375,28 +2402,14 @@ for (i = 1; i < argc; i++)
         while (isspace(*s)) s++;
         }
 
         while (isspace(*s)) s++;
         }
 
-      for (m = macros; m != NULL; m = m->next)
-        {
+      for (m = macros_user; m; m = m->next)
         if (Ustrcmp(m->name, name) == 0)
         if (Ustrcmp(m->name, name) == 0)
-          {
-          fprintf(stderr, "exim: duplicated -D in command line\n");
-          exit(EXIT_FAILURE);
-          }
-        mlast = m;
-        }
+          exim_fail("exim: duplicated -D in command line\n");
 
 
-      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);
 
       if (clmacro_count >= MAX_CLMACROS)
 
       if (clmacro_count >= MAX_CLMACROS)
-        {
-        fprintf(stderr, "exim: too many -D options on command line\n");
-        exit(EXIT_FAILURE);
-        }
+        exim_fail("exim: too many -D options on command line\n");
       clmacros[clmacro_count++] = string_sprintf("-D%s=%s", m->name,
         m->replacement);
       }
       clmacros[clmacro_count++] = string_sprintf("-D%s=%s", m->name,
         m->replacement);
       }
@@ -2423,12 +2436,12 @@ for (i = 1; i < argc; i++)
       debug_file = NULL;
       if (*argrest == 'd')
         {
       debug_file = NULL;
       if (*argrest == 'd')
         {
-        debug_daemon = TRUE;
+        f.debug_daemon = TRUE;
         argrest++;
         }
       if (*argrest != 0)
         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;
       debug_selector = selector;
       }
     break;
@@ -2442,7 +2455,7 @@ for (i = 1; i < argc; i++)
     message_reference at it, for logging. */
 
     case 'E':
     message_reference at it, for logging. */
 
     case 'E':
-    local_error_message = TRUE;
+    f.local_error_message = TRUE;
     if (mac_ismsgid(argrest)) message_reference = argrest;
     break;
 
     if (mac_ismsgid(argrest)) message_reference = argrest;
     break;
 
@@ -2479,7 +2492,7 @@ for (i = 1; i < argc; i++)
         { badarg = TRUE; break; }
       }
     originator_name = argrest;
         { badarg = TRUE; break; }
       }
     originator_name = argrest;
-    sender_name_forced = TRUE;
+    f.sender_name_forced = TRUE;
     break;
 
 
     break;
 
 
@@ -2500,7 +2513,7 @@ for (i = 1; i < argc; i++)
 
     case 'f':
       {
 
     case 'f':
       {
-      int start, end;
+      int dummy_start, dummy_end;
       uschar *errmess;
       if (*argrest == 0)
         {
       uschar *errmess;
       if (*argrest == 0)
         {
@@ -2508,9 +2521,7 @@ for (i = 1; i < argc; i++)
           { badarg = TRUE; break; }
         }
       if (*argrest == 0)
           { badarg = TRUE; break; }
         }
       if (*argrest == 0)
-        {
         sender_address = string_sprintf("");  /* Ensure writeable memory */
         sender_address = string_sprintf("");  /* Ensure writeable memory */
-        }
       else
         {
         uschar *temp = argrest + Ustrlen(argrest) - 1;
       else
         {
         uschar *temp = argrest + Ustrlen(argrest) - 1;
@@ -2518,17 +2529,21 @@ for (i = 1; i < argc; i++)
         if (temp >= argrest && *temp == '.') f_end_dot = TRUE;
         allow_domain_literals = TRUE;
         strip_trailing_dot = TRUE;
         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;
         allow_domain_literals = FALSE;
         strip_trailing_dot = FALSE;
-        if (sender_address == NULL)
-          {
-          fprintf(stderr, "exim: bad -f address \"%s\": %s\n", argrest, errmess);
-          return EXIT_FAILURE;
-          }
+        if (!sender_address)
+          exim_fail("exim: bad -f address \"%s\": %s\n", argrest, errmess);
         }
         }
-      sender_address_forced = TRUE;
+      f.sender_address_forced = TRUE;
       }
     break;
 
       }
     break;
 
@@ -2559,7 +2574,7 @@ for (i = 1; i < argc; i++)
     not to be documented for sendmail but mailx (at least) uses it) */
 
     case 'i':
     not to be documented for sendmail but mailx (at least) uses it) */
 
     case 'i':
-    if (*argrest == 0) dot_ends = FALSE; else badarg = TRUE;
+    if (*argrest == 0) f.dot_ends = FALSE; else badarg = TRUE;
     break;
 
 
     break;
 
 
@@ -2572,17 +2587,10 @@ for (i = 1; i < argc; i++)
       if(++i < argc) argrest = argv[i]; else
         { badarg = TRUE; break; }
       }
       if(++i < argc) argrest = argv[i]; else
         { badarg = TRUE; break; }
       }
-    sz = Ustrlen(argrest);
-    if (sz > 32)
-      {
-      fprintf(stderr, "exim: the -L syslog name is too long: \"%s\"\n", argrest);
-      return EXIT_FAILURE;
-      }
+    if ((sz = Ustrlen(argrest)) > 32)
+      exim_fail("exim: the -L syslog name is too long: \"%s\"\n", argrest);
     if (sz < 1)
     if (sz < 1)
-      {
-      fprintf(stderr, "exim: the -L syslog name is too short\n");
-      return EXIT_FAILURE;
-      }
+      exim_fail("exim: the -L syslog name is too short\n");
     cmdline_syslog_name = argrest;
     break;
 
     cmdline_syslog_name = argrest;
     break;
 
@@ -2608,16 +2616,10 @@ for (i = 1; i < argc; i++)
       EXIM_SOCKLEN_T size = sizeof(interface_sock);
 
       if (argc != i + 6)
       EXIM_SOCKLEN_T size = sizeof(interface_sock);
 
       if (argc != i + 6)
-        {
-        fprintf(stderr, "exim: too many or too few arguments after -MC\n");
-        return EXIT_FAILURE;
-        }
+        exim_fail("exim: too many or too few arguments after -MC\n");
 
       if (msg_action_arg >= 0)
 
       if (msg_action_arg >= 0)
-        {
-        fprintf(stderr, "exim: incompatible arguments\n");
-        return EXIT_FAILURE;
-        }
+        exim_fail("exim: incompatible arguments\n");
 
       continue_transport = argv[++i];
       continue_hostname = argv[++i];
 
       continue_transport = argv[++i];
       continue_hostname = argv[++i];
@@ -2630,91 +2632,104 @@ for (i = 1; i < argc; i++)
       queue_run_pipe = passed_qr_pipe;
 
       if (!mac_ismsgid(argv[i]))
       queue_run_pipe = passed_qr_pipe;
 
       if (!mac_ismsgid(argv[i]))
-        {
-        fprintf(stderr, "exim: malformed message id %s after -MC option\n",
+        exim_fail("exim: malformed message id %s after -MC option\n",
           argv[i]);
           argv[i]);
-        return EXIT_FAILURE;
-        }
 
 
-      /* Set up $sending_ip_address and $sending_port */
+      /* Set up $sending_ip_address and $sending_port, unless proxied */
 
 
-      if (getsockname(fileno(stdin), (struct sockaddr *)(&interface_sock),
-          &size) == 0)
-        sending_ip_address = host_ntoa(-1, &interface_sock, NULL,
-          &sending_port);
-      else
-        {
-        fprintf(stderr, "exim: getsockname() failed after -MC option: %s\n",
-          strerror(errno));
-        return EXIT_FAILURE;
-        }
+      if (!continue_proxy_cipher)
+       if (getsockname(fileno(stdin), (struct sockaddr *)(&interface_sock),
+           &size) == 0)
+         sending_ip_address = host_ntoa(-1, &interface_sock, NULL,
+           &sending_port);
+       else
+         exim_fail("exim: getsockname() failed after -MC option: %s\n",
+           strerror(errno));
 
 
-      if (running_in_test_harness) millisleep(500);
+      if (f.running_in_test_harness) millisleep(500);
       break;
       }
 
       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. */
 
     /* -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': f.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 */
     /* -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 |= OPTION_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 |= OPTION_CHUNKING; break;
 
     /* -MCP: set the smtp_use_pipelining flag; this is useful only when
     it preceded -MC (see above) */
 
 
     /* -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 |= OPTION_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) */
 
 
     /* -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) */
 
 
     /* -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 |= OPTION_SIZE; break;
+
+#ifdef SUPPORT_TLS
+    /* -MCt: similar to -MCT below but the connection is still open
+    via a proxy process which handles the TLS context and coding.
+    Require three arguments for the proxied local address and port,
+    and the TLS cipher.  */
+
+       case 't': if (++i < argc) sending_ip_address = argv[i];
+                 else badarg = TRUE;
+                 if (++i < argc) sending_port = (int)(Uatol(argv[i]));
+                 else badarg = TRUE;
+                 if (++i < argc) continue_proxy_cipher = argv[i];
+                 else badarg = TRUE;
+                 /*FALLTHROUGH*/
 
     /* -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. */
 
 
     /* -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)
+       case 'T': smtp_peer_options |= OPTION_TLS; break;
+#endif
+
+       default:  badarg = TRUE; break;
+       }
+      break;
+      }
+
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+    /* -MS   set REQUIRETLS on (new) message */
+
+    else if (*argrest == 'S')
       {
       {
-      tls_offered = TRUE;
+      tls_requiretls |= REQUIRETLS_MSG;
       break;
       }
       break;
       }
-    #endif
+#endif
 
     /* -M[x]: various operations on the following list of message ids:
        -M    deliver the messages, ignoring next retry times and thawing
 
     /* -M[x]: various operations on the following list of message ids:
        -M    deliver the messages, ignoring next retry times and thawing
@@ -2740,7 +2755,7 @@ for (i = 1; i < argc; i++)
     else if (*argrest == 0)
       {
       msg_action = MSG_DELIVER;
     else if (*argrest == 0)
       {
       msg_action = MSG_DELIVER;
-      forced_delivery = deliver_force_thaw = TRUE;
+      forced_delivery = f.deliver_force_thaw = TRUE;
       }
     else if (Ustrcmp(argrest, "ar") == 0)
       {
       }
     else if (Ustrcmp(argrest, "ar") == 0)
       {
@@ -2801,10 +2816,7 @@ for (i = 1; i < argc; i++)
 
     msg_action_arg = i + 1;
     if (msg_action_arg >= argc)
 
     msg_action_arg = i + 1;
     if (msg_action_arg >= argc)
-      {
-      fprintf(stderr, "exim: no message ids given after %s option\n", arg);
-      return EXIT_FAILURE;
-      }
+      exim_fail("exim: no message ids given after %s option\n", arg);
 
     /* Some require only message ids to follow */
 
 
     /* Some require only message ids to follow */
 
@@ -2812,11 +2824,8 @@ for (i = 1; i < argc; i++)
       {
       int j;
       for (j = msg_action_arg; j < argc; j++) if (!mac_ismsgid(argv[j]))
       {
       int j;
       for (j = msg_action_arg; j < argc; j++) if (!mac_ismsgid(argv[j]))
-        {
-        fprintf(stderr, "exim: malformed message id %s after %s option\n",
+        exim_fail("exim: malformed message id %s after %s option\n",
           argv[j], arg);
           argv[j], arg);
-        return EXIT_FAILURE;
-        }
       goto END_ARG;   /* Remaining args are ids */
       }
 
       goto END_ARG;   /* Remaining args are ids */
       }
 
@@ -2826,11 +2835,8 @@ for (i = 1; i < argc; i++)
     else
       {
       if (!mac_ismsgid(argv[msg_action_arg]))
     else
       {
       if (!mac_ismsgid(argv[msg_action_arg]))
-        {
-        fprintf(stderr, "exim: malformed message id %s after %s option\n",
+        exim_fail("exim: malformed message id %s after %s option\n",
           argv[msg_action_arg], arg);
           argv[msg_action_arg], arg);
-        return EXIT_FAILURE;
-        }
       i++;
       }
     break;
       i++;
       }
     break;
@@ -2850,7 +2856,7 @@ for (i = 1; i < argc; i++)
     case 'N':
     if (*argrest == 0)
       {
     case 'N':
     if (*argrest == 0)
       {
-      dont_deliver = TRUE;
+      f.dont_deliver = TRUE;
       debug_selector |= D_v;
       debug_file = stderr;
       }
       debug_selector |= D_v;
       debug_file = stderr;
       }
@@ -2874,10 +2880,7 @@ for (i = 1; i < argc; i++)
     if (*argrest == 0)
       {
       if (++i >= argc)
     if (*argrest == 0)
       {
       if (++i >= argc)
-        {
-        fprintf(stderr, "exim: string expected after -O\n");
-        exit(EXIT_FAILURE);
-        }
+        exim_fail("exim: string expected after -O\n");
       }
     break;
 
       }
     break;
 
@@ -2892,10 +2895,7 @@ for (i = 1; i < argc; i++)
       if (alias_arg[0] == 0)
         {
         if (i+1 < argc) alias_arg = argv[++i]; else
       if (alias_arg[0] == 0)
         {
         if (i+1 < argc) alias_arg = argv[++i]; else
-          {
-          fprintf(stderr, "exim: string expected after -oA\n");
-          exit(EXIT_FAILURE);
-          }
+          exim_fail("exim: string expected after -oA\n");
         }
       }
 
         }
       }
 
@@ -2916,10 +2916,7 @@ for (i = 1; i < argc; i++)
       if (p != NULL)
         {
         if (!isdigit(*p))
       if (p != NULL)
         {
         if (!isdigit(*p))
-          {
-          fprintf(stderr, "exim: number expected after -oB\n");
-          exit(EXIT_FAILURE);
-          }
+          exim_fail("exim: number expected after -oB\n");
         connection_max_messages = Uatoi(p);
         }
       }
         connection_max_messages = Uatoi(p);
         }
       }
@@ -2928,7 +2925,7 @@ for (i = 1; i < argc; i++)
 
     else if (Ustrcmp(argrest, "db") == 0)
       {
 
     else if (Ustrcmp(argrest, "db") == 0)
       {
-      synchronous_delivery = FALSE;
+      f.synchronous_delivery = FALSE;
       arg_queue_only = FALSE;
       queue_only_set = TRUE;
       }
       arg_queue_only = FALSE;
       queue_only_set = TRUE;
       }
@@ -2939,7 +2936,7 @@ for (i = 1; i < argc; i++)
 
     else if (Ustrcmp(argrest, "df") == 0 || Ustrcmp(argrest, "di") == 0)
       {
 
     else if (Ustrcmp(argrest, "df") == 0 || Ustrcmp(argrest, "di") == 0)
       {
-      synchronous_delivery = TRUE;
+      f.synchronous_delivery = TRUE;
       arg_queue_only = FALSE;
       queue_only_set = TRUE;
       }
       arg_queue_only = FALSE;
       queue_only_set = TRUE;
       }
@@ -2948,7 +2945,7 @@ for (i = 1; i < argc; i++)
 
     else if (Ustrcmp(argrest, "dq") == 0)
       {
 
     else if (Ustrcmp(argrest, "dq") == 0)
       {
-      synchronous_delivery = FALSE;
+      f.synchronous_delivery = FALSE;
       arg_queue_only = TRUE;
       queue_only_set = TRUE;
       }
       arg_queue_only = TRUE;
       queue_only_set = TRUE;
       }
@@ -2958,7 +2955,7 @@ for (i = 1; i < argc; i++)
 
     else if (Ustrcmp(argrest, "dqs") == 0)
       {
 
     else if (Ustrcmp(argrest, "dqs") == 0)
       {
-      queue_smtp = TRUE;
+      f.queue_smtp = TRUE;
       arg_queue_only = FALSE;
       queue_only_set = TRUE;
       }
       arg_queue_only = FALSE;
       queue_only_set = TRUE;
       }
@@ -2972,7 +2969,7 @@ for (i = 1; i < argc; i++)
 
     else if (Ustrcmp(argrest, "i") == 0 ||
              Ustrcmp(argrest, "itrue") == 0)
 
     else if (Ustrcmp(argrest, "i") == 0 ||
              Ustrcmp(argrest, "itrue") == 0)
-      dot_ends = FALSE;
+      f.dot_ends = FALSE;
 
     /* -oM*: Set various characteristics for an incoming message; actually
     acted on for trusted callers only. */
 
     /* -oM*: Set various characteristics for an incoming message; actually
     acted on for trusted callers only. */
@@ -2980,10 +2977,7 @@ for (i = 1; i < argc; i++)
     else if (*argrest == 'M')
       {
       if (i+1 >= argc)
     else if (*argrest == 'M')
       {
       if (i+1 >= argc)
-        {
-        fprintf(stderr, "exim: data expected after -o%s\n", argrest);
-        exit(EXIT_FAILURE);
-        }
+        exim_fail("exim: data expected after -o%s\n", argrest);
 
       /* -oMa: Set sender host address */
 
 
       /* -oMa: Set sender host address */
 
@@ -3011,21 +3005,20 @@ for (i = 1; i < argc; i++)
       else if (Ustrcmp(argrest, "Mm") == 0)
         {
         if (!mac_ismsgid(argv[i+1]))
       else if (Ustrcmp(argrest, "Mm") == 0)
         {
         if (!mac_ismsgid(argv[i+1]))
-          {
-            fprintf(stderr,"-oMm must be a valid message ID\n");
-            exit(EXIT_FAILURE);
-          }
-        if (!trusted_config)
-          {
-            fprintf(stderr,"-oMm must be called by a trusted user/config\n");
-            exit(EXIT_FAILURE);
-          }
+            exim_fail("-oMm must be a valid message ID\n");
+        if (!f.trusted_config)
+            exim_fail("-oMm must be called by a trusted user/config\n");
           message_reference = argv[++i];
         }
 
       /* -oMr: Received protocol */
 
           message_reference = argv[++i];
         }
 
       /* -oMr: Received protocol */
 
-      else if (Ustrcmp(argrest, "Mr") == 0) received_protocol = argv[++i];
+      else if (Ustrcmp(argrest, "Mr") == 0)
+
+        if (received_protocol)
+          exim_fail("received_protocol is set already\n");
+        else
+         received_protocol = argv[++i];
 
       /* -oMs: Set sender host name */
 
 
       /* -oMs: Set sender host name */
 
@@ -3077,10 +3070,7 @@ for (i = 1; i < argc; i++)
         }
       else *tp = readconf_readtime(argrest + 1, 0, FALSE);
       if (*tp < 0)
         }
       else *tp = readconf_readtime(argrest + 1, 0, FALSE);
       if (*tp < 0)
-        {
-        fprintf(stderr, "exim: bad time value %s: abandoned\n", argv[i]);
-        exit(EXIT_FAILURE);
-        }
+        exim_fail("exim: bad time value %s: abandoned\n", argv[i]);
       }
 
     /* -oX <list>: Override local_interfaces and/or default daemon ports */
       }
 
     /* -oX <list>: Override local_interfaces and/or default daemon ports */
@@ -3114,21 +3104,27 @@ for (i = 1; i < argc; i++)
     which sets the host protocol and host name */
 
     if (*argrest == 0)
     which sets the host protocol and host name */
 
     if (*argrest == 0)
-      {
-      if (i+1 < argc) argrest = argv[++i]; else
+      if (i+1 < argc)
+       argrest = argv[++i];
+      else
         { badarg = TRUE; break; }
         { badarg = TRUE; break; }
-      }
 
     if (*argrest != 0)
       {
 
     if (*argrest != 0)
       {
-      uschar *hn = Ustrchr(argrest, ':');
+      uschar *hn;
+
+      if (received_protocol)
+        exim_fail("received_protocol is set already\n");
+
+      hn = Ustrchr(argrest, ':');
       if (hn == NULL)
       if (hn == NULL)
-        {
         received_protocol = argrest;
         received_protocol = argrest;
-        }
       else
         {
       else
         {
+       int old_pool = store_pool;
+       store_pool = POOL_PERM;
         received_protocol = string_copyn(argrest, hn - argrest);
         received_protocol = string_copyn(argrest, hn - argrest);
+       store_pool = old_pool;
         sender_host_name = hn + 1;
         }
       }
         sender_host_name = hn + 1;
         }
       }
@@ -3138,16 +3134,13 @@ for (i = 1; i < argc; i++)
     case 'q':
     receiving_message = FALSE;
     if (queue_interval >= 0)
     case 'q':
     receiving_message = FALSE;
     if (queue_interval >= 0)
-      {
-      fprintf(stderr, "exim: -q specified more than once\n");
-      exit(EXIT_FAILURE);
-      }
+      exim_fail("exim: -q specified more than once\n");
 
     /* -qq...: Do queue runs in a 2-stage manner */
 
     if (*argrest == 'q')
       {
 
     /* -qq...: Do queue runs in a 2-stage manner */
 
     if (*argrest == 'q')
       {
-      queue_2stage = TRUE;
+      f.queue_2stage = TRUE;
       argrest++;
       }
 
       argrest++;
       }
 
@@ -3155,7 +3148,7 @@ for (i = 1; i < argc; i++)
 
     if (*argrest == 'i')
       {
 
     if (*argrest == 'i')
       {
-      queue_run_first_delivery = TRUE;
+      f.queue_run_first_delivery = TRUE;
       argrest++;
       }
 
       argrest++;
       }
 
@@ -3164,10 +3157,10 @@ for (i = 1; i < argc; i++)
 
     if (*argrest == 'f')
       {
 
     if (*argrest == 'f')
       {
-      queue_run_force = TRUE;
-      if (*(++argrest) == 'f')
+      f.queue_run_force = TRUE;
+      if (*++argrest == 'f')
         {
         {
-        deliver_force_thaw = TRUE;
+        f.deliver_force_thaw = TRUE;
         argrest++;
         }
       }
         argrest++;
         }
       }
@@ -3176,12 +3169,23 @@ for (i = 1; i < argc; i++)
 
     if (*argrest == 'l')
       {
 
     if (*argrest == 'l')
       {
-      queue_run_local = TRUE;
+      f.queue_run_local = TRUE;
       argrest++;
       }
 
       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])))
 
     if (*argrest == 0 &&
         (i + 1 >= argc || argv[i+1][0] == '-' || mac_ismsgid(argv[i+1])))
@@ -3193,21 +3197,12 @@ for (i = 1; i < argc; i++)
         stop_queue_run_id = argv[++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
-      {
-      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);
-        }
-      }
+    else if ((queue_interval = readconf_readtime(*argrest ? argrest : argv[++i],
+                                               0, FALSE)) <= 0)
+      exim_fail("exim: bad time value %s: abandoned\n", argv[i]);
     break;
 
 
     break;
 
 
@@ -3226,30 +3221,25 @@ for (i = 1; i < argc; i++)
     if (*argrest != 0)
       {
       int 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 (Ustrcmp(argrest, rsopts[i]) == 0)
           {
-          if (i != 2) queue_run_force = TRUE;
-          if (i >= 2) deliver_selectstring_regex = TRUE;
-          if (i == 1 || i == 4) deliver_force_thaw = TRUE;
+          if (i != 2) f.queue_run_force = TRUE;
+          if (i >= 2) f.deliver_selectstring_regex = TRUE;
+          if (i == 1 || i == 4) f.deliver_force_thaw = TRUE;
           argrest += Ustrlen(rsopts[i]);
           }
           argrest += Ustrlen(rsopts[i]);
           }
-        }
       }
 
     /* -R: Set string to match in addresses for forced queue run to
     pick out particular messages. */
 
       }
 
     /* -R: Set string to match in addresses for forced queue run to
     pick out particular messages. */
 
-    if (*argrest == 0)
-      {
-      if (i+1 < argc) deliver_selectstring = argv[++i]; else
-        {
-        fprintf(stderr, "exim: string expected after -R\n");
-        exit(EXIT_FAILURE);
-        }
-      }
-    else deliver_selectstring = argrest;
+    if (*argrest)
+      deliver_selectstring = argrest;
+    else if (i+1 < argc)
+      deliver_selectstring = argv[++i];
+    else
+      exim_fail("exim: string expected after -R\n");
     break;
 
 
     break;
 
 
@@ -3270,33 +3260,28 @@ for (i = 1; i < argc; i++)
     in all cases provided there are no further characters in this
     argument. */
 
     in all cases provided there are no further characters in this
     argument. */
 
-    if (*argrest != 0)
+    if (*argrest)
       {
       int i;
       {
       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 (Ustrcmp(argrest, rsopts[i]) == 0)
           {
-          if (i != 2) queue_run_force = TRUE;
-          if (i >= 2) deliver_selectstring_sender_regex = TRUE;
-          if (i == 1 || i == 4) deliver_force_thaw = TRUE;
+          if (i != 2) f.queue_run_force = TRUE;
+          if (i >= 2) f.deliver_selectstring_sender_regex = TRUE;
+          if (i == 1 || i == 4) f.deliver_force_thaw = TRUE;
           argrest += Ustrlen(rsopts[i]);
           }
           argrest += Ustrlen(rsopts[i]);
           }
-        }
       }
 
     /* -S: Set string to match in addresses for forced queue run to
     pick out particular messages. */
 
       }
 
     /* -S: Set string to match in addresses for forced queue run to
     pick out particular messages. */
 
-    if (*argrest == 0)
-      {
-      if (i+1 < argc) deliver_selectstring_sender = argv[++i]; else
-        {
-        fprintf(stderr, "exim: string expected after -S\n");
-        exit(EXIT_FAILURE);
-        }
-      }
-    else deliver_selectstring_sender = argrest;
+    if (*argrest)
+      deliver_selectstring_sender = argrest;
+    else if (i+1 < argc)
+      deliver_selectstring_sender = argv[++i];
+    else
+      exim_fail("exim: string expected after -S\n");
     break;
 
     /* -Tqt is an option that is exclusively for use by the testing suite.
     break;
 
     /* -Tqt is an option that is exclusively for use by the testing suite.
@@ -3305,7 +3290,7 @@ for (i = 1; i < argc; i++)
     tested. Otherwise variability of clock ticks etc. cause problems. */
 
     case 'T':
     tested. Otherwise variability of clock ticks etc. cause problems. */
 
     case 'T':
-    if (running_in_test_harness && Ustrcmp(argrest, "qt") == 0)
+    if (f.running_in_test_harness && Ustrcmp(argrest, "qt") == 0)
       fudged_queue_times = argv[++i];
     else badarg = TRUE;
     break;
       fudged_queue_times = argv[++i];
     else badarg = TRUE;
     break;
@@ -3322,7 +3307,7 @@ for (i = 1; i < argc; i++)
     else if (Ustrcmp(argrest, "i") == 0)
       {
       extract_recipients = TRUE;
     else if (Ustrcmp(argrest, "i") == 0)
       {
       extract_recipients = TRUE;
-      dot_ends = FALSE;
+      f.dot_ends = FALSE;
       }
 
     /* -tls-on-connect: don't wait for STARTTLS (for old clients) */
       }
 
     /* -tls-on-connect: don't wait for STARTTLS (for old clients) */
@@ -3374,13 +3359,16 @@ for (i = 1; i < argc; i++)
 
     case 'X':
     if (*argrest == '\0')
 
     case 'X':
     if (*argrest == '\0')
-      {
       if (++i >= argc)
       if (++i >= argc)
-        {
-        fprintf(stderr, "exim: string expected after -X\n");
-        exit(EXIT_FAILURE);
-        }
-      }
+        exim_fail("exim: string expected after -X\n");
+    break;
+
+    case 'z':
+    if (*argrest == '\0')
+      if (++i < argc)
+       log_oneline = argv[i];
+      else
+        exim_fail("exim: file name expected after %s\n", argv[i-1]);
     break;
 
     /* All other initial characters are errors */
     break;
 
     /* All other initial characters are errors */
@@ -3393,18 +3381,16 @@ for (i = 1; i < argc; i++)
   /* Failed to recognize the option, or syntax error */
 
   if (badarg)
   /* Failed to recognize the option, or syntax error */
 
   if (badarg)
-    {
-    fprintf(stderr, "exim abandoned: unknown, malformed, or incomplete "
+    exim_fail("exim abandoned: unknown, malformed, or incomplete "
       "option %s\n", arg);
       "option %s\n", arg);
-    exit(EXIT_FAILURE);
-    }
   }
 
 
 /* If -R or -S have been specified without -q, assume a single queue run. */
 
   }
 
 
 /* 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:
 
 
 END_ARG:
@@ -3414,26 +3400,26 @@ if (usage_wanted) exim_usage(called_as);
 /* Arguments have been processed. Check for incompatibilities. */
 if ((
     (smtp_input || extract_recipients || recipients_arg < argc) &&
 /* Arguments have been processed. Check for incompatibilities. */
 if ((
     (smtp_input || extract_recipients || recipients_arg < argc) &&
-    (daemon_listen || queue_interval >= 0 || bi_option ||
+    (f.daemon_listen || queue_interval >= 0 || bi_option ||
       test_retry_arg >= 0 || test_rewrite_arg >= 0 ||
       filter_test != FTEST_NONE || (msg_action_arg > 0 && !one_msg_action))
     ) ||
     (
     msg_action_arg > 0 &&
       test_retry_arg >= 0 || test_rewrite_arg >= 0 ||
       filter_test != FTEST_NONE || (msg_action_arg > 0 && !one_msg_action))
     ) ||
     (
     msg_action_arg > 0 &&
-    (daemon_listen || queue_interval >= 0 || list_options ||
+    (f.daemon_listen || queue_interval > 0 || list_options ||
       (checking && msg_action != MSG_LOAD) ||
       bi_option || test_retry_arg >= 0 || test_rewrite_arg >= 0)
     ) ||
     (
       (checking && msg_action != MSG_LOAD) ||
       bi_option || test_retry_arg >= 0 || test_rewrite_arg >= 0)
     ) ||
     (
-    (daemon_listen || queue_interval >= 0) &&
+    (f.daemon_listen || queue_interval > 0) &&
     (sender_address != NULL || list_options || list_queue || checking ||
       bi_option)
     ) ||
     (
     (sender_address != NULL || list_options || list_queue || checking ||
       bi_option)
     ) ||
     (
-    daemon_listen && queue_interval == 0
+    f.daemon_listen && queue_interval == 0
     ) ||
     (
     ) ||
     (
-    inetd_wait_mode && queue_interval >= 0
+    f.inetd_wait_mode && queue_interval >= 0
     ) ||
     (
     list_options &&
     ) ||
     (
     list_options &&
@@ -3442,11 +3428,11 @@ if ((
     ) ||
     (
     verify_address_mode &&
     ) ||
     (
     verify_address_mode &&
-    (address_test_mode || smtp_input || extract_recipients ||
+    (f.address_test_mode || smtp_input || extract_recipients ||
       filter_test != FTEST_NONE || bi_option)
     ) ||
     (
       filter_test != FTEST_NONE || bi_option)
     ) ||
     (
-    address_test_mode && (smtp_input || extract_recipients ||
+    f.address_test_mode && (smtp_input || extract_recipients ||
       filter_test != FTEST_NONE || bi_option)
     ) ||
     (
       filter_test != FTEST_NONE || bi_option)
     ) ||
     (
@@ -3461,10 +3447,7 @@ if ((
       (!expansion_test || expansion_test_message != NULL)
     )
    )
       (!expansion_test || expansion_test_message != NULL)
     )
    )
-  {
-  fprintf(stderr, "exim: incompatible command-line options or arguments\n");
-  exit(EXIT_FAILURE);
-  }
+  exim_fail("exim: incompatible command-line options or arguments\n");
 
 /* If debugging is set up, set the file and the file descriptor to pass on to
 child processes. It should, of course, be 2 for stderr. Also, force the daemon
 
 /* If debugging is set up, set the file and the file descriptor to pass on to
 child processes. It should, of course, be 2 for stderr. Also, force the daemon
@@ -3474,8 +3457,8 @@ if (debug_selector != 0)
   {
   debug_file = stderr;
   debug_fd = fileno(debug_file);
   {
   debug_file = stderr;
   debug_fd = fileno(debug_file);
-  background_daemon = FALSE;
-  if (running_in_test_harness) millisleep(100);   /* lets caller finish */
+  f.background_daemon = FALSE;
+  if (f.running_in_test_harness) millisleep(100);   /* lets caller finish */
   if (debug_selector != D_v)    /* -v only doesn't show this */
     {
     debug_printf("Exim version %s uid=%ld gid=%ld pid=%d D=%x\n",
   if (debug_selector != D_v)    /* -v only doesn't show this */
     {
     debug_printf("Exim version %s uid=%ld gid=%ld pid=%d D=%x\n",
@@ -3561,12 +3544,8 @@ check on the additional groups for the admin user privilege - can't do that
 till after reading the config, which might specify the exim gid. Therefore,
 save the group list here first. */
 
 till after reading the config, which might specify the exim gid. Therefore,
 save the group list here first. */
 
-group_count = getgroups(NGROUPS_MAX, group_list);
-if (group_count < 0)
-  {
-  fprintf(stderr, "exim: getgroups() failed: %s\n", strerror(errno));
-  exit(EXIT_FAILURE);
-  }
+if ((group_count = getgroups(nelem(group_list), group_list)) < 0)
+  exim_fail("exim: getgroups() failed: %s\n", strerror(errno));
 
 /* There is a fundamental difference in some BSD systems in the matter of
 groups. FreeBSD and BSDI are known to be different; NetBSD and OpenBSD are
 
 /* There is a fundamental difference in some BSD systems in the matter of
 groups. FreeBSD and BSDI are known to be different; NetBSD and OpenBSD are
@@ -3579,19 +3558,18 @@ over a single group - the current group, which is always the first group in the
 list. Calling setgroups() with zero groups on a "different" system results in
 an error return. The following code should cope with both types of system.
 
 list. Calling setgroups() with zero groups on a "different" system results in
 an error return. The following code should cope with both types of system.
 
+ Unfortunately, recent MacOS, which should be a FreeBSD, "helpfully" succeeds
+ the "setgroups() with zero groups" - and changes the egid.
+ Thanks to that we had to stash the original_egid above, for use below
+ in the call to exim_setugid().
+
 However, if this process isn't running as root, setgroups() can't be used
 since you have to be root to run it, even if throwing away groups. Not being
 root here happens only in some unusual configurations. We just ignore the
 error. */
 
 However, if this process isn't running as root, setgroups() can't be used
 since you have to be root to run it, even if throwing away groups. Not being
 root here happens only in some unusual configurations. We just ignore the
 error. */
 
-if (setgroups(0, NULL) != 0)
-  {
-  if (setgroups(1, group_list) != 0 && !unprivileged)
-    {
-    fprintf(stderr, "exim: setgroups() failed: %s\n", strerror(errno));
-    exit(EXIT_FAILURE);
-    }
-  }
+if (setgroups(0, NULL) != 0 && setgroups(1, group_list) != 0 && !unprivileged)
+  exim_fail("exim: setgroups() failed: %s\n", strerror(errno));
 
 /* If the configuration file name has been altered by an argument on the
 command line (either a new file name or a macro definition) and the caller is
 
 /* If the configuration file name has been altered by an argument on the
 command line (either a new file name or a macro definition) and the caller is
@@ -3611,10 +3589,10 @@ values (such as the path name). If running in the test harness, pretend that
 configuration file changes and macro definitions haven't happened. */
 
 if ((                                            /* EITHER */
 configuration file changes and macro definitions haven't happened. */
 
 if ((                                            /* EITHER */
-    (!trusted_config ||                          /* Config changed, or */
-     !macros_trusted()) &&                       /*  impermissible macros and */
+    (!f.trusted_config ||                          /* Config changed, or */
+     !macros_trusted(opt_D_used)) &&            /*  impermissible macros and */
     real_uid != root_uid &&                      /* Not root, and */
     real_uid != root_uid &&                      /* Not root, and */
-    !running_in_test_harness                     /* Not fudged */
+    !f.running_in_test_harness                     /* Not fudged */
     ) ||                                         /*   OR   */
     expansion_test                               /* expansion testing */
     ||                                           /*   OR   */
     ) ||                                         /*   OR   */
     expansion_test                               /* expansion testing */
     ||                                           /*   OR   */
@@ -3634,8 +3612,8 @@ if ((                                            /* EITHER */
   Note that if the invoker is Exim, the logs remain available. Messing with
   this causes unlogged successful deliveries.  */
 
   Note that if the invoker is Exim, the logs remain available. Messing with
   this causes unlogged successful deliveries.  */
 
-  if ((log_stderr != NULL) && (real_uid != exim_uid))
-    really_exim = FALSE;
+  if (log_stderr && real_uid != exim_uid)
+    f.really_exim = FALSE;
   }
 
 /* Privilege is to be retained for the moment. It may be dropped later,
   }
 
 /* Privilege is to be retained for the moment. It may be dropped later,
@@ -3643,32 +3621,21 @@ depending on the job that this Exim process has been asked to do. For now, set
 the real uid to the effective so that subsequent re-execs of Exim are done by a
 privileged user. */
 
 the real uid to the effective so that subsequent re-execs of Exim are done by a
 privileged user. */
 
-else exim_setugid(geteuid(), getegid(), FALSE, US"forcing real = effective");
+else
+  exim_setugid(geteuid(), original_egid, FALSE, US"forcing real = effective");
 
 /* If testing a filter, open the file(s) now, before wasting time doing other
 setups and reading the message. */
 
 
 /* If testing a filter, open the file(s) now, before wasting time doing other
 setups and reading the message. */
 
-if ((filter_test & FTEST_SYSTEM) != 0)
-  {
-  filter_sfd = Uopen(filter_test_sfile, O_RDONLY, 0);
-  if (filter_sfd < 0)
-    {
-    fprintf(stderr, "exim: failed to open %s: %s\n", filter_test_sfile,
+if (filter_test & FTEST_SYSTEM)
+  if ((filter_sfd = Uopen(filter_test_sfile, O_RDONLY, 0)) < 0)
+    exim_fail("exim: failed to open %s: %s\n", filter_test_sfile,
       strerror(errno));
       strerror(errno));
-    return EXIT_FAILURE;
-    }
-  }
 
 
-if ((filter_test & FTEST_USER) != 0)
-  {
-  filter_ufd = Uopen(filter_test_ufile, O_RDONLY, 0);
-  if (filter_ufd < 0)
-    {
-    fprintf(stderr, "exim: failed to open %s: %s\n", filter_test_ufile,
+if (filter_test & FTEST_USER)
+  if ((filter_ufd = Uopen(filter_test_ufile, O_RDONLY, 0)) < 0)
+    exim_fail("exim: failed to open %s: %s\n", filter_test_ufile,
       strerror(errno));
       strerror(errno));
-    return EXIT_FAILURE;
-    }
-  }
 
 /* Initialise lookup_list
 If debugging, already called above via version reporting.
 
 /* Initialise lookup_list
 If debugging, already called above via version reporting.
@@ -3681,18 +3648,42 @@ is equivalent to the ability to modify a setuid binary!
 This needs to happen before we read the main configuration. */
 init_lookup_list();
 
 This needs to happen before we read the main configuration. */
 init_lookup_list();
 
+#ifdef SUPPORT_I18N
+if (f.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
 /* 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.
 
 
-/* To be safe: change the working directory to /. */
-if (Uchdir("/") < 0)
-  {
-    perror("exim: chdir `/': ");
-    exit(EXIT_FAILURE);
-  }
+NOTE: immediately 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.  Can be NULL if the
+dir has already been unlinked. */
+initial_cwd = os_getcwd(NULL, 0);
+
+/* 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);
 
 
-readconf_main();
+
+/* Now in directory "/" */
 
 if (cleanup_environment() == FALSE)
   log_write(0, LOG_PANIC_DIE, "Can't cleanup environment");
 
 if (cleanup_environment() == FALSE)
   log_write(0, LOG_PANIC_DIE, "Can't cleanup environment");
@@ -3707,21 +3698,17 @@ since some actions can be performed by non-admin users. Instead, set admin_user
 for later interrogation. */
 
 if (real_uid == root_uid || real_uid == exim_uid || real_gid == exim_gid)
 for later interrogation. */
 
 if (real_uid == root_uid || real_uid == exim_uid || real_gid == exim_gid)
-  admin_user = TRUE;
+  f.admin_user = TRUE;
 else
   {
   int i, j;
 else
   {
   int i, j;
-  for (i = 0; i < group_count; i++)
-    {
-    if (group_list[i] == exim_gid) admin_user = TRUE;
-    else if (admin_groups != NULL)
-      {
-      for (j = 1; j <= (int)(admin_groups[0]); j++)
+  for (i = 0; i < group_count && !f.admin_user; i++)
+    if (group_list[i] == exim_gid)
+      f.admin_user = TRUE;
+    else if (admin_groups)
+      for (j = 1; j <= (int)admin_groups[0] && !f.admin_user; j++)
         if (admin_groups[j] == group_list[i])
         if (admin_groups[j] == group_list[i])
-          { admin_user = TRUE; break; }
-      }
-    if (admin_user) break;
-    }
+          f.admin_user = TRUE;
   }
 
 /* Another group of privileged users are the trusted users. These are root,
   }
 
 /* Another group of privileged users are the trusted users. These are root,
@@ -3730,82 +3717,71 @@ are permitted to specify sender_addresses with -f on the command line, and
 other message parameters as well. */
 
 if (real_uid == root_uid || real_uid == exim_uid)
 other message parameters as well. */
 
 if (real_uid == root_uid || real_uid == exim_uid)
-  trusted_caller = TRUE;
+  f.trusted_caller = TRUE;
 else
   {
   int i, j;
 
 else
   {
   int i, j;
 
-  if (trusted_users != NULL)
-    {
-    for (i = 1; i <= (int)(trusted_users[0]); i++)
+  if (trusted_users)
+    for (i = 1; i <= (int)trusted_users[0] && !f.trusted_caller; i++)
       if (trusted_users[i] == real_uid)
       if (trusted_users[i] == real_uid)
-        { trusted_caller = TRUE; break; }
-    }
+        f.trusted_caller = TRUE;
 
 
-  if (!trusted_caller && trusted_groups != NULL)
-    {
-    for (i = 1; i <= (int)(trusted_groups[0]); i++)
-      {
+  if (trusted_groups)
+    for (i = 1; i <= (int)trusted_groups[0] && !f.trusted_caller; i++)
       if (trusted_groups[i] == real_gid)
       if (trusted_groups[i] == real_gid)
-        trusted_caller = TRUE;
-      else for (j = 0; j < group_count; j++)
-        {
+        f.trusted_caller = TRUE;
+      else for (j = 0; j < group_count && !f.trusted_caller; j++)
         if (trusted_groups[i] == group_list[j])
         if (trusted_groups[i] == group_list[j])
-          { trusted_caller = TRUE; break; }
-        }
-      if (trusted_caller) break;
-      }
-    }
+          f.trusted_caller = TRUE;
   }
 
   }
 
+/* At this point, we know if the user is privileged and some command-line
+options become possibly impermissible, depending upon the configuration file. */
+
+if (checking && commandline_checks_require_admin && !f.admin_user)
+  exim_fail("exim: those command-line flags are set to require admin\n");
+
 /* Handle the decoding of logging options. */
 
 /* 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)
   {
   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("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
 supplied with -f. Ditto for a stripped trailing dot. */
 
   }
 
 /* If domain literals are not allowed, check the sender address that was
 supplied with -f. Ditto for a stripped trailing dot. */
 
-if (sender_address != NULL)
+if (sender_address)
   {
   if (sender_address[sender_address_domain] == '[' && !allow_domain_literals)
   {
   if (sender_address[sender_address_domain] == '[' && !allow_domain_literals)
-    {
-    fprintf(stderr, "exim: bad -f address \"%s\": domain literals not "
+    exim_fail("exim: bad -f address \"%s\": domain literals not "
       "allowed\n", sender_address);
       "allowed\n", sender_address);
-    return EXIT_FAILURE;
-    }
   if (f_end_dot && !strip_trailing_dot)
   if (f_end_dot && !strip_trailing_dot)
-    {
-    fprintf(stderr, "exim: bad -f address \"%s.\": domain is malformed "
+    exim_fail("exim: bad -f address \"%s.\": domain is malformed "
       "(trailing dot not allowed)\n", sender_address);
       "(trailing dot not allowed)\n", sender_address);
-    return EXIT_FAILURE;
-    }
   }
 
 /* See if an admin user overrode our logging. */
 
   }
 
 /* See if an admin user overrode our logging. */
 
-if (cmdline_syslog_name != NULL)
-  {
-  if (admin_user)
+if (cmdline_syslog_name)
+  if (f.admin_user)
     {
     syslog_processname = cmdline_syslog_name;
     log_file_path = string_copy(CUS"syslog");
     }
   else
     {
     syslog_processname = cmdline_syslog_name;
     log_file_path = string_copy(CUS"syslog");
     }
   else
-    {
     /* not a panic, non-privileged users should not be able to spam paniclog */
     /* not a panic, non-privileged users should not be able to spam paniclog */
-    fprintf(stderr,
+    exim_fail(
         "exim: you lack sufficient privilege to specify syslog process name\n");
         "exim: you lack sufficient privilege to specify syslog process name\n");
-    return EXIT_FAILURE;
-    }
-  }
 
 /* Paranoia check of maximum lengths of certain strings. There is a check
 on the length of the log file path in log.c, which will come into effect
 
 /* Paranoia check of maximum lengths of certain strings. There is a check
 on the length of the log file path in log.c, which will come into effect
@@ -3834,26 +3810,35 @@ if (Ustrlen(syslog_processname) > 32)
   log_write(0, LOG_MAIN|LOG_PANIC_DIE,
     "syslog_processname is longer than 32 chars: aborting");
 
   log_write(0, LOG_MAIN|LOG_PANIC_DIE,
     "syslog_processname is longer than 32 chars: aborting");
 
+if (log_oneline)
+  if (f.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
 /* 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;
   {
   uschar **p;
-  if (environ) 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;
       *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
 
   }
 #endif
 
@@ -3867,33 +3852,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. */
 
 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)
-  {
-  timestamps_utc = TRUE;
-  }
+if (timezone_string && strcmpic(timezone_string, US"UTC") == 0)
+  f.timestamps_utc = TRUE;
 else
   {
   uschar *envtz = US getenv("TZ");
 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;
     {
     uschar **p = USS environ;
     uschar **new;
     uschar **newp;
     int count = 0;
-    if (environ) while (*p++ != NULL) count++;
-    if (envtz == NULL) count++;
-    newp = new = malloc(sizeof(uschar *) * (count + 1));
-    if (environ) 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;
       sprintf(CS *newp++, "TZ=%s", timezone_string);
       }
     *newp = NULL;
@@ -3925,16 +3905,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). */
 
       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
+   && (!f.trusted_config || opt_D_used)
+   && real_uid == exim_uid)
   if (deliver_drop_privilege)
   if (deliver_drop_privilege)
-    really_exim = TRUE; /* let logging work normally */
+    f.really_exim = TRUE; /* let logging work normally */
   else
     log_write(0, LOG_MAIN|LOG_PANIC,
       "exim user lost privilege for using %s option",
   else
     log_write(0, LOG_MAIN|LOG_PANIC,
       "exim user lost privilege for using %s option",
-      trusted_config? "-D" : "-C");
-  }
+      f.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
 
 /* Start up Perl interpreter if Perl support is configured and there is a
 perl_startup option, and the configuration or the command line specifies
@@ -3948,12 +3927,8 @@ if (opt_perl_at_start && opt_perl_startup != NULL)
   {
   uschar *errstr;
   DEBUG(D_any) debug_printf("Starting Perl interpreter\n");
   {
   uschar *errstr;
   DEBUG(D_any) debug_printf("Starting Perl interpreter\n");
-  errstr = init_perl(opt_perl_startup);
-  if (errstr != NULL)
-    {
-    fprintf(stderr, "exim: error in perl_startup code: %s\n", errstr);
-    return EXIT_FAILURE;
-    }
+  if ((errstr = init_perl(opt_perl_startup)))
+    exim_fail("exim: error in perl_startup code: %s\n", errstr);
   opt_perl_started = TRUE;
   }
 #endif /* EXIM_PERL */
   opt_perl_started = TRUE;
   }
 #endif /* EXIM_PERL */
@@ -3963,21 +3938,30 @@ 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. */
 
 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)
-      && really_exim && !list_options && !checking)
+if (  (debug_selector & D_any  ||  LOGGING(arguments))
+   && f.really_exim && !list_options && !checking)
   {
   int i;
   uschar *p = big_buffer;
   {
   int i;
   uschar *p = big_buffer;
-  char * dummy;
   Ustrcpy(p, "cwd= (failed)");
   Ustrcpy(p, "cwd= (failed)");
-  dummy = /* quieten compiler */ getcwd(CS p+4, big_buffer_size - 4);
-  while (*p) p++;
+
+  if (!initial_cwd)
+    p += 13;
+  else
+    {
+    Ustrncpy(p + 4, initial_cwd, big_buffer_size-5);
+    p += 4 + Ustrlen(initial_cwd);
+    /* in case p is near the end and we don't provide enough space for
+     * string_format to be willing to write. */
+    *p = '\0';
+    }
+
   (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]);
   (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)
       {
     uschar *quote;
     if (p + len + 8 >= big_buffer + big_buffer_size)
       {
@@ -3989,16 +3973,15 @@ if (((debug_selector & D_any) != 0 || (log_extra_selector & LX_arguments) != 0)
     printing = string_printing(argv[i]);
     if (printing[0] == 0) quote = US"\""; else
       {
     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; }
       }
       quote = US"";
       while (*pp != 0) if (isspace(*pp++)) { quote = US"\""; break; }
       }
-    sprintf(CS p, " %s%.*s%s", quote, (int)(big_buffer_size -
+    p += sprintf(CS p, " %s%.*s%s", quote, (int)(big_buffer_size -
       (p - big_buffer) - 4), printing, quote);
       (p - big_buffer) - 4), printing, quote);
-    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);
     log_write(0, LOG_MAIN, "%s", big_buffer);
   else
     debug_printf("%s\n", big_buffer);
@@ -4016,6 +3999,7 @@ if (Uchdir(spool_directory) != 0)
   int dummy;
   (void)directory_make(spool_directory, US"", SPOOL_DIRECTORY_MODE, FALSE);
   dummy = /* quieten compiler */ Uchdir(spool_directory);
   int dummy;
   (void)directory_make(spool_directory, US"", SPOOL_DIRECTORY_MODE, FALSE);
   dummy = /* quieten compiler */ Uchdir(spool_directory);
+  dummy = dummy;       /* yet more compiler quietening, sigh */
   }
 
 /* Handle calls with the -bi option. This is a sendmail option to rebuild *the*
   }
 
 /* Handle calls with the -bi option. This is a sendmail option to rebuild *the*
@@ -4042,8 +4026,7 @@ if (bi_option)
       (argv[1] == NULL)? US"" : argv[1]);
 
     execv(CS argv[0], (char *const *)argv);
       (argv[1] == NULL)? US"" : argv[1]);
 
     execv(CS argv[0], (char *const *)argv);
-    fprintf(stderr, "exim: exec failed: %s\n", strerror(errno));
-    exit(EXIT_FAILURE);
+    exim_fail("exim: exec failed: %s\n", strerror(errno));
     }
   else
     {
     }
   else
     {
@@ -4056,8 +4039,8 @@ if (bi_option)
 configuration file.  We leave these prints here to ensure that syslog setup,
 logfile setup, and so on has already happened. */
 
 configuration file.  We leave these prints here to ensure that syslog setup,
 logfile setup, and so on has already happened. */
 
-if (trusted_caller) DEBUG(D_any) debug_printf("trusted user\n");
-if (admin_user) DEBUG(D_any) debug_printf("admin user\n");
+if (f.trusted_caller) DEBUG(D_any) debug_printf("trusted user\n");
+if (f.admin_user) DEBUG(D_any) debug_printf("admin user\n");
 
 /* Only an admin user may start the daemon or force a queue run in the default
 configuration, but the queue run restriction can be relaxed. Only an admin
 
 /* Only an admin user may start the daemon or force a queue run in the default
 configuration, but the queue run restriction can be relaxed. Only an admin
@@ -4067,18 +4050,15 @@ passwords, etc. in lookup queries). Only an admin user may request a queue
 count. Only an admin user can use the test interface to scan for email
 (because Exim will be in the spool dir and able to look at mails). */
 
 count. Only an admin user can use the test interface to scan for email
 (because Exim will be in the spool dir and able to look at mails). */
 
-if (!admin_user)
+if (!f.admin_user)
   {
   BOOL debugset = (debug_selector & ~D_v) != 0;
   {
   BOOL debugset = (debug_selector & ~D_v) != 0;
-  if (deliver_give_up || daemon_listen || malware_test_file ||
+  if (deliver_give_up || f.daemon_listen || malware_test_file ||
      (count_queue && queue_list_requires_admin) ||
      (list_queue && queue_list_requires_admin) ||
      (queue_interval >= 0 && prod_requires_admin) ||
      (count_queue && queue_list_requires_admin) ||
      (list_queue && queue_list_requires_admin) ||
      (queue_interval >= 0 && prod_requires_admin) ||
-     (debugset && !running_in_test_harness))
-    {
-    fprintf(stderr, "exim:%s permission denied\n", debugset? " debugging" : "");
-    exit(EXIT_FAILURE);
-    }
+     (debugset && !f.running_in_test_harness))
+    exim_fail("exim:%s permission denied\n", debugset? " debugging" : "");
   }
 
 /* If the real user is not root or the exim uid, the argument for passing
   }
 
 /* If the real user is not root or the exim uid, the argument for passing
@@ -4089,20 +4069,17 @@ regression testing. */
 
 if (real_uid != root_uid && real_uid != exim_uid &&
      (continue_hostname != NULL ||
 
 if (real_uid != root_uid && real_uid != exim_uid &&
      (continue_hostname != NULL ||
-       (dont_deliver &&
-         (queue_interval >= 0 || daemon_listen || msg_action_arg > 0)
-       )) && !running_in_test_harness)
-  {
-  fprintf(stderr, "exim: Permission denied\n");
-  return EXIT_FAILURE;
-  }
+       (f.dont_deliver &&
+         (queue_interval >= 0 || f.daemon_listen || msg_action_arg > 0)
+       )) && !f.running_in_test_harness)
+  exim_fail("exim: Permission denied\n");
 
 /* If the caller is not trusted, certain arguments are ignored when running for
 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 the caller is not trusted, certain arguments are ignored when running for
 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 (!f.trusted_caller && !checking)
   {
   sender_host_name = sender_host_address = interface_address =
     sender_ident = received_protocol = NULL;
   {
   sender_host_name = sender_host_address = interface_address =
     sender_ident = received_protocol = NULL;
@@ -4125,16 +4102,13 @@ else
 /* If the caller is trusted, then they can use -G to suppress_local_fixups. */
 if (flag_G)
   {
 /* If the caller is trusted, then they can use -G to suppress_local_fixups. */
 if (flag_G)
   {
-  if (trusted_caller)
+  if (f.trusted_caller)
     {
     {
-    suppress_local_fixups = suppress_local_fixups_default = TRUE;
+    f.suppress_local_fixups = f.suppress_local_fixups_default = TRUE;
     DEBUG(D_acl) debug_printf("suppress_local_fixups forced on by -G\n");
     }
   else
     DEBUG(D_acl) debug_printf("suppress_local_fixups forced on by -G\n");
     }
   else
-    {
-    fprintf(stderr, "exim: permission denied (-G requires a trusted user)\n");
-    return EXIT_FAILURE;
-    }
+    exim_fail("exim: permission denied (-G requires a trusted user)\n");
   }
 
 /* If an SMTP message is being received check to see if the standard input is a
   }
 
 /* If an SMTP message is being received check to see if the standard input is a
@@ -4162,18 +4136,15 @@ if (smtp_input)
 
       if (real_uid == root_uid || real_uid == exim_uid || interface_port < 1024)
         {
 
       if (real_uid == root_uid || real_uid == exim_uid || interface_port < 1024)
         {
-        is_inetd = TRUE;
+        f.is_inetd = TRUE;
         sender_host_address = host_ntoa(-1, (struct sockaddr *)(&inetd_sock),
           NULL, &sender_host_port);
         if (mua_wrapper) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Input from "
           "inetd is not supported when mua_wrapper is set");
         }
       else
         sender_host_address = host_ntoa(-1, (struct sockaddr *)(&inetd_sock),
           NULL, &sender_host_port);
         if (mua_wrapper) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Input from "
           "inetd is not supported when mua_wrapper is set");
         }
       else
-        {
-        fprintf(stderr,
+        exim_fail(
           "exim: Permission denied (unprivileged user, unprivileged port)\n");
           "exim: Permission denied (unprivileged user, unprivileged port)\n");
-        return EXIT_FAILURE;
-        }
       }
     }
   }
       }
     }
   }
@@ -4185,7 +4156,7 @@ root. There will be further calls later for each message received. */
 #ifdef LOAD_AVG_NEEDS_ROOT
 if (receiving_message &&
       (queue_only_load >= 0 ||
 #ifdef LOAD_AVG_NEEDS_ROOT
 if (receiving_message &&
       (queue_only_load >= 0 ||
-        (is_inetd && smtp_load_reserve >= 0)
+        (f.is_inetd && smtp_load_reserve >= 0)
       ))
   {
   load_average = OS_GETLOADAVG();
       ))
   {
   load_average = OS_GETLOADAVG();
@@ -4217,7 +4188,7 @@ to the state Exim usually runs in. */
 
 if (!unprivileged &&                      /* originally had root AND */
     !removed_privilege &&                 /* still got root AND      */
 
 if (!unprivileged &&                      /* originally had root AND */
     !removed_privilege &&                 /* still got root AND      */
-    !daemon_listen &&                     /* not starting the daemon */
+    !f.daemon_listen &&                     /* not starting the daemon */
     queue_interval <= 0 &&                /* (either kind of daemon) */
       (                                   /*    AND EITHER           */
       deliver_drop_privilege ||           /* requested unprivileged  */
     queue_interval <= 0 &&                /* (either kind of daemon) */
       (                                   /*    AND EITHER           */
       deliver_drop_privilege ||           /* requested unprivileged  */
@@ -4225,12 +4196,9 @@ if (!unprivileged &&                      /* originally had root AND */
         queue_interval < 0 &&             /* not running the queue   */
         (msg_action_arg < 0 ||            /*       and               */
           msg_action != MSG_DELIVER) &&   /* not delivering and      */
         queue_interval < 0 &&             /* not running the queue   */
         (msg_action_arg < 0 ||            /*       and               */
           msg_action != MSG_DELIVER) &&   /* not delivering and      */
-        (!checking || !address_test_mode) /* not address checking    */
-        )
-      ))
-  {
+        (!checking || !f.address_test_mode) /* not address checking    */
+   )  ) )
   exim_setugid(exim_uid, exim_gid, TRUE, US"privilege not needed");
   exim_setugid(exim_uid, exim_gid, TRUE, US"privilege not needed");
-  }
 
 /* When we are retaining a privileged uid, we still change to the exim gid. */
 
 
 /* When we are retaining a privileged uid, we still change to the exim gid. */
 
@@ -4244,17 +4212,11 @@ else
   there's no security risk.  For me, it's { exim -bV } on a just-built binary,
   no need to complain then. */
   if (rv == -1)
   there's no security risk.  For me, it's { exim -bV } on a just-built binary,
   no need to complain then. */
   if (rv == -1)
-    {
     if (!(unprivileged || removed_privilege))
     if (!(unprivileged || removed_privilege))
-      {
-      fprintf(stderr,
-          "exim: changing group failed: %s\n", strerror(errno));
-      exit(EXIT_FAILURE);
-      }
+      exim_fail("exim: changing group failed: %s\n", strerror(errno));
     else
       DEBUG(D_any) debug_printf("changing group to %ld failed: %s\n",
           (long int)exim_gid, strerror(errno));
     else
       DEBUG(D_any) debug_printf("changing group to %ld failed: %s\n",
           (long int)exim_gid, strerror(errno));
-    }
   }
 
 /* Handle a request to scan a file for malware */
   }
 
 /* Handle a request to scan a file for malware */
@@ -4312,6 +4274,12 @@ if (msg_action_arg > 0 && msg_action != MSG_DELIVER && msg_action != MSG_LOAD)
   int yield = EXIT_SUCCESS;
   set_process_info("acting on specified messages");
 
   int yield = EXIT_SUCCESS;
   set_process_info("acting on specified messages");
 
+  /* ACL definitions may be needed when removing a message (-Mrm) because
+  event_action gets expanded */
+
+  if (msg_action == MSG_REMOVE)
+    readconf_rest();
+
   if (!one_msg_action)
     {
     for (i = msg_action_arg; i < argc; i++)
   if (!one_msg_action)
     {
     for (i = msg_action_arg; i < argc; i++)
@@ -4325,7 +4293,7 @@ if (msg_action_arg > 0 && msg_action != MSG_DELIVER && msg_action != MSG_LOAD)
   }
 
 /* We used to set up here to skip reading the ACL section, on
   }
 
 /* We used to set up here to skip reading the ACL section, on
- (msg_action_arg > 0 || (queue_interval == 0 && !daemon_listen)
+ (msg_action_arg > 0 || (queue_interval == 0 && !f.daemon_listen)
 Now, since the intro of the ${acl } expansion, ACL definitions may be
 needed in transports so we lost the optimisation. */
 
 Now, since the intro of the ${acl } expansion, ACL definitions may be
 needed in transports so we lost the optimisation. */
 
@@ -4355,7 +4323,7 @@ if (test_retry_arg >= 0)
   if (test_retry_arg >= argc)
     {
     printf("-brt needs a domain or address argument\n");
   if (test_retry_arg >= argc)
     {
     printf("-brt needs a domain or address argument\n");
-    exim_exit(EXIT_FAILURE);
+    exim_exit(EXIT_FAILURE, US"main");
     }
   s1 = argv[test_retry_arg++];
   s2 = NULL;
     }
   s1 = argv[test_retry_arg++];
   s2 = NULL;
@@ -4404,8 +4372,9 @@ if (test_retry_arg >= 0)
       }
     }
 
       }
     }
 
-  yield = retry_find_config(s1, s2, basic_errno, more_errno);
-  if (yield == NULL) printf("No retry information found\n"); else
+  if (!(yield = retry_find_config(s1, s2, basic_errno, more_errno)))
+    printf("No retry information found\n");
+  else
     {
     retry_rule *r;
     more_errno = yield->more_errno;
     {
     retry_rule *r;
     more_errno = yield->more_errno;
@@ -4437,7 +4406,7 @@ if (test_retry_arg >= 0)
       printf("auth_failed  ");
     else printf("*  ");
 
       printf("auth_failed  ");
     else printf("*  ");
 
-    for (r = yield->rules; r != NULL; r = r->next)
+    for (r = yield->rules; r; r = r->next)
       {
       printf("%c,%s", r->rule, readconf_printtime(r->timeout)); /* Do not */
       printf(",%s", readconf_printtime(r->p1));                 /* amalgamate */
       {
       printf("%c,%s", r->rule, readconf_printtime(r->timeout)); /* Do not */
       printf(",%s", readconf_printtime(r->p1));                 /* amalgamate */
@@ -4460,7 +4429,7 @@ if (test_retry_arg >= 0)
 
     printf("\n");
     }
 
     printf("\n");
     }
-  exim_exit(EXIT_SUCCESS);
+  exim_exit(EXIT_SUCCESS, US"main");
   }
 
 /* Handle a request to list one or more configuration options */
   }
 
 /* Handle a request to list one or more configuration options */
@@ -4468,25 +4437,42 @@ if (test_retry_arg >= 0)
 
 if (list_options)
   {
 
 if (list_options)
   {
+  BOOL fail = FALSE;
   set_process_info("listing variables");
   set_process_info("listing variables");
-  if (recipients_arg >= argc) readconf_print(US"all", NULL, flag_n);
-    else for (i = recipients_arg; i < argc; i++)
+  if (recipients_arg >= argc)
+    fail = !readconf_print(US"all", NULL, flag_n);
+  else for (i = recipients_arg; i < argc; i++)
+    {
+    if (i < argc - 1 &&
+       (Ustrcmp(argv[i], "router") == 0 ||
+        Ustrcmp(argv[i], "transport") == 0 ||
+        Ustrcmp(argv[i], "authenticator") == 0 ||
+        Ustrcmp(argv[i], "macro") == 0 ||
+        Ustrcmp(argv[i], "environment") == 0))
       {
       {
-      if (i < argc - 1 &&
-          (Ustrcmp(argv[i], "router") == 0 ||
-           Ustrcmp(argv[i], "transport") == 0 ||
-           Ustrcmp(argv[i], "authenticator") == 0 ||
-           Ustrcmp(argv[i], "macro") == 0 ||
-           Ustrcmp(argv[i], "environment") == 0))
-        {
-        readconf_print(argv[i+1], argv[i], flag_n);
-        i++;
-        }
-      else readconf_print(argv[i], NULL, flag_n);
+      fail |= !readconf_print(argv[i+1], argv[i], flag_n);
+      i++;
       }
       }
-  exim_exit(EXIT_SUCCESS);
+    else
+      fail = !readconf_print(argv[i], NULL, flag_n);
+    }
+  exim_exit(fail ? EXIT_FAILURE : EXIT_SUCCESS, US"main");
   }
 
   }
 
+if (list_config)
+  {
+  set_process_info("listing config");
+  exim_exit(readconf_print(US"config", NULL, flag_n)
+               ? EXIT_SUCCESS : EXIT_FAILURE, US"main");
+  }
+
+
+/* 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
 
 /* 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
@@ -4503,13 +4489,13 @@ message. */
 
 if (msg_action_arg > 0 && msg_action != MSG_LOAD)
   {
 
 if (msg_action_arg > 0 && msg_action != MSG_LOAD)
   {
-  if (prod_requires_admin && !admin_user)
+  if (prod_requires_admin && !f.admin_user)
     {
     fprintf(stderr, "exim: Permission denied\n");
     {
     fprintf(stderr, "exim: Permission denied\n");
-    exim_exit(EXIT_FAILURE);
+    exim_exit(EXIT_FAILURE, US"main");
     }
   set_process_info("delivering specified messages");
     }
   set_process_info("delivering specified messages");
-  if (deliver_give_up) forced_delivery = deliver_force_thaw = TRUE;
+  if (deliver_give_up) forced_delivery = f.deliver_force_thaw = TRUE;
   for (i = msg_action_arg; i < argc; i++)
     {
     int status;
   for (i = msg_action_arg; i < argc; i++)
     {
     int status;
@@ -4525,27 +4511,30 @@ if (msg_action_arg > 0 && msg_action != MSG_LOAD)
       {
       fprintf(stderr, "failed to fork delivery process for %s: %s\n", argv[i],
         strerror(errno));
       {
       fprintf(stderr, "failed to fork delivery process for %s: %s\n", argv[i],
         strerror(errno));
-      exim_exit(EXIT_FAILURE);
+      exim_exit(EXIT_FAILURE, US"main");
       }
     else wait(&status);
     }
       }
     else wait(&status);
     }
-  exim_exit(EXIT_SUCCESS);
+  exim_exit(EXIT_SUCCESS, US"main");
   }
 
 
 /* If only a single queue run is requested, without SMTP listening, we can just
 turn into a queue runner, with an optional starting message id. */
 
   }
 
 
 /* If only a single queue run is requested, without SMTP listening, we can just
 turn into a queue runner, with an optional starting message id. */
 
-if (queue_interval == 0 && !daemon_listen)
+if (queue_interval == 0 && !f.daemon_listen)
   {
   DEBUG(D_queue_run) debug_printf("Single queue run%s%s%s%s\n",
     (start_queue_run_id == NULL)? US"" : US" starting at ",
     (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);
   {
   DEBUG(D_queue_run) debug_printf("Single queue run%s%s%s%s\n",
     (start_queue_run_id == NULL)? US"" : US" starting at ",
     (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);
   queue_run(start_queue_run_id, stop_queue_run_id, FALSE);
-  exim_exit(EXIT_SUCCESS);
+  exim_exit(EXIT_SUCCESS, US"main");
   }
 
 
   }
 
 
@@ -4568,10 +4557,9 @@ for (i = 0;;)
     /* If user name has not been set by -F, set it from the passwd entry
     unless -f has been used to set the sender address by a trusted user. */
 
     /* If user name has not been set by -F, set it from the passwd entry
     unless -f has been used to set the sender address by a trusted user. */
 
-    if (originator_name == NULL)
+    if (!originator_name)
       {
       {
-      if (sender_address == NULL ||
-           (!trusted_caller && filter_test == FTEST_NONE))
+      if (!sender_address || (!f.trusted_caller && filter_test == FTEST_NONE))
         {
         uschar *name = US pw->pw_gecos;
         uschar *amp = Ustrchr(name, '&');
         {
         uschar *name = US pw->pw_gecos;
         uschar *amp = Ustrchr(name, '&');
@@ -4581,11 +4569,11 @@ for (i = 0;;)
         replaced by a copy of the login name, and some even specify that
         the first character should be upper cased, so that's what we do. */
 
         replaced by a copy of the login name, and some even specify that
         the first character should be upper cased, so that's what we do. */
 
-        if (amp != NULL)
+        if (amp)
           {
           int loffset;
           string_format(buffer, sizeof(buffer), "%.*s%n%s%s",
           {
           int loffset;
           string_format(buffer, sizeof(buffer), "%.*s%n%s%s",
-            amp - name, name, &loffset, originator_login, amp + 1);
+            (int)(amp - name), name, &loffset, originator_login, amp + 1);
           buffer[loffset] = toupper(buffer[loffset]);
           name = buffer;
           }
           buffer[loffset] = toupper(buffer[loffset]);
           name = buffer;
           }
@@ -4593,7 +4581,7 @@ for (i = 0;;)
         /* If a pattern for matching the gecos field was supplied, apply
         it and then expand the name string. */
 
         /* If a pattern for matching the gecos field was supplied, apply
         it and then expand the name string. */
 
-        if (gecos_pattern != NULL && gecos_name != NULL)
+        if (gecos_pattern && gecos_name)
           {
           const pcre *re;
           re = regex_must_compile(gecos_pattern, FALSE, TRUE); /* Use malloc */
           {
           const pcre *re;
           re = regex_must_compile(gecos_pattern, FALSE, TRUE); /* Use malloc */
@@ -4602,7 +4590,7 @@ for (i = 0;;)
             {
             uschar *new_name = expand_string(gecos_name);
             expand_nmax = -1;
             {
             uschar *new_name = expand_string(gecos_name);
             expand_nmax = -1;
-            if (new_name != NULL)
+            if (new_name)
               {
               DEBUG(D_receive) debug_printf("user name \"%s\" extracted from "
                 "gecos field \"%s\"\n", new_name, name);
               {
               DEBUG(D_receive) debug_printf("user name \"%s\" extracted from "
                 "gecos field \"%s\"\n", new_name, name);
@@ -4636,7 +4624,7 @@ for (i = 0;;)
 configuration specifies something to use. When running in the test harness,
 any setting of unknown_login overrides the actual name. */
 
 configuration specifies something to use. When running in the test harness,
 any setting of unknown_login overrides the actual name. */
 
-if (originator_login == NULL || running_in_test_harness)
+if (originator_login == NULL || f.running_in_test_harness)
   {
   if (unknown_login != NULL)
     {
   {
   if (unknown_login != NULL)
     {
@@ -4671,7 +4659,7 @@ returns. We leave this till here so that the originator_ fields are available
 for incoming messages via the daemon. The daemon cannot be run in mua_wrapper
 mode. */
 
 for incoming messages via the daemon. The daemon cannot be run in mua_wrapper
 mode. */
 
-if (daemon_listen || inetd_wait_mode || queue_interval > 0)
+if (f.daemon_listen || f.inetd_wait_mode || queue_interval > 0)
   {
   if (mua_wrapper)
     {
   {
   if (mua_wrapper)
     {
@@ -4695,14 +4683,14 @@ originator_* variables set. */
 
 if (test_rewrite_arg >= 0)
   {
 
 if (test_rewrite_arg >= 0)
   {
-  really_exim = FALSE;
+  f.really_exim = FALSE;
   if (test_rewrite_arg >= argc)
     {
     printf("-brw needs an address argument\n");
   if (test_rewrite_arg >= argc)
     {
     printf("-brw needs an address argument\n");
-    exim_exit(EXIT_FAILURE);
+    exim_exit(EXIT_FAILURE, US"main");
     }
   rewrite_test(argv[test_rewrite_arg]);
     }
   rewrite_test(argv[test_rewrite_arg]);
-  exim_exit(EXIT_SUCCESS);
+  exim_exit(EXIT_SUCCESS, US"main");
   }
 
 /* A locally-supplied message is considered to be coming from a local user
   }
 
 /* A locally-supplied message is considered to be coming from a local user
@@ -4710,9 +4698,9 @@ unless a trusted caller supplies a sender address with -f, or is passing in the
 message via SMTP (inetd invocation or otherwise). */
 
 if ((sender_address == NULL && !smtp_input) ||
 message via SMTP (inetd invocation or otherwise). */
 
 if ((sender_address == NULL && !smtp_input) ||
-    (!trusted_caller && filter_test == FTEST_NONE))
+    (!f.trusted_caller && filter_test == FTEST_NONE))
   {
   {
-  sender_local = TRUE;
+  f.sender_local = TRUE;
 
   /* A trusted caller can supply authenticated_sender and authenticated_id
   via -oMas and -oMai and if so, they will already be set. Otherwise, force
 
   /* A trusted caller can supply authenticated_sender and authenticated_id
   via -oMas and -oMai and if so, they will already be set. Otherwise, force
@@ -4742,18 +4730,17 @@ if ((!smtp_input && sender_address == NULL) ||
   if (sender_address == NULL             /* No sender_address set */
        ||                                /*         OR            */
        (sender_address[0] != 0 &&        /* Non-empty sender address, AND */
   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 = originator_login;
-    sender_address_forced = FALSE;
+    f.sender_address_forced = FALSE;
     sender_address_domain = 0;
     }
   }
 
 /* Remember whether an untrusted caller set the sender address */
 
     sender_address_domain = 0;
     }
   }
 
 /* Remember whether an untrusted caller set the sender address */
 
-sender_set_untrusted = sender_address != originator_login && !trusted_caller;
+f.sender_set_untrusted = sender_address != originator_login && !f.trusted_caller;
 
 /* Ensure that the sender address is fully qualified unless it is the empty
 address, which indicates an error message, or doesn't exist (root caller, smtp
 
 /* Ensure that the sender address is fully qualified unless it is the empty
 address, which indicates an error message, or doesn't exist (root caller, smtp
@@ -4772,7 +4759,7 @@ predicated upon the sender. If no arguments are given, read addresses from
 stdin. Set debug_level to at least D_v to get full output for address testing.
 */
 
 stdin. Set debug_level to at least D_v to get full output for address testing.
 */
 
-if (verify_address_mode || address_test_mode)
+if (verify_address_mode || f.address_test_mode)
   {
   int exit_value = 0;
   int flags = vopt_qualify;
   {
   int exit_value = 0;
   int flags = vopt_qualify;
@@ -4818,7 +4805,7 @@ if (verify_address_mode || address_test_mode)
     }
 
   route_tidyup();
     }
 
   route_tidyup();
-  exim_exit(exit_value);
+  exim_exit(exit_value, US"main");
   }
 
 /* Handle expansion checking. Either expand items on the command line, or read
   }
 
 /* Handle expansion checking. Either expand items on the command line, or read
@@ -4828,17 +4815,15 @@ Otherwise, if -bem was used, read a message from stdin. */
 
 if (expansion_test)
   {
 
 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() */
   if (msg_action_arg > 0 && msg_action == MSG_LOAD)
     {
     uschar spoolname[256];  /* Not big_buffer; used in spool_read_header() */
-    if (!admin_user)
-      {
-      fprintf(stderr, "exim: permission denied\n");
-      exit(EXIT_FAILURE);
-      }
+    if (!f.admin_user)
+      exim_fail("exim: permission denied\n");
     message_id = argv[msg_action_arg];
     (void)string_format(spoolname, sizeof(spoolname), "%s-H", message_id);
     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);
       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);
@@ -4847,16 +4832,13 @@ if (expansion_test)
   /* Read a test message from a file. We fudge it up to be on stdin, saving
   stdin itself for later reading of expansion strings. */
 
   /* Read a test message from a file. We fudge it up to be on stdin, saving
   stdin itself for later reading of expansion strings. */
 
-  else if (expansion_test_message != NULL)
+  else if (expansion_test_message)
     {
     int save_stdin = dup(0);
     int fd = Uopen(expansion_test_message, O_RDONLY, 0);
     if (fd < 0)
     {
     int save_stdin = dup(0);
     int fd = Uopen(expansion_test_message, O_RDONLY, 0);
     if (fd < 0)
-      {
-      fprintf(stderr, "exim: failed to open %s: %s\n", expansion_test_message,
+      exim_fail("exim: failed to open %s: %s\n", expansion_test_message,
         strerror(errno));
         strerror(errno));
-      return EXIT_FAILURE;
-      }
     (void) dup2(fd, 0);
     filter_test = FTEST_USER;      /* Fudge to make it look like filter test */
     message_ended = END_NOTENDED;
     (void) dup2(fd, 0);
     filter_test = FTEST_USER;      /* Fudge to make it look like filter test */
     message_ended = END_NOTENDED;
@@ -4867,22 +4849,19 @@ if (expansion_test)
     clearerr(stdin);               /* Required by Darwin */
     }
 
     clearerr(stdin);               /* Required by Darwin */
     }
 
+  /* Only admin users may see config-file macros this way */
+
+  if (!f.admin_user) macros_user = macros = mlast = NULL;
+
   /* Allow $recipients for this testing */
 
   /* Allow $recipients for this testing */
 
-  enable_dollar_recipients = TRUE;
+  f.enable_dollar_recipients = TRUE;
 
   /* Expand command line items */
 
   if (recipients_arg < argc)
 
   /* Expand command line items */
 
   if (recipients_arg < argc)
-    {
     while (recipients_arg < argc)
     while (recipients_arg < argc)
-      {
-      uschar *s = argv[recipients_arg++];
-      uschar *ss = expand_string(s);
-      if (ss == NULL) printf ("Failed: %s\n", expand_string_message);
-      else printf("%s\n", CS ss);
-      }
-    }
+      expansion_test_line(argv[recipients_arg++]);
 
   /* Read stdin */
 
 
   /* Read stdin */
 
@@ -4890,25 +4869,18 @@ if (expansion_test)
     {
     char *(*fn_readline)(const char *) = NULL;
     void (*fn_addhist)(const char *) = NULL;
     {
     char *(*fn_readline)(const char *) = NULL;
     void (*fn_addhist)(const char *) = NULL;
+    uschar * s;
 
 
-    #ifdef USE_READLINE
+#ifdef USE_READLINE
     void *dlhandle = set_readline(&fn_readline, &fn_addhist);
     void *dlhandle = set_readline(&fn_readline, &fn_addhist);
-    #endif
+#endif
 
 
-    for (;;)
-      {
-      uschar *ss;
-      uschar *source = get_stdinput(fn_readline, fn_addhist);
-      if (source == NULL) break;
-      ss = expand_string(source);
-      if (ss == NULL)
-        printf ("Failed: %s\n", expand_string_message);
-      else printf("%s\n", CS ss);
-      }
+    while (s = get_stdinput(fn_readline, fn_addhist))
+      expansion_test_line(s);
 
 
-    #ifdef USE_READLINE
-    if (dlhandle != NULL) dlclose(dlhandle);
-    #endif
+#ifdef USE_READLINE
+    if (dlhandle) dlclose(dlhandle);
+#endif
     }
 
   /* The data file will be open after -Mset */
     }
 
   /* The data file will be open after -Mset */
@@ -4919,7 +4891,7 @@ if (expansion_test)
     deliver_datafile = -1;
     }
 
     deliver_datafile = -1;
     }
 
-  exim_exit(EXIT_SUCCESS);
+  exim_exit(EXIT_SUCCESS, US"main: expansion test");
   }
 
 
   }
 
 
@@ -4933,7 +4905,7 @@ if (raw_active_hostname != NULL)
   uschar *nah = expand_string(raw_active_hostname);
   if (nah == NULL)
     {
   uschar *nah = expand_string(raw_active_hostname);
   if (nah == NULL)
     {
-    if (!expand_string_forcedfail)
+    if (!f.expand_string_forcedfail)
       log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to expand \"%s\" "
         "(smtp_active_hostname): %s", raw_active_hostname,
         expand_string_message);
       log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to expand \"%s\" "
         "(smtp_active_hostname): %s", raw_active_hostname,
         expand_string_message);
@@ -4956,12 +4928,12 @@ if (host_checking)
   if (!sender_ident_set)
     {
     sender_ident = NULL;
   if (!sender_ident_set)
     {
     sender_ident = NULL;
-    if (running_in_test_harness && sender_host_port != 0 &&
+    if (f.running_in_test_harness && sender_host_port != 0 &&
         interface_address != NULL && interface_port != 0)
       verify_get_ident(1413);
     }
 
         interface_address != NULL && interface_port != 0)
       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);
   it. The code works for both IPv4 and IPv6, as it happens. */
 
   size = host_aton(sender_host_address, x);
@@ -4974,8 +4946,8 @@ if (host_checking)
   smtp_input = TRUE;
   smtp_in = stdin;
   smtp_out = stdout;
   smtp_input = TRUE;
   smtp_in = stdin;
   smtp_out = stdout;
-  sender_local = FALSE;
-  sender_host_notsocket = TRUE;
+  f.sender_local = FALSE;
+  f.sender_host_notsocket = TRUE;
   debug_file = stderr;
   debug_fd = fileno(debug_file);
   fprintf(stdout, "\n**** SMTP testing session as if from host %s\n"
   debug_file = stderr;
   debug_fd = fileno(debug_file);
   fprintf(stdout, "\n**** SMTP testing session as if from host %s\n"
@@ -4983,8 +4955,9 @@ if (host_checking)
     "**** This is not for real!\n\n",
       sender_host_address);
 
     "**** 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)
   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,
   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,
@@ -4994,16 +4967,25 @@ if (host_checking)
 
   if (smtp_start_session())
     {
 
   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;
       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();
     }
       }
     smtp_log_no_mail();
     }
-  exim_exit(EXIT_SUCCESS);
+  exim_exit(EXIT_SUCCESS, US"main");
   }
 
 
   }
 
 
@@ -5016,6 +4998,8 @@ if (recipients_arg >= argc && !extract_recipients && !smtp_input)
   {
   if (version_printed)
     {
   {
   if (version_printed)
     {
+    if (Ustrchr(config_main_filelist, ':'))
+      printf("Configuration file search path is %s\n", config_main_filelist);
     printf("Configuration file is %s\n", config_main_filename);
     return EXIT_SUCCESS;
     }
     printf("Configuration file is %s\n", config_main_filename);
     return EXIT_SUCCESS;
     }
@@ -5047,12 +5031,15 @@ to override any SMTP queueing. */
 
 if (mua_wrapper)
   {
 
 if (mua_wrapper)
   {
-  synchronous_delivery = TRUE;
+  f.synchronous_delivery = TRUE;
   arg_error_handling = ERRORS_STDERR;
   remote_max_parallel = 1;
   deliver_drop_privilege = TRUE;
   arg_error_handling = ERRORS_STDERR;
   remote_max_parallel = 1;
   deliver_drop_privilege = TRUE;
-  queue_smtp = FALSE;
+  f.queue_smtp = FALSE;
   queue_smtp_domains = NULL;
   queue_smtp_domains = NULL;
+#ifdef SUPPORT_I18N
+  message_utf8_downconvert = -1;       /* convert-if-needed */
+#endif
   }
 
 
   }
 
 
@@ -5071,7 +5058,7 @@ if (!smtp_input) error_handling = arg_error_handling;
 logging being sent down the socket and make an identd call to get the
 sender_ident. */
 
 logging being sent down the socket and make an identd call to get the
 sender_ident. */
 
-else if (is_inetd)
+else if (f.is_inetd)
   {
   (void)fclose(stderr);
   exim_nullstd();                       /* Re-open to /dev/null */
   {
   (void)fclose(stderr);
   exim_nullstd();                       /* Re-open to /dev/null */
@@ -5086,18 +5073,18 @@ already been done (which it will have been for inetd). This caters for the
 case when it is forced by -oMa. However, we must flag that it isn't a socket,
 so that the test for IP options is skipped for -bs input. */
 
 case when it is forced by -oMa. However, we must flag that it isn't a socket,
 so that the test for IP options is skipped for -bs input. */
 
-if (sender_host_address != NULL && sender_fullhost == NULL)
+if (sender_host_address && !sender_fullhost)
   {
   host_build_sender_fullhost();
   set_process_info("handling incoming connection from %s via -oMa",
     sender_fullhost);
   {
   host_build_sender_fullhost();
   set_process_info("handling incoming connection from %s via -oMa",
     sender_fullhost);
-  sender_host_notsocket = TRUE;
+  f.sender_host_notsocket = TRUE;
   }
 
 /* Otherwise, set the sender host as unknown except for inetd calls. This
 prevents host checking in the case of -bs not from inetd and also for -bS. */
 
   }
 
 /* Otherwise, set the sender host as unknown except for inetd calls. This
 prevents host checking in the case of -bs not from inetd and also for -bS. */
 
-else if (!is_inetd) sender_host_unknown = TRUE;
+else if (!f.is_inetd) f.sender_host_unknown = TRUE;
 
 /* If stdout does not exist, then dup stdin to stdout. This can happen
 if exim is started from inetd. In this case fd 0 will be set to the socket,
 
 /* If stdout does not exist, then dup stdin to stdout. This can happen
 if exim is started from inetd. In this case fd 0 will be set to the socket,
@@ -5113,14 +5100,17 @@ batch/HELO/EHLO/AUTH/TLS. */
 
 if (smtp_input)
   {
 
 if (smtp_input)
   {
-  if (!is_inetd) set_process_info("accepting a local %sSMTP message from <%s>",
+  if (!f.is_inetd) set_process_info("accepting a local %sSMTP message from <%s>",
     smtp_batched_input? "batched " : "",
     (sender_address!= NULL)? sender_address : originator_login);
   }
 else
   {
     smtp_batched_input? "batched " : "",
     (sender_address!= NULL)? sender_address : originator_login);
   }
 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);
     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);
   }
   set_process_info("accepting a local non-SMTP message from <%s>",
     sender_address);
   }
@@ -5137,10 +5127,7 @@ message! (For interactive SMTP, the check happens at MAIL FROM and an SMTP
 error code is given.) */
 
 if ((!smtp_input || smtp_batched_input) && !receive_check_fs(0))
 error code is given.) */
 
 if ((!smtp_input || smtp_batched_input) && !receive_check_fs(0))
-  {
-  fprintf(stderr, "exim: insufficient disk space\n");
-  return EXIT_FAILURE;
-  }
+  exim_fail("exim: insufficient disk space\n");
 
 /* If this is smtp input of any kind, real or batched, handle the start of the
 SMTP session.
 
 /* If this is smtp input of any kind, real or batched, handle the start of the
 SMTP session.
@@ -5154,13 +5141,14 @@ if (smtp_input)
   {
   smtp_in = stdin;
   smtp_out = stdout;
   {
   smtp_in = stdin;
   smtp_out = stdout;
+  memset(sender_host_cache, 0, sizeof(sender_host_cache));
   if (verify_check_host(&hosts_connection_nolog) == OK)
   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())
     {
     mac_smtp_fflush();
   log_write(L_smtp_connection, LOG_MAIN, "%s", smtp_get_connection_info());
   if (!smtp_start_session())
     {
     mac_smtp_fflush();
-    exim_exit(EXIT_SUCCESS);
+    exim_exit(EXIT_SUCCESS, US"smtp_start toplevel");
     }
   }
 
     }
   }
 
@@ -5169,15 +5157,13 @@ if (smtp_input)
 else
   {
   thismessage_size_limit = expand_string_integer(message_size_limit, TRUE);
 else
   {
   thismessage_size_limit = expand_string_integer(message_size_limit, TRUE);
-  if (expand_string_message != NULL)
-    {
+  if (expand_string_message)
     if (thismessage_size_limit == -1)
       log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to expand "
         "message_size_limit: %s", expand_string_message);
     else
       log_write(0, LOG_MAIN|LOG_PANIC_DIE, "invalid value for "
         "message_size_limit: %s", expand_string_message);
     if (thismessage_size_limit == -1)
       log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to expand "
         "message_size_limit: %s", expand_string_message);
     else
       log_write(0, LOG_MAIN|LOG_PANIC_DIE, "invalid value for "
         "message_size_limit: %s", expand_string_message);
-    }
   }
 
 /* Loop for several messages when reading SMTP input. If we fork any child
   }
 
 /* Loop for several messages when reading SMTP input. If we fork any child
@@ -5205,10 +5191,10 @@ 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.
 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. */
 
 that SIG_IGN works. */
 
-if (!synchronous_delivery)
+if (!f.synchronous_delivery)
   {
   #ifdef SA_NOCLDWAIT
   struct sigaction act;
   {
   #ifdef SA_NOCLDWAIT
   struct sigaction act;
@@ -5233,7 +5219,6 @@ collapsed). */
 
 while (more)
   {
 
 while (more)
   {
-  store_reset(reset_point);
   message_id[0] = 0;
 
   /* Handle the SMTP case; call smtp_setup_mst() to deal with the initial SMTP
   message_id[0] = 0;
 
   /* Handle the SMTP case; call smtp_setup_mst() to deal with the initial SMTP
@@ -5264,10 +5249,10 @@ while (more)
       if (smtp_batched_input && acl_not_smtp_start != NULL)
         {
         uschar *user_msg, *log_msg;
       if (smtp_batched_input && acl_not_smtp_start != NULL)
         {
         uschar *user_msg, *log_msg;
-        enable_dollar_recipients = TRUE;
+        f.enable_dollar_recipients = TRUE;
         (void)acl_check(ACL_WHERE_NOTSMTP_START, NULL, acl_not_smtp_start,
           &user_msg, &log_msg);
         (void)acl_check(ACL_WHERE_NOTSMTP_START, NULL, acl_not_smtp_start,
           &user_msg, &log_msg);
-        enable_dollar_recipients = FALSE;
+        f.enable_dollar_recipients = FALSE;
         }
 
       /* Now get the data for the message */
         }
 
       /* Now get the data for the message */
@@ -5275,15 +5260,17 @@ while (more)
       more = receive_msg(extract_recipients);
       if (message_id[0] == 0)
         {
       more = receive_msg(extract_recipients);
       if (message_id[0] == 0)
         {
-        if (more) continue;
+       cancel_cutthrough_connection(TRUE, US"receive dropped");
+        if (more) goto moreloop;
         smtp_log_no_mail();               /* Log no mail if configured */
         smtp_log_no_mail();               /* Log no mail if configured */
-        exim_exit(EXIT_FAILURE);
+        exim_exit(EXIT_FAILURE, US"receive toplevel");
         }
       }
     else
       {
         }
       }
     else
       {
+      cancel_cutthrough_connection(TRUE, US"message setup dropped");
       smtp_log_no_mail();               /* Log no mail if configured */
       smtp_log_no_mail();               /* Log no mail if configured */
-      exim_exit((rc == 0)? EXIT_SUCCESS : EXIT_FAILURE);
+      exim_exit(rc ? EXIT_FAILURE : EXIT_SUCCESS, US"msg setup toplevel");
       }
     }
 
       }
     }
 
@@ -5302,8 +5289,8 @@ while (more)
 
     /* These options cannot be changed dynamically for non-SMTP messages */
 
 
     /* These options cannot be changed dynamically for non-SMTP messages */
 
-    active_local_sender_retain = local_sender_retain;
-    active_local_from_check = local_from_check;
+    f.active_local_sender_retain = local_sender_retain;
+    f.active_local_from_check = local_from_check;
 
     /* Save before any rewriting */
 
 
     /* Save before any rewriting */
 
@@ -5331,24 +5318,32 @@ while (more)
 
         if (recipients_max > 0 && ++rcount > recipients_max &&
             !extract_recipients)
 
         if (recipients_max > 0 && ++rcount > recipients_max &&
             !extract_recipients)
-          {
           if (error_handling == ERRORS_STDERR)
             {
             fprintf(stderr, "exim: too many recipients\n");
           if (error_handling == ERRORS_STDERR)
             {
             fprintf(stderr, "exim: too many recipients\n");
-            exim_exit(EXIT_FAILURE);
+            exim_exit(EXIT_FAILURE, US"main");
             }
           else
             }
           else
-            {
             return
               moan_to_sender(ERRMESS_TOOMANYRECIP, NULL, NULL, stdin, TRUE)?
                 errors_sender_rc : EXIT_FAILURE;
             return
               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);
 
         recipient =
           parse_extract_address(s, &errmess, &start, &end, &domain, FALSE);
 
-        if (domain == 0 && !allow_unqualified_recipient)
+#ifdef SUPPORT_I18N
+       if (string_is_utf8(recipient))
+         message_smtputf8 = TRUE;
+       else
+         allow_utf8_domains = b;
+       }
+#endif
+        if (domain == 0 && !f.allow_unqualified_recipient)
           {
           recipient = NULL;
           errmess = US"unqualified recipient address not allowed";
           {
           recipient = NULL;
           errmess = US"unqualified recipient address not allowed";
@@ -5360,7 +5355,7 @@ while (more)
             {
             fprintf(stderr, "exim: bad recipient address \"%s\": %s\n",
               string_printing(list[i]), errmess);
             {
             fprintf(stderr, "exim: bad recipient address \"%s\": %s\n",
               string_printing(list[i]), errmess);
-            exim_exit(EXIT_FAILURE);
+            exim_exit(EXIT_FAILURE, US"main");
             }
           else
             {
             }
           else
             {
@@ -5399,13 +5394,27 @@ 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. */
 
     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;
       {
       uschar *user_msg, *log_msg;
-      enable_dollar_recipients = TRUE;
+      f.enable_dollar_recipients = TRUE;
       (void)acl_check(ACL_WHERE_NOTSMTP_START, NULL, acl_not_smtp_start,
         &user_msg, &log_msg);
       (void)acl_check(ACL_WHERE_NOTSMTP_START, NULL, acl_not_smtp_start,
         &user_msg, &log_msg);
-      enable_dollar_recipients = FALSE;
+      f.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 = { .tv_sec = 30*60, .tv_usec = 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
       }
 
     /* Read the data for the message. If filter_test is not FTEST_NONE, this
@@ -5419,7 +5428,7 @@ while (more)
     for real; when reading the headers of a message for filter testing,
     it is TRUE if the headers were terminated by '.' and FALSE otherwise. */
 
     for real; when reading the headers of a message for filter testing,
     it is TRUE if the headers were terminated by '.' and FALSE otherwise. */
 
-    if (message_id[0] == 0) exim_exit(EXIT_FAILURE);
+    if (message_id[0] == 0) exim_exit(EXIT_FAILURE, US"main");
     }  /* Non-SMTP message reception */
 
   /* If this is a filter testing run, there are headers in store, but
     }  /* Non-SMTP message reception */
 
   /* If this is a filter testing run, there are headers in store, but
@@ -5447,9 +5456,7 @@ while (more)
       return_path = string_copy(sender_address);
       }
     else
       return_path = string_copy(sender_address);
       }
     else
-      {
       printf("Return-path = %s\n", (return_path[0] == 0)? US"<>" : return_path);
       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(
     printf("Sender      = %s\n", (sender_address[0] == 0)? US"<>" : sender_address);
 
     receive_add_recipient(
@@ -5466,7 +5473,7 @@ while (more)
     if (chdir("/"))   /* Get away from wherever the user is running this from */
       {
       DEBUG(D_receive) debug_printf("chdir(\"/\") failed\n");
     if (chdir("/"))   /* Get away from wherever the user is running this from */
       {
       DEBUG(D_receive) debug_printf("chdir(\"/\") failed\n");
-      exim_exit(EXIT_FAILURE);
+      exim_exit(EXIT_FAILURE, US"main");
       }
 
     /* Now we run either a system filter test, or a user filter test, or both.
       }
 
     /* Now we run either a system filter test, or a user filter test, or both.
@@ -5475,20 +5482,16 @@ while (more)
     explicitly. */
 
     if ((filter_test & FTEST_SYSTEM) != 0)
     explicitly. */
 
     if ((filter_test & FTEST_SYSTEM) != 0)
-      {
       if (!filter_runtest(filter_sfd, filter_test_sfile, TRUE, more))
       if (!filter_runtest(filter_sfd, filter_test_sfile, TRUE, more))
-        exim_exit(EXIT_FAILURE);
-      }
+        exim_exit(EXIT_FAILURE, US"main");
 
     memcpy(filter_sn, filter_n, sizeof(filter_sn));
 
     if ((filter_test & FTEST_USER) != 0)
 
     memcpy(filter_sn, filter_n, sizeof(filter_sn));
 
     if ((filter_test & FTEST_USER) != 0)
-      {
       if (!filter_runtest(filter_ufd, filter_test_ufile, FALSE, more))
       if (!filter_runtest(filter_ufd, filter_test_ufile, FALSE, more))
-        exim_exit(EXIT_FAILURE);
-      }
+        exim_exit(EXIT_FAILURE, US"main");
 
 
-    exim_exit(EXIT_SUCCESS);
+    exim_exit(EXIT_SUCCESS, US"main");
     }
 
   /* Else act on the result of message reception. We should not get here unless
     }
 
   /* Else act on the result of message reception. We should not get here unless
@@ -5529,27 +5532,34 @@ while (more)
   are ignored. */
 
   if (mua_wrapper)
   are ignored. */
 
   if (mua_wrapper)
-    local_queue_only = queue_only_policy = deliver_freeze = FALSE;
+    local_queue_only = f.queue_only_policy = f.deliver_freeze = FALSE;
 
   /* Log the queueing here, when it will get a message id attached, but
   not if queue_only is set (case 0). Case 1 doesn't happen here (too many
   connections). */
 
 
   /* Log the queueing here, when it will get a message id attached, but
   not if queue_only is set (case 0). Case 1 doesn't happen here (too many
   connections). */
 
-  if (local_queue_only) switch(queue_only_reason)
+  if (local_queue_only)
     {
     {
-    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;
-
-    case 3:
-    log_write(L_delay_delivery,
-              LOG_MAIN, "no immediate delivery: load average %.2f",
-              (double)load_average/1000.0);
-    break;
+    cancel_cutthrough_connection(TRUE, US"no delivery; queueing");
+    switch(queue_only_reason)
+      {
+      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;
+
+      case 3:
+       log_write(L_delay_delivery,
+               LOG_MAIN, "no immediate delivery: load average %.2f",
+               (double)load_average/1000.0);
+      break;
+      }
     }
 
     }
 
+  else if (f.queue_only_policy || f.deliver_freeze)
+    cancel_cutthrough_connection(TRUE, US"no delivery; queueing");
+
   /* Else do the delivery unless the ACL or local_scan() called for queue only
   or froze the message. Always deliver in a separate process. A fork failure is
   not a disaster, as the delivery will eventually happen on a subsequent queue
   /* Else do the delivery unless the ACL or local_scan() called for queue only
   or froze the message. Always deliver in a separate process. A fork failure is
   not a disaster, as the delivery will eventually happen on a subsequent queue
@@ -5558,7 +5568,7 @@ while (more)
   thereby defer the delivery if it tries to use (for example) a cached ldap
   connection that the parent has called unbind on. */
 
   thereby defer the delivery if it tries to use (for example) a cached ldap
   connection that the parent has called unbind on. */
 
-  else if (!queue_only_policy && !deliver_freeze)
+  else
     {
     pid_t pid;
     search_tidyup();
     {
     pid_t pid;
     search_tidyup();
@@ -5574,8 +5584,7 @@ while (more)
 
       if (geteuid() != root_uid && !deliver_drop_privilege && !unprivileged)
         {
 
       if (geteuid() != root_uid && !deliver_drop_privilege && !unprivileged)
         {
-        (void)child_exec_exim(CEE_EXEC_EXIT, FALSE, NULL, FALSE, 2, US"-Mc",
-          message_id);
+       delivery_re_exec(CEE_EXEC_EXIT);
         /* Control does not return here. */
         }
 
         /* Control does not return here. */
         }
 
@@ -5589,22 +5598,27 @@ while (more)
 
     if (pid < 0)
       {
 
     if (pid < 0)
       {
+      cancel_cutthrough_connection(TRUE, US"delivery fork failed");
       log_write(0, LOG_MAIN|LOG_PANIC, "failed to fork automatic delivery "
         "process: %s", strerror(errno));
       }
       log_write(0, LOG_MAIN|LOG_PANIC, "failed to fork automatic delivery "
         "process: %s", strerror(errno));
       }
-
-    /* In the parent, wait if synchronous delivery is required. This will
-    always be the case in MUA wrapper mode. */
-
-    else if (synchronous_delivery)
+    else
       {
       {
-      int status;
-      while (wait(&status) != pid);
-      if ((status & 0x00ff) != 0)
-        log_write(0, LOG_MAIN|LOG_PANIC,
-          "process %d crashed with signal %d while delivering %s",
-          (int)pid, status & 0x00ff, message_id);
-      if (mua_wrapper && (status & 0xffff) != 0) exim_exit(EXIT_FAILURE);
+      release_cutthrough_connection(US"msg passed for delivery");
+
+      /* In the parent, wait if synchronous delivery is required. This will
+      always be the case in MUA wrapper mode. */
+
+      if (f.synchronous_delivery)
+       {
+       int status;
+       while (wait(&status) != pid);
+       if ((status & 0x00ff) != 0)
+         log_write(0, LOG_MAIN|LOG_PANIC,
+           "process %d crashed with signal %d while delivering %s",
+           (int)pid, status & 0x00ff, message_id);
+       if (mua_wrapper && (status & 0xffff) != 0) exim_exit(EXIT_FAILURE, US"main");
+       }
       }
     }
 
       }
     }
 
@@ -5616,10 +5630,28 @@ while (more)
   #ifndef SIG_IGN_WORKS
   while (waitpid(-1, NULL, WNOHANG) > 0);
   #endif
   #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 */
+exim_exit(EXIT_SUCCESS, US"main");   /* Never returns */
 return 0;                  /* To stop compiler warning */
 }
 
 return 0;                  /* To stop compiler warning */
 }
 
+
 /* End of exim.c */
 /* End of exim.c */
index fb48a43..79d1acf 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
 /* 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. */
 
 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
 /* Assume most systems have statfs() unless os.h undefines this macro */
 
 #define HAVE_STATFS
@@ -281,18 +284,6 @@ disabused of the notion. Luckily, since EX_OK is not used, it didn't matter.] */
 #include <arpa/nameser.h>
 
 
 #include <arpa/nameser.h>
 
 
-/* If arpa/nameser.h defines a maximum name server packet size, use it,
-provided it is greater than 2048. Otherwise go for a default. PACKETSZ was used
-for this, but it seems that NS_PACKETSZ is coming into use. */
-
-#if defined(NS_PACKETSZ) && NS_PACKETSZ >= 2048
-  #define MAXPACKET NS_PACKETSZ
-#elif defined(PACKETSZ) && PACKETSZ >= 2048
-  #define MAXPACKET PACKETSZ
-#else
-  #define MAXPACKET 2048
-#endif
-
 /* While IPv6 is still young the definitions of T_AAAA and T_A6 may not be
 included in arpa/nameser.h. Fudge them here. */
 
 /* While IPv6 is still young the definitions of T_AAAA and T_A6 may not be
 included in arpa/nameser.h. Fudge them here. */
 
@@ -418,7 +409,7 @@ iconv(). It's os.h file defines ICONV_ARG2_TYPE. For the rest, define a default
 here. */
 
 #ifndef ICONV_ARG2_TYPE
 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 */
 #endif
 
 /* One OS uses a different type for the 5th argument of getsockopt */
@@ -489,7 +480,9 @@ config.h, mytypes.h, and store.h, so we don't need to mention them explicitly.
 #include "macros.h"
 #include "dbstuff.h"
 #include "structs.h"
 #include "macros.h"
 #include "dbstuff.h"
 #include "structs.h"
+#include "blob.h"
 #include "globals.h"
 #include "globals.h"
+#include "hash.h"
 #include "functions.h"
 #include "dbfunctions.h"
 #include "osfunctions.h"
 #include "functions.h"
 #include "dbfunctions.h"
 #include "osfunctions.h"
@@ -497,7 +490,7 @@ config.h, mytypes.h, and store.h, so we don't need to mention them explicitly.
 #ifdef EXPERIMENTAL_BRIGHTMAIL
 # include "bmi_spam.h"
 #endif
 #ifdef EXPERIMENTAL_BRIGHTMAIL
 # include "bmi_spam.h"
 #endif
-#ifdef EXPERIMENTAL_SPF
+#ifdef SUPPORT_SPF
 # include "spf.h"
 #endif
 #ifdef EXPERIMENTAL_SRS
 # include "spf.h"
 #endif
 #ifdef EXPERIMENTAL_SRS
@@ -547,10 +540,16 @@ union sockaddr_46 {
 };
 
 /* If SUPPORT_TLS is not defined, ensure that USE_GNUTLS is also not defined
 };
 
 /* 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 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,
 #endif
 
 /* If SPOOL_DIRECTORY, LOG_FILE_PATH or PID_FILE_PATH have not been defined,
@@ -581,14 +580,22 @@ default to EDQUOT if it exists, otherwise ENOSPC. */
 # endif
 #endif
 
 # endif
 #endif
 
-/* Ensure PATH_MAX is defined */
+/* DANE w/o DNSSEC is useless */
+#if defined(SUPPORT_DANE) && defined(DISABLE_DNSSEC)
+# error DANE support requires DNSSEC support
+#endif
 
 
-#ifndef PATH_MAX
-  #ifdef MAXPATHLEN
-  # define PATH_MAX MAXPATHLEN
-  #else
-  # define PATH_MAX 1024
-  #endif
+/* Some platforms (FreeBSD, OpenBSD, Solaris) do not seem to define this */
+
+#ifndef POLLRDHUP
+# define POLLRDHUP (POLLIN | POLLHUP)
 #endif
 
 #endif
 
+/* Some platforms (Darwin) have to define a larger limit on groups membership */
+
+#ifndef EXIM_GROUPLIST_SIZE
+# define EXIM_GROUPLIST_SIZE NGROUPS_MAX
+#endif
+
+#endif
 /* End of exim.h */
 /* End of exim.h */
index 84d03c0..360f307 100755 (executable)
@@ -63,9 +63,18 @@ if test "$exim_path" = ""; then exim_path=BIN_DIRECTORY/exim; fi
 
 PERL_COMMAND - $exim_path $args <<'End'
 
 
 PERL_COMMAND - $exim_path $args <<'End'
 
+BEGIN { pop @INC if $INC[-1] eq '.' };
 use FileHandle;
 use FileHandle;
+use File::Basename;
 use IPC::Open2;
 
 use IPC::Open2;
 
+if ($ARGV[0] eq '--version') {
+    print basename($0) . ": $0\n",
+          "build: EXIM_RELEASE_VERSIONEXIM_VARIANT_VERSION\n",
+          "perl(runtime): $]\n";
+          exit 0;
+}
+
 if (scalar(@ARGV) < 3)
   {
   print "Usage: exim_checkaccess <IP address> <email address> [exim options]\n";
 if (scalar(@ARGV) < 3)
   {
   print "Usage: exim_checkaccess <IP address> <email address> [exim options]\n";
index ce06f16..afd5095 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -30,6 +30,7 @@ characters. */
 
 #include "exim.h"
 
 
 #include "exim.h"
 
+uschar * spool_directory = NULL;       /* dummy for dbstuff.h */
 
 #define max_insize   20000
 #define max_outsize 100000
 
 #define max_insize   20000
 #define max_outsize 100000
@@ -88,10 +89,10 @@ Returns:   the value of the character escape
 */
 
 int
 */
 
 int
-string_interpret_escape(uschar **pp)
+string_interpret_escape(const uschar **pp)
 {
 int ch;
 {
 int ch;
-uschar *p = *pp;
+const uschar *p = *pp;
 ch = *(++p);
 if (isdigit(ch) && ch != '8' && ch != '9')
   {
 ch = *(++p);
 if (isdigit(ch) && ch != '8' && ch != '9')
   {
@@ -151,6 +152,7 @@ uschar *bptr;
 uschar  keybuffer[256];
 uschar  temp_dbmname[512];
 uschar  real_dbmname[512];
 uschar  keybuffer[256];
 uschar  temp_dbmname[512];
 uschar  real_dbmname[512];
+uschar  dirname[512];
 uschar *buffer = malloc(max_outsize);
 uschar *line = malloc(max_insize);
 
 uschar *buffer = malloc(max_outsize);
 uschar *line = malloc(max_insize);
 
@@ -205,10 +207,16 @@ if (strlen(argv[arg+1]) > sizeof(temp_dbmname) - 20)
 Ustrcpy(temp_dbmname, argv[arg+1]);
 Ustrcat(temp_dbmname, ".dbmbuild_temp");
 
 Ustrcpy(temp_dbmname, argv[arg+1]);
 Ustrcat(temp_dbmname, ".dbmbuild_temp");
 
+Ustrcpy(dirname, temp_dbmname);
+if ((bptr = Ustrrchr(dirname, '/')))
+  *bptr = '\0';
+else
+  Ustrcpy(dirname, ".");
+
 /* It is apparently necessary to open with O_RDWR for this to work
 with gdbm-1.7.3, though no reading is actually going to be done. */
 
 /* It is apparently necessary to open with O_RDWR for this to work
 with gdbm-1.7.3, though no reading is actually going to be done. */
 
-EXIM_DBOPEN(temp_dbmname, O_RDWR|O_CREAT|O_EXCL, 0644, &d);
+EXIM_DBOPEN(temp_dbmname, dirname, O_RDWR|O_CREAT|O_EXCL, 0644, &d);
 
 if (d == NULL)
   {
 
 if (d == NULL)
   {
@@ -329,7 +337,7 @@ while (Ufgets(line, max_insize, f) != NULL)
       keystart = t;
       while (*s != 0 && *s != '\"')
         {
       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++;
         }
           else *t++ = *s;
         s++;
         }
@@ -478,9 +486,11 @@ if (yield == 0 || yield == 1)
 else
   {
   printf("dbmbuild abandoned\n");
 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);
   Uunlink(temp_dbmname);
-  #else
+#else
   if (is_db)
     {
     sprintf(CS real_dbmname, "%s.db", temp_dbmname);
   if (is_db)
     {
     sprintf(CS real_dbmname, "%s.db", temp_dbmname);
@@ -493,7 +503,7 @@ else
     sprintf(CS real_dbmname, "%s.pag", temp_dbmname);
     Uunlink(real_dbmname);
     }
     sprintf(CS real_dbmname, "%s.pag", temp_dbmname);
     Uunlink(real_dbmname);
     }
-  #endif /* USE_DB || USE_TDB */
+#endif /* USE_DB || USE_TDB */
   }
 
 return yield;
   }
 
 return yield;
index 124303a..45bad20 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -253,18 +253,25 @@ dbfn_open(uschar *name, int flags, open_db *dbblock, BOOL lof)
 int rc;
 struct flock lock_data;
 BOOL read_only = flags == O_RDONLY;
 int rc;
 struct flock lock_data;
 BOOL read_only = flags == O_RDONLY;
-uschar buffer[256];
+uschar * dirname, * filename;
 
 /* The first thing to do is to open a separate file on which to lock. This
 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. */
 
 
 /* The first thing to do is to open a separate file on which to lock. This
 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);
+#ifdef COMPILE_UTILITY
+if (  asprintf(CSS &dirname, "%s/db", spool_directory) < 0
+   || asprintf(CSS &filename, "%s/%s.lockfile", dirname, name) < 0)
+  return NULL;
+#else
+dirname = string_sprintf("%s/db", spool_directory);
+filename = string_sprintf("%s/%s.lockfile", dirname, name);
+#endif
 
 
-dbblock->lockfd = Uopen(buffer, flags, 0);
+dbblock->lockfd = Uopen(filename, flags, 0);
 if (dbblock->lockfd < 0)
   {
 if (dbblock->lockfd < 0)
   {
-  printf("** Failed to open database lock file %s: %s\n", buffer,
+  printf("** Failed to open database lock file %s: %s\n", filename,
     strerror(errno));
   return NULL;
   }
     strerror(errno));
   return NULL;
   }
@@ -272,21 +279,22 @@ if (dbblock->lockfd < 0)
 /* Now we must get a lock on the opened lock file; do this with a blocking
 lock that times out. */
 
 /* Now we must get a lock on the opened lock file; do this with a blocking
 lock that times out. */
 
-lock_data.l_type = read_only? F_RDLCK : F_WRLCK;
+lock_data.l_type = read_only ? F_RDLCK : F_WRLCK;
 lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
 
 sigalrm_seen = FALSE;
 os_non_restarting_signal(SIGALRM, sigalrm_handler);
 lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
 
 sigalrm_seen = FALSE;
 os_non_restarting_signal(SIGALRM, sigalrm_handler);
-alarm(EXIMDB_LOCK_TIMEOUT);
+ALARM(EXIMDB_LOCK_TIMEOUT);
 rc = fcntl(dbblock->lockfd, F_SETLKW, &lock_data);
 rc = fcntl(dbblock->lockfd, F_SETLKW, &lock_data);
-alarm(0);
+ALARM_CLR(0);
 
 if (sigalrm_seen) errno = ETIMEDOUT;
 if (rc < 0)
   {
   printf("** Failed to get %s lock for %s: %s",
 
 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",
+    filename,
+    errno == ETIMEDOUT ? "timed out" : strerror(errno));
   (void)close(dbblock->lockfd);
   return NULL;
   }
   (void)close(dbblock->lockfd);
   return NULL;
   }
@@ -294,12 +302,16 @@ if (rc < 0)
 /* 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. */
 
 /* 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. */
 
-sprintf(CS buffer, "%s/db/%s", spool_directory, name);
-EXIM_DBOPEN(buffer, flags, 0, &(dbblock->dbptr));
+#ifdef COMPILE_UTILITY
+if (asprintf(CSS &filename, "%s/%s", dirname, name) < 0) return NULL;
+#else
+filename = string_sprintf("%s/%s", dirname, name);
+#endif
+EXIM_DBOPEN(filename, dirname, flags, 0, &(dbblock->dbptr));
 
 
-if (dbblock->dbptr == NULL)
+if (!dbblock->dbptr)
   {
   {
-  printf("** Failed to open DBM file %s for %s:\n   %s%s\n", buffer,
+  printf("** Failed to open DBM file %s for %s:\n   %s%s\n", filename,
     read_only? "reading" : "writing", strerror(errno),
     #ifdef USE_DB
     " (or Berkeley DB error while opening)"
     read_only? "reading" : "writing", strerror(errno),
     #ifdef USE_DB
     " (or Berkeley DB error while opening)"
@@ -356,15 +368,19 @@ Returns: a pointer to the retrieved record, or
 */
 
 void *
 */
 
 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;
 {
 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_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;
 
 
 if (!EXIM_DBGET(dbblock->dbptr, key_datum, result_datum)) return NULL;
 
@@ -396,16 +412,20 @@ Returns:    the yield of the underlying dbm or db "write" function. If this
 */
 
 int
 */
 
 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;
 {
 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. */
 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);
 EXIM_DATUM_DATA(value_datum) = CS ptr;
 EXIM_DATUM_SIZE(value_datum) = length;
 return EXIM_DBPUT(dbblock->dbptr, key_datum, value_datum);
@@ -426,12 +446,16 @@ Returns: the yield of the underlying dbm or db "delete" function.
 */
 
 int
 */
 
 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 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);
 }
 
 return EXIM_DBDEL(dbblock->dbptr, key_datum);
 }
 
@@ -503,15 +527,16 @@ uschar keybuffer[1024];
 
 dbdata_type = check_args(argc, argv, US"dumpdb", US"");
 spool_directory = argv[1];
 
 dbdata_type = check_args(argc, argv, US"dumpdb", US"");
 spool_directory = argv[1];
-dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE);
-if (dbm == NULL) exit(1);
+if (!(dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE)))
+  exit(1);
 
 /* Scan the file, formatting the information for each entry. Note
 that data is returned in a malloc'ed block, in order that it be
 correctly aligned. */
 
 
 /* Scan the file, formatting the information for each entry. Note
 that data is returned in a malloc'ed block, in order that it be
 correctly aligned. */
 
-key = dbfn_scan(dbm, TRUE, &cursor);
-while (key != NULL)
+for (key = dbfn_scan(dbm, TRUE, &cursor);
+     key;
+     key = dbfn_scan(dbm, FALSE, &cursor))
   {
   dbdata_retry *retry;
   dbdata_wait *wait;
   {
   dbdata_retry *retry;
   dbdata_wait *wait;
@@ -533,9 +558,8 @@ while (key != NULL)
     return 1;
     }
   Ustrcpy(keybuffer, key);
     return 1;
     }
   Ustrcpy(keybuffer, key);
-  value = dbfn_read_with_length(dbm, keybuffer, &length);
 
 
-  if (value == NULL)
+  if (!(value = dbfn_read_with_length(dbm, keybuffer, &length)))
     fprintf(stderr, "**** Entry \"%s\" was in the key scan, but the record "
                     "was not found in the file - something is wrong!\n",
       CS keybuffer);
     fprintf(stderr, "**** Entry \"%s\" was in the key scan, but the record "
                     "was not found in the file - something is wrong!\n",
       CS keybuffer);
@@ -630,19 +654,6 @@ while (key != NULL)
         printf("\n");
         }
 
         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:
       break;
 
       case type_ratelimit:
@@ -668,7 +679,6 @@ while (key != NULL)
       }
     store_reset(value);
     }
       }
     store_reset(value);
     }
-  key = dbfn_scan(dbm, FALSE, &cursor);
   }
 
 dbfn_close(dbm);
   }
 
 dbfn_close(dbm);
@@ -775,8 +785,9 @@ for(;;)
     {
     int verify = 1;
     spool_directory = argv[1];
     {
     int verify = 1;
     spool_directory = argv[1];
-    dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE);
-    if (dbm == NULL) continue;
+
+    if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE)))
+      continue;
 
     if (Ustrcmp(field, "d") == 0)
       {
 
     if (Ustrcmp(field, "d") == 0)
       {
@@ -802,13 +813,13 @@ for(;;)
         if (record == NULL) printf("not found\n"); else
           {
           time_t tt;
         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;
 
           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)
               {
 
             switch(fieldno)
               {
@@ -858,7 +869,7 @@ for(;;)
 
             case type_callout:
             callout = (dbdata_callout_cache *)record;
 
             case type_callout:
             callout = (dbdata_callout_cache *)record;
-            length = sizeof(dbdata_callout_cache);
+            /* length = sizeof(dbdata_callout_cache); */
             switch(fieldno)
               {
               case 0:
             switch(fieldno)
               {
               case 0:
@@ -972,11 +983,10 @@ for(;;)
   /* Handle a read request, or verify after an update. */
 
   spool_directory = argv[1];
   /* Handle a read request, or verify after an update. */
 
   spool_directory = argv[1];
-  dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE);
-  if (dbm == NULL) continue;
+  if (!(dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE)))
+    continue;
 
 
-  record = dbfn_read_with_length(dbm, name, &oldlength);
-  if (record == NULL)
+  if (!(record = dbfn_read_with_length(dbm, name, &oldlength)))
     {
     printf("record %s not found\n", name);
     name[0] = 0;
     {
     printf("record %s not found\n", name);
     name[0] = 0;
@@ -1159,8 +1169,8 @@ oldest = time(NULL) - maxkeep;
 printf("Tidying Exim hints database %s/db/%s\n", argv[1], argv[2]);
 
 spool_directory = argv[1];
 printf("Tidying Exim hints database %s/db/%s\n", argv[1], argv[2]);
 
 spool_directory = argv[1];
-dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE);
-if (dbm == NULL) exit(1);
+if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE)))
+  exit(1);
 
 /* Prepare for building file names */
 
 
 /* Prepare for building file names */
 
@@ -1173,14 +1183,14 @@ to the file while scanning it. Pity the man page doesn't warn you about that.
 Therefore, we scan and build a list of all the keys. Then we use that to
 read the records and possibly update them. */
 
 Therefore, we scan and build a list of all the keys. Then we use that to
 read the records and possibly update them. */
 
-key = dbfn_scan(dbm, TRUE, &cursor);
-while (key != NULL)
+for (key = dbfn_scan(dbm, TRUE, &cursor);
+     key;
+     key = dbfn_scan(dbm, FALSE, &cursor))
   {
   key_item *k = store_get(sizeof(key_item) + Ustrlen(key));
   k->next = keychain;
   keychain = k;
   Ustrcpy(k->key, key);
   {
   key_item *k = store_get(sizeof(key_item) + Ustrlen(key));
   k->next = keychain;
   keychain = k;
   Ustrcpy(k->key, key);
-  key = dbfn_scan(dbm, FALSE, &cursor);
   }
 
 /* Now scan the collected keys and operate on the records, resetting
   }
 
 /* Now scan the collected keys and operate on the records, resetting
@@ -1188,7 +1198,7 @@ the store each time round. */
 
 reset_point = store_get(0);
 
 
 reset_point = store_get(0);
 
-while (keychain != NULL)
+while (keychain)
   {
   dbdata_generic *value;
 
   {
   dbdata_generic *value;
 
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
 Default is -fcntl -lockfile.
 
 Argument: the name of the lock file
+
+Copyright (c) The Exim Maintainers 2016
 */
 
 #include "os.h"
 */
 
 #include "os.h"
@@ -36,7 +38,7 @@ in sys/file.h. */
 #endif
 
 
 #endif
 
 
-typedef int BOOL;
+typedef unsigned BOOL;
 #define FALSE 0
 #define TRUE  1
 
 #define FALSE 0
 #define TRUE  1
 
@@ -216,7 +218,7 @@ for (i = 1; i < argc; i++)
   else usage();
   }
 
   else usage();
   }
 
-if (quiet) verbose = 0;
+if (quiet) verbose = FALSE;
 
 /* Can't use flock() if the OS doesn't provide it */
 
 
 /* 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)
     {
 
   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)
     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. */
 
 
     /* 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);
 
     (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;
       {
       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. */
 
 
   /* 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));
     {
     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))
   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)
       {
     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;
       }
         }
       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
 
   /* 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;
 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;
   struct utimbuf ut;
+
   stat(filename, &strestore);
   i = system(command);
   ut.actime = strestore.st_atime;
   ut.modtime = strestore.st_mtime;
   utime(filename, &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);
 
   }
 else i = system(command);
 
index fac2420..6293a7c 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.
 
 # 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
 # See the file NOTICE for conditions of use and distribution.
 
 # Except when they appear in comments, the following placeholders in this
 # X11_LD_LIBRARY
 
 # PROCESSED_FLAG
 # X11_LD_LIBRARY
 
 # PROCESSED_FLAG
+#
+if test "x$1" = x--version
+then
+    echo "`basename $0`: $0"
+    echo "build: EXIM_RELEASE_VERSIONEXIM_VARIANT_VERSION"
+    exit 0
+fi
 
 # See if caller wants to invoke gdb
 
 
 # See if caller wants to invoke gdb
 
@@ -79,11 +86,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
 
 # 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"
 
 if [ "$EXIMON_LOG_FILE_PATH" != "" ] ; then
   LOG_FILE_NAME="$EXIMON_LOG_FILE_PATH"
@@ -94,6 +102,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 \*\*\*
   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 *: */:/' \
 else
   LOG_FILE_NAME=`echo $LOG_FILE_PATH | \
     sed -e 's/ *: *syslog *: */:/' \
@@ -101,7 +111,17 @@ else
         -e 's/^ *syslog *: *//' \
         -e 's/%s/main/'`
   if [ "$LOG_FILE_NAME" = "" ] ; then
         -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
 
   fi
 fi
 
index 4370b4e..5e1a084 100644 (file)
@@ -1,6 +1,6 @@
-#!PERL_COMMAND -w
+#!PERL_COMMAND
 
 
-# Copyright (c) 2001-2014 University of Cambridge.
+# Copyright (c) 2001-2017 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.
 # 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
 # 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)'
 #
 #             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.
 #             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.
 #
 # 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
 #             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
 #             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
 #             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.
 #             Allow any combination of xls, txt, and html output.
 #             Fixed display of large numbers with -nvr option
 #             Fixed merging of reports with empty tables.
@@ -533,7 +533,7 @@ about how to create charts from the tables.
 
 =head1 AUTHOR
 
 
 =head1 AUTHOR
 
-There is a web site at http://www.exim.org - this contains details of the
+There is a website at https://www.exim.org - this contains details of the
 mailing list exim-users@exim.org.
 
 =head1 TO DO
 mailing list exim-users@exim.org.
 
 =head1 TO DO
@@ -547,13 +547,23 @@ Merging of xls files is not (yet) possible. Be free to implement :)
 
 =cut
 
 
 =cut
 
+use warnings;
 use integer;
 use integer;
+BEGIN { pop @INC if $INC[-1] eq '.' };
 use strict;
 use IO::File;
 use strict;
 use IO::File;
+use File::Basename;
 
 # use Time::Local;  # PH/FANF
 use POSIX;
 
 
 # use Time::Local;  # PH/FANF
 use POSIX;
 
+if (@ARGV and $ARGV[0] eq '--version') {
+    print basename($0) . ": $0\n",
+        "build: EXIM_RELEASE_VERSIONEXIM_VARIANT_VERSION\n",
+        "perl(runtime): $]\n";
+        exit 0;
+}
+
 use vars qw($HAVE_GD_Graph_pie $HAVE_GD_Graph_linespoints $HAVE_Spreadsheet_WriteExcel);
 eval { require GD::Graph::pie; };
 $HAVE_GD_Graph_pie = $@ ? 0 : 1;
 use vars qw($HAVE_GD_Graph_pie $HAVE_GD_Graph_linespoints $HAVE_Spreadsheet_WriteExcel);
 eval { require GD::Graph::pie; };
 $HAVE_GD_Graph_pie = $@ ? 0 : 1;
@@ -598,9 +608,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($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?
 use vars qw($localtime_offset);
 use vars qw($i);                                #General loop counter.
 use vars qw($debug);                            #Debug mode?
@@ -614,7 +624,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);
 
 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);
 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 +767,8 @@ sub volume_rounded {
   }
   else {
     # We don't want any rounding to be done.
   }
   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);
     # 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 +881,10 @@ $p;
 # Eg 3h20m5s => 12005
 #######################################################################
 sub unformat_time {
 # Eg 3h20m5s => 12005
 #######################################################################
 sub unformat_time {
-  my($formated_time) = pop @_;
+  my($formatted_time) = pop @_;
   my $time = 0;
 
   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');
     $time +=  $1 if ($2 eq '' || $2 eq 's');
     $time +=  $1 * 60 if ($2 eq 'm');
     $time +=  $1 * 60 * 60 if ($2 eq 'h');
@@ -894,6 +904,7 @@ sub unformat_time {
 # POSIX::mktime.  We expect the timestamp to be of the form
 # "$year-$mon-$day $hour:$min:$sec", with month going from 1 to 12,
 # and the year to be absolute (we do the necessary conversions). The
 # POSIX::mktime.  We expect the timestamp to be of the form
 # "$year-$mon-$day $hour:$min:$sec", with month going from 1 to 12,
 # and the year to be absolute (we do the necessary conversions). The
+# seconds value can be followed by decimals, which we ignore. The
 # timestamp may be followed with an offset from UTC like "+$hh$mm"; if the
 # offset is not present, and we have not been told that the log is in UTC
 # (with the -utc option), then we adjust the time by the current local
 # timestamp may be followed with an offset from UTC like "+$hh$mm"; if the
 # offset is not present, and we have not been told that the log is in UTC
 # (with the -utc option), then we adjust the time by the current local
@@ -917,7 +928,7 @@ sub seconds {
   # Is the timestamp the same as the last one?
   return $last_time if ($last_timestamp eq $timestamp);
 
   # Is the timestamp the same as the last one?
   return $last_time if ($last_timestamp eq $timestamp);
 
-  return 0 unless ($timestamp =~ /^((\d{4})\-(\d\d)-(\d\d))\s(\d\d):(\d\d):(\d\d)( ([+-])(\d\d)(\d\d))?/o);
+  return 0 unless ($timestamp =~ /^((\d{4})\-(\d\d)-(\d\d))\s(\d\d):(\d\d):(\d\d)(?:\.\d+)?( ([+-])(\d\d)(\d\d))?/o);
 
   unless ($last_date eq $1) {
     $last_date = $1;
 
   unless ($last_date eq $1) {
     $last_date = $1;
@@ -928,8 +939,8 @@ sub seconds {
   }
   my $time = $date_seconds + ($5 * 3600) + ($6 * 60) + $7;
 
   }
   my $time = $date_seconds + ($5 * 3600) + ($6 * 60) + $7;
 
-  # SC. Use cacheing. Also note we want seconds not minutes.
-  #my($this_offset) = ($10 * 60 + $11) * ($9 . "1") if defined $8;
+  # SC. Use caching. Also note we want seconds not minutes.
+  #my($this_offset) = ($10 * 60 + $12) * ($9 . "1") if defined $8;
   if (defined $8 && ($8 ne $last_offset)) {
     $last_offset = $8;
     $offset_seconds = ($10 * 60 + $11) * 60;
   if (defined $8 && ($8 ne $last_offset)) {
     $last_offset = $8;
     $offset_seconds = ($10 * 60 + $11) * 60;
@@ -937,7 +948,7 @@ sub seconds {
   }
 
 
   }
 
 
-  if (defined $7) {
+  if (defined $8) {
     #$time -= $this_offset;
     $time -= $offset_seconds;
   } elsif (defined $localtime_offset) {
     #$time -= $this_offset;
     $time -= $offset_seconds;
   } elsif (defined $localtime_offset) {
@@ -1650,7 +1661,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 &
 
     # 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};
     # 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};
@@ -1851,12 +1862,23 @@ sub generate_parser {
 
     $length = length($_);
     next if ($length < 38);
 
     $length = length($_);
     next if ($length < 38);
-    next unless /^(\\d{4}\\-\\d\\d-\\d\\d\\s(\\d\\d):(\\d\\d):\\d\\d( [-+]\\d\\d\\d\\d)?)( \\[\\d+\\])?/o;
-
-    ($tod,$m_hour,$m_min) = ($1,$2,$3);
+    next unless /^
+               (\\d{4}\\-\\d\\d-\\d\\d\\s      # 1: YYYYMMDD HHMMSS
+                       (\\d\\d)                # 2: HH
+                       :
+                       (\\d\\d)                # 3: MM
+                       :\\d\\d
+               )
+               (\\.\\d+)?                      # 4: subseconds
+               (\s[-+]\\d\\d\\d\\d)?           # 5: tz-offset
+               (\s\\[\\d+\\])?                 # 6: pid
+               /ox;
+
+    $tod = defined($5) ?  $1 . $5 : $1;
+    ($m_hour,$m_min) = ($2,$3);
 
     # PH - watch for GMT offsets in the timestamp.
 
     # PH - watch for GMT offsets in the timestamp.
-    if (defined($4)) {
+    if (defined($5)) {
       $extra = 6;
       next if ($length < 44);
     }
       $extra = 6;
       next if ($length < 44);
     }
@@ -1864,9 +1886,15 @@ sub generate_parser {
       $extra = 0;
     }
 
       $extra = 0;
     }
 
+    # watch for subsecond precision
+    if (defined($4)) {
+      $extra += length($4);
+      next if ($length < 38 + $extra);
+    }
+
     # PH - watch for PID added after the timestamp.
     # PH - watch for PID added after the timestamp.
-    if (defined($5)) {
-      $extra += length($5);
+    if (defined($6)) {
+      $extra += length($6);
       next if ($length < 38 + $extra);
     }
 
       next if ($length < 38 + $extra);
     }
 
@@ -2729,7 +2757,7 @@ sub print_grandtotals {
       if ($messages > 0) {
         @content = ($total_aref->[0], '', $messages, '');
 
       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)
         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 +3388,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 $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');
           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 +3704,7 @@ sub update_relayed {
 #
 #  add_to_totals(\%totals,\@keys,$values);
 #
 #
 #  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
 # as the hash keys.
 #
 # If the value contains a '%', then the value is set rather than added. Otherwise, we
@@ -3706,7 +3734,7 @@ sub add_to_totals {
 #
 #  line_to_hash(\%hash,\@keys,$line);
 #
 #
 #  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 {
 # using @keys as the hash keys.
 #######################################################################
 sub line_to_hash {
@@ -3772,7 +3800,7 @@ sub html2txt {
 # until we've got all of the argument.
 #
 # This isn't perfect as all white space gets reduced to one space,
 # 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.
 #######################################################################
 # be preserved precisely, then you get that by not using shell
 # variables.
 #######################################################################
@@ -3814,7 +3842,7 @@ sub set_worksheet_line {
 #######################################################################
 # @rcpt_times = parse_time_list($string);
 #
 #######################################################################
 # @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.
 # the user and fill an array.
 #
 # Return a default list if $string is undefined.
index 182e395..9138018 100644 (file)
@@ -25,6 +25,13 @@ config=
 eximmacdef=
 exim_path=
 
 eximmacdef=
 exim_path=
 
+if test "x$1" = x--version
+then
+    echo "`basename $0`: $0"
+    echo "build: EXIM_RELEASE_VERSIONEXIM_VARIANT_VERSION"
+    exit 0
+fi
+
 if expr -- $1 : '\-' >/dev/null ; then
   while expr -- $1 : '\-' >/dev/null ; do
     if [ "$1" = "-C" ]; then
 if expr -- $1 : '\-' >/dev/null ; then
   while expr -- $1 : '\-' >/dev/null ; do
     if [ "$1" = "-C" ]; then
@@ -115,6 +122,9 @@ fi
 
 perl - $exim_path "$eximmacdef" $argone $spool_directory $qualify_domain $config <<'End'
 
 
 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];
   # Name the arguments
 
   $exim = $ARGV[0];
index 4708ebb..7959d75 100644 (file)
@@ -1,7 +1,12 @@
 #!PERL_COMMAND
 #!PERL_COMMAND
+# Copyright (c) 1995 - 2018 University of Cambridge.
+# See the file NOTICE for conditions of use and distribution.
+
+
+# This variables should be set by the building process
+my $spool = 'SPOOL_DIRECTORY'; # may be overridden later
+my $exim  = 'BIN_DIRECTORY/exim';
 
 
-# This variable should be set by the building process to Exim's spool directory.
-my $spool = 'SPOOL_DIRECTORY';
 # Need to set this dynamically during build, but it's not used right now anyway.
 my $charset = 'ISO-8859-1';
 
 # Need to set this dynamically during build, but it's not used right now anyway.
 my $charset = 'ISO-8859-1';
 
@@ -10,7 +15,9 @@ my $charset = 'ISO-8859-1';
 #       http://www.exim.org/eximwiki/ToolExipickManPage
 
 use strict;
 #       http://www.exim.org/eximwiki/ToolExipickManPage
 
 use strict;
+BEGIN { pop @INC if $INC[-1] eq '.' };
 use Getopt::Long;
 use Getopt::Long;
+use File::Basename;
 
 my($p_name)   = $0 =~ m|/?([^/]+)$|;
 my $p_version = "20100323.0";
 
 my($p_name)   = $0 =~ m|/?([^/]+)$|;
 my $p_version = "20100323.0";
@@ -39,6 +46,7 @@ $| = 1; # unbuffer STDOUT
 Getopt::Long::Configure("bundling_override");
 GetOptions(
   'spool=s'     => \$G::spool,      # exim spool dir
 Getopt::Long::Configure("bundling_override");
 GetOptions(
   'spool=s'     => \$G::spool,      # exim spool dir
+  'C|Config=s'  => \$G::config,     # use alternative Exim configuration file
   'input-dir=s' => \$G::input_dir,  # name of the "input" dir
   'finput'      => \$G::finput,     # same as "--input-dir Finput"
   'bp'          => \$G::mailq_bp,   # List the queue (noop - default)
   'input-dir=s' => \$G::input_dir,  # name of the "input" dir
   'finput'      => \$G::finput,     # same as "--input-dir Finput"
   'bp'          => \$G::mailq_bp,   # List the queue (noop - default)
@@ -76,10 +84,16 @@ GetOptions(
   'show-vars=s' => \$G::show_vars,  # display the contents of these vars
   'just-vars'   => \$G::just_vars,  # only display vars, no other info
   'show-rules'  => \$G::show_rules, # display compiled match rules
   'show-vars=s' => \$G::show_vars,  # display the contents of these vars
   'just-vars'   => \$G::just_vars,  # only display vars, no other info
   'show-rules'  => \$G::show_rules, # display compiled match rules
-  'show-tests'  => \$G::show_tests  # display tests as applied to each message
+  'show-tests'  => \$G::show_tests, # display tests as applied to each message
+  'version'     => sub {
+        print basename($0) . ": $0\n",
+            "build: EXIM_RELEASE_VERSIONEXIM_VARIANT_VERSION\n",
+            "perl(runtime): $]\n";
+            exit 0;
+  },
 ) || exit(1);
 
 ) || 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);
 $G::freeze = undef               if ($G::freeze && $G::thaw);
 freeze_start()                   if ($G::freeze);
 thaw_start()                     if ($G::thaw);
@@ -111,7 +125,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
 $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 @{[defined $G::config ? "-C $G::config" : '']} -n -bP spool_directory`)
+                             and $_ or $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 ||
 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 ||
@@ -381,7 +397,7 @@ sub process_criteria {
     } else {
       $c[-1]{cmp} .= $G::negate ? " ? 0 : 1" : " ? 1 : 0";
     }
     } else {
       $c[-1]{cmp} .= $G::negate ? " ? 0 : 1" : " ? 1 : 0";
     }
-    # support the each_* psuedo variables.  Steal the criteria off of the
+    # support the each_* pseudo variables.  Steal the criteria off of the
     # queue for special processing later
     if ($c[-1]{var} =~ /^each_(recipients(_(un)?del)?)$/) {
       my $var = $1;
     # queue for special processing later
     if ($c[-1]{var} =~ /^each_(recipients(_(un)?del)?)$/) {
       my $var = $1;
@@ -757,7 +773,7 @@ sub _decode_2047 {
               $i += 2;
             }
           }
               $i += 2;
             }
           }
-          elsif ($ow[$i] =~ /\s/) { # whitspace is illegal
+          elsif ($ow[$i] =~ /\s/) { # whitespace is illegal
             $e = 1;
             last;
           }
             $e = 1;
             last;
           }
@@ -1349,6 +1365,11 @@ Same as '-bpu --unsorted' (exim)
 
 Same as -bp, but only show undelivered messages (exim)
 
 
 Same as -bp, but only show undelivered messages (exim)
 
+=item -C | --config <config>
+
+Use <config> to determine the proper spool directory. (See C<--spool>
+or C<--input> for alternative ways to specify the directories to operate on.)
+
 =item -c
 
 Show a count of matching messages (exiqgrep)
 =item -c
 
 Show a count of matching messages (exiqgrep)
@@ -1387,7 +1408,7 @@ Display only the message IDs (exiqgrep)
 
 =item --input-dir <inputname>
 
 
 =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
 
 
 =item -l
 
@@ -1427,7 +1448,7 @@ Same as '$shown_message_size eq <string>' (exiqgrep)
 
 =item --spool <path>
 
 
 =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 [-C config] -n -bP spool_directory>, and if this call fails, the  F</opt/exim/spool> from build time (F<Local/Makefile>) is used. See also --config.
 
 =item --show-rules
 
 
 =item --show-rules
 
@@ -1583,7 +1604,7 @@ TRUE if, under normal circumstances, Exim will not try to deliver the message.
 
 =item S + $each_recipients
 
 
 =item S + $each_recipients
 
-This is a psuedo variable which allows you to apply a test against each address in $recipients individually.  Whereas '$recipients =~ /@aol.com/' will match if any recipient address contains aol.com, '$each_recipients =~ /@aol.com$/' will only be true if every recipient matches that pattern.  Note that this obeys --and or --or being set.  Using it with --or is very similar to just matching against $recipients, but with the added benefit of being able to use anchors at the beginning and end of each recipient address.
+This is a pseudo variable which allows you to apply a test against each address in $recipients individually.  Whereas '$recipients =~ /@aol.com/' will match if any recipient address contains aol.com, '$each_recipients =~ /@aol.com$/' will only be true if every recipient matches that pattern.  Note that this obeys --and or --or being set.  Using it with --or is very similar to just matching against $recipients, but with the added benefit of being able to use anchors at the beginning and end of each recipient address.
 
 =item S + $each_recipients_del
 
 
 =item S + $each_recipients_del
 
index afecbff..c4f7c4b 100644 (file)
 # Version 1.2
 
 use strict;
 # Version 1.2
 
 use strict;
+BEGIN { pop @INC if $INC[-1] eq '.' };
+
 use Getopt::Std;
 use Getopt::Std;
+use File::Basename;
 
 # Have this variable point to your exim binary.
 my $exim = 'BIN_DIRECTORY/exim';
 
 # Have this variable point to your exim binary.
 my $exim = 'BIN_DIRECTORY/exim';
@@ -43,7 +46,15 @@ if ($^O eq 'darwin') { # aka MacOS X
   $base = 62;
 };
 
   $base = 62;
 };
 
+if ($ARGV[0] eq '--version') {
+    print basename($0) . ": $0\n",
+        "build: EXIM_RELEASE_VERSIONEXIM_VARIANT_VERSION\n",
+        "perl(runtime): $]\n";
+        exit 0;
+}
+
 getopts('hf:r:y:o:s:C:zxlibRca',\%opt);
 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}; }
 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 +97,7 @@ EOF
 }
 
 sub collect() {
 }
 
 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 = $_;
        while(<QUEUE>) {
                chomp();
                my $line = $_;
index fc5ad26..67772f5 100644 (file)
@@ -1,4 +1,4 @@
-#! PERL_COMMAND -w
+#! PERL_COMMAND
 
 # Mail Queue Summary
 # Christoph Lameter, 21 May 1997
 
 # 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
 #   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
 #
 #   Changed regexps to compile once to very minor speed optimization
 #   Short circuit for empty lines
 #
 
 # Slightly modified sub from eximstats
 
 
 # Slightly modified sub from eximstats
 
+use warnings;
+BEGIN { pop @INC if $INC[-1] eq '.' };
+use File::Basename;
+
+if (@ARGV && $ARGV[0] eq '--version') {
+    print basename($0) . ": $0\n",
+        "build: EXIM_RELEASE_VERSIONEXIM_VARIANT_VERSION\n",
+        "perl(runtime): $]\n";
+        exit 0;
+}
+
 sub print_volume_rounded {
 my($x) = pop @_;
 if ($x < 10000)
 sub print_volume_rounded {
 my($x) = pop @_;
 if ($x < 10000)
index 2542b01..a1f748e 100644 (file)
@@ -15,6 +15,7 @@
 # EXIWHAT_EGREP_ARG
 # EXIWHAT_MULTIKILL_CMD
 # EXIWHAT_MULTIKILL_ARG
 # EXIWHAT_EGREP_ARG
 # EXIWHAT_MULTIKILL_CMD
 # EXIWHAT_MULTIKILL_ARG
+# RM_COMMAND
 
 # PROCESSED_FLAG
 
 
 # PROCESSED_FLAG
 
@@ -29,6 +30,8 @@
 # the script in the next Exim rebuild/install. However, it's best to
 # arrange your build-time configuration file to get the correct values.
 
 # the script in the next Exim rebuild/install. However, it's best to
 # arrange your build-time configuration file to get the correct values.
 
+rm=RM_COMMAND
+
 # Some operating systems have a command that finds processes that match
 # certain conditions (by default usually those running specific commands)
 # and sends them signals. If such a command is defined for your OS, the
 # Some operating systems have a command that finds processes that match
 # certain conditions (by default usually those running specific commands)
 # and sends them signals. If such a command is defined for your OS, the
@@ -52,6 +55,13 @@ signal=EXIWHAT_KILL_SIGNAL
 # See if this installation is using the esoteric "USE_NODE" feature of Exim,
 # in which it uses the host's name as a suffix for the configuration file name.
 
 # See if this installation is using the esoteric "USE_NODE" feature of Exim,
 # in which it uses the host's name as a suffix for the configuration file name.
 
+if test "x$1" = x--version
+then
+    echo "`basename $0`: $0"
+    echo "build: EXIM_RELEASE_VERSIONEXIM_VARIANT_VERSION"
+    exit 0
+fi
+
 if [ "CONFIGURE_FILE_USE_NODE" = "yes" ]; then
   hostsuffix=.`uname -n`
 fi
 if [ "CONFIGURE_FILE_USE_NODE" = "yes" ]; then
   hostsuffix=.`uname -n`
 fi
@@ -103,7 +113,7 @@ fi
 
 # Now do the job.
 
 
 # Now do the job.
 
-/bin/rm -f ${log}
+$rm -f ${log}
 if [ -f ${log} ]; then
   echo "** Failed to remove ${log}"
   exit 1
 if [ -f ${log} ]; then
   echo "** Failed to remove ${log}"
   exit 1
index 70d7c7d..b2a9217 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
 /* See the file NOTICE for conditions of use and distribution. */
 
 
 
 /* Recursively called function */
 
 
 /* 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
 
 #ifdef STAND_ALONE
-#ifndef SUPPORT_CRYPTEQ
-#define SUPPORT_CRYPTEQ
-#endif
+# ifndef SUPPORT_CRYPTEQ
+#  define SUPPORT_CRYPTEQ
+# endif
 #endif
 
 #ifdef LOOKUP_LDAP
 #endif
 
 #ifdef LOOKUP_LDAP
-#include "lookups/ldap.h"
+# include "lookups/ldap.h"
 #endif
 
 #ifdef SUPPORT_CRYPTEQ
 #endif
 
 #ifdef SUPPORT_CRYPTEQ
-#ifdef CRYPT_H
-#include <crypt.h>
-#endif
-#ifndef HAVE_CRYPT16
+# ifdef CRYPT_H
+#  include <crypt.h>
+# endif
+# ifndef HAVE_CRYPT16
 extern char* crypt16(char*, char*);
 extern char* crypt16(char*, char*);
-#endif
+# endif
 #endif
 
 /* The handling of crypt16() is a mess. I will record below the analysis of the
 #endif
 
 /* The handling of crypt16() is a mess. I will record below the analysis of the
@@ -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
 (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:
 
 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            *
 *************************************************/
 /*************************************************
 *            Local statics and tables            *
 *************************************************/
@@ -107,13 +103,18 @@ alphabetical order. */
 
 static uschar *item_table[] = {
   US"acl",
 
 static uschar *item_table[] = {
   US"acl",
+  US"authresults",
   US"certextract",
   US"dlfunc",
   US"certextract",
   US"dlfunc",
+  US"env",
   US"extract",
   US"filter",
   US"hash",
   US"hmac",
   US"if",
   US"extract",
   US"filter",
   US"hash",
   US"hmac",
   US"if",
+#ifdef SUPPORT_I18N
+  US"imapfolder",
+#endif
   US"length",
   US"listextract",
   US"lookup",
   US"length",
   US"listextract",
   US"lookup",
@@ -127,18 +128,24 @@ static uschar *item_table[] = {
   US"reduce",
   US"run",
   US"sg",
   US"reduce",
   US"run",
   US"sg",
+  US"sort",
   US"substr",
   US"tr" };
 
 enum {
   EITEM_ACL,
   US"substr",
   US"tr" };
 
 enum {
   EITEM_ACL,
+  EITEM_AUTHRESULTS,
   EITEM_CERTEXTRACT,
   EITEM_DLFUNC,
   EITEM_CERTEXTRACT,
   EITEM_DLFUNC,
+  EITEM_ENV,
   EITEM_EXTRACT,
   EITEM_FILTER,
   EITEM_HASH,
   EITEM_HMAC,
   EITEM_IF,
   EITEM_EXTRACT,
   EITEM_FILTER,
   EITEM_HASH,
   EITEM_HMAC,
   EITEM_IF,
+#ifdef SUPPORT_I18N
+  EITEM_IMAPFOLDER,
+#endif
   EITEM_LENGTH,
   EITEM_LISTEXTRACT,
   EITEM_LOOKUP,
   EITEM_LENGTH,
   EITEM_LISTEXTRACT,
   EITEM_LOOKUP,
@@ -152,6 +159,7 @@ enum {
   EITEM_REDUCE,
   EITEM_RUN,
   EITEM_SG,
   EITEM_REDUCE,
   EITEM_RUN,
   EITEM_SG,
+  EITEM_SORT,
   EITEM_SUBSTR,
   EITEM_TR };
 
   EITEM_SUBSTR,
   EITEM_TR };
 
@@ -166,7 +174,14 @@ static uschar *op_table_underscore[] = {
   US"quote_local_part",
   US"reverse_ip",
   US"time_eval",
   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,
 
 enum {
   EOP_FROM_UTF8,
@@ -174,15 +189,27 @@ enum {
   EOP_QUOTE_LOCAL_PART,
   EOP_REVERSE_IP,
   EOP_TIME_EVAL,
   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",
 
 static uschar *op_table_main[] = {
   US"address",
   US"addresses",
+  US"base32",
+  US"base32d",
   US"base62",
   US"base62d",
   US"base62",
   US"base62d",
+  US"base64",
+  US"base64d",
   US"domain",
   US"escape",
   US"domain",
   US"escape",
+  US"escape8bit",
   US"eval",
   US"eval10",
   US"expand",
   US"eval",
   US"eval10",
   US"expand",
@@ -190,6 +217,8 @@ static uschar *op_table_main[] = {
   US"hash",
   US"hex2b64",
   US"hexquote",
   US"hash",
   US"hex2b64",
   US"hexquote",
+  US"ipv6denorm",
+  US"ipv6norm",
   US"l",
   US"lc",
   US"length",
   US"l",
   US"lc",
   US"length",
@@ -207,6 +236,7 @@ static uschar *op_table_main[] = {
   US"s",
   US"sha1",
   US"sha256",
   US"s",
   US"sha1",
   US"sha256",
+  US"sha3",
   US"stat",
   US"str2b64",
   US"strlen",
   US"stat",
   US"str2b64",
   US"strlen",
@@ -215,12 +245,17 @@ static uschar *op_table_main[] = {
   US"utf8clean" };
 
 enum {
   US"utf8clean" };
 
 enum {
-  EOP_ADDRESS =  sizeof(op_table_underscore)/sizeof(uschar *),
+  EOP_ADDRESS =  nelem(op_table_underscore),
   EOP_ADDRESSES,
   EOP_ADDRESSES,
+  EOP_BASE32,
+  EOP_BASE32D,
   EOP_BASE62,
   EOP_BASE62D,
   EOP_BASE62,
   EOP_BASE62D,
+  EOP_BASE64,
+  EOP_BASE64D,
   EOP_DOMAIN,
   EOP_ESCAPE,
   EOP_DOMAIN,
   EOP_ESCAPE,
+  EOP_ESCAPE8BIT,
   EOP_EVAL,
   EOP_EVAL10,
   EOP_EXPAND,
   EOP_EVAL,
   EOP_EVAL10,
   EOP_EXPAND,
@@ -228,6 +263,8 @@ enum {
   EOP_HASH,
   EOP_HEX2B64,
   EOP_HEXQUOTE,
   EOP_HASH,
   EOP_HEX2B64,
   EOP_HEXQUOTE,
+  EOP_IPV6DENORM,
+  EOP_IPV6NORM,
   EOP_L,
   EOP_LC,
   EOP_LENGTH,
   EOP_L,
   EOP_LC,
   EOP_LENGTH,
@@ -245,6 +282,7 @@ enum {
   EOP_S,
   EOP_SHA1,
   EOP_SHA256,
   EOP_S,
   EOP_SHA1,
   EOP_SHA256,
+  EOP_SHA3,
   EOP_STAT,
   EOP_STR2B64,
   EOP_STRLEN,
   EOP_STAT,
   EOP_STR2B64,
   EOP_STRLEN,
@@ -423,6 +461,12 @@ static var_entry var_table[] = {
   { "address_data",        vtype_stringptr,   &deliver_address_data },
   { "address_file",        vtype_stringptr,   &address_file },
   { "address_pipe",        vtype_stringptr,   &address_pipe },
   { "address_data",        vtype_stringptr,   &deliver_address_data },
   { "address_file",        vtype_stringptr,   &address_file },
   { "address_pipe",        vtype_stringptr,   &address_pipe },
+#ifdef EXPERIMENTAL_ARC
+  { "arc_domains",         vtype_string_func, &fn_arc_domains },
+  { "arc_oldest_pass",     vtype_int,         &arc_oldest_pass },
+  { "arc_state",           vtype_stringptr,   &arc_state },
+  { "arc_state_reason",    vtype_stringptr,   &arc_state_reason },
+#endif
   { "authenticated_fail_id",vtype_stringptr,  &authenticated_fail_id },
   { "authenticated_id",    vtype_stringptr,   &authenticated_id },
   { "authenticated_sender",vtype_stringptr,   &authenticated_sender },
   { "authenticated_fail_id",vtype_stringptr,  &authenticated_fail_id },
   { "authenticated_id",    vtype_stringptr,   &authenticated_id },
   { "authenticated_sender",vtype_stringptr,   &authenticated_sender },
@@ -442,17 +486,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 },
   { "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 },
   { "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
   { "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 },
 #ifndef DISABLE_DKIM
   { "dkim_algo",           vtype_dkim,        (void *)DKIM_ALGO },
   { "dkim_bodylength",     vtype_dkim,        (void *)DKIM_BODYLENGTH },
@@ -466,17 +509,17 @@ 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_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 },
   { "dkim_key_testing",    vtype_dkim,        (void *)DKIM_KEY_TESTING },
   { "dkim_selector",       vtype_stringptr,   &dkim_signing_selector },
   { "dkim_signers",        vtype_stringptr,   &dkim_signers },
   { "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 },
   { "dkim_key_testing",    vtype_dkim,        (void *)DKIM_KEY_TESTING },
   { "dkim_selector",       vtype_stringptr,   &dkim_signing_selector },
   { "dkim_signers",        vtype_stringptr,   &dkim_signers },
-  { "dkim_verify_reason",  vtype_dkim,        (void *)DKIM_VERIFY_REASON },
-  { "dkim_verify_status",  vtype_dkim,        (void *)DKIM_VERIFY_STATUS},
+  { "dkim_verify_reason",  vtype_stringptr,   &dkim_verify_reason },
+  { "dkim_verify_status",  vtype_stringptr,   &dkim_verify_status },
 #endif
 #ifdef EXPERIMENTAL_DMARC
 #endif
 #ifdef EXPERIMENTAL_DMARC
-  { "dmarc_ar_header",     vtype_stringptr,   &dmarc_ar_header },
   { "dmarc_domain_policy", vtype_stringptr,   &dmarc_domain_policy },
   { "dmarc_status",        vtype_stringptr,   &dmarc_status },
   { "dmarc_status_text",   vtype_stringptr,   &dmarc_status_text },
   { "dmarc_domain_policy", vtype_stringptr,   &dmarc_domain_policy },
   { "dmarc_status",        vtype_stringptr,   &dmarc_status },
   { "dmarc_status_text",   vtype_stringptr,   &dmarc_status_text },
@@ -488,12 +531,18 @@ static var_entry var_table[] = {
   { "dnslist_value",       vtype_stringptr,   &dnslist_value },
   { "domain",              vtype_stringptr,   &deliver_domain },
   { "domain_data",         vtype_stringptr,   &deliver_domain_data },
   { "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 },
   { "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 },
   { "headers_added",       vtype_string_func, &fn_hdrs_added },
   { "home",                vtype_stringptr,   &deliver_home },
   { "host",                vtype_stringptr,   &deliver_host },
@@ -501,6 +550,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_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 },
   { "inode",               vtype_ino,         &deliver_inode },
   { "interface_address",   vtype_stringptr,   &interface_address },
   { "interface_port",      vtype_int,         &interface_port },
@@ -513,7 +564,9 @@ static var_entry var_table[] = {
   { "local_part_data",     vtype_stringptr,   &deliver_localpart_data },
   { "local_part_prefix",   vtype_stringptr,   &deliver_localpart_prefix },
   { "local_part_suffix",   vtype_stringptr,   &deliver_localpart_suffix },
   { "local_part_data",     vtype_stringptr,   &deliver_localpart_data },
   { "local_part_prefix",   vtype_stringptr,   &deliver_localpart_prefix },
   { "local_part_suffix",   vtype_stringptr,   &deliver_localpart_suffix },
+#ifdef HAVE_LOCAL_SCAN
   { "local_scan_data",     vtype_stringptr,   &local_scan_data },
   { "local_scan_data",     vtype_stringptr,   &local_scan_data },
+#endif
   { "local_user_gid",      vtype_gid,         &local_user_gid },
   { "local_user_uid",      vtype_uid,         &local_user_uid },
   { "localhost_number",    vtype_int,         &host_number },
   { "local_user_gid",      vtype_gid,         &local_user_gid },
   { "local_user_uid",      vtype_uid,         &local_user_uid },
   { "localhost_number",    vtype_int,         &host_number },
@@ -535,6 +588,9 @@ static var_entry var_table[] = {
   { "message_id",          vtype_stringptr,   &message_id },
   { "message_linecount",   vtype_int,         &message_linecount },
   { "message_size",        vtype_int,         &message_size },
   { "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 },
 #ifdef WITH_CONTENT_SCAN
   { "mime_anomaly_level",  vtype_int,         &mime_anomaly_level },
   { "mime_anomaly_text",   vtype_stringptr,   &mime_anomaly_text },
@@ -570,19 +626,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 },
   { "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 },
   { "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_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 },
 #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 },
   { "rcpt_count",          vtype_int,         &rcpt_count },
   { "rcpt_defer_count",    vtype_int,         &rcpt_defer_count },
   { "rcpt_fail_count",     vtype_int,         &rcpt_fail_count },
@@ -591,7 +651,7 @@ static var_entry var_table[] = {
   { "received_ip_address", vtype_stringptr,   &interface_address },
   { "received_port",       vtype_int,         &interface_port },
   { "received_protocol",   vtype_stringptr,   &received_protocol },
   { "received_ip_address", vtype_stringptr,   &interface_address },
   { "received_port",       vtype_int,         &interface_port },
   { "received_protocol",   vtype_stringptr,   &received_protocol },
-  { "received_time",       vtype_int,         &received_time },
+  { "received_time",       vtype_int,         &received_time.tv_sec },
   { "recipient_data",      vtype_stringptr,   &recipient_data },
   { "recipient_verify_failure",vtype_stringptr,&recipient_verify_failure },
   { "recipients",          vtype_string_func, &fn_recipients },
   { "recipient_data",      vtype_stringptr,   &recipient_data },
   { "recipient_verify_failure",vtype_stringptr,&recipient_verify_failure },
   { "recipients",          vtype_string_func, &fn_recipients },
@@ -600,6 +660,9 @@ static var_entry var_table[] = {
   { "regex_match_string",  vtype_stringptr,   &regex_match_string },
 #endif
   { "reply_address",       vtype_reply,       NULL },
   { "regex_match_string",  vtype_stringptr,   &regex_match_string },
 #endif
   { "reply_address",       vtype_reply,       NULL },
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+  { "requiretls",          vtype_bool,        &tls_requiretls },
+#endif
   { "return_path",         vtype_stringptr,   &return_path },
   { "return_size_limit",   vtype_int,         &bounce_return_size_limit },
   { "router_name",         vtype_stringptr,   &router_name },
   { "return_path",         vtype_stringptr,   &return_path },
   { "return_size_limit",   vtype_int,         &bounce_return_size_limit },
   { "router_name",         vtype_stringptr,   &router_name },
@@ -611,6 +674,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_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 },
   { "sender_helo_name",    vtype_stringptr,   &sender_helo_name },
   { "sender_host_address", vtype_stringptr,   &sender_host_address },
   { "sender_host_authenticated",vtype_stringptr, &sender_host_authenticated },
@@ -628,6 +692,7 @@ static var_entry var_table[] = {
   { "smtp_active_hostname", vtype_stringptr,  &smtp_active_hostname },
   { "smtp_command",        vtype_stringptr,   &smtp_cmd_buffer },
   { "smtp_command_argument", vtype_stringptr, &smtp_cmd_argument },
   { "smtp_active_hostname", vtype_stringptr,  &smtp_active_hostname },
   { "smtp_command",        vtype_stringptr,   &smtp_cmd_buffer },
   { "smtp_command_argument", vtype_stringptr, &smtp_cmd_argument },
+  { "smtp_command_history", vtype_string_func, &smtp_cmd_hist },
   { "smtp_count_at_connection_start", vtype_int, &smtp_accept_count },
   { "smtp_notquit_reason", vtype_stringptr,   &smtp_notquit_reason },
   { "sn0",                 vtype_filter_int,  &filter_sn[0] },
   { "smtp_count_at_connection_start", vtype_int, &smtp_accept_count },
   { "smtp_notquit_reason", vtype_stringptr,   &smtp_notquit_reason },
   { "sn0",                 vtype_filter_int,  &filter_sn[0] },
@@ -641,16 +706,18 @@ static var_entry var_table[] = {
   { "sn8",                 vtype_filter_int,  &filter_sn[8] },
   { "sn9",                 vtype_filter_int,  &filter_sn[9] },
 #ifdef WITH_CONTENT_SCAN
   { "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 },
   { "spam_score_int",      vtype_stringptr,   &spam_score_int },
 #endif
   { "spam_bar",            vtype_stringptr,   &spam_bar },
   { "spam_report",         vtype_stringptr,   &spam_report },
   { "spam_score",          vtype_stringptr,   &spam_score },
   { "spam_score_int",      vtype_stringptr,   &spam_score_int },
 #endif
-#ifdef EXPERIMENTAL_SPF
+#ifdef SUPPORT_SPF
   { "spf_guess",           vtype_stringptr,   &spf_guess },
   { "spf_header_comment",  vtype_stringptr,   &spf_header_comment },
   { "spf_received",        vtype_stringptr,   &spf_received },
   { "spf_result",          vtype_stringptr,   &spf_result },
   { "spf_guess",           vtype_stringptr,   &spf_guess },
   { "spf_header_comment",  vtype_stringptr,   &spf_header_comment },
   { "spf_received",        vtype_stringptr,   &spf_received },
   { "spf_result",          vtype_stringptr,   &spf_result },
+  { "spf_result_guessed",  vtype_bool,        &spf_result_guessed },
   { "spf_smtp_comment",    vtype_stringptr,   &spf_smtp_comment },
 #endif
   { "spool_directory",     vtype_stringptr,   &spool_directory },
   { "spf_smtp_comment",    vtype_stringptr,   &spf_smtp_comment },
 #endif
   { "spool_directory",     vtype_stringptr,   &spool_directory },
@@ -684,6 +751,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 },
   { "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 SUPPORT_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 },
   { "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 +761,9 @@ static var_entry var_table[] = {
 #if defined(SUPPORT_TLS)
   { "tls_out_sni",         vtype_stringptr,   &tls_out.sni },
 #endif
 #if defined(SUPPORT_TLS)
   { "tls_out_sni",         vtype_stringptr,   &tls_out.sni },
 #endif
+#ifdef SUPPORT_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)
 
   { "tls_peerdn",          vtype_stringptr,   &tls_in.peerdn },        /* mind the alphabetical order! */
 #if defined(SUPPORT_TLS)
@@ -705,18 +778,9 @@ static var_entry var_table[] = {
   { "tod_logfile",         vtype_todlf,       NULL },
   { "tod_zone",            vtype_todzone,     NULL },
   { "tod_zulu",            vtype_todzulu,     NULL },
   { "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 },
   { "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 },
   { "version_number",      vtype_stringptr,   &version_string },
   { "warn_message_delay",  vtype_stringptr,   &warnmsg_delay },
   { "warn_message_recipient",vtype_stringptr, &warnmsg_recipients },
@@ -726,7 +790,7 @@ static var_entry var_table[] = {
   { "warnmsg_recipients",  vtype_stringptr,   &warnmsg_recipients }
 };
 
   { "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;
 
 static uschar var_buffer[256];
 static BOOL malformed_header;
 
@@ -756,6 +820,10 @@ static uschar *mtable_setid[] =
 static uschar *mtable_sticky[] =
   { US"--T", US"--t", US"-wT", US"-wt", US"r-T", US"r-t", US"rwT", US"rwt" };
 
 static uschar *mtable_sticky[] =
   { US"--T", US"--t", US"-wT", US"-wt", US"r-T", US"r-t", US"rwT", US"rwt" };
 
+/* flags for find_header() */
+#define FH_EXISTS_ONLY BIT(0)
+#define FH_WANT_RAW    BIT(1)
+#define FH_WANT_LIST   BIT(2)
 
 
 /*************************************************
 
 
 /*************************************************
@@ -794,6 +862,9 @@ static int utf8_table2[] = { 0xff, 0x1f, 0x0f, 0x07, 0x03, 0x01};
     }
 
 
     }
 
 
+
+static uschar * base32_chars = US"abcdefghijklmnopqrstuvwxyz234567";
+
 /*************************************************
 *           Binary chop search on a table        *
 *************************************************/
 /*************************************************
 *           Binary chop search on a table        *
 *************************************************/
@@ -856,7 +927,7 @@ int rc;
 uschar *ss = expand_string(condition);
 if (ss == NULL)
   {
 uschar *ss = expand_string(condition);
 if (ss == NULL)
   {
-  if (!expand_string_forcedfail && !search_find_defer)
+  if (!f.expand_string_forcedfail && !f.search_find_defer)
     log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand condition \"%s\" "
       "for %s %s: %s", condition, m1, m2, expand_string_message);
   return FALSE;
     log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand condition \"%s\" "
       "for %s %s: %s", condition, m1, m2, expand_string_message);
   return FALSE;
@@ -913,7 +984,9 @@ vaguely_random_number(int max)
 #ifdef HAVE_ARC4RANDOM
       /* cryptographically strong randomness, common on *BSD platforms, not
       so much elsewhere.  Alas. */
 #ifdef HAVE_ARC4RANDOM
       /* cryptographically strong randomness, common on *BSD platforms, not
       so much elsewhere.  Alas. */
+#ifndef NOT_HAVE_ARC4RANDOM_STIR
       arc4random_stir();
       arc4random_stir();
+#endif
 #elif defined(HAVE_SRANDOM) || defined(HAVE_SRANDOMDEV)
 #ifdef HAVE_SRANDOMDEV
       /* uses random(4) for seeding */
 #elif defined(HAVE_SRANDOM) || defined(HAVE_SRANDOMDEV)
 #ifdef HAVE_SRANDOMDEV
       /* uses random(4) for seeding */
@@ -961,8 +1034,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). */
 
 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))
 {
 int ptr = 0;
 while (*s != 0 && (isalnum(*s) || Ustrchr(extras, *s) != NULL))
@@ -995,8 +1068,8 @@ Arguments:
 Returns:    a pointer to the first character after the header name
 */
 
 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;
 {
 int prelen = Ustrchr(name, '_') - name + 1;
 int ptr = Ustrlen(name) - prelen;
@@ -1024,6 +1097,8 @@ return s;
 
 Returns:  a pointer to the character after the last digit
 */
 
 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)
 
 static uschar *
 read_number(int *n, uschar *s)
@@ -1033,6 +1108,14 @@ while (isdigit(*s)) *n = *n * 10 + (*s++ - '0');
 return s;
 }
 
 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,20 +1133,20 @@ Returns:    NULL if the subfield was not found, or
 */
 
 static uschar *
 */
 
 static uschar *
-expand_getkeyed(uschar *key, uschar *s)
+expand_getkeyed(uschar * key, const uschar * s)
 {
 int length = Ustrlen(key);
 while (isspace(*s)) s++;
 
 /* Loop to search for the key */
 
 {
 int length = Ustrlen(key);
 while (isspace(*s)) s++;
 
 /* Loop to search for the key */
 
-while (*s != 0)
+while (*s)
   {
   int dkeylength;
   {
   int dkeylength;
-  uschar *data;
-  uschar *dkey = s;
+  uschar * data;
+  const uschar * dkey = s;
 
 
-  while (*s != 0 && *s != '=' && !isspace(*s)) s++;
+  while (*s && *s != '=' && !isspace(*s)) s++;
   dkeylength = s - dkey;
   while (isspace(*s)) s++;
   if (*s == '=') while (isspace((*(++s))));
   dkeylength = s - dkey;
   while (isspace(*s)) s++;
   if (*s == '=') while (isspace((*(++s))));
@@ -1172,19 +1255,19 @@ return fieldtext;
 
 
 static uschar *
 
 
 static uschar *
-expand_getlistele(int field, uschar * list)
+expand_getlistele(int field, const uschar * list)
 {
 {
-uschar * tlist= list;
-int sep= 0;
+const uschar * tlist = list;
+int sep = 0;
 uschar dummy;
 
 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))) ;
+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);
 }
 
 return string_nextinlist(&list, &sep, NULL, 0);
 }
 
@@ -1222,7 +1305,7 @@ certfield * cp;
 
 if (!(vp = find_var_ent(certvar)))
   {
 
 if (!(vp = find_var_ent(certvar)))
   {
-  expand_string_message = 
+  expand_string_message =
     string_sprintf("no variable named \"%s\"", certvar);
   return NULL;          /* Unknown variable name */
   }
     string_sprintf("no variable named \"%s\"", certvar);
   return NULL;          /* Unknown variable name */
   }
@@ -1230,7 +1313,7 @@ if (!(vp = find_var_ent(certvar)))
 want to do that in future */
 if (vp->type != vtype_cert)
   {
 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 */
   }
     string_sprintf("\"%s\" is not a certificate", certvar);
   return NULL;          /* Unknown variable name */
   }
@@ -1241,7 +1324,7 @@ if (*field >= '0' && *field <= '9')
   return tls_cert_ext_by_oid(*(void **)vp->value, field, 0);
 
 for(cp = certfields;
   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)
     {
     cp++)
   if (Ustrncmp(cp->name, field, cp->namelen) == 0)
     {
@@ -1250,7 +1333,7 @@ for(cp = certfields;
     return (*cp->getfn)( *(void **)vp->value, modifier );
     }
 
     return (*cp->getfn)( *(void **)vp->value, modifier );
     }
 
-expand_string_message = 
+expand_string_message =
   string_sprintf("bad field selector \"%s\" for certextract", field);
 return NULL;
 }
   string_sprintf("bad field selector \"%s\" for certextract", field);
 return NULL;
 }
@@ -1411,23 +1494,21 @@ unsigned long int total = 0; /* no overflow */
 
 while (*s != 0)
   {
 
 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++);
   }
 
 /* If value2 is unset, just compute one number */
 
 if (value2 < 0)
   total += prime[i--] * (unsigned int)(*s++);
   }
 
 /* If value2 is unset, just compute one number */
 
 if (value2 < 0)
-  {
-  s = string_sprintf("%d", total % value1);
-  }
+  s = string_sprintf("%lu", total % value1);
 
 /* Otherwise do a div/mod hash */
 
 else
   {
   total = total % (value1 * value2);
 
 /* Otherwise do a div/mod hash */
 
 else
   {
   total = total % (value1 * value2);
-  s = string_sprintf("%d/%d", total/value2, total % value2);
+  s = string_sprintf("%lu/%lu", total/value2, total % value2);
   }
 
 *len = Ustrlen(s);
   }
 
 *len = Ustrlen(s);
@@ -1447,22 +1528,25 @@ can also return a concatenation of all the header lines. When concatenating
 specific headers that contain lists of addresses, a comma is inserted between
 them. Otherwise we use a straight concatenation. Because some messages can have
 pathologically large number of lines, there is a limit on the length that is
 specific headers that contain lists of addresses, a comma is inserted between
 them. Otherwise we use a straight concatenation. Because some messages can have
 pathologically large number of lines, there is a limit on the length that is
-returned. Also, to avoid massive store use which would result from using
-string_cat() as it copies and extends strings, we do a preliminary pass to find
-out exactly how much store will be needed. On "normal" messages this will be
-pretty trivial.
+returned.
 
 Arguments:
   name          the name of the header, without the leading $header_ or $h_,
                 or NULL if a concatenation of all headers is required
 
 Arguments:
   name          the name of the header, without the leading $header_ or $h_,
                 or NULL if a concatenation of all headers is required
-  exists_only   TRUE if called from a def: test; don't need to build a string;
-                just return a string that is not "" and not "0" if the header
-                exists
   newsize       return the size of memory block that was obtained; may be NULL
                 if exists_only is TRUE
   newsize       return the size of memory block that was obtained; may be NULL
                 if exists_only is TRUE
-  want_raw      TRUE if called for $rh_ or $rheader_ variables; no processing,
-                other than concatenating, will be done on the header. Also used
-                for $message_headers_raw.
+  flags                FH_EXISTS_ONLY
+                 set if called from a def: test; don't need to build a string;
+                 just return a string that is not "" and not "0" if the header
+                 exists
+               FH_WANT_RAW
+                 set if called for $rh_ or $rheader_ items; no processing,
+                 other than concatenating, will be done on the header. Also used
+                 for $message_headers_raw.
+               FH_WANT_LIST
+                 Double colon chars in the content, and replace newline with
+                 colon between each element when concatenating; returning a
+                 colon-sep list (elements might contain newlines)
   charset       name of charset to translate MIME words to; used only if
                 want_raw is false; if NULL, no translation is done (this is
                 used for $bh_ and $bheader_)
   charset       name of charset to translate MIME words to; used only if
                 want_raw is false; if NULL, no translation is done (this is
                 used for $bh_ and $bheader_)
@@ -1472,130 +1556,141 @@ Returns:        NULL if the header does not exist, else a pointer to a new
 */
 
 static uschar *
 */
 
 static uschar *
-find_header(uschar *name, BOOL exists_only, int *newsize, BOOL want_raw,
-  uschar *charset)
+find_header(uschar *name, int *newsize, unsigned flags, uschar *charset)
 {
 {
-BOOL found = name == NULL;
-int comma = 0;
-int len = found? 0 : Ustrlen(name);
-int i;
-uschar *yield = NULL;
-uschar *ptr = NULL;
-
-/* Loop for two passes - saves code repetition */
-
-for (i = 0; i < 2; i++)
-  {
-  int size = 0;
-  header_line *h;
-
-  for (h = header_list; size < header_insert_maxlen && h != NULL; h = h->next)
-    {
-    if (h->type != htype_old && h->text != NULL)  /* NULL => Received: placeholder */
+BOOL found = !name;
+int len = name ? Ustrlen(name) : 0;
+BOOL comma = FALSE;
+header_line * h;
+gstring * g = NULL;
+
+for (h = header_list; h; h = h->next)
+  if (h->type != htype_old && h->text)  /* NULL => Received: placeholder */
+    if (!name || (len <= h->slen && strncmpic(name, h->text, len) == 0))
       {
       {
-      if (name == NULL || (len <= h->slen && strncmpic(name, h->text, len) == 0))
-        {
-        int ilen;
-        uschar *t;
-
-        if (exists_only) return US"1";      /* don't need actual string */
-        found = TRUE;
-        t = h->text + len;                  /* text to insert */
-        if (!want_raw)                      /* unless wanted raw, */
-          while (isspace(*t)) t++;          /* remove leading white space */
-        ilen = h->slen - (t - h->text);     /* length to insert */
-
-        /* Unless wanted raw, remove trailing whitespace, including the
-        newline. */
+      uschar * s, * t;
+      size_t inc;
 
 
-        if (!want_raw)
-          while (ilen > 0 && isspace(t[ilen-1])) ilen--;
+      if (flags & FH_EXISTS_ONLY)
+       return US"1";  /* don't need actual string */
 
 
-        /* Set comma = 1 if handling a single header and it's one of those
-        that contains an address list, except when asked for raw headers. Only
-        need to do this once. */
+      found = TRUE;
+      s = h->text + len;               /* text to insert */
+      if (!(flags & FH_WANT_RAW))      /* unless wanted raw, */
+       while (isspace(*s)) s++;        /* remove leading white space */
+      t = h->text + h->slen;           /* end-point */
 
 
-        if (!want_raw && name != NULL && comma == 0 &&
-            Ustrchr("BCFRST", h->type) != NULL)
-          comma = 1;
+      /* Unless wanted raw, remove trailing whitespace, including the
+      newline. */
 
 
-        /* First pass - compute total store needed; second pass - compute
-        total store used, including this header. */
-
-        size += ilen + comma + 1;  /* +1 for the newline */
+      if (flags & FH_WANT_LIST)
+       while (t > s && t[-1] == '\n') t--;
+      else if (!(flags & FH_WANT_RAW))
+       {
+       while (t > s && isspace(t[-1])) t--;
 
 
-        /* Second pass - concatentate the data, up to a maximum. Note that
-        the loop stops when size hits the limit. */
+       /* Set comma if handling a single header and it's one of those
+       that contains an address list, except when asked for raw headers. Only
+       need to do this once. */
 
 
-        if (i != 0)
-          {
-          if (size > header_insert_maxlen)
-            {
-            ilen -= size - header_insert_maxlen - 1;
-            comma = 0;
-            }
-          Ustrncpy(ptr, t, ilen);
-          ptr += ilen;
+       if (name && !comma && Ustrchr("BCFRST", h->type)) comma = TRUE;
+       }
 
 
-          /* For a non-raw header, put in the comma if needed, then add
-          back the newline we removed above, provided there was some text in
-          the header. */
+      /* Trim the header roughly if we're approaching limits */
+      inc = t - s;
+      if ((g ? g->ptr : 0) + inc > header_insert_maxlen)
+       inc = header_insert_maxlen - (g ? g->ptr : 0);
+
+      /* For raw just copy the data; for a list, add the data as a colon-sep
+      list-element; for comma-list add as an unchecked comma,newline sep
+      list-elemment; for other nonraw add as an unchecked newline-sep list (we
+      stripped trailing WS above including the newline). We ignore the potential
+      expansion due to colon-doubling, just leaving the loop if the limit is met
+      or exceeded. */
+
+      if (flags & FH_WANT_LIST)
+        g = string_append_listele_n(g, ':', s, (unsigned)inc);
+      else if (flags & FH_WANT_RAW)
+       {
+       g = string_catn(g, s, (unsigned)inc);
+       (void) string_from_gstring(g);
+       }
+      else if (inc > 0)
+       if (comma)
+         g = string_append2_listele_n(g, US",\n", s, (unsigned)inc);
+       else
+         g = string_append2_listele_n(g, US"\n", s, (unsigned)inc);
 
 
-          if (!want_raw && ilen > 0)
-            {
-            if (comma != 0) *ptr++ = ',';
-            *ptr++ = '\n';
-            }
-          }
-        }
+      if (g && g->ptr >= header_insert_maxlen) break;
       }
       }
-    }
-
-  /* At end of first pass, return NULL if no header found. Then truncate size
-  if necessary, and get the buffer to hold the data, returning the buffer size.
-  */
 
 
-  if (i == 0)
-    {
-    if (!found) return NULL;
-    if (size > header_insert_maxlen) size = header_insert_maxlen;
-    *newsize = size + 1;
-    ptr = yield = store_get(*newsize);
-    }
-  }
+if (!found) return NULL;       /* No header found */
+if (!g) return US"";
 
 /* That's all we do for raw header expansion. */
 
 
 /* That's all we do for raw header expansion. */
 
-if (want_raw)
-  {
-  *ptr = 0;
-  }
+*newsize = g->size;
+if (flags & FH_WANT_RAW)
+  return g->s;
 
 
-/* Otherwise, remove a final newline and a redundant added comma. Then we do
-RFC 2047 decoding, translating the charset if requested. The rfc2047_decode2()
-function can return an error with decoded data if the charset translation
-fails. If decoding fails, it returns NULL. */
+/* Otherwise do RFC 2047 decoding, translating the charset if requested.
+The rfc2047_decode2() function can return an error with decoded data if the
+charset translation fails. If decoding fails, it returns NULL. */
 
 else
   {
   uschar *decoded, *error;
 
 else
   {
   uschar *decoded, *error;
-  if (ptr > yield && ptr[-1] == '\n') ptr--;
-  if (ptr > yield && comma != 0 && ptr[-1] == ',') ptr--;
-  *ptr = 0;
-  decoded = rfc2047_decode2(yield, check_rfc2047_length, charset, '?', NULL,
+
+  decoded = rfc2047_decode2(g->s, check_rfc2047_length, charset, '?', NULL,
     newsize, &error);
     newsize, &error);
-  if (error != NULL)
+  if (error)
     {
     DEBUG(D_any) debug_printf("*** error in RFC 2047 decoding: %s\n"
     {
     DEBUG(D_any) debug_printf("*** error in RFC 2047 decoding: %s\n"
-      "    input was: %s\n", error, yield);
+      "    input was: %s\n", error, g->s);
     }
     }
-  if (decoded != NULL) yield = decoded;
+  return decoded ? decoded : g->s;
   }
   }
+}
 
 
-return yield;
+
+
+
+/* Append a "local" element to an Authentication-Results: header
+if this was a non-smtp message.
+*/
+
+static gstring *
+authres_local(gstring * g, const uschar * sysname)
+{
+if (!f.authentication_local)
+  return g;
+g = string_append(g, 3, US";\n\tlocal=pass (non-smtp, ", sysname, US")");
+if (authenticated_id) g = string_append(g, 2, " u=", authenticated_id);
+return g;
 }
 
 
 }
 
 
+/* Append an "iprev" element to an Authentication-Results: header
+if we have attempted to get the calling host's name.
+*/
+
+static gstring *
+authres_iprev(gstring * g)
+{
+if (sender_host_name)
+  g = string_append(g, 3, US";\n\tiprev=pass (", sender_host_name, US")");
+else if (host_lookup_deferred)
+  g = string_catn(g, US";\n\tiprev=temperror", 19);
+else if (host_lookup_failed)
+  g = string_catn(g, US";\n\tiprev=fail", 13);
+else
+  return g;
+
+if (sender_host_address)
+  g = string_append(g, 2, US" smtp.remote-ip=", sender_host_address);
+return g;
+}
+
 
 
 /*************************************************
 
 
 /*************************************************
@@ -1608,21 +1703,18 @@ generated from a system filter, but not elsewhere. */
 static uschar *
 fn_recipients(void)
 {
 static uschar *
 fn_recipients(void)
 {
-if (!enable_dollar_recipients) return NULL; else
+uschar * s;
+gstring * g = NULL;
+int i;
+
+if (!f.enable_dollar_recipients) return NULL;
+
+for (i = 0; i < recipients_count; i++)
   {
   {
-  int size = 128;
-  int ptr = 0;
-  int i;
-  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));
-    }
-  s[ptr] = 0;     /* string_cat() leaves room */
-  return s;
+  s = recipients_list[i].address;
+  g = string_append2_listele_n(g, US", ", s, Ustrlen(s));
   }
   }
+return g ? g->s : NULL;
 }
 
 
 }
 
 
@@ -1672,7 +1764,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);
   {
   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. */
   }
 
 /* Handle $auth<n> variables. */
@@ -1682,7 +1774,14 @@ if (Ustrncmp(name, "auth", 4) == 0)
   uschar *endptr;
   int n = Ustrtoul(name + 4, &endptr, 10);
   if (*endptr == 0 && n != 0 && n <= AUTH_VARS)
   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 */
   }
 
 /* For all other variables, search the table */
@@ -1700,153 +1799,154 @@ val = vp->value;
 switch (vp->type)
   {
   case vtype_filter_int:
 switch (vp->type)
   {
   case vtype_filter_int:
-  if (!filter_running) return NULL;
-  /* Fall through */
-  /* VVVVVVVVVVVV */
+    if (!f.filter_running) return NULL;
+    /* Fall through */
+    /* VVVVVVVVVVVV */
   case vtype_int:
   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:
 
   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:
 
   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:
 
   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:
 
   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 */
 
   case vtype_stringptr:                      /* Pointer to string */
-  s = *((uschar **)(val));
-  return (s == NULL)? US"" : s;
+    return (s = *((uschar **)(val))) ? s : US"";
 
   case vtype_pid:
 
   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:
 
   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 */
 
   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 && sender_host_address
+       && !host_lookup_failed && host_name_lookup() == OK)
+      host_build_sender_fullhost();
+    return sender_host_name ? sender_host_name : US"";
 
   case vtype_localpart:                      /* Get local part from address */
 
   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 */
 
   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:
 
   case vtype_msgheaders:
-  return find_header(NULL, exists_only, newsize, FALSE, NULL);
+    return find_header(NULL, newsize, exists_only ? FH_EXISTS_ONLY : 0, NULL);
 
   case vtype_msgheaders_raw:
 
   case vtype_msgheaders_raw:
-  return find_header(NULL, exists_only, newsize, TRUE, NULL);
+    return find_header(NULL, newsize,
+               exists_only ? FH_EXISTS_ONLY|FH_WANT_RAW : FH_WANT_RAW, NULL);
 
   case vtype_msgbody:                        /* Pointer to msgbody string */
   case vtype_msgbody_end:                    /* Ditto, the end of the msg */
 
   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 && 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 ? *ss : US"";
 
   case vtype_todbsdin:                       /* BSD inbox time of day */
 
   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 */
 
   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 */
 
   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 */
 
   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 */
 
   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 */
 
   case vtype_todzone:                        /* Time zone offset only */
-  return tod_stamp(tod_zone);
+    return tod_stamp(tod_zone);
 
   case vtype_todzulu:                        /* Zulu time */
 
   case vtype_todzulu:                        /* Zulu time */
-  return tod_stamp(tod_zulu);
+    return tod_stamp(tod_zulu);
 
   case vtype_todlf:                          /* Log file datestamp tod */
 
   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 */
 
   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:", newsize,
+               exists_only ? FH_EXISTS_ONLY|FH_WANT_RAW : FH_WANT_RAW,
+               headers_charset);
+    if (s) while (isspace(*s)) s++;
+    if (!s || !*s)
+      {
+      *newsize = 0;                            /* For the *s==0 case */
+      s = find_header(US"from:", newsize,
+               exists_only ? FH_EXISTS_ONLY|FH_WANT_RAW : FH_WANT_RAW,
+               headers_charset);
+      }
+    if (s)
+      {
+      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 ? s : US"";
 
   case vtype_string_func:
     {
 
   case vtype_string_func:
     {
@@ -1857,7 +1957,7 @@ switch (vp->type)
   case vtype_pspace:
     {
     int inodes;
   case vtype_pspace:
     {
     int inodes;
-    sprintf(CS var_buffer, "%d",
+    sprintf(CS var_buffer, PR_EXIM_ARITH,
       receive_statvfs(val == (void *)TRUE, &inodes));
     }
   return var_buffer;
       receive_statvfs(val == (void *)TRUE, &inodes));
     }
   return var_buffer;
@@ -1871,12 +1971,12 @@ switch (vp->type)
   return var_buffer;
 
   case vtype_cert:
   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:
   case vtype_dkim:
-  return dkim_exim_expand_query((int)(long)val);
-  #endif
+    return dkim_exim_expand_query((int)(long)val);
+#endif
 
   }
 
 
   }
 
@@ -1898,6 +1998,7 @@ return;          /* Unknown variable name, fail silently */
 
 
 
 
 
 
+
 /*************************************************
 *           Read and expand substrings           *
 *************************************************/
 /*************************************************
 *           Read and expand substrings           *
 *************************************************/
@@ -1924,23 +2025,28 @@ Returns:     0 OK; string pointer updated
 */
 
 static int
 */
 
 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;
   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 != '{')
     {
 
 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] = 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++;
   }
   if (*s++ != '}') return 1;
   while (isspace(*s)) s++;
   }
@@ -1948,10 +2054,11 @@ if (check_end && *s++ != '}')
   {
   if (s[-1] == '{')
     {
   {
   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;
     }
       "(max is %d)", name, n);
     return 2;
     }
+  expand_string_message = string_sprintf("missing '}' after '%s'", name);
   return 1;
   }
 
   return 1;
   }
 
@@ -1994,7 +2101,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
 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
               FAIL       access is denied
               FAIL_DROP  access is denied; drop the connection
               DEFER      can't tell at the moment
@@ -2004,15 +2111,15 @@ static int
 eval_acl(uschar ** sub, int nsub, uschar ** user_msgp)
 {
 int i;
 eval_acl(uschar ** sub, int nsub, uschar ** user_msgp)
 {
 int i;
-uschar *tmp;
 int sav_narg = acl_narg;
 int ret;
 int sav_narg = acl_narg;
 int ret;
+uschar * dummy_logmsg;
 extern int acl_where;
 
 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++)
   {
 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 */
   }
   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 +2131,12 @@ while (i < nsub)
   }
 
 DEBUG(D_expand)
   }
 
 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"   : "");
 
     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 */
 
 for (i = 0; i < nsub; i++)
   acl_arg[i] = sub[i+1];       /* restore old args */
@@ -2060,8 +2167,8 @@ Returns:   a pointer to the first character after the condition, or
            NULL after an error
 */
 
            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;
 {
 BOOL testfor = TRUE;
 BOOL tempcond, combined_cond;
@@ -2071,7 +2178,7 @@ int i, rc, cond_type, roffset;
 int_eximarith_t num[2];
 struct stat statbuf;
 uschar name[256];
 int_eximarith_t num[2];
 struct stat statbuf;
 uschar name[256];
-uschar *sub[10];
+const uschar *sub[10];
 
 const pcre *re;
 const uschar *rerror;
 
 const pcre *re;
 const uschar *rerror;
@@ -2111,63 +2218,65 @@ if (name[0] == 0)
 
 /* Find which condition we are dealing with, and switch on it */
 
 
 /* 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
   yield == NULL we are in a skipping state, and don't care about the answer. */
 
   case ECOND_DEF:
 switch(cond_type)
   {
   /* def: tests for a non-empty variable, or for the existence of a header. If
   yield == NULL we are in a skipping state, and don't care about the answer. */
 
   case ECOND_DEF:
-  if (*s != ':')
     {
     {
-    expand_string_message = US"\":\" expected after \"def\"";
-    return NULL;
-    }
+    uschar * t;
 
 
-  s = read_name(name, 256, s+1, US"_");
+    if (*s != ':')
+      {
+      expand_string_message = US"\":\" expected after \"def\"";
+      return NULL;
+      }
 
 
-  /* Test for a header's existence. If the name contains a closing brace
-  character, this may be a user error where the terminating colon has been
-  omitted. Set a flag to adjust a subsequent error message in this case. */
+    s = read_name(name, 256, s+1, US"_");
 
 
-  if (Ustrncmp(name, "h_", 2) == 0 ||
-      Ustrncmp(name, "rh_", 3) == 0 ||
-      Ustrncmp(name, "bh_", 3) == 0 ||
-      Ustrncmp(name, "header_", 7) == 0 ||
-      Ustrncmp(name, "rheader_", 8) == 0 ||
-      Ustrncmp(name, "bheader_", 8) == 0)
-    {
-    s = read_header_name(name, 256, s);
-    /* {-for-text-editors */
-    if (Ustrchr(name, '}') != NULL) malformed_header = TRUE;
-    if (yield != NULL) *yield =
-      (find_header(name, TRUE, NULL, FALSE, NULL) != NULL) == testfor;
-    }
+    /* Test for a header's existence. If the name contains a closing brace
+    character, this may be a user error where the terminating colon has been
+    omitted. Set a flag to adjust a subsequent error message in this case. */
+
+    if (  ( *(t = name) == 'h'
+         || (*t == 'r' || *t == 'l' || *t == 'b') && *++t == 'h'
+         )
+       && (*++t == '_' || Ustrncmp(t, "eader_", 6) == 0)
+       )
+      {
+      s = read_header_name(name, 256, s);
+      /* {-for-text-editors */
+      if (Ustrchr(name, '}') != NULL) malformed_header = TRUE;
+      if (yield) *yield =
+       (find_header(name, NULL, FH_EXISTS_ONLY, NULL) != NULL) == testfor;
+      }
 
 
-  /* Test for a variable's having a non-empty value. A non-existent variable
-  causes an expansion failure. */
+    /* Test for a variable's having a non-empty value. A non-existent variable
+    causes an expansion failure. */
 
 
-  else
-    {
-    uschar *value = find_variable(name, TRUE, yield == NULL, NULL);
-    if (value == NULL)
+    else
       {
       {
-      expand_string_message = (name[0] == 0)?
-        string_sprintf("variable name omitted after \"def:\"") :
-        string_sprintf("unknown variable \"%s\" after \"def:\"", name);
-      check_variable_error_message(name);
-      return NULL;
+      if (!(t = find_variable(name, TRUE, yield == NULL, NULL)))
+       {
+       expand_string_message = (name[0] == 0)?
+         string_sprintf("variable name omitted after \"def:\"") :
+         string_sprintf("unknown variable \"%s\" after \"def:\"", name);
+       check_variable_error_message(name);
+       return NULL;
+       }
+      if (yield) *yield = (t[0] != 0) == testfor;
       }
       }
-    if (yield != NULL) *yield = (value[0] != 0) == testfor;
-    }
 
 
-  return s;
+    return s;
+    }
 
 
   /* first_delivery tests for first delivery attempt */
 
   case ECOND_FIRST_DELIVERY:
 
 
   /* first_delivery tests for first delivery attempt */
 
   case ECOND_FIRST_DELIVERY:
-  if (yield != NULL) *yield = deliver_firsttime == testfor;
+  if (yield != NULL) *yield = f.deliver_firsttime == testfor;
   return s;
 
 
   return s;
 
 
@@ -2291,15 +2400,14 @@ switch(cond_type)
   case ECOND_ACL:
     /* ${if acl {{name}{arg1}{arg2}...}  {yes}{no}} */
     {
   case ECOND_ACL:
     /* ${if acl {{name}{arg1}{arg2}...}  {yes}{no}} */
     {
+    uschar *sub[10];
     uschar *user_msg;
     BOOL cond = FALSE;
     uschar *user_msg;
     BOOL cond = FALSE;
-    int size = 0;
-    int ptr = 0;
 
     while (isspace(*s)) s++;
     if (*s++ != '{') goto COND_FAILED_CURLY_START;     /*}*/
 
 
     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 "
       &s, yield == NULL, TRUE, US"acl", resetok))
       {
       case 1: expand_string_message = US"too few arguments or bracketing "
@@ -2308,27 +2416,28 @@ switch(cond_type)
       case 3: return NULL;
       }
 
       case 3: return NULL;
       }
 
-    *resetok = FALSE;
-    if (yield != NULL) switch(eval_acl(sub, sizeof(sub)/sizeof(*sub), &user_msg))
+    if (yield != NULL)
+      {
+      *resetok = FALSE;        /* eval_acl() might allocate; do not reclaim */
+      switch(eval_acl(sub, nelem(sub), &user_msg))
        {
        case OK:
          cond = TRUE;
        case FAIL:
           lookup_value = NULL;
          if (user_msg)
        {
        case OK:
          cond = TRUE;
        case FAIL:
           lookup_value = NULL;
          if (user_msg)
-           {
-            lookup_value = string_cat(NULL, &size, &ptr, user_msg, Ustrlen(user_msg));
-            lookup_value[ptr] = '\0';
-           }
+            lookup_value = string_copy(user_msg);
          *yield = cond == testfor;
          break;
 
        case DEFER:
          *yield = cond == testfor;
          break;
 
        case DEFER:
-          expand_string_forcedfail = TRUE;
+          f.expand_string_forcedfail = TRUE;
+         /*FALLTHROUGH*/
        default:
           expand_string_message = string_sprintf("error from acl \"%s\"", sub[0]);
          return NULL;
        }
        default:
           expand_string_message = string_sprintf("error from acl \"%s\"", sub[0]);
          return NULL;
        }
+      }
     return s;
     }
 
     return s;
     }
 
@@ -2341,29 +2450,32 @@ switch(cond_type)
   in their own set of braces. */
 
   case ECOND_SASLAUTHD:
   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
 
 
   /* symbolic operators for numeric and string comparison, and a number of
@@ -2429,9 +2541,12 @@ switch(cond_type)
         "after \"%s\"", name);
       return NULL;
       }
         "after \"%s\"", name);
       return NULL;
       }
-    sub[i] = expand_string_internal(s+1, TRUE, &s, yield == NULL,
-        honour_dollar, resetok);
-    if (sub[i] == NULL) return NULL;
+    if (!(sub[i] = expand_string_internal(s+1, TRUE, &s, yield == NULL,
+        honour_dollar, resetok)))
+      return NULL;
+    DEBUG(D_expand) if (i == 1 && !sub2_honour_dollar && Ustrchr(sub[1], '$'))
+      debug_printf_indent("WARNING: the second arg is NOT expanded,"
+                       " for security reasons\n");
     if (*s++ != '}') goto COND_FAILED_CURLY_END;
 
     /* Convert to numerical if required; we know that the names of all the
     if (*s++ != '}') goto COND_FAILED_CURLY_END;
 
     /* Convert to numerical if required; we know that the names of all the
@@ -2439,19 +2554,17 @@ switch(cond_type)
     checking for them individually. */
 
     if (!isalpha(name[0]) && yield != NULL)
     checking for them individually. */
 
     if (!isalpha(name[0]) && yield != NULL)
-      {
       if (sub[i][0] == 0)
         {
         num[i] = 0;
         DEBUG(D_expand)
       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;
         }
         }
       else
         {
         num[i] = expanded_string_integer(sub[i], FALSE);
         if (expand_string_message != NULL) return NULL;
         }
-      }
     }
 
   /* Result not required */
     }
 
   /* Result not required */
@@ -2619,7 +2732,7 @@ switch(cond_type)
       uschar digest[16];
 
       md5_start(&base);
       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,
 
       /* 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 +2741,7 @@ switch(cond_type)
 
       if (sublen == 24)
         {
 
       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);
         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 +2767,11 @@ switch(cond_type)
     else if (strncmpic(sub[1], US"{sha1}", 6) == 0)
       {
       int sublen = Ustrlen(sub[1]+6);
     else if (strncmpic(sub[1], US"{sha1}", 6) == 0)
       {
       int sublen = Ustrlen(sub[1]+6);
-      sha1 base;
+      hctx h;
       uschar digest[20];
 
       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
 
       /* 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 +2779,7 @@ switch(cond_type)
 
       if (sublen == 28)
         {
 
       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);
         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 +2836,7 @@ switch(cond_type)
       #define XSTR(s) STR(s)
       DEBUG(D_auth) debug_printf("crypteq: using %s()\n"
         "  subject=%s\n  crypted=%s\n",
       #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
         coded, sub[1]);
       #undef STR
       #undef XSTR
@@ -2732,8 +2845,16 @@ switch(cond_type)
       salt), force failure. Otherwise we get false positives: with an empty
       string the yield of crypt() is an empty string! */
 
       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 */
       }
     break;
     #endif  /* SUPPORT_CRYPTEQ */
@@ -2741,22 +2862,26 @@ switch(cond_type)
     case ECOND_INLIST:
     case ECOND_INLISTI:
       {
     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 *);
 
       int sep = 0;
       uschar *save_iterate_item = iterate_item;
       int (*compare)(const uschar *, const uschar *);
 
+      DEBUG(D_expand) debug_printf_indent("condition: %s  item: %s\n", name, sub[0]);
+
       tempcond = FALSE;
       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)))
+       {
+       DEBUG(D_expand) debug_printf_indent(" compare %s\n", iterate_item);
         if (compare(sub[0], iterate_item) == 0)
           {
           tempcond = TRUE;
           break;
           }
         if (compare(sub[0], iterate_item) == 0)
           {
           tempcond = TRUE;
           break;
           }
+       }
       iterate_item = save_iterate_item;
       }
 
       iterate_item = save_iterate_item;
       }
 
@@ -2829,9 +2954,12 @@ switch(cond_type)
   case ECOND_FORALL:
   case ECOND_FORANY:
     {
   case ECOND_FORALL:
   case ECOND_FORANY:
     {
+    const uschar * list;
     int sep = 0;
     uschar *save_iterate_item = iterate_item;
 
     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);
     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 +2994,10 @@ switch(cond_type)
       }
 
     if (yield != NULL) *yield = !testfor;
       }
 
     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",
       if (!eval_condition(sub[1], resetok, &tempcond))
         {
         expand_string_message = string_sprintf("%s inside \"%s\" condition",
@@ -2876,7 +3005,7 @@ switch(cond_type)
         iterate_item = save_iterate_item;
         return NULL;
         }
         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);
         tempcond? "true":"false");
 
       if (yield != NULL) *yield = (tempcond == testfor);
@@ -2933,7 +3062,7 @@ switch(cond_type)
         }
       }
     DEBUG(D_expand)
         }
       }
     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. */
     /* 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 +3089,8 @@ switch(cond_type)
        "value \"%s\"", t);
       return NULL;
       }
        "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;
     }
     if (yield != NULL) *yield = (boolvalue == testfor);
     return s;
     }
@@ -3071,10 +3202,9 @@ Arguments:
   yes            TRUE if the first string is to be used, else use the second
   save_lookup    a value to put back into lookup_value before the 2nd expansion
   sptr           points to the input string pointer
   yes            TRUE if the first string is to be used, else use the second
   save_lookup    a value to put back into lookup_value before the 2nd expansion
   sptr           points to the input string pointer
-  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
+  yieldptr       points to the output growable-string pointer
+  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.
 
   resetok       if not NULL, pointer to flag - write FALSE if unsafe to reset
                the store.
 
@@ -3084,12 +3214,13 @@ Returns:         0 OK; lookup_value has been reset to save_lookup
 */
 
 static int
 */
 
 static int
-process_yesno(BOOL skipping, BOOL yes, uschar *save_lookup, uschar **sptr,
-  uschar **yieldptr, int *sizeptr, int *ptrptr, uschar *type, BOOL *resetok)
+process_yesno(BOOL skipping, BOOL yes, uschar *save_lookup, const uschar **sptr,
+  gstring ** yieldptr, uschar *type, BOOL *resetok)
 {
 int rc = 0;
 {
 int rc = 0;
-uschar *s = *sptr;    /* Local value */
+const uschar *s = *sptr;    /* Local value */
 uschar *sub1, *sub2;
 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
 
 /* 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 +3232,13 @@ if (*s == '}')
   {
   if (type[0] == 'i')
     {
   {
   if (type[0] == 'i')
     {
-    if (yes) *yieldptr = string_cat(*yieldptr, sizeptr, ptrptr, US"true", 4);
+    if (yes && !skipping)
+      *yieldptr = string_catn(*yieldptr, US"true", 4);
     }
   else
     {
     }
   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, lookup_value);
     lookup_value = save_lookup;
     }
   s++;
     lookup_value = save_lookup;
     }
   s++;
@@ -3116,26 +3247,35 @@ if (*s == '}')
 
 /* The first following string must be braced. */
 
 
 /* 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
 be the case if we were already skipping). */
 
 sub1 = expand_string_internal(s, TRUE, &s, !yes, TRUE, resetok);
 
 /* 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
 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 (sub1 == NULL && (yes || !f.expand_string_forcedfail)) goto FAILED;
+f.expand_string_forcedfail = FALSE;
+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)
 
 /* 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, 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;
 
 
 lookup_value = save_lookup;
 
@@ -3148,14 +3288,18 @@ while (isspace(*s)) s++;
 if (*s == '{')
   {
   sub2 = expand_string_internal(s+1, TRUE, &s, yes || skipping, TRUE, resetok);
 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 (sub2 == NULL && (!yes || !f.expand_string_forcedfail)) goto FAILED;
+  f.expand_string_forcedfail = FALSE;
+  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)
 
   /* 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, sub2);
   }
 
 /* If there is no second string, but the word "fail" is present when the use of
   }
 
 /* If there is no second string, but the word "fail" is present when the use of
@@ -3166,16 +3310,21 @@ inside another lookup or if or extract. */
 else if (*s != '}')
   {
   uschar name[256];
 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 (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_message =
         string_sprintf("\"%s\" failed and \"fail\" requested", type);
-      expand_string_forcedfail = TRUE;
+      f.expand_string_forcedfail = TRUE;
       goto FAILED;
       }
     }
       goto FAILED;
       }
     }
@@ -3190,23 +3339,30 @@ else if (*s != '}')
 /* All we have to do now is to check on the final closing brace. */
 
 while (isspace(*s)) 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:
 
 RETURN:
+/* Update the input pointer value before returning */
 *sptr = s;
 return rc;
 *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 +3388,7 @@ chash_start(int type, void *base)
 if (type == HMAC_MD5)
   md5_start((md5 *)base);
 else
 if (type == HMAC_MD5)
   md5_start((md5 *)base);
 else
-  sha1_start((sha1 *)base);
+  sha1_start((hctx *)base);
 }
 
 static void
 }
 
 static void
@@ -3241,7 +3397,7 @@ chash_mid(int type, void *base, uschar *string)
 if (type == HMAC_MD5)
   md5_mid((md5 *)base, string);
 else
 if (type == HMAC_MD5)
   md5_mid((md5 *)base, string);
 else
-  sha1_mid((sha1 *)base, string);
+  sha1_mid((hctx *)base, string);
 }
 
 static void
 }
 
 static void
@@ -3250,7 +3406,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
 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);
 }
 
 
 }
 
 
@@ -3307,10 +3463,10 @@ Returns:  pointer to string containing the first three
 static uschar *
 prvs_hmac_sha1(uschar *address, uschar *key, uschar *key_num, uschar *daystamp)
 {
 static uschar *
 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;
+gstring * hash_source;
+uschar * p;
+int i;
+hctx h;
 uschar innerhash[20];
 uschar finalhash[20];
 uschar innerkey[64];
 uschar innerhash[20];
 uschar finalhash[20];
 uschar innerkey[64];
@@ -3323,12 +3479,13 @@ if (key_num == NULL)
 if (Ustrlen(key) > 64)
   return 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[offset] = '\0';
+hash_source = string_catn(NULL, key_num, 1);
+hash_source = string_catn(hash_source, daystamp, 3);
+hash_source = string_cat(hash_source, address);
+(void) string_from_gstring(hash_source);
 
 
-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->s);
 
 memset(innerkey, 0x36, 64);
 memset(outerkey, 0x5c, 64);
 
 memset(innerkey, 0x36, 64);
 memset(outerkey, 0x5c, 64);
@@ -3339,13 +3496,13 @@ for (i = 0; i < Ustrlen(key); i++)
   outerkey[i] ^= 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->s, hash_source->ptr, 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++)
 
 p = finalhash_hex;
 for (i = 0; i < 3; i++)
@@ -3365,43 +3522,57 @@ return finalhash_hex;
 *        Join a file onto the output string      *
 *************************************************/
 
 *        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
 
 Arguments:
   f            the FILE
-  yield        pointer to the expandable string
-  sizep        pointer to the current size
-  ptrp         pointer to the current position
+  yield        pointer to the expandable string struct
   eol          newline replacement string, or NULL
 
   eol          newline replacement string, or NULL
 
-Returns:       new value of string pointer
+Returns:       new pointer for expandable string, terminated if non-null
 */
 
 */
 
-static uschar *
-cat_file(FILE *f, uschar *yield, int *sizep, int *ptrp, uschar *eol)
+static gstring *
+cat_file(FILE *f, gstring *yield, uschar *eol)
 {
 {
-int eollen;
 uschar buffer[1024];
 
 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);
   {
   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, buffer, len);
+  if (eol && buffer[len])
+    yield = string_cat(yield, eol);
   }
 
   }
 
-if (yield != NULL) yield[*ptrp] = 0;
-
+(void) string_from_gstring(yield);
 return yield;
 }
 
 
 return yield;
 }
 
 
+#ifdef SUPPORT_TLS
+static gstring *
+cat_file_tls(void * tls_ctx, gstring * yield, uschar * eol)
+{
+int rc;
+uschar * s;
+uschar buffer[1024];
+
+while ((rc = tls_read(tls_ctx, buffer, sizeof(buffer))) > 0)
+  for (s = buffer; rc--; s++)
+    yield = eol && *s == '\n'
+      ? string_cat(yield, eol) : string_catn(yield, s, 1);
+
+/* We assume that all errors, and any returns of zero bytes,
+are actually EOF. */
+
+(void) string_from_gstring(yield);
+return yield;
+}
+#endif
 
 
 /*************************************************
 
 
 /*************************************************
@@ -3520,7 +3691,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
     /* 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
      * 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 +3741,20 @@ eval_op_sum(uschar **sptr, BOOL decimal, uschar **error)
 {
 uschar *s = *sptr;
 int_eximarith_t x = eval_op_mult(&s, decimal, 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);
   {
   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;
     }
   }
     if (op == '+') x += y; else x -= y;
     }
   }
@@ -3671,27 +3849,108 @@ return x;
 
 
 
 
 
 
-/*************************************************
-*                 Expand string                  *
-*************************************************/
+/* Return pointer to dewrapped string, with enclosing specified chars removed.
+The given string is modified on return.  Leading whitespace is skipped while
+looking for the opening wrap character, then the rest is scanned for the trailing
+(non-escaped) wrap character.  A backslash in the string will act as an escape.
 
 
-/* Returns either an unchanged string, or the expanded string in stacking pool
-store. Interpreted sequences are:
+A nul is written over the trailing wrap, and a pointer to the char after the
+leading wrap is returned.
 
 
-   \...                    normal escaping rules
-   $name                   substitutes the variable
-   ${name}                 ditto
-   ${op:string}            operates on the expanded string value
-   ${item{arg1}{arg2}...}  expands the args and then does the business
-                             some literal args are not enclosed in {}
+Arguments:
+  s    String for de-wrapping
+  wrap  Two-char string, the first being the opener, second the closer wrapping
+        character
+Return:
+  Pointer to de-wrapped string, or NULL on error (with expand_string_message set).
+*/
 
 
-There are now far too many operators and item types to make it worth listing
+static uschar *
+dewrap(uschar * s, const uschar * wrap)
+{
+uschar * p = s;
+unsigned depth = 0;
+BOOL quotesmode = wrap[0] == wrap[1];
+
+while (isspace(*p)) p++;
+
+if (*p == *wrap)
+  {
+  s = ++p;
+  wrap++;
+  while (*p)
+    {
+    if (*p == '\\') p++;
+    else if (!quotesmode && *p == wrap[-1]) depth++;
+    else if (*p == *wrap)
+      if (depth == 0)
+       {
+       *p = '\0';
+       return s;
+       }
+      else
+       depth--;
+    p++;
+    }
+  }
+expand_string_message = string_sprintf("missing '%c'", *wrap);
+return NULL;
+}
+
+
+/* Pull off the leading array or object element, returning
+a copy in an allocated string.  Update the list pointer.
+
+The element may itself be an abject or array.
+*/
+
+uschar *
+json_nextinlist(const uschar ** list)
+{
+unsigned array_depth = 0, object_depth = 0;
+const uschar * s = *list, * item;
+
+while (isspace(*s)) s++;
+
+for (item = s;
+     *s && (*s != ',' || array_depth != 0 || object_depth != 0);
+     s++)
+  switch (*s)
+    {
+    case '[': array_depth++; break;
+    case ']': array_depth--; break;
+    case '{': object_depth++; break;
+    case '}': object_depth--; break;
+    }
+*list = *s ? s+1 : s;
+item = string_copyn(item, s - item);
+DEBUG(D_expand) debug_printf_indent("  json ele: '%s'\n", item);
+return US item;
+}
+
+
+
+/*************************************************
+*                 Expand string                  *
+*************************************************/
+
+/* Returns either an unchanged string, or the expanded string in stacking pool
+store. Interpreted sequences are:
+
+   \...                    normal escaping rules
+   $name                   substitutes the variable
+   ${name}                 ditto
+   ${op:string}            operates on the expanded string value
+   ${item{arg1}{arg2}...}  expands the args and then does the business
+                             some literal args are not enclosed in {}
+
+There are now far too many operators and item types to make it worth listing
 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
 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
 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,19 +3994,29 @@ Returns:         NULL if expansion fails:
 */
 
 static uschar *
 */
 
 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)
 {
   BOOL skipping, BOOL honour_dollar, BOOL *resetok_p)
 {
-int ptr = 0;
-int size = Ustrlen(string)+ 64;
+gstring * yield = string_get(Ustrlen(string) + 64);
 int item_type;
 int item_type;
-uschar *yield = store_get(size);
-uschar *s = string;
+const uschar *s = string;
 uschar *save_expand_nstring[EXPAND_MAXN+1];
 int save_expand_nlength[EXPAND_MAXN+1];
 BOOL resetok = TRUE;
 
 uschar *save_expand_nstring[EXPAND_MAXN+1];
 int save_expand_nlength[EXPAND_MAXN+1];
 BOOL resetok = TRUE;
 
-expand_string_forcedfail = FALSE;
+expand_level++;
+DEBUG(D_expand)
+  DEBUG(D_noutf8)
+    debug_printf_indent("/%s: %s\n",
+      skipping ? "---scanning" : "considering", string);
+  else
+    debug_printf_indent(UTF8_DOWN_RIGHT "%s: %s\n",
+      skipping
+      ? UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ "scanning"
+      : "considering",
+      string);
+
+f.expand_string_forcedfail = FALSE;
 expand_string_message = US"";
 
 while (*s != 0)
 expand_string_message = US"";
 
 while (*s != 0)
@@ -3770,9 +4039,9 @@ while (*s != 0)
 
     if (s[1] == 'N')
       {
 
     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;
       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, t, s - t);
       if (*s != 0) s += 2;
       }
 
       if (*s != 0) s += 2;
       }
 
@@ -3781,7 +4050,7 @@ while (*s != 0)
       uschar ch[1];
       ch[0] = string_interpret_escape(&s);
       s++;
       uschar ch[1];
       ch[0] = string_interpret_escape(&s);
       s++;
-      yield = string_cat(yield, &size, &ptr, ch, 1);
+      yield = string_catn(yield, ch, 1);
       }
 
     continue;
       }
 
     continue;
@@ -3796,7 +4065,7 @@ while (*s != 0)
 
   if (*s != '$' || !honour_dollar)
     {
 
   if (*s != '$' || !honour_dollar)
     {
-    yield = string_cat(yield, &size, &ptr, s++, 1);
+    yield = string_catn(yield, s++, 1);
     continue;
     }
 
     continue;
     }
 
@@ -3812,39 +4081,45 @@ while (*s != 0)
     {
     int len;
     int newsize = 0;
     {
     int len;
     int newsize = 0;
+    gstring * g = NULL;
+    uschar * t;
 
     s = read_name(name, sizeof(name), s, US"_");
 
     /* If this is the first thing to be expanded, release the pre-allocated
     buffer. */
 
 
     s = read_name(name, sizeof(name), s, US"_");
 
     /* If this is the first thing to be expanded, release the pre-allocated
     buffer. */
 
-    if (ptr == 0 && yield != NULL)
+    if (!yield)
+      g = store_get(sizeof(gstring));
+    else if (yield->ptr == 0)
       {
       if (resetok) store_reset(yield);
       yield = NULL;
       {
       if (resetok) store_reset(yield);
       yield = NULL;
-      size = 0;
+      g = store_get(sizeof(gstring));  /* alloc _before_ calling find_variable() */
       }
 
     /* Header */
 
       }
 
     /* Header */
 
-    if (Ustrncmp(name, "h_", 2) == 0 ||
-        Ustrncmp(name, "rh_", 3) == 0 ||
-        Ustrncmp(name, "bh_", 3) == 0 ||
-        Ustrncmp(name, "header_", 7) == 0 ||
-        Ustrncmp(name, "rheader_", 8) == 0 ||
-        Ustrncmp(name, "bheader_", 8) == 0)
+    if (  ( *(t = name) == 'h'
+          || (*t == 'r' || *t == 'l' || *t == 'b') && *++t == 'h'
+         )
+       && (*++t == '_' || Ustrncmp(t, "eader_", 6) == 0)
+       )
       {
       {
-      BOOL want_raw = (name[0] == 'r')? TRUE : FALSE;
-      uschar *charset = (name[0] == 'b')? NULL : headers_charset;
+      unsigned flags = *name == 'r' ? FH_WANT_RAW
+                     : *name == 'l' ? FH_WANT_RAW|FH_WANT_LIST
+                     : 0;
+      uschar * charset = *name == 'b' ? NULL : headers_charset;
+
       s = read_header_name(name, sizeof(name), s);
       s = read_header_name(name, sizeof(name), s);
-      value = find_header(name, FALSE, &newsize, want_raw, charset);
+      value = find_header(name, &newsize, flags, charset);
 
       /* If we didn't find the header, and the header contains a closing brace
       character, this may be a user error where the terminating colon
       has been omitted. Set a flag to adjust the error message in this case.
       But there is no error here - nothing gets inserted. */
 
 
       /* If we didn't find the header, and the header contains a closing brace
       character, this may be a user error where the terminating colon
       has been omitted. Set a flag to adjust the error message in this case.
       But there is no error here - nothing gets inserted. */
 
-      if (value == NULL)
+      if (!value)
         {
         if (Ustrchr(name, '}') != NULL) malformed_header = TRUE;
         continue;
         {
         if (Ustrchr(name, '}') != NULL) malformed_header = TRUE;
         continue;
@@ -3853,32 +4128,32 @@ while (*s != 0)
 
     /* Variable */
 
 
     /* 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
     size of that buffer. If this is the first thing in an expansion string,
     yield will be NULL; just point it at the new store instead of copying. Many
     expansion strings contain just one reference, so this is a useful
       }
 
     /* If the data is known to be in a new buffer, newsize will be set to the
     size of that buffer. If this is the first thing in an expansion string,
     yield will be NULL; just point it at the new store instead of copying. Many
     expansion strings contain just one reference, so this is a useful
-    optimization, especially for humungous headers. */
+    optimization, especially for humungous headers.  We need to use a gstring
+    structure that is not allocated after that new-buffer, else a later store
+    reset in the middle of the buffer will make it inaccessible. */
 
     len = Ustrlen(value);
 
     len = Ustrlen(value);
-    if (yield == NULL && newsize != 0)
+    if (!yield && newsize != 0)
       {
       {
-      yield = value;
-      size = newsize;
-      ptr = len;
+      yield = g;
+      yield->size = newsize;
+      yield->ptr = len;
+      yield->s = value;
       }
       }
-    else yield = string_cat(yield, &size, &ptr, value, len);
+    else
+      yield = string_catn(yield, value, len);
 
     continue;
     }
 
     continue;
     }
@@ -3886,10 +4161,9 @@ while (*s != 0)
   if (isdigit(*s))
     {
     int n;
   if (isdigit(*s))
     {
     int n;
-    s = read_number(&n, s);
+    s = read_cnumber(&n, s);
     if (n >= 0 && n <= expand_nmax)
     if (n >= 0 && n <= expand_nmax)
-      yield = string_cat(yield, &size, &ptr, expand_nstring[n],
-        expand_nlength[n]);
+      yield = string_catn(yield, expand_nstring[n], expand_nlength[n]);
     continue;
     }
 
     continue;
     }
 
@@ -3907,15 +4181,14 @@ while (*s != 0)
   if (isdigit((*(++s))))
     {
     int n;
   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)
     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],
-        expand_nlength[n]);
+      yield = string_catn(yield, expand_nstring[n], expand_nlength[n]);
     continue;
     }
 
     continue;
     }
 
@@ -3930,7 +4203,7 @@ while (*s != 0)
   OK. */
 
   s = read_name(name, sizeof(name), s, US"_-");
   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)
     {
 
   switch(item_type)
     {
@@ -3949,7 +4222,8 @@ while (*s != 0)
       uschar *sub[10]; /* name + arg1-arg9 (which must match number of acl_arg[]) */
       uschar *user_msg;
 
       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:
         {
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:
@@ -3958,24 +4232,60 @@ while (*s != 0)
       if (skipping) continue;
 
       resetok = FALSE;
       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)
        {
        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)
          if (user_msg)
-            yield = string_cat(yield, &size, &ptr, user_msg, Ustrlen(user_msg));
+            yield = string_cat(yield, user_msg);
          continue;
 
        case DEFER:
          continue;
 
        case DEFER:
-          expand_string_forcedfail = TRUE;
+          f.expand_string_forcedfail = TRUE;
+         /*FALLTHROUGH*/
        default:
           expand_string_message = string_sprintf("error from acl \"%s\"", sub[0]);
          goto EXPAND_FAILED;
        }
       }
 
        default:
           expand_string_message = string_sprintf("error from acl \"%s\"", sub[0]);
          goto EXPAND_FAILED;
        }
       }
 
+    case EITEM_AUTHRESULTS:
+      /* ${authresults {mysystemname}} */
+      {
+      uschar *sub_arg[1];
+
+      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;
+        }
+
+      yield = string_append(yield, 3,
+                       US"Authentication-Results: ", sub_arg[0], US"; none");
+      yield->ptr -= 6;
+
+      yield = authres_local(yield, sub_arg[0]);
+      yield = authres_iprev(yield);
+      yield = authres_smtpauth(yield);
+#ifdef SUPPORT_SPF
+      yield = authres_spf(yield);
+#endif
+#ifndef DISABLE_DKIM
+      yield = authres_dkim(yield);
+#endif
+#ifdef EXPERIMENTAL_DMARC
+      yield = authres_dmarc(yield);
+#endif
+#ifdef EXPERIMENTAL_ARC
+      yield = authres_arc(yield);
+#endif
+      continue;
+      }
+
     /* Handle conditionals - preserve the values of the numerical expansion
     variables in case they get changed by a regular expression match in the
     condition. If not, they retain their external settings. At the end
     /* Handle conditionals - preserve the values of the numerical expansion
     variables in case they get changed by a regular expression match in the
     condition. If not, they retain their external settings. At the end
@@ -3984,17 +4294,30 @@ while (*s != 0)
     case EITEM_IF:
       {
       BOOL cond = FALSE;
     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++;
       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)
       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(D_noutf8)
+         {
+         debug_printf_indent("|--condition: %.*s\n", (int)(next_s - s), s);
+         debug_printf_indent("|-----result: %s\n", cond ? "true" : "false");
+         }
+       else
+         {
+         debug_printf_indent(UTF8_VERT_RIGHT UTF8_HORIZ UTF8_HORIZ
+           "condition: %.*s\n",
+           (int)(next_s - s), s);
+         debug_printf_indent(UTF8_VERT_RIGHT UTF8_HORIZ UTF8_HORIZ
+           UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ
+           "result: %s\n",
+           cond ? "true" : "false");
+         }
 
       s = next_s;
 
 
       s = next_s;
 
@@ -4007,8 +4330,6 @@ while (*s != 0)
                lookup_value,                 /* value to reset for string2 */
                &s,                           /* input pointer */
                &yield,                       /* output pointer */
                lookup_value,                 /* value to reset for string2 */
                &s,                           /* input pointer */
                &yield,                       /* output pointer */
-               &size,                        /* output size */
-               &ptr,                         /* output current point */
                US"if",                       /* condition type */
               &resetok))
         {
                US"if",                       /* condition type */
               &resetok))
         {
@@ -4024,6 +4345,45 @@ while (*s != 0)
       continue;
       }
 
       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, 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
     /* 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 +4396,8 @@ while (*s != 0)
       int stype, partial, affixlen, starflags;
       int expand_setup = 0;
       int nameptr = 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);
       uschar *save_lookup_value = lookup_value;
       int save_expand_nmax =
         save_expand_strings(save_expand_nstring, save_expand_nlength);
@@ -4054,8 +4415,12 @@ while (*s != 0)
       if (*s == '{')                                   /*}*/
         {
         key = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok);
       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;
         while (isspace(*s)) s++;
         }
       else key = NULL;
@@ -4118,10 +4483,18 @@ while (*s != 0)
       queries that also require a file name (e.g. sqlite), the file name comes
       first. */
 
       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;
       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
       while (isspace(*s)) s++;
 
       /* If this isn't a single-key+file lookup, re-arrange the variables
@@ -4129,15 +4502,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. */
 
       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))
         {
         while (isspace(*filename)) filename++;
         key = filename;
 
         if (mac_islookup(stype, lookup_querystyle))
-          {
           filename = NULL;
           filename = NULL;
-          }
         else
           {
           if (*filename != '/')
         else
           {
           if (*filename != '/')
@@ -4176,7 +4547,7 @@ while (*s != 0)
           }
         lookup_value = search_find(handle, filename, key, partial, affix,
           affixlen, starflags, &expand_setup);
           }
         lookup_value = search_find(handle, filename, key, partial, affix,
           affixlen, starflags, &expand_setup);
-        if (search_find_defer)
+        if (f.search_find_defer)
           {
           expand_string_message =
             string_sprintf("lookup of \"%s\" gave DEFER: %s",
           {
           expand_string_message =
             string_sprintf("lookup of \"%s\" gave DEFER: %s",
@@ -4195,8 +4566,6 @@ while (*s != 0)
                save_lookup_value,            /* value to reset for string2 */
                &s,                           /* input pointer */
                &yield,                       /* output pointer */
                save_lookup_value,            /* value to reset for string2 */
                &s,                           /* input pointer */
                &yield,                       /* output pointer */
-               &size,                        /* output size */
-               &ptr,                         /* output current point */
                US"lookup",                   /* condition type */
               &resetok))
         {
                US"lookup",                   /* condition type */
               &resetok))
         {
@@ -4228,7 +4597,7 @@ while (*s != 0)
     #else   /* EXIM_PERL */
       {
       uschar *sub_arg[EXIM_PERL_MAX_ARGS + 2];
     #else   /* EXIM_PERL */
       {
       uschar *sub_arg[EXIM_PERL_MAX_ARGS + 2];
-      uschar *new_yield;
+      gstring *new_yield;
 
       if ((expand_forbid & RDO_PERL) != 0)
         {
 
       if ((expand_forbid & RDO_PERL) != 0)
         {
@@ -4273,7 +4642,7 @@ while (*s != 0)
       /* Call the function */
 
       sub_arg[EXIM_PERL_MAX_ARGS + 1] = NULL;
       /* Call the function */
 
       sub_arg[EXIM_PERL_MAX_ARGS + 1] = NULL;
-      new_yield = call_perl_cat(yield, &size, &ptr, &expand_string_message,
+      new_yield = call_perl_cat(yield, &expand_string_message,
         sub_arg[0], sub_arg + 1);
 
       /* NULL yield indicates failure; if the message pointer has been set to
         sub_arg[0], sub_arg + 1);
 
       /* NULL yield indicates failure; if the message pointer has been set to
@@ -4287,7 +4656,7 @@ while (*s != 0)
           expand_string_message =
             string_sprintf("Perl subroutine \"%s\" returned undef to force "
               "failure", sub_arg[0]);
           expand_string_message =
             string_sprintf("Perl subroutine \"%s\" returned undef to force "
               "failure", sub_arg[0]);
-          expand_string_forcedfail = TRUE;
+          f.expand_string_forcedfail = TRUE;
           }
         goto EXPAND_FAILED;
         }
           }
         goto EXPAND_FAILED;
         }
@@ -4295,7 +4664,7 @@ while (*s != 0)
       /* Yield succeeded. Ensure forcedfail is unset, just in case it got
       set during a callback from Perl. */
 
       /* Yield succeeded. Ensure forcedfail is unset, just in case it got
       set during a callback from Perl. */
 
-      expand_string_forcedfail = FALSE;
+      f.expand_string_forcedfail = FALSE;
       yield = new_yield;
       continue;
       }
       yield = new_yield;
       continue;
       }
@@ -4320,25 +4689,25 @@ while (*s != 0)
       if (skipping) continue;
 
       /* sub_arg[0] is the address */
       if (skipping) continue;
 
       /* sub_arg[0] is the address */
-      domain = Ustrrchr(sub_arg[0],'@');
-      if ( (domain == NULL) || (domain == sub_arg[0]) || (Ustrlen(domain) == 1) )
+      if (  !(domain = Ustrrchr(sub_arg[0],'@'))
+        || domain == sub_arg[0] || Ustrlen(domain) == 1)
         {
         expand_string_message = US"prvs first argument must be a qualified email address";
         goto EXPAND_FAILED;
         }
 
         {
         expand_string_message = US"prvs first argument must be a qualified email address";
         goto EXPAND_FAILED;
         }
 
-      /* Calculate the hash. The second argument must be a single-digit
+      /* Calculate the hash. The third argument must be a single-digit
       key number, or unset. */
 
       key number, or unset. */
 
-      if (sub_arg[2] != NULL &&
-          (!isdigit(sub_arg[2][0]) || sub_arg[2][1] != 0))
+      if (  sub_arg[2]
+         && (!isdigit(sub_arg[2][0]) || sub_arg[2][1] != 0))
         {
         {
-        expand_string_message = US"prvs second argument must be a single digit";
+        expand_string_message = US"prvs third argument must be a single digit";
         goto EXPAND_FAILED;
         }
 
         goto EXPAND_FAILED;
         }
 
-      p = prvs_hmac_sha1(sub_arg[0],sub_arg[1],sub_arg[2],prvs_daystamp(7));
-      if (p == NULL)
+      p = prvs_hmac_sha1(sub_arg[0], sub_arg[1], sub_arg[2], prvs_daystamp(7));
+      if (!p)
         {
         expand_string_message = US"prvs hmac-sha1 conversion failed";
         goto EXPAND_FAILED;
         {
         expand_string_message = US"prvs hmac-sha1 conversion failed";
         goto EXPAND_FAILED;
@@ -4347,14 +4716,14 @@ while (*s != 0)
       /* Now separate the domain from the local part */
       *domain++ = '\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, US"prvs=", 5);
+      yield = string_catn(yield, sub_arg[2] ? sub_arg[2] : US"0", 1);
+      yield = string_catn(yield, prvs_daystamp(7), 3);
+      yield = string_catn(yield, p, 6);
+      yield = string_catn(yield, US"=", 1);
+      yield = string_cat (yield, sub_arg[0]);
+      yield = string_catn(yield, US"@", 1);
+      yield = string_cat (yield, domain);
 
       continue;
       }
 
       continue;
       }
@@ -4364,7 +4733,7 @@ while (*s != 0)
     case EITEM_PRVSCHECK:
       {
       uschar *sub_arg[3];
     case EITEM_PRVSCHECK:
       {
       uschar *sub_arg[3];
-      int mysize = 0, myptr = 0;
+      gstring * g;
       const pcre *re;
       uschar *p;
 
       const pcre *re;
       uschar *p;
 
@@ -4401,17 +4770,17 @@ while (*s != 0)
         uschar *hash = string_copyn(expand_nstring[3],expand_nlength[3]);
         uschar *domain = string_copyn(expand_nstring[5],expand_nlength[5]);
 
         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 */
 
         /* 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[myptr] = '\0';
+        g = string_cat (NULL, local_part);
+        g = string_catn(g, US"@", 1);
+        g = string_cat (g, domain);
+        prvscheck_address = string_from_gstring(g);
         prvscheck_keynum = string_copy(key_num);
 
         /* Now expand the second argument */
         prvscheck_keynum = string_copy(key_num);
 
         /* Now expand the second argument */
@@ -4427,14 +4796,14 @@ while (*s != 0)
         p = prvs_hmac_sha1(prvscheck_address, sub_arg[0], prvscheck_keynum,
           daystamp);
 
         p = prvs_hmac_sha1(prvscheck_address, sub_arg[0], prvscheck_keynum,
           daystamp);
 
-        if (p == NULL)
+        if (!p)
           {
           expand_string_message = US"hmac-sha1 conversion failed";
           goto EXPAND_FAILED;
           }
 
           {
           expand_string_message = US"hmac-sha1 conversion failed";
           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)
           {
 
         if (Ustrcmp(p,hash) == 0)
           {
@@ -4445,25 +4814,25 @@ while (*s != 0)
           (void)sscanf(CS now,"%u",&inow);
           (void)sscanf(CS daystamp,"%u",&iexpire);
 
           (void)sscanf(CS now,"%u",&inow);
           (void)sscanf(CS daystamp,"%u",&iexpire);
 
-          /* When "iexpire" is < 7, a "flip" has occured.
+          /* When "iexpire" is < 7, a "flip" has occurred.
              Adjust "inow" accordingly. */
           if ( (iexpire < 7) && (inow >= 993) ) inow = 0;
 
           if (iexpire >= inow)
             {
             prvscheck_result = US"1";
              Adjust "inow" accordingly. */
           if ( (iexpire < 7) && (inow >= 993) ) inow = 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
+         else
             {
             prvscheck_result = NULL;
             {
             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;
             }
           }
         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
           }
 
         /* Now expand the final argument. We leave this till now so that
@@ -4476,10 +4845,8 @@ while (*s != 0)
           case 3: goto EXPAND_FAILED;
           }
 
           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,
+         !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. */
 
         /* Reset the "internal" variables afterwards, because they are in
         dynamic store that will be reclaimed if the expansion succeeded. */
@@ -4488,7 +4855,6 @@ while (*s != 0)
         prvscheck_keynum = NULL;
         }
       else
         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. */
         /* 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 +4865,6 @@ while (*s != 0)
           case 2:
           case 3: goto EXPAND_FAILED;
           }
           case 2:
           case 3: goto EXPAND_FAILED;
           }
-        }
 
       continue;
       }
 
       continue;
       }
@@ -4530,31 +4895,36 @@ while (*s != 0)
 
       /* Open the file and read it */
 
 
       /* Open the file and read it */
 
-      f = Ufopen(sub_arg[0], "rb");
-      if (f == NULL)
+      if (!(f = Ufopen(sub_arg[0], "rb")))
         {
         expand_string_message = string_open_failed(errno, "%s", sub_arg[0]);
         goto EXPAND_FAILED;
         }
 
         {
         expand_string_message = string_open_failed(errno, "%s", sub_arg[0]);
         goto EXPAND_FAILED;
         }
 
-      yield = cat_file(f, yield, &size, &ptr, sub_arg[1]);
+      yield = cat_file(f, yield, sub_arg[1]);
       (void)fclose(f);
       continue;
       }
 
       (void)fclose(f);
       continue;
       }
 
-    /* Handle "readsocket" to insert data from a Unix domain socket */
+    /* Handle "readsocket" to insert data from a socket, either
+    Inet or Unix domain */
 
     case EITEM_READSOCK:
       {
       int fd;
       int timeout = 5;
 
     case EITEM_READSOCK:
       {
       int fd;
       int timeout = 5;
-      int save_ptr = ptr;
-      FILE *f;
-      struct sockaddr_un sockun;         /* don't call this "sun" ! */
-      uschar *arg;
-      uschar *sub_arg[4];
-
-      if ((expand_forbid & RDO_READSOCK) != 0)
+      int save_ptr = yield->ptr;
+      FILE * fp;
+      uschar * arg;
+      uschar * sub_arg[4];
+      uschar * server_name = NULL;
+      host_item host;
+      BOOL do_shutdown = TRUE;
+      BOOL do_tls = FALSE;     /* Only set under SUPPORT_TLS */
+      void * tls_ctx = NULL;   /* ditto                      */
+      blob reqstr;
+
+      if (expand_forbid & RDO_READSOCK)
         {
         expand_string_message = US"socket insertions are not permitted";
         goto EXPAND_FAILED;
         {
         expand_string_message = US"socket insertions are not permitted";
         goto EXPAND_FAILED;
@@ -4570,19 +4940,38 @@ while (*s != 0)
         case 3: goto EXPAND_FAILED;
         }
 
         case 3: goto EXPAND_FAILED;
         }
 
-      /* Sort out timeout, if given */
+      /* Grab the request string, if any */
+
+      reqstr.data = sub_arg[1];
+      reqstr.len = Ustrlen(sub_arg[1]);
+
+      /* Sort out timeout, if given.  The second arg is a list with the first element
+      being a time value.  Any more are options of form "name=value".  Currently the
+      only option recognised is "shutdown". */
 
 
-      if (sub_arg[2] != NULL)
+      if (sub_arg[2])
         {
         {
-        timeout = readconf_readtime(sub_arg[2], 0, FALSE);
-        if (timeout < 0)
+       const uschar * list = sub_arg[2];
+       uschar * item;
+       int sep = 0;
+
+       item = string_nextinlist(&list, &sep, NULL, 0);
+        if ((timeout = readconf_readtime(item, 0, FALSE)) < 0)
           {
           {
-          expand_string_message = string_sprintf("bad time value %s",
-            sub_arg[2]);
+          expand_string_message = string_sprintf("bad time value %s", item);
           goto EXPAND_FAILED;
           }
           goto EXPAND_FAILED;
           }
+
+       while ((item = string_nextinlist(&list, &sep, NULL, 0)))
+         if (Ustrncmp(item, US"shutdown=", 9) == 0)
+           { if (Ustrcmp(item + 9, US"no") == 0) do_shutdown = FALSE; }
+#ifdef SUPPORT_TLS
+         else if (Ustrncmp(item, US"tls=", 4) == 0)
+           { if (Ustrcmp(item + 9, US"no") != 0) do_tls = TRUE; }
+#endif
         }
         }
-      else sub_arg[3] = NULL;                     /* No eol if no timeout */
+      else
+       sub_arg[3] = NULL;                     /* No eol if no timeout */
 
       /* If skipping, we don't actually do anything. Otherwise, arrange to
       connect to either an IP or a Unix socket. */
 
       /* If skipping, we don't actually do anything. Otherwise, arrange to
       connect to either an IP or a Unix socket. */
@@ -4594,12 +4983,14 @@ while (*s != 0)
         if (Ustrncmp(sub_arg[0], "inet:", 5) == 0)
           {
           int port;
         if (Ustrncmp(sub_arg[0], "inet:", 5) == 0)
           {
           int port;
-          uschar *server_name = sub_arg[0] + 5;
-          uschar *port_name = Ustrrchr(server_name, ':');
+          uschar * port_name;
+
+          server_name = sub_arg[0] + 5;
+          port_name = Ustrrchr(server_name, ':');
 
           /* Sort out the port */
 
 
           /* Sort out the port */
 
-          if (port_name == NULL)
+          if (!port_name)
             {
             expand_string_message =
               string_sprintf("missing port for readsocket %s", sub_arg[0]);
             {
             expand_string_message =
               string_sprintf("missing port for readsocket %s", sub_arg[0]);
@@ -4621,7 +5012,7 @@ while (*s != 0)
           else
             {
             struct servent *service_info = getservbyname(CS port_name, "tcp");
           else
             {
             struct servent *service_info = getservbyname(CS port_name, "tcp");
-            if (service_info == NULL)
+            if (!service_info)
               {
               expand_string_message = string_sprintf("unknown port \"%s\"",
                 port_name);
               {
               expand_string_message = string_sprintf("unknown port \"%s\"",
                 port_name);
@@ -4630,16 +5021,24 @@ while (*s != 0)
             port = ntohs(service_info->s_port);
             }
 
             port = ntohs(service_info->s_port);
             }
 
-         if ((fd = ip_connectedsocket(SOCK_STREAM, server_name, port, port,
-                 timeout, NULL, &expand_string_message)) < 0)
-              goto SOCK_FAIL;
+         /*XXX we trust that the request is idempotent.  Hmm. */
+         fd = ip_connectedsocket(SOCK_STREAM, server_name, port, port,
+                 timeout, &host, &expand_string_message,
+                 do_tls ? NULL : &reqstr);
+         callout_address = NULL;
+         if (fd < 0)
+           goto SOCK_FAIL;
+         if (!do_tls)
+           reqstr.len = 0;
           }
 
         /* Handle a Unix domain socket */
 
         else
           {
           }
 
         /* Handle a Unix domain socket */
 
         else
           {
+         struct sockaddr_un sockun;         /* don't call this "sun" ! */
           int rc;
           int rc;
+
           if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) == -1)
             {
             expand_string_message = string_sprintf("failed to create socket: %s",
           if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) == -1)
             {
             expand_string_message = string_sprintf("failed to create socket: %s",
@@ -4650,11 +5049,12 @@ while (*s != 0)
           sockun.sun_family = AF_UNIX;
           sprintf(sockun.sun_path, "%.*s", (int)(sizeof(sockun.sun_path)-1),
             sub_arg[0]);
           sockun.sun_family = AF_UNIX;
           sprintf(sockun.sun_path, "%.*s", (int)(sizeof(sockun.sun_path)-1),
             sub_arg[0]);
+         server_name = US sockun.sun_path;
 
           sigalrm_seen = FALSE;
 
           sigalrm_seen = FALSE;
-          alarm(timeout);
+          ALARM(timeout);
           rc = connect(fd, (struct sockaddr *)(&sockun), sizeof(sockun));
           rc = connect(fd, (struct sockaddr *)(&sockun), sizeof(sockun));
-          alarm(0);
+          ALARM_CLR(0);
           if (sigalrm_seen)
             {
             expand_string_message = US "socket connect timed out";
           if (sigalrm_seen)
             {
             expand_string_message = US "socket connect timed out";
@@ -4666,21 +5066,44 @@ while (*s != 0)
               "%s: %s", sub_arg[0], strerror(errno));
             goto SOCK_FAIL;
             }
               "%s: %s", sub_arg[0], strerror(errno));
             goto SOCK_FAIL;
             }
+         host.name = server_name;
+         host.address = US"";
           }
 
           }
 
-        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]);
+
+#ifdef SUPPORT_TLS
+       if (do_tls)
+         {
+         tls_support tls_dummy = {.sni=NULL};
+         uschar * errstr;
+
+         if (!(tls_ctx = tls_client_start(fd, &host, NULL, NULL,
+# ifdef SUPPORT_DANE
+                               NULL,
+# endif
+                               &tls_dummy, &errstr)))
+           {
+           expand_string_message = string_sprintf("TLS connect failed: %s", errstr);
+           goto SOCK_FAIL;
+           }
+         }
+#endif
 
        /* Allow sequencing of test actions */
 
        /* Allow sequencing of test actions */
-       if (running_in_test_harness) millisleep(100);
+       if (f.running_in_test_harness) millisleep(100);
 
 
-        /* Write the request string, if not empty */
+        /* Write the request string, if not empty or already done */
 
 
-        if (sub_arg[1][0] != 0)
+        if (reqstr.len)
           {
           {
-          int len = Ustrlen(sub_arg[1]);
-          DEBUG(D_expand) debug_printf("writing \"%s\" to socket\n",
-            sub_arg[1]);
-          if (write(fd, sub_arg[1], len) != len)
+          DEBUG(D_expand) debug_printf_indent("writing \"%s\" to socket\n",
+            reqstr.data);
+          if ( (
+#ifdef SUPPORT_TLS
+             tls_ctx ? tls_write(tls_ctx, reqstr.data, reqstr.len, FALSE) :
+#endif
+                       write(fd, reqstr.data, reqstr.len)) != reqstr.len)
             {
             expand_string_message = string_sprintf("request write to socket "
               "failed: %s", strerror(errno));
             {
             expand_string_message = string_sprintf("request write to socket "
               "failed: %s", strerror(errno));
@@ -4692,28 +5115,42 @@ while (*s != 0)
         recognise that it is their turn to do some work. Just in case some
         system doesn't have this function, make it conditional. */
 
         recognise that it is their turn to do some work. Just in case some
         system doesn't have this function, make it conditional. */
 
-        #ifdef SHUT_WR
-        shutdown(fd, SHUT_WR);
-        #endif
+#ifdef SHUT_WR
+       if (!tls_ctx && do_shutdown) shutdown(fd, SHUT_WR);
+#endif
 
 
-       if (running_in_test_harness) millisleep(100);
+       if (f.running_in_test_harness) millisleep(100);
 
         /* Now we need to read from the socket, under a timeout. The function
         that reads a file can be used. */
 
 
         /* Now we need to read from the socket, under a timeout. The function
         that reads a file can be used. */
 
-        f = fdopen(fd, "rb");
+       if (!tls_ctx)
+         fp = fdopen(fd, "rb");
         sigalrm_seen = FALSE;
         sigalrm_seen = FALSE;
-        alarm(timeout);
-        yield = cat_file(f, yield, &size, &ptr, sub_arg[3]);
-        alarm(0);
-        (void)fclose(f);
+        ALARM(timeout);
+        yield =
+#ifdef SUPPORT_TLS
+         tls_ctx ? cat_file_tls(tls_ctx, yield, sub_arg[3]) :
+#endif
+                   cat_file(fp, yield, sub_arg[3]);
+        ALARM_CLR(0);
+
+#ifdef SUPPORT_TLS
+       if (tls_ctx)
+         {
+         tls_close(tls_ctx, TRUE);
+         close(fd);
+         }
+       else
+#endif
+         (void)fclose(fp);
 
         /* After a timeout, we restore the pointer in the result, that is,
         make sure we add nothing from the socket. */
 
         if (sigalrm_seen)
           {
 
         /* After a timeout, we restore the pointer in the result, that is,
         make sure we add nothing from the socket. */
 
         if (sigalrm_seen)
           {
-          ptr = save_ptr;
+          yield->ptr = save_ptr;
           expand_string_message = US "socket read timed out";
           goto SOCK_FAIL;
           }
           expand_string_message = US "socket read timed out";
           goto SOCK_FAIL;
           }
@@ -4726,26 +5163,39 @@ while (*s != 0)
         {
         if (expand_string_internal(s+1, TRUE, &s, TRUE, TRUE, &resetok) == NULL)
           goto EXPAND_FAILED;
         {
         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++;
         }
         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
       socket, or timeout on reading. If another substring follows, expand and
       use it. Otherwise, those conditions give expand errors. */
 
       continue;
 
       /* Come here on failure to create socket, connect socket, write to the
       socket, or timeout on reading. If another substring follows, expand and
       use it. Otherwise, those conditions give expand errors. */
 
-      SOCK_FAIL:
+    SOCK_FAIL:
       if (*s != '{') goto EXPAND_FAILED;
       DEBUG(D_any) debug_printf("%s\n", expand_string_message);
       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, arg);
+      if (*s++ != '}')
+        {
+       expand_string_message = US"missing '}' closing failstring for readsocket";
+       goto EXPAND_FAILED_CURLY;
+       }
       while (isspace(*s)) s++;
       while (isspace(*s)) s++;
-      if (*s++ != '}') goto EXPAND_FAILED_CURLY;
-      continue;
+      goto READSOCK_DONE;
       }
 
     /* Handle "run" to execute a program. */
       }
 
     /* Handle "run" to execute a program. */
@@ -4754,11 +5204,9 @@ while (*s != 0)
       {
       FILE *f;
       uschar *arg;
       {
       FILE *f;
       uschar *arg;
-      uschar **argv;
+      const uschar **argv;
       pid_t pid;
       int fd_in, fd_out;
       pid_t pid;
       int fd_in, fd_out;
-      int lsize = 0;
-      int lptr = 0;
 
       if ((expand_forbid & RDO_RUN) != 0)
         {
 
       if ((expand_forbid & RDO_RUN) != 0)
         {
@@ -4767,16 +5215,25 @@ while (*s != 0)
         }
 
       while (isspace(*s)) s++;
         }
 
       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++;
       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 */
 
       if (skipping)   /* Just pretend it worked when we're skipping */
-        {
+       {
         runrc = 0;
         runrc = 0;
-        }
+       lookup_value = NULL;
+       }
       else
         {
         if (!transport_set_up_command(&argv,    /* anchor for arg list */
       else
         {
         if (!transport_set_up_command(&argv,    /* anchor for arg list */
@@ -4786,15 +5243,11 @@ while (*s != 0)
             NULL,                               /* no transporting address */
             US"${run} expansion",               /* for error messages */
             &expand_string_message))            /* where to put error message */
             NULL,                               /* no transporting address */
             US"${run} expansion",               /* for error messages */
             &expand_string_message))            /* where to put error message */
-          {
           goto EXPAND_FAILED;
           goto EXPAND_FAILED;
-          }
 
         /* Create the child process, making it a group leader. */
 
 
         /* 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));
           {
           expand_string_message =
             string_sprintf("couldn't create child process: %s", strerror(errno));
@@ -4807,22 +5260,24 @@ 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
 
         /* 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;
         f = fdopen(fd_out, "rb");
         sigalrm_seen = FALSE;
-        alarm(60);
-        lookup_value = cat_file(f, lookup_value, &lsize, &lptr, NULL);
-        alarm(0);
+        ALARM(60);
+       lookup_value = string_from_gstring(cat_file(f, NULL, NULL));
+        ALARM_CLR(0);
         (void)fclose(f);
 
         /* Wait for the process to finish, applying the timeout, and inspect its
         return code for serious disasters. Simple non-zero returns are passed on.
         */
 
         (void)fclose(f);
 
         /* Wait for the process to finish, applying the timeout, and inspect its
         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 */
             {
             expand_string_message = string_sprintf("command timed out");
             killpg(pid, SIGKILL);       /* Kill the whole process group */
@@ -4848,8 +5303,6 @@ while (*s != 0)
                lookup_value,                 /* value to reset for string2 */
                &s,                           /* input pointer */
                &yield,                       /* output pointer */
                lookup_value,                 /* value to reset for string2 */
                &s,                           /* input pointer */
                &yield,                       /* output pointer */
-               &size,                        /* output size */
-               &ptr,                         /* output current point */
                US"run",                      /* condition type */
               &resetok))
         {
                US"run",                      /* condition type */
               &resetok))
         {
@@ -4864,7 +5317,7 @@ while (*s != 0)
 
     case EITEM_TR:
       {
 
     case EITEM_TR:
       {
-      int oldptr = ptr;
+      int oldptr = yield->ptr;
       int o2m;
       uschar *sub[3];
 
       int o2m;
       uschar *sub[3];
 
@@ -4875,16 +5328,16 @@ while (*s != 0)
         case 3: goto EXPAND_FAILED;
         }
 
         case 3: goto EXPAND_FAILED;
         }
 
-      yield = string_cat(yield, &size, &ptr, sub[0], Ustrlen(sub[0]));
+      yield = string_cat(yield, sub[0]);
       o2m = Ustrlen(sub[2]) - 1;
 
       o2m = Ustrlen(sub[2]) - 1;
 
-      if (o2m >= 0) for (; oldptr < ptr; oldptr++)
+      if (o2m >= 0) for (; oldptr < yield->ptr; oldptr++)
         {
         {
-        uschar *m = Ustrrchr(sub[1], yield[oldptr]);
+        uschar *m = Ustrrchr(sub[1], yield->s[oldptr]);
         if (m != NULL)
           {
           int o = m - sub[1];
         if (m != NULL)
           {
           int o = m - sub[1];
-          yield[oldptr] = sub[2][(o < o2m)? o : o2m];
+          yield->s[oldptr] = sub[2][(o < o2m)? o : o2m];
           }
         }
 
           }
         }
 
@@ -4952,7 +5405,7 @@ while (*s != 0)
           extract_substr(sub[2], val[0], val[1], &len);
 
       if (ret == NULL) goto EXPAND_FAILED;
           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, ret, len);
       continue;
       }
 
       continue;
       }
 
@@ -4970,7 +5423,7 @@ while (*s != 0)
       {
       uschar *sub[3];
       md5 md5_base;
       {
       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 */
       void *use_base;
       int type, i;
       int hashlen;      /* Number of octets for the hash algorithm's output */
@@ -4992,79 +5445,81 @@ while (*s != 0)
         case 3: goto EXPAND_FAILED;
         }
 
         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, 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. */
 
     /* 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 +5567,7 @@ while (*s != 0)
         {
         int ovector[3*(EXPAND_MAXN+1)];
         int n = pcre_exec(re, NULL, CS subject, slen, moffset + moffsetextra,
         {
         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;
 
         int nn;
         uschar *insert;
 
@@ -5131,7 +5586,7 @@ while (*s != 0)
             emptyopt = 0;
             continue;
             }
             emptyopt = 0;
             continue;
             }
-          yield = string_cat(yield, &size, &ptr, subject+moffset, slen-moffset);
+          yield = string_catn(yield, subject+moffset, slen-moffset);
           break;
           }
 
           break;
           }
 
@@ -5148,11 +5603,10 @@ while (*s != 0)
 
         /* Copy the characters before the match, plus the expanded insertion. */
 
 
         /* Copy the characters before the match, plus the expanded insertion. */
 
-        yield = string_cat(yield, &size, &ptr, subject + moffset,
-          ovector[0] - moffset);
+        yield = string_catn(yield, subject + moffset, ovector[0] - moffset);
         insert = expand_string(sub[2]);
         if (insert == NULL) goto EXPAND_FAILED;
         insert = expand_string(sub[2]);
         if (insert == NULL) goto EXPAND_FAILED;
-        yield = string_cat(yield, &size, &ptr, insert, Ustrlen(insert));
+        yield = string_cat(yield, insert);
 
         moffset = ovector[1];
         moffsetextra = 0;
 
         moffset = ovector[1];
         moffsetextra = 0;
@@ -5185,29 +5639,73 @@ while (*s != 0)
     case EITEM_EXTRACT:
       {
       int i;
     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;
       uschar *sub[3];
       int save_expand_nmax =
         save_expand_strings(save_expand_nstring, save_expand_nlength);
       int field_number = 1;
       BOOL field_number_set = FALSE;
       uschar *save_lookup_value = lookup_value;
       uschar *sub[3];
       int save_expand_nmax =
         save_expand_strings(save_expand_nstring, save_expand_nlength);
+      enum {extract_basic, extract_json} fmt = extract_basic;
+
+      while (isspace(*s)) s++;
+
+      /* Check for a format-variant specifier */
+
+      if (*s != '{')                                   /*}*/
+       {
+       if (Ustrncmp(s, "json", 4) == 0) {fmt = extract_json; s += 4;}
+       }
 
 
-      /* 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)
+       {
+        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 == '{')                                                 /*}*/
+       while (isspace(*s)) s++;
+        if (*s == '{')                                                 /*'}'*/
           {
           sub[i] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok);
           {
           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 (sub[i] == NULL) goto EXPAND_FAILED;              /*'{'*/
+          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
           (optionally preceded by a minus sign), this is a numerical
 
           /* After removal of leading and trailing white space, the first
           argument must not be empty; if it consists entirely of digits
           (optionally preceded by a minus sign), this is a numerical
-          extraction, and we expect 3 arguments. */
+          extraction, and we expect 3 arguments (normal) or 2 (json). */
 
           if (i == 0)
             {
 
           if (i == 0)
             {
@@ -5222,36 +5720,115 @@ while (*s != 0)
             while (len > 0 && isspace(p[len-1])) len--;
             p[len] = 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;
+             if (fmt != extract_json) 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
       skipping, just pretend the extraction failed. */
 
         }
 
       /* Extract either the numbered or the keyed substring into $value. If
       skipping, just pretend the extraction failed. */
 
-      lookup_value = skipping? NULL : field_number_set?
-        expand_gettokened(field_number, sub[1], sub[2]) :
-        expand_getkeyed(sub[0], sub[1]);
+      if (skipping)
+       lookup_value = NULL;
+      else switch (fmt)
+       {
+       case extract_basic:
+         lookup_value = field_number_set
+           ? expand_gettokened(field_number, sub[1], sub[2])
+           : expand_getkeyed(sub[0], sub[1]);
+         break;
+
+       case extract_json:
+         {
+         uschar * s, * item;
+         const uschar * list;
+
+         /* Array: Bracket-enclosed and comma-separated.
+         Object: Brace-enclosed, comma-sep list of name:value pairs */
+
+         if (!(s = dewrap(sub[1], field_number_set ? US"[]" : US"{}")))
+           {
+           expand_string_message =
+             string_sprintf("%s wrapping %s for extract json",
+               expand_string_message,
+               field_number_set ? "array" : "object");
+           goto EXPAND_FAILED_CURLY;
+           }
+
+         list = s;
+         if (field_number_set)
+           {
+           if (field_number <= 0)
+             {
+             expand_string_message = US"first argument of \"extract\" must "
+               "be greater than zero";
+             goto EXPAND_FAILED;
+             }
+           while (field_number > 0 && (item = json_nextinlist(&list)))
+             field_number--;
+           s = item;
+           lookup_value = s;
+           while (*s) s++;
+           while (--s >= lookup_value && isspace(*s)) *s = '\0';
+           }
+         else
+           {
+           lookup_value = NULL;
+           while ((item = json_nextinlist(&list)))
+             {
+             /* Item is:  string name-sep value.  string is quoted.
+             Dequote the string and compare with the search key. */
+
+             if (!(item = dewrap(item, US"\"\"")))
+               {
+               expand_string_message =
+                 string_sprintf("%s wrapping string key for extract json",
+                   expand_string_message);
+               goto EXPAND_FAILED_CURLY;
+               }
+             if (Ustrcmp(item, sub[0]) == 0)   /*XXX should be a UTF8-compare */
+               {
+               s = item + Ustrlen(item) + 1;
+               while (isspace(*s)) s++;
+               if (*s != ':')
+                 {
+                 expand_string_message = string_sprintf(
+                   "missing object value-separator for extract json");
+                 goto EXPAND_FAILED_CURLY;
+                 }
+               s++;
+               while (isspace(*s)) s++;
+               lookup_value = s;
+               break;
+               }
+             }
+           }
+         }
+       }
 
       /* If no string follows, $value gets substituted; otherwise there can
       be yes/no strings, as for lookup or if. */
 
       /* If no string follows, $value gets substituted; otherwise there can
       be yes/no strings, as for lookup or if. */
@@ -5262,8 +5839,6 @@ while (*s != 0)
                save_lookup_value,            /* value to reset for string2 */
                &s,                           /* input pointer */
                &yield,                       /* output pointer */
                save_lookup_value,            /* value to reset for string2 */
                &s,                           /* input pointer */
                &yield,                       /* output pointer */
-               &size,                        /* output size */
-               &ptr,                         /* output current point */
                US"extract",                  /* condition type */
               &resetok))
         {
                US"extract",                  /* condition type */
               &resetok))
         {
@@ -5296,11 +5871,20 @@ while (*s != 0)
         {
         while (isspace(*s)) s++;
         if (*s != '{')                                 /*}*/
         {
         while (isspace(*s)) s++;
         if (*s != '{')                                 /*}*/
+         {
+         expand_string_message = string_sprintf(
+           "missing '{' for arg %d of listextract", i+1);
          goto EXPAND_FAILED_CURLY;
          goto EXPAND_FAILED_CURLY;
+         }
 
        sub[i] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok);
        if (!sub[i])     goto EXPAND_FAILED;            /*{*/
 
        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. */
 
        /* After removal of leading and trailing white space, the first
        argument must be numeric and nonempty. */
@@ -5344,7 +5928,7 @@ while (*s != 0)
       /* Extract the numbered element into $value. If
       skipping, just pretend the extraction failed. */
 
       /* Extract the numbered element into $value. If
       skipping, just pretend the extraction failed. */
 
-      lookup_value = skipping? NULL : expand_getlistele(field_number, sub[1]);
+      lookup_value = skipping ? NULL : expand_getlistele(field_number, sub[1]);
 
       /* If no string follows, $value gets substituted; otherwise there can
       be yes/no strings, as for lookup or if. */
 
       /* If no string follows, $value gets substituted; otherwise there can
       be yes/no strings, as for lookup or if. */
@@ -5355,9 +5939,7 @@ while (*s != 0)
                save_lookup_value,            /* value to reset for string2 */
                &s,                           /* input pointer */
                &yield,                       /* output pointer */
                save_lookup_value,            /* value to reset for string2 */
                &s,                           /* input pointer */
                &yield,                       /* output pointer */
-               &size,                        /* output size */
-               &ptr,                         /* output current point */
-               US"extract",                  /* condition type */
+               US"listextract",              /* condition type */
               &resetok))
         {
         case 1: goto EXPAND_FAILED;          /* when all is well, the */
               &resetok))
         {
         case 1: goto EXPAND_FAILED;          /* when all is well, the */
@@ -5383,10 +5965,17 @@ while (*s != 0)
       /* Read the field argument */
       while (isspace(*s)) s++;
       if (*s != '{')                                   /*}*/
       /* Read the field argument */
       while (isspace(*s)) s++;
       if (*s != '{')                                   /*}*/
+       {
+       expand_string_message = US"missing '{' for field arg of certextract";
        goto EXPAND_FAILED_CURLY;
        goto EXPAND_FAILED_CURLY;
+       }
       sub[0] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok);
       if (!sub[0])     goto EXPAND_FAILED;             /*{*/
       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;
       /* strip spaces fore & aft */
       {
       int len;
@@ -5403,7 +5992,10 @@ while (*s != 0)
       /* inspect the cert argument */
       while (isspace(*s)) s++;
       if (*s != '{')                                   /*}*/
       /* 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;
        goto EXPAND_FAILED_CURLY;
+       }
       if (*++s != '$')
         {
        expand_string_message = US"second argument of \"certextract\" must "
       if (*++s != '$')
         {
        expand_string_message = US"second argument of \"certextract\" must "
@@ -5412,7 +6004,11 @@ while (*s != 0)
        }
       sub[1] = expand_string_internal(s+1, TRUE, &s, skipping, FALSE, &resetok);
       if (!sub[1])     goto EXPAND_FAILED;             /*{*/
        }
       sub[1] = expand_string_internal(s+1, TRUE, &s, skipping, FALSE, &resetok);
       if (!sub[1])     goto EXPAND_FAILED;             /*{*/
-      if (*s++ != '}') goto EXPAND_FAILED_CURLY;
+      if (*s++ != '}')
+        {
+       expand_string_message = US"missing '}' closing cert variable arg of certextract";
+       goto EXPAND_FAILED_CURLY;
+       }
 
       if (skipping)
        lookup_value = NULL;
 
       if (skipping)
        lookup_value = NULL;
@@ -5427,9 +6023,7 @@ while (*s != 0)
                save_lookup_value,            /* value to reset for string2 */
                &s,                           /* input pointer */
                &yield,                       /* output pointer */
                save_lookup_value,            /* value to reset for string2 */
                &s,                           /* input pointer */
                &yield,                       /* output pointer */
-               &size,                        /* output size */
-               &ptr,                         /* output current point */
-               US"extract",                  /* condition type */
+               US"certextract",              /* condition type */
               &resetok))
         {
         case 1: goto EXPAND_FAILED;          /* when all is well, the */
               &resetok))
         {
         case 1: goto EXPAND_FAILED;          /* when all is well, the */
@@ -5449,31 +6043,55 @@ while (*s != 0)
     case EITEM_REDUCE:
       {
       int sep = 0;
     case EITEM_REDUCE:
       {
       int sep = 0;
-      int save_ptr = ptr;
+      int save_ptr = yield->ptr;
       uschar outsep[2] = { '\0', '\0' };
       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++;
       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;
 
       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)
         {
 
       if (item_type == EITEM_REDUCE)
         {
+       uschar * t;
         while (isspace(*s)) s++;
         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++;
         }
 
       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;
 
 
       expr = s;
 
@@ -5489,9 +6107,7 @@ while (*s != 0)
         if (temp != NULL) s = temp;
         }
       else
         if (temp != NULL) s = temp;
         }
       else
-        {
         temp = expand_string_internal(s, TRUE, &s, TRUE, TRUE, &resetok);
         temp = expand_string_internal(s, TRUE, &s, TRUE, TRUE, &resetok);
-        }
 
       if (temp == NULL)
         {
 
       if (temp == NULL)
         {
@@ -5504,7 +6120,8 @@ while (*s != 0)
       if (*s++ != '}')
         {                                              /*{*/
         expand_string_message = string_sprintf("missing } at end of condition "
       if (*s++ != '}')
         {                                              /*{*/
         expand_string_message = string_sprintf("missing } at end of condition "
-          "or expression inside \"%s\"", name);
+          "or expression inside \"%s\"; could be an unquoted } in the content",
+         name);
         goto EXPAND_FAILED;
         }
 
         goto EXPAND_FAILED;
         }
 
@@ -5520,11 +6137,12 @@ while (*s != 0)
       processing for real, we perform the iteration. */
 
       if (skipping) continue;
       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 */
 
         {
         *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)
           {
 
         if (item_type == EITEM_FILTER)
           {
@@ -5537,7 +6155,7 @@ while (*s != 0)
               expand_string_message, name);
             goto EXPAND_FAILED;
             }
               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 */
             condresult? "true":"false");
           if (condresult)
             temp = iterate_item;    /* TRUE => include this item */
@@ -5549,7 +6167,8 @@ while (*s != 0)
 
         else
           {
 
         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;
           if (temp == NULL)
             {
             iterate_item = save_iterate_item;
@@ -5559,7 +6178,7 @@ while (*s != 0)
             }
           if (item_type == EITEM_REDUCE)
             {
             }
           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 */
             }
           }
             continue;                 /* and continue the iteration */
             }
           }
@@ -5571,8 +6190,8 @@ while (*s != 0)
         item of the output list, add in a space if the new item begins with the
         separator character, or is an empty string. */
 
         item of the output list, add in a space if the new item begins with the
         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);
+        if (yield->ptr != save_ptr && (temp[0] == *outsep || temp[0] == 0))
+          yield = string_catn(yield, 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. */
 
         /* 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 +6199,22 @@ while (*s != 0)
         for (;;)
           {
           size_t seglen = Ustrcspn(temp, outsep);
         for (;;)
           {
           size_t seglen = Ustrcspn(temp, outsep);
-            yield = string_cat(yield, &size, &ptr, temp, seglen + 1);
+
+         yield = string_catn(yield, 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 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);
+          if (temp[seglen] == '\0') { yield->ptr--; break; }
+          yield = string_catn(yield, outsep, 1);
           temp += seglen + 1;
           }
 
         /* Output a separator after the string: we will remove the redundant
         final one at the end. */
 
           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, outsep, 1);
         }   /* End of iteration over the list loop */
 
       /* REDUCE has generated no output above: output the final value of
         }   /* End of iteration over the list loop */
 
       /* REDUCE has generated no output above: output the final value of
@@ -5602,8 +6222,7 @@ while (*s != 0)
 
       if (item_type == EITEM_REDUCE)
         {
 
       if (item_type == EITEM_REDUCE)
         {
-        yield = string_cat(yield, &size, &ptr, lookup_value,
-          Ustrlen(lookup_value));
+        yield = string_cat(yield, lookup_value);
         lookup_value = save_lookup_value;  /* Restore $value */
         }
 
         lookup_value = save_lookup_value;  /* Restore $value */
         }
 
@@ -5611,7 +6230,7 @@ while (*s != 0)
       the redundant final separator. Even though an empty item at the end of a
       list does not count, this is tidier. */
 
       the redundant final separator. Even though an empty item at the end of a
       list does not count, this is tidier. */
 
-      else if (ptr != save_ptr) ptr--;
+      else if (yield->ptr != save_ptr) yield->ptr--;
 
       /* Restore preserved $item */
 
 
       /* Restore preserved $item */
 
@@ -5619,6 +6238,168 @@ while (*s != 0)
       continue;
       }
 
       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;
+       gstring * newlist = NULL;
+       gstring * 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->s;
+       dstkeylist = newkeylist->s;
+
+        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, 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}}
 
     /* If ${dlfunc } support is configured, handle calling dynamically-loaded
     functions, unless locked out at this time. Syntax is ${dlfunc{file}{func}}
@@ -5628,12 +6409,12 @@ while (*s != 0)
     #define EXPAND_DLFUNC_MAX_ARGS 8
 
     case EITEM_DLFUNC:
     #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;
       {
       tree_node *t;
       exim_dlfunc_t *func;
@@ -5706,20 +6487,54 @@ while (*s != 0)
       if(status == OK)
         {
         if (result == NULL) result = US"";
       if(status == OK)
         {
         if (result == NULL) result = US"";
-        yield = string_cat(yield, &size, &ptr, result, Ustrlen(result));
+        yield = string_cat(yield, result);
         continue;
         }
       else
         {
         expand_string_message = result == NULL ? US"(no message)" : result;
         continue;
         }
       else
         {
         expand_string_message = result == NULL ? US"(no message)" : result;
-        if(status == FAIL_FORCED) expand_string_forcedfail = TRUE;
+        if(status == FAIL_FORCED) f.expand_string_forcedfail = TRUE;
           else if(status != FAIL)
             log_write(0, LOG_MAIN|LOG_PANIC, "dlfunc{%s}{%s} failed (%d): %s",
               argv[0], argv[1], status, expand_string_message);
         goto EXPAND_FAILED;
         }
       }
           else if(status != FAIL)
             log_write(0, LOG_MAIN|LOG_PANIC, "dlfunc{%s}{%s} failed (%d): %s",
               argv[0], argv[1], status, expand_string_message);
         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 */
+               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
     }  /* EITEM_* switch */
 
   /* Control reaches here if the name is not recognized as one of the more
@@ -5732,7 +6547,9 @@ while (*s != 0)
     int c;
     uschar *arg = NULL;
     uschar *sub;
     int c;
     uschar *arg = NULL;
     uschar *sub;
+#ifdef SUPPORT_TLS
     var_entry *vp = NULL;
     var_entry *vp = NULL;
+#endif
 
     /* Owing to an historical mis-design, an underscore may be part of the
     operator name, or it may introduce arguments.  We therefore first scan the
 
     /* Owing to an historical mis-design, an underscore may be part of the
     operator name, or it may introduce arguments.  We therefore first scan the
@@ -5740,13 +6557,12 @@ while (*s != 0)
     the arguments and then scan the main table. */
 
     if ((c = chop_match(name, op_table_underscore,
     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;
       {
       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 */
       }
 
       if (arg != NULL) *arg++ = '_';   /* Put back for error messages */
       }
 
@@ -5758,13 +6574,19 @@ while (*s != 0)
       case EOP_MD5:
       case EOP_SHA1:
       case EOP_SHA256:
       case EOP_MD5:
       case EOP_SHA1:
       case EOP_SHA256:
+      case EOP_BASE64:
        if (s[1] == '$')
          {
        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;           /*{*/
          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;
          if ((vp = find_var_ent(sub)) && vp->type == vtype_cert)
            {
            s = s1+1;
@@ -5793,6 +6615,46 @@ while (*s != 0)
 
     switch(c)
       {
 
     switch(c)
       {
+      case EOP_BASE32:
+       {
+        uschar *t;
+        unsigned long int n = Ustrtoul(sub, &t, 10);
+       gstring * g = NULL;
+
+        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)
+         g = string_catn(g, &base32_chars[n & 0x1f], 1);
+
+       if (g) while (g->ptr > 0) yield = string_catn(yield, &g->s[--g->ptr], 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, s);
+        continue;
+        }
+
       case EOP_BASE62:
         {
         uschar *t;
       case EOP_BASE62:
         {
         uschar *t;
@@ -5804,7 +6666,7 @@ while (*s != 0)
           goto EXPAND_FAILED;
           }
         t = string_base62(n);
           goto EXPAND_FAILED;
           }
         t = string_base62(n);
-        yield = string_cat(yield, &size, &ptr, t, Ustrlen(t));
+        yield = string_cat(yield, t);
         continue;
         }
 
         continue;
         }
 
@@ -5812,7 +6674,6 @@ while (*s != 0)
 
       case EOP_BASE62D:
         {
 
       case EOP_BASE62D:
         {
-        uschar buf[16];
         uschar *tt = sub;
         unsigned long int n = 0;
         while (*tt != 0)
         uschar *tt = sub;
         unsigned long int n = 0;
         while (*tt != 0)
@@ -5827,8 +6688,7 @@ while (*s != 0)
             }
           n = n * BASE_62 + (t - base62_chars);
           }
             }
           n = n * BASE_62 + (t - base62_chars);
           }
-        (void)sprintf(CS buf, "%ld", n);
-        yield = string_cat(yield, &size, &ptr, buf, Ustrlen(buf));
+        yield = string_fmt_append(yield, "%ld", n);
         continue;
         }
 
         continue;
         }
 
@@ -5842,7 +6702,7 @@ while (*s != 0)
               expand_string_message);
           goto EXPAND_FAILED;
           }
               expand_string_message);
           goto EXPAND_FAILED;
           }
-        yield = string_cat(yield, &size, &ptr, expanded, Ustrlen(expanded));
+        yield = string_cat(yield, expanded);
         continue;
         }
 
         continue;
         }
 
@@ -5851,7 +6711,7 @@ while (*s != 0)
         int count = 0;
         uschar *t = sub - 1;
         while (*(++t) != 0) { *t = tolower(*t); count++; }
         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, sub, count);
         continue;
         }
 
         continue;
         }
 
@@ -5860,7 +6720,7 @@ while (*s != 0)
         int count = 0;
         uschar *t = sub - 1;
         while (*(++t) != 0) { *t = toupper(*t); count++; }
         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, sub, count);
         continue;
         }
 
         continue;
         }
 
@@ -5869,7 +6729,7 @@ while (*s != 0)
        if (vp && *(void **)vp->value)
          {
          uschar * cp = tls_cert_fprt_md5(*(void **)vp->value);
        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, cp);
          }
        else
 #endif
          }
        else
 #endif
@@ -5877,11 +6737,10 @@ while (*s != 0)
          md5 base;
          uschar digest[16];
          int j;
          md5 base;
          uschar digest[16];
          int j;
-         char st[33];
          md5_start(&base);
          md5_end(&base, sub, Ustrlen(sub), digest);
          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));
+         for (j = 0; j < 16; j++)
+           yield = string_fmt_append(yield, "%02x", digest[j]);
          }
         continue;
 
          }
         continue;
 
@@ -5890,34 +6749,77 @@ while (*s != 0)
        if (vp && *(void **)vp->value)
          {
          uschar * cp = tls_cert_fprt_sha1(*(void **)vp->value);
        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, cp);
          }
        else
 #endif
          {
          }
        else
 #endif
          {
-         sha1 base;
+         hctx h;
          uschar digest[20];
          int j;
          uschar digest[20];
          int j;
-         char st[41];
-         sha1_start(&base);
-         sha1_end(&base, sub, Ustrlen(sub), digest);
-         for(j = 0; j < 20; j++) sprintf(st+2*j, "%02X", digest[j]);
-         yield = string_cat(yield, &size, &ptr, US st, (int)strlen(st));
+         sha1_start(&h);
+         sha1_end(&h, sub, Ustrlen(sub), digest);
+         for (j = 0; j < 20; j++)
+           yield = string_fmt_append(yield, "%02X", digest[j]);
          }
         continue;
 
       case EOP_SHA256:
          }
         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);
        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, cp);
          }
        else
          }
        else
+         {
+         hctx h;
+         blob b;
+
+         if (!exim_sha_init(&h, HASH_SHA2_256))
+           {
+           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)
+           yield = string_fmt_append(yield, "%02X", *b.data++);
+         }
+#else
+         expand_string_message = US"sha256 only supported with TLS";
 #endif
 #endif
-         expand_string_message = US"sha256 only supported for certificates";
         continue;
 
         continue;
 
+      case EOP_SHA3:
+#ifdef EXIM_HAVE_SHA3
+       {
+       hctx h;
+       blob b;
+       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)
+         yield = string_fmt_append(yield, "%02X", *b.data++);
+       }
+        continue;
+#else
+       expand_string_message = US"sha3 only supported with GnuTLS 3.5.0 + or OpenSSL 1.1.1 +";
+       goto EXPAND_FAILED;
+#endif
+
       /* Convert hex encoding to base64 encoding */
 
       case EOP_HEX2B64:
       /* Convert hex encoding to base64 encoding */
 
       case EOP_HEX2B64:
@@ -5961,8 +6863,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, enc);
         continue;
         }
 
         continue;
         }
 
@@ -5974,10 +6876,9 @@ while (*s != 0)
         while (*(++t) != 0)
           {
           if (*t < 0x21 || 0x7E < *t)
         while (*(++t) != 0)
           {
           if (*t < 0x21 || 0x7E < *t)
-            yield = string_cat(yield, &size, &ptr,
-             string_sprintf("\\x%02x", *t), 4);
+            yield = string_fmt_append(yield, "\\x%02x", *t);
          else
          else
-           yield = string_cat(yield, &size, &ptr, t, 1);
+           yield = string_catn(yield, t, 1);
           }
        continue;
        }
           }
        continue;
        }
@@ -5988,12 +6889,10 @@ while (*s != 0)
         {
        int cnt = 0;
        int sep = 0;
         {
        int cnt = 0;
        int sep = 0;
-       uschar * cp;
        uschar buffer[256];
 
        uschar buffer[256];
 
-       while (string_nextinlist(&sub, &sep, buffer, sizeof(buffer)) != NULL) cnt++;
-       cp = string_sprintf("%d", cnt);
-        yield = string_cat(yield, &size, &ptr, cp, Ustrlen(cp));
+       while (string_nextinlist(CUSS &sub, &sep, buffer, sizeof(buffer)) != NULL) cnt++;
+       yield = string_fmt_append(yield, "%d", cnt);
         continue;
         }
 
         continue;
         }
 
@@ -6003,7 +6902,7 @@ while (*s != 0)
       case EOP_LISTNAMED:
        {
        tree_node *t = NULL;
       case EOP_LISTNAMED:
        {
        tree_node *t = NULL;
-       uschar * list;
+       const uschar * list;
        int sep = 0;
        uschar * item;
        uschar * suffix = US"";
        int sep = 0;
        uschar * item;
        uschar * suffix = US"";
@@ -6043,11 +6942,11 @@ while (*s != 0)
 
        list = ((namedlist_block *)(t->data.ptr))->string;
 
 
        list = ((namedlist_block *)(t->data.ptr))->string;
 
-       while ((item = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
+       while ((item = string_nextinlist(&list, &sep, buffer, sizeof(buffer))))
          {
          uschar * buf = US" : ";
          if (needsep)
          {
          uschar * buf = US" : ";
          if (needsep)
-           yield = string_cat(yield, &size, &ptr, buf, 3);
+           yield = string_catn(yield, buf, 3);
          else
            needsep = TRUE;
 
          else
            needsep = TRUE;
 
@@ -6061,23 +6960,23 @@ while (*s != 0)
            char * cp;
            char tok[3];
            tok[0] = sep; tok[1] = ':'; tok[2] = 0;
            char * cp;
            char tok[3];
            tok[0] = sep; tok[1] = ':'; tok[2] = 0;
-           while ((cp= strpbrk((const char *)item, tok)))
+           while ((cp= strpbrk(CCS item, tok)))
              {
              {
-              yield = string_cat(yield, &size, &ptr, item, cp-(char *)item);
+              yield = string_catn(yield, item, cp - CS item);
              if (*cp++ == ':') /* colon in a non-colon-sep list item, needs doubling */
                {
              if (*cp++ == ':') /* colon in a non-colon-sep list item, needs doubling */
                {
-                yield = string_cat(yield, &size, &ptr, US"::", 2);
-               item = (uschar *)cp;
+                yield = string_catn(yield, US"::", 2);
+               item = US cp;
                }
              else              /* sep in item; should already be doubled; emit once */
                {
                }
              else              /* sep in item; should already be doubled; emit once */
                {
-                yield = string_cat(yield, &size, &ptr, (uschar *)tok, 1);
+                yield = string_catn(yield, US tok, 1);
                if (*cp == sep) cp++;
                if (*cp == sep) cp++;
-               item = (uschar *)cp;
+               item = US cp;
                }
              }
            }
                }
              }
            }
-          yield = string_cat(yield, &size, &ptr, item, Ustrlen(item));
+          yield = string_cat(yield, item);
          }
         continue;
        }
          }
         continue;
        }
@@ -6125,32 +7024,62 @@ while (*s != 0)
 
         /* Convert to masked textual format and add to output. */
 
 
         /* Convert to masked textual format and add to output. */
 
-        yield = string_cat(yield, &size, &ptr, buffer,
+        yield = string_catn(yield, buffer,
           host_nmtoa(count, binary, mask, buffer, '.'));
         continue;
         }
 
           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, 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:
         {
       case EOP_ADDRESS:
       case EOP_LOCAL_PART:
       case EOP_DOMAIN:
         {
-        uschar *error;
+        uschar * error;
         int start, end, domain;
         int start, end, domain;
-        uschar *t = parse_extract_address(sub, &error, &start, &end, &domain,
+        uschar * t = parse_extract_address(sub, &error, &start, &end, &domain,
           FALSE);
           FALSE);
-        if (t != NULL)
-          {
+        if (t)
           if (c != EOP_DOMAIN)
             {
             if (c == EOP_LOCAL_PART && domain != 0) end = start + domain - 1;
           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, sub+start, end-start);
             }
           else if (domain != 0)
             {
             domain += start;
             }
           else if (domain != 0)
             {
             domain += start;
-            yield = string_cat(yield, &size, &ptr, sub+domain, end-domain);
+            yield = string_catn(yield, sub+domain, end-domain);
             }
             }
-          }
         continue;
         }
 
         continue;
         }
 
@@ -6158,12 +7087,19 @@ while (*s != 0)
         {
         uschar outsep[2] = { ':', '\0' };
         uschar *address, *error;
         {
         uschar outsep[2] = { ':', '\0' };
         uschar *address, *error;
-        int save_ptr = ptr;
+        int save_ptr = yield->ptr;
         int start, end, domain;  /* Not really used */
 
         while (isspace(*sub)) sub++;
         int start, end, domain;  /* Not really used */
 
         while (isspace(*sub)) sub++;
-        if (*sub == '>') { *outsep = *++sub; ++sub; }
-        parse_allow_group = TRUE;
+        if (*sub == '>')
+          if (*outsep = *++sub) ++sub;
+          else
+           {
+            expand_string_message = string_sprintf("output separator "
+              "missing in expanding ${addresses:%s}", --sub);
+            goto EXPAND_FAILED;
+            }
+        f.parse_allow_group = TRUE;
 
         for (;;)
           {
 
         for (;;)
           {
@@ -6182,26 +7118,26 @@ while (*s != 0)
 
           if (address != NULL)
             {
 
           if (address != NULL)
             {
-            if (ptr != save_ptr && address[0] == *outsep)
-              yield = string_cat(yield, &size, &ptr, US" ", 1);
+            if (yield->ptr != save_ptr && address[0] == *outsep)
+              yield = string_catn(yield, US" ", 1);
 
             for (;;)
               {
               size_t seglen = Ustrcspn(address, outsep);
 
             for (;;)
               {
               size_t seglen = Ustrcspn(address, outsep);
-              yield = string_cat(yield, &size, &ptr, address, seglen + 1);
+              yield = string_catn(yield, address, seglen + 1);
 
               /* If we got to the end of the string we output one character
               too many. */
 
 
               /* 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);
+              if (address[seglen] == '\0') { yield->ptr--; break; }
+              yield = string_catn(yield, outsep, 1);
               address += seglen + 1;
               }
 
             /* Output a separator after the string: we will remove the
             redundant final one at the end. */
 
               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, outsep, 1);
             }
 
           if (saveend == '\0') break;
             }
 
           if (saveend == '\0') break;
@@ -6211,8 +7147,8 @@ while (*s != 0)
         /* If we have generated anything, remove the redundant final
         separator. */
 
         /* If we have generated anything, remove the redundant final
         separator. */
 
-        if (ptr != save_ptr) ptr--;
-        parse_allow_group = FALSE;
+        if (yield->ptr != save_ptr) yield->ptr--;
+        f.parse_allow_group = FALSE;
         continue;
         }
 
         continue;
         }
 
@@ -6248,24 +7184,24 @@ while (*s != 0)
 
         if (needs_quote)
           {
 
         if (needs_quote)
           {
-          yield = string_cat(yield, &size, &ptr, US"\"", 1);
+          yield = string_catn(yield, US"\"", 1);
           t = sub - 1;
           while (*(++t) != 0)
             {
             if (*t == '\n')
           t = sub - 1;
           while (*(++t) != 0)
             {
             if (*t == '\n')
-              yield = string_cat(yield, &size, &ptr, US"\\n", 2);
+              yield = string_catn(yield, US"\\n", 2);
             else if (*t == '\r')
             else if (*t == '\r')
-              yield = string_cat(yield, &size, &ptr, US"\\r", 2);
+              yield = string_catn(yield, US"\\r", 2);
             else
               {
               if (*t == '\\' || *t == '"')
             else
               {
               if (*t == '\\' || *t == '"')
-                yield = string_cat(yield, &size, &ptr, US"\\", 1);
-              yield = string_cat(yield, &size, &ptr, t, 1);
+                yield = string_catn(yield, US"\\", 1);
+              yield = string_catn(yield, t, 1);
               }
             }
               }
             }
-          yield = string_cat(yield, &size, &ptr, US"\"", 1);
+          yield = string_catn(yield, US"\"", 1);
           }
           }
-        else yield = string_cat(yield, &size, &ptr, sub, Ustrlen(sub));
+        else yield = string_cat(yield, sub);
         continue;
         }
 
         continue;
         }
 
@@ -6297,7 +7233,7 @@ while (*s != 0)
           goto EXPAND_FAILED;
           }
 
           goto EXPAND_FAILED;
           }
 
-        yield = string_cat(yield, &size, &ptr, sub, Ustrlen(sub));
+        yield = string_cat(yield, sub);
         continue;
         }
 
         continue;
         }
 
@@ -6310,8 +7246,8 @@ while (*s != 0)
         while (*(++t) != 0)
           {
           if (!isalnum(*t))
         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, US"\\", 1);
+          yield = string_catn(yield, t, 1);
           }
         continue;
         }
           }
         continue;
         }
@@ -6322,9 +7258,9 @@ while (*s != 0)
       case EOP_RFC2047:
         {
         uschar buffer[2048];
       case EOP_RFC2047:
         {
         uschar buffer[2048];
-        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,
+                           parse_quote_2047(sub, Ustrlen(sub), headers_charset,
+                             buffer, sizeof(buffer), FALSE));
         continue;
         }
 
         continue;
         }
 
@@ -6341,7 +7277,7 @@ while (*s != 0)
           expand_string_message = error;
           goto EXPAND_FAILED;
           }
           expand_string_message = error;
           goto EXPAND_FAILED;
           }
-        yield = string_cat(yield, &size, &ptr, decoded, len);
+        yield = string_catn(yield, decoded, len);
         continue;
         }
 
         continue;
         }
 
@@ -6357,57 +7293,52 @@ while (*s != 0)
           GETUTF8INC(c, sub);
           if (c > 255) c = '_';
           buff[0] = c;
           GETUTF8INC(c, sub);
           if (c > 255) c = '_';
           buff[0] = c;
-          yield = string_cat(yield, &size, &ptr, buff, 1);
+          yield = string_catn(yield, buff, 1);
           }
         continue;
         }
 
          /* replace illegal UTF-8 sequences by replacement character  */
           }
         continue;
         }
 
          /* replace illegal UTF-8 sequences by replacement character  */
-         
+
       #define UTF8_REPLACEMENT_CHAR US"?"
 
       case EOP_UTF8CLEAN:
         {
       #define UTF8_REPLACEMENT_CHAR US"?"
 
       case EOP_UTF8CLEAN:
         {
-        int seq_len, index = 0;
+        int seq_len = 0, index = 0;
         int bytes_left = 0;
         int bytes_left = 0;
+        long codepoint = -1;
+        int complete;
         uschar seq_buff[4];                    /* accumulate utf-8 here */
         uschar seq_buff[4];                    /* accumulate utf-8 here */
-        
+
         while (*sub != 0)
          {
         while (*sub != 0)
          {
-         int complete;
-         long codepoint;
-         uschar c;
-
          complete = 0;
          complete = 0;
-         c = *sub++;
+         uschar c = *sub++;
+
          if (bytes_left)
            {
            if ((c & 0xc0) != 0x80)
          if (bytes_left)
            {
            if ((c & 0xc0) != 0x80)
-             {
                    /* wrong continuation byte; invalidate all bytes */
              complete = 1; /* error */
                    /* 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 */
            else
              {
              codepoint = (codepoint << 6) | (c & 0x3f);
              seq_buff[index++] = c;
              if (--bytes_left == 0)            /* codepoint complete */
-               {
                if(codepoint > 0x10FFFF)        /* is it too large? */
                if(codepoint > 0x10FFFF)        /* is it too large? */
-                 complete = -1;        /* error */
+                 complete = -1;        /* error (RFC3629 limit) */
                else
                  {             /* finished; output utf-8 sequence */
                else
                  {             /* finished; output utf-8 sequence */
-                 yield = string_cat(yield, &size, &ptr, seq_buff, seq_len);
+                 yield = string_catn(yield, seq_buff, seq_len);
                  index = 0;
                  }
                  index = 0;
                  }
-               }
              }
            }
          else  /* no bytes left: new sequence */
            {
            if((c & 0x80) == 0) /* 1-byte sequence, US-ASCII, keep it */
              {
              }
            }
          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, &c, 1);
              continue;
              }
            if((c & 0xe0) == 0xc0)              /* 2-byte sequence */
              continue;
              }
            if((c & 0xe0) == 0xc0)              /* 2-byte sequence */
@@ -6440,25 +7371,106 @@ while (*s != 0)
          if (complete != 0)
            {
            bytes_left = index = 0;
          if (complete != 0)
            {
            bytes_left = index = 0;
-           yield = string_cat(yield, &size, &ptr, UTF8_REPLACEMENT_CHAR, 1);
+           yield = string_catn(yield, UTF8_REPLACEMENT_CHAR, 1);
            }
          if ((complete == 1) && ((c & 0x80) == 0))
            }
          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, &c, 1);
          }
          }
+        /* If given a sequence truncated mid-character, we also want to report ?
+        * Eg, ${length_1:フィル} is one byte, not one character, so we expect
+        * ${utf8clean:${length_1:フィル}} to yield '?' */
+        if (bytes_left != 0)
+          {
+          yield = string_catn(yield, UTF8_REPLACEMENT_CHAR, 1);
+          }
         continue;
         }
 
         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, 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, 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, s);
+       DEBUG(D_expand) debug_printf_indent("yield: '%s'\n", yield->s);
+        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, s);
+        continue;
+       }
+#endif /* EXPERIMENTAL_INTERNATIONAL */
+
       /* escape turns all non-printing characters into escape sequences. */
 
       case EOP_ESCAPE:
         {
       /* 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, t);
         continue;
         }
 
         continue;
         }
 
+      case EOP_ESCAPE8BIT:
+       {
+       const uschar * s = sub;
+       uschar c;
+
+       for (s = sub; (c = *s); s++)
+         yield = c < 127 && c != '\\'
+           ? string_catn(yield, s, 1)
+           : string_fmt_append(yield, "\\%03o", c);
+       continue;
+       }
+
       /* Handle numeric expression evaluation */
 
       case EOP_EVAL:
       /* Handle numeric expression evaluation */
 
       case EOP_EVAL:
@@ -6467,19 +7479,18 @@ while (*s != 0)
         uschar *save_sub = sub;
         uschar *error = NULL;
         int_eximarith_t n = eval_expr(&sub, (c == EOP_EVAL10), &error, FALSE);
         uschar *save_sub = sub;
         uschar *error = NULL;
         int_eximarith_t n = eval_expr(&sub, (c == EOP_EVAL10), &error, FALSE);
-        if (error != NULL)
+        if (error)
           {
           expand_string_message = string_sprintf("error in expression "
           {
           expand_string_message = string_sprintf("error in expression "
-            "evaluation: %s (after processing \"%.*s\")", error, sub-save_sub,
-              save_sub);
+            "evaluation: %s (after processing \"%.*s\")", error,
+           (int)(sub-save_sub), save_sub);
           goto EXPAND_FAILED;
           }
           goto EXPAND_FAILED;
           }
-        sprintf(CS var_buffer, PR_EXIM_ARITH, n);
-        yield = string_cat(yield, &size, &ptr, var_buffer, Ustrlen(var_buffer));
+        yield = string_fmt_append(yield, PR_EXIM_ARITH, n);
         continue;
         }
 
         continue;
         }
 
-      /* Handle time period formating */
+      /* Handle time period formatting */
 
       case EOP_TIME_EVAL:
         {
 
       case EOP_TIME_EVAL:
         {
@@ -6490,8 +7501,7 @@ while (*s != 0)
             "Exim time interval in \"%s\" operator", sub, name);
           goto EXPAND_FAILED;
           }
             "Exim time interval in \"%s\" operator", sub, name);
           goto EXPAND_FAILED;
           }
-        sprintf(CS var_buffer, "%d", n);
-        yield = string_cat(yield, &size, &ptr, var_buffer, Ustrlen(var_buffer));
+        yield = string_fmt_append(yield, "%d", n);
         continue;
         }
 
         continue;
         }
 
@@ -6506,28 +7516,45 @@ while (*s != 0)
           goto EXPAND_FAILED;
           }
         t = readconf_printtime(n);
           goto EXPAND_FAILED;
           }
         t = readconf_printtime(n);
-        yield = string_cat(yield, &size, &ptr, t, Ustrlen(t));
+        yield = string_cat(yield, t);
         continue;
         }
 
       /* Convert string to base64 encoding */
 
       case EOP_STR2B64:
         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, 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, s);
         continue;
         }
 
       /* strlen returns the length of the string */
 
       case EOP_STRLEN:
         continue;
         }
 
       /* strlen returns the length of the string */
 
       case EOP_STRLEN:
-        {
-        uschar buff[24];
-        (void)sprintf(CS buff, "%d", Ustrlen(sub));
-        yield = string_cat(yield, &size, &ptr, buff, Ustrlen(buff));
+        yield = string_fmt_append(yield, "%d", Ustrlen(sub));
         continue;
         continue;
-        }
 
       /* length_n or l_n takes just the first n characters or the whole string,
       whichever is the shorter;
 
       /* length_n or l_n takes just the first n characters or the whole string,
       whichever is the shorter;
@@ -6560,7 +7587,7 @@ while (*s != 0)
         int len;
         uschar *ret;
 
         int len;
         uschar *ret;
 
-        if (arg == NULL)
+        if (!arg)
           {
           expand_string_message = string_sprintf("missing values after %s",
             name);
           {
           expand_string_message = string_sprintf("missing values after %s",
             name);
@@ -6616,7 +7643,7 @@ while (*s != 0)
              extract_substr(sub, value1, value2, &len);
 
         if (ret == NULL) goto EXPAND_FAILED;
              extract_substr(sub, value1, value2, &len);
 
         if (ret == NULL) goto EXPAND_FAILED;
-        yield = string_cat(yield, &size, &ptr, ret, len);
+        yield = string_catn(yield, ret, len);
         continue;
         }
 
         continue;
         }
 
@@ -6624,14 +7651,13 @@ while (*s != 0)
 
       case EOP_STAT:
         {
 
       case EOP_STAT:
         {
-        uschar *s;
         uschar smode[12];
         uschar **modetable[3];
         int i;
         mode_t mode;
         struct stat st;
 
         uschar smode[12];
         uschar **modetable[3];
         int i;
         mode_t mode;
         struct stat st;
 
-        if ((expand_forbid & RDO_EXISTS) != 0)
+        if (expand_forbid & RDO_EXISTS)
           {
           expand_string_message = US"Use of the stat() expansion is not permitted";
           goto EXPAND_FAILED;
           {
           expand_string_message = US"Use of the stat() expansion is not permitted";
           goto EXPAND_FAILED;
@@ -6665,13 +7691,13 @@ while (*s != 0)
           }
 
         smode[10] = 0;
           }
 
         smode[10] = 0;
-        s = string_sprintf("mode=%04lo smode=%s inode=%ld device=%ld links=%ld "
+        yield = string_fmt_append(yield,
+         "mode=%04lo smode=%s inode=%ld device=%ld links=%ld "
           "uid=%ld gid=%ld size=" OFF_T_FMT " atime=%ld mtime=%ld ctime=%ld",
           (long)(st.st_mode & 077777), smode, (long)st.st_ino,
           (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);
           "uid=%ld gid=%ld size=" OFF_T_FMT " atime=%ld mtime=%ld ctime=%ld",
           (long)(st.st_mode & 077777), smode, (long)st.st_ino,
           (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));
         continue;
         }
 
         continue;
         }
 
@@ -6679,14 +7705,11 @@ while (*s != 0)
 
       case EOP_RANDINT:
         {
 
       case EOP_RANDINT:
         {
-        int_eximarith_t max;
-        uschar *s;
+        int_eximarith_t max = expanded_string_integer(sub, TRUE);
 
 
-        max = expanded_string_integer(sub, TRUE);
-        if (expand_string_message != NULL)
+        if (expand_string_message)
           goto EXPAND_FAILED;
           goto EXPAND_FAILED;
-        s = string_sprintf("%d", vaguely_random_number((int)max));
-        yield = string_cat(yield, &size, &ptr, s, Ustrlen(s));
+        yield = string_fmt_append(yield, "%d", vaguely_random_number((int)max));
         continue;
         }
 
         continue;
         }
 
@@ -6705,16 +7728,16 @@ while (*s != 0)
           goto EXPAND_FAILED;
           }
         invert_address(reversed, sub);
           goto EXPAND_FAILED;
           }
         invert_address(reversed, sub);
-        yield = string_cat(yield, &size, &ptr, reversed, Ustrlen(reversed));
+        yield = string_cat(yield, reversed);
         continue;
         }
 
       /* Unknown operator */
 
       default:
         continue;
         }
 
       /* Unknown operator */
 
       default:
-      expand_string_message =
-        string_sprintf("unknown expansion operator \"%s\"", name);
-      goto EXPAND_FAILED;
+       expand_string_message =
+         string_sprintf("unknown expansion operator \"%s\"", name);
+       goto EXPAND_FAILED;
       }
     }
 
       }
     }
 
@@ -6729,14 +7752,17 @@ while (*s != 0)
     {
     int len;
     int newsize = 0;
     {
     int len;
     int newsize = 0;
-    if (ptr == 0)
+    gstring * g = NULL;
+
+    if (!yield)
+      g = store_get(sizeof(gstring));
+    else if (yield->ptr == 0)
       {
       if (resetok) store_reset(yield);
       yield = NULL;
       {
       if (resetok) store_reset(yield);
       yield = NULL;
-      size = 0;
+      g = store_get(sizeof(gstring));  /* alloc _before_ calling find_variable() */
       }
       }
-    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);
       {
       expand_string_message =
         string_sprintf("unknown variable in \"${%s}\"", name);
@@ -6744,13 +7770,15 @@ while (*s != 0)
       goto EXPAND_FAILED;
       }
     len = Ustrlen(value);
       goto EXPAND_FAILED;
       }
     len = Ustrlen(value);
-    if (yield == NULL && newsize != 0)
+    if (!yield && newsize)
       {
       {
-      yield = value;
-      size = newsize;
-      ptr = len;
+      yield = g;
+      yield->size = newsize;
+      yield->ptr = len;
+      yield->s = value;
       }
       }
-    else yield = string_cat(yield, &size, &ptr, value, len);
+    else
+      yield = string_catn(yield, value, len);
     continue;
     }
 
     continue;
     }
 
@@ -6767,10 +7795,9 @@ terminating brace. */
 
 if (ket_ends && *s == 0)
   {
 
 if (ket_ends && *s == 0)
   {
-  expand_string_message = malformed_header?
-    US"missing } at end of string - could be header name not terminated by colon"
-    :
-    US"missing } at end of string";
+  expand_string_message = malformed_header
+    ? US"missing } at end of string - could be header name not terminated by colon"
+    : US"missing } at end of string";
   goto EXPAND_FAILED;
   }
 
   goto EXPAND_FAILED;
   }
 
@@ -6778,47 +7805,83 @@ if (ket_ends && *s == 0)
 added to the string. If so, set up an empty string. Add a terminating zero. If
 left != NULL, return a pointer to the terminator. */
 
 added to the string. If so, set up an empty string. Add a terminating zero. If
 left != NULL, return a pointer to the terminator. */
 
-if (yield == NULL) yield = store_get(1);
-yield[ptr] = 0;
-if (left != NULL) *left = s;
+if (!yield)
+  yield = string_get(1);
+(void) string_from_gstring(yield);
+if (left) *left = s;
 
 /* Any stacking store that was used above the final string is no longer needed.
 In many cases the final string will be the first one that was got and so there
 will be optimal store usage. */
 
 
 /* Any stacking store that was used above the final string is no longer needed.
 In many cases the final string will be the first one that was got and so there
 will be optimal store usage. */
 
-if (resetok) store_reset(yield + ptr + 1);
+if (resetok) store_reset(yield->s + (yield->size = yield->ptr + 1));
 else if (resetok_p) *resetok_p = FALSE;
 
 DEBUG(D_expand)
 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");
-  }
-return yield;
+  DEBUG(D_noutf8)
+    {
+    debug_printf_indent("|--expanding: %.*s\n", (int)(s - string), string);
+    debug_printf_indent("%sresult: %s\n",
+      skipping ? "|-----" : "\\_____", yield->s);
+    if (skipping)
+      debug_printf_indent("\\___skipping: result is not used\n");
+    }
+  else
+    {
+    debug_printf_indent(UTF8_VERT_RIGHT UTF8_HORIZ UTF8_HORIZ
+      "expanding: %.*s\n",
+      (int)(s - string), string);
+    debug_printf_indent("%s"
+      UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ
+      "result: %s\n",
+      skipping ? UTF8_VERT_RIGHT : UTF8_UP_RIGHT,
+      yield->s);
+    if (skipping)
+      debug_printf_indent(UTF8_UP_RIGHT UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ
+       "skipping: result is not used\n");
+    }
+expand_level--;
+return yield->s;
 
 /* This is the failure exit: easiest to program with a goto. We still need
 to update the pointer to the terminator, for cases of nested calls with "fail".
 */
 
 EXPAND_FAILED_CURLY:
 
 /* This is the failure exit: easiest to program with a goto. We still need
 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. */
 
 EXPAND_FAILED:
 
 /* 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. */
 
 EXPAND_FAILED:
-if (left != NULL) *left = s;
+if (left) *left = s;
 DEBUG(D_expand)
 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");
-  }
-if (resetok_p) *resetok_p = resetok;
+  DEBUG(D_noutf8)
+    {
+    debug_printf_indent("|failed to expand: %s\n", string);
+    debug_printf_indent("%serror message: %s\n",
+      f.expand_string_forcedfail ? "|---" : "\\___", expand_string_message);
+    if (f.expand_string_forcedfail)
+      debug_printf_indent("\\failure was forced\n");
+    }
+  else
+    {
+    debug_printf_indent(UTF8_VERT_RIGHT "failed to expand: %s\n",
+      string);
+    debug_printf_indent("%s" UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ
+      "error message: %s\n",
+      f.expand_string_forcedfail ? UTF8_VERT_RIGHT : UTF8_UP_RIGHT,
+      expand_string_message);
+    if (f.expand_string_forcedfail)
+      debug_printf_indent(UTF8_UP_RIGHT "failure was forced\n");
+    }
+if (resetok_p && !resetok) *resetok_p = FALSE;
+expand_level--;
 return NULL;
 }
 
 return NULL;
 }
 
@@ -6831,34 +7894,53 @@ Returns:  the expanded string, or NULL if expansion failed; if failure was
           due to a lookup deferring, search_find_defer will be TRUE
 */
 
           due to a lookup deferring, search_find_defer will be TRUE
 */
 
+const uschar *
+expand_cstring(const uschar * string)
+{
+if (Ustrpbrk(string, "$\\") != NULL)
+  {
+  int old_pool = store_pool;
+  uschar * s;
+
+  f.search_find_defer = FALSE;
+  malformed_header = FALSE;
+  store_pool = POOL_MAIN;
+    s = expand_string_internal(string, FALSE, NULL, FALSE, TRUE, NULL);
+  store_pool = old_pool;
+  return s;
+  }
+return string;
+}
+
+
 uschar *
 uschar *
-expand_string(uschar *string)
+expand_string(uschar * string)
 {
 {
-search_find_defer = FALSE;
-malformed_header = FALSE;
-return (Ustrpbrk(string, "$\\") == NULL)? string :
-  expand_string_internal(string, FALSE, NULL, FALSE, TRUE, NULL);
+return US expand_cstring(CUS string);
 }
 
 
 
 }
 
 
 
+
+
 /*************************************************
 *              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.
 /*************************************************
 *              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 *
 
 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);
 if (yield == string) yield = string_copy(string);
-return yield;
+return US yield;
 }
 
 
 }
 
 
@@ -6905,7 +7987,7 @@ Returns:  the integer value, or
 */
 
 static int_eximarith_t
 */
 
 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\"";
 {
 int_eximarith_t value;
 uschar *msg = US"invalid integer \"%s\"";
@@ -6933,7 +8015,7 @@ if (isspace(*s))
   if (*s == '\0')
     {
       DEBUG(D_expand)
   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;
     }
   }
       return 0;
     }
   }
@@ -6984,6 +8066,188 @@ 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 (f.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;
+}
+
+
+/* Read given named file into big_buffer.  Use for keying material etc.
+The content will have an ascii NUL appended.
+
+Arguments:
+ filename      as it says
+
+Return:  pointer to buffer, or NULL on error.
+*/
+
+uschar *
+expand_file_big_buffer(const uschar * filename)
+{
+int fd, off = 0, len;
+
+if ((fd = open(CS filename, O_RDONLY)) < 0)
+  {
+  log_write(0, LOG_MAIN | LOG_PANIC, "unable to open file for reading: %s",
+            filename);
+  return NULL;
+  }
+
+do
+  {
+  if ((len = read(fd, big_buffer + off, big_buffer_size - 2 - off)) < 0)
+    {
+    (void) close(fd);
+    log_write(0, LOG_MAIN|LOG_PANIC, "unable to read file: %s", filename);
+    return NULL;
+    }
+  off += len;
+  }
+while (len > 0);
+
+(void) close(fd);
+big_buffer[off] = '\0';
+return big_buffer;
+}
+
+
+
+/*************************************************
+* Error-checking for testsuite                   *
+*************************************************/
+typedef struct {
+  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 = { .region_start = ptr, .region_end = US ptr + len,
+             .var_name = NULL, .var_data = 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);
+
+/* check dns and address trees */
+tree_walk(tree_dns_fails,     assert_variable_notin, &e);
+tree_walk(tree_duplicates,    assert_variable_notin, &e);
+tree_walk(tree_nonrecipients, assert_variable_notin, &e);
+tree_walk(tree_unusable,      assert_variable_notin, &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, filename, linenumber, e.var_data);
+}
+
+
+
 /*************************************************
 **************************************************
 *             Stand-alone test program           *
 /*************************************************
 **************************************************
 *             Stand-alone test program           *
@@ -6998,7 +8262,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,
 {
 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)
 BOOL yield = n >= 0;
 if (n == 0) n = EXPAND_MAXN + 1;
 if (yield)
@@ -7039,22 +8303,22 @@ for (i = 1; i < argc; i++)
     if (Ustrspn(argv[i], "abcdefghijklmnopqrtsuvwxyz0123456789-.:/") ==
         Ustrlen(argv[i]))
       {
     if (Ustrspn(argv[i], "abcdefghijklmnopqrtsuvwxyz0123456789-.:/") ==
         Ustrlen(argv[i]))
       {
-      #ifdef LOOKUP_LDAP
+#ifdef LOOKUP_LDAP
       eldap_default_servers = argv[i];
       eldap_default_servers = argv[i];
-      #endif
-      #ifdef LOOKUP_MYSQL
+#endif
+#ifdef LOOKUP_MYSQL
       mysql_servers = argv[i];
       mysql_servers = argv[i];
-      #endif
-      #ifdef LOOKUP_PGSQL
+#endif
+#ifdef LOOKUP_PGSQL
       pgsql_servers = argv[i];
       pgsql_servers = argv[i];
-      #endif
-      #ifdef EXPERIMENTAL_REDIS
+#endif
+#ifdef LOOKUP_REDIS
       redis_servers = argv[i];
       redis_servers = argv[i];
-      #endif
+#endif
       }
       }
-  #ifdef EXIM_PERL
+#ifdef EXIM_PERL
   else opt_perl_startup = argv[i];
   else opt_perl_startup = argv[i];
-  #endif
+#endif
   }
 
 printf("Testing string expansion: debug_level = %d\n\n", debug_level);
   }
 
 printf("Testing string expansion: debug_level = %d\n\n", debug_level);
@@ -7088,9 +8352,9 @@ while (fgets(buffer, sizeof(buffer), stdin) != NULL)
     }
   else
     {
     }
   else
     {
-    if (search_find_defer) printf("search_find deferred\n");
+    if (f.search_find_defer) printf("search_find deferred\n");
     printf("Failed: %s\n", expand_string_message);
     printf("Failed: %s\n", expand_string_message);
-    if (expand_string_forcedfail) printf("Forced failure\n");
+    if (f.expand_string_forcedfail) printf("Forced failure\n");
     printf("\n");
     }
   }
     printf("\n");
     }
   }
index 7132d22..a16416c 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -91,7 +91,7 @@ static const char *mailargs[] = {  /* "to" must be first, and */
 
 /* The count of string arguments */
 
 
 /* The count of string arguments */
 
-#define MAILARGS_STRING_COUNT (sizeof(mailargs)/sizeof(uschar *))
+#define MAILARGS_STRING_COUNT (nelem(mailargs))
 
 /* The count of string arguments that are actually passed over as strings
 (once_repeat is converted to an int). */
 
 /* The count of string arguments that are actually passed over as strings
 (once_repeat is converted to an int). */
@@ -185,7 +185,7 @@ static const char *cond_words[] = {
    "match",
    "matches"};
 
    "match",
    "matches"};
 
-static int cond_word_count = (sizeof(cond_words)/sizeof(uschar *));
+static int cond_word_count = nelem(cond_words);
 
 static int cond_types[] = { cond_BEGINS, cond_BEGINS, cond_CONTAINS,
   cond_CONTAINS, cond_ENDS, cond_ENDS, cond_IS, cond_MATCHES, cond_MATCHES,
 
 static int cond_types[] = { cond_BEGINS, cond_BEGINS, cond_CONTAINS,
   cond_CONTAINS, cond_ENDS, cond_ENDS, cond_IS, cond_MATCHES, cond_MATCHES,
@@ -207,7 +207,7 @@ static const char *command_list[] = {
   "noerror", "pipe",    "save",    "seen", "testprint", "unseen",   "vacation"
 };
 
   "noerror", "pipe",    "save",    "seen", "testprint", "unseen",   "vacation"
 };
 
-static int command_list_count = sizeof(command_list)/sizeof(uschar *);
+static int command_list_count = nelem(command_list);
 
 /* This table contains the number of expanded arguments in the bottom 4 bits.
 If the top bit is set, it means that the default for the command is "seen". */
 
 /* This table contains the number of expanded arguments in the bottom 4 bits.
 If the top bit is set, it means that the default for the command is "seen". */
@@ -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);
         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 (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;
         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);
     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\" "
       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);
       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 (*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);
       }
 
       ptr = nextsigchar(ptr+1, TRUE);
       }
 
@@ -577,7 +572,7 @@ for (;;)
 
       c->left.u = string_copy(buffer);
       ptr = nextword(ptr, buffer, sizeof(buffer), TRUE);
 
       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>". */
 
       /* 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 (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 (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);
           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 (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;
           }
           {
           *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);
         }
 
       /* 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);
       }
     }
       c->right.u = string_copy(buffer);
       }
     }
@@ -664,7 +656,7 @@ for (;;)
     {
     uschar *saveptr = ptr;
     ptr = nextword(ptr, buffer, sizeof(buffer), FALSE);
     {
     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
 
     /* "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;
     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;
       }
           "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;
 
       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. */
 
                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;
           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;
       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
 *************************************************/
 
 static void
@@ -958,7 +950,7 @@ switch (command)
         yield = FALSE;
         }
 
         yield = FALSE;
         }
 
-      if (!system_filtering && second_argument.b != TRUE_UNSET)
+      if (!f.system_filtering && second_argument.b != TRUE_UNSET)
         {
         *error_pointer = string_sprintf("header addition and removal is "
           "available only in system filters: near line %d of filter file",
         {
         *error_pointer = string_sprintf("header addition and removal is "
           "available only in system filters: near line %d of filter file",
@@ -1378,7 +1370,7 @@ return yield;
 *              Read a list of commands           *
 *************************************************/
 
 *              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:
 by the words "else" or "endif".
 
 Arguments:
@@ -1460,7 +1452,7 @@ switch (c->type)
   scan Cc: (hence the FALSE argument). */
 
   case cond_personal:
   scan Cc: (hence the FALSE argument). */
 
   case cond_personal:
-  yield = system_filtering? FALSE : filter_personal(c->left.a, FALSE);
+  yield = f.system_filtering? FALSE : filter_personal(c->left.a, FALSE);
   break;
 
   case cond_delivered:
   break;
 
   case cond_delivered:
@@ -1479,14 +1471,14 @@ switch (c->type)
   and filter testing and verification. */
 
   case cond_firsttime:
   and filter testing and verification. */
 
   case cond_firsttime:
-  yield = filter_test != FTEST_NONE || message_id[0] == 0 || deliver_firsttime;
+  yield = filter_test != FTEST_NONE || message_id[0] == 0 || f.deliver_firsttime;
   break;
 
   /* Only TRUE if a message is actually being processed; FALSE for address
   testing and verification. */
 
   case cond_manualthaw:
   break;
 
   /* Only TRUE if a message is actually being processed; FALSE for address
   testing and verification. */
 
   case cond_manualthaw:
-  yield = message_id[0] != 0 && deliver_manual_thaw;
+  yield = message_id[0] != 0 && f.deliver_manual_thaw;
   break;
 
   /* The foranyaddress condition loops through a list of addresses */
   break;
 
   /* The foranyaddress condition loops through a list of addresses */
@@ -1502,7 +1494,7 @@ switch (c->type)
     }
 
   yield = FALSE;
     }
 
   yield = FALSE;
-  parse_allow_group = TRUE;     /* Allow group syntax */
+  f.parse_allow_group = TRUE;     /* Allow group syntax */
 
   while (*pp != 0)
     {
 
   while (*pp != 0)
     {
@@ -1534,8 +1526,8 @@ switch (c->type)
     pp = p + 1;
     }
 
     pp = p + 1;
     }
 
-  parse_allow_group = FALSE;      /* Reset group syntax flags */
-  parse_found_group = FALSE;
+  f.parse_allow_group = FALSE;      /* Reset group syntax flags */
+  f.parse_found_group = FALSE;
   break;
 
   /* All other conditions have left and right values that need expanding;
   break;
 
   /* All other conditions have left and right values that need expanding;
@@ -1782,7 +1774,7 @@ while (commands != NULL)
 
     s = expargs[1];
 
 
     s = expargs[1];
 
-    if (s != NULL && !system_filtering)
+    if (s != NULL && !f.system_filtering)
       {
       uschar *ownaddress = expand_string(US"$local_part@$domain");
       if (strcmpic(ownaddress, s) != 0)
       {
       uschar *ownaddress = expand_string(US"$local_part@$domain");
       if (strcmpic(ownaddress, s) != 0)
@@ -1821,9 +1813,9 @@ 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 */
       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 */
         s : string_copy(s);                        /* Default is NULL */
-      if (commands->noerror) setflag(addr, af_ignore_error);
+      if (commands->noerror) addr->prop.ignore_error = TRUE;
       addr->next = *generated;
       *generated = addr;
       }
       addr->next = *generated;
       *generated = addr;
       }
@@ -1863,8 +1855,9 @@ while (commands != NULL)
       mode value. */
 
       addr = deliver_make_addr(s, TRUE);  /* TRUE => copy s */
       mode value. */
 
       addr = deliver_make_addr(s, TRUE);  /* TRUE => copy s */
-      setflag(addr, af_pfr|af_file);
-      if (commands->noerror) setflag(addr, af_ignore_error);
+      setflag(addr, af_pfr);
+      setflag(addr, af_file);
+      if (commands->noerror) addr->prop.ignore_error = TRUE;
       addr->mode = mode;
       addr->next = *generated;
       *generated = addr;
       addr->mode = mode;
       addr->next = *generated;
       *generated = addr;
@@ -1892,8 +1885,9 @@ while (commands != NULL)
       has been split up into separate arguments. */
 
       addr = deliver_make_addr(s, TRUE);  /* TRUE => copy s */
       has been split up into separate arguments. */
 
       addr = deliver_make_addr(s, TRUE);  /* TRUE => copy s */
-      setflag(addr, af_pfr|af_expand_pipe);
-      if (commands->noerror) setflag(addr, af_ignore_error);
+      setflag(addr, af_pfr);
+      setflag(addr, af_expand_pipe);
+      if (commands->noerror) addr->prop.ignore_error = TRUE;
       addr->next = *generated;
       *generated = addr;
 
       addr->next = *generated;
       *generated = addr;
 
@@ -2021,7 +2015,7 @@ while (commands != NULL)
         {
         int sep = 0;
         uschar *ss;
         {
         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)
         uschar buffer[128];
         while ((ss = string_nextinlist(&list, &sep, buffer, sizeof(buffer)))
                != NULL)
@@ -2057,7 +2051,7 @@ while (commands != NULL)
     DEFERFREEZEFAIL:
     fmsg = expargs[0];
     if (Ustrlen(fmsg) > 1024) Ustrcpy(fmsg + 1000, " ... (truncated)");
     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)
     *error_pointer = fmsg;
 
     if (filter_test != FTEST_NONE)
@@ -2107,256 +2101,252 @@ while (commands != NULL)
 
     case mail_command:
     case vacation_command:
 
     case mail_command:
     case vacation_command:
-    if (return_path == NULL || return_path[0] == 0)
-      {
+      if (return_path == NULL || return_path[0] == 0)
+       {
+       if (filter_test != FTEST_NONE)
+         printf("%s command ignored because return_path is empty\n",
+           command_list[commands->command]);
+       else DEBUG(D_filter) debug_printf("%s command ignored because return_path "
+         "is empty\n", command_list[commands->command]);
+       break;
+       }
+
+      /* Check the contents of the strings. The type of string can be deduced
+      from the value of i.
+
+      . If i is equal to mailarg_index_text it's a text string for the body,
+       where anything goes.
+
+      . If i is > mailarg_index_text, we are dealing with a file name, which
+       cannot contain non-printing characters.
+
+      . If i is less than mailarg_index_headers we are dealing with something
+       that will go in a single message header line, where newlines must be
+       followed by white space.
+
+      . If i is equal to mailarg_index_headers, we have a string that contains
+       one or more headers. Newlines that are not followed by white space must
+       be followed by a header name.
+      */
+
+      for (i = 0; i < MAILARGS_STRING_COUNT; i++)
+       {
+       uschar *p;
+       uschar *s = expargs[i];
+
+       if (s == NULL) continue;
+
+       if (i != mailarg_index_text) for (p = s; *p != 0; p++)
+         {
+         int c = *p;
+         if (i > mailarg_index_text)
+           {
+           if (!mac_isprint(c))
+             {
+             *error_pointer = string_sprintf("non-printing character in \"%s\" "
+               "in %s command", string_printing(s),
+               command_list[commands->command]);
+             return FF_ERROR;
+             }
+           }
+
+         /* i < mailarg_index_text */
+
+         else if (c == '\n' && !isspace(p[1]))
+           {
+           if (i < mailarg_index_headers)
+             {
+             *error_pointer = string_sprintf("\\n not followed by space in "
+               "\"%.1024s\" in %s command", string_printing(s),
+               command_list[commands->command]);
+             return FF_ERROR;
+             }
+
+           /* Check for the start of a new header line within the string */
+
+           else
+             {
+             uschar *pp;
+             for (pp = p + 1;; pp++)
+               {
+               c = *pp;
+               if (c == ':' && pp != p + 1) break;
+               if (c == 0 || c == ':' || isspace(*pp))
+                 {
+                 *error_pointer = string_sprintf("\\n not followed by space or "
+                   "valid header name in \"%.1024s\" in %s command",
+                   string_printing(s), command_list[commands->command]);
+                 return FF_ERROR;
+                 }
+               }
+             p = pp;
+             }
+           }
+         }       /* Loop to scan the string */
+
+       /* The string is OK */
+
+       commands->args[i].u = s;
+       }
+
+      /* Proceed with mail or vacation command */
+
       if (filter_test != FTEST_NONE)
       if (filter_test != FTEST_NONE)
-        printf("%s command ignored because return_path is empty\n",
-          command_list[commands->command]);
-      else DEBUG(D_filter) debug_printf("%s command ignored because return_path "
-        "is empty\n", command_list[commands->command]);
+       {
+       uschar *to = commands->args[mailarg_index_to].u;
+       indent();
+       printf("%sail to: %s%s%s\n", (commands->seen)? "Seen m" : "M",
+         to ? to : US"<default>",
+         commands->command == vacation_command ? " (vacation)" : "",
+         commands->noerror ? " (noerror)" : "");
+       for (i = 1; i < MAILARGS_STRING_COUNT; i++)
+         {
+         uschar *arg = commands->args[i].u;
+         if (arg)
+           {
+           int len = Ustrlen(mailargs[i]);
+           int indent = (debug_selector != 0)? output_indent : 0;
+           while (len++ < 7 + indent) printf(" ");
+           printf("%s: %s%s\n", mailargs[i], string_printing(arg),
+             (commands->args[mailarg_index_expand].u != NULL &&
+               Ustrcmp(mailargs[i], "file") == 0)? " (expanded)" : "");
+           }
+         }
+       if (commands->args[mailarg_index_return].u)
+         printf("Return original message\n");
+       }
+      else
+       {
+       uschar *tt;
+       uschar *to = commands->args[mailarg_index_to].u;
+       gstring * log_addr = NULL;
+
+       if (!to) to = expand_string(US"$reply_address");
+       while (isspace(*to)) to++;
+
+       for (tt = to; *tt != 0; tt++)     /* Get rid of newlines */
+         if (*tt == '\n') *tt = ' ';
+
+       DEBUG(D_filter)
+         {
+         debug_printf("Filter: %smail to: %s%s%s\n",
+           commands->seen ? "seen " : "",
+           to,
+           commands->command == vacation_command ? " (vacation)" : "",
+           commands->noerror ? " (noerror)" : "");
+         for (i = 1; i < MAILARGS_STRING_COUNT; i++)
+           {
+           uschar *arg = commands->args[i].u;
+           if (arg != NULL)
+             {
+             int len = Ustrlen(mailargs[i]);
+             while (len++ < 15) debug_printf(" ");
+             debug_printf("%s: %s%s\n", mailargs[i], string_printing(arg),
+               (commands->args[mailarg_index_expand].u != NULL &&
+                 Ustrcmp(mailargs[i], "file") == 0)? " (expanded)" : "");
+             }
+           }
+         }
+
+       /* Create the "address" for the autoreply. This is used only for logging,
+       as the actual recipients are extracted from the To: line by -t. We use the
+       same logic here to extract the working addresses (there may be more than
+       one). Just in case there are a vast number of addresses, stop when the
+       string gets too long. */
+
+       tt = to;
+       while (*tt != 0)
+         {
+         uschar *ss = parse_find_address_end(tt, FALSE);
+         uschar *recipient, *errmess;
+         int start, end, domain;
+         int temp = *ss;
+
+         *ss = 0;
+         recipient = parse_extract_address(tt, &errmess, &start, &end, &domain,
+           FALSE);
+         *ss = temp;
+
+         /* Ignore empty addresses and errors; an error will occur later if
+         there's something really bad. */
+
+         if (recipient)
+           {
+           log_addr = string_catn(log_addr, log_addr ? US"," : US">", 1);
+           log_addr = string_cat (log_addr, recipient);
+           }
+
+         /* Check size */
+
+         if (log_addr && log_addr->ptr > 256)
+           {
+           log_addr = string_catn(log_addr, US", ...", 5);
+           break;
+           }
+
+         /* Move on past this address */
+
+         tt = ss + (*ss ? 1 : 0);
+         while (isspace(*tt)) tt++;
+         }
+
+       if (log_addr)
+         addr = deliver_make_addr(string_from_gstring(log_addr), FALSE);
+       else
+         {
+         addr = deliver_make_addr(US ">**bad-reply**", FALSE);
+         setflag(addr, af_bad_reply);
+         }
+
+       setflag(addr, af_pfr);
+       if (commands->noerror) addr->prop.ignore_error = TRUE;
+       addr->next = *generated;
+       *generated = addr;
+
+       addr->reply = store_get(sizeof(reply_item));
+       addr->reply->from = NULL;
+       addr->reply->to = string_copy(to);
+       addr->reply->file_expand =
+         commands->args[mailarg_index_expand].u != NULL;
+       addr->reply->expand_forbid = expand_forbid;
+       addr->reply->return_message =
+         commands->args[mailarg_index_return].u != NULL;
+       addr->reply->once_repeat = 0;
+
+       if (commands->args[mailarg_index_once_repeat].u != NULL)
+         {
+         addr->reply->once_repeat =
+           readconf_readtime(commands->args[mailarg_index_once_repeat].u, 0,
+             FALSE);
+         if (addr->reply->once_repeat < 0)
+           {
+           *error_pointer = string_sprintf("Bad time value for \"once_repeat\" "
+             "in mail or vacation command: %s",
+             commands->args[mailarg_index_once_repeat].u);
+           return FF_ERROR;
+           }
+         }
+
+       /* Set up all the remaining string arguments (those other than "to") */
+
+       for (i = 1; i < mailargs_string_passed; i++)
+         {
+         uschar *ss = commands->args[i].u;
+         *(USS((US addr->reply) + reply_offsets[i])) =
+           ss ? string_copy(ss) : NULL;
+         }
+       }
       break;
       break;
-      }
-
-    /* Check the contents of the strings. The type of string can be deduced
-    from the value of i.
-
-    . If i is equal to mailarg_index_text it's a text string for the body,
-      where anything goes.
-
-    . If i is > mailarg_index_text, we are dealing with a file name, which
-      cannot contain non-printing characters.
-
-    . If i is less than mailarg_index_headers we are dealing with something
-      that will go in a single message header line, where newlines must be
-      followed by white space.
-
-    . If i is equal to mailarg_index_headers, we have a string that contains
-      one or more headers. Newlines that are not followed by white space must
-      be followed by a header name.
-    */
-
-    for (i = 0; i < MAILARGS_STRING_COUNT; i++)
-      {
-      uschar *p;
-      uschar *s = expargs[i];
-
-      if (s == NULL) continue;
-
-      if (i != mailarg_index_text) for (p = s; *p != 0; p++)
-        {
-        int c = *p;
-        if (i > mailarg_index_text)
-          {
-          if (!mac_isprint(c))
-            {
-            *error_pointer = string_sprintf("non-printing character in \"%s\" "
-              "in %s command", string_printing(s),
-              command_list[commands->command]);
-            return FF_ERROR;
-            }
-          }
-
-        /* i < mailarg_index_text */
-
-        else if (c == '\n' && !isspace(p[1]))
-          {
-          if (i < mailarg_index_headers)
-            {
-            *error_pointer = string_sprintf("\\n not followed by space in "
-              "\"%.1024s\" in %s command", string_printing(s),
-              command_list[commands->command]);
-            return FF_ERROR;
-            }
-
-          /* Check for the start of a new header line within the string */
-
-          else
-            {
-            uschar *pp;
-            for (pp = p + 1;; pp++)
-              {
-              c = *pp;
-              if (c == ':' && pp != p + 1) break;
-              if (c == 0 || c == ':' || isspace(*pp))
-                {
-                *error_pointer = string_sprintf("\\n not followed by space or "
-                  "valid header name in \"%.1024s\" in %s command",
-                  string_printing(s), command_list[commands->command]);
-                return FF_ERROR;
-                }
-              }
-            p = pp;
-            }
-          }
-        }       /* Loop to scan the string */
-
-      /* The string is OK */
-
-      commands->args[i].u = s;
-      }
-
-    /* Proceed with mail or vacation command */
-
-    if (filter_test != FTEST_NONE)
-      {
-      uschar *to = commands->args[mailarg_index_to].u;
-      indent();
-      printf("%sail to: %s%s%s\n", (commands->seen)? "Seen m" : "M",
-        (to == NULL)? US"<default>" : to,
-        (commands->command == vacation_command)? " (vacation)" : "",
-        (commands->noerror)? " (noerror)" : "");
-      for (i = 1; i < MAILARGS_STRING_COUNT; i++)
-        {
-        uschar *arg = commands->args[i].u;
-        if (arg != NULL)
-          {
-          int len = Ustrlen(mailargs[i]);
-          int indent = (debug_selector != 0)? output_indent : 0;
-          while (len++ < 7 + indent) printf(" ");
-          printf("%s: %s%s\n", mailargs[i], string_printing(arg),
-            (commands->args[mailarg_index_expand].u != NULL &&
-              Ustrcmp(mailargs[i], "file") == 0)? " (expanded)" : "");
-          }
-        }
-      if (commands->args[mailarg_index_return].u != NULL)
-        printf("Return original message\n");
-      }
-    else
-      {
-      uschar *tt;
-      uschar *log_addr = NULL;
-      uschar *to = commands->args[mailarg_index_to].u;
-      int size = 0;
-      int ptr = 0;
-      int badflag = 0;
-
-      if (to == NULL) to = expand_string(US"$reply_address");
-      while (isspace(*to)) to++;
-
-      for (tt = to; *tt != 0; tt++)     /* Get rid of newlines */
-        if (*tt == '\n') *tt = ' ';
-
-      DEBUG(D_filter)
-        {
-        debug_printf("Filter: %smail to: %s%s%s\n",
-          (commands->seen)? "seen " : "",
-          to,
-          (commands->command == vacation_command)? " (vacation)" : "",
-          (commands->noerror)? " (noerror)" : "");
-        for (i = 1; i < MAILARGS_STRING_COUNT; i++)
-          {
-          uschar *arg = commands->args[i].u;
-          if (arg != NULL)
-            {
-            int len = Ustrlen(mailargs[i]);
-            while (len++ < 15) debug_printf(" ");
-            debug_printf("%s: %s%s\n", mailargs[i], string_printing(arg),
-              (commands->args[mailarg_index_expand].u != NULL &&
-                Ustrcmp(mailargs[i], "file") == 0)? " (expanded)" : "");
-            }
-          }
-        }
-
-      /* Create the "address" for the autoreply. This is used only for logging,
-      as the actual recipients are extracted from the To: line by -t. We use the
-      same logic here to extract the working addresses (there may be more than
-      one). Just in case there are a vast number of addresses, stop when the
-      string gets too long. */
-
-      tt = to;
-      while (*tt != 0)
-        {
-        uschar *ss = parse_find_address_end(tt, FALSE);
-        uschar *recipient, *errmess;
-        int start, end, domain;
-        int temp = *ss;
-
-        *ss = 0;
-        recipient = parse_extract_address(tt, &errmess, &start, &end, &domain,
-          FALSE);
-        *ss = temp;
-
-        /* Ignore empty addresses and errors; an error will occur later if
-        there's something really bad. */
-
-        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));
-          }
-
-        /* Check size */
-
-        if (ptr > 256)
-          {
-          log_addr = string_cat(log_addr, &size, &ptr, US", ...", 5);
-          break;
-          }
-
-        /* Move on past this address */
-
-        tt = ss + (*ss? 1:0);
-        while (isspace(*tt)) tt++;
-        }
-
-      if (log_addr == NULL)
-        {
-        log_addr = string_sprintf(">**bad-reply**");
-        badflag = af_bad_reply;
-        }
-      else log_addr[ptr] = 0;
-
-      addr = deliver_make_addr(log_addr, FALSE);
-      setflag(addr, (af_pfr|badflag));
-      if (commands->noerror) setflag(addr, af_ignore_error);
-      addr->next = *generated;
-      *generated = addr;
-      addr->reply = store_get(sizeof(reply_item));
-      addr->reply->from = NULL;
-      addr->reply->to = string_copy(to);
-      addr->reply->file_expand =
-        commands->args[mailarg_index_expand].u != NULL;
-      addr->reply->expand_forbid = expand_forbid;
-      addr->reply->return_message =
-        commands->args[mailarg_index_return].u != NULL;
-      addr->reply->once_repeat = 0;
-
-      if (commands->args[mailarg_index_once_repeat].u != NULL)
-        {
-        addr->reply->once_repeat =
-          readconf_readtime(commands->args[mailarg_index_once_repeat].u, 0,
-            FALSE);
-        if (addr->reply->once_repeat < 0)
-          {
-          *error_pointer = string_sprintf("Bad time value for \"once_repeat\" "
-            "in mail or vacation command: %s",
-            commands->args[mailarg_index_once_repeat]);
-          return FF_ERROR;
-          }
-        }
-
-      /* Set up all the remaining string arguments (those other than "to") */
-
-      for (i = 1; i < mailargs_string_passed; i++)
-        {
-        uschar *ss = commands->args[i].u;
-        *((uschar **)(((uschar *)(addr->reply)) + reply_offsets[i])) =
-          (ss == NULL)? NULL : string_copy(ss);
-        }
-      }
-    break;
 
     case testprint_command:
 
     case testprint_command:
-    if (filter_test != FTEST_NONE || (debug_selector & D_filter) != 0)
-      {
-      uschar *s = string_printing(expargs[0]);
-      if (filter_test == FTEST_NONE)
-        debug_printf("Filter: testprint: %s\n", s);
-      else
-        printf("Testprint: %s\n", s);
-      }
+      if (filter_test != FTEST_NONE || (debug_selector & D_filter) != 0)
+       {
+       const uschar *s = string_printing(expargs[0]);
+       if (filter_test == FTEST_NONE)
+         debug_printf("Filter: testprint: %s\n", s);
+       else
+         printf("Testprint: %s\n", s);
+       }
     }
 
   commands = commands->next;
     }
 
   commands = commands->next;
@@ -2531,7 +2521,7 @@ while filtering, and zero the variables. */
 
 expect_endif = 0;
 output_indent = 0;
 
 expect_endif = 0;
 output_indent = 0;
-filter_running = TRUE;
+f.filter_running = TRUE;
 for (i = 0; i < FILTER_VARIABLE_COUNT; i++) filter_n[i] = 0;
 
 /* To save having to pass certain values about all the time, make them static.
 for (i = 0; i < FILTER_VARIABLE_COUNT; i++) filter_n[i] = 0;
 
 /* To save having to pass certain values about all the time, make them static.
@@ -2600,7 +2590,7 @@ before returning. Reset the header decoding charset. */
 
 if (log_fd >= 0) (void)close(log_fd);
 expand_nmax = -1;
 
 if (log_fd >= 0) (void)close(log_fd);
 expand_nmax = -1;
-filter_running = FALSE;
+f.filter_running = FALSE;
 headers_charset = save_headers_charset;
 
 DEBUG(D_route) debug_printf("Filter: end of processing\n");
 headers_charset = save_headers_charset;
 
 DEBUG(D_route) debug_printf("Filter: end of processing\n");
index 7245608..f3d3acc 100644 (file)
@@ -47,7 +47,7 @@ header_size = message_size;
 
 if (!dot_ended && !feof(stdin))
   {
 
 if (!dot_ended && !feof(stdin))
   {
-  if (!dot_ends)
+  if (!f.dot_ends)
     {
     while ((ch = getc(stdin)) != EOF)
       {
     {
     while ((ch = getc(stdin)) != EOF)
       {
@@ -259,13 +259,13 @@ testing a system filter that is going to be followed by a user filter test. */
 
 if (is_system)
   {
 
 if (is_system)
   {
-  system_filtering = TRUE;
-  enable_dollar_recipients = TRUE; /* Permit $recipients in system filter */
+  f.system_filtering = TRUE;
+  f.enable_dollar_recipients = TRUE; /* Permit $recipients in system filter */
   yield = filter_interpret
     (filebuf,
     RDO_DEFER|RDO_FAIL|RDO_FILTER|RDO_FREEZE|RDO_REWRITE, &generated, &error);
   yield = filter_interpret
     (filebuf,
     RDO_DEFER|RDO_FAIL|RDO_FILTER|RDO_FREEZE|RDO_REWRITE, &generated, &error);
-  enable_dollar_recipients = FALSE;
-  system_filtering = FALSE;
+  f.enable_dollar_recipients = FALSE;
+  f.system_filtering = FALSE;
   }
 else
   {
   }
 else
   {
index 67a3916..cab7a73 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -13,8 +13,8 @@ are in in fact in separate headers. */
 
 
 #ifdef EXIM_PERL
 
 
 #ifdef EXIM_PERL
-extern uschar *call_perl_cat(uschar *, int *, int *, uschar **, uschar *,
-                 uschar **);
+extern gstring *call_perl_cat(gstring *, uschar **, uschar *,
+                 uschar **) WARN_UNUSED_RESULT;
 extern void    cleanup_perl(void);
 extern uschar *init_perl(uschar *);
 #endif
 extern void    cleanup_perl(void);
 extern uschar *init_perl(uschar *);
 #endif
@@ -39,33 +39,44 @@ 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_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 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 *);
-extern void    tls_close(BOOL, BOOL);
+extern void *  tls_client_start(int, host_item *, address_item *,
+                transport_instance *,
+# ifdef SUPPORT_DANE
+               dns_answer *,
+# endif
+               tls_support *, uschar **);
+extern void    tls_close(void *, int);
+extern BOOL    tls_could_read(void);
 extern int     tls_export_cert(uschar *, size_t, void *);
 extern int     tls_feof(void);
 extern int     tls_ferror(void);
 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 uschar *tls_getbuf(unsigned *);
+extern void    tls_get_cache(void);
 extern int     tls_import_cert(const uschar *, void **);
 extern int     tls_import_cert(const uschar *, void **);
-extern int     tls_read(BOOL, uschar *, size_t);
-extern int     tls_server_start(const uschar *);
+extern int     tls_read(void *, uschar *, size_t);
+extern int     tls_server_start(const uschar *, uschar **);
 extern BOOL    tls_smtp_buffered(void);
 extern int     tls_ungetc(int);
 extern BOOL    tls_smtp_buffered(void);
 extern int     tls_ungetc(int);
-extern int     tls_write(BOOL, const uschar *, size_t);
+extern int     tls_write(void *, const uschar *, size_t, BOOL);
 extern uschar *tls_validate_require_cipher(void);
 extern void    tls_version_report(FILE *);
 # ifndef USE_GNUTLS
 extern BOOL    tls_openssl_options_parse(uschar *, long *);
 # endif
 extern uschar *tls_validate_require_cipher(void);
 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 SUPPORT_DANE
+extern int     tlsa_lookup(const host_item *, dns_answer *, BOOL);
 # endif
 # endif
+
 #endif /*SUPPORT_TLS*/
 
 
 #endif /*SUPPORT_TLS*/
 
 
@@ -77,33 +88,69 @@ extern int     acl_eval(int, uschar *, uschar **, uschar **);
 
 extern tree_node *acl_var_create(uschar *);
 extern void    acl_var_write(uschar *, uschar *, void *);
 
 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 **);
+
+#ifdef EXPERIMENTAL_ARC
+extern void   *arc_ams_setup_sign_bodyhash(void);
+extern const uschar *arc_header_feed(gstring *, BOOL);
+extern gstring *arc_sign(const uschar *, gstring *, uschar **);
+extern void     arc_sign_init(void);
+extern const uschar *acl_verify_arc(void);
+extern uschar * fn_arc_domains(void);
+#endif
+
+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_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_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 int     auth_get_data(uschar **, uschar *, int);
 extern int     auth_get_no64_data(uschar **, uschar *);
+extern void    auth_show_supported(FILE *);
 extern uschar *auth_xtextencode(uschar *, int);
 extern int     auth_xtextdecode(uschar *, uschar **);
 
 extern uschar *auth_xtextencode(uschar *, int);
 extern int     auth_xtextdecode(uschar *, uschar **);
 
-extern void    cancel_cutthrough_connection(const char *);
-extern int     check_host(void *, uschar *, uschar **, uschar **);
+#ifdef EXPERIMENTAL_ARC
+extern gstring *authres_arc(gstring *);
+#endif
+#ifndef DISABLE_DKIM
+extern gstring *authres_dkim(gstring *);
+#endif
+#ifdef EXPERIMENTAL_DMARC
+extern gstring *authres_dmarc(gstring *);
+#endif
+extern gstring *authres_smtpauth(gstring *);
+#ifdef SUPPORT_SPF
+extern gstring *authres_spf(gstring *);
+#endif
+
+extern uschar *b64encode(uschar *, int);
+extern int     b64decode(const uschar *, uschar **);
+extern int     bdat_getc(unsigned);
+extern uschar *bdat_getbuf(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(BOOL, const uschar *);
+extern int     check_host(void *, const uschar *, const uschar **, uschar **);
 extern uschar **child_exec_exim(int, BOOL, int *, BOOL, int, ...);
 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 BOOL    cleanup_environment(void);
+extern void    cutthrough_data_puts(uschar *, int);
+extern void    cutthrough_data_put_nl(void);
 extern uschar *cutthrough_finaldot(void);
 extern BOOL    cutthrough_flush_send(void);
 extern BOOL    cutthrough_headers_send(void);
 extern BOOL    cutthrough_predata(void);
 extern uschar *cutthrough_finaldot(void);
 extern BOOL    cutthrough_flush_send(void);
 extern BOOL    cutthrough_headers_send(void);
 extern BOOL    cutthrough_predata(void);
-extern BOOL    cutthrough_puts(uschar *, int);
-extern BOOL    cutthrough_put_nl(void);
+extern void    release_cutthrough_connection(const uschar *);
 
 extern void    daemon_go(void);
 
 
 extern void    daemon_go(void);
 
@@ -112,54 +159,74 @@ extern int     dcc_process(uschar **);
 #endif
 
 extern void    debug_logging_activate(uschar *, 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_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_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    debug_vprintf(int, const char *, va_list);
+extern void    decode_bits(unsigned int *, size_t, int *,
+                  uschar *, bit_table *, int, uschar *, int);
 extern address_item *deliver_make_addr(uschar *, BOOL);
 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 *);
 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 void    delivery_re_exec(int);
+
 extern BOOL    directory_make(const uschar *, const uschar *, int, BOOL);
 #ifndef DISABLE_DKIM
 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 uschar *dkim_exim_query_dns_txt(uschar *);
+extern void    dkim_exim_sign_init(void);
+
+extern BOOL    dkim_transport_write_message(transport_ctx *,
+                 struct ob_dkim *, const uschar ** errstr);
 #endif
 extern dns_address *dns_address_from_rr(dns_answer *, dns_record *);
 #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 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 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 void    exim_exit(int);
+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, const uschar *);
 extern void    exim_nullstd(void);
 extern void    exim_setugid(uid_t, gid_t, BOOL, uschar *);
 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 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 BOOL    expand_check_condition(uschar *, uschar *, uschar *);
-extern uschar *expand_string(uschar *);
-extern uschar *expand_string_copy(uschar *);
+extern uschar *expand_file_big_buffer(const 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 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);
 extern int     filter_interpret(uschar *, int, address_item **, uschar **);
 extern BOOL    filter_personal(string_item *, BOOL);
 extern BOOL    filter_runtest(int, uschar *, BOOL, BOOL);
@@ -167,21 +234,24 @@ extern BOOL    filter_system_interpret(address_item **, uschar **);
 
 extern uschar * fn_hdrs_added(void);
 
 
 extern uschar * fn_hdrs_added(void);
 
+extern void    gstring_reset_unused(gstring *);
+
 extern void    header_add(int, const char *, ...);
 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 void    header_add(int, const char *, ...);
 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 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 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);
 extern BOOL    host_is_tls_on_connect_port(int);
 extern int     host_item_get_port(host_item *);
 extern void    host_mask(int, int *, int);
@@ -190,34 +260,51 @@ 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 *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 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_bind(int, int, uschar *, int);
-extern int     ip_connect(int, int, uschar *, int, int);
+extern int     ip_connect(int, int, const uschar *, int, int, const blob *);
 extern int     ip_connectedsocket(int, const uschar *, int, int,
 extern int     ip_connectedsocket(int, const uschar *, int, int,
-                 int, host_item *, uschar **);
+                 int, host_item *, uschar **, const blob *);
 extern int     ip_get_address_family(int);
 extern int     ip_get_address_family(int);
-extern void    ip_keepalive(int, uschar *, BOOL);
-extern int     ip_recv(int, uschar *, int, int);
+extern void    ip_keepalive(int, const uschar *, BOOL);
+extern int     ip_recv(client_conn_ctx *, uschar *, int, int);
 extern int     ip_socket(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 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);
+extern BOOL    macro_read_assignment(uschar *);
+extern uschar *macros_expand(int, int *, BOOL *);
+extern void    mainlog_close(void);
 #ifdef WITH_CONTENT_SCAN
 #ifdef WITH_CONTENT_SCAN
-extern int     malware(uschar **);
+extern int     malware(const uschar *, int);
 extern int     malware_in_file(uschar *);
 extern int     malware_in_file(uschar *);
+extern void    malware_init(void);
+extern void    malware_show_supported(FILE *);
 #endif
 #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_address_list_basic(const uschar *, const uschar **, int);
+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 *);
 extern void    md5_end(md5 *, const uschar *, int, uschar *);
 extern void    md5_mid(md5 *, const uschar *);
 extern void    md5_start(md5 *);
@@ -226,30 +313,34 @@ extern void    millisleep(int);
 struct mime_boundary_context;
 extern int     mime_acl_check(uschar *acl, FILE *f,
                  struct mime_boundary_context *, uschar **, uschar **);
 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 *,
                  BOOL, uschar *);
 extern void    moan_smtp_batch(uschar *, const char *, ...) PRINTF_FUNCTION(2,3);
 #endif
 extern uschar *moan_check_errorcopy(uschar *);
 extern BOOL    moan_skipped_syntax_errors(uschar *, error_block *, uschar *,
                  BOOL, uschar *);
 extern void    moan_smtp_batch(uschar *, const char *, ...) PRINTF_FUNCTION(2,3);
+extern BOOL    moan_send_message(uschar *, int, error_block *eblock,
+                header_line *, FILE *, uschar *);
 extern void    moan_tell_someone(uschar *, address_item *,
                  const uschar *, const char *, ...) PRINTF_FUNCTION(4,5);
 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    moan_tell_someone(uschar *, address_item *,
                  const uschar *, const char *, ...) PRINTF_FUNCTION(4,5);
 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 **,
 
 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_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_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
 extern uschar *parse_date_time(uschar *str, time_t *t);
 extern int     vaguely_random_number(int);
 #ifdef SUPPORT_TLS
@@ -263,9 +354,6 @@ extern void    queue_count(void);
 extern void    queue_run(uschar *, uschar *, BOOL);
 
 extern int     random_number(int);
 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 *);
 extern int     rda_interpret(redirect_block *, int, uschar *, uschar *,
                  uschar *, uschar *, uschar *, ugid_block *, address_item **,
                  uschar **, error_block **, int *, uschar *);
@@ -274,35 +362,38 @@ 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_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_print(uschar *, uschar *, BOOL);
+extern void    readconf_main(BOOL);
+extern void    readconf_options_from_list(optionlist *, unsigned, const uschar *, uschar *);
+extern BOOL    readconf_print(uschar *, uschar *, BOOL);
 extern uschar *readconf_printtime(int);
 extern uschar *readconf_readname(uschar *, int, uschar *);
 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);
 extern BOOL    receive_check_set_sender(uschar *);
 extern BOOL    receive_msg(BOOL);
 extern void    read_message_body(BOOL);
 extern void    receive_bomb_out(uschar *, uschar *);
 extern BOOL    receive_check_fs(int);
 extern BOOL    receive_check_set_sender(uschar *);
 extern BOOL    receive_msg(BOOL);
-extern int     receive_statvfs(BOOL, int *);
+extern int_eximarith_t receive_statvfs(BOOL, int *);
 extern void    receive_swallow_smtp(void);
 #ifdef WITH_CONTENT_SCAN
 extern void    receive_swallow_smtp(void);
 #ifdef WITH_CONTENT_SCAN
-extern int     regex(uschar **);
+extern int     regex(const uschar **);
 #endif
 #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 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 **);
                  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);
                  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 *);
                rewrite_rule *, int, BOOL);
 extern uschar *rewrite_one(uschar *, int, BOOL *, BOOL, uschar *,
                  rewrite_rule *);
@@ -311,139 +402,187 @@ 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);
                  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_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 *,
                  struct passwd **, uid_t *, uschar **);
 extern void    route_init(void);
 extern BOOL    route_find_expanded_group(uschar *, uschar *, uschar *, gid_t *,
                  uschar **);
 extern BOOL    route_find_expanded_user(uschar *, uschar *, uschar *,
                  struct passwd **, uid_t *, uschar **);
 extern void    route_init(void);
+extern void    route_show_supported(FILE *);
 extern void    route_tidyup(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 *);
                  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);
                  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     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 void    smtp_command_timeout_exit(void);
+extern void    smtp_command_sigterm_exit(void);
+extern void    smtp_data_timeout_exit(void);
+extern void    smtp_data_sigint_exit(void);
+extern uschar *smtp_cmd_hist(void);
+extern int     smtp_connect(smtp_connect_args *, const blob *);
+extern int     smtp_sock_connect(host_item *, int, int, uschar *,
+                transport_instance * tb, int, const blob *);
 extern int     smtp_feof(void);
 extern int     smtp_ferror(void);
 extern uschar *smtp_get_connection_info(void);
 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 *);
                  uschar **, uschar *);
 extern BOOL    smtp_get_port(uschar *, address_item *, int *, uschar *);
-extern int     smtp_getc(void);
+extern int     smtp_getc(unsigned);
+extern uschar *smtp_getbuf(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 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 BOOL    smtp_read_response(smtp_inblock *, uschar *, int, int, int);
+extern void    smtp_message_code(uschar **, int *, uschar **, uschar **, BOOL);
+extern void    smtp_proxy_tls(void *, uschar *, size_t, int *, int);
+extern BOOL    smtp_read_response(void *, uschar *, int, int, int);
+extern void    smtp_reset(void *);
 extern void    smtp_respond(uschar *, int, BOOL, uschar *);
 extern void    smtp_notquit_exit(uschar *, uschar *, uschar *, ...);
 extern void    smtp_respond(uschar *, int, BOOL, uschar *);
 extern void    smtp_notquit_exit(uschar *, uschar *, uschar *, ...);
+extern void    smtp_port_for_connect(host_item *, int);
 extern void    smtp_send_prohibition_message(int, uschar *);
 extern int     smtp_setup_msg(void);
 extern BOOL    smtp_start_session(void);
 extern int     smtp_ungetc(int);
 extern BOOL    smtp_verify_helo(void);
 extern void    smtp_send_prohibition_message(int, uschar *);
 extern int     smtp_setup_msg(void);
 extern BOOL    smtp_start_session(void);
 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);
+extern int     smtp_write_command(void *, int, const char *, ...) PRINTF_FUNCTION(3,4);
 #ifdef WITH_CONTENT_SCAN
 #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 *, uschar **);
 #endif
 #endif
+extern void    spool_clear_header_globals(void);
+extern uschar *spool_dname(const uschar *, uschar *);
+extern uschar *spool_fname(const uschar *, const uschar *, const uschar *, const uschar *);
 extern BOOL    spool_move_message(uschar *, uschar *, uschar *, uschar *);
 extern BOOL    spool_move_message(uschar *, uschar *, uschar *, uschar *);
-extern BOOL    spool_open_datafile(uschar *);
+extern int     spool_open_datafile(uschar *);
 extern int     spool_open_temp(uschar *);
 extern int     spool_read_header(uschar *, BOOL, BOOL);
 extern int     spool_open_temp(uschar *);
 extern int     spool_read_header(uschar *, BOOL, BOOL);
+extern uschar *spool_sname(const uschar *, uschar *);
 extern int     spool_write_header(uschar *, int, uschar **);
 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 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 gstring *string_append(gstring *, int, ...) WARN_UNUSED_RESULT;
+extern gstring *string_append_listele(gstring *, uschar, const uschar *) WARN_UNUSED_RESULT;
+extern gstring *string_append_listele_n(gstring *, uschar, const uschar *, unsigned) WARN_UNUSED_RESULT;
+extern gstring *string_append2_listele_n(gstring *, const uschar *, const uschar *, unsigned) WARN_UNUSED_RESULT;
 extern uschar *string_base62(unsigned long int);
 extern uschar *string_base62(unsigned long int);
-extern uschar *string_cat(uschar *, int *, int *, const uschar *, int);
+extern gstring *string_cat (gstring *, const uschar *     ) WARN_UNUSED_RESULT;
+extern gstring *string_catn(gstring *, const uschar *, int) WARN_UNUSED_RESULT;
 extern int     string_compare_by_pointer(const void *, const void *);
 extern uschar *string_copy_dnsdomain(uschar *);
 extern 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_copynlc(uschar *, int);
-extern uschar *string_dequote(uschar **);
+extern uschar *string_dequote(const uschar **);
+extern gstring *string_fmt_append(gstring *, const char *, ...) ALMOST_PRINTF(2,3);
 extern BOOL    string_format(uschar *, int, const char *, ...) ALMOST_PRINTF(3,4);
 extern uschar *string_format_size(int, 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 uschar *string_from_gstring(gstring *);
+extern gstring *string_get(unsigned);
+extern int     string_interpret_escape(const uschar **);
 extern int     string_is_ip_address(const uschar *, int *);
 extern int     string_is_ip_address(const uschar *, int *);
-extern uschar *string_log_address(address_item *, BOOL, BOOL);
-extern uschar *string_nextinlist(uschar **, int *, uschar *, int);
+#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_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_split_message(uschar *);
+extern uschar *string_timediff(struct timeval *);
+extern uschar *string_timesince(struct timeval *);
 extern uschar *string_unprinting(uschar *);
 extern uschar *string_unprinting(uschar *);
-extern BOOL    string_vformat(uschar *, int, const char *, va_list);
+#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 gstring *string_vformat(gstring *, BOOL, 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 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);
+#ifdef EXIM_TFO_PROBE
+extern void    tfo_probe(void);
+#endif
+extern void    timesince(struct timeval * diff, struct timeval * then);
 extern void    tls_modify_variables(tls_support *);
 extern void    tls_modify_variables(tls_support *);
-extern BOOL    transport_check_waiting(uschar *, uschar *, int, uschar *,
-                 BOOL *);
+extern uschar *tod_stamp(int);
+
+extern BOOL    transport_check_waiting(const uschar *, const uschar *, int, uschar *,
+                 BOOL *, oicf, void*);
 extern void    transport_init(void);
 extern void    transport_init(void);
-extern BOOL    transport_pass_socket(uschar *, uschar *, uschar *, uschar *,
+extern void    transport_do_pass_socket(const uschar *, const uschar *,
+                const uschar *, uschar *, int);
+extern BOOL    transport_pass_socket(const uschar *, const uschar *, const uschar *, uschar *,
                  int);
 extern uschar *transport_rcpt_address(address_item *, BOOL);
                  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 void    transport_update_waiting(host_item *, uschar *);
-extern BOOL    transport_write_block(int, uschar *, int);
+extern BOOL    transport_write_block(transport_ctx *, uschar *, int, BOOL);
+extern void    transport_write_reset(int);
 extern BOOL    transport_write_string(int, const char *, ...);
 extern BOOL    transport_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(transport_ctx *,
+                 BOOL (*)(transport_ctx *, uschar *, int));
+extern void    transport_show_supported(FILE *);
+extern BOOL    transport_write_message(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 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
 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_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_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(const uschar **, const 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 **);
 extern BOOL    verify_sender_preliminary(int *, uschar **);
 extern void    version_init(void);
 
 extern address_item *verify_checked_sender(uschar *);
 extern void    verify_get_ident(int);
 extern BOOL    verify_sender(int *, uschar **);
 extern BOOL    verify_sender_preliminary(int *, uschar **);
 extern void    version_init(void);
 
+extern BOOL    write_chunk(transport_ctx *, uschar *, int);
 extern ssize_t write_to_fd_buf(int, const uschar *, size_t);
 
 extern ssize_t write_to_fd_buf(int, const uschar *, size_t);
 
+
 /* vi: aw
 */
 /* End of functions.h */
 /* vi: aw
 */
 /* End of functions.h */
index 08e6e8d..b3362a3 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* All the global variables are defined together in this one module, so
 /* 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)) }
 };
 
                  (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. */
 
 
 /* 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;
 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
 #endif
 
 #ifdef EXPAND_DLFUNC
@@ -83,7 +84,7 @@ uschar *oracle_servers         = NULL;
 uschar *pgsql_servers          = NULL;
 #endif
 
 uschar *pgsql_servers          = NULL;
 #endif
 
-#ifdef EXPERIMENTAL_REDIS
+#ifdef LOOKUP_REDIS
 uschar *redis_servers          = NULL;
 #endif
 
 uschar *redis_servers          = NULL;
 #endif
 
@@ -97,52 +98,55 @@ BOOL    move_frozen_messages   = FALSE;
 
 /* These variables are outside the #ifdef because it keeps the code less
 cluttered in several places (e.g. during logging) if we can always refer to
 
 /* These variables are outside the #ifdef because it keeps the code less
 cluttered in several places (e.g. during logging) if we can always refer to
-them. Also, the tls_ variables are now always visible. */
+them. Also, the tls_ variables are now always visible.  Note that these are
+only used for smtp connections, not for service-daemon access. */
 
 tls_support tls_in = {
 
 tls_support tls_in = {
- -1,   /* tls_active */
- 0,    /* tls_bits */
- FALSE,/* tls_certificate_verified */
- NULL, /* tls_cipher */
- FALSE,/* tls_on_connect */
- NULL, /* tls_on_connect_ports */
- NULL, /* tls_ourcert */
- NULL, /* tls_peercert */
- NULL, /* tls_peerdn */
- NULL, /* tls_sni */
- 0     /* tls_ocsp */
+ .active =             {.sock = -1},
+ .bits =               0,
+ .certificate_verified = FALSE,
+#ifdef SUPPORT_DANE
+ .dane_verified =      FALSE,
+ .tlsa_usage =         0,
+#endif
+ .cipher =             NULL,
+ .on_connect =         FALSE,
+ .on_connect_ports =   NULL,
+ .ourcert =            NULL,
+ .peercert =           NULL,
+ .peerdn =             NULL,
+ .sni =                        NULL,
+ .ocsp =               OCSP_NOT_REQ
 };
 tls_support tls_out = {
 };
 tls_support tls_out = {
- -1,   /* tls_active */
- 0,    /* tls_bits */
- FALSE,/* tls_certificate_verified */
- NULL, /* tls_cipher */
- FALSE,/* tls_on_connect */
- NULL, /* tls_on_connect_ports */
- NULL, /* tls_ourcert */
- NULL, /* tls_peercert */
- NULL, /* tls_peerdn */
- NULL, /* tls_sni */
- 0     /* tls_ocsp */
+ .active =             {.sock = -1},
+ .bits =               0,
+ .certificate_verified = FALSE,
+#ifdef SUPPORT_DANE
+ .dane_verified =      FALSE,
+ .tlsa_usage =         0,
+#endif
+ .cipher =             NULL,
+ .on_connect =         FALSE,
+ .on_connect_ports =   NULL,
+ .ourcert =            NULL,
+ .peercert =           NULL,
+ .peerdn =             NULL,
+ .sni =                        NULL,
+ .ocsp =               OCSP_NOT_REQ
 };
 
 };
 
-#ifdef EXPERIMENTAL_DSN
 uschar *dsn_envid              = NULL;
 int     dsn_ret                = 0;
 const pcre  *regex_DSN         = NULL;
 uschar *dsn_envid              = NULL;
 int     dsn_ret                = 0;
 const pcre  *regex_DSN         = NULL;
-BOOL    smtp_use_dsn           = FALSE;
 uschar *dsn_advertise_hosts    = NULL;
 uschar *dsn_advertise_hosts    = NULL;
-#endif
 
 #ifdef SUPPORT_TLS
 BOOL    gnutls_compat_mode     = FALSE;
 BOOL    gnutls_allow_auto_pkcs11 = FALSE;
 
 #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 *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
 uschar *tls_certificate        = NULL;
 uschar *tls_crl                = NULL;
 /* This default matches NSS DH_MAX_P_BITS value at current time (2012), because
@@ -150,16 +154,23 @@ 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;
 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;
 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_privatekey         = NULL;
 BOOL    tls_remember_esmtp     = FALSE;
 uschar *tls_require_ciphers    = NULL;
+# ifdef EXPERIMENTAL_REQUIRETLS
+uschar  tls_requiretls         = 0;    /* REQUIRETLS_MSG etc. bit #defines */
+uschar *tls_advertise_requiretls = US"*";
+const pcre *regex_REQUIRETLS   = NULL;
+# endif
 uschar *tls_try_verify_hosts   = NULL;
 uschar *tls_try_verify_hosts   = NULL;
-uschar *tls_verify_certificates= NULL;
+uschar *tls_verify_certificates= US"system";
 uschar *tls_verify_hosts       = NULL;
 uschar *tls_verify_hosts       = NULL;
+#else  /*!SUPPORT_TLS*/
+uschar *tls_advertise_hosts    = NULL;
 #endif
 
 #ifndef DISABLE_PRDR
 #endif
 
 #ifndef DISABLE_PRDR
@@ -169,12 +180,21 @@ BOOL    prdr_requested         = FALSE;
 const pcre *regex_PRDR         = NULL;
 #endif
 
 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. */
 
 /* 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;
+#if !defined(STAND_ALONE) && !defined(MACRO_PREDEF)
+int (*lwr_receive_getc)(unsigned) = stdin_getc;
+uschar * (*lwr_receive_getbuf)(unsigned *) = NULL;
+int (*lwr_receive_ungetc)(int) = stdin_ungetc;
+int (*receive_getc)(unsigned)  = stdin_getc;
+uschar * (*receive_getbuf)(unsigned *)  = NULL;
+void (*receive_get_cache)(void)= NULL;
 int (*receive_ungetc)(int)     = stdin_ungetc;
 int (*receive_feof)(void)      = stdin_feof;
 int (*receive_ferror)(void)    = stdin_ferror;
 int (*receive_ungetc)(int)     = stdin_ungetc;
 int (*receive_feof)(void)      = stdin_feof;
 int (*receive_ferror)(void)    = stdin_ferror;
@@ -186,29 +206,244 @@ 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. */
 
 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 **);
 
   NULL };
 
 int address_expansions_count = sizeof(address_expansions)/sizeof(uschar **);
 
-/* General global variables */
+/******************************************************************************/
+/* General global variables.  Boolean flags are done as a group
+so that only one bit each is needed, packed, for all those we never
+need to take a pointer - and only a char for the rest.
+This means a struct, unfortunately since it clutters the sourcecode. */
+
+struct global_flags f =
+{
+       .acl_temp_details       = FALSE,
+       .active_local_from_check = FALSE,
+       .active_local_sender_retain = FALSE,
+       .address_test_mode      = FALSE,
+       .admin_user             = FALSE,
+       .allow_auth_unadvertised= FALSE,
+       .allow_unqualified_recipient = TRUE,    /* For local messages */
+       .allow_unqualified_sender = TRUE,       /* Reset for SMTP */
+       .authentication_local   = FALSE,
+
+       .background_daemon      = TRUE,
+
+       .chunking_offered       = FALSE,
+       .config_changed         = FALSE,
+       .continue_more          = FALSE,
+
+       .daemon_listen          = FALSE,
+       .debug_daemon           = FALSE,
+       .deliver_firsttime      = FALSE,
+       .deliver_force          = FALSE,
+       .deliver_freeze         = FALSE,
+       .deliver_force_thaw     = FALSE,
+       .deliver_manual_thaw    = FALSE,
+       .deliver_selectstring_regex = FALSE,
+       .deliver_selectstring_sender_regex = FALSE,
+       .disable_callout_flush  = FALSE,
+       .disable_delay_flush    = FALSE,
+       .disable_logging        = FALSE,
+#ifndef DISABLE_DKIM
+       .dkim_disable_verify      = FALSE,
+#endif
+#ifdef EXPERIMENTAL_DMARC
+       .dmarc_has_been_checked  = FALSE,
+       .dmarc_disable_verify    = FALSE,
+       .dmarc_enable_forensic   = FALSE,
+#endif
+       .dont_deliver           = FALSE,
+       .dot_ends               = TRUE,
+
+       .enable_dollar_recipients = FALSE,
+       .expand_string_forcedfail = FALSE,
+
+       .filter_running         = FALSE,
+
+       .header_rewritten       = FALSE,
+       .helo_verified          = FALSE,
+       .helo_verify_failed     = FALSE,
+       .host_checking_callout  = FALSE,
+       .host_find_failed_syntax= FALSE,
+
+       .inetd_wait_mode        = FALSE,
+       .is_inetd               = FALSE,
+
+       .local_error_message    = FALSE,
+       .log_testing_mode       = FALSE,
+
+#ifdef WITH_CONTENT_SCAN
+       .no_mbox_unspool        = FALSE,
+#endif
+       .no_multiline_responses = FALSE,
+
+       .parse_allow_group      = FALSE,
+       .parse_found_group      = FALSE,
+       .pipelining_enable      = TRUE,
+#if defined(SUPPORT_PROXY) || defined(SUPPORT_SOCKS)
+       .proxy_session_failed   = FALSE,
+#endif
+
+       .queue_2stage           = FALSE,
+       .queue_only_policy      = FALSE,
+       .queue_run_first_delivery = FALSE,
+       .queue_run_force        = FALSE,
+       .queue_run_local        = FALSE,
+       .queue_running          = FALSE,
+       .queue_smtp             = FALSE,
+
+       .really_exim            = TRUE,
+       .receive_call_bombout   = FALSE,
+       .recipients_discarded   = FALSE,
+       .running_in_test_harness = FALSE,
+
+       .search_find_defer      = FALSE,
+       .sender_address_forced  = FALSE,
+       .sender_host_notsocket  = FALSE,
+       .sender_host_unknown    = FALSE,
+       .sender_local           = FALSE,
+       .sender_name_forced     = FALSE,
+       .sender_set_untrusted   = FALSE,
+       .smtp_authenticated     = FALSE,
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+       .smtp_in_early_pipe_advertised = FALSE,
+       .smtp_in_early_pipe_no_auth = FALSE,
+       .smtp_in_early_pipe_used = FALSE,
+#endif
+       .smtp_in_pipelining_advertised = FALSE,
+       .smtp_in_pipelining_used = FALSE,
+       .spool_file_wireformat  = FALSE,
+       .submission_mode        = FALSE,
+       .suppress_local_fixups  = FALSE,
+       .suppress_local_fixups_default = FALSE,
+       .synchronous_delivery   = FALSE,
+       .system_filtering       = FALSE,
+
+       .tcp_fastopen_ok        = FALSE,
+       .tcp_in_fastopen        = FALSE,
+       .tcp_in_fastopen_data   = FALSE,
+       .tcp_in_fastopen_logged = FALSE,
+       .tcp_out_fastopen_logged= FALSE,
+       .timestamps_utc         = FALSE,
+       .transport_filter_timed_out = FALSE,
+       .trusted_caller         = FALSE,
+       .trusted_config         = TRUE,
+};
+
+/******************************************************************************/
+/* These are the flags which are either variables or mainsection options,
+so an address is needed for access, or are exported to local_scan. */
+
+BOOL    accept_8bitmime        = TRUE; /* deliberately not RFC compliant */
+BOOL    allow_domain_literals  = FALSE;
+BOOL    allow_mx_to_ip         = FALSE;
+BOOL    allow_utf8_domains     = FALSE;
+BOOL    authentication_failed  = FALSE;
+
+BOOL    bounce_return_body     = TRUE;
+BOOL    bounce_return_message  = TRUE;
+BOOL    check_rfc2047_length   = TRUE;
+BOOL    commandline_checks_require_admin = FALSE;
+
+#ifdef EXPERIMENTAL_DCC
+BOOL    dcc_direct_add_header  = FALSE;
+#endif
+BOOL    debug_store            = FALSE;
+BOOL    delivery_date_remove   = TRUE;
+BOOL    deliver_drop_privilege = FALSE;
+#ifdef ENABLE_DISABLE_FSYNC
+BOOL    disable_fsync          = FALSE;
+#endif
+BOOL    disable_ipv6           = FALSE;
+BOOL    dns_csa_use_reverse    = TRUE;
+BOOL    drop_cr                = FALSE;         /* No longer used */
+
+BOOL    envelope_to_remove     = TRUE;
+BOOL    exim_gid_set           = TRUE;          /* This gid is always set */
+BOOL    exim_uid_set           = TRUE;          /* This uid is always set */
+BOOL    extract_addresses_remove_arguments = TRUE;
+
+BOOL    host_checking          = FALSE;
+BOOL    host_lookup_deferred   = FALSE;
+BOOL    host_lookup_failed     = FALSE;
+BOOL    ignore_fromline_local  = FALSE;
+
+BOOL    local_from_check       = TRUE;
+BOOL    local_sender_retain    = FALSE;
+BOOL    log_timezone           = FALSE;
+BOOL    message_body_newlines  = FALSE;
+BOOL    message_logs           = TRUE;
+#ifdef SUPPORT_I18N
+BOOL    message_smtputf8       = FALSE;
+#endif
+BOOL    mua_wrapper            = FALSE;
+
+BOOL    preserve_message_logs  = FALSE;
+BOOL    print_topbitchars      = FALSE;
+BOOL    prod_requires_admin    = TRUE;
+#if defined(SUPPORT_PROXY) || defined(SUPPORT_SOCKS)
+BOOL    proxy_session          = FALSE;
+#endif
+
+BOOL    queue_list_requires_admin = TRUE;
+BOOL    queue_only             = FALSE;
+BOOL    queue_only_load_latch  = TRUE;
+BOOL    queue_only_override    = TRUE;
+BOOL    queue_run_in_order     = FALSE;
+BOOL    recipients_max_reject  = FALSE;
+BOOL    return_path_remove     = TRUE;
+
+BOOL    smtp_batched_input     = FALSE;
+BOOL    sender_helo_dnssec     = FALSE;
+BOOL    sender_host_dnssec     = FALSE;
+BOOL    smtp_accept_keepalive  = TRUE;
+BOOL    smtp_check_spool_space = TRUE;
+BOOL    smtp_enforce_sync      = TRUE;
+BOOL    smtp_etrn_serialize    = TRUE;
+BOOL    smtp_input             = FALSE;
+BOOL    smtp_return_error_details = FALSE;
+#ifdef SUPPORT_SPF
+BOOL    spf_result_guessed     = FALSE;
+#endif
+BOOL    split_spool_directory  = FALSE;
+BOOL    spool_wireformat       = FALSE;
+#ifdef EXPERIMENTAL_SRS
+BOOL    srs_usehash            = TRUE;
+BOOL    srs_usetimestamp       = TRUE;
+#endif
+BOOL    strict_acl_vars        = FALSE;
+BOOL    strip_excess_angle_brackets = FALSE;
+BOOL    strip_trailing_dot     = FALSE;
+BOOL    syslog_duplication     = TRUE;
+BOOL    syslog_pid             = TRUE;
+BOOL    syslog_timestamp       = TRUE;
+BOOL    system_filter_gid_set  = FALSE;
+BOOL    system_filter_uid_set  = FALSE;
+
+BOOL    tcp_nodelay            = TRUE;
+BOOL    write_rejectlog        = TRUE;
+
+/******************************************************************************/
 
 header_line *acl_added_headers = NULL;
 tree_node *acl_anchor          = NULL;
 
 header_line *acl_added_headers = NULL;
 tree_node *acl_anchor          = NULL;
@@ -216,6 +451,8 @@ uschar *acl_arg[9]             = {NULL, NULL, NULL, NULL, NULL,
                                   NULL, NULL, NULL, NULL};
 int     acl_narg               = 0;
 
                                   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;
 uschar *acl_not_smtp           = NULL;
 #ifdef WITH_CONTENT_SCAN
 uschar *acl_not_smtp_mime      = NULL;
@@ -226,7 +463,7 @@ uschar *acl_smtp_auth          = NULL;
 uschar *acl_smtp_connect       = NULL;
 uschar *acl_smtp_data          = NULL;
 #ifndef DISABLE_PRDR
 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;
 #endif
 #ifndef DISABLE_DKIM
 uschar *acl_smtp_dkim          = NULL;
@@ -246,7 +483,6 @@ uschar *acl_smtp_rcpt          = NULL;
 uschar *acl_smtp_starttls      = NULL;
 uschar *acl_smtp_vrfy          = NULL;
 
 uschar *acl_smtp_starttls      = NULL;
 uschar *acl_smtp_vrfy          = NULL;
 
-BOOL    acl_temp_details       = FALSE;
 tree_node *acl_var_c           = NULL;
 tree_node *acl_var_m           = NULL;
 uschar *acl_verify_message     = NULL;
 tree_node *acl_var_c           = NULL;
 tree_node *acl_var_m           = NULL;
 uschar *acl_verify_message     = NULL;
@@ -305,121 +541,126 @@ uschar *acl_wherecodes[]       = { US"550",     /* RCPT */
                                   US"0"        /* unknown; not relevant */
                                  };
 
                                   US"0"        /* unknown; not relevant */
                                  };
 
-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 = {
 uschar *add_environment        = NULL;
 address_item  *addr_duplicate  = NULL;
 
 address_item address_defaults = {
-  NULL,                 /* next */
-  NULL,                 /* parent */
-  NULL,                 /* first */
-  NULL,                 /* dupof */
-  NULL,                 /* start_router */
-  NULL,                 /* router */
-  NULL,                 /* transport */
-  NULL,                 /* host_list */
-  NULL,                 /* host_used */
-  NULL,                 /* fallback_hosts */
-  NULL,                 /* reply */
-  NULL,                 /* retries */
-  NULL,                 /* address */
-  NULL,                 /* unique */
-  NULL,                 /* cc_local_part */
-  NULL,                 /* lc_local_part */
-  NULL,                 /* local_part */
-  NULL,                 /* prefix */
-  NULL,                 /* suffix */
-  NULL,                 /* domain */
-  NULL,                 /* address_retry_key */
-  NULL,                 /* domain_retry_key */
-  NULL,                 /* current_dir */
-  NULL,                 /* home_dir */
-  NULL,                 /* message */
-  NULL,                 /* user_message */
-  NULL,                 /* onetime_parent */
-  NULL,                 /* pipe_expandn */
-  NULL,                 /* return_filename */
-  NULL,                 /* self_hostname */
-  NULL,                 /* shadow_message */
-  #ifdef SUPPORT_TLS
-  NULL,                 /* cipher */
-  NULL,                        /* ourcert */
-  NULL,                        /* peercert */
-  NULL,                 /* peerdn */
-  OCSP_NOT_REQ,         /* ocsp */
-  #endif
-  NULL,                        /* authenticator */
-  NULL,                        /* auth_id */
-  NULL,                        /* auth_sndr */
-  #ifdef EXPERIMENTAL_DSN
-  NULL,                 /* dsn_orcpt */
-  0,                    /* dsn_flags */
-  0,                    /* dsn_aware */
-  #endif
-  (uid_t)(-1),          /* uid */
-  (gid_t)(-1),          /* gid */
-  0,                    /* flags */
-  { 0 },                /* domain_cache - any larger array should be zeroed */
-  { 0 },                /* localpart_cache - ditto */
-  -1,                   /* mode */
-  0,                    /* more_errno */
-  ERRNO_UNKNOWNERROR,   /* basic_errno */
-  0,                    /* child_count */
-  -1,                   /* return_file */
-  SPECIAL_NONE,         /* special_action */
-  DEFER,                /* transport_return */
-  {                     /* fields that are propagated to children */
-    NULL,               /* address_data */
-    NULL,               /* domain_data */
-    NULL,               /* localpart_data */
-    NULL,               /* errors_address */
-    NULL,               /* extra_headers */
-    NULL,               /* remove_headers */
+  .next =              NULL,
+  .parent =            NULL,
+  .first =             NULL,
+  .dupof =             NULL,
+  .start_router =      NULL,
+  .router =            NULL,
+  .transport =         NULL,
+  .host_list =         NULL,
+  .host_used =         NULL,
+  .fallback_hosts =    NULL,
+  .reply =             NULL,
+  .retries =           NULL,
+  .address =           NULL,
+  .unique =            NULL,
+  .cc_local_part =     NULL,
+  .lc_local_part =     NULL,
+  .local_part =                NULL,
+  .prefix =            NULL,
+  .suffix =            NULL,
+  .domain =            NULL,
+  .address_retry_key = NULL,
+  .domain_retry_key =  NULL,
+  .current_dir =       NULL,
+  .home_dir =          NULL,
+  .message =           NULL,
+  .user_message =      NULL,
+  .onetime_parent =    NULL,
+  .pipe_expandn =      NULL,
+  .return_filename =   NULL,
+  .self_hostname =     NULL,
+  .shadow_message =    NULL,
+#ifdef SUPPORT_TLS
+  .cipher =            NULL,
+  .ourcert =           NULL,
+  .peercert =          NULL,
+  .peerdn =            NULL,
+  .ocsp =              OCSP_NOT_REQ,
+#endif
+#ifdef EXPERIMENTAL_DSN_INFO
+  .smtp_greeting =     NULL,
+  .helo_response =     NULL,
+#endif
+  .authenticator =     NULL,
+  .auth_id =           NULL,
+  .auth_sndr =         NULL,
+  .dsn_orcpt =         NULL,
+  .dsn_flags =         0,
+  .dsn_aware =         0,
+  .uid =               (uid_t)(-1),
+  .gid =               (gid_t)(-1),
+  .flags =             { 0 },
+  .domain_cache =      { 0 },                /* domain_cache - any larger array should be zeroed */
+  .localpart_cache =   { 0 },                /* localpart_cache - ditto */
+  .mode =              -1,
+  .more_errno =                0,
+  .delivery_usec =     0,
+  .basic_errno =       ERRNO_UNKNOWNERROR,
+  .child_count =       0,
+  .return_file =       -1,
+  .special_action =    SPECIAL_NONE,
+  .transport_return =  DEFER,
+  .prop = {                                    /* fields that are propagated to children */
+    .address_data =    NULL,
+    .domain_data =     NULL,
+    .localpart_data =  NULL,
+    .errors_address =  NULL,
+    .extra_headers =   NULL,
+    .remove_headers =  NULL,
 #ifdef EXPERIMENTAL_SRS
 #ifdef EXPERIMENTAL_SRS
-    NULL,               /* srs_sender */
+    .srs_sender =      NULL,
+#endif
+    .ignore_error =    FALSE,
+#ifdef SUPPORT_I18N
+    .utf8_msg =                FALSE,
+    .utf8_downcvt =    FALSE,
+    .utf8_downcvt_maybe = FALSE
 #endif
   }
 };
 
 uschar *address_file           = NULL;
 uschar *address_pipe           = NULL;
 #endif
   }
 };
 
 uschar *address_file           = NULL;
 uschar *address_pipe           = NULL;
-BOOL    address_test_mode      = FALSE;
 tree_node *addresslist_anchor  = NULL;
 int     addresslist_count      = 0;
 gid_t  *admin_groups           = NULL;
 tree_node *addresslist_anchor  = NULL;
 int     addresslist_count      = 0;
 gid_t  *admin_groups           = NULL;
-BOOL    admin_user             = FALSE;
-BOOL    allow_auth_unadvertised= FALSE;
-BOOL    allow_domain_literals  = FALSE;
-BOOL    allow_mx_to_ip         = FALSE;
-BOOL    allow_unqualified_recipient = TRUE;    /* For local messages */
-BOOL    allow_unqualified_sender = TRUE;       /* Reset for SMTP */
-BOOL    allow_utf8_domains     = FALSE;
+
+#ifdef EXPERIMENTAL_ARC
+struct arc_set *arc_received   = NULL;
+int     arc_received_instance  = 0;
+int     arc_oldest_pass                = 0;
+const uschar *arc_state                = NULL;
+const uschar *arc_state_reason = NULL;
+#endif
+
 uschar *authenticated_fail_id  = NULL;
 uschar *authenticated_id       = NULL;
 uschar *authenticated_sender   = NULL;
 uschar *authenticated_fail_id  = NULL;
 uschar *authenticated_id       = NULL;
 uschar *authenticated_sender   = NULL;
-BOOL    authentication_failed  = FALSE;
 auth_instance  *auths          = NULL;
 uschar *auth_advertise_hosts   = US"*";
 auth_instance auth_defaults    = {
 auth_instance  *auths          = NULL;
 uschar *auth_advertise_hosts   = US"*";
 auth_instance auth_defaults    = {
-    NULL,                      /* chain pointer */
-    NULL,                      /* name */
-    NULL,                      /* info */
-    NULL,                      /* private options block pointer */
-    NULL,                      /* driver_name */
-    NULL,                      /* advertise_condition */
-    NULL,                      /* client_condition */
-    NULL,                      /* public_name */
-    NULL,                      /* set_id */
-    NULL,                      /* set_client_id */
-    NULL,                      /* server_mail_auth_condition */
-    NULL,                      /* server_debug_string */
-    NULL,                      /* server_condition */
-    FALSE,                     /* client */
-    FALSE,                     /* server */
-    FALSE                      /* advertised */
+    .next =            NULL,
+    .name =            NULL,
+    .info =            NULL,
+    .options_block =   NULL,
+    .driver_name =     NULL,
+    .advertise_condition = NULL,
+    .client_condition =        NULL,
+    .public_name =     NULL,
+    .set_id =          NULL,
+    .set_client_id =   NULL,
+    .mail_auth_condition = NULL,
+    .server_debug_string = NULL,
+    .server_condition =        NULL,
+    .client =          FALSE,
+    .server =          FALSE,
+    .advertised =      FALSE
 };
 
 uschar *auth_defer_msg         = US"reason not recorded";
 };
 
 uschar *auth_defer_msg         = US"reason not recorded";
@@ -427,12 +668,10 @@ uschar *auth_defer_user_msg    = US"";
 uschar *auth_vars[AUTH_VARS];
 int     auto_thaw              = 0;
 #ifdef WITH_CONTENT_SCAN
 uschar *auth_vars[AUTH_VARS];
 int     auto_thaw              = 0;
 #ifdef WITH_CONTENT_SCAN
-BOOL    av_failed              = FALSE;
+int     av_failed              = FALSE;        /* boolean but accessed as vtype_int*/
 uschar *av_scanner             = US"sophie:/var/run/sophie";  /* AV scanner */
 #endif
 
 uschar *av_scanner             = US"sophie:/var/run/sophie";  /* AV scanner */
 #endif
 
-BOOL    background_daemon      = TRUE;
-
 #if BASE_62 == 62
 uschar *base62_chars=
     US"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
 #if BASE_62 == 62
 uschar *base62_chars=
     US"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
@@ -452,109 +691,131 @@ int     bmi_deliver            = 1;
 int     bmi_run                = 0;
 uschar *bmi_verdicts           = NULL;
 #endif
 int     bmi_run                = 0;
 uschar *bmi_verdicts           = NULL;
 #endif
+int     bsmtp_transaction_linecount = 0;
+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;
 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;
-BOOL    bounce_return_message  = TRUE;
+int     bounce_return_linesize_limit = 998;
 int     bounce_return_size_limit = 100*1024;
 uschar *bounce_sender_authentication = NULL;
 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     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;
-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_log_inodes       = 100;
+int_eximarith_t check_log_space = 10*1024;     /* 10K Kbyte == 10MB */
+int     check_spool_inodes     = 100;
+int_eximarith_t check_spool_space = 10*1024;   /* 10K Kbyte == 10MB */
+
+uschar *chunking_advertise_hosts = US"*";
+unsigned chunking_datasize     = 0;
+unsigned chunking_data_left    = 0;
+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];
 int     clmacro_count          = 0;
 uschar *clmacros[MAX_CLMACROS];
-BOOL    config_changed         = FALSE;
 FILE   *config_file            = NULL;
 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;
 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;
 #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;
 
 #ifdef CONFIGURE_OWNER
 uid_t   config_uid             = CONFIGURE_OWNER;
+#else
+uid_t   config_uid             = 0;
 #endif
 
 int     connection_max_messages= -1;
 #endif
 
 int     connection_max_messages= -1;
+uschar *continue_proxy_cipher  = NULL;
 uschar *continue_hostname      = NULL;
 uschar *continue_host_address  = NULL;
 uschar *continue_hostname      = NULL;
 uschar *continue_host_address  = NULL;
-BOOL    continue_more          = FALSE;
 int     continue_sequence      = 1;
 uschar *continue_transport     = NULL;
 
 uschar *csa_status             = NULL;
 int     continue_sequence      = 1;
 uschar *continue_transport     = NULL;
 
 uschar *csa_status             = NULL;
-BOOL    cutthrough_delivery    = FALSE;
-int     cutthrough_fd          = -1;
+cut_t   cutthrough = {
+  .callout_hold_only = FALSE,                          /* verify-only: normal delivery */
+  .delivery =          FALSE,                          /* when to attempt */
+  .defer_pass =                FALSE,                          /* on defer: spool locally */
+  .is_tls =            FALSE,                          /* not a TLS conn yet */
+  .cctx =              {.sock = -1},                   /* open connection */
+  .nrcpt =             0,                              /* number of addresses */
+};
 
 
-BOOL    daemon_listen          = FALSE;
 uschar *daemon_smtp_port       = US"smtp";
 int     daemon_startup_retries = 9;
 int     daemon_startup_sleep   = 30;
 
 #ifdef EXPERIMENTAL_DCC
 uschar *daemon_smtp_port       = US"smtp";
 int     daemon_startup_retries = 9;
 int     daemon_startup_sleep   = 30;
 
 #ifdef EXPERIMENTAL_DCC
-BOOL    dcc_direct_add_header  = FALSE;
 uschar *dcc_header             = NULL;
 uschar *dcc_result             = NULL;
 uschar *dccifd_address         = US"/usr/local/dcc/var/dccifd";
 uschar *dccifd_options         = US"header";
 #endif
 
 uschar *dcc_header             = NULL;
 uschar *dcc_result             = NULL;
 uschar *dccifd_address         = US"/usr/local/dcc/var/dccifd";
 uschar *dccifd_options         = US"header";
 #endif
 
-BOOL    debug_daemon           = FALSE;
 int     debug_fd               = -1;
 FILE   *debug_file             = NULL;
 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,
+  Di_noutf8,
+  -1
+};
+bit_table debug_options[]      = { /* must be in alphabetical order and use
+                                only the enum values from macro.h */
+  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, noutf8),
+  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;
 int     delay_warning[DELAY_WARNING_SIZE] = { DELAY_WARNING_SIZE, 1, 24*60*60 };
 uschar *delay_warning_condition=
 unsigned int debug_selector    = 0;
 int     delay_warning[DELAY_WARNING_SIZE] = { DELAY_WARNING_SIZE, 1, 24*60*60 };
 uschar *delay_warning_condition=
@@ -563,21 +824,17 @@ uschar *delay_warning_condition=
             "{ match{$h_precedence:}{(?i)bulk|list|junk} }"
             "{ match{$h_auto-submitted:}{(?i)auto-generated|auto-replied} }"
             "} {no}{yes}}";
             "{ match{$h_precedence:}{(?i)bulk|list|junk} }"
             "{ match{$h_auto-submitted:}{(?i)auto-generated|auto-replied} }"
             "} {no}{yes}}";
-BOOL    delivery_date_remove   = TRUE;
 uschar *deliver_address_data   = NULL;
 int     deliver_datafile       = -1;
 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_data    = NULL;
-uschar *deliver_domain_orig    = NULL;
-uschar *deliver_domain_parent  = NULL;
-BOOL    deliver_drop_privilege = FALSE;
-BOOL    deliver_firsttime      = FALSE;
-BOOL    deliver_force          = FALSE;
-BOOL    deliver_freeze         = FALSE;
+const uschar *deliver_domain_orig = NULL;
+const uschar *deliver_domain_parent = NULL;
 time_t  deliver_frozen_at      = 0;
 uschar *deliver_home           = NULL;
 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;
 uschar *deliver_in_buffer      = NULL;
 ino_t   deliver_inode          = 0;
 uschar *deliver_localpart      = NULL;
@@ -586,40 +843,26 @@ uschar *deliver_localpart_orig = NULL;
 uschar *deliver_localpart_parent = NULL;
 uschar *deliver_localpart_prefix = NULL;
 uschar *deliver_localpart_suffix = NULL;
 uschar *deliver_localpart_parent = NULL;
 uschar *deliver_localpart_prefix = NULL;
 uschar *deliver_localpart_suffix = NULL;
-BOOL    deliver_force_thaw     = FALSE;
-BOOL    deliver_manual_thaw    = FALSE;
 uschar *deliver_out_buffer     = NULL;
 int     deliver_queue_load_max = -1;
 address_item  *deliver_recipients = NULL;
 uschar *deliver_selectstring   = NULL;
 uschar *deliver_out_buffer     = NULL;
 int     deliver_queue_load_max = -1;
 address_item  *deliver_recipients = NULL;
 uschar *deliver_selectstring   = NULL;
-BOOL    deliver_selectstring_regex = FALSE;
 uschar *deliver_selectstring_sender = NULL;
 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
-BOOL    disable_fsync          = FALSE;
-#endif
-BOOL    disable_ipv6           = FALSE;
-BOOL    disable_logging        = FALSE;
 
 #ifndef DISABLE_DKIM
 
 #ifndef DISABLE_DKIM
+unsigned dkim_collect_input      = 0;
 uschar *dkim_cur_signer          = NULL;
 uschar *dkim_cur_signer          = NULL;
+int     dkim_key_length          = 0;
+void   *dkim_signatures                 = NULL;
 uschar *dkim_signers             = NULL;
 uschar *dkim_signing_domain      = NULL;
 uschar *dkim_signing_selector    = NULL;
 uschar *dkim_signers             = NULL;
 uschar *dkim_signing_domain      = NULL;
 uschar *dkim_signing_selector    = NULL;
+uschar *dkim_verify_overall      = NULL;
 uschar *dkim_verify_signers      = US"$dkim_signers";
 uschar *dkim_verify_signers      = US"$dkim_signers";
-BOOL    dkim_collect_input       = FALSE;
-BOOL    dkim_disable_verify      = FALSE;
+uschar *dkim_verify_status      = NULL;
+uschar *dkim_verify_reason      = NULL;
 #endif
 #ifdef EXPERIMENTAL_DMARC
 #endif
 #ifdef EXPERIMENTAL_DMARC
-BOOL    dmarc_has_been_checked  = FALSE;
-uschar *dmarc_ar_header         = NULL;
 uschar *dmarc_domain_policy     = NULL;
 uschar *dmarc_forensic_sender   = NULL;
 uschar *dmarc_history_file      = NULL;
 uschar *dmarc_domain_policy     = NULL;
 uschar *dmarc_forensic_sender   = NULL;
 uschar *dmarc_history_file      = NULL;
@@ -627,17 +870,19 @@ uschar *dmarc_status            = NULL;
 uschar *dmarc_status_text       = NULL;
 uschar *dmarc_tld_file          = NULL;
 uschar *dmarc_used_domain       = NULL;
 uschar *dmarc_status_text       = NULL;
 uschar *dmarc_tld_file          = NULL;
 uschar *dmarc_used_domain       = NULL;
-BOOL    dmarc_disable_verify    = FALSE;
-BOOL    dmarc_enable_forensic   = FALSE;
 #endif
 
 uschar *dns_again_means_nonexist = NULL;
 int     dns_csa_search_limit   = 5;
 #endif
 
 uschar *dns_again_means_nonexist = NULL;
 int     dns_csa_search_limit   = 5;
-BOOL    dns_csa_use_reverse    = TRUE;
+int    dns_cname_loops        = 1;
+#ifdef SUPPORT_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_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;
 int     dns_use_edns0          = -1; /* <0 = not coerced */
 uschar *dnslist_domain         = NULL;
 uschar *dnslist_matched        = NULL;
@@ -645,32 +890,31 @@ uschar *dnslist_text           = NULL;
 uschar *dnslist_value          = NULL;
 tree_node *domainlist_anchor   = NULL;
 int     domainlist_count       = 0;
 uschar *dnslist_value          = NULL;
 tree_node *domainlist_anchor   = NULL;
 int     domainlist_count       = 0;
-BOOL    dont_deliver           = FALSE;
-BOOL    dot_ends               = TRUE;
-BOOL    drop_cr                = FALSE;         /* No longer used */
 uschar *dsn_from               = US DEFAULT_DSN_FROM;
 
 uschar *dsn_from               = US DEFAULT_DSN_FROM;
 
-BOOL    enable_dollar_recipients = FALSE;
-BOOL    envelope_to_remove     = TRUE;
 int     errno_quota            = ERRNO_QUOTA;
 uschar *errors_copy            = NULL;
 int     error_handling         = ERRORS_SENDER;
 uschar *errors_reply_to        = NULL;
 int     errors_sender_rc       = EXIT_FAILURE;
 int     errno_quota            = ERRNO_QUOTA;
 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;
 
 gid_t   exim_gid               = EXIM_GID;
-BOOL    exim_gid_set           = TRUE;          /* This gid is always set */
 uschar *exim_path              = US BIN_DIRECTORY "/exim"
                         "\0<---------------Space to patch exim_path->";
 uid_t   exim_uid               = EXIM_UID;
 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;
 uschar *expand_nstring[EXPAND_MAXN+1];
 int     expand_forbid          = 0;
 int     expand_nlength[EXPAND_MAXN+1];
 int     expand_nmax            = -1;
 uschar *expand_nstring[EXPAND_MAXN+1];
-BOOL    expand_string_forcedfail = FALSE;
 uschar *expand_string_message;
 uschar *expand_string_message;
-BOOL    extract_addresses_remove_arguments = TRUE;
 uschar *extra_local_interfaces = NULL;
 
 int     fake_response          = OK;
 uschar *extra_local_interfaces = NULL;
 
 int     fake_response          = OK;
@@ -679,16 +923,12 @@ uschar *fake_response_text     = US"Your message has been rejected but is "
                                    "legitimate message, it may still be "
                                    "delivered to the target recipient(s).";
 int     filter_n[FILTER_VARIABLE_COUNT];
                                    "legitimate message, it may still be "
                                    "delivered to the target recipient(s).";
 int     filter_n[FILTER_VARIABLE_COUNT];
-BOOL    filter_running         = FALSE;
 int     filter_sn[FILTER_VARIABLE_COUNT];
 int     filter_test            = FTEST_NONE;
 uschar *filter_test_sfile      = NULL;
 uschar *filter_test_ufile      = NULL;
 uschar *filter_thisaddress     = NULL;
 int     finduser_retries       = 0;
 int     filter_sn[FILTER_VARIABLE_COUNT];
 int     filter_test            = FTEST_NONE;
 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;
 uid_t   fixed_never_users[]    = { FIXED_NEVER_USERS };
 uschar *freeze_tell            = NULL;
 uschar *freeze_tell_config     = NULL;
@@ -698,6 +938,10 @@ uschar *gecos_name             = NULL;
 uschar *gecos_pattern          = NULL;
 rewrite_rule  *global_rewrite_rules = NULL;
 
 uschar *gecos_pattern          = NULL;
 rewrite_rule  *global_rewrite_rules = NULL;
 
+volatile sig_atomic_t had_command_timeout = 0;
+volatile sig_atomic_t had_command_sigterm = 0;
+volatile sig_atomic_t had_data_timeout    = 0;
+volatile sig_atomic_t had_data_sigint     = 0;
 uschar *headers_charset        = US HEADERS_CHARSET;
 int     header_insert_maxlen   = 64 * 1024;
 header_line  *header_last      = NULL;
 uschar *headers_charset        = US HEADERS_CHARSET;
 int     header_insert_maxlen   = 64 * 1024;
 header_line  *header_last      = NULL;
@@ -706,40 +950,33 @@ int     header_maxsize         = HEADER_MAXSIZE;
 int     header_line_maxsize    = 0;
 
 header_name header_names[] = {
 int     header_line_maxsize    = 0;
 
 header_name header_names[] = {
-  { US"bcc",            3, TRUE,  htype_bcc },
-  { US"cc",             2, TRUE,  htype_cc },
-  { US"date",           4, TRUE,  htype_date },
-  { US"delivery-date", 13, FALSE, htype_delivery_date },
-  { US"envelope-to",   11, FALSE, htype_envelope_to },
-  { US"from",           4, TRUE,  htype_from },
-  { US"message-id",    10, TRUE,  htype_id },
-  { US"received",       8, FALSE, htype_received },
-  { US"reply-to",       8, FALSE, htype_reply_to },
-  { US"return-path",   11, FALSE, htype_return_path },
-  { US"sender",         6, TRUE,  htype_sender },
-  { US"subject",        7, FALSE, htype_subject },
-  { US"to",             2, TRUE,  htype_to }
+  /* name              len     allow_resent    htype */
+  { US"bcc",            3,     TRUE,           htype_bcc },
+  { US"cc",             2,     TRUE,           htype_cc },
+  { US"date",           4,     TRUE,           htype_date },
+  { US"delivery-date", 13,     FALSE,          htype_delivery_date },
+  { US"envelope-to",   11,     FALSE,          htype_envelope_to },
+  { US"from",           4,     TRUE,           htype_from },
+  { US"message-id",    10,     TRUE,           htype_id },
+  { US"received",       8,     FALSE,          htype_received },
+  { US"reply-to",       8,     FALSE,          htype_reply_to },
+  { US"return-path",   11,     FALSE,          htype_return_path },
+  { US"sender",         6,     TRUE,           htype_sender },
+  { US"subject",        7,     FALSE,          htype_subject },
+  { US"to",             2,     TRUE,           htype_to }
 };
 
 };
 
-int header_names_size          = sizeof(header_names)/sizeof(header_name);
+int header_names_size          = nelem(header_names);
 
 
-BOOL    header_rewritten       = FALSE;
 uschar *helo_accept_junk_hosts = NULL;
 uschar *helo_allow_chars       = US"";
 uschar *helo_lookup_domains    = US"@ : @[]";
 uschar *helo_try_verify_hosts  = NULL;
 uschar *helo_accept_junk_hosts = NULL;
 uschar *helo_allow_chars       = US"";
 uschar *helo_lookup_domains    = US"@ : @[]";
 uschar *helo_try_verify_hosts  = NULL;
-BOOL    helo_verified          = FALSE;
-BOOL    helo_verify_failed     = FALSE;
 uschar *helo_verify_hosts      = NULL;
 const uschar *hex_digits       = CUS"0123456789abcdef";
 uschar *hold_domains           = NULL;
 uschar *helo_verify_hosts      = NULL;
 const uschar *hex_digits       = CUS"0123456789abcdef";
 uschar *hold_domains           = NULL;
-BOOL    host_checking          = FALSE;
-BOOL    host_checking_callout  = FALSE;
 uschar *host_data              = NULL;
 uschar *host_data              = NULL;
-BOOL    host_find_failed_syntax= FALSE;
 uschar *host_lookup            = NULL;
 uschar *host_lookup            = NULL;
-BOOL    host_lookup_deferred   = FALSE;
-BOOL    host_lookup_failed     = FALSE;
 uschar *host_lookup_order      = US"bydns:byaddr";
 uschar *host_lookup_msg        = US"";
 int     host_number            = 0;
 uschar *host_lookup_order      = US"bydns:byaddr";
 uschar *host_lookup_msg        = US"";
 int     host_number            = 0;
@@ -751,13 +988,11 @@ uschar *hosts_treat_as_local   = NULL;
 uschar *hosts_connection_nolog = NULL;
 
 int     ignore_bounce_errors_after = 10*7*24*60*60;  /* 10 weeks */
 uschar *hosts_connection_nolog = NULL;
 
 int     ignore_bounce_errors_after = 10*7*24*60*60;  /* 10 weeks */
-BOOL    ignore_fromline_local  = FALSE;
 uschar *ignore_fromline_hosts  = NULL;
 uschar *ignore_fromline_hosts  = NULL;
-BOOL    inetd_wait_mode        = FALSE;
 int     inetd_wait_timeout     = -1;
 int     inetd_wait_timeout     = -1;
+uschar *initial_cwd            = NULL;
 uschar *interface_address      = NULL;
 int     interface_port         = -1;
 uschar *interface_address      = NULL;
 int     interface_port         = -1;
-BOOL    is_inetd               = FALSE;
 uschar *iterate_item           = NULL;
 
 int     journal_fd             = -1;
 uschar *iterate_item           = NULL;
 
 int     journal_fd             = -1;
@@ -768,8 +1003,6 @@ int     keep_malformed         = 4*24*60*60;    /* 4 days */
 
 uschar *eldap_dn               = NULL;
 int     load_average           = -2;
 
 uschar *eldap_dn               = NULL;
 int     load_average           = -2;
-BOOL    local_error_message    = FALSE;
-BOOL    local_from_check       = TRUE;
 uschar *local_from_prefix      = NULL;
 uschar *local_from_suffix      = NULL;
 
 uschar *local_from_prefix      = NULL;
 uschar *local_from_suffix      = NULL;
 
@@ -779,93 +1012,116 @@ uschar *local_interfaces       = US"<; ::0 ; 0.0.0.0";
 uschar *local_interfaces       = US"0.0.0.0";
 #endif
 
 uschar *local_interfaces       = US"0.0.0.0";
 #endif
 
+#ifdef HAVE_LOCAL_SCAN
 uschar *local_scan_data        = NULL;
 int     local_scan_timeout     = 5*60;
 uschar *local_scan_data        = NULL;
 int     local_scan_timeout     = 5*60;
-BOOL    local_sender_retain    = FALSE;
+#endif
 gid_t   local_user_gid         = (gid_t)(-1);
 uid_t   local_user_uid         = (uid_t)(-1);
 
 tree_node *localpartlist_anchor= NULL;
 int     localpartlist_count    = 0;
 uschar *log_buffer             = NULL;
 gid_t   local_user_gid         = (gid_t)(-1);
 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_dkim,
+  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->";
 
 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 },
-#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 }
+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),
+#ifndef DISABLE_DKIM
+  BIT_TABLE(L, dkim),
+  BIT_TABLE(L, dkim_verbose),
+#endif
+  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, millisec),
+  BIT_TABLE(L, outgoing_interface),
+  BIT_TABLE(L, outgoing_port),
+  BIT_TABLE(L, pid),
+  BIT_TABLE(L, pipelining),
+#if defined(SUPPORT_PROXY) || defined(SUPPORT_SOCKS)
+  BIT_TABLE(L, proxy),
+#endif
+  BIT_TABLE(L, queue_run),
+  BIT_TABLE(L, queue_time),
+  BIT_TABLE(L, queue_time_overall),
+  BIT_TABLE(L, receive_time),
+  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;
 int     log_reject_target      = 0;
+unsigned int log_selector[log_selector_size]; /* initialized in main() */
 uschar *log_selector_string    = NULL;
 FILE   *log_stderr             = NULL;
 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;
 
 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 *macros_user        = NULL;
 uschar *mailstore_basename     = NULL;
 #ifdef WITH_CONTENT_SCAN
 uschar *malware_name           = NULL;  /* Virus Name */
 uschar *mailstore_basename     = NULL;
 #ifdef WITH_CONTENT_SCAN
 uschar *malware_name           = NULL;  /* Virus Name */
@@ -875,7 +1131,6 @@ int     max_username_length    = 0;
 int     message_age            = 0;
 uschar *message_body           = NULL;
 uschar *message_body_end       = NULL;
 int     message_age            = 0;
 uschar *message_body           = NULL;
 uschar *message_body_end       = NULL;
-BOOL    message_body_newlines  = FALSE;
 int     message_body_size      = 0;
 int     message_body_visible   = 500;
 int     message_ended          = END_NOTSTARTED;
 int     message_body_size      = 0;
 int     message_body_visible   = 500;
 int     message_ended          = END_NOTSTARTED;
@@ -887,9 +1142,11 @@ struct timeval message_id_tv   = { 0, 0 };
 uschar  message_id_option[MESSAGE_ID_LENGTH + 3];
 uschar *message_id_external;
 int     message_linecount      = 0;
 uschar  message_id_option[MESSAGE_ID_LENGTH + 3];
 uschar *message_id_external;
 int     message_linecount      = 0;
-BOOL    message_logs           = TRUE;
 int     message_size           = 0;
 uschar *message_size_limit     = US"50M";
 int     message_size           = 0;
 uschar *message_size_limit     = US"50M";
+#ifdef SUPPORT_I18N
+int     message_utf8_downconvert = 0;  /* -1 ifneeded; 0 never; 1 always */
+#endif
 uschar  message_subdir[2]      = { 0, 0 };
 uschar *message_reference      = NULL;
 
 uschar  message_subdir[2]      = { 0, 0 };
 uschar *message_reference      = NULL;
 
@@ -913,13 +1170,10 @@ int     mime_is_rfc822         = 0;
 int     mime_part_count        = -1;
 #endif
 
 int     mime_part_count        = -1;
 #endif
 
-BOOL    mua_wrapper            = FALSE;
-
 uid_t  *never_users            = NULL;
 uid_t  *never_users            = NULL;
-#ifdef WITH_CONTENT_SCAN
-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;
 
 uid_t   original_euid;
 gid_t   originator_gid;
@@ -929,29 +1183,24 @@ uid_t   originator_uid;
 uschar *override_local_interfaces = NULL;
 uschar *override_pid_file_path = NULL;
 
 uschar *override_local_interfaces = NULL;
 uschar *override_pid_file_path = NULL;
 
-BOOL    parse_allow_group      = FALSE;
-BOOL    parse_found_group      = FALSE;
 uschar *percent_hack_domains   = NULL;
 uschar *pid_file_path          = US PID_FILE_PATH
                            "\0<--------------Space to patch pid_file_path->";
 uschar *percent_hack_domains   = NULL;
 uschar *pid_file_path          = US PID_FILE_PATH
                            "\0<--------------Space to patch pid_file_path->";
-BOOL    pipelining_enable      = TRUE;
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+uschar *pipe_connect_advertise_hosts = US"*";
+#endif
 uschar *pipelining_advertise_hosts = US"*";
 uschar *pipelining_advertise_hosts = US"*";
-BOOL    preserve_message_logs  = FALSE;
 uschar *primary_hostname       = NULL;
 uschar *primary_hostname       = NULL;
-BOOL    print_topbitchars      = FALSE;
 uschar  process_info[PROCESS_INFO_SIZE];
 int     process_info_len       = 0;
 uschar *process_log_path       = NULL;
 uschar  process_info[PROCESS_INFO_SIZE];
 int     process_info_len       = 0;
 uschar *process_log_path       = NULL;
-BOOL    prod_requires_admin    = TRUE;
 
 
-#ifdef EXPERIMENTAL_PROXY
-uschar *proxy_host_address     = US"";
-int     proxy_host_port        = 0;
-uschar *proxy_required_hosts   = US"";
-BOOL    proxy_session          = FALSE;
-BOOL    proxy_session_failed   = FALSE;
-uschar *proxy_target_address   = US"";
-int     proxy_target_port      = 0;
+#if defined(SUPPORT_PROXY) || defined(SUPPORT_SOCKS)
+uschar *hosts_proxy            = NULL;
+uschar *proxy_external_address = NULL;
+int     proxy_external_port    = 0;
+uschar *proxy_local_address    = NULL;
+int     proxy_local_port       = 0;
 #endif
 
 uschar *prvscheck_address      = NULL;
 #endif
 
 uschar *prvscheck_address      = NULL;
@@ -959,30 +1208,19 @@ uschar *prvscheck_keynum       = NULL;
 uschar *prvscheck_result       = NULL;
 
 
 uschar *prvscheck_result       = NULL;
 
 
-uschar *qualify_domain_recipient = NULL;
+const uschar *qualify_domain_recipient = NULL;
 uschar *qualify_domain_sender  = NULL;
 uschar *qualify_domain_sender  = NULL;
-BOOL    queue_2stage           = FALSE;
 uschar *queue_domains          = NULL;
 int     queue_interval         = -1;
 uschar *queue_domains          = NULL;
 int     queue_interval         = -1;
-BOOL    queue_list_requires_admin = TRUE;
-BOOL    queue_only             = FALSE;
+uschar *queue_name             = US"";
 uschar *queue_only_file        = NULL;
 int     queue_only_load        = -1;
 uschar *queue_only_file        = NULL;
 int     queue_only_load        = -1;
-BOOL    queue_only_load_latch  = TRUE;
-BOOL    queue_only_override    = TRUE;
-BOOL    queue_only_policy      = FALSE;
-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;
 pid_t   queue_run_pid          = (pid_t)0;
 int     queue_run_pipe         = -1;
-BOOL    queue_running          = FALSE;
-BOOL    queue_smtp             = FALSE;
 uschar *queue_smtp_domains     = NULL;
 
 uschar *queue_smtp_domains     = NULL;
 
-unsigned int random_seed       = 0;
+uint32_t random_seed          = 0;
 tree_node *ratelimiters_cmd    = NULL;
 tree_node *ratelimiters_conn   = NULL;
 tree_node *ratelimiters_mail   = NULL;
 tree_node *ratelimiters_cmd    = NULL;
 tree_node *ratelimiters_conn   = NULL;
 tree_node *ratelimiters_mail   = NULL;
@@ -996,8 +1234,6 @@ int     rcpt_fail_count        = 0;
 int     rcpt_defer_count       = 0;
 gid_t   real_gid;
 uid_t   real_uid;
 int     rcpt_defer_count       = 0;
 gid_t   real_gid;
 uid_t   real_uid;
-BOOL    really_exim            = TRUE;
-BOOL    receive_call_bombout   = FALSE;
 int     receive_linecount      = 0;
 int     receive_messagecount   = 0;
 int     receive_timeout        = 0;
 int     receive_linecount      = 0;
 int     receive_messagecount   = 0;
 int     receive_timeout        = 0;
@@ -1025,24 +1261,27 @@ uschar *received_header_text   = US
 
 int     received_headers_max   = 30;
 uschar *received_protocol      = NULL;
 
 int     received_headers_max   = 30;
 uschar *received_protocol      = NULL;
-int     received_time          = 0;
+struct timeval received_time   = { 0, 0 };
+struct timeval received_time_taken = { 0, 0 };
 uschar *recipient_data         = NULL;
 uschar *recipient_unqualified_hosts = NULL;
 uschar *recipient_verify_failure = NULL;
 int     recipients_count       = 0;
 uschar *recipient_data         = NULL;
 uschar *recipient_unqualified_hosts = NULL;
 uschar *recipient_verify_failure = NULL;
 int     recipients_count       = 0;
-BOOL    recipients_discarded   = FALSE;
 recipient_item  *recipients_list = NULL;
 int     recipients_list_max    = 0;
 int     recipients_max         = 0;
 recipient_item  *recipients_list = NULL;
 int     recipients_list_max    = 0;
 int     recipients_max         = 0;
-BOOL    recipients_max_reject  = FALSE;
 const pcre *regex_AUTH         = NULL;
 const pcre *regex_check_dns_names = NULL;
 const pcre *regex_From         = NULL;
 const pcre *regex_IGNOREQUOTA  = NULL;
 const pcre *regex_PIPELINING   = NULL;
 const pcre *regex_SIZE         = NULL;
 const pcre *regex_AUTH         = NULL;
 const pcre *regex_check_dns_names = NULL;
 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;
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+const pcre *regex_EARLY_PIPE   = NULL;
+#endif
 const pcre *regex_ismsgid      = 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
 #ifdef WHITELIST_D_MACROS
 const pcre *regex_whitelisted_macro = NULL;
 #endif
@@ -1057,99 +1296,96 @@ int     retry_interval_max     = 24*60*60;
 int     retry_maximum_timeout  = 0;        /* set from retry config */
 retry_config  *retries         = NULL;
 uschar *return_path            = NULL;
 int     retry_maximum_timeout  = 0;        /* set from retry config */
 retry_config  *retries         = NULL;
 uschar *return_path            = NULL;
-BOOL    return_path_remove     = TRUE;
 int     rewrite_existflags     = 0;
 int     rewrite_existflags     = 0;
-uschar *rfc1413_hosts          = US"*";
-int     rfc1413_query_timeout  = 5;
-/* BOOL    rfc821_domains         = FALSE;  <<< on the way out */
+uschar *rfc1413_hosts          = US"@[]";
+int     rfc1413_query_timeout  = 0;
 uid_t   root_gid               = ROOT_GID;
 uid_t   root_uid               = ROOT_UID;
 
 router_instance  *routers  = NULL;
 router_instance  router_defaults = {
 uid_t   root_gid               = ROOT_GID;
 uid_t   root_uid               = ROOT_UID;
 
 router_instance  *routers  = NULL;
 router_instance  router_defaults = {
-    NULL,                      /* chain pointer */
-    NULL,                      /* name */
-    NULL,                      /* info */
-    NULL,                      /* private options block pointer */
-    NULL,                      /* driver name */
+    .next =                    NULL,
+    .name =                    NULL,
+    .info =                    NULL,
+    .options_block =           NULL,
+    .driver_name =             NULL,
 
 
-    NULL,                      /* address_data */
+    .address_data =            NULL,
 #ifdef EXPERIMENTAL_BRIGHTMAIL
 #ifdef EXPERIMENTAL_BRIGHTMAIL
-    NULL,                      /* bmi_rule */
-#endif
-    NULL,                      /* cannot_route_message */
-    NULL,                      /* condition */
-    NULL,                      /* current_directory */
-    NULL,                      /* debug_string */
-    NULL,                      /* domains */
-    NULL,                      /* errors_to */
-    NULL,                      /* expand_gid */
-    NULL,                      /* expand_uid */
-    NULL,                      /* expand_more */
-    NULL,                      /* expand_unseen */
-    NULL,                      /* extra_headers */
-    NULL,                      /* fallback_hosts */
-    NULL,                      /* home_directory */
-    NULL,                      /* ignore_target_hosts */
-    NULL,                      /* local_parts */
-    NULL,                      /* pass_router_name */
-    NULL,                      /* prefix */
-    NULL,                      /* redirect_router_name */
-    NULL,                      /* remove_headers */
-    NULL,                      /* require_files */
-    NULL,                      /* router_home_directory */
-    US"freeze",                /* self */
-    NULL,                      /* senders */
-    NULL,                      /* suffix */
-    NULL,                      /* translate_ip_address */
-    NULL,                      /* transport_name */
-
-    TRUE,                      /* address_test */
+    .bmi_rule =                        NULL,
+#endif
+    .cannot_route_message =    NULL,
+    .condition =               NULL,
+    .current_directory =       NULL,
+    .debug_string =            NULL,
+    .domains =                 NULL,
+    .errors_to =               NULL,
+    .expand_gid =              NULL,
+    .expand_uid =              NULL,
+    .expand_more =             NULL,
+    .expand_unseen =           NULL,
+    .extra_headers =           NULL,
+    .fallback_hosts =          NULL,
+    .home_directory =          NULL,
+    .ignore_target_hosts =     NULL,
+    .local_parts =             NULL,
+    .pass_router_name =                NULL,
+    .prefix =                  NULL,
+    .redirect_router_name =    NULL,
+    .remove_headers =          NULL,
+    .require_files =           NULL,
+    .router_home_directory =   NULL,
+    .self =                    US"freeze",
+    .senders =                 NULL,
+    .suffix =                  NULL,
+    .translate_ip_address =    NULL,
+    .transport_name =          NULL,
+
+    .address_test =            TRUE,
 #ifdef EXPERIMENTAL_BRIGHTMAIL
 #ifdef EXPERIMENTAL_BRIGHTMAIL
-    FALSE,                     /* bmi_deliver_alternate */
-    FALSE,                     /* bmi_deliver_default */
-    FALSE,                     /* bmi_dont_deliver */
-#endif
-    TRUE,                      /* expn */
-    FALSE,                     /* caseful_local_part */
-    FALSE,                     /* check_local_user */
-    FALSE,                     /* disable_logging */
-    FALSE,                     /* fail_verify_recipient */
-    FALSE,                     /* fail_verify_sender */
-    FALSE,                     /* gid_set */
-    FALSE,                     /* initgroups */
-    TRUE_UNSET,                /* log_as_local */
-    TRUE,                      /* more */
-    FALSE,                     /* pass_on_timeout */
-    FALSE,                     /* prefix_optional */
-    TRUE,                      /* repeat_use */
-    TRUE_UNSET,                /* retry_use_local_part - fudge "unset" */
-    FALSE,                     /* same_domain_copy_routing */
-    FALSE,                     /* self_rewrite */
-    FALSE,                     /* suffix_optional */
-    FALSE,                     /* verify_only */
-    TRUE,                      /* verify_recipient */
-    TRUE,                      /* verify_sender */
-    FALSE,                     /* uid_set */
-    FALSE,                     /* unseen */
-#ifdef EXPERIMENTAL_DSN
-    FALSE,                     /* dsn_lasthop */
-#endif
-
-    self_freeze,               /* self_code */
-    (uid_t)(-1),               /* uid */
-    (gid_t)(-1),               /* gid */
-
-    NULL,                      /* fallback_hostlist */
-    NULL,                      /* transport instance */
-    NULL,                      /* pass_router */
-    NULL                       /* redirect_router */
+    .bmi_deliver_alternate =   FALSE,
+    .bmi_deliver_default =     FALSE,
+    .bmi_dont_deliver =                FALSE,
+#endif
+    .expn =                    TRUE,
+    .caseful_local_part =      FALSE,
+    .check_local_user =                FALSE,
+    .disable_logging =         FALSE,
+    .fail_verify_recipient =   FALSE,
+    .fail_verify_sender =      FALSE,
+    .gid_set =                 FALSE,
+    .initgroups =              FALSE,
+    .log_as_local =            TRUE_UNSET,
+    .more =                    TRUE,
+    .pass_on_timeout =         FALSE,
+    .prefix_optional =         FALSE,
+    .repeat_use =              TRUE,
+    .retry_use_local_part =    TRUE_UNSET,
+    .same_domain_copy_routing =        FALSE,
+    .self_rewrite =            FALSE,
+    .suffix_optional =         FALSE,
+    .verify_only =             FALSE,
+    .verify_recipient =                TRUE,
+    .verify_sender =           TRUE,
+    .uid_set =                 FALSE,
+    .unseen =                  FALSE,
+    .dsn_lasthop =             FALSE,
+
+    .self_code =               self_freeze,
+    .uid =                     (uid_t)(-1),
+    .gid =                     (gid_t)(-1),
+
+    .fallback_hostlist =       NULL,
+    .transport =               NULL,
+    .pass_router =             NULL,
+    .redirect_router =         NULL,
+
+    .dnssec =                  { NULL, NULL },            /* dnssec_domains {require,request} */
 };
 
 uschar *router_name            = NULL;
 
 ip_address_item *running_interfaces = NULL;
 };
 
 uschar *router_name            = NULL;
 
 ip_address_item *running_interfaces = NULL;
-BOOL    running_in_test_harness = FALSE;
 
 /* This is a weird one. The following string gets patched in the binary by the
 script that sets up a copy of Exim for running in the test harness. It seems
 
 /* This is a weird one. The following string gets patched in the binary by the
 script that sets up a copy of Exim for running in the test harness. It seems
@@ -1164,12 +1400,10 @@ uschar *running_status         = US">>>running<<<" "\0EXTRA";
 int     runrc                  = 0;
 
 uschar *search_error_message   = NULL;
 int     runrc                  = 0;
 
 uschar *search_error_message   = NULL;
-BOOL    search_find_defer      = FALSE;
 uschar *self_hostname          = NULL;
 uschar *sender_address         = NULL;
 unsigned int sender_address_cache[(MAX_NAMED_LIST * 2)/32];
 uschar *sender_address_data    = NULL;
 uschar *self_hostname          = NULL;
 uschar *sender_address         = NULL;
 unsigned int sender_address_cache[(MAX_NAMED_LIST * 2)/32];
 uschar *sender_address_data    = NULL;
-BOOL    sender_address_forced  = FALSE;
 uschar *sender_address_unrewritten = NULL;
 uschar *sender_data            = NULL;
 unsigned int sender_domain_cache[(MAX_NAMED_LIST * 2)/32];
 uschar *sender_address_unrewritten = NULL;
 uschar *sender_data            = NULL;
 unsigned int sender_domain_cache[(MAX_NAMED_LIST * 2)/32];
@@ -1178,32 +1412,27 @@ uschar *sender_helo_name       = NULL;
 uschar **sender_host_aliases   = &no_aliases;
 uschar *sender_host_address    = NULL;
 uschar *sender_host_authenticated = NULL;
 uschar **sender_host_aliases   = &no_aliases;
 uschar *sender_host_address    = NULL;
 uschar *sender_host_authenticated = NULL;
+uschar *sender_host_auth_pubname  = NULL;
 unsigned int sender_host_cache[(MAX_NAMED_LIST * 2)/32];
 unsigned int sender_host_cache[(MAX_NAMED_LIST * 2)/32];
-BOOL    sender_host_dnssec     = FALSE;
 uschar *sender_host_name       = NULL;
 int     sender_host_port       = 0;
 uschar *sender_host_name       = NULL;
 int     sender_host_port       = 0;
-BOOL    sender_host_notsocket  = FALSE;
-BOOL    sender_host_unknown    = FALSE;
 uschar *sender_ident           = NULL;
 uschar *sender_ident           = NULL;
-BOOL    sender_local           = FALSE;
-BOOL    sender_name_forced     = FALSE;
 uschar *sender_rate            = NULL;
 uschar *sender_rate_limit      = NULL;
 uschar *sender_rate_period     = NULL;
 uschar *sender_rcvhost         = NULL;
 uschar *sender_rate            = NULL;
 uschar *sender_rate_limit      = NULL;
 uschar *sender_rate_period     = NULL;
 uschar *sender_rcvhost         = NULL;
-BOOL    sender_set_untrusted   = FALSE;
 uschar *sender_unqualified_hosts = NULL;
 uschar *sender_verify_failure = NULL;
 address_item *sender_verified_list  = NULL;
 address_item *sender_verified_failed = NULL;
 int     sender_verified_rc     = -1;
 uschar *sender_unqualified_hosts = NULL;
 uschar *sender_verify_failure = NULL;
 address_item *sender_verified_list  = NULL;
 address_item *sender_verified_failed = NULL;
 int     sender_verified_rc     = -1;
-BOOL    sender_verified_responded = FALSE;
 uschar *sending_ip_address     = NULL;
 int     sending_port           = -1;
 SIGNAL_BOOL sigalrm_seen       = FALSE;
 uschar *sending_ip_address     = NULL;
 int     sending_port           = -1;
 SIGNAL_BOOL sigalrm_seen       = FALSE;
+const uschar *sigalarm_setter  = NULL;
 uschar **sighup_argv           = NULL;
 uschar **sighup_argv           = NULL;
+int     slow_lookup_log        = 0;    /* millisecs, zero disables */
 int     smtp_accept_count      = 0;
 int     smtp_accept_count      = 0;
-BOOL    smtp_accept_keepalive  = TRUE;
 int     smtp_accept_max        = 20;
 int     smtp_accept_max_nonmail= 10;
 uschar *smtp_accept_max_nonmail_hosts = US"*";
 int     smtp_accept_max        = 20;
 int     smtp_accept_max_nonmail= 10;
 uschar *smtp_accept_max_nonmail_hosts = US"*";
@@ -1213,28 +1442,22 @@ int     smtp_accept_queue      = 0;
 int     smtp_accept_queue_per_connection = 10;
 int     smtp_accept_reserve    = 0;
 uschar *smtp_active_hostname   = NULL;
 int     smtp_accept_queue_per_connection = 10;
 int     smtp_accept_reserve    = 0;
 uschar *smtp_active_hostname   = NULL;
-BOOL    smtp_authenticated     = FALSE;
 uschar *smtp_banner            = US"$smtp_active_hostname ESMTP "
                              "Exim $version_number $tod_full"
                              "\0<---------------Space to patch smtp_banner->";
 uschar *smtp_banner            = US"$smtp_active_hostname ESMTP "
                              "Exim $version_number $tod_full"
                              "\0<---------------Space to patch smtp_banner->";
-BOOL    smtp_batched_input     = FALSE;
-BOOL    smtp_check_spool_space = TRUE;
 int     smtp_ch_index          = 0;
 uschar *smtp_cmd_argument      = NULL;
 uschar *smtp_cmd_buffer        = NULL;
 int     smtp_ch_index          = 0;
 uschar *smtp_cmd_argument      = NULL;
 uschar *smtp_cmd_buffer        = NULL;
-time_t  smtp_connection_start  = 0;
+struct timeval smtp_connection_start  = {0,0};
 uschar  smtp_connection_had[SMTP_HBUFF_SIZE];
 int     smtp_connect_backlog   = 20;
 double  smtp_delay_mail        = 0.0;
 double  smtp_delay_rcpt        = 0.0;
 uschar  smtp_connection_had[SMTP_HBUFF_SIZE];
 int     smtp_connect_backlog   = 20;
 double  smtp_delay_mail        = 0.0;
 double  smtp_delay_rcpt        = 0.0;
-BOOL    smtp_enforce_sync      = TRUE;
 FILE   *smtp_in                = NULL;
 FILE   *smtp_in                = NULL;
-BOOL    smtp_input             = FALSE;
 int     smtp_load_reserve      = -1;
 int     smtp_mailcmd_count     = 0;
 FILE   *smtp_out               = NULL;
 uschar *smtp_etrn_command      = NULL;
 int     smtp_load_reserve      = -1;
 int     smtp_mailcmd_count     = 0;
 FILE   *smtp_out               = NULL;
 uschar *smtp_etrn_command      = NULL;
-BOOL    smtp_etrn_serialize    = TRUE;
 int     smtp_max_synprot_errors= 3;
 int     smtp_max_unknown_commands = 3;
 uschar *smtp_notquit_reason    = NULL;
 int     smtp_max_synprot_errors= 3;
 int     smtp_max_unknown_commands = 3;
 uschar *smtp_notquit_reason    = NULL;
@@ -1243,8 +1466,8 @@ uschar *smtp_ratelimit_mail    = NULL;
 uschar *smtp_ratelimit_rcpt    = NULL;
 uschar *smtp_read_error        = US"";
 int     smtp_receive_timeout   = 5*60;
 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;
 uschar *smtp_reserve_hosts     = NULL;
-BOOL    smtp_return_error_details = FALSE;
 int     smtp_rlm_base          = 0;
 double  smtp_rlm_factor        = 0.0;
 int     smtp_rlm_limit         = 0;
 int     smtp_rlm_base          = 0;
 double  smtp_rlm_factor        = 0.0;
 int     smtp_rlm_limit         = 0;
@@ -1253,17 +1476,21 @@ int     smtp_rlr_base          = 0;
 double  smtp_rlr_factor        = 0.0;
 int     smtp_rlr_limit         = 0;
 int     smtp_rlr_threshold     = INT_MAX;
 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;
 
 #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
 uschar *spam_score             = NULL;
 uschar *spam_score_int         = NULL;
 #endif
-#ifdef EXPERIMENTAL_SPF
+#ifdef SUPPORT_SPF
 uschar *spf_guess              = US"v=spf1 a/24 mx/24 ptr ?all";
 uschar *spf_header_comment     = NULL;
 uschar *spf_received           = NULL;
 uschar *spf_guess              = US"v=spf1 a/24 mx/24 ptr ?all";
 uschar *spf_header_comment     = NULL;
 uschar *spf_received           = NULL;
@@ -1271,7 +1498,7 @@ uschar *spf_result             = NULL;
 uschar *spf_smtp_comment       = NULL;
 #endif
 
 uschar *spf_smtp_comment       = NULL;
 #endif
 
-BOOL    split_spool_directory  = FALSE;
+FILE   *spool_data_file               = NULL;
 uschar *spool_directory        = US SPOOL_DIRECTORY
                            "\0<--------------Space to patch spool_directory->";
 #ifdef EXPERIMENTAL_SRS
 uschar *spool_directory        = US SPOOL_DIRECTORY
                            "\0<--------------Space to patch spool_directory->";
 #ifdef EXPERIMENTAL_SRS
@@ -1286,25 +1513,14 @@ uschar *srs_orig_sender        = NULL;
 uschar *srs_recipient          = NULL;
 uschar *srs_secrets            = NULL;
 uschar *srs_status             = NULL;
 uschar *srs_recipient          = NULL;
 uschar *srs_secrets            = NULL;
 uschar *srs_status             = NULL;
-BOOL    srs_usehash            = TRUE;
-BOOL    srs_usetimestamp       = TRUE;
 #endif
 #endif
-BOOL    strict_acl_vars        = FALSE;
 int     string_datestamp_offset= -1;
 int     string_datestamp_length= 0;
 int     string_datestamp_type  = -1;
 int     string_datestamp_offset= -1;
 int     string_datestamp_length= 0;
 int     string_datestamp_type  = -1;
-BOOL    strip_excess_angle_brackets = FALSE;
-BOOL    strip_trailing_dot     = FALSE;
 uschar *submission_domain      = NULL;
 uschar *submission_domain      = NULL;
-BOOL    submission_mode        = FALSE;
 uschar *submission_name        = NULL;
 uschar *submission_name        = NULL;
-BOOL    suppress_local_fixups  = FALSE;
-BOOL    suppress_local_fixups_default = FALSE;
-BOOL    synchronous_delivery   = FALSE;
-BOOL    syslog_duplication     = TRUE;
 int     syslog_facility        = LOG_MAIL;
 uschar *syslog_processname     = US"exim";
 int     syslog_facility        = LOG_MAIL;
 uschar *syslog_processname     = US"exim";
-BOOL    syslog_timestamp       = TRUE;
 uschar *system_filter          = NULL;
 
 uschar *system_filter_directory_transport = NULL;
 uschar *system_filter          = NULL;
 
 uschar *system_filter_directory_transport = NULL;
@@ -1313,94 +1529,81 @@ uschar *system_filter_pipe_transport = NULL;
 uschar *system_filter_reply_transport = NULL;
 
 gid_t   system_filter_gid      = 0;
 uschar *system_filter_reply_transport = NULL;
 
 gid_t   system_filter_gid      = 0;
-BOOL    system_filter_gid_set  = FALSE;
 uid_t   system_filter_uid      = (uid_t)-1;
 uid_t   system_filter_uid      = (uid_t)-1;
-BOOL    system_filter_uid_set  = FALSE;
-BOOL    system_filtering       = FALSE;
 
 
-BOOL    tcp_nodelay            = TRUE;
+blob   tcp_fastopen_nodata    = { .data = NULL, .len = 0 };
+tfo_state_t tcp_out_fastopen   = TFO_NOT_USED;
 #ifdef USE_TCP_WRAPPERS
 uschar *tcp_wrappers_daemon_name = US TCP_WRAPPERS_DAEMON_NAME;
 #endif
 int     test_harness_load_avg  = 0;
 int     thismessage_size_limit = 0;
 int     timeout_frozen_after   = 0;
 #ifdef USE_TCP_WRAPPERS
 uschar *tcp_wrappers_daemon_name = US TCP_WRAPPERS_DAEMON_NAME;
 #endif
 int     test_harness_load_avg  = 0;
 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 = {
 
 transport_instance  *transports = NULL;
 
 transport_instance  transport_defaults = {
-    NULL,                     /* chain pointer */
-    NULL,                     /* name */
-    NULL,                     /* info */
-    NULL,                     /* private options block pointer */
-    NULL,                     /* driver name */
-    NULL,                     /* setup entry point */
-    1,                        /* batch_max */
-    NULL,                     /* batch_id */
-    NULL,                     /* home_dir */
-    NULL,                     /* current_dir */
-    TRUE,                     /* multi-domain */
-    FALSE,                    /* overrides_hosts */
-    100,                      /* max_addresses */
-    500,                      /* connection_max_messages */
-    FALSE,                    /* deliver_as_creator */
-    FALSE,                    /* disable_logging */
-    FALSE,                    /* initgroups */
-    FALSE,                    /* uid_set */
-    FALSE,                    /* gid_set */
-    (uid_t)(-1),              /* uid */
-    (gid_t)(-1),              /* gid */
-    NULL,                     /* expand_uid */
-    NULL,                     /* expand_gid */
-    NULL,                     /* warn_message */
-    NULL,                     /* shadow */
-    NULL,                     /* shadow_condition */
-    NULL,                     /* filter_command */
-    NULL,                     /* add_headers */
-    NULL,                     /* remove_headers */
-    NULL,                     /* return_path */
-    NULL,                     /* debug_string */
-    NULL,                     /* message_size_limit */
-    NULL,                     /* headers_rewrite */
-    NULL,                     /* rewrite_rules */
-    0,                        /* rewrite_existflags */
-    300,                      /* filter_timeout */
-    FALSE,                    /* body_only */
-    FALSE,                    /* delivery_date_add */
-    FALSE,                    /* envelope_to_add */
-    FALSE,                    /* headers_only */
-    FALSE,                    /* rcpt_include_affixes */
-    FALSE,                    /* return_path_add */
-    FALSE,                    /* return_output */
-    FALSE,                    /* return_fail_output */
-    FALSE,                    /* log_output */
-    FALSE,                    /* log_fail_output */
-    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 */
+    .next =                    NULL,
+    .name =                    NULL,
+    .info =                    NULL,
+    .options_block =           NULL,
+    .driver_name =             NULL,
+    .setup =                   NULL,
+    .batch_max =               1,
+    .batch_id =                        NULL,
+    .home_dir =                        NULL,
+    .current_dir =             NULL,
+    .expand_multi_domain =     NULL,
+    .multi_domain =            TRUE,
+    .overrides_hosts =         FALSE,
+    .max_addresses =           100,
+    .connection_max_messages = 500,
+    .deliver_as_creator =      FALSE,
+    .disable_logging =         FALSE,
+    .initgroups =              FALSE,
+    .uid_set =                 FALSE,
+    .gid_set =                 FALSE,
+    .uid =                     (uid_t)(-1),
+    .gid =                     (gid_t)(-1),
+    .expand_uid =              NULL,
+    .expand_gid =              NULL,
+    .warn_message =            NULL,
+    .shadow =                  NULL,
+    .shadow_condition =                NULL,
+    .filter_command =          NULL,
+    .add_headers =             NULL,
+    .remove_headers =          NULL,
+    .return_path =             NULL,
+    .debug_string =            NULL,
+    .max_parallel =            NULL,
+    .message_size_limit =      NULL,
+    .headers_rewrite =         NULL,
+    .rewrite_rules =           NULL,
+    .rewrite_existflags =      0,
+    .filter_timeout =          300,
+    .body_only =               FALSE,
+    .delivery_date_add =       FALSE,
+    .envelope_to_add =         FALSE,
+    .headers_only =            FALSE,
+    .rcpt_include_affixes =    FALSE,
+    .return_path_add =         FALSE,
+    .return_output =           FALSE,
+    .return_fail_output =      FALSE,
+    .log_output =              FALSE,
+    .log_fail_output =         FALSE,
+    .log_defer_output =                FALSE,
+    .retry_use_local_part =    TRUE_UNSET,     /* retry_use_local_part: BOOL, but set neither
+                                                1 nor 0 so can detect unset */
+#ifndef DISABLE_EVENT
+   .event_action =             NULL
 #endif
 };
 
 int     transport_count;
 uschar *transport_name          = NULL;
 int     transport_newlines;
 #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;
 int     transport_filter_timeout;
-BOOL    transport_filter_timed_out = FALSE;
 int     transport_write_timeout= 0;
 
 tree_node  *tree_dns_fails     = NULL;
 int     transport_write_timeout= 0;
 
 tree_node  *tree_dns_fails     = NULL;
@@ -1408,8 +1611,6 @@ tree_node  *tree_duplicates    = NULL;
 tree_node  *tree_nonrecipients = NULL;
 tree_node  *tree_unusable      = NULL;
 
 tree_node  *tree_nonrecipients = NULL;
 tree_node  *tree_unusable      = NULL;
 
-BOOL    trusted_caller         = FALSE;
-BOOL    trusted_config         = TRUE;
 gid_t  *trusted_groups         = NULL;
 uid_t  *trusted_users          = NULL;
 uschar *timezone_string        = US TIMEZONE_DEFAULT;
 gid_t  *trusted_groups         = NULL;
 uid_t  *trusted_users          = NULL;
 uschar *timezone_string        = US TIMEZONE_DEFAULT;
@@ -1443,18 +1644,18 @@ uschar *uucp_from_pattern      = US
 
 uschar *uucp_from_sender       = US"$1";
 
 
 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      =
 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 - 2018\n"
+   "(c) The Exim Maintainers and contributors in ACKNOWLEDGMENTS file, 2007 - 2018";
 uschar *version_date           = US"?";
 uschar *version_cnumber        = US"????";
 uschar *version_string         = US"?";
 
 uschar *version_date           = US"?";
 uschar *version_cnumber        = US"????";
 uschar *version_string         = US"?";
 
+uschar *warn_message_file      = NULL;
 int     warning_count          = 0;
 int     warning_count          = 0;
+uschar *warnmsg_delay          = NULL;
+uschar *warnmsg_recipients     = NULL;
+
 
 /*  End of globals.c */
 
 /*  End of globals.c */
index b3ad4b2..f71f104 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Almost all the global variables are defined together in this one header, so
 /* 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 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
 #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
 
 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
 
 extern uschar *redis_servers;          /* List of servers and connect info */
 #endif
 
@@ -79,9 +80,13 @@ cluttered in several places (e.g. during logging) if we can always refer to
 them. Also, the tls_ variables are now always visible. */
 
 typedef struct {
 them. Also, the tls_ variables are now always visible. */
 
 typedef struct {
-  int     active;             /* fd/socket when in a TLS session */
+  client_conn_ctx active;     /* fd/socket when in a TLS session, and ptr to TLS context */
   int     bits;               /* bits used in TLS session */
   BOOL    certificate_verified; /* Client certificate verified */
   int     bits;               /* bits used in TLS session */
   BOOL    certificate_verified; /* Client certificate verified */
+#ifdef SUPPORT_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 */
   uschar *cipher;             /* Cipher used */
   BOOL    on_connect;         /* For older MTAs that don't STARTTLS */
   uschar *on_connect_ports;   /* Ports always tls-on-connect */
@@ -103,41 +108,45 @@ 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 */
 #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 *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 */
 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 */
 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 uschar *tls_privatekey;         /* Private key file */
+# ifdef EXPERIMENTAL_REQUIRETLS
+extern uschar  tls_requiretls;         /* REQUIRETLS active for this message */
+extern uschar *tls_advertise_requiretls; /* hosts for which REQUIRETLS adv */
+extern const pcre *regex_REQUIRETLS;   /* for recognising the command */
+# endif
 extern BOOL    tls_remember_esmtp;     /* For YAEB */
 extern uschar *tls_require_ciphers;    /* So some can be avoided */
 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 BOOL    tls_remember_esmtp;     /* For YAEB */
 extern uschar *tls_require_ciphers;    /* So some can be avoided */
 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 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 */
 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. */
 
 
 /* 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 uschar * (*lwr_receive_getbuf)(unsigned *);
+extern int (*lwr_receive_ungetc)(int);
+extern int (*receive_getc)(unsigned);
+extern uschar * (*receive_getbuf)(unsigned *);
+extern void (*receive_get_cache)(void);
 extern int (*receive_ungetc)(int);
 extern int (*receive_feof)(void);
 extern int (*receive_ferror)(void);
 extern int (*receive_ungetc)(int);
 extern int (*receive_feof)(void);
 extern int (*receive_ferror)(void);
@@ -148,17 +157,135 @@ extern BOOL (*receive_smtp_buffered)(void);
 the size of this vector set explicitly, because it is referenced from more than
 one module. */
 
 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];
+
+/* Flags for which we don't need an address ever so can use a bitfield */
+
+extern struct global_flags {
+ BOOL   acl_temp_details               :1; /* TRUE to give details for 4xx error */
+ BOOL   active_local_from_check                :1; /* For adding Sender: (switchable) */
+ BOOL   active_local_sender_retain     :1; /* For keeping Sender: (switchable) */
+ BOOL   address_test_mode              :1; /* True for -bt */
+ BOOL   admin_user                     :1; /* True if caller can do admin */
+ BOOL   allow_auth_unadvertised                :1; /* As it says */
+ BOOL   allow_unqualified_recipient    :1;    /* For local messages */ /* As it says */
+ BOOL   allow_unqualified_sender       :1;       /* Reset for SMTP */ /* Ditto */
+ BOOL   authentication_local           :1; /* TRUE if non-smtp (implicit authentication) */
+
+ BOOL   background_daemon              :1; /* Set FALSE to keep in foreground */
+
+ BOOL   chunking_offered               :1;
+ BOOL   config_changed                 :1; /* True if -C used */
+ BOOL   continue_more                  :1; /* Flag more addresses waiting */
+
+ BOOL   daemon_listen                  :1; /* True if listening required */
+ BOOL   debug_daemon                   :1; /* Debug the daemon process only */
+ BOOL   deliver_firsttime              :1; /* True for first delivery attempt */
+ BOOL   deliver_force                  :1; /* TRUE if delivery was forced */
+ BOOL   deliver_freeze                 :1; /* TRUE if delivery is frozen */
+ BOOL   deliver_force_thaw             :1; /* TRUE to force thaw in queue run */
+ BOOL   deliver_manual_thaw            :1; /* TRUE if manually thawed */
+ BOOL   deliver_selectstring_regex     :1; /* String is regex */
+ BOOL   deliver_selectstring_sender_regex :1; /* String is regex */
+ BOOL   disable_callout_flush          :1; /* Don't flush before callouts */
+ BOOL   disable_delay_flush            :1; /* Don't flush before "delay" in ACL */
+ BOOL   disable_logging                        :1; /* Disables log writing when TRUE */
+#ifndef DISABLE_DKIM
+ BOOL   dkim_disable_verify            :1; /* Set via ACL control statement. When set, DKIM verification is disabled for the current message */
+#endif
+#ifdef EXPERIMENTAL_DMARC
+ BOOL   dmarc_has_been_checked         :1; /* Global variable to check if test has been called yet */
+ BOOL   dmarc_disable_verify           :1; /* Set via ACL control statement. When set, DMARC verification is disabled for the current message */
+ BOOL   dmarc_enable_forensic          :1; /* Set via ACL control statement. When set, DMARC forensic reports are enabled for the current message */
+#endif
+ BOOL   dont_deliver                   :1; /* TRUE for -N option */
+ BOOL   dot_ends                       :1; /* TRUE if "." ends non-SMTP input */
+
+ BOOL   enable_dollar_recipients       :1; /* Make $recipients available */
+ BOOL   expand_string_forcedfail       :1; /* TRUE if failure was "expected" */
+
+ BOOL   filter_running                 :1; /* TRUE while running a filter */
+
+ BOOL   header_rewritten               :1; /* TRUE if header changed by router */
+ BOOL   helo_verified                  :1; /* True if HELO verified */
+ BOOL   helo_verify_failed             :1; /* True if attempt failed */
+ BOOL   host_checking_callout          :1; /* TRUE if real callout wanted */
+ BOOL   host_find_failed_syntax                :1; /* DNS syntax check failure */
+
+ BOOL   inetd_wait_mode                        :1; /* Whether running in inetd wait mode */
+ BOOL   is_inetd                       :1; /* True for inetd calls */
+
+ BOOL   local_error_message            :1; /* True if handling one of these */
+ BOOL   log_testing_mode               :1; /* TRUE in various testing modes */
+
+#ifdef WITH_CONTENT_SCAN
+ BOOL   no_mbox_unspool                        :1; /* don't unlink files in /scan directory */
+#endif
+ BOOL   no_multiline_responses         :1; /* For broken clients */
+
+ BOOL   parse_allow_group              :1; /* Allow group syntax */
+ BOOL   parse_found_group              :1; /* In the middle of a group */
+ BOOL   pipelining_enable              :1; /* As it says */
+#if defined(SUPPORT_PROXY) || defined(SUPPORT_SOCKS)
+ BOOL   proxy_session_failed           :1; /* TRUE if required proxy negotiation failed */
+#endif
+
+ BOOL   queue_2stage                   :1; /* Run queue in 2-stage manner */
+ BOOL   queue_only_policy              :1; /* ACL or local_scan wants queue_only */
+ BOOL   queue_run_first_delivery       :1; /* If TRUE, first deliveries only */
+ BOOL   queue_run_force                        :1; /* TRUE to force during queue run */
+ BOOL   queue_run_local                        :1; /* Local deliveries only in queue run */
+ BOOL   queue_running                  :1; /* TRUE for queue running process and */
+ BOOL   queue_smtp                     :1; /* Disable all immediate SMTP (-odqs)*/
+
+ BOOL   really_exim                    :1; /* FALSE in utilities */
+ BOOL   receive_call_bombout           :1; /* Flag for crashing log */
+ BOOL   recipients_discarded           :1; /* By an ACL */
+ BOOL   running_in_test_harness                :1; /*TRUE when running_status is patched */
+
+ BOOL   search_find_defer              :1; /* Set TRUE if lookup deferred */
+ BOOL   sender_address_forced          :1; /* Set by -f */
+ BOOL   sender_host_notsocket          :1; /* Set for -bs and -bS */
+ BOOL   sender_host_unknown            :1; /* TRUE for -bs and -bS except inetd */
+ BOOL   sender_local                   :1; /* TRUE for local senders */
+ BOOL   sender_name_forced             :1; /* Set by -F */
+ BOOL   sender_set_untrusted           :1; /* Sender set by untrusted caller */
+ BOOL   smtp_authenticated             :1; /* Sending client has authenticated */
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ BOOL   smtp_in_early_pipe_advertised  :1; /* server advertised PIPE_CONNECT */
+ BOOL  smtp_in_early_pipe_no_auth      :1; /* too many authenticator names */
+ BOOL   smtp_in_early_pipe_used                :1; /* client did send early data */
+#endif
+ BOOL   smtp_in_pipelining_advertised  :1; /* server advertised PIPELINING */
+ BOOL   smtp_in_pipelining_used                :1; /* server noted client using PIPELINING */
+ BOOL   spool_file_wireformat          :1; /* current -D file has CRLF rather than NL */
+ BOOL   submission_mode                        :1; /* Can be forced from ACL */
+ BOOL   suppress_local_fixups          :1; /* Can be forced from ACL */
+ BOOL   suppress_local_fixups_default  :1; /* former is reset to this; override with -G */
+ BOOL   synchronous_delivery           :1; /* TRUE if -odi is set */
+ BOOL   system_filtering               :1; /* TRUE when running system filter */
+
+ BOOL   tcp_fastopen_ok                        :1; /* appears to be supported by kernel */
+ BOOL   tcp_in_fastopen                        :1; /* conn usefully used fastopen */
+ BOOL   tcp_in_fastopen_data           :1; /* fastopen carried data */
+ BOOL   tcp_in_fastopen_logged         :1; /* one-time logging */
+ BOOL   tcp_out_fastopen_logged                :1; /* one-time logging */
+ BOOL   timestamps_utc                 :1; /* Use UTC for all times */
+ BOOL   transport_filter_timed_out     :1; /* True if it did */
+ BOOL   trusted_caller                 :1; /* Caller is trusted */
+ BOOL   trusted_config                 :1; /* Configuration file is trusted */
+} f;
+
 
 /* General global variables */
 
 extern BOOL    accept_8bitmime;        /* Allow *BITMIME incoming */
 extern uschar *add_environment;        /* List of environment variables to add */
 
 /* General global variables */
 
 extern BOOL    accept_8bitmime;        /* Allow *BITMIME incoming */
 extern uschar *add_environment;        /* List of environment variables to add */
-extern int     body_8bitmime;          /* sender declared BODY= ; 7=7BIT, 8=8BITMIME */
 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 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 */
 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 */
@@ -189,29 +316,28 @@ extern uschar *acl_smtp_quit;          /* ACL run for QUIT */
 extern uschar *acl_smtp_rcpt;          /* ACL run for RCPT */
 extern uschar *acl_smtp_starttls;      /* ACL run for STARTTLS */
 extern uschar *acl_smtp_vrfy;          /* ACL run for VRFY */
 extern uschar *acl_smtp_rcpt;          /* ACL run for RCPT */
 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_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 */
 extern uschar *acl_wherenames[];       /* Names for messages */
 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 */
 extern uschar *acl_wherenames[];       /* Names for messages */
-extern BOOL    active_local_from_check;/* For adding Sender: (switchable) */
-extern BOOL    active_local_sender_retain; /* For keeping Sender: (switchable) */
 extern address_item *addr_duplicate;   /* Duplicate address list */
 extern address_item address_defaults;  /* Default data for address item */
 extern uschar *address_file;           /* Name of file when delivering to one */
 extern uschar *address_pipe;           /* Pipe command when delivering to one */
 extern address_item *addr_duplicate;   /* Duplicate address list */
 extern address_item address_defaults;  /* Default data for address item */
 extern uschar *address_file;           /* Name of file when delivering to one */
 extern uschar *address_pipe;           /* Pipe command when delivering to one */
-extern BOOL    address_test_mode;      /* True for -bt */
 extern tree_node *addresslist_anchor;  /* Tree of defined address lists */
 extern int     addresslist_count;      /* Number defined */
 extern gid_t  *admin_groups;           /* List of admin groups */
 extern tree_node *addresslist_anchor;  /* Tree of defined address lists */
 extern int     addresslist_count;      /* Number defined */
 extern gid_t  *admin_groups;           /* List of admin groups */
-extern BOOL    admin_user;             /* True if caller can do admin */
-extern BOOL    allow_auth_unadvertised;/* As it says */
 extern BOOL    allow_domain_literals;  /* As it says */
 extern BOOL    allow_mx_to_ip;         /* Allow MX records to -> ip address */
 extern BOOL    allow_domain_literals;  /* As it says */
 extern BOOL    allow_mx_to_ip;         /* Allow MX records to -> ip address */
-extern BOOL    allow_unqualified_recipient; /* As it says */
-extern BOOL    allow_unqualified_sender; /* Ditto */
+#ifdef EXPERIMENTAL_ARC
+struct arc_set *arc_received;         /* highest ARC instance evaluation struct */
+extern int     arc_received_instance;  /* highest ARC instance number in headers */
+extern int     arc_oldest_pass;        /* lowest passing instance number in headers */
+extern const uschar *arc_state;               /* verification state */
+extern const uschar *arc_state_reason;
+#endif
 extern BOOL    allow_utf8_domains;     /* For experimenting */
 extern uschar *authenticated_fail_id;  /* ID that failed authentication */
 extern uschar *authenticated_id;       /* ID that was authenticated */
 extern BOOL    allow_utf8_domains;     /* For experimenting */
 extern uschar *authenticated_fail_id;  /* ID that failed authentication */
 extern uschar *authenticated_id;       /* ID that was authenticated */
@@ -226,11 +352,10 @@ extern uschar *auth_defer_user_msg;    /* Error message for user */
 extern uschar *auth_vars[];            /* $authn variables */
 extern int     auto_thaw;              /* Auto-thaw interval */
 #ifdef WITH_CONTENT_SCAN
 extern uschar *auth_vars[];            /* $authn variables */
 extern int     auto_thaw;              /* Auto-thaw interval */
 #ifdef WITH_CONTENT_SCAN
-extern BOOL    av_failed;              /* TRUE if the AV process failed */
+extern int     av_failed;              /* TRUE if the AV process failed */
 extern uschar *av_scanner;             /* AntiVirus scanner to use for the malware condition */
 #endif
 
 extern uschar *av_scanner;             /* AntiVirus scanner to use for the malware condition */
 #endif
 
-extern BOOL    background_daemon;      /* Set FALSE to keep in foreground */
 extern uschar *base62_chars;           /* Table of base-62 characters */
 extern uschar *bi_command;             /* Command for -bi option */
 extern uschar *big_buffer;             /* Used for various temp things */
 extern uschar *base62_chars;           /* Table of base-62 characters */
 extern uschar *bi_command;             /* Command for -bi option */
 extern uschar *big_buffer;             /* Used for various temp things */
@@ -244,15 +369,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     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     bsmtp_transaction_linecount; /* Start of last transaction */
+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 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 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 */
 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 */
@@ -260,39 +388,54 @@ extern int     callout_cache_negative_expire; /* Time for negative callout cache
 extern uschar *callout_random_local_part; /* Local part to be used to check if server called will accept any local part */
 extern uschar *check_dns_names_pattern;/* Regex for syntax check */
 extern int     check_log_inodes;       /* Minimum for message acceptance */
 extern uschar *callout_random_local_part; /* Local part to be used to check if server called will accept any local part */
 extern uschar *check_dns_names_pattern;/* Regex for syntax check */
 extern int     check_log_inodes;       /* Minimum for message acceptance */
-extern int     check_log_space;        /* Minimum for message acceptance */
+extern int_eximarith_t 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 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 int_eximarith_t 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 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) */
 extern int     clmacro_count;          /* Number of command line macros */
 extern uschar *clmacros[];             /* Copy of them, for re-exec */
 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) */
 extern int     clmacro_count;          /* Number of command line macros */
 extern uschar *clmacros[];             /* Copy of them, for re-exec */
+extern BOOL    commandline_checks_require_admin; /* belt and braces for insecure setups */
 extern int     connection_max_messages;/* Max down one SMTP connection */
 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 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 */
 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 */
 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 */
 extern uid_t   config_uid;             /* Additional owner */
-#endif
+extern uschar *continue_proxy_cipher;  /* TLS cipher for proxied continued delivery */
 extern uschar *continue_hostname;      /* Host for continued delivery */
 extern uschar *continue_host_address;  /* IP address for ditto */
 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 */
 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 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 */
 
 
-extern BOOL    daemon_listen;          /* True if listening required */
+typedef struct {
+  unsigned     callout_hold_only:1;    /* Conn is only for verify callout */
+  unsigned     delivery:1;             /* When to attempt */
+  unsigned     defer_pass:1;           /* Pass 4xx to caller rather than spooling */
+  unsigned     is_tls:1;              /* Conn has TLS active */
+  client_conn_ctx cctx;                /* Open connection */
+  int          nrcpt;                  /* Count of addresses */
+  uschar *     transport;             /* Name of transport */
+  uschar *     interface;              /* (address of) */
+  uschar *     snd_ip;                /* sending_ip_address */
+  int         snd_port;               /* sending_port */
+  unsigned     peer_options;          /* smtp_peer_options */
+  host_item    host;                   /* Host used */
+  address_item addr;                   /* (Chain of) addresses */
+} cut_t;
+extern cut_t cutthrough;               /* Deliver-concurrently */
+
 extern uschar *daemon_smtp_port;       /* Can be a list of ports */
 extern int     daemon_startup_retries; /* Number of times to retry */
 extern int     daemon_startup_sleep;   /* Sleep between retries */
 extern uschar *daemon_smtp_port;       /* Can be a list of ports */
 extern int     daemon_startup_retries; /* Number of times to retry */
 extern int     daemon_startup_sleep;   /* Sleep between retries */
@@ -305,30 +448,29 @@ extern uschar *dccifd_address;         /* address of the dccifd daemon */
 extern uschar *dccifd_options;         /* options for the dccifd daemon */
 #endif
 
 extern uschar *dccifd_options;         /* options for the dccifd daemon */
 #endif
 
-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_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 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 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_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_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 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 */
                                        /* 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 */
 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 */
@@ -337,40 +479,30 @@ extern uschar *deliver_localpart_orig; /* The original local part for delivery *
 extern uschar *deliver_localpart_parent; /* The parent local part for delivery */
 extern uschar *deliver_localpart_prefix; /* The stripped prefix, if any */
 extern uschar *deliver_localpart_suffix; /* The stripped suffix, if any */
 extern uschar *deliver_localpart_parent; /* The parent local part for delivery */
 extern uschar *deliver_localpart_prefix; /* The stripped prefix, if any */
 extern uschar *deliver_localpart_suffix; /* The stripped suffix, if any */
-extern BOOL    deliver_force_thaw;     /* TRUE to force thaw in queue run */
-extern BOOL    deliver_manual_thaw;    /* TRUE if manually thawed */
 extern uschar *deliver_out_buffer;     /* Buffer for copying file */
 extern int     deliver_queue_load_max; /* Different value for queue running */
 extern address_item *deliver_recipients; /* Current set of addresses */
 extern uschar *deliver_selectstring;   /* For selecting by recipient */
 extern uschar *deliver_out_buffer;     /* Buffer for copying file */
 extern int     deliver_queue_load_max; /* Different value for queue running */
 extern address_item *deliver_recipients; /* Current set of addresses */
 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 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
 extern BOOL    disable_fsync;          /* Not for normal use */
 #endif
 extern BOOL    disable_ipv6;           /* Don't do any IPv6 things */
 #ifdef ENABLE_DISABLE_FSYNC
 extern BOOL    disable_fsync;          /* Not for normal use */
 #endif
 extern BOOL    disable_ipv6;           /* Don't do any IPv6 things */
-extern BOOL    disable_logging;        /* Disables log writing when TRUE */
 
 #ifndef DISABLE_DKIM
 
 #ifndef DISABLE_DKIM
+extern unsigned dkim_collect_input;    /* Runtime count of dkim signtures; tracks whether 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 uschar *dkim_cur_signer;        /* Expansion variable, holds the current "signer" domain or identity during a acl_smtp_dkim run */
+extern int     dkim_key_length;        /* Expansion variable, length of signing key in bits */
+extern void   *dkim_signatures;               /* Actually a (pdkim_signature *) but most files do not need to know */
 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_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_overall;    /* First successful domain verified, or null */
 extern uschar *dkim_verify_signers;    /* Colon-separated list of domains for each of which we call the DKIM ACL */
 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 */
+extern uschar *dkim_verify_status;     /* result for this signature */
+extern uschar *dkim_verify_reason;     /* result for this signature */
 #endif
 #ifdef EXPERIMENTAL_DMARC
 #endif
 #ifdef EXPERIMENTAL_DMARC
-extern BOOL    dmarc_has_been_checked; /* Global variable to check if test has been called yet */
-extern uschar *dmarc_ar_header;        /* Expansion variable, suggested header for dmarc auth results */
 extern uschar *dmarc_domain_policy;    /* Expansion for declared policy of used domain */
 extern uschar *dmarc_forensic_sender;  /* Set sender address for forensic reports */
 extern uschar *dmarc_history_file;     /* Expansion variable, file to store dmarc results */
 extern uschar *dmarc_domain_policy;    /* Expansion for declared policy of used domain */
 extern uschar *dmarc_forensic_sender;  /* Set sender address for forensic reports */
 extern uschar *dmarc_history_file;     /* Expansion variable, file to store dmarc results */
@@ -378,17 +510,20 @@ extern uschar *dmarc_status;           /* Expansion variable, one word value */
 extern uschar *dmarc_status_text;      /* Expansion variable, human readable value */
 extern uschar *dmarc_tld_file;         /* Mozilla TLDs text file */
 extern uschar *dmarc_used_domain;      /* Expansion variable, domain libopendmarc chose for DMARC policy lookup */
 extern uschar *dmarc_status_text;      /* Expansion variable, human readable value */
 extern uschar *dmarc_tld_file;         /* Mozilla TLDs text file */
 extern uschar *dmarc_used_domain;      /* Expansion variable, domain libopendmarc chose for DMARC policy lookup */
-extern BOOL    dmarc_disable_verify;   /* Set via ACL control statement. When set, DMARC verification is disabled for the current message */
-extern BOOL    dmarc_enable_forensic;  /* Set via ACL control statement. When set, DMARC forensic reports are enabled for the current message */
 #endif
 
 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) */
 #endif
 
 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 int     dns_cname_loops;               /* Follow CNAMEs returned by resolver to this depth */
 extern uschar *dns_ipv4_lookup;        /* For these domains, don't look for AAAA (or A6) */
 extern uschar *dns_ipv4_lookup;        /* For these domains, don't look for AAAA (or A6) */
+#ifdef SUPPORT_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 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 */
 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 */
@@ -396,39 +531,43 @@ extern uschar *dnslist_text;           /* DNS (black) list text message */
 extern uschar *dnslist_value;          /* DNS (black) list IP address */
 extern tree_node *domainlist_anchor;   /* Tree of defined domain lists */
 extern int     domainlist_count;       /* Number defined */
 extern uschar *dnslist_value;          /* DNS (black) list IP address */
 extern tree_node *domainlist_anchor;   /* Tree of defined domain lists */
 extern int     domainlist_count;       /* Number defined */
-extern BOOL    dont_deliver;           /* TRUE for -N option */
-extern BOOL    dot_ends;               /* TRUE if "." ends non-SMTP input */
 
 /* This option is now a no-opt, retained for compatibility */
 extern BOOL    drop_cr;                /* For broken local MUAs */
 
 extern uschar *dsn_from;               /* From: string for DSNs */
 
 
 /* This option is now a no-opt, retained for compatibility */
 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*/
 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 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 */
 extern uschar *expand_nstring[];       /* Numbered strings */
 extern int     expand_forbid;          /* RDO flags for forbidding things */
 extern int     expand_nlength[];       /* Lengths of numbered strings */
 extern int     expand_nmax;            /* Max numerical value */
 extern uschar *expand_nstring[];       /* Numbered strings */
-extern BOOL    expand_string_forcedfail; /* TRUE if failure was "expected" */
 extern BOOL    extract_addresses_remove_arguments; /* Controls -t behaviour */
 extern uschar *extra_local_interfaces; /* Local, non-listen interfaces */
 
 extern int     fake_response;          /* Fake FAIL or DEFER response to data */
 extern uschar *fake_response_text;     /* User defined message for the above. Default is in globals.c. */
 extern int     filter_n[FILTER_VARIABLE_COUNT]; /* filter variables */
 extern BOOL    extract_addresses_remove_arguments; /* Controls -t behaviour */
 extern uschar *extra_local_interfaces; /* Local, non-listen interfaces */
 
 extern int     fake_response;          /* Fake FAIL or DEFER response to data */
 extern uschar *fake_response_text;     /* User defined message for the above. Default is in globals.c. */
 extern int     filter_n[FILTER_VARIABLE_COUNT]; /* filter variables */
-extern BOOL    filter_running;         /* TRUE while running a filter */
 extern int     filter_sn[FILTER_VARIABLE_COUNT]; /* variables set by system filter */
 extern int     filter_test;            /* Filter test type */
 extern uschar *filter_test_sfile;      /* System filter test file */
 extern int     filter_sn[FILTER_VARIABLE_COUNT]; /* variables set by system filter */
 extern int     filter_test;            /* Filter test type */
 extern uschar *filter_test_sfile;      /* System filter test file */
@@ -436,9 +575,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 */
 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 */
 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 */
@@ -447,23 +583,22 @@ extern uschar *gecos_name;             /* To be expanded when pattern matches */
 extern uschar *gecos_pattern;          /* Pattern to match */
 extern rewrite_rule *global_rewrite_rules;  /* Chain of rewriting rules */
 
 extern uschar *gecos_pattern;          /* Pattern to match */
 extern rewrite_rule *global_rewrite_rules;  /* Chain of rewriting rules */
 
+extern volatile sig_atomic_t had_command_timeout;   /* Alarm sighandler called */
+extern volatile sig_atomic_t had_command_sigterm;   /* TERM  sighandler called */
+extern volatile sig_atomic_t had_data_timeout;      /* Alarm sighandler called */
+extern volatile sig_atomic_t had_data_sigint;       /* TERM/INT  sighandler called */
 extern int     header_insert_maxlen;   /* Max for inserting headers */
 extern int     header_maxsize;         /* Max total length for header */
 extern int     header_line_maxsize;    /* Max for an individual line */
 extern header_name header_names[];     /* Table of header names */
 extern int     header_names_size;      /* Number of entries */
 extern int     header_insert_maxlen;   /* Max for inserting headers */
 extern int     header_maxsize;         /* Max total length for header */
 extern int     header_line_maxsize;    /* Max for an individual line */
 extern header_name header_names[];     /* Table of header names */
 extern int     header_names_size;      /* Number of entries */
-extern BOOL    header_rewritten;       /* TRUE if header changed by router */
 extern uschar *helo_accept_junk_hosts; /* Allowed to use junk arg */
 extern uschar *helo_allow_chars;       /* Rogue chars to allow in HELO/EHLO */
 extern uschar *helo_lookup_domains;    /* If these given, lookup host name */
 extern uschar *helo_try_verify_hosts;  /* Soft check HELO argument for these */
 extern uschar *helo_accept_junk_hosts; /* Allowed to use junk arg */
 extern uschar *helo_allow_chars;       /* Rogue chars to allow in HELO/EHLO */
 extern uschar *helo_lookup_domains;    /* If these given, lookup host name */
 extern uschar *helo_try_verify_hosts;  /* Soft check HELO argument for these */
-extern BOOL    helo_verified;          /* True if HELO verified */
-extern BOOL    helo_verify_failed;     /* True if attempt failed */
 extern uschar *helo_verify_hosts;      /* Hard check HELO argument for these */
 extern const uschar *hex_digits;             /* Used in several places */
 extern uschar *hold_domains;           /* Hold up deliveries to these */
 extern uschar *helo_verify_hosts;      /* Hard check HELO argument for these */
 extern const uschar *hex_digits;             /* Used in several places */
 extern uschar *hold_domains;           /* Hold up deliveries to these */
-extern BOOL    host_find_failed_syntax;/* DNS syntax check failure */
-extern BOOL    host_checking_callout;  /* TRUE if real callout wanted */
 extern uschar *host_data;              /* Obtained from lookup in ACL */
 extern uschar *host_lookup;            /* For which IP addresses are always looked up */
 extern BOOL    host_lookup_deferred;   /* TRUE if lookup deferred */
 extern uschar *host_data;              /* Obtained from lookup in ACL */
 extern uschar *host_lookup;            /* For which IP addresses are always looked up */
 extern BOOL    host_lookup_deferred;   /* TRUE if lookup deferred */
@@ -481,9 +616,8 @@ extern uschar *hosts_treat_as_local;   /* For routing */
 extern int     ignore_bounce_errors_after; /* Keep them for this time. */
 extern BOOL    ignore_fromline_local;  /* Local SMTP ignore fromline */
 extern uschar *ignore_fromline_hosts;  /* Hosts permitted to send "From " */
 extern int     ignore_bounce_errors_after; /* Keep them for this time. */
 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 int     inetd_wait_timeout;     /* Timeout for inetd wait mode */
-extern BOOL    is_inetd;               /* True for inetd calls */
+extern uschar *initial_cwd;            /* The directory we where in at startup */
 extern uschar *iterate_item;           /* Item from iterate list */
 
 extern int     journal_fd;             /* Fd for journal file */
 extern uschar *iterate_item;           /* Item from iterate list */
 
 extern int     journal_fd;             /* Fd for journal file */
@@ -493,31 +627,32 @@ extern int     keep_malformed;         /* Time to keep malformed messages */
 
 extern uschar *eldap_dn;               /* Where LDAP DNs are left */
 extern int     load_average;           /* Most recently read load average */
 
 extern uschar *eldap_dn;               /* Where LDAP DNs are left */
 extern int     load_average;           /* Most recently read load average */
-extern BOOL    local_error_message;    /* True if handling one of these */
 extern BOOL    local_from_check;       /* For adding Sender: (global value) */
 extern uschar *local_from_prefix;      /* Permitted prefixes */
 extern uschar *local_from_suffix;      /* Permitted suffixes */
 extern uschar *local_interfaces;       /* For forcing specific interfaces */
 extern BOOL    local_from_check;       /* For adding Sender: (global value) */
 extern uschar *local_from_prefix;      /* Permitted prefixes */
 extern uschar *local_from_suffix;      /* Permitted suffixes */
 extern uschar *local_interfaces;       /* For forcing specific interfaces */
+#ifdef HAVE_LOCAL_SCAN
 extern uschar *local_scan_data;        /* Text returned by local_scan() */
 extern optionlist local_scan_options[];/* Option list for local_scan() */
 extern int     local_scan_options_count; /* Size of the list */
 extern int     local_scan_timeout;     /* Timeout for local_scan() */
 extern uschar *local_scan_data;        /* Text returned by local_scan() */
 extern optionlist local_scan_options[];/* Option list for local_scan() */
 extern int     local_scan_options_count; /* Size of the list */
 extern int     local_scan_timeout;     /* Timeout for local_scan() */
+#endif
 extern BOOL    local_sender_retain;    /* Retain Sender: (with no From: check) */
 extern gid_t   local_user_gid;         /* As it says; may be set in routers */
 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 BOOL    local_sender_retain;    /* Retain Sender: (with no From: check) */
 extern gid_t   local_user_gid;         /* As it says; may be set in routers */
 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 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 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 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 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 */
 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 */
@@ -526,6 +661,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 uschar *lookup_value;           /* Value looked up from file */
 
 extern macro_item *macros;             /* Configuration macros */
+extern macro_item *macros_user;        /* Non-builtin configuration macros */
+extern macro_item *mlast;              /* Last item in macro list */
 extern uschar *mailstore_basename;     /* For mailstore deliveries */
 #ifdef WITH_CONTENT_SCAN
 extern uschar *malware_name;           /* Name of virus or malware ("W32/Klez-H") */
 extern uschar *mailstore_basename;     /* For mailstore deliveries */
 #ifdef WITH_CONTENT_SCAN
 extern uschar *malware_name;           /* Name of virus or malware ("W32/Klez-H") */
@@ -549,6 +686,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 */
 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 */
 
 extern uschar  message_subdir[];       /* Subdirectory for messages */
 extern uschar *message_reference;      /* Reference for error messages */
 
@@ -576,9 +718,10 @@ extern BOOL    mua_wrapper;            /* TRUE when Exim is wrapping an MUA */
 
 extern uid_t  *never_users;            /* List of uids never to be used */
 #ifdef WITH_CONTENT_SCAN
 
 extern uid_t  *never_users;            /* List of uids never to be used */
 #ifdef WITH_CONTENT_SCAN
-extern BOOL    no_mbox_unspool;        /* don't unlink files in /scan directory */
 #endif
 #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_auths[];      /* These option lists are made */
 extern int     optionlist_auths_size;      /* global so that readconf can */
@@ -595,12 +738,12 @@ extern uid_t   originator_uid;         /* Uid of ditto */
 extern uschar *override_local_interfaces; /* Value of -oX argument */
 extern uschar *override_pid_file_path; /* Value of -oP argument */
 
 extern uschar *override_local_interfaces; /* Value of -oX argument */
 extern uschar *override_pid_file_path; /* Value of -oP argument */
 
-extern BOOL    parse_allow_group;      /* Allow group syntax */
-extern BOOL    parse_found_group;      /* In the middle of a group */
 extern uschar *percent_hack_domains;   /* Local domains for which '% operates */
 extern uschar *pid_file_path;          /* For writing daemon pids */
 extern uschar *percent_hack_domains;   /* Local domains for which '% operates */
 extern uschar *pid_file_path;          /* For writing daemon pids */
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+extern uschar *pipe_connect_advertise_hosts; /* for banner/EHLO pipelining */
+#endif
 extern uschar *pipelining_advertise_hosts; /* As it says */
 extern uschar *pipelining_advertise_hosts; /* As it says */
-extern BOOL    pipelining_enable;      /* As it says */
 #ifndef DISABLE_PRDR
 extern BOOL    prdr_enable;            /* As it says */
 extern BOOL    prdr_requested;         /* Connecting mail server wants PRDR */
 #ifndef DISABLE_PRDR
 extern BOOL    prdr_enable;            /* As it says */
 extern BOOL    prdr_requested;         /* Connecting mail server wants PRDR */
@@ -613,42 +756,35 @@ extern int     process_info_len;
 extern uschar *process_log_path;       /* Alternate path */
 extern BOOL    prod_requires_admin;    /* TRUE if prodding requires admin */
 
 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;          /* 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 */
 
 #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 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 */
 extern BOOL    queue_list_requires_admin; /* TRUE if -bp requires admin */
 extern uschar *queue_domains;          /* Queue these domains */
 extern BOOL    queue_list_requires_admin; /* TRUE if -bp requires admin */
-extern BOOL    queue_run_first_delivery; /* If TRUE, first deliveries only */
-extern BOOL    queue_run_force;        /* TRUE to force during queue run */
-extern BOOL    queue_run_local;        /* Local deliveries only in queue run */
-extern BOOL    queue_running;          /* TRUE for queue running process and */
                                        /*   immediate children */
 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 */
                                        /*   immediate children */
 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 */
 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;             /* 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 */
 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 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 uschar *queue_smtp_domains;     /* Ditto, for these domains */
 
 extern unsigned int random_seed;       /* Seed for random numbers */
 extern uschar *queue_smtp_domains;     /* Ditto, for these domains */
 
 extern unsigned int random_seed;       /* Seed for random numbers */
@@ -664,8 +800,6 @@ extern int     rcpt_fail_count;        /* Those that got 5xx */
 extern int     rcpt_defer_count;       /* Those that got 4xx */
 extern gid_t   real_gid;               /* Real gid */
 extern uid_t   real_uid;               /* Real user running program */
 extern int     rcpt_defer_count;       /* Those that got 4xx */
 extern gid_t   real_gid;               /* Real gid */
 extern uid_t   real_uid;               /* Real user running program */
-extern BOOL    really_exim;            /* FALSE in utilities */
-extern BOOL    receive_call_bombout;   /* Flag for crashing log */
 extern int     receive_linecount;      /* Mainly for BSMTP errors */
 extern int     receive_messagecount;   /* Mainly for BSMTP errors */
 extern int     receive_timeout;        /* For non-SMTP acceptance */
 extern int     receive_linecount;      /* Mainly for BSMTP errors */
 extern int     receive_messagecount;   /* Mainly for BSMTP errors */
 extern int     receive_timeout;        /* For non-SMTP acceptance */
@@ -673,22 +807,27 @@ extern int     received_count;         /* Count of Received: headers */
 extern uschar *received_for;           /* For "for" field */
 extern uschar *received_header_text;   /* Definition of Received: header */
 extern int     received_headers_max;   /* Max count of Received: headers */
 extern uschar *received_for;           /* For "for" field */
 extern uschar *received_header_text;   /* Definition of Received: header */
 extern int     received_headers_max;   /* Max count of Received: headers */
-extern int     received_time;          /* Time the message was received */
+extern struct timeval received_time;   /* Time the message was received */
+extern struct timeval received_time_taken; /* Interval the message took to be received */
 extern uschar *recipient_data;         /* lookup data for recipients */
 extern uschar *recipient_unqualified_hosts; /* Permitted unqualified recipients */
 extern uschar *recipient_verify_failure; /* What went wrong */
 extern uschar *recipient_data;         /* lookup data for recipients */
 extern uschar *recipient_unqualified_hosts; /* Permitted unqualified recipients */
 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_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_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_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 */
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+extern const pcre  *regex_EARLY_PIPE;  /* For recognizing PIPE_CONNCT */
+#endif
 extern const pcre  *regex_ismsgid;     /* Compiled r.e. for message it */
 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
 #ifdef WHITELIST_D_MACROS
 extern const pcre  *regex_whitelisted_macro; /* For -D macro values */
 #endif
@@ -714,35 +853,29 @@ extern router_info routers_available[];/* Vector of available routers */
 extern router_instance *routers;       /* Chain of instantiated routers */
 extern router_instance router_defaults;/* Default values */
 extern uschar *router_name;            /* Name of router last started */
 extern router_instance *routers;       /* Chain of instantiated routers */
 extern router_instance router_defaults;/* Default values */
 extern uschar *router_name;            /* Name of router last started */
-extern BOOL    running_in_test_harness; /*TRUE when running_status is patched */
 extern ip_address_item *running_interfaces; /* Host's running interfaces */
 extern uschar *running_status;         /* Flag string for testing */
 extern int     runrc;                  /* rc from ${run} */
 
 extern uschar *search_error_message;   /* Details of lookup problem */
 extern ip_address_item *running_interfaces; /* Host's running interfaces */
 extern uschar *running_status;         /* Flag string for testing */
 extern int     runrc;                  /* rc from ${run} */
 
 extern uschar *search_error_message;   /* Details of lookup problem */
-extern BOOL    search_find_defer;      /* Set TRUE if lookup deferred */
 extern uschar *self_hostname;          /* Self host after routing->directors */
 extern unsigned int sender_address_cache[(MAX_NAMED_LIST * 2)/32]; /* Cache bits for sender */
 extern uschar *sender_address_data;    /* address_data from sender verify */
 extern uschar *self_hostname;          /* Self host after routing->directors */
 extern unsigned int sender_address_cache[(MAX_NAMED_LIST * 2)/32]; /* Cache bits for sender */
 extern uschar *sender_address_data;    /* address_data from sender verify */
-extern BOOL    sender_address_forced;  /* Set by -f */
 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 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 uschar *sender_helo_name;       /* Host name from HELO/EHLO */
 extern uschar **sender_host_aliases;   /* Points to list of alias names */
+extern uschar *sender_host_auth_pubname; /* Public-name of authentication method */
 extern unsigned int sender_host_cache[(MAX_NAMED_LIST * 2)/32]; /* Cache bits for incoming host */
 extern BOOL    sender_host_dnssec;     /* true if sender_host_name verified in DNSSEC */
 extern unsigned int sender_host_cache[(MAX_NAMED_LIST * 2)/32]; /* Cache bits for incoming host */
 extern BOOL    sender_host_dnssec;     /* true if sender_host_name verified in DNSSEC */
-extern BOOL    sender_host_notsocket;  /* Set for -bs and -bS */
-extern BOOL    sender_host_unknown;    /* TRUE for -bs and -bS except inetd */
 extern uschar *sender_ident;           /* Sender identity via RFC 1413 */
 extern uschar *sender_ident;           /* Sender identity via RFC 1413 */
-extern BOOL    sender_local;           /* TRUE for local senders */
-extern BOOL    sender_name_forced;     /* Set by -F */
 extern uschar *sender_rate;            /* Sender rate computed by ACL */
 extern uschar *sender_rate_limit;      /* Configured rate limit */
 extern uschar *sender_rate_period;     /* Configured smoothing period */
 extern uschar *sender_rcvhost;         /* Host data for Received: */
 extern uschar *sender_rate;            /* Sender rate computed by ACL */
 extern uschar *sender_rate_limit;      /* Configured rate limit */
 extern uschar *sender_rate_period;     /* Configured smoothing period */
 extern uschar *sender_rcvhost;         /* Host data for Received: */
-extern BOOL    sender_set_untrusted;   /* Sender set by untrusted caller */
 extern uschar *sender_unqualified_hosts; /* Permitted unqualified senders */
 extern uschar *sender_verify_failure;  /* What went wrong */
 extern address_item *sender_verified_list; /* Saved chain of sender verifies */
 extern uschar *sender_unqualified_hosts; /* Permitted unqualified senders */
 extern uschar *sender_verify_failure;  /* What went wrong */
 extern address_item *sender_verified_list; /* Saved chain of sender verifies */
@@ -750,7 +883,9 @@ extern address_item *sender_verified_failed; /* The one that caused denial */
 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 *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 const uschar *sigalarm_setter;  /* For debug, set to callpoint of alarm() */
 extern uschar **sighup_argv;           /* Args for re-execing after SIGHUP */
 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 */
 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 */
@@ -762,13 +897,12 @@ extern int     smtp_accept_queue;      /* Queue after so many connections */
 extern int     smtp_accept_queue_per_connection; /* Queue after so many msgs */
 extern int     smtp_accept_reserve;    /* Reserve these SMTP connections */
 extern uschar *smtp_active_hostname;   /* Hostname for this message */
 extern int     smtp_accept_queue_per_connection; /* Queue after so many msgs */
 extern int     smtp_accept_reserve;    /* Reserve these SMTP connections */
 extern uschar *smtp_active_hostname;   /* Hostname for this message */
-extern BOOL    smtp_authenticated;     /* Sending client has authenticated */
 extern uschar *smtp_banner;            /* Banner string (to be expanded) */
 extern BOOL    smtp_check_spool_space; /* TRUE to check SMTP SIZE value */
 extern int     smtp_ch_index;          /* Index in smtp_connection_had */
 extern uschar *smtp_cmd_argument;      /* For all SMTP commands */
 extern uschar *smtp_cmd_buffer;        /* SMTP command buffer */
 extern uschar *smtp_banner;            /* Banner string (to be expanded) */
 extern BOOL    smtp_check_spool_space; /* TRUE to check SMTP SIZE value */
 extern int     smtp_ch_index;          /* Index in smtp_connection_had */
 extern uschar *smtp_cmd_argument;      /* For all SMTP commands */
 extern uschar *smtp_cmd_buffer;        /* SMTP command buffer */
-extern time_t  smtp_connection_start;  /* Start time of SMTP connection */
+extern struct timeval smtp_connection_start; /* Start time of SMTP connection */
 extern uschar  smtp_connection_had[];  /* Recent SMTP commands */
 extern int     smtp_connect_backlog;   /* Max backlog permitted */
 extern double  smtp_delay_mail;        /* Current MAIL delay */
 extern uschar  smtp_connection_had[];  /* Recent SMTP commands */
 extern int     smtp_connect_backlog;   /* Max backlog permitted */
 extern double  smtp_delay_mail;        /* Current MAIL delay */
@@ -788,6 +922,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_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 */
 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 */
@@ -798,25 +933,32 @@ 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 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) */
 
 #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
 extern uschar *spam_score;             /* the spam score (float) */
 extern uschar *spam_score_int;         /* spam_score * 10 (int) */
 #endif
-#ifdef EXPERIMENTAL_SPF
+#ifdef SUPPORT_SPF
 extern uschar *spf_guess;              /* spf best-guess record */
 extern uschar *spf_header_comment;     /* spf header comment */
 extern uschar *spf_received;           /* Received-SPF: header */
 extern uschar *spf_result;             /* spf result in string form */
 extern uschar *spf_guess;              /* spf best-guess record */
 extern uschar *spf_header_comment;     /* spf header comment */
 extern uschar *spf_received;           /* Received-SPF: header */
 extern uschar *spf_result;             /* spf result in string form */
+extern BOOL    spf_result_guessed;     /* spf result is of best-guess operation */
 extern uschar *spf_smtp_comment;       /* spf comment to include in SMTP reply */
 #endif
 extern BOOL    split_spool_directory;  /* TRUE to use multiple subdirs */
 extern uschar *spf_smtp_comment;       /* spf comment to include in SMTP reply */
 #endif
 extern BOOL    split_spool_directory;  /* TRUE to use multiple subdirs */
+extern FILE   *spool_data_file;               /* handle for -D file */
 extern uschar *spool_directory;        /* Name of spool directory */
 extern uschar *spool_directory;        /* Name of spool directory */
+extern BOOL    spool_wireformat;       /* can write wireformat -D files */
 #ifdef EXPERIMENTAL_SRS
 extern uschar *srs_config;             /* SRS config secret:max age:hash length:use timestamp:use hash */
 extern uschar *srs_db_address;         /* SRS db address */
 #ifdef EXPERIMENTAL_SRS
 extern uschar *srs_config;             /* SRS config secret:max age:hash length:use timestamp:use hash */
 extern uschar *srs_db_address;         /* SRS db address */
@@ -839,13 +981,10 @@ extern int     string_datestamp_type;  /* After insertion by string_format */
 extern BOOL    strip_excess_angle_brackets; /* Surrounding route-addrs */
 extern BOOL    strip_trailing_dot;     /* Remove dots at ends of domains */
 extern uschar *submission_domain;      /* Domain for submission mode */
 extern BOOL    strip_excess_angle_brackets; /* Surrounding route-addrs */
 extern BOOL    strip_trailing_dot;     /* Remove dots at ends of domains */
 extern uschar *submission_domain;      /* Domain for submission mode */
-extern BOOL    submission_mode;        /* Can be forced from ACL */
 extern uschar *submission_name;        /* User name set from ACL */
 extern uschar *submission_name;        /* User name set from ACL */
-extern BOOL    suppress_local_fixups;  /* Can be forced from ACL */
-extern BOOL    suppress_local_fixups_default; /* former is reset to this; override with -G */
-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_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 */
 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,34 +998,22 @@ extern gid_t   system_filter_gid;      /* Gid for running system filter */
 extern BOOL    system_filter_gid_set;  /* TRUE if gid set */
 extern uid_t   system_filter_uid;      /* Uid for running system filter */
 extern BOOL    system_filter_uid_set;  /* TRUE if uid set */
 extern BOOL    system_filter_gid_set;  /* TRUE if gid set */
 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 blob    tcp_fastopen_nodata;    /* for zero-data TFO connect requests */
 extern BOOL    tcp_nodelay;            /* Controls TCP_NODELAY on daemon */
 extern BOOL    tcp_nodelay;            /* Controls TCP_NODELAY on daemon */
+extern tfo_state_t tcp_out_fastopen;   /* TCP fast open */
 #ifdef USE_TCP_WRAPPERS
 extern uschar *tcp_wrappers_daemon_name; /* tcpwrappers daemon lookup name */
 #endif
 extern int     test_harness_load_avg;  /* For use when testing */
 extern int     thismessage_size_limit; /* Limit for this message */
 extern int     timeout_frozen_after;   /* Max time to keep frozen messages */
 #ifdef USE_TCP_WRAPPERS
 extern uschar *tcp_wrappers_daemon_name; /* tcpwrappers daemon lookup name */
 #endif
 extern int     test_harness_load_avg;  /* For use when testing */
 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_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 int     transport_filter_timeout; /* Timeout for same */
-extern BOOL    transport_filter_timed_out; /* True if it did */
 
 extern transport_info transports_available[]; /* Vector of available transports */
 extern transport_instance *transports; /* Chain of instantiated transports */
 
 extern transport_info transports_available[]; /* Vector of available transports */
 extern transport_instance *transports; /* Chain of instantiated transports */
@@ -899,8 +1026,6 @@ extern tree_node *tree_duplicates;     /* Tree of duplicate addresses */
 extern tree_node *tree_nonrecipients;  /* Tree of nonrecipient addresses */
 extern tree_node *tree_unusable;       /* Tree of unusable addresses */
 
 extern tree_node *tree_nonrecipients;  /* Tree of nonrecipient addresses */
 extern tree_node *tree_unusable;       /* Tree of unusable addresses */
 
-extern BOOL    trusted_caller;         /* Caller is trusted */
-extern BOOL    trusted_config;         /* Configuration file is trusted */
 extern gid_t  *trusted_groups;         /* List of trusted groups */
 extern uid_t  *trusted_users;          /* List of trusted users */
 extern uschar *timezone_string;        /* Required timezone setting */
 extern gid_t  *trusted_groups;         /* List of trusted groups */
 extern uid_t  *trusted_users;          /* List of trusted users */
 extern uschar *timezone_string;        /* Required timezone setting */
@@ -916,6 +1041,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 *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 */
 extern uschar *version_copyright;      /* Copyright notice */
 extern uschar *version_date;           /* Date of compilation */
 extern uschar *version_cnumber;        /* Compile number */
similarity index 84%
rename from src/auths/sha1.c
rename to src/hash.c
index 67a1191..2ef64c8 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) 2010 - 2018  Exim maintainers
+ *  Copyright (c) University of Cambridge 1995 - 2009
+ *
+ *  Hash interface functions
+ */
 
 #ifndef STAND_ALONE
 
 #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 */
 
 
 /* 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;
 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)
+{
+/*XXX extend for sha512 */
+switch (h->method = m)
+  {
+  case HASH_SHA1:     h->hashlen = 20; SHA1_Init  (&h->u.sha1);     break;
+  case HASH_SHA2_256: h->hashlen = 32; SHA256_Init(&h->u.sha2_256); break;
+  case HASH_SHA2_384: h->hashlen = 48; SHA384_Init(&h->u.sha2_512); break;
+  case HASH_SHA2_512: h->hashlen = 64; SHA512_Init(&h->u.sha2_512); break;
+#ifdef EXIM_HAVE_SHA3
+  case HASH_SHA3_224: h->hashlen = 28;
+                     EVP_DigestInit(h->u.mctx = EVP_MD_CTX_new(), EVP_sha3_224());
+                     break;
+  case HASH_SHA3_256: h->hashlen = 32;
+                     EVP_DigestInit(h->u.mctx = EVP_MD_CTX_new(), EVP_sha3_256());
+                     break;
+  case HASH_SHA3_384: h->hashlen = 48;
+                     EVP_DigestInit(h->u.mctx = EVP_MD_CTX_new(), EVP_sha3_384());
+                     break;
+  case HASH_SHA3_512: h->hashlen = 64;
+                     EVP_DigestInit(h->u.mctx = EVP_MD_CTX_new(), EVP_sha3_512());
+                     break;
 #endif
 #endif
+  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_SHA2_256: SHA256_Update(&h->u.sha2_256, data, len); break;
+  case HASH_SHA2_384: SHA384_Update(&h->u.sha2_512, data, len); break;
+  case HASH_SHA2_512: SHA512_Update(&h->u.sha2_512, data, len); break;
+#ifdef EXIM_HAVE_SHA3
+  case HASH_SHA3_224:
+  case HASH_SHA3_256:
+  case HASH_SHA3_384:
+  case HASH_SHA3_512: EVP_DigestUpdate(h->u.mctx, data, len); break;
+#endif
+  /* 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_SHA2_256: SHA256_Final(b->data, &h->u.sha2_256); break;
+  case HASH_SHA2_384: SHA384_Final(b->data, &h->u.sha2_512); break;
+  case HASH_SHA2_512: SHA512_Final(b->data, &h->u.sha2_512); break;
+#ifdef EXIM_HAVE_SHA3
+  case HASH_SHA3_224:
+  case HASH_SHA3_256:
+  case HASH_SHA3_384:
+  case HASH_SHA3_512: EVP_DigestFinal(h->u.mctx, b->data, NULL); break;
+#endif
+  default: assert(0);
+  }
+}
+
+
+
+#elif defined(SHA_GNUTLS)
+/******************************************************************************/
+
+BOOL
+exim_sha_init(hctx * h, hashmethod m)
+{
+/*XXX extend for sha512 */
+switch (h->method = m)
+  {
+  case HASH_SHA1:     h->hashlen = 20; gnutls_hash_init(&h->sha, GNUTLS_DIG_SHA1);   break;
+  case HASH_SHA2_256: h->hashlen = 32; gnutls_hash_init(&h->sha, GNUTLS_DIG_SHA256); break;
+  case HASH_SHA2_384: h->hashlen = 48; gnutls_hash_init(&h->sha, GNUTLS_DIG_SHA384); break;
+  case HASH_SHA2_512: h->hashlen = 64; gnutls_hash_init(&h->sha, GNUTLS_DIG_SHA512); break;
+#ifdef EXIM_HAVE_SHA3
+  case HASH_SHA3_224: h->hashlen = 28; gnutls_hash_init(&h->sha, GNUTLS_DIG_SHA3_224); break;
+  case HASH_SHA3_256: h->hashlen = 32; gnutls_hash_init(&h->sha, GNUTLS_DIG_SHA3_256); break;
+  case HASH_SHA3_384: h->hashlen = 48; gnutls_hash_init(&h->sha, GNUTLS_DIG_SHA3_384); break;
+  case HASH_SHA3_512: h->hashlen = 64; gnutls_hash_init(&h->sha, GNUTLS_DIG_SHA3_512); 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)
+{
+/*XXX extend for sha512 */
+switch (h->method = m)
+  {
+  case HASH_SHA1:     h->hashlen = 20; gcry_md_open(&h->sha, GCRY_MD_SHA1, 0);   break;
+  case HASH_SHA2_256: h->hashlen = 32; gcry_md_open(&h->sha, GCRY_MD_SHA256, 0); break;
+  case HASH_SHA2_384: h->hashlen = 48; gcry_md_open(&h->sha, GCRY_MD_SHA384, 0); break;
+  case HASH_SHA2_512: h->hashlen = 64; gcry_md_open(&h->sha, GCRY_MD_SHA512, 0); break;
+  case HASH_SHA3_256: h->hashlen = 32; gcry_md_open(&h->sha, GCRY_MD_SHA3_256, 0); break;
+  case HASH_SHA3_384: h->hashlen = 48; gcry_md_open(&h->sha, GCRY_MD_SHA3_384, 0); break;
+  case HASH_SHA3_512: h->hashlen = 64; gcry_md_open(&h->sha, GCRY_MD_SHA3_512, 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)
+{
+/*XXX extend for sha512 */
+switch (h->method = m)
+  {
+  case HASH_SHA1:   h->hashlen = 20; sha1_starts(&h->u.sha1);    break;
+  case HASH_SHA2_256: 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_SHA2_256: 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_SHA2_256: sha2_finish(h->u.sha2, b->data); break;
+  }
+}
+
+
+
+
+#elif defined(SHA_NATIVE)
+/******************************************************************************/
+/* Only sha-1 supported */
 
 /*************************************************
 *        Start off a new SHA-1 computation.      *
 
 /*************************************************
 *        Start off a new SHA-1 computation.      *
@@ -33,8 +239,8 @@ Argument:  pointer to sha1 storage structure
 Returns:   nothing
 */
 
 Returns:   nothing
 */
 
-void
-sha1_start(sha1 *base)
+static void
+native_sha1_start(sha1 *base)
 {
 base->H[0] = 0x67452301;
 base->H[1] = 0xefcdab89;
 {
 base->H[0] = 0x67452301;
 base->H[1] = 0xefcdab89;
@@ -59,18 +265,18 @@ Arguments:
 Returns:     nothing
 */
 
 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++)
   {
 
 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;
   }
 
   text += 4;
   }
 
@@ -158,8 +364,8 @@ Arguments:
 Returns:    nothing
 */
 
 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];
 {
 int i;
 uschar work[64];
@@ -168,7 +374,7 @@ uschar work[64];
 
 while (length >= 64)
   {
 
 while (length >= 64)
   {
-  sha1_mid(base, text);
+  native_sha1_mid(base, text);
   text += 64;
   length -= 64;
   }
   text += 64;
   length -= 64;
   }
@@ -184,7 +390,7 @@ work[length] = 0x80;
 if (length > 55)
   {
   memset(work+length+1, 0, 63-length);
 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);
   }
   base->length -= 64;
   memset(work, 0, 56);
   }
@@ -210,7 +416,7 @@ memset(work+56, 0, 4);
 
 /* Process the final 64-byte chunk */
 
 
 /* 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. */
 
 
 /* Pass back the result, high-order byte first in each word. */
 
@@ -226,13 +432,103 @@ 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
+
+
+/******************************************************************************/
+/******************************************************************************/
+/******************************************************************************/
+/******************************************************************************/
+/* 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           *
 **************************************************
 *************************************************/
 
 /*************************************************
 **************************************************
 *             Stand-alone test program           *
 **************************************************
 *************************************************/
 
-#ifdef STAND_ALONE
+# ifdef STAND_ALONE
 
 /* Test values. The first 128 may contain binary zeros and have increasing
 length. */
 
 /* Test values. The first 128 may contain binary zeros and have increasing
 length. */
@@ -516,7 +812,7 @@ int main(void)
 sha1 base;
 int j;
 int i = 0x01020304;
 sha1 base;
 int j;
 int i = 0x01020304;
-uschar *ctest = (uschar *)(&i);
+uschar *ctest = US (&i);
 uschar buffer[256];
 uschar digest[20];
 uschar s[41];
 uschar buffer[256];
 uschar digest[20];
 uschar s[41];
@@ -525,8 +821,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]);
 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");
   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 +836,13 @@ memset(ctest, 'a', 1000000);
 
 printf("1 000 000 repetitions of 'a'\n");
 printf("Should be: %s\n", atest);
 
 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");
 
 }
 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..5bd47ac
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ *  Exim - an Internet mail transport agent
+ *
+ *  Copyright (C) 1995 - 2018  Exim maintainers
+ *
+ *  Hash interface functions
+ */
+
+#include "exim.h"
+
+#if !defined(HASH_H)   /* entire file */
+#define HASH_H
+
+#include "sha_ver.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_NULL,
+  HASH_SHA1,
+
+  HASH_SHA2_256,
+  HASH_SHA2_384,
+  HASH_SHA2_512,
+
+  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_256;   /* SHA256 or 224 block                       */
+    SHA512_CTX   sha2_512;   /* SHA512 or 384 block                       */
+#ifdef EXIM_HAVE_SHA3
+    EVP_MD_CTX * mctx;      /* SHA3 block                                */
+#endif
+  } 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 *);
+
+#endif
+/* End of File */
index 8136c69..74df32c 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     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. */
 
 
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -98,16 +98,18 @@ header_line **hptr;
 
 uschar *p, *q;
 uschar buffer[HEADER_ADD_BUFFER_SIZE];
 
 uschar *p, *q;
 uschar buffer[HEADER_ADD_BUFFER_SIZE];
+gstring gs = { .size = HEADER_ADD_BUFFER_SIZE, .ptr = 0, .s = buffer };
 
 
-if (header_last == NULL) return;
+if (!header_last) return;
 
 
-if (!string_vformat(buffer, sizeof(buffer), format, ap))
+if (!string_vformat(&gs, FALSE, format, ap))
   log_write(0, LOG_MAIN|LOG_PANIC_DIE, "string too long in header_add: "
   log_write(0, LOG_MAIN|LOG_PANIC_DIE, "string too long in header_add: "
-    "%.100s ...", buffer);
+    "%.100s ...", string_from_gstring(&gs));
+string_from_gstring(&gs);
 
 /* Find where to insert this header */
 
 
 /* Find where to insert this header */
 
-if (name == NULL)
+if (!name)
   {
   if (after)
     {
   {
   if (after)
     {
@@ -119,10 +121,10 @@ if (name == NULL)
     hptr = &header_list;
 
     /* header_list->text can be NULL if we get here between when the new
     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. */
 
     that header to be first, so skip it for now. */
 
-    if (header_list->text == NULL)
+    if (!header_list->text)
       hptr = &header_list->next;
     h = *hptr;
     }
       hptr = &header_list->next;
     h = *hptr;
     }
@@ -132,17 +134,16 @@ else
   {
   int len = Ustrlen(name);
 
   {
   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))
-    {
-    if (header_testname(h, name, len, TRUE)) break;
-    }
+  for (hptr = &header_list; (h = *hptr); hptr = &h->next)
+    if (header_testname(h, name, len, TRUE))
+      break;
 
   /* Handle the case where no header is found. To insert at the bottom, nothing
   needs to be done. */
 
 
   /* Handle the case where no header is found. To insert at the bottom, nothing
   needs to be done. */
 
-  if (h == NULL)
+  if (!h)
     {
     if (topnot)
       {
     {
     if (topnot)
       {
@@ -155,14 +156,12 @@ else
   true. In this case, we want to include deleted headers in the block. */
 
   else if (after)
   true. In this case, we want to include deleted headers in the block. */
 
   else if (after)
-    {
     for (;;)
       {
     for (;;)
       {
-      if (h->next == NULL || !header_testname(h, name, len, FALSE)) break;
+      if (!h->next || !header_testname(h, name, len, FALSE)) break;
       hptr = &(h->next);
       h = h->next;
       }
       hptr = &(h->next);
       h = h->next;
       }
-    }
   }
 
 /* Loop for multiple header lines, taking care about continuations. At this
   }
 
 /* Loop for multiple header lines, taking care about continuations. At this
@@ -174,7 +173,7 @@ for (p = q = buffer; *p != 0; )
   for (;;)
     {
     q = Ustrchr(q, '\n');
   for (;;)
     {
     q = Ustrchr(q, '\n');
-    if (q == NULL) q = p + Ustrlen(p);
+    if (!q) q = p + Ustrlen(p);
     if (*(++q) != ' ' && *q != '\t') break;
     }
 
     if (*(++q) != ' ' && *q != '\t') break;
     }
 
@@ -187,7 +186,7 @@ for (p = q = buffer; *p != 0; )
   *hptr = new;
   hptr = &(new->next);
 
   *hptr = new;
   hptr = &(new->next);
 
-  if (h == NULL) header_last = new;
+  if (!h) header_last = new;
   p = q;
   }
 }
   p = q;
   }
 }
@@ -450,10 +449,11 @@ for (s = strings; s != NULL; s = s->next)
 
 va_start(ap, count);
 for (i = 0; i < count; i++)
 
 va_start(ap, count);
 for (i = 0; i < count; i++)
-  {
   if (one_pattern_match(name, slen, has_addresses, va_arg(ap, uschar *)))
   if (one_pattern_match(name, slen, has_addresses, va_arg(ap, uschar *)))
+    {
+    va_end(ap);
     return cond;
     return cond;
-  }
+    }
 va_end(ap);
 
 return !cond;
 va_end(ap);
 
 return !cond;
index 00524f4..29c977f 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2012 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for finding hosts, either by gethostbyname(), gethostbyaddr(), or
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for finding hosts, either by gethostbyname(), gethostbyaddr(), or
@@ -84,7 +84,7 @@ if (limit < 1)
   return 0;
 if (random_seed == 0)
   {
   return 0;
 if (random_seed == 0)
   {
-  if (running_in_test_harness) random_seed = 42; else
+  if (f.running_in_test_harness) random_seed = 42; else
     {
     int p = (int)getpid();
     random_seed = (int)time(NULL) ^ ((p << 16) | p);
     {
     int p = (int)getpid();
     random_seed = (int)time(NULL) ^ ((p << 16) | p);
@@ -94,6 +94,53 @@ random_seed = 1103515245 * random_seed + 12345;
 return (unsigned int)(random_seed >> 16) % limit;
 }
 
 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
 *************************************************/
 
 /* 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
 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 *
 */
 
 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);
 {
 #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;
 #endif
 
 int ipa;
-uschar *lname = name;
+const uschar *lname = name;
 uschar *adds;
 uschar **alist;
 struct hostent *yield;
 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");
 
   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)
 /* 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;
 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;
   int count = 0;
 
   lookup_dnssec_authenticated = NULL;
@@ -233,14 +251,13 @@ else
     }
 
   for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
     }
 
   for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
-       rr != NULL;
+       rr;
        rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
        rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
-    {
-    if (rr->type == type) count++;
-    }
+    if (rr->type == type)
+      count++;
 
   yield = store_get(sizeof(struct hostent));
 
   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;
   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);
   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;
        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++)
     *alist++ = adds;
     n = host_aton(da->address, x);
     for (i = 0; i < n; i++)
@@ -295,19 +312,18 @@ Returns:      nothing
 */
 
 void
 */
 
 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;
 {
 int sep = 0;
 int fake_mx = MX_NONE;          /* This value is actually -1 */
 uschar *name;
-uschar buffer[1024];
 
 
-if (list == NULL) return;
+if (!list) return;
 if (randomize) fake_mx--;       /* Start at -2 for randomizing */
 
 *anchor = NULL;
 
 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)))
   {
   host_item *h;
 
   {
   host_item *h;
 
@@ -318,7 +334,7 @@ while ((name = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
     }
 
   h = store_get(sizeof(host_item));
     }
 
   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;
   h->address = NULL;
   h->port = PORT_NONE;
   h->mx = fake_mx;
@@ -327,7 +343,7 @@ while ((name = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
   h->why = hwhy_unknown;
   h->last_try = 0;
 
   h->why = hwhy_unknown;
   h->last_try = 0;
 
-  if (*anchor == NULL)
+  if (!*anchor)
     {
     h->next = NULL;
     *anchor = h;
     {
     h->next = NULL;
     *anchor = h;
@@ -342,7 +358,7 @@ while ((name = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
       }
     else
       {
       }
     else
       {
-      while (hh->next != NULL && h->sort_key >= (hh->next)->sort_key)
+      while (hh->next && h->sort_key >= hh->next->sort_key)
         hh = hh->next;
       h->next = hh->next;
       hh->next = h;
         hh = hh->next;
       h->next = hh->next;
       hh->next = h;
@@ -444,7 +460,7 @@ Returns:    a port number or PORT_NONE
 int
 host_item_get_port(host_item *h)
 {
 int
 host_item_get_port(host_item *h)
 {
-uschar *p;
+const uschar *p;
 int port, x;
 int len = Ustrlen(h->name);
 
 int port, x;
 int len = Ustrlen(h->name);
 
@@ -504,7 +520,9 @@ There wouldn't be two different variables if I had got all this right in the
 first place.
 
 Because this data may survive over more than one incoming SMTP message, it has
 first place.
 
 Because this data may survive over more than one incoming SMTP message, it has
-to be in permanent store.
+to be in permanent store.  However, STARTTLS has to be forgotten and redone
+on a multi-message conn, so this will be called once per message then.  Hence
+we use malloc, so we can free.
 
 Arguments:  none
 Returns:    nothing
 
 Arguments:  none
 Returns:    nothing
@@ -514,13 +532,12 @@ void
 host_build_sender_fullhost(void)
 {
 BOOL show_helo = TRUE;
 host_build_sender_fullhost(void)
 {
 BOOL show_helo = TRUE;
-uschar *address;
+uschar * address, * fullhost, * rcvhost, * reset_point;
 int len;
 int len;
-int old_pool = store_pool;
 
 
-if (sender_host_address == NULL) return;
+if (!sender_host_address) return;
 
 
-store_pool = POOL_PERM;
+reset_point = store_get(0);
 
 /* Set up address, with or without the port. After discussion, it seems that
 the only format that doesn't cause trouble is [aaaa]:pppp. However, we can't
 
 /* Set up address, with or without the port. After discussion, it seems that
 the only format that doesn't cause trouble is [aaaa]:pppp. However, we can't
@@ -528,17 +545,17 @@ 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);
 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. */
 
   *(Ustrrchr(address, ':')) = 0;
 
 /* If there's no EHLO/HELO data, we can't show it. */
 
-if (sender_helo_name == NULL) show_helo = FALSE;
+if (!sender_helo_name) 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
 
 /* 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] == '[' &&
 it happens, the code works for both IPv4 and IPv6. */
 
 else if (sender_helo_name[0] == '[' &&
@@ -570,46 +587,40 @@ else if (sender_helo_name[0] == '[' &&
 
 /* Host name is not verified */
 
 
 /* Host name is not verified */
 
-if (sender_host_name == NULL)
+if (!sender_host_name)
   {
   uschar *portptr = Ustrstr(address, "]:");
   {
   uschar *portptr = Ustrstr(address, "]:");
-  int size = 0;
-  int ptr = 0;
+  gstring * g;
   int adlen;    /* Sun compiler doesn't like ++ in initializers */
 
   int adlen;    /* Sun compiler doesn't like ++ in initializers */
 
-  adlen = (portptr == NULL)? Ustrlen(address) : (++portptr - address);
-  sender_fullhost = (sender_helo_name == NULL)? address :
-    string_sprintf("(%s) %s", sender_helo_name, address);
+  adlen = portptr ? (++portptr - address) : Ustrlen(address);
+  fullhost = sender_helo_name
+    ? string_sprintf("(%s) %s", sender_helo_name, address)
+    : address;
 
 
-  sender_rcvhost = string_cat(NULL, &size, &ptr, address, adlen);
+  g = string_catn(NULL, address, adlen);
 
 
-  if (sender_ident != NULL || show_helo || portptr != NULL)
+  if (sender_ident || show_helo || portptr)
     {
     int firstptr;
     {
     int firstptr;
-    sender_rcvhost = string_cat(sender_rcvhost, &size, &ptr, US" (", 2);
-    firstptr = ptr;
+    g = string_catn(g, US" (", 2);
+    firstptr = g->ptr;
 
 
-    if (portptr != NULL)
-      sender_rcvhost = string_append(sender_rcvhost, &size, &ptr, 2, US"port=",
-        portptr + 1);
+    if (portptr)
+      g = string_append(g, 2, US"port=", portptr + 1);
 
     if (show_helo)
 
     if (show_helo)
-      sender_rcvhost = string_append(sender_rcvhost, &size, &ptr, 2,
-        (firstptr == ptr)? US"helo=" : US" helo=", sender_helo_name);
+      g = string_append(g, 2,
+        firstptr == g->ptr ? US"helo=" : US" helo=", sender_helo_name);
 
 
-    if (sender_ident != NULL)
-      sender_rcvhost = string_append(sender_rcvhost, &size, &ptr, 2,
-        (firstptr == ptr)? US"ident=" : US" ident=", sender_ident);
+    if (sender_ident)
+      g = string_append(g, 2,
+        firstptr == g->ptr ? US"ident=" : US" ident=", sender_ident);
 
 
-    sender_rcvhost = string_cat(sender_rcvhost, &size, &ptr, US")", 1);
+    g = string_catn(g, US")", 1);
     }
 
     }
 
-  sender_rcvhost[ptr] = 0;   /* string_cat() always leaves room */
-
-  /* Release store, because string_cat allocated a minimum of 100 bytes that
-  are rarely completely used. */
-
-  store_reset(sender_rcvhost + ptr + 1);
+  rcvhost = string_from_gstring(g);
   }
 
 /* Host name is known and verified. Unless we've already found that the HELO
   }
 
 /* Host name is known and verified. Unless we've already found that the HELO
@@ -622,25 +633,30 @@ else
 
   if (show_helo)
     {
 
   if (show_helo)
     {
-    sender_fullhost = string_sprintf("%s (%s) %s", sender_host_name,
+    fullhost = string_sprintf("%s (%s) %s", sender_host_name,
       sender_helo_name, address);
       sender_helo_name, address);
-    sender_rcvhost = (sender_ident == NULL)?
-      string_sprintf("%s (%s helo=%s)", sender_host_name,
-        address, sender_helo_name) :
-      string_sprintf("%s\n\t(%s helo=%s ident=%s)", sender_host_name,
-        address, sender_helo_name, sender_ident);
+    rcvhost = sender_ident
+      ?  string_sprintf("%s\n\t(%s helo=%s ident=%s)", sender_host_name,
+        address, sender_helo_name, sender_ident)
+      : string_sprintf("%s (%s helo=%s)", sender_host_name,
+        address, sender_helo_name);
     }
   else
     {
     }
   else
     {
-    sender_fullhost = string_sprintf("%s %s", sender_host_name, address);
-    sender_rcvhost = (sender_ident == NULL)?
-      string_sprintf("%s (%s)", sender_host_name, address) :
-      string_sprintf("%s (%s ident=%s)", sender_host_name, address,
-        sender_ident);
+    fullhost = string_sprintf("%s %s", sender_host_name, address);
+    rcvhost = sender_ident
+      ?  string_sprintf("%s (%s ident=%s)", sender_host_name, address,
+        sender_ident)
+      : string_sprintf("%s (%s)", sender_host_name, address);
     }
   }
 
     }
   }
 
-store_pool = old_pool;
+if (sender_fullhost) store_free(sender_fullhost);
+sender_fullhost = string_copy_malloc(fullhost);
+if (sender_rcvhost) store_free(sender_rcvhost);
+sender_rcvhost = string_copy_malloc(rcvhost);
+
+store_reset(reset_point);
 
 DEBUG(D_host_lookup) debug_printf("sender_fullhost = %s\n", sender_fullhost);
 DEBUG(D_host_lookup) debug_printf("sender_rcvhost = %s\n", sender_rcvhost);
 
 DEBUG(D_host_lookup) debug_printf("sender_fullhost = %s\n", sender_fullhost);
 DEBUG(D_host_lookup) debug_printf("sender_rcvhost = %s\n", sender_rcvhost);
@@ -670,24 +686,21 @@ Returns:    pointer to a string in big_buffer
 uschar *
 host_and_ident(BOOL useflag)
 {
 uschar *
 host_and_ident(BOOL useflag)
 {
-if (sender_fullhost == NULL)
-  {
-  (void)string_format(big_buffer, big_buffer_size, "%s%s", useflag? "U=" : "",
-     (sender_ident == NULL)? US"unknown" : sender_ident);
-  }
+if (!sender_fullhost)
+  (void)string_format(big_buffer, big_buffer_size, "%s%s", useflag ? "U=" : "",
+     sender_ident ? sender_ident : US"unknown");
 else
   {
 else
   {
-  uschar *flag = useflag? US"H=" : US"";
-  uschar *iface = US"";
-  if ((log_extra_selector & LX_incoming_interface) != 0 &&
-       interface_address != NULL)
+  uschar * flag = useflag ? US"H=" : US"";
+  uschar * iface = US"";
+  if (LOGGING(incoming_interface) && interface_address)
     iface = string_sprintf(" I=[%s]:%d", interface_address, interface_port);
     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",
-      flag, sender_fullhost, iface);
-  else
+  if (sender_ident)
     (void)string_format(big_buffer, big_buffer_size, "%s%s%s U=%s",
       flag, sender_fullhost, iface, sender_ident);
     (void)string_format(big_buffer, big_buffer_size, "%s%s%s U=%s",
       flag, sender_fullhost, iface, sender_ident);
+  else
+    (void)string_format(big_buffer, big_buffer_size, "%s%s%s",
+      flag, sender_fullhost, iface);
   }
 return big_buffer;
 }
   }
 return big_buffer;
 }
@@ -718,20 +731,18 @@ Returns:      a chain of ip_address_items, each containing to a textual
 */
 
 ip_address_item *
 */
 
 ip_address_item *
-host_build_ifacelist(uschar *list, uschar *name)
+host_build_ifacelist(const uschar *list, uschar *name)
 {
 int sep = 0;
 uschar *s;
 {
 int sep = 0;
 uschar *s;
-uschar buffer[64];
-ip_address_item *yield = NULL;
-ip_address_item *last = NULL;
-ip_address_item *next;
+ip_address_item * yield = NULL, * last = NULL, * next;
 
 
-while ((s = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
+while ((s = string_nextinlist(&list, &sep, NULL, 0)))
   {
   int ipv;
   int port = host_address_extract_port(s);            /* Leaves just the IP address */
   {
   int ipv;
   int port = host_address_extract_port(s);            /* Leaves just the IP address */
-  if ((ipv = string_is_ip_address(s, NULL)) == 0)
+
+  if (!(ipv = string_is_ip_address(s, NULL)))
     log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Malformed IP address \"%s\" in %s",
       s, name);
 
     log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Malformed IP address \"%s\" in %s",
       s, name);
 
@@ -749,7 +760,9 @@ while ((s = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
   next->port = port;
   next->v6_include_v4 = FALSE;
 
   next->port = port;
   next->v6_include_v4 = FALSE;
 
-  if (yield == NULL) yield = last = next; else
+  if (!yield)
+    yield = last = next;
+  else
     {
     last->next = next;
     last = next;
     {
     last->next = next;
     last = next;
@@ -811,9 +824,9 @@ ip_address_item *running_interfaces = NULL;
 if (local_interface_data == NULL)
   {
   void *reset_item = store_get(0);
 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");
     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;
 
     US"extra_local_interfaces");
   ip_address_item *ipa;
 
@@ -904,21 +917,21 @@ if (type < 0)
   if (family == AF_INET6)
     {
     struct sockaddr_in6 *sk = (struct sockaddr_in6 *)arg;
   if (family == AF_INET6)
     {
     struct sockaddr_in6 *sk = (struct sockaddr_in6 *)arg;
-    yield = (uschar *)inet_ntop(family, &(sk->sin6_addr), CS addr_buffer,
+    yield = US inet_ntop(family, &(sk->sin6_addr), CS addr_buffer,
       sizeof(addr_buffer));
     if (portptr != NULL) *portptr = ntohs(sk->sin6_port);
     }
   else
     {
     struct sockaddr_in *sk = (struct sockaddr_in *)arg;
       sizeof(addr_buffer));
     if (portptr != NULL) *portptr = ntohs(sk->sin6_port);
     }
   else
     {
     struct sockaddr_in *sk = (struct sockaddr_in *)arg;
-    yield = (uschar *)inet_ntop(family, &(sk->sin_addr), CS addr_buffer,
+    yield = US inet_ntop(family, &(sk->sin_addr), CS addr_buffer,
       sizeof(addr_buffer));
     if (portptr != NULL) *portptr = ntohs(sk->sin_port);
     }
   }
 else
   {
       sizeof(addr_buffer));
     if (portptr != NULL) *portptr = ntohs(sk->sin_port);
     }
   }
 else
   {
-  yield = (uschar *)inet_ntop(type, arg, CS addr_buffer, sizeof(addr_buffer));
+  yield = US inet_ntop(type, arg, CS addr_buffer, sizeof(addr_buffer));
   }
 
 /* If the result is a mapped IPv4 address, show it in V4 format. */
   }
 
 /* If the result is a mapped IPv4 address, show it in V4 format. */
@@ -972,7 +985,7 @@ Returns:     the number of ints used
 */
 
 int
 */
 
 int
-host_aton(uschar *address, int *bin)
+host_aton(const uschar *address, int *bin)
 {
 int x[4];
 int v4offset = 0;
 {
 int x[4];
 int v4offset = 0;
@@ -984,8 +997,8 @@ supported. */
 
 if (Ustrchr(address, ':') != NULL)
   {
 
 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;
   BOOL ipv4_ends = FALSE;
   int ci = 0;
   int nulloffset = 0;
@@ -1052,7 +1065,7 @@ if (Ustrchr(address, ':') != NULL)
 /* Handle IPv4 address */
 
 (void)sscanf(CS address, "%d.%d.%d.%d", x, x+1, x+2, x+3);
 /* 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;
 }
 
 return v4offset+1;
 }
 
@@ -1083,7 +1096,7 @@ for (i = 0; i < count; i++)
   if (mask == 0) wordmask = 0;
   else if (mask < 32)
     {
   if (mask == 0) wordmask = 0;
   else if (mask < 32)
     {
-    wordmask = (-1) << (32 - mask);
+    wordmask = (uint)(-1) << (32 - mask);
     mask = 0;
     }
   else
     mask = 0;
     }
   else
@@ -1131,35 +1144,83 @@ if (count == 1)
   {
   j = binary[0];
   for (i = 24; i >= 0; i -= 8)
   {
   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
   }
 else
-  {
   for (i = 0; i < 4; i++)
     {
     j = binary[i];
   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 */
 
 if (mask < 0)
   *tt = 0;
 else
 
 tt--;   /* lose final separator */
 
 if (mask < 0)
   *tt = 0;
 else
-  {
-  sprintf(CS tt, "/%d", mask);
-  while (*tt) tt++;
-  }
+  tt += sprintf(CS tt, "/%d", mask);
 
 return tt - buffer;
 }
 
 
 
 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           *
 
 /*************************************************
 *        Check port for tls_on_connect           *
@@ -1179,7 +1240,7 @@ host_is_tls_on_connect_port(int port)
 {
 int sep = 0;
 uschar buffer[32];
 {
 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;
 
 uschar *s;
 uschar *end;
 
@@ -1214,7 +1275,7 @@ Returns:
 */
 
 BOOL
 */
 
 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];
 {
 int i;
 int address[4];
@@ -1255,7 +1316,7 @@ for (i = 0; i < size; i++)
   if (mlen == 0) mask = 0;
   else if (mlen < 32)
     {
   if (mlen == 0) mask = 0;
   else if (mlen < 32)
     {
-    mask = (-1) << (32 - mlen);
+    mask = (uint)(-1) << (32 - mlen);
     mlen = 0;
     }
   else
     mlen = 0;
     }
   else
@@ -1329,9 +1390,9 @@ for (h = host; h != last->next; h = h->next)
   if (hosts_treat_as_local != NULL)
     {
     int rc;
   if (hosts_treat_as_local != NULL)
     {
     int rc;
-    uschar *save = deliver_domain;
+    const uschar *save = deliver_domain;
     deliver_domain = h->name;   /* set $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;
       &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL);
     deliver_domain = save;
     if (rc == OK) goto FOUND_LOCAL;
@@ -1455,6 +1516,9 @@ int len;
 uschar *s, *t;
 struct hostent *hosts;
 struct in_addr addr;
 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 */
 
 
 /* Lookup on IPv6 system */
 
@@ -1490,6 +1554,11 @@ addr.s_addr = (S_ADDR_TYPE)inet_addr(CS sender_host_address);
 hosts = gethostbyaddr(CS(&addr), sizeof(addr), AF_INET);
 #endif
 
 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)
 /* Failed to look up the host. */
 
 if (hosts == NULL)
@@ -1513,7 +1582,7 @@ if (hosts->h_name == NULL || hosts->h_name[0] == 0 || hosts->h_name[0] == '.')
 /* Copy and lowercase the name, which is in static storage in many systems.
 Put it in permanent memory. */
 
 /* Copy and lowercase the name, which is in static storage in many systems.
 Put it in permanent memory. */
 
-s = (uschar *)hosts->h_name;
+s = US hosts->h_name;
 len = Ustrlen(s) + 1;
 t = sender_host_name = store_get_perm(len);
 while (*s != 0) *t++ = tolower(*s++);
 len = Ustrlen(s) + 1;
 t = sender_host_name = store_get_perm(len);
 while (*s != 0) *t++ = tolower(*s++);
@@ -1572,7 +1641,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
 
               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.
 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 +1659,7 @@ uschar *hname, *save_hostname;
 uschar **aliases;
 uschar buffer[256];
 uschar *ordername;
 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;
 dns_record *rr;
 dns_answer dnsa;
 dns_scan dnss;
@@ -1603,7 +1672,7 @@ HDEBUG(D_host_lookup)
 /* For testing the case when a lookup does not complete, we have a special
 reserved IP address. */
 
 /* For testing the case when a lookup does not complete, we have a special
 reserved IP address. */
 
-if (running_in_test_harness &&
+if (f.running_in_test_harness &&
     Ustrcmp(sender_host_address, "99.99.99.99") == 0)
   {
   HDEBUG(D_host_lookup)
     Ustrcmp(sender_host_address, "99.99.99.99") == 0)
   {
   HDEBUG(D_host_lookup)
@@ -1615,14 +1684,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. */
 
 /* 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);
   {
   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
 
     /* 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 +1705,6 @@ while ((ordername = string_nextinlist(&list, &sep, buffer, sizeof(buffer)))
       int count = 0;
       int old_pool = store_pool;
 
       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",
       sender_host_dnssec = dns_is_secure(&dnsa);
       DEBUG(D_dns)
         debug_printf("Reverse DNS security status: %s\n",
@@ -1647,11 +1713,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);
       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))
            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. */
 
       /* Get store for the list of aliases. For compatibility with
       gethostbyaddr, we make an empty list if there are none. */
@@ -1661,7 +1726,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);
       /* 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;
            rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
         {
         uschar *s = NULL;
@@ -1672,7 +1737,7 @@ while ((ordername = string_nextinlist(&list, &sep, buffer, sizeof(buffer)))
         truncated and dn_expand may fail. */
 
         if (dn_expand(dnsa.answer, dnsa.answer + dnsa.answerlen,
         truncated and dn_expand may fail. */
 
         if (dn_expand(dnsa.answer, dnsa.answer + dnsa.answerlen,
-             (uschar *)(rr->data), (DN_EXPAND_ARG4_TYPE)(s), ssize) < 0)
+             US (rr->data), (DN_EXPAND_ARG4_TYPE)(s), ssize) < 0)
           {
           log_write(0, LOG_MAIN, "host name alias list truncated for %s",
             sender_host_address);
           {
           log_write(0, LOG_MAIN, "host name alias list truncated for %s",
             sender_host_address);
@@ -1686,8 +1751,8 @@ while ((ordername = string_nextinlist(&list, &sep, buffer, sizeof(buffer)))
             "empty name: treated as non-existent host name\n");
           continue;
           }
             "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++; }
         }
 
         while (*s != 0) { *s = tolower(*s); s++; }
         }
 
@@ -1729,9 +1794,9 @@ while ((ordername = string_nextinlist(&list, &sep, buffer, sizeof(buffer)))
 /* If we have failed to find a name, return FAIL and log when required.
 NB host_lookup_msg must be in permanent store.  */
 
 /* If we have failed to find a name, return FAIL and log when required.
 NB host_lookup_msg must be in permanent store.  */
 
-if (sender_host_name == NULL)
+if (!sender_host_name)
   {
   {
-  if (host_checking || !log_testing_mode)
+  if (host_checking || !f.log_testing_mode)
     log_write(L_host_lookup_failed, LOG_MAIN, "no host name found for IP "
       "address %s", sender_host_address);
   host_lookup_msg = US" (failed to find host name from IP address)";
     log_write(L_host_lookup_failed, LOG_MAIN, "no host name found for IP "
       "address %s", sender_host_address);
   host_lookup_msg = US" (failed to find host name from IP address)";
@@ -1742,8 +1807,8 @@ if (sender_host_name == NULL)
 HDEBUG(D_host_lookup)
   {
   uschar **aliases = sender_host_aliases;
 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
   }
 
 /* We need to verify that a forward lookup on the name we found does indeed
@@ -1761,26 +1826,29 @@ the names, and accepts only those that have the correct IP address. */
 
 save_hostname = sender_host_name;   /* Save for error messages */
 aliases = sender_host_aliases;
 
 save_hostname = sender_host_name;   /* Save for error messages */
 aliases = sender_host_aliases;
-for (hname = sender_host_name; hname != NULL; hname = *aliases++)
+for (hname = sender_host_name; hname; hname = *aliases++)
   {
   int rc;
   BOOL ok = FALSE;
   {
   int rc;
   BOOL ok = FALSE;
-  host_item h;
-  h.next = NULL;
-  h.name = hname;
-  h.mx = MX_NONE;
-  h.address = 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. */
+  host_item h = { .next = NULL, .name = hname, .mx = MX_NONE, .address = NULL };
+  dnssec_domains d =
+    { .request = sender_host_dnssec ? US"*" : NULL, .require = NULL };
 
 
-  if ((rc = host_find_byname(&h, NULL, 0, NULL, FALSE)) == HOST_FOUND)
+  if (  (rc = host_find_bydns(&h, NULL, HOST_FIND_BY_A | HOST_FIND_BY_AAAA,
+         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);
     {
     host_item *hh;
     HDEBUG(D_host_lookup) debug_printf("checking addresses for %s\n", hname);
-    for (hh = &h; hh != NULL; hh = hh->next)
-      {
+
+    /* 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; 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);
       if (host_is_in_net(hh->address, sender_host_address, 0))
         {
         HDEBUG(D_host_lookup) debug_printf("  %s OK\n", hh->address);
@@ -1788,10 +1856,8 @@ for (hname = sender_host_name; hname != NULL; hname = *aliases++)
         break;
         }
       else
         break;
         }
       else
-        {
         HDEBUG(D_host_lookup) debug_printf("  %s\n", hh->address);
         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);
     if (!ok) HDEBUG(D_host_lookup)
       debug_printf("no IP address for %s matched %s\n", hname,
         sender_host_address);
@@ -1804,9 +1870,7 @@ for (hname = sender_host_name; hname != NULL; hname = *aliases++)
     return DEFER;
     }
   else
     return DEFER;
     }
   else
-    {
     HDEBUG(D_host_lookup) debug_printf("no IP addresses found for %s\n", hname);
     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. */
 
   /* 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 +1956,8 @@ Returns:                 HOST_FIND_FAILED  Failed to find the host or domain
 */
 
 int
 */
 
 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;
 {
 int i, yield, times;
 uschar **addrlist;
@@ -1903,22 +1967,12 @@ BOOL temp_error = FALSE;
 int af;
 #endif
 
 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,
 /* 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
 
 /* 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 +1986,8 @@ lookups here (except when testing standalone). */
   #else
   if (disable_ipv6 ||
     (dns_ipv4_lookup != NULL &&
   #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; }
   #endif
 
     { af = AF_INET; times = 1; }
@@ -1949,7 +2003,7 @@ lookups here (except when testing standalone). */
 /* Initialize the flag that gets set for DNS syntax check errors, so that the
 interface to this function can be similar to host_find_bydns. */
 
 /* Initialize the flag that gets set for DNS syntax check errors, so that the
 interface to this function can be similar to host_find_bydns. */
 
-host_find_failed_syntax = FALSE;
+f.host_find_failed_syntax = FALSE;
 
 /* Loop to look up both kinds of address in an IPv6 world */
 
 
 /* Loop to look up both kinds of address in an IPv6 world */
 
@@ -1962,13 +2016,16 @@ for (i = 1; i <= times;
   BOOL ipv4_addr;
   int error_num = 0;
   struct hostent *hostdata;
   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
 
 
   #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 HAVE_IPV6
-  if (running_in_test_harness)
+  if (f.running_in_test_harness)
     hostdata = host_fake_gethostbyname(host->name, af, &error_num);
   else
     {
     hostdata = host_fake_gethostbyname(host->name, af, &error_num);
   else
     {
@@ -1981,7 +2038,7 @@ for (i = 1; i <= times;
     }
 
   #else    /* not HAVE_IPV6 */
     }
 
   #else    /* not HAVE_IPV6 */
-  if (running_in_test_harness)
+  if (f.running_in_test_harness)
     hostdata = host_fake_gethostbyname(host->name, AF_INET, &error_num);
   else
     {
     hostdata = host_fake_gethostbyname(host->name, AF_INET, &error_num);
   else
     {
@@ -1990,17 +2047,21 @@ for (i = 1; i <= times;
     }
   #endif   /* HAVE_IPV6 */
 
     }
   #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;
   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
       #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;
       }
       #endif
       default: error = US"?"; break;
       }
@@ -2027,7 +2088,7 @@ for (i = 1; i <= times;
 
   if (hostdata->h_name[0] != 0 &&
       Ustrcmp(host->name, hostdata->h_name) != 0)
 
   if (hostdata->h_name[0] != 0 &&
       Ustrcmp(host->name, hostdata->h_name) != 0)
-    host->name = string_copy_dnsdomain((uschar *)hostdata->h_name);
+    host->name = string_copy_dnsdomain(US hostdata->h_name);
   if (fully_qualified_name != NULL) *fully_qualified_name = host->name;
 
   /* Get the list of addresses. IPv4 and IPv6 addresses can be distinguished
   if (fully_qualified_name != NULL) *fully_qualified_name = host->name;
 
   /* Get the list of addresses. IPv4 and IPv6 addresses can be distinguished
@@ -2102,7 +2163,7 @@ if (host->address == NULL)
 
   HDEBUG(D_host_lookup) debug_printf("%s\n", msg);
   if (temp_error) goto RETURN_AGAIN;
 
   HDEBUG(D_host_lookup) debug_printf("%s\n", msg);
   if (temp_error) goto RETURN_AGAIN;
-  if (host_checking || !log_testing_mode)
+  if (host_checking || !f.log_testing_mode)
     log_write(L_host_lookup_failed, LOG_MAIN, "%s", msg);
   return HOST_FIND_FAILED;
   }
     log_write(L_host_lookup_failed, LOG_MAIN, "%s", msg);
   return HOST_FIND_FAILED;
   }
@@ -2116,7 +2177,7 @@ yield = local_host_check?
 
 HDEBUG(D_host_lookup)
   {
 
 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",
   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 +2207,9 @@ RETURN_AGAIN:
   {
   #ifndef STAND_ALONE
   int rc;
   {
   #ifndef STAND_ALONE
   int rc;
-  uschar *save = deliver_domain;
+  const uschar *save = deliver_domain;
   deliver_domain = host->name;  /* set $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)
     MCL_DOMAIN, TRUE, NULL);
   deliver_domain = save;
   if (rc == OK)
@@ -2173,9 +2234,7 @@ field set to NULL, fill in its IP address from the DNS. If it is multi-homed,
 create additional host items for the additional addresses, copying all the
 other fields, and randomizing the order.
 
 create additional host items for the additional addresses, copying all the
 other fields, and randomizing the order.
 
-On IPv6 systems, A6 records are sought first (but only if support for A6 is
-configured - they may never become mainstream), then AAAA records are sought,
-and finally A records are sought as well.
+On IPv6 systems, AAAA records are sought first, then A records.
 
 The host name may be changed if the DNS returns a different name - e.g. fully
 qualified or changed via CNAME. If fully_qualified_name is not NULL, dns_lookup
 
 The host name may be changed if the DNS returns a different name - e.g. fully
 qualified or changed via CNAME. If fully_qualified_name is not NULL, dns_lookup
@@ -2196,22 +2255,27 @@ 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)
   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
+  whichrrs             select ipv4, ipv6 results
 
 Returns:       HOST_FIND_FAILED     couldn't find A record
                HOST_FIND_AGAIN      try again later
 
 Returns:       HOST_FIND_FAILED     couldn't find A record
                HOST_FIND_AGAIN      try again later
+              HOST_FIND_SECURITY   dnssec required but not acheived
                HOST_FOUND           found AAAA and/or A record(s)
                HOST_IGNORED         found, but all IPs ignored
 */
 
 static int
 set_address_from_dns(host_item *host, host_item **lastptr,
                HOST_FOUND           found AAAA and/or A record(s)
                HOST_IGNORED         found, but all IPs ignored
 */
 
 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, int whichrrs)
 {
 dns_record *rr;
 host_item *thishostlast = NULL;    /* Indicates not yet filled in anything */
 BOOL v6_find_again = FALSE;
 {
 dns_record *rr;
 host_item *thishostlast = NULL;    /* Indicates not yet filled in anything */
 BOOL v6_find_again = FALSE;
+BOOL dnssec_fail = FALSE;
 int i;
 
 /* If allow_ip is set, a name which is an IP address returns that value
 int i;
 
 /* If allow_ip is set, a name which is an IP address returns that value
@@ -2221,8 +2285,8 @@ those sites that feel they have to flaunt the RFC rules. */
 if (allow_ip && string_is_ip_address(host->name, NULL) != 0)
   {
   #ifndef STAND_ALONE
 if (allow_ip && string_is_ip_address(host->name, NULL) != 0)
   {
   #ifndef STAND_ALONE
-  if (ignore_target_hosts != NULL &&
-        verify_check_this_host(&ignore_target_hosts, NULL, host->name,
+  if (  ignore_target_hosts
+     && verify_check_this_host(&ignore_target_hosts, NULL, host->name,
         host->name, NULL) == OK)
     return HOST_IGNORED;
   #endif
         host->name, NULL) == OK)
     return HOST_IGNORED;
   #endif
@@ -2231,27 +2295,24 @@ if (allow_ip && string_is_ip_address(host->name, NULL) != 0)
   return HOST_FOUND;
   }
 
   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
-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
-loop once only, looking only for A records. */
+/* 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 global.
+On an IPv4 system, go round the loop once only, looking only for A records. */
 
 #if HAVE_IPV6
   #ifndef STAND_ALONE
 
 #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))
+    if (  disable_ipv6
+       || !(whichrrs & HOST_FIND_BY_AAAA)
+       || (dns_ipv4_lookup
+          && 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 */
 
       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 */
   i = 1;        /* look up AAAA and A records */
-  #endif        /* SUPPORT_A6 */
 
 /* The IPv4 world */
 
 
 /* The IPv4 world */
 
@@ -2261,17 +2322,25 @@ loop once only, looking only for A records. */
 
 for (; i >= 0; i--)
   {
 
 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 type = types[i];
-  int randoffset = (i == 0)? 500 : 0;  /* Ensures v6 sorts before v4 */
+  int randoffset = i == (whichrrs & HOST_FIND_IPV4_FIRST ? 1 : 0)
+    ? 500 : 0;  /* Ensures v6/4 sort order */
   dns_answer dnsa;
   dns_scan dnss;
 
   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";
 
     : 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.) */
   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 +2349,68 @@ for (; i >= 0; i--)
     {
     if (i == 0)  /* Just tried for an A record, i.e. end of loop */
       {
     {
     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 */
       }
 
       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;
     }
     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)
+       {
+       dnssec_fail = TRUE;
+       DEBUG(D_host_lookup) debug_printf("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
     }
 
   /* 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);
 
   for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
-       rr != NULL;
+       rr;
        rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
     {
     if (rr->type == type)
       {
        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)
 
       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);
             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. */
 
 
       /* 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 &&
         {
         #ifndef STAND_ALONE
         if (ignore_target_hosts != NULL &&
@@ -2411,10 +2497,14 @@ for (; i >= 0; i--)
     }
   }
 
     }
   }
 
-/* Control gets here only if the third lookup (the A record) succeeded.
+/* Control gets here only if the second lookup (the A record) succeeded.
 However, the address may not be filled in if it was ignored. */
 
 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
+  : dnssec_fail
+  ? HOST_FIND_SECURITY
+  : HOST_IGNORED;
 }
 
 
 }
 
 
@@ -2437,15 +2527,18 @@ Arguments:
   whichrrs              flags indicating which RRs to look for:
                           HOST_FIND_BY_SRV  => look for SRV
                           HOST_FIND_BY_MX   => look for MX
   whichrrs              flags indicating which RRs to look for:
                           HOST_FIND_BY_SRV  => look for SRV
                           HOST_FIND_BY_MX   => look for MX
-                          HOST_FIND_BY_A    => look for A or AAAA
+                          HOST_FIND_BY_A    => look for A
+                          HOST_FIND_BY_AAAA => look for AAAA
                         also flags indicating how the lookup is done
                           HOST_FIND_QUALIFY_SINGLE   ) passed to the
                           HOST_FIND_SEARCH_PARENTS   )   resolver
                         also flags indicating how the lookup is done
                           HOST_FIND_QUALIFY_SINGLE   ) passed to the
                           HOST_FIND_SEARCH_PARENTS   )   resolver
+                         HOST_FIND_IPV4_FIRST => reverse usual result ordering
+                         HOST_FIND_IPV4_ONLY  => MX results elide ipv6
   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
   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
 
   fully_qualified_name  if not NULL, return fully-qualified name
   removed               set TRUE if local host was removed from the list
 
@@ -2453,6 +2546,7 @@ Returns:                HOST_FIND_FAILED  Failed to find the host or domain;
                                           if there was a syntax error,
                                           host_find_failed_syntax is set.
                         HOST_FIND_AGAIN   Could not resolve at this time
                                           if there was a syntax error,
                                           host_find_failed_syntax is set.
                         HOST_FIND_AGAIN   Could not resolve at this time
+                       HOST_FIND_SECURITY dnsssec required but not acheived
                         HOST_FOUND        Host found
                         HOST_FOUND_LOCAL  The lowest MX record points to this
                                           machine, if MX records were found, or
                         HOST_FOUND        Host found
                         HOST_FOUND_LOCAL  The lowest MX record points to this
                                           machine, if MX records were found, or
@@ -2461,10 +2555,10 @@ Returns:                HOST_FIND_FAILED  Failed to find the host or domain;
 */
 
 int
 */
 
 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 *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;
 {
 host_item *h, *last;
 dns_record *rr;
@@ -2473,11 +2567,13 @@ int ind_type = 0;
 int yield;
 dns_answer dnsa;
 dns_scan dnss;
 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
                                    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
 dnssec_status_t dnssec;
 
 /* Set the default fully qualified name to the incoming name, initialize the
@@ -2487,22 +2583,22 @@ 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,
 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
-        );
-host_find_failed_syntax = FALSE;
+        dnssec_request);
+f.host_find_failed_syntax = FALSE;
 
 /* First, if requested, look for SRV records. The service name is given; we
 
 /* 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. */
 
 characters, so the code below should be safe. */
 
-if ((whichrrs & HOST_FIND_BY_SRV) != 0)
+if (whichrrs & HOST_FIND_BY_SRV)
   {
   {
-  uschar buffer[300];
-  uschar *temp_fully_qualified_name = buffer;
+  gstring * g;
+  uschar * temp_fully_qualified_name;
   int prefix_length;
 
   int prefix_length;
 
-  (void)sprintf(CS buffer, "_%s._tcp.%n%.256s", srv_service, &prefix_length,
-    host->name);
+  g = string_fmt_append(NULL, "_%s._tcp.%n%.256s",
+       srv_service, &prefix_length, host->name);
+  temp_fully_qualified_name = string_from_gstring(g);
   ind_type = T_SRV;
 
   /* Search for SRV records. If the fully qualified name is different to
   ind_type = T_SRV;
 
   /* Search for SRV records. If the fully qualified name is different to
@@ -2511,7 +2607,14 @@ if ((whichrrs & HOST_FIND_BY_SRV) != 0)
 
   dnssec = DS_UNK;
   lookup_dnssec_authenticated = NULL;
 
   dnssec = DS_UNK;
   lookup_dnssec_authenticated = NULL;
-  rc = dns_lookup(&dnsa, buffer, ind_type, &temp_fully_qualified_name);
+  rc = dns_lookup_timerwrap(&dnsa, temp_fully_qualified_name, 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)
     {
 
   if (dnssec_request)
     {
@@ -2521,7 +2624,7 @@ if ((whichrrs & HOST_FIND_BY_SRV) != 0)
       { dnssec = DS_NO; lookup_dnssec_authenticated = US"no"; }
     }
 
       { dnssec = DS_NO; lookup_dnssec_authenticated = US"no"; }
     }
 
-  if (temp_fully_qualified_name != buffer && fully_qualified_name != NULL)
+  if (temp_fully_qualified_name != g->s && fully_qualified_name != NULL)
     *fully_qualified_name = temp_fully_qualified_name + prefix_length;
 
   /* On DNS failures, we give the "try again" error unless the domain is
     *fully_qualified_name = temp_fully_qualified_name + prefix_length;
 
   /* On DNS failures, we give the "try again" error unless the domain is
@@ -2536,8 +2639,8 @@ if ((whichrrs & HOST_FIND_BY_SRV) != 0)
   if (rc == DNS_FAIL || rc == DNS_AGAIN)
     {
     #ifndef STAND_ALONE
   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 "
     #endif
       { yield = HOST_FIND_AGAIN; goto out; }
     DEBUG(D_host_lookup) debug_printf("DNS_%s treated as DNS_NODATA "
@@ -2552,20 +2655,29 @@ same domain. The result will be DNS_NODATA if the domain exists but has no MX
 records. On DNS failures, we give the "try again" error unless the domain is
 listed as one for which we continue. */
 
 records. On DNS failures, we give the "try again" error unless the domain is
 listed as one for which we continue. */
 
-if (rc != DNS_SUCCEED && (whichrrs & HOST_FIND_BY_MX) != 0)
+if (rc != DNS_SUCCEED  &&  whichrrs & HOST_FIND_BY_MX)
   {
   ind_type = T_MX;
   dnssec = DS_UNK;
   lookup_dnssec_authenticated = NULL;
   {
   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 (dnssec_request)
-    {
     if (dns_is_secure(&dnsa))
     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
     else
-      { dnssec = DS_NO; lookup_dnssec_authenticated = US"no"; }
-    }
+      {
+      dnssec = DS_NO; lookup_dnssec_authenticated = US"no";
+      }
 
   switch (rc)
     {
 
   switch (rc)
     {
@@ -2575,17 +2687,22 @@ if (rc != DNS_SUCCEED && (whichrrs & HOST_FIND_BY_MX) != 0)
     case DNS_SUCCEED:
       if (!dnssec_require || dns_is_secure(&dnsa))
        break;
     case DNS_SUCCEED:
       if (!dnssec_require || dns_is_secure(&dnsa))
        break;
-      log_write(L_host_lookup_failed, LOG_MAIN,
-                 "dnssec fail on MX for %.256s", host->name);
+      DEBUG(D_host_lookup)
+       debug_printf("dnssec fail on MX for %.256s", host->name);
+#ifndef STAND_ALONE
+      if (match_isinlist(host->name, CUSS &mx_fail_domains, 0, NULL, NULL,
+         MCL_DOMAIN, TRUE, NULL) != OK)
+       { yield = HOST_FIND_SECURITY; goto out; }
+#endif
       rc = DNS_FAIL;
       rc = DNS_FAIL;
-      /*FALLTRHOUGH*/
+      /*FALLTHROUGH*/
 
     case DNS_FAIL:
     case DNS_AGAIN:
 
     case DNS_FAIL:
     case DNS_AGAIN:
-      #ifndef STAND_ALONE
-      if (match_isinlist(host->name, &mx_fail_domains, 0, NULL, NULL, MCL_DOMAIN,
-         TRUE, NULL) != OK)
-      #endif
+#ifndef STAND_ALONE
+      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 "
        "(domain in mx_fail_domains)\n", (rc == DNS_FAIL)? "FAIL":"AGAIN");
        { yield = HOST_FIND_AGAIN; goto out; }
       DEBUG(D_host_lookup) debug_printf("DNS_%s treated as DNS_NODATA "
        "(domain in mx_fail_domains)\n", (rc == DNS_FAIL)? "FAIL":"AGAIN");
@@ -2599,7 +2716,7 @@ host. */
 
 if (rc != DNS_SUCCEED)
   {
 
 if (rc != DNS_SUCCEED)
   {
-  if ((whichrrs & HOST_FIND_BY_A) == 0)
+  if (!(whichrrs & (HOST_FIND_BY_A | HOST_FIND_BY_AAAA)))
     {
     DEBUG(D_host_lookup) debug_printf("Address records are not being sought\n");
     yield = HOST_FIND_FAILED;
     {
     DEBUG(D_host_lookup) debug_printf("Address records are not being sought\n");
     yield = HOST_FIND_FAILED;
@@ -2609,18 +2726,10 @@ if (rc != DNS_SUCCEED)
   last = host;        /* End of local chainlet */
   host->mx = MX_NONE;
   host->port = PORT_NONE;
   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,
   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"; }
-    }
+    fully_qualified_name, dnssec_request, dnssec_require, whichrrs);
 
   /* 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
 
   /* 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
@@ -2674,17 +2783,14 @@ 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);
 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 precedence, weight;
   int port = PORT_NONE;
   int port = PORT_NONE;
-  uschar *s;             /* MUST be unsigned for GETSHORT */
+  const uschar * s = rr->data; /* MUST be unsigned for GETSHORT */
   uschar data[256];
 
   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
   GETSHORT(precedence, s);      /* Pointer s is advanced */
 
   /* For MX records, we use a random "weight" which causes multiple records of
@@ -2692,13 +2798,11 @@ for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
 
   if (ind_type == T_MX)
     weight = random_number(500);
 
   if (ind_type == T_MX)
     weight = random_number(500);
-
-  /* SRV records are specified with a port and a weight. The weight is used
-  in a special algorithm. However, to start with, we just use it to order the
-  records of equal priority (precedence). */
-
   else
     {
   else
     {
+    /* SRV records are specified with a port and a weight. The weight is used
+    in a special algorithm. However, to start with, we just use it to order the
+    records of equal priority (precedence). */
     GETSHORT(weight, s);
     GETSHORT(port, s);
     }
     GETSHORT(weight, s);
     GETSHORT(port, s);
     }
@@ -2713,17 +2817,16 @@ for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
   never know what junk might get into the DNS (and this case has been seen on
   more than one occasion). */
 
   never know what junk might get into the DNS (and this case has been seen on
   more than one occasion). */
 
-  if (last != NULL)       /* This is not the first record */
+  if (last)       /* This is not the first record */
     {
     host_item *prev = NULL;
 
     for (h = host; h != last->next; prev = h, h = h->next)
     {
     host_item *prev = NULL;
 
     for (h = host; h != last->next; prev = h, h = h->next)
-      {
       if (strcmpic(h->name, data) == 0)
         {
         DEBUG(D_host_lookup)
           debug_printf("discarded duplicate host %s (MX=%d)\n", data,
       if (strcmpic(h->name, data) == 0)
         {
         DEBUG(D_host_lookup)
           debug_printf("discarded duplicate host %s (MX=%d)\n", data,
-            (precedence > h->mx)? precedence : h->mx);
+            precedence > h->mx ? precedence : h->mx);
         if (precedence >= h->mx) goto NEXT_MX_RR; /* Skip greater precedence */
         if (h == host)                            /* Override first item */
           {
         if (precedence >= h->mx) goto NEXT_MX_RR; /* Skip greater precedence */
         if (h == host)                            /* Override first item */
           {
@@ -2739,14 +2842,13 @@ for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
         if (h == last) last = prev;
         break;
         }
         if (h == last) last = prev;
         break;
         }
-      }
     }
 
   /* If this is the first MX or SRV record, put the data into the existing host
   block. Otherwise, add a new block in the correct place; if it has to be
   before the first block, copy the first block's data to a new second block. */
 
     }
 
   /* If this is the first MX or SRV record, put the data into the existing host
   block. Otherwise, add a new block in the correct place; if it has to be
   before the first block, copy the first block's data to a new second block. */
 
-  if (last == NULL)
+  if (!last)
     {
     host->name = string_copy_dnsdomain(data);
     host->address = NULL;
     {
     host->name = string_copy_dnsdomain(data);
     host->address = NULL;
@@ -2758,10 +2860,9 @@ for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
     host->dnssec = dnssec;
     last = host;
     }
     host->dnssec = dnssec;
     last = host;
     }
+  else
 
   /* Make a new host item and seek the correct insertion place */
 
   /* Make a new host item and seek the correct insertion place */
-
-  else
     {
     int sort_key = precedence * 1000 + weight;
     host_item *next = store_get(sizeof(host_item));
     {
     int sort_key = precedence * 1000 + weight;
     host_item *next = store_get(sizeof(host_item));
@@ -2786,21 +2887,18 @@ for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
       host->next = next;
       if (last == host) last = next;
       }
       host->next = next;
       if (last == host) last = next;
       }
+    else
 
     /* Else scan down the items we have inserted as part of this exercise;
     don't go further. */
 
     /* Else scan down the items we have inserted as part of this exercise;
     don't go further. */
-
-    else
       {
       for (h = host; h != last; h = h->next)
       {
       for (h = host; h != last; h = h->next)
-        {
         if (sort_key < h->next->sort_key)
           {
           next->next = h->next;
           h->next = next;
           break;
           }
         if (sort_key < h->next->sort_key)
           {
           next->next = h->next;
           h->next = next;
           break;
           }
-        }
 
       /* Join on after the last host item that's part of this
       processing if we haven't stopped sooner. */
 
       /* Join on after the last host item that's part of this
       processing if we haven't stopped sooner. */
@@ -2817,6 +2915,12 @@ for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
   NEXT_MX_RR: continue;
   }
 
   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
 /* 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 +2947,7 @@ if (ind_type == T_SRV)
       debug_printf("  %s P=%d W=%d\n", h->name, h->mx, h->sort_key % 1000);
     }
 
       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;
     {
     int sum = 0;
     host_item *hh;
@@ -2871,10 +2975,9 @@ if (ind_type == T_SRV)
 
       for (ppptr = pptr, hhh = h;
            hhh != hh;
 
       for (ppptr = pptr, hhh = h;
            hhh != hh;
-           ppptr = &(hhh->next), hhh = hhh->next)
-        {
-        if (hhh->sort_key >= randomizer) break;
-        }
+           ppptr = &hhh->next, hhh = hhh->next)
+        if (hhh->sort_key >= randomizer)
+         break;
 
       /* hhh now points to the host that should go first; ppptr points to the
       place that points to it. Unfortunately, if the start of the minilist is
 
       /* hhh now points to the host that should go first; ppptr points to the
       place that points to it. Unfortunately, if the start of the minilist is
@@ -2899,7 +3002,6 @@ if (ind_type == T_SRV)
           hhh->next = temp.next;
           h->next = hhh;
           }
           hhh->next = temp.next;
           h->next = hhh;
           }
-
         else
           {
           hhh->next = h;               /* The rest of the chain follows it */
         else
           {
           hhh->next = h;               /* The rest of the chain follows it */
@@ -2948,19 +3050,22 @@ dns_init(FALSE, FALSE,       /* Disable qualify_single and search_parents */
 
 for (h = host; h != last->next; h = h->next)
   {
 
 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,
   rc = set_address_from_dns(h, &last, ignore_target_hosts, allow_mx_to_ip,
-    NULL, dnssec_request, dnssec_require);
+    NULL, dnssec_request, dnssec_require,
+    whichrrs & HOST_FIND_IPV4_ONLY
+    ?  HOST_FIND_BY_A  :  HOST_FIND_BY_A | HOST_FIND_BY_AAAA);
   if (rc != HOST_FOUND)
     {
     h->status = hstatus_unusable;
   if (rc != HOST_FOUND)
     {
     h->status = hstatus_unusable;
-    if (rc == HOST_FIND_AGAIN)
+    switch (rc)
       {
       {
-      yield = rc;
-      h->why = hwhy_deferred;
+      case HOST_FIND_AGAIN:    yield = rc; h->why = hwhy_deferred; break;
+      case HOST_FIND_SECURITY: yield = rc; h->why = hwhy_insecure; break;
+      case HOST_IGNORED:       h->why = hwhy_ignored; break;
+      default:                 h->why = hwhy_failed; break;
       }
       }
-    else
-      h->why = (rc == HOST_IGNORED)? hwhy_ignored : hwhy_failed;
     }
   }
 
     }
   }
 
@@ -2969,7 +3074,7 @@ been explicitly ignored, and remove them from the list, as if they did not
 exist. If we end up with just a single, ignored host, flatten its fields as if
 nothing was found. */
 
 exist. If we end up with just a single, ignored host, flatten its fields as if
 nothing was found. */
 
-if (ignore_target_hosts != NULL)
+if (ignore_target_hosts)
   {
   host_item *prev = NULL;
   for (h = host; h != last->next; h = h->next)
   {
   host_item *prev = NULL;
   for (h = host; h != last->next; h = h->next)
@@ -3005,24 +3110,32 @@ single MX preference value, IPv6 addresses come first. This can separate the
 addresses of a multihomed host, but that should not matter. */
 
 #if HAVE_IPV6
 addresses of a multihomed host, but that should not matter. */
 
 #if HAVE_IPV6
-if (h != last && !disable_ipv6)
+if (h != last && !disable_ipv6) for (h = host; h != last; h = h->next)
   {
   {
-  for (h = host; h != last; h = h->next)
-    {
-    host_item temp;
-    host_item *next = h->next;
-    if (h->mx != next->mx ||                   /* If next is different MX */
-        h->address == NULL ||                  /* OR this one is unset */
-        Ustrchr(h->address, ':') != NULL ||    /* OR this one is IPv6 */
-        (next->address != NULL &&
-         Ustrchr(next->address, ':') == NULL)) /* OR next is IPv4 */
-      continue;                                /* move on to next */
-    temp = *h;                                 /* otherwise, swap */
-    temp.next = next->next;
-    *h = *next;
-    h->next = next;
-    *next = temp;
-    }
+  host_item temp;
+  host_item *next = h->next;
+
+  if (  h->mx != next->mx                      /* If next is different MX */
+     || !h->address                            /* OR this one is unset */
+     )
+    continue;                                  /* move on to next */
+
+  if (  whichrrs & HOST_FIND_IPV4_FIRST
+     ?     !Ustrchr(h->address, ':')           /* OR this one is IPv4 */
+        || next->address
+           && Ustrchr(next->address, ':')      /* OR next is IPv6 */
+
+     :     Ustrchr(h->address, ':')            /* OR this one is IPv6 */
+        || next->address
+           && !Ustrchr(next->address, ':')     /* OR next is IPv4 */
+     )
+    continue;                                /* move on to next */
+
+  temp = *h;                                 /* otherwise, swap */
+  temp.next = next->next;
+  *h = *next;
+  h->next = next;
+  *next = temp;
   }
 #endif
 
   }
 #endif
 
@@ -3046,13 +3159,15 @@ DEBUG(D_host_lookup)
   debug_printf("host_find_bydns yield = %s (%d); returned hosts:\n",
     (yield == HOST_FOUND)? "HOST_FOUND" :
     (yield == HOST_FOUND_LOCAL)? "HOST_FOUND_LOCAL" :
   debug_printf("host_find_bydns yield = %s (%d); returned hosts:\n",
     (yield == HOST_FOUND)? "HOST_FOUND" :
     (yield == HOST_FOUND_LOCAL)? "HOST_FOUND_LOCAL" :
+    (yield == HOST_FIND_SECURITY)? "HOST_FIND_SECURITY" :
     (yield == HOST_FIND_AGAIN)? "HOST_FIND_AGAIN" :
     (yield == HOST_FIND_FAILED)? "HOST_FIND_FAILED" : "?",
     yield);
   for (h = host; h != last->next; h = h->next)
     {
     (yield == HOST_FIND_AGAIN)? "HOST_FIND_AGAIN" :
     (yield == HOST_FIND_FAILED)? "HOST_FIND_FAILED" : "?",
     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");
     if (h->port != PORT_NONE) debug_printf("port=%d ", h->port);
     if (h->status >= hstatus_unusable) debug_printf("*");
     debug_printf("\n");
@@ -3065,9 +3180,6 @@ dns_init(FALSE, FALSE, FALSE);    /* clear the dnssec bit for getaddrbyname */
 return yield;
 }
 
 return yield;
 }
 
-
-
-
 /*************************************************
 **************************************************
 *             Stand-alone test program           *
 /*************************************************
 **************************************************
 *             Stand-alone test program           *
@@ -3079,7 +3191,7 @@ return yield;
 int main(int argc, char **cargv)
 {
 host_item h;
 int main(int argc, char **cargv)
 {
 host_item h;
-int whichrrs = HOST_FIND_BY_MX | HOST_FIND_BY_A;
+int whichrrs = HOST_FIND_BY_MX | HOST_FIND_BY_A | HOST_FIND_BY_AAAA;
 BOOL byname = FALSE;
 BOOL qualify_single = TRUE;
 BOOL search_parents = FALSE;
 BOOL byname = FALSE;
 BOOL qualify_single = TRUE;
 BOOL search_parents = FALSE;
@@ -3121,15 +3233,15 @@ while (Ufgets(buffer, 256, stdin) != NULL)
 
   if (Ustrcmp(buffer, "byname") == 0) byname = TRUE;
   else if (Ustrcmp(buffer, "no_byname") == 0) byname = FALSE;
 
   if (Ustrcmp(buffer, "byname") == 0) byname = TRUE;
   else if (Ustrcmp(buffer, "no_byname") == 0) byname = FALSE;
-  else if (Ustrcmp(buffer, "a_only") == 0) whichrrs = HOST_FIND_BY_A;
+  else if (Ustrcmp(buffer, "a_only") == 0) whichrrs = HOST_FIND_BY_A | HOST_FIND_BY_AAAA;
   else if (Ustrcmp(buffer, "mx_only") == 0) whichrrs = HOST_FIND_BY_MX;
   else if (Ustrcmp(buffer, "srv_only") == 0) whichrrs = HOST_FIND_BY_SRV;
   else if (Ustrcmp(buffer, "srv+a") == 0)
   else if (Ustrcmp(buffer, "mx_only") == 0) whichrrs = HOST_FIND_BY_MX;
   else if (Ustrcmp(buffer, "srv_only") == 0) whichrrs = HOST_FIND_BY_SRV;
   else if (Ustrcmp(buffer, "srv+a") == 0)
-    whichrrs = HOST_FIND_BY_SRV | HOST_FIND_BY_A;
+    whichrrs = HOST_FIND_BY_SRV | HOST_FIND_BY_A | HOST_FIND_BY_AAAA;
   else if (Ustrcmp(buffer, "srv+mx") == 0)
     whichrrs = HOST_FIND_BY_SRV | HOST_FIND_BY_MX;
   else if (Ustrcmp(buffer, "srv+mx+a") == 0)
   else if (Ustrcmp(buffer, "srv+mx") == 0)
     whichrrs = HOST_FIND_BY_SRV | HOST_FIND_BY_MX;
   else if (Ustrcmp(buffer, "srv+mx+a") == 0)
-    whichrrs = HOST_FIND_BY_SRV | HOST_FIND_BY_MX | HOST_FIND_BY_A;
+    whichrrs = HOST_FIND_BY_SRV | HOST_FIND_BY_MX | HOST_FIND_BY_A | HOST_FIND_BY_AAAA;
   else if (Ustrcmp(buffer, "qualify_single")    == 0) qualify_single = TRUE;
   else if (Ustrcmp(buffer, "no_qualify_single") == 0) qualify_single = FALSE;
   else if (Ustrcmp(buffer, "search_parents")    == 0) search_parents = TRUE;
   else if (Ustrcmp(buffer, "qualify_single")    == 0) qualify_single = TRUE;
   else if (Ustrcmp(buffer, "no_qualify_single") == 0) qualify_single = FALSE;
   else if (Ustrcmp(buffer, "search_parents")    == 0) search_parents = TRUE;
@@ -3137,9 +3249,9 @@ 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, "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)
   else if (Ustrcmp(buffer, "test_harness") == 0)
-    running_in_test_harness = !running_in_test_harness;
+    f.running_in_test_harness = !f.running_in_test_harness;
   else if (Ustrcmp(buffer, "ipv6") == 0) disable_ipv6 = !disable_ipv6;
   else if (Ustrcmp(buffer, "res_debug") == 0)
     {
   else if (Ustrcmp(buffer, "ipv6") == 0) disable_ipv6 = !disable_ipv6;
   else if (Ustrcmp(buffer, "res_debug") == 0)
     {
@@ -3158,6 +3270,7 @@ while (Ufgets(buffer, 256, stdin) != NULL)
   else
     {
     int flags = whichrrs;
   else
     {
     int flags = whichrrs;
+    dnssec_domains d;
 
     h.name = buffer;
     h.next = NULL;
 
     h.name = buffer;
     h.next = NULL;
@@ -3170,16 +3283,21 @@ while (Ufgets(buffer, 256, stdin) != NULL)
     if (qualify_single) flags |= HOST_FIND_QUALIFY_SINGLE;
     if (search_parents) flags |= HOST_FIND_SEARCH_PARENTS;
 
     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,
     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");
-        else if (rc == HOST_FOUND_LOCAL) printf("Local\n");
+    switch (rc)
+      {
+      case HOST_FIND_FAILED:   printf("Failed\n");     break;
+      case HOST_FIND_AGAIN:    printf("Again\n");      break;
+      case HOST_FIND_SECURITY: printf("Security\n");   break;
+      case HOST_FOUND_LOCAL:   printf("Local\n");      break;
+      }
     }
 
   printf("\n> ");
     }
 
   printf("\n> ");
diff --git a/src/imap_utf7.c b/src/imap_utf7.c
new file mode 100644 (file)
index 0000000..501ceaf
--- /dev/null
@@ -0,0 +1,211 @@
+/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+#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+,";
+size_t slen;
+uschar *sptr;
+gstring * 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, 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, outbuf, outptr - outbuf);
+
+if (yield->s[yield->ptr-1] == '.')
+  yield->ptr--;
+
+return string_from_gstring(yield);
+}
+
+#endif /* whole file */
+/* vi: aw ai sw=2
+*/
index b492b9d..552f7dc 100644 (file)
--- a/src/ip.c
+++ b/src/ip.c
@@ -2,12 +2,12 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* 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
 /* 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. */
 
 
 different places in the code where sockets are used. */
 
 
@@ -64,11 +64,11 @@ Returns:      nothing - failure provokes a panic-die
 */
 
 static void
 */
 
 static void
-ip_addrinfo(uschar *address, struct sockaddr_in6 *saddr)
+ip_addrinfo(const uschar *address, struct sockaddr_in6 *saddr)
 {
 #ifdef IPV6_USE_INET_PTON
 
 {
 #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;
     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;
   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));
     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      *
 *************************************************/
 
 *         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
 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. */
 
 /* 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)
     {
   {
   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
     }
   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 */
   }
 else
 #else     /* HAVE_IPv6 */
@@ -142,22 +127,59 @@ af = af;  /* Avoid compiler warning */
 /* Setup code when using IPv4 socket. The wildcard address is "". */
 
   {
 /* 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);
   }
   }
+}
 
 
-/* Now we can call the bind() function */
 
 
+
+/* 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)
+{
+union sockaddr_46 sin;
+int s_len = ip_addr(&sin, af, address, port);
 return bind(sock, (struct sockaddr *)&sin, s_len);
 }
 
 
 
 return bind(sock, (struct sockaddr *)&sin, s_len);
 }
 
 
 
+/*************************************************
+*************************************************/
+
+#ifdef EXIM_TFO_PROBE
+void
+tfo_probe(void)
+{
+# ifdef TCP_FASTOPEN
+int sock, backlog = 5;
+
+if (  (sock = socket(SOCK_STREAM, AF_INET, 0)) < 0
+   && setsockopt(sock, IPPROTO_TCP, TCP_FASTOPEN, &backlog, sizeof(backlog))
+   )
+  f.tcp_fastopen_ok = TRUE;
+close(sock);
+# endif
+}
+#endif
+
+
 /*************************************************
 *        Connect socket to remote host           *
 *************************************************/
 /*************************************************
 *        Connect socket to remote host           *
 *************************************************/
@@ -173,12 +195,15 @@ Arguments:
   address     the remote address, in text form
   port        the remote port
   timeout     a timeout (zero for indefinite timeout)
   address     the remote address, in text form
   port        the remote port
   timeout     a timeout (zero for indefinite timeout)
+  fastopen_blob    non-null iff TCP_FASTOPEN can be used; may indicate early-data to
+               be sent in SYN segment.  Any such data must be idempotent.
 
 Returns:      0 on success; -1 on failure, with errno set
 */
 
 int
 
 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,
+  const blob * fastopen_blob)
 {
 struct sockaddr_in s_in4;
 struct sockaddr *s_ptr;
 {
 struct sockaddr_in s_in4;
 struct sockaddr *s_ptr;
@@ -208,7 +233,7 @@ IPv6 support. */
   memset(&s_in4, 0, sizeof(s_in4));
   s_in4.sin_family = AF_INET;
   s_in4.sin_port = htons(port);
   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);
   }
   s_ptr = (struct sockaddr *)&s_in4;
   s_len = sizeof(s_in4);
   }
@@ -216,34 +241,129 @@ IPv6 support. */
 /* If no connection timeout is set, just call connect() without setting a
 timer, thereby allowing the inbuilt OS timeout to operate. */
 
 /* 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;
 sigalrm_seen = FALSE;
-if (timeout > 0) alarm(timeout);
-rc = connect(sock, s_ptr, s_len);
+if (timeout > 0) ALARM(timeout);
+
+#ifdef TCP_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.  Other (than SMTP) cases of TCP connections can
+possibly use the data-on-syn, so support that too. */
+
+if (fastopen_blob && f.tcp_fastopen_ok)
+  {
+# ifdef MSG_FASTOPEN
+  /* This is a Linux implementation.  It might be useable on FreeBSD; I have
+  not checked. */
+
+  if ((rc = sendto(sock, fastopen_blob->data, fastopen_blob->len,
+                   MSG_FASTOPEN | MSG_DONTWAIT, s_ptr, s_len)) >= 0)
+       /* seen for with-data, experimental TFO option, with-cookie case */
+       /* seen for with-data, proper TFO opt, with-cookie case */
+    {
+    DEBUG(D_transport|D_v)
+      debug_printf("TFO mode connection attempt to %s, %lu data\n",
+       address, (unsigned long)fastopen_blob->len);
+    /*XXX also seen on successful TFO, sigh */
+    tcp_out_fastopen = fastopen_blob->len > 0 ?  TFO_ATTEMPTED_DATA : TFO_ATTEMPTED_NODATA;
+    }
+  else if (errno == EINPROGRESS)       /* expected if we had no cookie for peer */
+       /* seen for no-data, proper TFO option, both cookie-request and with-cookie cases */
+       /*  apparently no visibility of the diffference at this point */
+       /* seen for with-data, proper TFO opt, cookie-req */
+       /*   with netwk delay, post-conn tcp_info sees unacked 1 for R, 2 for C; code in smtp_out.c */
+       /* ? older Experimental TFO option behaviour ? */
+    {                                  /* queue unsent data */
+    DEBUG(D_transport|D_v) debug_printf("TFO mode sendto, %s data: EINPROGRESS\n",
+      fastopen_blob->len > 0 ? "with"  : "no");
+    if (!fastopen_blob->data)
+      {
+      tcp_out_fastopen = TFO_ATTEMPTED_NODATA;         /* we tried; unknown if useful yet */
+      rc = 0;
+      }
+    else
+      rc = send(sock, fastopen_blob->data, fastopen_blob->len, 0);
+    }
+  else if(errno == EOPNOTSUPP)
+    {
+    DEBUG(D_transport)
+      debug_printf("Tried TCP Fast Open but apparently not enabled by sysctl\n");
+    goto legacy_connect;
+    }
+# endif
+# ifdef EXIM_TFO_CONNECTX
+  /* MacOS */
+  sa_endpoints_t ends = {
+    .sae_srcif = 0, .sae_srcaddr = NULL, .sae_srcaddrlen = 0,
+    .sae_dstaddr = s_ptr, .sae_dstaddrlen = s_len };
+  struct iovec iov = {
+    .iov_base = fastopen_blob->data, .iov_len = fastopen_blob->len };
+  size_t len;
+
+  if ((rc = connectx(sock, &ends, SAE_ASSOCID_ANY,
+            CONNECT_DATA_IDEMPOTENT, &iov, 1, &len, NULL)) == 0)
+    {
+    DEBUG(D_transport|D_v)
+      debug_printf("TFO mode connection attempt to %s, %lu data\n",
+       address, (unsigned long)fastopen_blob->len);
+    tcp_out_fastopen = fastopen_blob->len > 0 ?  TFO_ATTEMPTED_DATA : TFO_ATTEMPTED_NODATA;
+
+    if (len != fastopen_blob->len)
+      DEBUG(D_transport|D_v)
+       debug_printf(" only queued %lu data!\n", (unsigned long)len);
+    }
+  else if (errno == EINPROGRESS)
+    {
+    DEBUG(D_transport|D_v) debug_printf("TFO mode sendto, %s data: EINPROGRESS\n",
+      fastopen_blob->len > 0 ? "with"  : "no");
+    if (!fastopen_blob->data)
+      {
+      tcp_out_fastopen = TFO_ATTEMPTED_NODATA;         /* we tried; unknown if useful yet */
+      rc = 0;
+      }
+    else       /* assume that no data was queued; block in send */
+      rc = send(sock, fastopen_blob->data, fastopen_blob->len, 0);
+    }
+# endif
+  }
+else
+#endif /*TCP_FASTOPEN*/
+  {
+legacy_connect:
+  DEBUG(D_transport|D_v) if (fastopen_blob)
+    debug_printf("non-TFO mode connection attempt to %s, %lu data\n",
+      address, (unsigned long)fastopen_blob->len);
+  if ((rc = connect(sock, s_ptr, s_len)) >= 0)
+    if (  fastopen_blob && fastopen_blob->data && fastopen_blob->len
+       && send(sock, fastopen_blob->data, fastopen_blob->len, 0) < 0)
+       rc = -1;
+  }
+
 save_errno = errno;
 save_errno = errno;
-alarm(0);
+ALARM_CLR(0);
 
 /* There is a testing facility for simulating a connection timeout, as I
 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. */
 
 
 /* There is a testing facility for simulating a connection timeout, as I
 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 (f.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 */
 
   }
 
 /* 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. */
 
 
 /* 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;
 }
 
 return -1;
 }
 
@@ -259,18 +379,20 @@ return -1;
 Arguments:
   type          SOCK_DGRAM or SOCK_STREAM
   af            AF_INET6 or AF_INET for the socket type
 Arguments:
   type          SOCK_DGRAM or SOCK_STREAM
   af            AF_INET6 or AF_INET for the socket type
-  address       the remote address, in text form
+  hostname     host name, or ip address (as text)
   portlo,porthi the remote port range
   timeout       a timeout
   portlo,porthi the remote port range
   timeout       a timeout
-  connhost     if not NULL, host_item filled in with connection details
+  connhost     if not NULL, host_item to be filled in with connection details
   errstr        pointer for allocated string on error
   errstr        pointer for allocated string on error
+  fastopen_blob        with SOCK_STREAM, if non-null, request TCP Fast Open.
+               Additionally, optional idempotent early-data to send
 
 Return:
   socket fd, or -1 on failure (having allocated an error string)
 */
 int
 ip_connectedsocket(int type, const uschar * hostname, int portlo, int porthi,
 
 Return:
   socket fd, or -1 on failure (having allocated an error string)
 */
 int
 ip_connectedsocket(int type, const uschar * hostname, int portlo, int porthi,
-       int timeout, host_item * connhost, uschar ** errstr)
+      int timeout, host_item * connhost, uschar ** errstr, const blob * fastopen_blob)
 {
 int namelen, port;
 host_item shost;
 {
 int namelen, port;
 host_item shost;
@@ -289,9 +411,7 @@ namelen = Ustrlen(hostname);
 if (hostname[0] == '[' &&
     hostname[namelen - 1] == ']')
   {
 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);
   if (string_is_ip_address(host, NULL) == 0)
     {
     *errstr = string_sprintf("malformed IP address \"%s\"", hostname);
@@ -303,15 +423,15 @@ if (hostname[0] == '[' &&
 /* Otherwise check for an unadorned IP address */
 
 else if (string_is_ip_address(hostname, NULL) != 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
   {
 
 /* 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;
     {
     *errstr = string_sprintf("no IP address found for host %s", shost.name);
     return -1;
@@ -320,11 +440,11 @@ else
 
 /* Try to connect to the server - test each IP till one works */
 
 
 /* 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)
     {
 
   if (fd < 0)
     {
@@ -333,11 +453,12 @@ for (h = &shost; h != NULL; h = h->next)
     }
 
   for(port = portlo; port <= porthi; port++)
     }
 
   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_blob) == 0)
       {
       if (fd != fd6) close(fd6);
       if (fd != fd4) close(fd4);
       {
       if (fd != fd6) close(fd6);
       if (fd != fd4) close(fd4);
-      if (connhost) {
+      if (connhost)
+       {
        h->port = port;
        *connhost = *h;
        connhost->next = NULL;
        h->port = port;
        *connhost = *h;
        connhost->next = NULL;
@@ -346,14 +467,72 @@ 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;
 }
 
 
 
 bad:
   close(fd4); close(fd6); return -1;
 }
 
 
+/*XXX TFO? */
+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, NULL);
+}
+
+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              *
 *************************************************/
 /*************************************************
 *         Set keepalive on a socket              *
 *************************************************/
@@ -369,11 +548,11 @@ Returns:     nothing
 */
 
 void
 */
 
 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,
 {
 int fodder = 1;
 if (setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE,
-    (uschar *)(&fodder), sizeof(fodder)) != 0)
+    US (&fodder), sizeof(fodder)) != 0)
   log_write(0, LOG_MAIN, "setsockopt(SO_KEEPALIVE) on connection %s %s "
     "failed: %s", torf? "to":"from", address, strerror(errno));
 }
   log_write(0, LOG_MAIN, "setsockopt(SO_KEEPALIVE) on connection %s %s "
     "failed: %s", torf? "to":"from", address, strerror(errno));
 }
@@ -384,39 +563,36 @@ if (setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE,
 *         Receive from a socket with timeout     *
 *************************************************/
 
 *         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:
 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;
 {
 fd_set select_inset;
-struct timeval tv;
 time_t start_recv = time(NULL);
 time_t start_recv = time(NULL);
+int time_left = timeout;
 int rc;
 
 int rc;
 
+if (time_left <= 0)
+  {
+  errno = ETIMEDOUT;
+  return FALSE;
+  }
 /* Wait until the socket is ready */
 
 /* Wait until the socket is ready */
 
-for (;;)
+do
   {
   {
+  struct timeval tv = { .tv_sec = time_left, .tv_usec = 0 };
   FD_ZERO (&select_inset);
   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
 
   /* 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,40 +601,64 @@ 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
   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 (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;
 
   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:
+  cctx        the connection context (socket fd, possibly TLS context)
+  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(client_conn_ctx * cctx, uschar * buffer, int buffsize, int timeout)
+{
+int rc;
+
+if (!fd_ready(cctx->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. */
 
 #ifdef SUPPORT_TLS
 
 /* 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. */
 
 #ifdef SUPPORT_TLS
-if (tls_out.active == sock)
-  rc = tls_read(FALSE, buffer, buffsize);
-else if (tls_in.active == sock)
-  rc = tls_read(TRUE, buffer, buffsize);
+if (cctx->tls_ctx)                                     /* client TLS */
+  rc = tls_read(cctx->tls_ctx, buffer, buffsize);
+else if (tls_in.active.sock == cctx->sock)             /* server TLS */
+  rc = tls_read(NULL, buffer, buffsize);
 else
 #endif
 else
 #endif
-  rc = recv(sock, buffer, buffsize, 0);
+  rc = recv(cctx->sock, buffer, buffsize, 0);
 
 if (rc > 0) return rc;
 if (rc == 0) errno = 0;
 
 if (rc > 0) return rc;
 if (rc == 0) errno = 0;
@@ -625,13 +825,9 @@ while (last > first)
     return TRUE;
     }
   else if (c > 0)
     return TRUE;
     }
   else if (c > 0)
-    {
     first = middle + 1;
     first = middle + 1;
-    }
   else
   else
-    {
     last = middle;
     last = middle;
-    }
   }
 return FALSE;
 }
   }
 return FALSE;
 }
@@ -646,3 +842,5 @@ for (i=0; i < dscp_table_size; ++i)
 
 
 /* End of ip.c */
 
 
 /* End of ip.c */
+/* vi: aw ai sw=2
+*/
index 3500047..4dd0b2b 100644 (file)
@@ -12,6 +12,7 @@ If you want to implement your own version, you should copy this file to, say
 Local/local_scan.c, and edit the copy. To use your version instead of the
 default, you must set
 
 Local/local_scan.c, and edit the copy. To use your version instead of the
 default, you must set
 
+HAVE_LOCAL_SCAN=yes
 LOCAL_SCAN_SOURCE=Local/local_scan.c
 
 in your Local/Makefile. This makes it easy to copy your version for use with
 LOCAL_SCAN_SOURCE=Local/local_scan.c
 
 in your Local/Makefile. This makes it easy to copy your version for use with
index 770348a..c974ac6 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2012 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* 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
 /* 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). */
 
 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
 
 #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 {
 /* 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;
   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 */
   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 */
   uschar *orcpt;                /* DSN orcpt */
   int     dsn_flags;            /* DSN flags */
-#endif
 #ifdef EXPERIMENTAL_BRIGHTMAIL
   uschar *bmi_optin;
 #endif
 #ifdef EXPERIMENTAL_BRIGHTMAIL
   uschar *bmi_optin;
 #endif
@@ -188,10 +186,10 @@ extern void    receive_add_recipient(uschar *, int);
 extern BOOL    receive_remove_recipient(uschar *);
 extern uschar *rfc2047_decode(uschar *, BOOL, uschar *, int, int *, uschar **);
 extern int     smtp_fflush(void);
 extern BOOL    receive_remove_recipient(uschar *);
 extern uschar *rfc2047_decode(uschar *, BOOL, uschar *, int, int *, uschar **);
 extern int     smtp_fflush(void);
-extern void    smtp_printf(const char *, ...) PRINTF_FUNCTION(1,2);
-extern void    smtp_vprintf(const char *, va_list);
+extern void    smtp_printf(const char *, BOOL, ...) PRINTF_FUNCTION(1,3);
+extern void    smtp_vprintf(const char *, BOOL, va_list);
 extern uschar *string_copy(const uschar *);
 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 */
 extern uschar *string_sprintf(const char *, ...) ALMOST_PRINTF(1,2);
 
 /* End of local_scan.h */
index c80c347..d082000 100644 (file)
--- a/src/log.c
+++ b/src/log.c
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for writing log files. The code for maintaining datestamped
 /* 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 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                   *
 
 /*************************************************
 *              Write to syslog                   *
@@ -68,25 +140,30 @@ Returns:         nothing
 */
 
 static void
 */
 
 static void
-write_syslog(int priority, uschar *s)
+write_syslog(int priority, const uschar *s)
 {
 int len, pass;
 int linecount = 0;
 
 {
 int len, pass;
 int linecount = 0;
 
-if (running_in_test_harness) return;
-
-if (!syslog_timestamp) s += log_timezone? 26 : 20;
+if (!syslog_pid && LOGGING(pid))
+  s = string_sprintf("%.*s%s", (int)pid_position[0], s, s + pid_position[1]);
+if (!syslog_timestamp)
+  {
+  len = log_timezone ? 26 : 20;
+  if (LOGGING(millisec)) len += 4;
+  s += len;
+  }
 
 len = Ustrlen(s);
 
 #ifndef NO_OPENLOG
 
 len = Ustrlen(s);
 
 #ifndef NO_OPENLOG
-if (!syslog_open)
+if (!syslog_open && !f.running_in_test_harness)
   {
   {
-  #ifdef SYSLOG_LOG_PID
+ifdef SYSLOG_LOG_PID
   openlog(CS syslog_processname, LOG_PID|LOG_CONS, syslog_facility);
   openlog(CS syslog_processname, LOG_PID|LOG_CONS, syslog_facility);
-  #else
+else
   openlog(CS syslog_processname, LOG_CONS, syslog_facility);
   openlog(CS syslog_processname, LOG_CONS, syslog_facility);
-  #endif
+endif
   syslog_open = TRUE;
   }
 #endif
   syslog_open = TRUE;
   }
 #endif
@@ -98,27 +175,35 @@ for (pass = 0; pass < 2; pass++)
   {
   int i;
   int tlen;
   {
   int i;
   int tlen;
-  uschar *ss = s;
+  const uschar * ss = s;
   for (i = 1, tlen = len; tlen > 0; i++)
     {
     int plen = tlen;
     uschar *nlptr = Ustrchr(ss, '\n');
     if (nlptr != NULL) plen = nlptr - ss;
   for (i = 1, tlen = len; tlen > 0; i++)
     {
     int plen = tlen;
     uschar *nlptr = Ustrchr(ss, '\n');
     if (nlptr != NULL) plen = nlptr - ss;
-    #ifndef SYSLOG_LONG_LINES
+#ifndef SYSLOG_LONG_LINES
     if (plen > MAX_SYSLOG_LEN) plen = MAX_SYSLOG_LEN;
     if (plen > MAX_SYSLOG_LEN) plen = MAX_SYSLOG_LEN;
-    #endif
+#endif
     tlen -= plen;
     if (ss[plen] == '\n') tlen--;    /* chars left */
 
     tlen -= plen;
     if (ss[plen] == '\n') tlen--;    /* chars left */
 
-    if (pass == 0) linecount++; else
-      {
+    if (pass == 0)
+      linecount++;
+    else if (f.running_in_test_harness)
+      if (linecount == 1)
+        fprintf(stderr, "SYSLOG: '%.*s'\n", plen, ss);
+      else
+        fprintf(stderr, "SYSLOG: '[%d%c%d] %.*s'\n", i,
+          ss[plen] == '\n' && tlen != 0 ? '\\' : '/',
+          linecount, plen, ss);
+    else
       if (linecount == 1)
         syslog(priority, "%.*s", plen, ss);
       else
         syslog(priority, "[%d%c%d] %.*s", i,
       if (linecount == 1)
         syslog(priority, "%.*s", plen, ss);
       else
         syslog(priority, "[%d%c%d] %.*s", i,
-          (ss[plen] == '\n' && tlen != 0)? '\\' : '/',
+          ss[plen] == '\n' && tlen != 0 ? '\\' : '/',
           linecount, plen, ss);
           linecount, plen, ss);
-      }
+
     ss += plen;
     if (*ss == '\n') ss++;
     }
     ss += plen;
     if (*ss == '\n') ss++;
     }
@@ -149,16 +234,16 @@ Returns:     The function does not return
 static void
 die(uschar *s1, uschar *s2)
 {
 static void
 die(uschar *s1, uschar *s2)
 {
-if (s1 != NULL)
+if (s1)
   {
   write_syslog(LOG_CRIT, s1);
   {
   write_syslog(LOG_CRIT, s1);
-  if (debug_file != NULL) debug_printf("%s\n", s1);
-  if (log_stderr != NULL && log_stderr != debug_file)
+  if (debug_file) debug_printf("%s\n", s1);
+  if (log_stderr && log_stderr != debug_file)
     fprintf(log_stderr, "%s\n", s1);
   }
     fprintf(log_stderr, "%s\n", s1);
   }
-if (receive_call_bombout) receive_bomb_out(NULL, s2);  /* does not return */
+if (f.receive_call_bombout) receive_bomb_out(NULL, s2);  /* does not return */
 if (smtp_input) smtp_closedown(s2);
 if (smtp_input) smtp_closedown(s2);
-exim_exit(EXIT_FAILURE);
+exim_exit(EXIT_FAILURE, NULL);
 }
 
 
 }
 
 
@@ -182,7 +267,11 @@ Returns:       a file descriptor, or < 0 on failure (errno set)
 int
 log_create(uschar *name)
 {
 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. */
 
 /* If creation failed, attempt to build a log directory in case that is the
 problem. */
@@ -194,9 +283,13 @@ if (fd < 0 && errno == ENOENT)
   *lastslash = 0;
   created = directory_make(NULL, name, LOG_DIRECTORY_MODE, FALSE);
   DEBUG(D_any) debug_printf("%s log directory %s\n",
   *lastslash = 0;
   created = directory_make(NULL, name, LOG_DIRECTORY_MODE, FALSE);
   DEBUG(D_any) debug_printf("%s log directory %s\n",
-    created? "created" : "failed to create", name);
+    created ? "created" : "failed to create", name);
   *lastslash = '/';
   *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;
   }
 
 return fd;
@@ -246,7 +339,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 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. */
 
 /* 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. */
@@ -305,7 +402,7 @@ it gets statted to see if it has been cycled. With a datestamp, the datestamp
 will be compared. The static slot for saving it is the same size as buffer,
 and the text has been checked above to fit, so this use of strcpy() is OK. */
 
 will be compared. The static slot for saving it is the same size as buffer,
 and the text has been checked above to fit, so this use of strcpy() is OK. */
 
-if (type == lt_main)
+if (type == lt_main && string_datestamp_offset >= 0)
   {
   Ustrcpy(mainlog_name, buffer);
   mainlog_datestamp = mainlog_name + string_datestamp_offset;
   {
   Ustrcpy(mainlog_name, buffer);
   mainlog_datestamp = mainlog_name + string_datestamp_offset;
@@ -313,7 +410,7 @@ if (type == lt_main)
 
 /* Ditto for the reject log */
 
 
 /* Ditto for the reject log */
 
-else if (type == lt_reject)
+else if (type == lt_reject && string_datestamp_offset >= 0)
   {
   Ustrcpy(rejectlog_name, buffer);
   rejectlog_datestamp = rejectlog_name + string_datestamp_offset;
   {
   Ustrcpy(rejectlog_name, buffer);
   rejectlog_datestamp = rejectlog_name + string_datestamp_offset;
@@ -341,38 +438,41 @@ char afterwards if at the start, otherwise one before. */
 
 else if (string_datestamp_offset >= 0)
   {
 
 else if (string_datestamp_offset >= 0)
   {
-  uschar *from = buffer + string_datestamp_offset;
-  uschar *to = from + string_datestamp_length;
+  uschar * from = buffer + string_datestamp_offset;
+  uschar * to = from + string_datestamp_length;
+
   if (from == buffer || from[-1] == '/')
     {
     if (!isalnum(*to)) to++;
     }
   else
   if (from == buffer || from[-1] == '/')
     {
     if (!isalnum(*to)) to++;
     }
   else
-    {
     if (!isalnum(from[-1])) from--;
     if (!isalnum(from[-1])) from--;
-    }
-
-  /* This strcpy is ok, because we know that to is a substring of from. */
 
 
-  Ustrcpy(from, to);
+  /* This copy is ok, because we know that to is a substring of from. But
+  due to overlap we must use memmove() not Ustrcpy(). */
+  memmove(from, to, Ustrlen(to)+1);
   }
 
 /* If the file name is too long, it is an unrecoverable disaster */
 
 if (!ok)
   }
 
 /* If the file name is too long, it is an unrecoverable disaster */
 
 if (!ok)
-  {
   die(US"exim: log file path too long: aborting",
       US"Logging failure; please try later");
   die(US"exim: log file path too long: aborting",
       US"Logging failure; please try later");
-  }
 
 /* We now have the file name. Try to open an existing file. After a successful
 open, arrange for automatic closure on exec(), and then return. */
 
 
 /* We now have the file name. 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)
   {
 
 if (*fd >= 0)
   {
+#ifndef O_CLOEXEC
   (void)fcntl(*fd, F_SETFD, fcntl(*fd, F_GETFD) | FD_CLOEXEC);
   (void)fcntl(*fd, F_SETFD, fcntl(*fd, F_GETFD) | FD_CLOEXEC);
+#endif
   return;
   }
 
   return;
   }
 
@@ -399,7 +499,9 @@ else if (euid == root_uid) *fd = log_create_as_exim(buffer);
 
 if (*fd >= 0)
   {
 
 if (*fd >= 0)
   {
+#ifndef O_CLOEXEC
   (void)fcntl(*fd, F_SETFD, fcntl(*fd, F_GETFD) | FD_CLOEXEC);
   (void)fcntl(*fd, F_SETFD, fcntl(*fd, F_GETFD) | FD_CLOEXEC);
+#endif
   return;
   }
 
   return;
   }
 
@@ -420,12 +522,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. */
 
 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);
     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());
 
 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 +532,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    *
 
 /*************************************************
 *     Add configuration file info to log line    *
@@ -448,26 +554,18 @@ Arguments:
 Returns:      updated pointer
 */
 
 Returns:      updated pointer
 */
 
-static uschar *
-log_config_info(uschar *ptr, int flags)
+static gstring *
+log_config_info(gstring * g, int flags)
 {
 {
-Ustrcpy(ptr, "Exim configuration error");
-ptr += 24;
+g = string_cat(g, US"Exim configuration error");
 
 
-if ((flags & (LOG_CONFIG_FOR & ~LOG_CONFIG)) != 0)
-  {
-  Ustrcpy(ptr, " for ");
-  return ptr + 5;
-  }
+if (flags & (LOG_CONFIG_FOR & ~LOG_CONFIG))
+  return string_cat(g, US" for ");
 
 
-if ((flags & (LOG_CONFIG_IN & ~LOG_CONFIG)) != 0)
-  {
-  sprintf(CS ptr, " in line %d of %s", config_lineno, config_filename);
-  while (*ptr) ptr++;
-  }
+if (flags & (LOG_CONFIG_IN & ~LOG_CONFIG))
+  g = string_fmt_append(g, " in line %d of %s", config_lineno, config_filename);
 
 
-Ustrcpy(ptr, ":\n  ");
-return ptr + 4;
+return string_catn(g, US":\n  ", 4);
 }
 
 
 }
 
 
@@ -498,12 +596,9 @@ log_write_failed(uschar *name, int length, int rc)
 {
 int save_errno = errno;
 
 {
 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);
     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,
 
 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           *
 *************************************************/
 /*************************************************
 *            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
 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
 
 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
 
 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
   flags     each bit indicates some independent action:
               LOG_SENDER      add raw sender to the message
               LOG_RECIPIENTS  add raw recipients list to message
@@ -616,10 +736,10 @@ Returns:    nothing
 void
 log_write(unsigned int selector, int flags, const char *format, ...)
 {
 void
 log_write(unsigned int selector, int flags, const char *format, ...)
 {
-uschar *ptr;
-int length;
 int paniclogfd;
 ssize_t written_len;
 int paniclogfd;
 ssize_t written_len;
+gstring gs = { .size = LOG_BUFFER_SIZE-1, .ptr = 0, .s = log_buffer };
+gstring * g;
 va_list ap;
 
 /* If panic_recurseflag is set, we have failed to open the panic log. This is
 va_list ap;
 
 /* If panic_recurseflag is set, we have failed to open the panic log. This is
@@ -629,11 +749,11 @@ original log line that caused the problem. Afterwards, expire. */
 
 if (panic_recurseflag)
   {
 
 if (panic_recurseflag)
   {
-  uschar *extra = (panic_save_buffer == NULL)? US"" : panic_save_buffer;
-  if (debug_file != NULL) debug_printf("%s%s", extra, log_buffer);
-  if (log_stderr != NULL && log_stderr != debug_file)
+  uschar *extra = panic_save_buffer ? panic_save_buffer : US"";
+  if (debug_file) debug_printf("%s%s", extra, log_buffer);
+  if (log_stderr && log_stderr != debug_file)
     fprintf(log_stderr, "%s%s", extra, log_buffer);
     fprintf(log_stderr, "%s%s", extra, log_buffer);
-  if (*extra != 0) write_syslog(LOG_CRIT, extra);
+  if (*extra) write_syslog(LOG_CRIT, extra);
   write_syslog(LOG_CRIT, log_buffer);
   die(US"exim: could not open panic log - aborting: see message(s) above",
     US"Unexpected log failure, please try later");
   write_syslog(LOG_CRIT, log_buffer);
   die(US"exim: could not open panic log - aborting: see message(s) above",
     US"Unexpected log failure, please try later");
@@ -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. */
 
 /* 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");
     {
     fprintf(stderr, "exim: failed to get store for log buffer\n");
-    exim_exit(EXIT_FAILURE);
+    exim_exit(EXIT_FAILURE, NULL);
     }
     }
-  }
 
 /* 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
 
 /* 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,27 +785,27 @@ 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 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;
     {
     int sep = ':';              /* Fixed separator - outside use */
     uschar *s;
-    uschar *ss = log_file_path;
+    const uschar *ss = log_file_path;
+
     logging_mode = 0;
     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;
       {
       if (Ustrcmp(s, "syslog") == 0)
         logging_mode |= LOG_MODE_SYSLOG;
-      else if ((logging_mode & LOG_MODE_FILE) != 0) multiple = TRUE;
+      else if (logging_mode & LOG_MODE_FILE)
+       multiple = TRUE;
       else
         {
         logging_mode |= LOG_MODE_FILE;
 
         /* If a non-empty path is given, use it */
 
       else
         {
         logging_mode |= LOG_MODE_FILE;
 
         /* If a non-empty path is given, use it */
 
-        if (s[0] != 0)
-          {
+        if (*s)
           file_path = string_copy(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
 
         /* 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 +813,7 @@ if (!path_inspected)
         use the ultimate default in the spool directory. */
 
         else
         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 */
     }
         }    /* First non-syslog item in log_file_path */
       }      /* Scan of log_file_path */
     }
@@ -720,7 +827,7 @@ if (!path_inspected)
   /* Set up the ultimate default if necessary. Then revert to the old store
   pool, and record that we've sorted out the path. */
 
   /* Set up the ultimate default if necessary. Then revert to the old store
   pool, and record that we've sorted out the path. */
 
-  if ((logging_mode & LOG_MODE_FILE) != 0 && file_path[0] == 0)
+  if (logging_mode & LOG_MODE_FILE  &&  !file_path[0])
     file_path = string_sprintf("%s/log/%%slog", spool_directory);
   store_pool = old_pool;
   path_inspected = TRUE;
     file_path = string_sprintf("%s/log/%%slog", spool_directory);
   store_pool = old_pool;
   path_inspected = TRUE;
@@ -729,10 +836,8 @@ if (!path_inspected)
   should work since we have now set up the routing. */
 
   if (multiple)
   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);
     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
   }
 
 /* If debugging, show all log entries, but don't show headers. Do it all
@@ -741,55 +846,52 @@ in one go so that it doesn't get split when multi-processing. */
 DEBUG(D_any|D_v)
   {
   int i;
 DEBUG(D_any|D_v)
   {
   int i;
-  ptr = log_buffer;
 
 
-  Ustrcpy(ptr, "LOG:");
-  ptr += 4;
+  g = string_catn(&gs, US"LOG:", 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++)
     {
 
   for (i = 0; i < log_options_count; i++)
     {
-    unsigned int bit = log_options[i].bit;
-    if ((bit & 0x80000000) != 0) continue;
-    if ((selector & bit) != 0)
-      {
-      *ptr++ = ' ';
-      Ustrcpy(ptr, log_options[i].name);
-      while (*ptr) ptr++;
-      }
+    unsigned int bitnum = log_options[i].bit;
+    if (bitnum < BITWORDSIZE && selector == BIT(bitnum))
+      g = string_fmt_append(g, " %s", log_options[i].name);
     }
 
     }
 
-  sprintf(CS ptr, "%s%s%s%s\n  ",
-    ((flags & LOG_MAIN) != 0)?    " MAIN"   : "",
-    ((flags & LOG_PANIC) != 0)?   " PANIC"  : "",
-    ((flags & LOG_PANIC_DIE) == LOG_PANIC_DIE)? " DIE" : "",
-    ((flags & LOG_REJECT) != 0)?  " REJECT" : "");
+  g = string_fmt_append(g, "%s%s%s%s\n  ",
+    flags & LOG_MAIN ?    " MAIN"   : "",
+    flags & LOG_PANIC ?   " PANIC"  : "",
+    (flags & LOG_PANIC_DIE) == LOG_PANIC_DIE ? " DIE" : "",
+    flags & LOG_REJECT ?  " REJECT" : "");
 
 
-  while(*ptr) ptr++;
-  if ((flags & LOG_CONFIG) != 0) ptr = log_config_info(ptr, flags);
+  if (flags & LOG_CONFIG) g = log_config_info(g, flags);
 
   va_start(ap, format);
 
   va_start(ap, format);
-  if (!string_vformat(ptr, LOG_BUFFER_SIZE - (ptr-log_buffer)-1, format, ap))
-    Ustrcpy(ptr, "**** log string overflowed log buffer ****");
+  i = g->ptr;
+  if (!string_vformat(g, FALSE, format, ap))
+    {
+    g->ptr = i;
+    g = string_cat(g, US"**** log string overflowed log buffer ****");
+    }
   va_end(ap);
 
   va_end(ap);
 
-  while(*ptr) ptr++;
-  Ustrcat(ptr, "\n");
-  debug_printf("%s", log_buffer);
-  }
+  g->size = LOG_BUFFER_SIZE;
+  g = string_catn(g, US"\n", 1);
+  debug_printf("%s", string_from_gstring(g));
 
 
+  gs.size = LOG_BUFFER_SIZE-1; /* Having used the buffer for debug output, */
+  gs.ptr = 0;                  /* reset it for the real use. */
+  gs.s = log_buffer;
+  }
 /* If no log file is specified, we are in a mess. */
 
 /* If no log file is specified, we are in a mess. */
 
-if ((flags & (LOG_MAIN|LOG_PANIC|LOG_REJECT)) == 0)
+if (!(flags & (LOG_MAIN|LOG_PANIC|LOG_REJECT)))
   log_write(0, LOG_MAIN|LOG_PANIC_DIE, "log_write called with no log "
     "flags set");
 
 /* There are some weird circumstances in which logging is disabled. */
 
   log_write(0, LOG_MAIN|LOG_PANIC_DIE, "log_write called with no log "
     "flags set");
 
 /* There are some weird circumstances in which logging is disabled. */
 
-if (disable_logging)
+if (f.disable_logging)
   {
   DEBUG(D_any) debug_printf("log writing disabled\n");
   return;
   {
   DEBUG(D_any) debug_printf("log writing disabled\n");
   return;
@@ -802,78 +904,76 @@ if (!write_rejectlog) flags &= ~LOG_REJECT;
 /* Create the main message in the log buffer. Do not include the message id
 when called by a utility. */
 
 /* Create the main message in the log buffer. Do not include the message id
 when called by a utility. */
 
-ptr = log_buffer;
-sprintf(CS ptr, "%s ", tod_stamp(tod_log));
-while(*ptr) ptr++;
+g = string_fmt_append(&gs, "%s ", tod_stamp(tod_log));
 
 
-if ((log_extra_selector & LX_pid) != 0)
+if (LOGGING(pid))
   {
   {
-  sprintf(CS ptr, "[%d] ", (int)getpid());
-  while (*ptr) ptr++;
+  if (!syslog_pid) pid_position[0] = g->ptr;           /* remember begin … */
+  g = string_fmt_append(g, "[%d] ", (int)getpid());
+  if (!syslog_pid) pid_position[1] = g->ptr;           /*  … and end+1 of the PID */
   }
 
   }
 
-if (really_exim && message_id[0] != 0)
-  {
-  sprintf(CS ptr, "%s ", message_id);
-  while(*ptr) ptr++;
-  }
+if (f.really_exim && message_id[0] != 0)
+  g = string_fmt_append(g, "%s ", message_id);
 
 
-if ((flags & LOG_CONFIG) != 0) ptr = log_config_info(ptr, flags);
+if (flags & LOG_CONFIG)
+  g = log_config_info(g, flags);
 
 va_start(ap, format);
 
 va_start(ap, format);
-if (!string_vformat(ptr, LOG_BUFFER_SIZE - (ptr-log_buffer)-1, format, ap))
-  Ustrcpy(ptr, "**** log string overflowed log buffer ****\n");
-while(*ptr) ptr++;
+  {
+  int i = g->ptr;
+  if (!string_vformat(g, FALSE, format, ap))
+    {
+    g->ptr = i;
+    g = string_cat(g, US"**** log string overflowed log buffer ****\n");
+    }
+  }
 va_end(ap);
 
 /* Add the raw, unrewritten, sender to the message if required. This is done
 this way because it kind of fits with LOG_RECIPIENTS. */
 
 va_end(ap);
 
 /* Add the raw, unrewritten, sender to the message if required. This is done
 this way because it kind of fits with LOG_RECIPIENTS. */
 
-if ((flags & LOG_SENDER) != 0 &&
-    ptr < log_buffer + LOG_BUFFER_SIZE - 10 - Ustrlen(raw_sender))
-  {
-  sprintf(CS ptr, " from <%s>", raw_sender);
-  while (*ptr) ptr++;
-  }
+if (   flags & LOG_SENDER
+   && g->ptr < LOG_BUFFER_SIZE - 10 - Ustrlen(raw_sender))
+  g = string_fmt_append(g, " from <%s>", raw_sender);
 
 /* Add list of recipients to the message if required; the raw list,
 before rewriting, was saved in raw_recipients. There may be none, if an ACL
 discarded them all. */
 
 
 /* Add list of recipients to the message if required; the raw list,
 before rewriting, was saved in raw_recipients. There may be none, if an ACL
 discarded them all. */
 
-if ((flags & LOG_RECIPIENTS) != 0 && ptr < log_buffer + LOG_BUFFER_SIZE - 6 &&
-     raw_recipients_count > 0)
+if (  flags & LOG_RECIPIENTS
+   && g->ptr < LOG_BUFFER_SIZE - 6
+   && raw_recipients_count > 0)
   {
   int i;
   {
   int i;
-  sprintf(CS ptr, " for");
-  while (*ptr) ptr++;
+  g = string_fmt_append(g, " for");
   for (i = 0; i < raw_recipients_count; i++)
     {
   for (i = 0; i < raw_recipients_count; i++)
     {
-    uschar *s = raw_recipients[i];
-    if (log_buffer + LOG_BUFFER_SIZE - ptr < Ustrlen(s) + 3) break;
-    sprintf(CS ptr, " %s", s);
-    while (*ptr) ptr++;
+    uschar * s = raw_recipients[i];
+    if (LOG_BUFFER_SIZE - g->ptr < Ustrlen(s) + 3) break;
+    g = string_fmt_append(g, " %s", s);
     }
   }
 
     }
   }
 
-sprintf(CS  ptr, "\n");
-while(*ptr) ptr++;
-length = ptr - log_buffer;
+g = string_catn(g, US"\n", 1);
+string_from_gstring(g);
 
 /* Handle loggable errors when running a utility, or when address testing.
 Write to log_stderr unless debugging (when it will already have been written),
 or unless there is no log_stderr (expn called from daemon, for example). */
 
 
 /* Handle loggable errors when running a utility, or when address testing.
 Write to log_stderr unless debugging (when it will already have been written),
 or unless there is no log_stderr (expn called from daemon, for example). */
 
-if (!really_exim || log_testing_mode)
+if (!f.really_exim || f.log_testing_mode)
   {
   {
-  if (debug_selector == 0 && log_stderr != NULL &&
-      (selector == 0 || (selector & log_write_selector) != 0))
-    {
+  if (  !debug_selector
+     && log_stderr
+     && (selector == 0 || (selector & log_selector[0]) != 0)
+    )
     if (host_checking)
       fprintf(log_stderr, "LOG: %s", CS(log_buffer + 20));  /* no timestamp */
     else
       fprintf(log_stderr, "%s", CS log_buffer);
     if (host_checking)
       fprintf(log_stderr, "LOG: %s", CS(log_buffer + 20));  /* no timestamp */
     else
       fprintf(log_stderr, "%s", CS log_buffer);
-    }
-  if ((flags & LOG_PANIC_DIE) == LOG_PANIC_DIE) exim_exit(EXIT_FAILURE);
+
+  if ((flags & LOG_PANIC_DIE) == LOG_PANIC_DIE) exim_exit(EXIT_FAILURE, US"");
   return;
   }
 
   return;
   }
 
@@ -883,14 +983,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. */
 
 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);
 
     write_syslog(LOG_INFO, log_buffer);
 
-  if ((logging_mode & LOG_MODE_FILE) != 0)
+  if (logging_mode & LOG_MODE_FILE)
     {
     struct stat statbuf;
 
     {
     struct stat statbuf;
 
@@ -898,7 +998,7 @@ if ((flags & LOG_MAIN) != 0 &&
     operation. This happens at midnight, at which point we want to roll over
     the file. Closing it has the desired effect. */
 
     operation. This happens at midnight, at which point we want to roll over
     the file. Closing it has the desired effect. */
 
-    if (mainlog_datestamp != NULL)
+    if (mainlog_datestamp)
       {
       uschar *nowstamp = tod_stamp(string_datestamp_type);
       if (Ustrncmp (mainlog_datestamp, nowstamp, Ustrlen(nowstamp)) != 0)
       {
       uschar *nowstamp = tod_stamp(string_datestamp_type);
       if (Ustrncmp (mainlog_datestamp, nowstamp, Ustrlen(nowstamp)) != 0)
@@ -916,14 +1016,8 @@ if ((flags & LOG_MAIN) != 0 &&
     happening. */
 
     if (mainlogfd >= 0)
     happening. */
 
     if (mainlogfd >= 0)
-      {
       if (Ustat(mainlog_name, &statbuf) < 0 || statbuf.st_ino != mainlog_inode)
       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. */
 
 
     /* If the log is closed, open it. Then write the line. */
 
@@ -935,10 +1029,10 @@ if ((flags & LOG_MAIN) != 0 &&
 
     /* Failing to write to the log is disastrous */
 
 
     /* Failing to write to the log is disastrous */
 
-    written_len = write_to_fd_buf(mainlogfd, log_buffer, length);
-    if (written_len != length)
+    written_len = write_to_fd_buf(mainlogfd, g->s, g->ptr);
+    if (written_len != g->ptr)
       {
       {
-      log_write_failed(US"main log", length, written_len);
+      log_write_failed(US"main log", g->ptr, written_len);
       /* That function does not return */
       }
     }
       /* That function does not return */
       }
     }
@@ -949,80 +1043,81 @@ which case the flags are altered above. If there are any header lines (i.e. if
 the rejection is happening after the DATA phase), log the recipients and the
 headers. */
 
 the rejection is happening after the DATA phase), log the recipients and the
 headers. */
 
-if ((flags & LOG_REJECT) != 0)
+if (flags & LOG_REJECT)
   {
   header_line *h;
 
   {
   header_line *h;
 
-  if (header_list != NULL && (log_extra_selector & LX_rejected_header) != 0)
+  if (header_list && LOGGING(rejected_header))
     {
     {
+    uschar * p = g->s + g->ptr;
+    int i;
+
     if (recipients_count > 0)
       {
     if (recipients_count > 0)
       {
-      int i;
-
       /* List the sender */
 
       /* List the sender */
 
-      string_format(ptr, LOG_BUFFER_SIZE - (ptr-log_buffer),
+      string_format(p, LOG_BUFFER_SIZE - g->ptr,
         "Envelope-from: <%s>\n", sender_address);
         "Envelope-from: <%s>\n", sender_address);
-      while (*ptr) ptr++;
+      while (*p) p++;
+      g->ptr = p - g->s;
 
       /* List up to 5 recipients */
 
 
       /* List up to 5 recipients */
 
-      string_format(ptr, LOG_BUFFER_SIZE - (ptr-log_buffer),
+      string_format(p, LOG_BUFFER_SIZE - g->ptr,
         "Envelope-to: <%s>\n", recipients_list[0].address);
         "Envelope-to: <%s>\n", recipients_list[0].address);
-      while (*ptr) ptr++;
+      while (*p) p++;
+      g->ptr = p - g->s;
 
       for (i = 1; i < recipients_count && i < 5; i++)
         {
 
       for (i = 1; i < recipients_count && i < 5; i++)
         {
-        string_format(ptr, LOG_BUFFER_SIZE - (ptr-log_buffer), "    <%s>\n",
+        string_format(p, LOG_BUFFER_SIZE - g->ptr, "    <%s>\n",
           recipients_list[i].address);
           recipients_list[i].address);
-        while (*ptr) ptr++;
+       while (*p) p++;
+       g->ptr = p - g->s;
         }
 
       if (i < recipients_count)
         {
         }
 
       if (i < recipients_count)
         {
-        (void)string_format(ptr, LOG_BUFFER_SIZE - (ptr-log_buffer),
+        string_format(p, LOG_BUFFER_SIZE - g->ptr,
           "    ...\n");
           "    ...\n");
-        while (*ptr) ptr++;
+       while (*p) p++;
+       g->ptr = p - g->s;
         }
       }
 
     /* A header with a NULL text is an unfilled in Received: header */
 
         }
       }
 
     /* A header with a NULL text is an unfilled in Received: header */
 
-    for (h = header_list; h != NULL; h = h->next)
+    for (h = header_list; h; h = h->next) if (h->text)
       {
       {
-      BOOL fitted;
-      if (h->text == NULL) continue;
-      fitted = string_format(ptr, LOG_BUFFER_SIZE - (ptr-log_buffer),
+      BOOL fitted = string_format(p, LOG_BUFFER_SIZE - g->ptr,
         "%c %s", h->type, h->text);
         "%c %s", h->type, h->text);
-      while(*ptr) ptr++;
+      while (*p) p++;
+      g->ptr = p - g->s;
       if (!fitted)         /* Buffer is full; truncate */
         {
       if (!fitted)         /* Buffer is full; truncate */
         {
-        ptr -= 100;        /* For message and separator */
-        if (ptr[-1] == '\n') ptr--;
-        Ustrcpy(ptr, "\n*** truncated ***\n");
-        while (*ptr) ptr++;
+        g->ptr -= 100;        /* For message and separator */
+        if (g->s[g->ptr-1] == '\n') g->ptr--;
+        g = string_cat(g, US"\n*** truncated ***\n");
         break;
         }
       }
         break;
         }
       }
-
-    length = ptr - log_buffer;
     }
 
   /* Write to syslog or to a log file */
 
     }
 
   /* Write to syslog or to a log file */
 
-  if ((logging_mode & LOG_MODE_SYSLOG) != 0 &&
-      (syslog_duplication || (flags & LOG_PANIC) == 0))
-    write_syslog(LOG_NOTICE, log_buffer);
+  if (  logging_mode & LOG_MODE_SYSLOG
+     && (syslog_duplication || !(flags & LOG_PANIC)))
+    write_syslog(LOG_NOTICE, string_from_gstring(g));
 
   /* Check for a change to the rejectlog file name when datestamping is in
   operation. This happens at midnight, at which point we want to roll over
   the file. Closing it has the desired effect. */
 
 
   /* Check for a change to the rejectlog file name when datestamping is in
   operation. This happens at midnight, at which point we want to roll over
   the file. Closing it has the desired effect. */
 
-  if ((logging_mode & LOG_MODE_FILE) != 0)
+  if (logging_mode & LOG_MODE_FILE)
     {
     struct stat statbuf;
 
     {
     struct stat statbuf;
 
-    if (rejectlog_datestamp != NULL)
+    if (rejectlog_datestamp)
       {
       uschar *nowstamp = tod_stamp(string_datestamp_type);
       if (Ustrncmp (rejectlog_datestamp, nowstamp, Ustrlen(nowstamp)) != 0)
       {
       uschar *nowstamp = tod_stamp(string_datestamp_type);
       if (Ustrncmp (rejectlog_datestamp, nowstamp, Ustrlen(nowstamp)) != 0)
@@ -1040,7 +1135,6 @@ if ((flags & LOG_REJECT) != 0)
     happening. */
 
     if (rejectlogfd >= 0)
     happening. */
 
     if (rejectlogfd >= 0)
-      {
       if (Ustat(rejectlog_name, &statbuf) < 0 ||
            statbuf.st_ino != rejectlog_inode)
         {
       if (Ustat(rejectlog_name, &statbuf) < 0 ||
            statbuf.st_ino != rejectlog_inode)
         {
@@ -1048,7 +1142,6 @@ if ((flags & LOG_REJECT) != 0)
         rejectlogfd = -1;
         rejectlog_inode = 0;
         }
         rejectlogfd = -1;
         rejectlog_inode = 0;
         }
-      }
 
     /* Open the file if necessary, and write the data */
 
 
     /* Open the file if necessary, and write the data */
 
@@ -1058,10 +1151,10 @@ if ((flags & LOG_REJECT) != 0)
       if (fstat(rejectlogfd, &statbuf) >= 0) rejectlog_inode = statbuf.st_ino;
       }
 
       if (fstat(rejectlogfd, &statbuf) >= 0) rejectlog_inode = statbuf.st_ino;
       }
 
-    written_len = write_to_fd_buf(rejectlogfd, log_buffer, length);
-    if (written_len != length)
+    written_len = write_to_fd_buf(rejectlogfd, g->s, g->ptr);
+    if (written_len != g->ptr)
       {
       {
-      log_write_failed(US"reject log", length, written_len);
+      log_write_failed(US"reject log", g->ptr, written_len);
       /* That function does not return */
       }
     }
       /* That function does not return */
       }
     }
@@ -1073,39 +1166,37 @@ open, there will be a recursive call to log_write(). We detect this above and
 attempt to write to the system log as a last-ditch try at telling somebody. In
 all cases except mua_wrapper, try to write to log_stderr. */
 
 attempt to write to the system log as a last-ditch try at telling somebody. In
 all cases except mua_wrapper, try to write to log_stderr. */
 
-if ((flags & LOG_PANIC) != 0)
+if (flags & LOG_PANIC)
   {
   {
-  if (log_stderr != NULL && log_stderr != debug_file && !mua_wrapper)
-    fprintf(log_stderr, "%s", CS log_buffer);
+  if (log_stderr && log_stderr != debug_file && !mua_wrapper)
+    fprintf(log_stderr, "%s", CS string_from_gstring(g));
 
 
-  if ((logging_mode & LOG_MODE_SYSLOG) != 0)
-    {
+  if (logging_mode & LOG_MODE_SYSLOG)
     write_syslog(LOG_ALERT, log_buffer);
     write_syslog(LOG_ALERT, log_buffer);
-    }
 
   /* If this panic logging was caused by a failure to open the main log,
   the original log line is in panic_save_buffer. Make an attempt to write it. */
 
 
   /* If this panic logging was caused by a failure to open the main log,
   the original log line is in panic_save_buffer. Make an attempt to write it. */
 
-  if ((logging_mode & LOG_MODE_FILE) != 0)
+  if (logging_mode & LOG_MODE_FILE)
     {
     panic_recurseflag = TRUE;
     open_log(&paniclogfd, lt_panic, NULL);  /* Won't return on failure */
     panic_recurseflag = FALSE;
 
     {
     panic_recurseflag = TRUE;
     open_log(&paniclogfd, lt_panic, NULL);  /* Won't return on failure */
     panic_recurseflag = FALSE;
 
-    if (panic_save_buffer != NULL)
+    if (panic_save_buffer)
       {
       int i = write(paniclogfd, panic_save_buffer, Ustrlen(panic_save_buffer));
       i = i;   /* compiler quietening */
       }
 
       {
       int i = write(paniclogfd, panic_save_buffer, Ustrlen(panic_save_buffer));
       i = i;   /* compiler quietening */
       }
 
-    written_len = write_to_fd_buf(paniclogfd, log_buffer, length);
-    if (written_len != length)
+    written_len = write_to_fd_buf(paniclogfd, g->s, g->ptr);
+    if (written_len != g->ptr)
       {
       int save_errno = errno;
       write_syslog(LOG_CRIT, log_buffer);
       sprintf(CS log_buffer, "write failed on panic log: length=%d result=%d "
       {
       int save_errno = errno;
       write_syslog(LOG_CRIT, log_buffer);
       sprintf(CS log_buffer, "write failed on panic log: length=%d result=%d "
-        "errno=%d (%s)", length, (int)written_len, save_errno, strerror(save_errno));
-      write_syslog(LOG_CRIT, log_buffer);
+        "errno=%d (%s)", g->ptr, (int)written_len, save_errno, strerror(save_errno));
+      write_syslog(LOG_CRIT, string_from_gstring(g));
       flags |= LOG_PANIC_DIE;
       }
 
       flags |= LOG_PANIC_DIE;
       }
 
@@ -1138,6 +1229,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      *
 *************************************************/
 /*************************************************
 *         Decode bit settings for log/debug      *
 *************************************************/
@@ -1148,13 +1268,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.
 
 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
 
 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 +1278,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:
 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
   string         the configured string
   options        the table of option names
   count          size of table
@@ -1176,9 +1291,8 @@ Returns:         nothing on success - bomb out on failure
 */
 
 void
 */
 
 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;
 {
 uschar *errmsg;
 if (string == NULL) return;
@@ -1186,7 +1300,8 @@ if (string == NULL) return;
 if (*string == '=')
   {
   char *end;    /* Not uschar */
 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);
   if (*end == 0) return;
   errmsg = string_sprintf("malformed numeric %s_selector setting: %s", which,
     string);
@@ -1229,40 +1344,22 @@ else for(;;)
       if (middle->name[len] != 0) c = -1; else
         {
         unsigned int bit = middle->bit;
       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 */
         }
       }
         break;  /* Out of loop to match selector name */
         }
       }
@@ -1319,7 +1416,7 @@ int fd = -1;
 if (debug_file)
   {
   debug_printf("DEBUGGING ACTIVATED FROM WITHIN CONFIG.\n"
 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;
   }
 
   return;
   }
 
@@ -1332,10 +1429,14 @@ if (tag_name != NULL && (Ustrchr(tag_name, '/') != NULL))
 
 debug_selector = D_default;
 if (opts)
 
 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);
       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);
 
 
 open_log(&fd, lt_debug, tag_name);
 
@@ -1346,4 +1447,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 */
 /* End of log.c */
index 2a27071..9055f52 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     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. */
 
 
 /* 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 */
   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 */
     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 */
   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
 } 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 2 adds: version_report */
+/* Version 3 change: non/cache becomes TTL in seconds */
 
 typedef struct lookup_module_info {
   uint magic;
 
 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
 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
 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
 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
 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
   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.
                       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.
 
 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..153fcf2 100644 (file)
@@ -18,6 +18,9 @@
  *   Changed over to using unsigned chars
  *   Makes use of lf_check_file() for file checking
  * --------------------------------------------------------------
  *   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
  *
  * 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.
  *
  *
  * 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
  * 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
  * 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;
 
 {
   uint32 h;
 
@@ -128,7 +131,7 @@ cdb_bread(int fd,
 
 /*
  * cdb_bread()
 
 /*
  * 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)
 
 static uint32
 cdb_unpack(uschar *buf)
@@ -272,141 +275,177 @@ cdb_check(void *handle,
 static int
 cdb_find(void *handle,
         uschar *filename,
 static int
 cdb_find(void *handle,
         uschar *filename,
-        uschar *keystring,
+        const uschar *keystring,
         int  key_len,
         uschar **result,
         uschar **errmsg,
         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
 #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 the position is zero then we have a definite miss */
+
     if (item_posn == 0)
       return FAIL;
 
     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 */
       /* 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);
        /* 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..9e3d68e 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2012 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "../exim.h"
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "../exim.h"
 static void *
 dbmdb_open(uschar *filename, uschar **errmsg)
 {
 static void *
 dbmdb_open(uschar *filename, uschar **errmsg)
 {
+uschar * dirname = string_copy(filename);
+uschar * s;
 EXIM_DB *yield = NULL;
 EXIM_DB *yield = NULL;
-EXIM_DBOPEN(filename, O_RDONLY, 0, &yield);
+
+if ((s = Ustrrchr(dirname, '/'))) *s = '\0';
+EXIM_DBOPEN(filename, dirname, O_RDONLY, 0, &yield);
 if (yield == NULL)
   {
   int save_errno = errno;
 if (yield == NULL)
   {
   int save_errno = errno;
@@ -86,8 +90,8 @@ return rc == 0;
 the keylength in order to include the terminating zero. */
 
 static int
 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;
 {
 EXIM_DB *d = (EXIM_DB *)handle;
 EXIM_DATUM key, data;
@@ -119,8 +123,8 @@ return FAIL;
 /* See local README for interface description */
 
 int
 /* 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);
 {
 return dbmdb_find(handle, filename, keystring, length-1, result, errmsg,
   do_cache);
@@ -139,11 +143,11 @@ return dbmdb_find(handle, filename, keystring, length-1, result, errmsg,
  */
 
 static int
  */
 
 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_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,
 int buflen, bufleft, key_item_len, sep = 0;
 
 /* To a first approximation, the size of the lookup key needs to be about,
index fde98b9..e75bd1e 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "../exim.h"
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "../exim.h"
 header files. */
 
 #ifndef T_TXT
 header files. */
 
 #ifndef T_TXT
-#define T_TXT 16
+# define T_TXT 16
 #endif
 
 /* Many systems do not have T_SPF. */
 #ifndef T_SPF
 #endif
 
 /* Many systems do not have T_SPF. */
 #ifndef T_SPF
-#define T_SPF 99
+# define T_SPF 99
 #endif
 
 /* New TLSA record for DANE */
 #ifndef T_TLSA
 #endif
 
 /* New TLSA record for DANE */
 #ifndef T_TLSA
-#define T_TLSA 52
+# define T_TLSA 52
 #endif
 
 /* Table of recognized DNS record types and their integer values. */
 #endif
 
 /* Table of recognized DNS record types and their integer values. */
@@ -34,9 +34,6 @@ static const char *type_names[] = {
 #if HAVE_IPV6
   "a+",
   "aaaa",
 #if HAVE_IPV6
   "a+",
   "aaaa",
-  #ifdef SUPPORT_A6
-  "a6",
-  #endif
 #endif
   "cname",
   "csa",
 #endif
   "cname",
   "csa",
@@ -44,6 +41,7 @@ static const char *type_names[] = {
   "mxh",
   "ns",
   "ptr",
   "mxh",
   "ns",
   "ptr",
+  "soa",
   "spf",
   "srv",
   "tlsa",
   "spf",
   "srv",
   "tlsa",
@@ -56,9 +54,6 @@ static int type_values[] = {
 #if HAVE_IPV6
   T_ADDRESSES,     /* Private type for AAAA + A */
   T_AAAA,
 #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". */
 #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_MXH,     /* Private type for "MX hostnames" */
   T_NS,
   T_PTR,
+  T_SOA,
   T_SPF,
   T_SRV,
   T_TLSA,
   T_SPF,
   T_SRV,
   T_TLSA,
@@ -108,47 +104,53 @@ 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.
 
 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.
 
 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".
 
 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
 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 rc;
-int size = 256;
-int ptr = 0;
 int sep = 0;
 int defer_mode = PASS;
 int dnssec_mode = OK;
 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;
 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 *equals, *domain, *found;
-uschar buffer[256];
 
 
-/* Because we're the working in the search pool, we try to reclaim as much
+/* Because we're working in the search pool, we try to reclaim as much
 store as possible later, so we preallocate the result here */
 
 store as possible later, so we preallocate the result here */
 
-uschar *yield = store_get(size);
+gstring * yield = string_get(256);
 
 
-dns_record *rr;
+dns_record * rr;
 dns_answer dnsa;
 dns_scan dnss;
 
 dns_answer dnsa;
 dns_scan dnss;
 
@@ -180,58 +182,62 @@ if (*keystring == '>')
 
 /* Check for a modifier keyword. */
 
 
 /* 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)
   {
   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)
     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)
     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
       {
       *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)
     {
     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)
     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;
       }
       return DEFER;
       }
+    dns_retry = retries;
     }
     }
+  else
+    break;
+
   while (isspace(*keystring)) keystring++;
   if (*keystring++ != ',')
     {
   while (isspace(*keystring)) keystring++;
   if (*keystring++ != ',')
     {
@@ -253,17 +259,15 @@ if ((equals = Ustrchr(keystring, '=')) != NULL)
   while (tend > keystring && isspace(tend[-1])) tend--;
   len = tend - keystring;
 
   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 (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;
     {
     *errmsg = US"unsupported DNS record type";
     return DEFER;
@@ -295,15 +299,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
 /* 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 */
 
 
 /* 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 */
   {
   uschar rbuffer[256];
   int searchtype = (type == T_CSA)? T_SRV :         /* record type we want */
@@ -340,19 +348,13 @@ while ((domain = string_nextinlist(&keystring, &sep, buffer, sizeof(buffer)))
 #if HAVE_IPV6
     if (type == T_ADDRESSES)           /* NB cannot happen unless HAVE_IPV6 */
       {
 #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;
       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
       }
     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";
 
     lookup_dnssec_authenticated = dnssec_mode==OK ? NULL
       : dns_is_secure(&dnsa) ? US"yes" : US"no";
@@ -364,6 +366,8 @@ while ((domain = string_nextinlist(&keystring, &sep, buffer, sizeof(buffer)))
       {
       if (defer_mode == DEFER)
        {
       {
       if (defer_mode == DEFER)
        {
+       dns_retrans = save_retrans;
+       dns_retry = save_retry;
        dns_init(FALSE, FALSE, FALSE);                  /* clr dnssec bit */
        return DEFER;                                   /* always defer */
        }
        dns_init(FALSE, FALSE, FALSE);                  /* clr dnssec bit */
        return DEFER;                                   /* always defer */
        }
@@ -374,29 +378,19 @@ while ((domain = string_nextinlist(&keystring, &sep, buffer, sizeof(buffer)))
 
     /* Search the returned records */
 
 
     /* Search the returned records */
 
-    for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
-         rr != NULL;
-         rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
+    for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS); rr;
+         rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT)) if (rr->type == searchtype)
       {
       {
-      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 (type == T_A ||
-          #ifdef SUPPORT_A6
-          type == T_A6 ||
-          #endif
-          type == T_AAAA ||
-          type == T_ADDRESSES)
+      if (*do_cache > rr->ttl)
+       *do_cache = rr->ttl;
+
+      if (type == T_A || type == T_AAAA || type == T_ADDRESSES)
         {
         dns_address *da;
         {
         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 (yield->ptr) yield = string_catn(yield, outsep, 1);
+          yield = string_cat(yield, da->address);
           }
         continue;
         }
           }
         continue;
         }
@@ -404,16 +398,12 @@ 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. */
 
       /* 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 (yield->ptr) yield = string_catn(yield, outsep, 1);
 
       if (type == T_TXT || type == T_SPF)
         {
 
       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),
-            (rr->data)[0]);
-          }
+        if (outsep2 == NULL)   /* output only the first item of data */
+          yield = string_catn(yield, US (rr->data+1), (rr->data)[0]);
         else
           {
           /* output all items */
         else
           {
           /* output all items */
@@ -422,9 +412,8 @@ while ((domain = string_nextinlist(&keystring, &sep, buffer, sizeof(buffer)))
             {
             uschar chunk_len = (rr->data)[data_offset++];
             if (outsep2[0] != '\0' && data_offset != 1)
             {
             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, outsep2, 1);
+            yield = string_catn(yield, US ((rr->data)+data_offset), chunk_len);
             data_offset += chunk_len;
             }
           }
             data_offset += chunk_len;
             }
           }
@@ -432,86 +421,89 @@ while ((domain = string_nextinlist(&keystring, &sep, buffer, sizeof(buffer)))
       else if (type == T_TLSA)
         {
         uint8_t usage, selector, matching_type;
       else if (type == T_TLSA)
         {
         uint8_t usage, selector, matching_type;
-        uint16_t i, payload_length;
+        uint16_t payload_length;
         uschar s[MAX_TLSA_EXPANDED_SIZE];
        uschar * sp = s;
         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;
 
         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 */
         /* 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));
+       while (payload_length-- > 0 && sp-s < (MAX_TLSA_EXPANDED_SIZE - 4))
+          sp += sprintf(CS sp, "%02x", *p++);
+
+        yield = string_cat(yield, 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];
         {
         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, 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, 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, s, 2);
+           break;
+
+         default:
+           break;
+         }
 
         /* GETSHORT() has advanced the pointer to the target domain. */
 
         rc = dn_expand(dnsa.answer, dnsa.answer + dnsa.answerlen, p,
 
         /* 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. */
 
         /* If an overlong response was received, the data will have been
         truncated and dn_expand may fail. */
@@ -522,27 +514,55 @@ while ((domain = string_nextinlist(&keystring, &sep, buffer, sizeof(buffer)))
             "domain=%s", dns_text_type(type), domain);
           break;
           }
             "domain=%s", dns_text_type(type), domain);
           break;
           }
-        else yield = string_cat(yield, &size, &ptr, s, Ustrlen(s));
+        else yield = string_cat(yield, s);
+
+       if (type == T_SOA && outsep2 != NULL)
+         {
+         unsigned long serial, refresh, retry, expire, minimum;
+
+         p += rc;
+         yield = string_catn(yield, outsep2, 1);
+
+         rc = dn_expand(dnsa.answer, dnsa.answer + dnsa.answerlen, p,
+           (DN_EXPAND_ARG4_TYPE)s, sizeof(s));
+         if (rc < 0)
+           {
+           log_write(0, LOG_MAIN, "responsible-mailbox truncated: type=%s "
+             "domain=%s", dns_text_type(type), domain);
+           break;
+           }
+         else yield = string_cat(yield, s);
+
+         p += rc;
+         GETLONG(serial, p); GETLONG(refresh, p);
+         GETLONG(retry,  p); GETLONG(expire,  p); GETLONG(minimum, p);
+         sprintf(CS s, "%c%lu%c%lu%c%lu%c%lu%c%lu",
+           *outsep2, serial, *outsep2, refresh,
+           *outsep2, retry,  *outsep2, expire,  *outsep2, minimum);
+         yield = string_cat(yield, s);
+         }
         }
       }    /* Loop for list of returned records */
 
         }
       }    /* 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 */
 
 /* Reclaim unused memory */
 
     } while (type == T_ADDRESSES && searchtype != T_A);
 
   }        /* Loop for list of domains */
 
 /* Reclaim unused memory */
 
-store_reset(yield + ptr + 1);
+store_reset(yield->s + yield->ptr + 1);
 
 
-/* If ptr == 0 we have not found anything. Otherwise, insert the terminating
+/* If yield NULL we have not found anything. Otherwise, insert the terminating
 zero and return the result. */
 
 zero and return the result. */
 
+dns_retrans = save_retrans;
+dns_retry = save_retry;
 dns_init(FALSE, FALSE, FALSE); /* clear the dnssec bit for getaddrbyname */
 
 dns_init(FALSE, FALSE, FALSE); /* clear the dnssec bit for getaddrbyname */
 
-if (ptr == 0) return failrc;
-yield[ptr] = 0;
-*result = yield;
+if (!yield || !yield->ptr) return failrc;
+
+*result = string_from_gstring(yield);
 return OK;
 }
 
 return OK;
 }
 
index 990b69c..9f7dd8d 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     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
 /* 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
 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;
 {
 struct stat statbuf;
 int save_errno;
index 23e1dea..e52ca27 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* The code in this module was contributed by Ard Biesheuvel. */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* The code in this module was contributed by Ard Biesheuvel. */
@@ -109,333 +109,334 @@ static int
 perform_ibase_search(uschar * query, uschar * server, uschar ** resultptr,
                      uschar ** errmsg, BOOL * defer_break)
 {
 perform_ibase_search(uschar * query, uschar * server, uschar ** resultptr,
                      uschar ** errmsg, BOOL * defer_break)
 {
-    isc_stmt_handle stmth = NULL;
-    XSQLDA *out_sqlda;
-    XSQLVAR *var;
-
-    char buffer[256];
-    ISC_STATUS status[20], *statusp = status;
-
-    int i;
-    int ssize = 0;
-    int offset = 0;
-    int yield = DEFER;
-    uschar *result = NULL;
-    ibase_connection *cn;
-    uschar *server_copy = NULL;
-    uschar *sdata[3];
+isc_stmt_handle stmth = NULL;
+XSQLDA *out_sqlda;
+XSQLVAR *var;
+
+char buffer[256];
+ISC_STATUS status[20], *statusp = status;
+
+gstring * result;
+int i;
+int yield = DEFER;
+ibase_connection *cn;
+uschar *server_copy = NULL;
+uschar *sdata[3];
 
 /* Disaggregate the parameters from the server argument. The order is host,
 database, user, password. We can write to the string, since it is in a
 nextinlist temporary buffer. The copy of the string that is used for caching
 has the password removed. This copy is also used for debugging output. */
 
 
 /* Disaggregate the parameters from the server argument. The order is host,
 database, user, password. We can write to the string, since it is in a
 nextinlist temporary buffer. The copy of the string that is used for caching
 has the password removed. This copy is also used for debugging output. */
 
-    for (i = 2; i > 0; i--) {
-        uschar *pp = Ustrrchr(server, '|');
-        if (pp == NULL) {
-            *errmsg =
-                string_sprintf("incomplete Interbase server data: %s",
-                               (i == 3) ? server : server_copy);
-            *defer_break = TRUE;
-            return DEFER;
-        }
-        *pp++ = 0;
-        sdata[i] = pp;
-        if (i == 2)
-            server_copy = string_copy(server);   /* sans password */
+for (i = 2; i > 0; i--)
+  {
+  uschar *pp = Ustrrchr(server, '|');
+
+  if (pp == NULL)
+    {
+    *errmsg = string_sprintf("incomplete Interbase server data: %s",
+                      (i == 3) ? server : server_copy);
+    *defer_break = TRUE;
+    return DEFER;
     }
     }
-    sdata[0] = server;          /* What's left at the start */
+  *pp++ = 0;
+  sdata[i] = pp;
+  if (i == 2)
+      server_copy = string_copy(server);   /* sans password */
+  }
+sdata[0] = server;          /* What's left at the start */
 
 /* See if we have a cached connection to the server */
 
 
 /* See if we have a cached connection to the server */
 
-    for (cn = ibase_connections; cn != NULL; cn = cn->next) {
-        if (Ustrcmp(cn->server, server_copy) == 0) {
-            break;
-        }
-    }
+for (cn = ibase_connections; cn != NULL; cn = cn->next)
+  if (Ustrcmp(cn->server, server_copy) == 0)
+    break;
 
 /* Use a previously cached connection ? */
 
 
 /* Use a previously cached connection ? */
 
-    if (cn != NULL) {
-        static char db_info_options[] = { isc_info_base_level };
-
-        /* test if the connection is alive */
-        if (isc_database_info
-            (status, &cn->dbh, sizeof(db_info_options), db_info_options,
-             sizeof(buffer), buffer)) {
-            /* error occurred: assume connection is down */
-            DEBUG(D_lookup)
-                debug_printf
-                ("Interbase cleaning up cached connection: %s\n",
-                 cn->server);
-            isc_detach_database(status, &cn->dbh);
-        } else {
-            DEBUG(D_lookup)
-                debug_printf("Interbase using cached connection for %s\n",
-                             server_copy);
-        }
-    } else {
-        cn = store_get(sizeof(ibase_connection));
-        cn->server = server_copy;
-        cn->dbh = NULL;
-        cn->transh = NULL;
-        cn->next = ibase_connections;
-        ibase_connections = cn;
+if (cn)
+  {
+  static char db_info_options[] = { isc_info_base_level };
+
+  /* test if the connection is alive */
+  if (isc_database_info(status, &cn->dbh, sizeof(db_info_options),
+       db_info_options, sizeof(buffer), buffer))
+    {
+    /* error occurred: assume connection is down */
+    DEBUG(D_lookup)
+       debug_printf
+       ("Interbase cleaning up cached connection: %s\n",
+        cn->server);
+    isc_detach_database(status, &cn->dbh);
     }
     }
+  else
+    {
+    DEBUG(D_lookup) debug_printf("Interbase using cached connection for %s\n",
+                    server_copy);
+    }
+  }
+else
+  {
+  cn = store_get(sizeof(ibase_connection));
+  cn->server = server_copy;
+  cn->dbh = NULL;
+  cn->transh = NULL;
+  cn->next = ibase_connections;
+  ibase_connections = cn;
+  }
 
 /* If no cached connection, we must set one up. */
 
 
 /* If no cached connection, we must set one up. */
 
-    if (cn->dbh == NULL || cn->transh == NULL) {
-
-        char *dpb, *p;
-        short dpb_length;
-        static char trans_options[] =
-            { isc_tpb_version3, isc_tpb_read, isc_tpb_read_committed,
-            isc_tpb_rec_version
-        };
-
-        /* Construct the database parameter buffer. */
-        dpb = buffer;
-        *dpb++ = isc_dpb_version1;
-        *dpb++ = isc_dpb_user_name;
-        *dpb++ = strlen(sdata[1]);
-        for (p = sdata[1]; *p;)
-            *dpb++ = *p++;
-        *dpb++ = isc_dpb_password;
-        *dpb++ = strlen(sdata[2]);
-        for (p = sdata[2]; *p;)
-            *dpb++ = *p++;
-        dpb_length = dpb - buffer;
-
-        DEBUG(D_lookup)
-            debug_printf("new Interbase connection: database=%s user=%s\n",
-                         sdata[0], sdata[1]);
-
-        /* Connect to the database */
-        if (isc_attach_database
-            (status, 0, sdata[0], &cn->dbh, dpb_length, buffer)) {
-            isc_interprete(buffer, &statusp);
-            *errmsg =
-                string_sprintf("Interbase attach() failed: %s", buffer);
-            *defer_break = FALSE;
-            goto IBASE_EXIT;
-        }
-
-        /* Now start a read-only read-committed transaction */
-        if (isc_start_transaction
-            (status, &cn->transh, 1, &cn->dbh, sizeof(trans_options),
-             trans_options)) {
-            isc_interprete(buffer, &statusp);
-            isc_detach_database(status, &cn->dbh);
-            *errmsg =
-                string_sprintf("Interbase start_transaction() failed: %s",
-                               buffer);
-            *defer_break = FALSE;
-            goto IBASE_EXIT;
-        }
+if (cn->dbh == NULL || cn->transh == NULL)
+  {
+  char *dpb, *p;
+  short dpb_length;
+  static char trans_options[] =
+      { isc_tpb_version3, isc_tpb_read, isc_tpb_read_committed,
+      isc_tpb_rec_version
+  };
+
+  /* Construct the database parameter buffer. */
+  dpb = buffer;
+  *dpb++ = isc_dpb_version1;
+  *dpb++ = isc_dpb_user_name;
+  *dpb++ = strlen(sdata[1]);
+  for (p = sdata[1]; *p;)
+      *dpb++ = *p++;
+  *dpb++ = isc_dpb_password;
+  *dpb++ = strlen(sdata[2]);
+  for (p = sdata[2]; *p;)
+      *dpb++ = *p++;
+  dpb_length = dpb - buffer;
+
+  DEBUG(D_lookup)
+      debug_printf("new Interbase connection: database=%s user=%s\n",
+                  sdata[0], sdata[1]);
+
+  /* Connect to the database */
+  if (isc_attach_database
+      (status, 0, sdata[0], &cn->dbh, dpb_length, buffer))
+    {
+    isc_interprete(buffer, &statusp);
+    *errmsg =
+       string_sprintf("Interbase attach() failed: %s", buffer);
+    *defer_break = FALSE;
+    goto IBASE_EXIT;
     }
 
     }
 
-/* Run the query */
-    if (isc_dsql_allocate_statement(status, &cn->dbh, &stmth)) {
-        isc_interprete(buffer, &statusp);
-        *errmsg =
-            string_sprintf("Interbase alloc_statement() failed: %s",
-                           buffer);
-        *defer_break = FALSE;
-        goto IBASE_EXIT;
+  /* Now start a read-only read-committed transaction */
+  if (isc_start_transaction
+      (status, &cn->transh, 1, &cn->dbh, sizeof(trans_options),
+       trans_options))
+    {
+    isc_interprete(buffer, &statusp);
+    isc_detach_database(status, &cn->dbh);
+    *errmsg =
+       string_sprintf("Interbase start_transaction() failed: %s",
+                      buffer);
+    *defer_break = FALSE;
+    goto IBASE_EXIT;
     }
     }
+  }
 
 
-    out_sqlda = store_get(XSQLDA_LENGTH(1));
-    out_sqlda->version = SQLDA_VERSION1;
-    out_sqlda->sqln = 1;
-
-    if (isc_dsql_prepare
-        (status, &cn->transh, &stmth, 0, query, 1, out_sqlda)) {
-        isc_interprete(buffer, &statusp);
-        store_reset(out_sqlda);
-        out_sqlda = NULL;
-        *errmsg =
-            string_sprintf("Interbase prepare_statement() failed: %s",
-                           buffer);
-        *defer_break = FALSE;
-        goto IBASE_EXIT;
-    }
+/* Run the query */
+if (isc_dsql_allocate_statement(status, &cn->dbh, &stmth))
+  {
+  isc_interprete(buffer, &statusp);
+  *errmsg =
+      string_sprintf("Interbase alloc_statement() failed: %s",
+                    buffer);
+  *defer_break = FALSE;
+  goto IBASE_EXIT;
+  }
+
+out_sqlda = store_get(XSQLDA_LENGTH(1));
+out_sqlda->version = SQLDA_VERSION1;
+out_sqlda->sqln = 1;
+
+if (isc_dsql_prepare
+    (status, &cn->transh, &stmth, 0, query, 1, out_sqlda))
+  {
+  isc_interprete(buffer, &statusp);
+  store_reset(out_sqlda);
+  out_sqlda = NULL;
+  *errmsg =
+      string_sprintf("Interbase prepare_statement() failed: %s",
+                    buffer);
+  *defer_break = FALSE;
+  goto IBASE_EXIT;
+  }
 
 /* re-allocate the output structure if there's more than one field */
 
 /* re-allocate the output structure if there's more than one field */
-    if (out_sqlda->sqln < out_sqlda->sqld) {
-        XSQLDA *new_sqlda = store_get(XSQLDA_LENGTH(out_sqlda->sqld));
-        if (isc_dsql_describe
-            (status, &stmth, out_sqlda->version, new_sqlda)) {
-            isc_interprete(buffer, &statusp);
-            isc_dsql_free_statement(status, &stmth, DSQL_drop);
-            store_reset(out_sqlda);
-            out_sqlda = NULL;
-            *errmsg =
-                string_sprintf("Interbase describe_statement() failed: %s",
-                               buffer);
-            *defer_break = FALSE;
-            goto IBASE_EXIT;
-        }
-        out_sqlda = new_sqlda;
+if (out_sqlda->sqln < out_sqlda->sqld)
+  {
+  XSQLDA *new_sqlda = store_get(XSQLDA_LENGTH(out_sqlda->sqld));
+  if (isc_dsql_describe
+      (status, &stmth, out_sqlda->version, new_sqlda))
+    {
+    isc_interprete(buffer, &statusp);
+    isc_dsql_free_statement(status, &stmth, DSQL_drop);
+    store_reset(out_sqlda);
+    out_sqlda = NULL;
+    *errmsg = string_sprintf("Interbase describe_statement() failed: %s",
+                      buffer);
+    *defer_break = FALSE;
+    goto IBASE_EXIT;
     }
     }
+  out_sqlda = new_sqlda;
+  }
 
 /* allocate storage for every returned field */
 
 /* allocate storage for every returned field */
-    for (i = 0, var = out_sqlda->sqlvar; i < out_sqlda->sqld; i++, var++) {
-        switch (var->sqltype & ~1) {
-        case SQL_VARYING:
-            var->sqldata =
-                (char *) store_get(sizeof(char) * var->sqllen + 2);
-            break;
-        case SQL_TEXT:
-            var->sqldata =
-                (char *) store_get(sizeof(char) * var->sqllen);
-            break;
-        case SQL_SHORT:
-            var->sqldata = (char *) store_get(sizeof(short));
-            break;
-        case SQL_LONG:
-            var->sqldata = (char *) store_get(sizeof(ISC_LONG));
-            break;
+for (i = 0, var = out_sqlda->sqlvar; i < out_sqlda->sqld; i++, var++)
+  {
+  switch (var->sqltype & ~1)
+    {
+    case SQL_VARYING:
+       var->sqldata = CS store_get(sizeof(char) * var->sqllen + 2);
+       break;
+    case SQL_TEXT:
+       var->sqldata = CS store_get(sizeof(char) * var->sqllen);
+       break;
+    case SQL_SHORT:
+       var->sqldata = CS  store_get(sizeof(short));
+       break;
+    case SQL_LONG:
+       var->sqldata = CS  store_get(sizeof(ISC_LONG));
+       break;
 #ifdef SQL_INT64
 #ifdef SQL_INT64
-        case SQL_INT64:
-            var->sqldata = (char *) store_get(sizeof(ISC_INT64));
-            break;
+    case SQL_INT64:
+       var->sqldata = CS  store_get(sizeof(ISC_INT64));
+       break;
 #endif
 #endif
-        case SQL_FLOAT:
-            var->sqldata = (char *) store_get(sizeof(float));
-            break;
-        case SQL_DOUBLE:
-            var->sqldata = (char *) store_get(sizeof(double));
-            break;
+    case SQL_FLOAT:
+       var->sqldata = CS  store_get(sizeof(float));
+       break;
+    case SQL_DOUBLE:
+       var->sqldata = CS  store_get(sizeof(double));
+       break;
 #ifdef SQL_TIMESTAMP
 #ifdef SQL_TIMESTAMP
-        case SQL_DATE:
-            var->sqldata = (char *) store_get(sizeof(ISC_QUAD));
-            break;
+    case SQL_DATE:
+       var->sqldata = CS  store_get(sizeof(ISC_QUAD));
+       break;
 #else
 #else
-        case SQL_TIMESTAMP:
-            var->sqldata = (char *) store_get(sizeof(ISC_TIMESTAMP));
-            break;
-        case SQL_TYPE_DATE:
-            var->sqldata = (char *) store_get(sizeof(ISC_DATE));
-            break;
-        case SQL_TYPE_TIME:
-            var->sqldata = (char *) store_get(sizeof(ISC_TIME));
-            break;
-#endif
-        }
-        if (var->sqltype & 1) {
-            var->sqlind = (short *) store_get(sizeof(short));
-        }
+    case SQL_TIMESTAMP:
+       var->sqldata = CS  store_get(sizeof(ISC_TIMESTAMP));
+       break;
+    case SQL_TYPE_DATE:
+       var->sqldata = CS  store_get(sizeof(ISC_DATE));
+       break;
+    case SQL_TYPE_TIME:
+       var->sqldata = CS  store_get(sizeof(ISC_TIME));
+       break;
+  #endif
     }
     }
-
-    /* finally, we're ready to execute the statement */
-    if (isc_dsql_execute
-        (status, &cn->transh, &stmth, out_sqlda->version, NULL)) {
-        isc_interprete(buffer, &statusp);
-        *errmsg =
-            string_sprintf("Interbase describe_statement() failed: %s",
-                           buffer);
-        isc_dsql_free_statement(status, &stmth, DSQL_drop);
-        *defer_break = FALSE;
-        goto IBASE_EXIT;
+  if (var->sqltype & 1)
+    var->sqlind = (short *) store_get(sizeof(short));
+  }
+
+/* finally, we're ready to execute the statement */
+if (isc_dsql_execute
+    (status, &cn->transh, &stmth, out_sqlda->version, NULL))
+  {
+  isc_interprete(buffer, &statusp);
+  *errmsg = string_sprintf("Interbase describe_statement() failed: %s",
+                    buffer);
+  isc_dsql_free_statement(status, &stmth, DSQL_drop);
+  *defer_break = FALSE;
+  goto IBASE_EXIT;
+  }
+
+while (isc_dsql_fetch(status, &stmth, out_sqlda->version, out_sqlda) != 100L)
+  {
+  /* check if an error occurred */
+  if (status[0] & status[1])
+    {
+    isc_interprete(buffer, &statusp);
+    *errmsg =
+       string_sprintf("Interbase fetch() failed: %s", buffer);
+    isc_dsql_free_statement(status, &stmth, DSQL_drop);
+    *defer_break = FALSE;
+    goto IBASE_EXIT;
     }
 
     }
 
-    while (isc_dsql_fetch(status, &stmth, out_sqlda->version, out_sqlda) !=
-           100L) {
-        /* check if an error occurred */
-        if (status[0] & status[1]) {
-            isc_interprete(buffer, &statusp);
-            *errmsg =
-                string_sprintf("Interbase fetch() failed: %s", buffer);
-            isc_dsql_free_statement(status, &stmth, DSQL_drop);
-            *defer_break = FALSE;
-            goto IBASE_EXIT;
-        }
-
-        if (result != NULL)
-            result = string_cat(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,
-                               fetch_field(buffer, sizeof(buffer),
-                                           &out_sqlda->sqlvar[0]));
-        }
-
-        else
-            for (i = 0; i < out_sqlda->sqld; i++) {
-                int len = fetch_field(buffer, sizeof(buffer),
-                                      &out_sqlda->sqlvar[i]);
-
-                result =
-                    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);
-
-                /* 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);
-                }
-
-                else if (buffer[0] == 0 || Ustrchr(buffer, ' ') != NULL) {
-                    int j;
-                    result =
-                        string_cat(result, &ssize, &offset, US "\"", 1);
-                    for (j = 0; j < len; j++) {
-                        if (buffer[j] == '\"' || buffer[j] == '\\')
-                            result =
-                                string_cat(result, &ssize, &offset,
-                                           US "\\", 1);
-                        result =
-                            string_cat(result, &ssize, &offset,
-                                       US buffer + j, 1);
-                    }
-                    result =
-                        string_cat(result, &ssize, &offset, US "\"", 1);
-                } else {
-                    result =
-                        string_cat(result, &ssize, &offset, US buffer,
-                                   len);
-                }
-                result = string_cat(result, &ssize, &offset, US " ", 1);
-            }
+  if (result)
+    result = string_catn(result, 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_catn(result, US buffer,
+                      fetch_field(buffer, sizeof(buffer),
+                                  &out_sqlda->sqlvar[0]));
     }
 
     }
 
+  else
+    for (i = 0; i < out_sqlda->sqld; i++)
+      {
+      int len = fetch_field(buffer, sizeof(buffer), &out_sqlda->sqlvar[i]);
+
+      result = string_catn(result, US out_sqlda->sqlvar[i].aliasname,
+                    out_sqlda->sqlvar[i].aliasname_length);
+      result = string_catn(result, US "=", 1);
+
+      /* Quote the value if it contains spaces or is empty */
+
+      if (*out_sqlda->sqlvar[i].sqlind == -1)       /* NULL value */
+       result = string_catn(result, US "\"\"", 2);
+
+      else if (buffer[0] == 0 || Ustrchr(buffer, ' ') != NULL)
+       {
+       int j;
+
+       result = string_catn(result, US "\"", 1);
+       for (j = 0; j < len; j++)
+         {
+         if (buffer[j] == '\"' || buffer[j] == '\\')
+             result = string_cat(result, US "\\", 1);
+         result = string_cat(result, US buffer + j, 1);
+         }
+       result = string_catn(result, US "\"", 1);
+       }
+      else
+       result = string_catn(result, US buffer, len);
+      result = string_catn(result, US " ", 1);
+      }
+  }
+
 /* If result is NULL then no data has been found and so we return FAIL.
 Otherwise, we must terminate the string which has been built; string_cat()
 always leaves enough room for a terminating zero. */
 
 /* If result is NULL then no data has been found and so we return FAIL.
 Otherwise, we must terminate the string which has been built; string_cat()
 always leaves enough room for a terminating zero. */
 
-    if (result == NULL) {
-        yield = FAIL;
-        *errmsg = US "Interbase: no data found";
-    } else {
-        result[offset] = 0;
-        store_reset(result + offset + 1);
-    }
+if (!result)
+  {
+  yield = FAIL;
+  *errmsg = US "Interbase: no data found";
+  }
+else
+  store_reset(result->s + result->ptr + 1);
 
 
 /* Get here by goto from various error checks. */
 
 
 
 /* Get here by goto from various error checks. */
 
-  IBASE_EXIT:
+IBASE_EXIT:
 
 
-    if (stmth != NULL)
-        isc_dsql_free_statement(status, &stmth, DSQL_drop);
+if (stmth)
+  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;
-        return OK;
-    } else {
-        DEBUG(D_lookup) debug_printf("%s\n", *errmsg);
-        return yield;           /* FAIL or DEFER */
-    }
+if (result)
+  {
+  *resultptr = string_from_gstring(result);
+  return OK;
+  }
+else
+  {
+  DEBUG(D_lookup) debug_printf("%s\n", *errmsg);
+  return yield;           /* FAIL or DEFER */
+  }
 }
 
 
 }
 
 
@@ -451,7 +452,7 @@ deferred with a retryable error. */
 
 static int
 ibase_find(void *handle, uschar * filename, uschar * query, int length,
 
 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;
 {
     int sep = 0;
     uschar *server;
@@ -577,7 +578,7 @@ static lookup_info _lookup_info = {
 #ifdef DYNLOOKUP
 #define ibase_lookup_module_info _lookup_module_info
 #endif
 #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 };
 
 static lookup_info *_lookup_list[] = { &_lookup_info };
 lookup_module_info ibase_lookup_module_info = { LOOKUP_MODULE_INFO_MAGIC, _lookup_list, 1 };
 
index f77229d..63c0edf 100644 (file)
@@ -2,11 +2,11 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Many thanks to Stuart Lynne for contributing the original code for this
 /* 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. */
 
 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
 */
 
 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;
 {
 LDAPURLDesc     *ludp = NULL;
 LDAPMessage     *result = NULL;
@@ -144,7 +145,7 @@ struct timeval *timeoutptr = NULL;
 
 uschar *attr;
 uschar **attrp;
 
 uschar *attr;
 uschar **attrp;
-uschar *data = NULL;
+gstring * data = NULL;
 uschar *dn = NULL;
 uschar *host;
 uschar **values;
 uschar *dn = NULL;
 uschar *host;
 uschar **values;
@@ -155,23 +156,21 @@ uschar *error1 = NULL;   /* string representation of errcode (static) */
 uschar *error2 = NULL;   /* error message from the server */
 uschar *matched = NULL;  /* partially matched DN */
 
 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;
 int    port;
 int    error_yield = DEFER;
 int    msgid;
 int    rc, ldap_rc, ldap_parse_rc;
 int    port;
-int    ptr = 0;
 int    rescount = 0;
 int    rescount = 0;
-int    size = 0;
 BOOL   attribute_found = FALSE;
 BOOL   ldapi = FALSE;
 
 DEBUG(D_lookup)
   debug_printf("perform_ldap_search: ldap%s URL = \"%s\" server=%s port=%d "
     "sizelimit=%d timelimit=%d tcplimit=%d\n",
 BOOL   attribute_found = FALSE;
 BOOL   ldapi = FALSE;
 
 DEBUG(D_lookup)
   debug_printf("perform_ldap_search: ldap%s URL = \"%s\" server=%s port=%d "
     "sizelimit=%d timelimit=%d tcplimit=%d\n",
-    (search_type == SEARCH_LDAP_MULTIPLE)? "m" :
-    (search_type == SEARCH_LDAP_DN)? "dn" :
-    (search_type == SEARCH_LDAP_AUTH)? "auth" : "",
+    search_type == SEARCH_LDAP_MULTIPLE ? "m" :
+    search_type == SEARCH_LDAP_DN       ? "dn" :
+    search_type == SEARCH_LDAP_AUTH     ? "auth" : "",
     ldap_url, server, s_port, sizelimit, timelimit, tcplimit);
 
 /* Check if LDAP thinks the URL is a valid LDAP URL. We assume that if the LDAP
     ldap_url, server, s_port, sizelimit, timelimit, tcplimit);
 
 /* Check if LDAP thinks the URL is a valid LDAP URL. We assume that if the LDAP
@@ -198,7 +197,7 @@ given. OpenLDAP 2.0.6 sets an unset hostname to "" rather than empty, but
 expects NULL later in ldap_init() to mean "default", annoyingly. In OpenLDAP
 2.0.11 this has changed (it uses NULL). */
 
 expects NULL later in ldap_init() to mean "default", annoyingly. In OpenLDAP
 2.0.11 this has changed (it uses NULL). */
 
-if ((ludp->lud_host == NULL || ludp->lud_host[0] == 0) && server != NULL)
+if ((!ludp->lud_host || !ludp->lud_host[0]) && server)
   {
   host = server;
   port = s_port;
   {
   host = server;
   port = s_port;
@@ -206,7 +205,7 @@ if ((ludp->lud_host == NULL || ludp->lud_host[0] == 0) && server != NULL)
 else
   {
   host = US ludp->lud_host;
 else
   {
   host = US ludp->lud_host;
-  if (host != NULL && host[0] == 0) host = NULL;
+  if (host && !host[0]) host = NULL;
   port = ludp->lud_port;
   }
 
   port = ludp->lud_port;
   }
 
@@ -226,7 +225,7 @@ first time.) If the host name is not a path, the use of "ldapi" causes an
 error, except in the default case. (But lud_scheme doesn't seem to exist in
 older libraries.) */
 
 error, except in the default case. (But lud_scheme doesn't seem to exist in
 older libraries.) */
 
-if (host != NULL)
+if (host)
   {
   if ((host[0] == '/' || Ustrncmp(host, "%2F", 3) == 0))
     {
   {
   if ((host[0] == '/' || Ustrncmp(host, "%2F", 3) == 0))
     {
@@ -234,27 +233,27 @@ if (host != NULL)
     porttext[0] = 0;    /* Remove port from messages */
     }
 
     porttext[0] = 0;    /* Remove port from messages */
     }
 
-  #if defined LDAP_LIB_OPENLDAP2
+#if defined LDAP_LIB_OPENLDAP2
   else if (strncmp(ludp->lud_scheme, "ldapi", 5) == 0)
     {
     *errmsg = string_sprintf("ldapi requires an absolute path (\"%s\" given)",
       host);
     goto RETURN_ERROR;
     }
   else if (strncmp(ludp->lud_scheme, "ldapi", 5) == 0)
     {
     *errmsg = string_sprintf("ldapi requires an absolute path (\"%s\" given)",
       host);
     goto RETURN_ERROR;
     }
-  #endif
+#endif
   }
 
 /* Count the attributes; we need this later to tell us how to format results */
 
   }
 
 /* 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++;
+for (attrp = USS ludp->lud_attrs; attrp && *attrp; attrp++)
+  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
 (implying the library default), rather than to the empty string. Note that in
 this case, there is no difference between ldap and ldapi. */
 
 
 /* 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
 (implying the library default), rather than to the empty string. Note that in
 this case, there is no difference between ldap and ldapi. */
 
-for (lcp = ldap_connections; lcp != NULL; lcp = lcp->next)
+for (lcp = ldap_connections; lcp; lcp = lcp->next)
   {
   if ((host == NULL) != (lcp->host == NULL) ||
       (host != NULL && strcmpic(lcp->host, host) != 0))
   {
   if ((host == NULL) != (lcp->host == NULL) ||
       (host != NULL && strcmpic(lcp->host, host) != 0))
@@ -276,16 +275,16 @@ the server name is actually an absolute path, we set ldapi=TRUE above. This
 requests connection via a Unix socket. However, as far as I know, only OpenLDAP
 supports the use of sockets, and the use of ldap_initialize(). */
 
 requests connection via a Unix socket. However, as far as I know, only OpenLDAP
 supports the use of sockets, and the use of ldap_initialize(). */
 
-if (lcp == NULL)
+if (!lcp)
   {
   LDAP *ld;
 
   {
   LDAP *ld;
 
-  #ifdef LDAP_OPT_X_TLS_NEWCTX
+#ifdef LDAP_OPT_X_TLS_NEWCTX
   int  am_server = 0;
   LDAP *ldsetctx;
   int  am_server = 0;
   LDAP *ldsetctx;
-  #else
+#else
   LDAP *ldsetctx = NULL;
   LDAP *ldsetctx = NULL;
-  #endif
+#endif
 
 
   /* --------------------------- OpenLDAP ------------------------ */
 
 
   /* --------------------------- OpenLDAP ------------------------ */
@@ -296,7 +295,7 @@ if (lcp == NULL)
   non-existent). So we handle OpenLDAP differently here. Also, support for
   ldapi seems to be OpenLDAP-only at present. */
 
   non-existent). So we handle OpenLDAP differently here. Also, support for
   ldapi seems to be OpenLDAP-only at present. */
 
-  #ifdef LDAP_LIB_OPENLDAP2
+#ifdef LDAP_LIB_OPENLDAP2
 
   /* We now need an empty string for the default host. Get some store in which
   to build a URL for ldap_initialize(). In the ldapi case, it can't be bigger
 
   /* We now need an empty string for the default host. Get some store in which
   to build a URL for ldap_initialize(). In the ldapi case, it can't be bigger
@@ -316,15 +315,11 @@ if (lcp == NULL)
     int ch;
     init_ptr = init_url + 8;
     Ustrcpy(init_url, "ldapi://");
     int ch;
     init_ptr = init_url + 8;
     Ustrcpy(init_url, "ldapi://");
-    while ((ch = *shost++) != 0)
-      {
+    while ((ch = *shost++))
       if (ch == '/')
       if (ch == '/')
-        {
-        Ustrncpy(init_ptr, "%2F", 3);
-        init_ptr += 3;
-        }
-      else *init_ptr++ = ch;
-      }
+       { Ustrncpy(init_ptr, "%2F", 3); init_ptr += 3; }
+      else
+       *init_ptr++ = ch;
     *init_ptr = 0;
     }
 
     *init_ptr = 0;
     }
 
@@ -342,8 +337,7 @@ if (lcp == NULL)
   /* Call ldap_initialize() and check the result */
 
   DEBUG(D_lookup) debug_printf("ldap_initialize with URL %s\n", init_url);
   /* Call ldap_initialize() and check the result */
 
   DEBUG(D_lookup) debug_printf("ldap_initialize with URL %s\n", init_url);
-  rc = ldap_initialize(&ld, CS init_url);
-  if (rc != LDAP_SUCCESS)
+  if ((rc = ldap_initialize(&ld, CS init_url)) != LDAP_SUCCESS)
     {
     *errmsg = string_sprintf("ldap_initialize: (error %d) URL \"%s\"\n",
       rc, init_url);
     {
     *errmsg = string_sprintf("ldap_initialize: (error %d) URL \"%s\"\n",
       rc, init_url);
@@ -356,30 +350,30 @@ if (lcp == NULL)
 
   /* For libraries other than OpenLDAP, use ldap_init(). */
 
 
   /* For libraries other than OpenLDAP, use ldap_init(). */
 
-  #else   /* LDAP_LIB_OPENLDAP2 */
+#else   /* LDAP_LIB_OPENLDAP2 */
   ld = ldap_init(CS host, port);
   ld = ldap_init(CS host, port);
-  #endif  /* LDAP_LIB_OPENLDAP2 */
+#endif  /* LDAP_LIB_OPENLDAP2 */
 
   /* -------------------------------------------------------------- */
 
 
   /* Handle failure to initialize */
 
 
   /* -------------------------------------------------------------- */
 
 
   /* Handle failure to initialize */
 
-  if (ld == NULL)
+  if (!ld)
     {
     *errmsg = string_sprintf("failed to initialize for LDAP server %s%s - %s",
       host, porttext, strerror(errno));
     goto RETURN_ERROR;
     }
 
     {
     *errmsg = string_sprintf("failed to initialize for LDAP server %s%s - %s",
       host, porttext, strerror(errno));
     goto RETURN_ERROR;
     }
 
-  #ifdef LDAP_OPT_X_TLS_NEWCTX
+#ifdef LDAP_OPT_X_TLS_NEWCTX
   ldsetctx = ld;
   ldsetctx = ld;
-  #endif
+#endif
 
   /* Set the TCP connect time limit if available. This is something that is
   in Netscape SDK v4.1; I don't know about other libraries. */
 
 
   /* Set the TCP connect time limit if available. This is something that is
   in Netscape SDK v4.1; I don't know about other libraries. */
 
-  #ifdef LDAP_X_OPT_CONNECT_TIMEOUT
+#ifdef LDAP_X_OPT_CONNECT_TIMEOUT
   if (tcplimit > 0)
     {
     int timeout1000 = tcplimit*1000;
   if (tcplimit > 0)
     {
     int timeout1000 = tcplimit*1000;
@@ -390,14 +384,14 @@ if (lcp == NULL)
     int notimeout = LDAP_X_IO_TIMEOUT_NO_TIMEOUT;
     ldap_set_option(ld, LDAP_X_OPT_CONNECT_TIMEOUT, (void *)&notimeout);
     }
     int notimeout = LDAP_X_IO_TIMEOUT_NO_TIMEOUT;
     ldap_set_option(ld, LDAP_X_OPT_CONNECT_TIMEOUT, (void *)&notimeout);
     }
-  #endif
+#endif
 
   /* Set the TCP connect timeout. This works with OpenLDAP 2.2.14. */
 
 
   /* Set the TCP connect timeout. This works with OpenLDAP 2.2.14. */
 
-  #ifdef LDAP_OPT_NETWORK_TIMEOUT
+#ifdef LDAP_OPT_NETWORK_TIMEOUT
   if (tcplimit > 0)
     ldap_set_option(ld, LDAP_OPT_NETWORK_TIMEOUT, (void *)timeoutptr);
   if (tcplimit > 0)
     ldap_set_option(ld, LDAP_OPT_NETWORK_TIMEOUT, (void *)timeoutptr);
-  #endif
+#endif
 
   /* I could not get TLS to work until I set the version to 3. That version
   seems to be the default nowadays. The RFC is dated 1997, so I would hope
 
   /* I could not get TLS to work until I set the version to 3. That version
   seems to be the default nowadays. The RFC is dated 1997, so I would hope
@@ -406,16 +400,16 @@ if (lcp == NULL)
 
   if (eldap_version < 0)
     {
 
   if (eldap_version < 0)
     {
-    #ifdef LDAP_VERSION3
+#ifdef LDAP_VERSION3
     eldap_version = LDAP_VERSION3;
     eldap_version = LDAP_VERSION3;
-    #else
+#else
     eldap_version = 2;
     eldap_version = 2;
-    #endif
+#endif
     }
 
     }
 
-  #ifdef LDAP_OPT_PROTOCOL_VERSION
+#ifdef LDAP_OPT_PROTOCOL_VERSION
   ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, (void *)&eldap_version);
   ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, (void *)&eldap_version);
-  #endif
+#endif
 
   DEBUG(D_lookup) debug_printf("initialized for LDAP (v%d) server %s%s\n",
     eldap_version, host, porttext);
 
   DEBUG(D_lookup) debug_printf("initialized for LDAP (v%d) server %s%s\n",
     eldap_version, host, porttext);
@@ -423,36 +417,26 @@ if (lcp == NULL)
   /* If not using ldapi and TLS is available, set appropriate TLS options: hard
   for "ldaps" and soft otherwise. */
 
   /* If not using ldapi and TLS is available, set appropriate TLS options: hard
   for "ldaps" and soft otherwise. */
 
-  #ifdef LDAP_OPT_X_TLS
+#ifdef LDAP_OPT_X_TLS
   if (!ldapi)
     {
     int tls_option;
   if (!ldapi)
     {
     int tls_option;
-    #ifdef LDAP_OPT_X_TLS_REQUIRE_CERT
-    if (eldap_require_cert != NULL)
+ifdef LDAP_OPT_X_TLS_REQUIRE_CERT
+    if (eldap_require_cert)
       {
       {
-      tls_option = LDAP_OPT_X_TLS_NEVER;
-      if (Ustrcmp(eldap_require_cert, "hard") == 0)
-        {
-        tls_option = LDAP_OPT_X_TLS_HARD;
-        }
-      else if (Ustrcmp(eldap_require_cert, "demand") == 0)
-        {
-        tls_option = LDAP_OPT_X_TLS_DEMAND;
-        }
-      else if (Ustrcmp(eldap_require_cert, "allow") == 0)
-        {
-        tls_option = LDAP_OPT_X_TLS_ALLOW;
-        }
-      else if (Ustrcmp(eldap_require_cert, "try") == 0)
-        {
-        tls_option = LDAP_OPT_X_TLS_TRY;
-        }
+      tls_option =
+       Ustrcmp(eldap_require_cert, "hard")     == 0 ? LDAP_OPT_X_TLS_HARD
+       : Ustrcmp(eldap_require_cert, "demand") == 0 ? LDAP_OPT_X_TLS_DEMAND
+       : Ustrcmp(eldap_require_cert, "allow")  == 0 ? LDAP_OPT_X_TLS_ALLOW
+       : Ustrcmp(eldap_require_cert, "try")    == 0 ? LDAP_OPT_X_TLS_TRY
+       : LDAP_OPT_X_TLS_NEVER;
+
       DEBUG(D_lookup)
         debug_printf("Require certificate overrides LDAP_OPT_X_TLS option (%d)\n",
                      tls_option);
       }
     else
       DEBUG(D_lookup)
         debug_printf("Require certificate overrides LDAP_OPT_X_TLS option (%d)\n",
                      tls_option);
       }
     else
-    #endif  /* LDAP_OPT_X_TLS_REQUIRE_CERT */
+endif  /* LDAP_OPT_X_TLS_REQUIRE_CERT */
     if (strncmp(ludp->lud_scheme, "ldaps", 5) == 0)
       {
       tls_option = LDAP_OPT_X_TLS_HARD;
     if (strncmp(ludp->lud_scheme, "ldaps", 5) == 0)
       {
       tls_option = LDAP_OPT_X_TLS_HARD;
@@ -467,79 +451,54 @@ if (lcp == NULL)
       }
     ldap_set_option(ld, LDAP_OPT_X_TLS, (void *)&tls_option);
     }
       }
     ldap_set_option(ld, LDAP_OPT_X_TLS, (void *)&tls_option);
     }
-  #endif  /* LDAP_OPT_X_TLS */
+#endif  /* LDAP_OPT_X_TLS */
 
 
-  #ifdef LDAP_OPT_X_TLS_CACERTFILE
-  if (eldap_ca_cert_file != NULL)
-    {
+#ifdef LDAP_OPT_X_TLS_CACERTFILE
+  if (eldap_ca_cert_file)
     ldap_set_option(ldsetctx, LDAP_OPT_X_TLS_CACERTFILE, eldap_ca_cert_file);
     ldap_set_option(ldsetctx, LDAP_OPT_X_TLS_CACERTFILE, eldap_ca_cert_file);
-    }
-  #endif
-  #ifdef LDAP_OPT_X_TLS_CACERTDIR
-  if (eldap_ca_cert_dir != NULL)
-    {
+#endif
+#ifdef LDAP_OPT_X_TLS_CACERTDIR
+  if (eldap_ca_cert_dir)
     ldap_set_option(ldsetctx, LDAP_OPT_X_TLS_CACERTDIR, eldap_ca_cert_dir);
     ldap_set_option(ldsetctx, LDAP_OPT_X_TLS_CACERTDIR, eldap_ca_cert_dir);
-    }
-  #endif
-  #ifdef LDAP_OPT_X_TLS_CERTFILE
-  if (eldap_cert_file != NULL)
-    {
+#endif
+#ifdef LDAP_OPT_X_TLS_CERTFILE
+  if (eldap_cert_file)
     ldap_set_option(ldsetctx, LDAP_OPT_X_TLS_CERTFILE, eldap_cert_file);
     ldap_set_option(ldsetctx, LDAP_OPT_X_TLS_CERTFILE, eldap_cert_file);
-    }
-  #endif
-  #ifdef LDAP_OPT_X_TLS_KEYFILE
-  if (eldap_cert_key != NULL)
-    {
+#endif
+#ifdef LDAP_OPT_X_TLS_KEYFILE
+  if (eldap_cert_key)
     ldap_set_option(ldsetctx, LDAP_OPT_X_TLS_KEYFILE, eldap_cert_key);
     ldap_set_option(ldsetctx, LDAP_OPT_X_TLS_KEYFILE, eldap_cert_key);
-    }
-  #endif
-  #ifdef LDAP_OPT_X_TLS_CIPHER_SUITE
-  if (eldap_cipher_suite != NULL)
-    {
+#endif
+#ifdef LDAP_OPT_X_TLS_CIPHER_SUITE
+  if (eldap_cipher_suite)
     ldap_set_option(ldsetctx, LDAP_OPT_X_TLS_CIPHER_SUITE, eldap_cipher_suite);
     ldap_set_option(ldsetctx, LDAP_OPT_X_TLS_CIPHER_SUITE, eldap_cipher_suite);
-    }
-  #endif
-  #ifdef LDAP_OPT_X_TLS_REQUIRE_CERT
-  if (eldap_require_cert != NULL)
+#endif
+#ifdef LDAP_OPT_X_TLS_REQUIRE_CERT
+  if (eldap_require_cert)
     {
     {
-    int cert_option = LDAP_OPT_X_TLS_NEVER;
-    if (Ustrcmp(eldap_require_cert, "hard") == 0)
-      {
-      cert_option = LDAP_OPT_X_TLS_HARD;
-      }
-    else if (Ustrcmp(eldap_require_cert, "demand") == 0)
-      {
-      cert_option = LDAP_OPT_X_TLS_DEMAND;
-      }
-    else if (Ustrcmp(eldap_require_cert, "allow") == 0)
-      {
-      cert_option = LDAP_OPT_X_TLS_ALLOW;
-      }
-    else if (Ustrcmp(eldap_require_cert, "try") == 0)
-      {
-      cert_option = LDAP_OPT_X_TLS_TRY;
-      }
+    int cert_option =
+      Ustrcmp(eldap_require_cert, "hard")     == 0 ? LDAP_OPT_X_TLS_HARD
+      : Ustrcmp(eldap_require_cert, "demand") == 0 ? LDAP_OPT_X_TLS_DEMAND
+      : Ustrcmp(eldap_require_cert, "allow")  == 0 ? LDAP_OPT_X_TLS_ALLOW
+      : Ustrcmp(eldap_require_cert, "try")    == 0 ? LDAP_OPT_X_TLS_TRY
+      : LDAP_OPT_X_TLS_NEVER;
+
     /* This ldap handle is set at compile time based on client libs. Older
      * versions want it to be global and newer versions can force a reload
      * of the TLS context (to reload these settings we are changing from the
      * default that loaded at instantiation). */
     rc = ldap_set_option(ldsetctx, LDAP_OPT_X_TLS_REQUIRE_CERT, &cert_option);
     if (rc)
     /* This ldap handle is set at compile time based on client libs. Older
      * versions want it to be global and newer versions can force a reload
      * of the TLS context (to reload these settings we are changing from the
      * default that loaded at instantiation). */
     rc = ldap_set_option(ldsetctx, LDAP_OPT_X_TLS_REQUIRE_CERT, &cert_option);
     if (rc)
-      {
       DEBUG(D_lookup)
         debug_printf("Unable to set TLS require cert_option(%d) globally: %s\n",
           cert_option, ldap_err2string(rc));
       DEBUG(D_lookup)
         debug_printf("Unable to set TLS require cert_option(%d) globally: %s\n",
           cert_option, ldap_err2string(rc));
-      }
     }
     }
-  #endif
-  #ifdef LDAP_OPT_X_TLS_NEWCTX
-  rc = ldap_set_option(ldsetctx, LDAP_OPT_X_TLS_NEWCTX, &am_server);
-  if (rc)
-    {
+#endif
+#ifdef LDAP_OPT_X_TLS_NEWCTX
+  if ((rc = ldap_set_option(ldsetctx, LDAP_OPT_X_TLS_NEWCTX, &am_server)))
     DEBUG(D_lookup)
       debug_printf("Unable to reload TLS context %d: %s\n",
                    rc, ldap_err2string(rc));
     DEBUG(D_lookup)
       debug_printf("Unable to reload TLS context %d: %s\n",
                    rc, ldap_err2string(rc));
-    }
   #endif
 
   /* Now add this connection to the chain of cached connections */
   #endif
 
   /* Now add this connection to the chain of cached connections */
@@ -559,27 +518,26 @@ if (lcp == NULL)
 /* Found cached connection */
 
 else
 /* Found cached connection */
 
 else
-  {
   DEBUG(D_lookup)
     debug_printf("re-using cached connection to LDAP server %s%s\n",
       host, porttext);
   DEBUG(D_lookup)
     debug_printf("re-using cached connection to LDAP server %s%s\n",
       host, porttext);
-  }
 
 /* Bind with the user/password supplied, or an anonymous bind if these values
 are NULL, unless a cached connection is already bound with the same values. */
 
 
 /* Bind with the user/password supplied, or an anonymous bind if these values
 are NULL, unless a cached connection is already bound with the same values. */
 
-if (!lcp->bound ||
-    (lcp->user == NULL && user != NULL) ||
-    (lcp->user != NULL && user == NULL) ||
-    (lcp->user != NULL && user != NULL && Ustrcmp(lcp->user, user) != 0) ||
-    (lcp->password == NULL && password != NULL) ||
-    (lcp->password != NULL && password == NULL) ||
-    (lcp->password != NULL && password != NULL &&
-      Ustrcmp(lcp->password, password) != 0))
+if (  !lcp->bound
+   || !lcp->user && user
+   || lcp->user && !user
+   || lcp->user && user && Ustrcmp(lcp->user, user) != 0
+   || !lcp->password && password
+   || lcp->password && !password
+   || lcp->password && password && Ustrcmp(lcp->password, password) != 0
+   )
   {
   DEBUG(D_lookup) debug_printf("%sbinding with user=%s password=%s\n",
   {
   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)
+    lcp->bound ? "re-" : "", user, password);
+
+  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.
     {
 #if defined(LDAP_OPT_X_TLS) && !defined(LDAP_LIB_SOLARIS)
     /* The Oracle LDAP libraries (LDAP_LIB_TYPE=SOLARIS) don't support this.
@@ -595,8 +553,8 @@ if (!lcp->bound ||
       }
     lcp->is_start_tls_called = TRUE;
 #else
       }
     lcp->is_start_tls_called = TRUE;
 #else
-    DEBUG(D_lookup)
-      debug_printf("TLS initiation not supported with this Exim and your LDAP library.\n");
+    DEBUG(D_lookup) debug_printf("TLS initiation not supported with this Exim"
+      " and your LDAP library.\n");
 #endif
     }
   if ((msgid = ldap_bind(lcp->ld, CS user, CS password, LDAP_AUTH_SIMPLE))
 #endif
     }
   if ((msgid = ldap_bind(lcp->ld, CS user, CS password, LDAP_AUTH_SIMPLE))
@@ -607,7 +565,7 @@ if (!lcp->bound ||
     goto RETURN_ERROR;
     }
 
     goto RETURN_ERROR;
     }
 
-  if ((rc = ldap_result( lcp->ld, msgid, 1, timeoutptr, &result )) <= 0)
+  if ((rc = ldap_result(lcp->ld, msgid, 1, timeoutptr, &result)) <= 0)
     {
     *errmsg = string_sprintf("failed to bind the LDAP connection to server "
       "%s%s - LDAP error: %s", host, porttext,
     {
     *errmsg = string_sprintf("failed to bind the LDAP connection to server "
       "%s%s - LDAP error: %s", host, porttext,
@@ -616,7 +574,7 @@ if (!lcp->bound ||
     goto RETURN_ERROR;
     }
 
     goto RETURN_ERROR;
     }
 
-  rc = ldap_result2error( lcp->ld, result, 0 );
+  rc = ldap_result2error(lcp->ld, result, 0);
 
   /* Invalid credentials when just checking credentials returns FAIL. This
   stops any further servers being tried. */
 
   /* Invalid credentials when just checking credentials returns FAIL. This
   stops any further servers being tried. */
@@ -642,8 +600,8 @@ if (!lcp->bound ||
   /* Successful bind */
 
   lcp->bound = TRUE;
   /* Successful bind */
 
   lcp->bound = TRUE;
-  lcp->user = (user == NULL)? NULL : string_copy(user);
-  lcp->password = (password == NULL)? NULL : string_copy(password);
+  lcp->user = !user ? NULL : string_copy(user);
+  lcp->password = !password ? NULL : string_copy(password);
 
   ldap_msgfree(result);
   result = NULL;
 
   ldap_msgfree(result);
   result = NULL;
@@ -692,15 +650,14 @@ msgid = ldap_search(lcp->ld, ludp->lud_dn, ludp->lud_scope, ludp->lud_filter,
 
 if (msgid == -1)
   {
 
 if (msgid == -1)
   {
-  #if defined LDAP_LIB_SOLARIS || defined LDAP_LIB_OPENLDAP2
+#if defined LDAP_LIB_SOLARIS || defined LDAP_LIB_OPENLDAP2
   int err;
   ldap_get_option(lcp->ld, LDAP_OPT_ERROR_NUMBER, &err);
   *errmsg = string_sprintf("ldap_search failed: %d, %s", err,
     ldap_err2string(err));
   int err;
   ldap_get_option(lcp->ld, LDAP_OPT_ERROR_NUMBER, &err);
   *errmsg = string_sprintf("ldap_search failed: %d, %s", err,
     ldap_err2string(err));
-
-  #else
+#else
   *errmsg = string_sprintf("ldap_search failed");
   *errmsg = string_sprintf("ldap_search failed");
-  #endif
+#endif
 
   goto RETURN_ERROR;
   }
 
   goto RETURN_ERROR;
   }
@@ -712,11 +669,16 @@ while ((rc = ldap_result(lcp->ld, msgid, 0, timeoutptr, &result)) ==
         LDAP_RES_SEARCH_ENTRY)
   {
   LDAPMessage  *e;
         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);
-      e != NULL;
+  for(e = ldap_first_entry(lcp->ld, result), valuecount = 0;
+      e;
       e = ldap_next_entry(lcp->ld, e))
     {
     uschar *new_dn;
       e = ldap_next_entry(lcp->ld, e))
     {
     uschar *new_dn;
@@ -728,20 +690,19 @@ while ((rc = ldap_result(lcp->ld, msgid, 0, timeoutptr, &result)) ==
 
     /* Results for multiple entries values are separated by newlines. */
 
 
     /* Results for multiple entries values are separated by newlines. */
 
-    if (data != NULL) data = string_cat(data, &size, &ptr, US"\n", 1);
+    if (data) data = string_catn(data, US"\n", 1);
 
     /* Get the DN from the last result. */
 
 
     /* Get the DN from the last result. */
 
-    new_dn = US ldap_get_dn(lcp->ld, e);
-    if (new_dn != NULL)
+    if ((new_dn = US ldap_get_dn(lcp->ld, e)))
       {
       {
-      if (dn != NULL)
+      if (dn)
         {
         {
-        #if defined LDAP_LIB_NETSCAPE || defined LDAP_LIB_OPENLDAP2
+#if defined LDAP_LIB_NETSCAPE || defined LDAP_LIB_OPENLDAP2
         ldap_memfree(dn);
         ldap_memfree(dn);
-        #else   /* OPENLDAP 1, UMich, Solaris */
+#else   /* OPENLDAP 1, UMich, Solaris */
         free(dn);
         free(dn);
-        #endif
+#endif
         }
       /* Save for later */
       dn = new_dn;
         }
       /* Save for later */
       dn = new_dn;
@@ -752,71 +713,82 @@ while ((rc = ldap_result(lcp->ld, msgid, 0, timeoutptr, &result)) ==
     entries, the DNs will be concatenated, but we test for this case below, as
     for SEARCH_LDAP_SINGLE, and give an error. */
 
     entries, the DNs will be concatenated, but we test for this case below, as
     for SEARCH_LDAP_SINGLE, and give an error. */
 
-    if (search_type == SEARCH_LDAP_DN)   /* Do not amalgamate these into one */
-      {                                  /* condition, because of the else */
-      if (new_dn != NULL)                /* below, that's for the first only */
+    if (search_type == SEARCH_LDAP_DN) /* Do not amalgamate these into one */
+      {                                        /* condition, because of the else */
+      if (new_dn)                      /* below, that's for the first only */
         {
         {
-        data = string_cat(data, &size, &ptr, new_dn, Ustrlen(new_dn));
-        data[ptr] = 0;
+        data = string_cat(data, new_dn);
+       (void) string_from_gstring(data);
         attribute_found = TRUE;
         }
       }
 
     /* Otherwise, loop through the entry, grabbing attribute values. If there's
     only one attribute being retrieved, no attribute name is given, and the
         attribute_found = TRUE;
         }
       }
 
     /* 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
     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);
 
     else for (attr = US ldap_first_attribute(lcp->ld, e, &ber);
-              attr != NULL;
-              attr = US ldap_next_attribute(lcp->ld, e, ber))
+              attr; 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. */
 
       if (attr[0] != 0)
         {
         /* Get array of values for this attribute. */
 
-        if ((firstval = values = USS ldap_get_values(lcp->ld, e, CS attr))
-             != NULL)
+        if ((firstval = values = USS ldap_get_values(lcp->ld, e, CS attr)))
           {
           {
-          if (attr_count != 1)
+          if (attrs_requested != 1)
             {
             if (insert_space)
             {
             if (insert_space)
-              data = string_cat(data, &size, &ptr, US" ", 1);
+              data = string_catn(data, US" ", 1);
             else
               insert_space = TRUE;
             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, attr);
+            data = string_catn(data, US"=\"", 2);
             }
 
             }
 
-          while (*values != NULL)
+          while (*values)
             {
             uschar *value = *values;
             int len = Ustrlen(value);
             {
             uschar *value = *values;
             int len = Ustrlen(value);
+            ++valuecount;
 
 
-            DEBUG(D_lookup) debug_printf("LDAP attr loop %s:%s\n", attr, value);
+            DEBUG(D_lookup) debug_printf("LDAP value loop %s:%s\n", attr, value);
 
 
-            if (values != firstval)
-              data = string_cat(data, &size, &ptr, US",", 1);
+            /* 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 (data && valuecount > 1)
+              data = string_catn(data, US",", 1);
 
             /* For multiple attributes, the data is in quotes. We must escape
             internal quotes, backslashes, newlines, and must double commas. */
 
 
             /* 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')
               {
               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, US"\\n", 2);
                 else if (value[j] == ',')
                 else if (value[j] == ',')
-                  data = string_cat(data, &size, &ptr, US",,", 2);
+                  data = string_catn(data, US",,", 2);
                 else
                   {
                   if (value[j] == '\"' || value[j] == '\\')
                 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, US"\\", 1);
+                  data = string_catn(data, value+j, 1);
                   }
                 }
               }
                   }
                 }
               }
@@ -827,12 +799,10 @@ while ((rc = ldap_result(lcp->ld, msgid, 0, timeoutptr, &result)) ==
              {
              int j;
              for (j = 0; j < len; j++)
              {
              int j;
              for (j = 0; j < len; j++)
-               {
                if (value[j] == ',')
                if (value[j] == ',')
-                 data = string_cat(data, &size, &ptr, US",,", 2);
+                 data = string_catn(data, US",,", 2);
                else
                else
-                 data = string_cat(data, &size, &ptr, value+j, 1);
-               }
+                 data = string_catn(data, value+j, 1);
              }
 
 
              }
 
 
@@ -844,8 +814,8 @@ while ((rc = ldap_result(lcp->ld, msgid, 0, timeoutptr, &result)) ==
 
           /* Closing quote at the end of the data for a named attribute. */
 
 
           /* 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, US"\"", 1);
 
           /* Free the values */
 
 
           /* Free the values */
 
@@ -853,14 +823,14 @@ while ((rc = ldap_result(lcp->ld, msgid, 0, timeoutptr, &result)) ==
           }
         }
 
           }
         }
 
-      #if defined LDAP_LIB_NETSCAPE || defined LDAP_LIB_OPENLDAP2
+#if defined LDAP_LIB_NETSCAPE || defined LDAP_LIB_OPENLDAP2
 
       /* Netscape and OpenLDAP2 LDAP's attrs are dynamically allocated and need
       to be freed. UMich LDAP stores them in static storage and does not require
       this. */
 
       ldap_memfree(attr);
 
       /* Netscape and OpenLDAP2 LDAP's attrs are dynamically allocated and need
       to be freed. UMich LDAP stores them in static storage and does not require
       this. */
 
       ldap_memfree(attr);
-      #endif
+#endif
       }        /* End "for" loop for extracting attributes from an entry */
     }          /* End "for" loop for extracting entries from a result */
 
       }        /* End "for" loop for extracting attributes from an entry */
     }          /* End "for" loop for extracting entries from a result */
 
@@ -870,24 +840,24 @@ while ((rc = ldap_result(lcp->ld, msgid, 0, timeoutptr, &result)) ==
   result = NULL;
   }            /* End "while" loop for multiple results */
 
   result = NULL;
   }            /* End "while" loop for multiple results */
 
-/* Terminate the dynamic string that we have built and reclaim unused store */
+/* Terminate the dynamic string that we have built and reclaim unused store.
+In the odd case of a single attribute with zero-length value, allocate
+an empty string. */
 
 
-if (data != NULL)
-  {
-  data[ptr] = 0;
-  store_reset(data + ptr + 1);
-  }
+if (!data) data = string_get(1);
+(void) string_from_gstring(data);
+gstring_reset_unused(data);
 
 /* Copy the last dn into eldap_dn */
 
 
 /* Copy the last dn into eldap_dn */
 
-if (dn != NULL)
+if (dn)
   {
   eldap_dn = string_copy(dn);
   {
   eldap_dn = string_copy(dn);
-  #if defined LDAP_LIB_NETSCAPE || defined LDAP_LIB_OPENLDAP2
+#if defined LDAP_LIB_NETSCAPE || defined LDAP_LIB_OPENLDAP2
   ldap_memfree(dn);
   ldap_memfree(dn);
-  #else   /* OPENLDAP 1, UMich, Solaris */
+#else   /* OPENLDAP 1, UMich, Solaris */
   free(dn);
   free(dn);
-  #endif
+#endif
   }
 
 DEBUG(D_lookup) debug_printf("search ended by ldap_result yielding %d\n",rc);
   }
 
 DEBUG(D_lookup) debug_printf("search ended by ldap_result yielding %d\n",rc);
@@ -909,25 +879,25 @@ the server, which we didn't get.
 Annoyingly, the different implementations of LDAP have gone for different
 methods of handling error codes and generating error messages. */
 
 Annoyingly, the different implementations of LDAP have gone for different
 methods of handling error codes and generating error messages. */
 
-if (rc == -1 || result == NULL)
+if (rc == -1 || !result)
   {
   int err;
   DEBUG(D_lookup) debug_printf("ldap_result failed\n");
 
   {
   int err;
   DEBUG(D_lookup) debug_printf("ldap_result failed\n");
 
-  #if defined LDAP_LIB_SOLARIS || defined LDAP_LIB_OPENLDAP2
+#if defined LDAP_LIB_SOLARIS || defined LDAP_LIB_OPENLDAP2
     ldap_get_option(lcp->ld, LDAP_OPT_ERROR_NUMBER, &err);
     *errmsg = string_sprintf("ldap_result failed: %d, %s",
       err, ldap_err2string(err));
 
     ldap_get_option(lcp->ld, LDAP_OPT_ERROR_NUMBER, &err);
     *errmsg = string_sprintf("ldap_result failed: %d, %s",
       err, ldap_err2string(err));
 
-  #elif defined LDAP_LIB_NETSCAPE
+#elif defined LDAP_LIB_NETSCAPE
     /* Dubious (surely 'matched' is spurious here?) */
     (void)ldap_get_lderrno(lcp->ld, &matched, &error1);
     *errmsg = string_sprintf("ldap_result failed: %s (%s)", error1, matched);
 
     /* Dubious (surely 'matched' is spurious here?) */
     (void)ldap_get_lderrno(lcp->ld, &matched, &error1);
     *errmsg = string_sprintf("ldap_result failed: %s (%s)", error1, matched);
 
-  #else                             /* UMich LDAP aka OpenLDAP 1.x */
+#else                             /* UMich LDAP aka OpenLDAP 1.x */
     *errmsg = string_sprintf("ldap_result failed: %d, %s",
       lcp->ld->ld_errno, ldap_err2string(lcp->ld->ld_errno));
     *errmsg = string_sprintf("ldap_result failed: %d, %s",
       lcp->ld->ld_errno, ldap_err2string(lcp->ld->ld_errno));
-  #endif
+#endif
 
   goto RETURN_ERROR;
   }
 
   goto RETURN_ERROR;
   }
@@ -1008,19 +978,19 @@ if (rc != LDAP_SUCCESS && rc != LDAP_SIZELIMIT_EXCEEDED
   {
   *errmsg = string_sprintf("LDAP search failed - error %d: %s%s%s%s%s",
     rc,
   {
   *errmsg = string_sprintf("LDAP search failed - error %d: %s%s%s%s%s",
     rc,
-    (error1 != NULL)?                       error1  : US"",
-    (error2 != NULL && error2[0] != 0)?     US"/"   : US"",
-    (error2 != NULL)?                       error2  : US"",
-    (matched != NULL && matched[0] != 0)?   US"/"   : US"",
-    (matched != NULL)?                      matched : US"");
+    error1 ?                  error1  : US"",
+    error2 && error2[0] ?     US"/"   : US"",
+    error2 ?                  error2  : US"",
+    matched && matched[0] ?   US"/"   : US"",
+    matched ?                 matched : US"");
 
 
-  #if defined LDAP_NAME_ERROR
+#if defined LDAP_NAME_ERROR
   if (LDAP_NAME_ERROR(rc))
   if (LDAP_NAME_ERROR(rc))
-  #elif defined NAME_ERROR    /* OPENLDAP1 calls it this */
+#elif defined NAME_ERROR    /* OPENLDAP1 calls it this */
   if (NAME_ERROR(rc))
   if (NAME_ERROR(rc))
-  #else
+#else
   if (rc == LDAP_NO_SUCH_OBJECT)
   if (rc == LDAP_NO_SUCH_OBJECT)
-  #endif
+#endif
 
     {
     DEBUG(D_lookup) debug_printf("lookup failure forced\n");
 
     {
     DEBUG(D_lookup) debug_printf("lookup failure forced\n");
@@ -1059,11 +1029,11 @@ if (!attribute_found)
 
 /* Otherwise, it's all worked */
 
 
 /* Otherwise, it's all worked */
 
-DEBUG(D_lookup) debug_printf("LDAP search: returning: %s\n", data);
-*res = data;
+DEBUG(D_lookup) debug_printf("LDAP search: returning: %s\n", data->s);
+*res = data->s;
 
 RETURN_OK:
 
 RETURN_OK:
-if (result != NULL) ldap_msgfree(result);
+if (result) ldap_msgfree(result);
 ldap_free_urldesc(ludp);
 return OK;
 
 ldap_free_urldesc(ludp);
 return OK;
 
@@ -1076,12 +1046,12 @@ RETURN_ERROR:
 DEBUG(D_lookup) debug_printf("%s\n", *errmsg);
 
 RETURN_ERROR_NOMSG:
 DEBUG(D_lookup) debug_printf("%s\n", *errmsg);
 
 RETURN_ERROR_NOMSG:
-if (result != NULL) ldap_msgfree(result);
-if (ludp != NULL) ldap_free_urldesc(ludp);
+if (result) ldap_msgfree(result);
+if (ludp) ldap_free_urldesc(ludp);
 
 #if defined LDAP_LIB_OPENLDAP2
 
 #if defined LDAP_LIB_OPENLDAP2
-  if (error2 != NULL)  ldap_memfree(error2);
-  if (matched != NULL) ldap_memfree(matched);
+  if (error2)  ldap_memfree(error2);
+  if (matched) ldap_memfree(matched);
 #endif
 
 return error_yield;
 #endif
 
 return error_yield;
@@ -1119,7 +1089,7 @@ Returns:        OK or FAIL or DEFER
 */
 
 static int
 */
 
 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;
   uschar **errmsg)
 {
 BOOL defer_break = FALSE;
@@ -1129,12 +1099,13 @@ int tcplimit = 0;
 int sep = 0;
 int dereference = LDAP_DEREF_NEVER;
 void* referrals = LDAP_OPT_ON;
 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 *user = NULL;
 uschar *password = NULL;
 uschar *local_servers = NULL;
-uschar *server, *list;
+uschar *server;
+const uschar *list;
 uschar buffer[512];
 
 while (isspace(*url)) url++;
 uschar buffer[512];
 
 while (isspace(*url)) url++;
@@ -1146,7 +1117,7 @@ NAME has the value "ldap". */
 
 while (strncmpic(url, US"ldap", 4) != 0)
   {
 
 while (strncmpic(url, US"ldap", 4) != 0)
   {
-  uschar *name = url;
+  const uschar *name = url;
   while (*url != 0 && *url != '=') url++;
   if (*url == '=')
     {
   while (*url != 0 && *url != '=') url++;
   if (*url == '=')
     {
@@ -1330,8 +1301,8 @@ are handled by a common function, with a flag to differentiate between them.
 The handle and filename arguments are not used. */
 
 static int
 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;
 {
 /* Keep picky compilers happy */
 do_cache = do_cache;
@@ -1339,8 +1310,8 @@ return(control_ldap_search(ldap_url, SEARCH_LDAP_SINGLE, result, errmsg));
 }
 
 static int
 }
 
 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;
 {
 /* Keep picky compilers happy */
 do_cache = do_cache;
@@ -1348,8 +1319,8 @@ return(control_ldap_search(ldap_url, SEARCH_LDAP_MULTIPLE, result, errmsg));
 }
 
 static int
 }
 
 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;
 {
 /* Keep picky compilers happy */
 do_cache = do_cache;
@@ -1357,8 +1328,8 @@ return(control_ldap_search(ldap_url, SEARCH_LDAP_DN, result, errmsg));
 }
 
 int
 }
 
 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;
 {
 /* Keep picky compilers happy */
 do_cache = do_cache;
index 5ce756d..ddfda85 100644 (file)
@@ -2,12 +2,12 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     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 */
 
 /* 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 */
                  uschar **, BOOL *);
 
 /* End of lookups/ldap.h */
index 9f7c7a2..8fa6027 100644 (file)
@@ -2,16 +2,17 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Header for the functions that are shared by the lookups */
 
 extern int     lf_check_file(int, uschar *, int, int, uid_t *, gid_t *,
                  const char *, uschar **);
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Header for the functions that are shared by the lookups */
 
 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 gstring *lf_quote(uschar *, uschar *, int, gstring *);
+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 */
 
 /* End of lf_functions.h */
index 60c0a76..8916fdc 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -22,22 +22,23 @@ Arguments:
   name           the field name
   value          the data value
   vlength        the data length
   name           the field name
   value          the data value
   vlength        the data length
-  result         the result pointer
-  asize          points to the size variable
-  aoffset        points to the offset variable
+  result         the result expanding-string
 
 Returns:         the result pointer (possibly updated)
 */
 
 
 Returns:         the result pointer (possibly updated)
 */
 
-uschar *
-lf_quote(uschar *name, uschar *value, int vlength, uschar *result, int *asize,
-  int *aoffset)
+gstring *
+lf_quote(uschar *name, uschar *value, int vlength, gstring * result)
 {
 {
-result = string_append(result, asize, aoffset, 2, name, US"=");
+result = string_append(result, 2, name, US"=");
 
 /* NULL is handled as an empty string */
 
 
 /* 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. */
 
 /* Quote the value if it is empty, contains white space, or starts with a quote
 character. */
@@ -45,21 +46,19 @@ character. */
 if (value[0] == 0 || Ustrpbrk(value, " \t\n\r") != NULL || value[0] == '\"')
   {
   int j;
 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, US"\"", 1);
   for (j = 0; j < vlength; j++)
     {
     if (value[j] == '\"' || value[j] == '\\')
   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, US"\\", 1);
+    result = string_catn(result, US value+j, 1);
     }
     }
-  result = string_cat(result, asize, aoffset, US"\"", 1);
+  result = string_catn(result, US"\"", 1);
   }
 else
   }
 else
-  {
-  result = string_cat(result, asize, aoffset, US value, vlength);
-  }
+  result = string_catn(result, US value, vlength);
 
 
-return string_cat(result, asize, aoffset, US" ", 1);
+return string_catn(result, US" ", 1);
 }
 
 /* End of lf_quote.c */
 }
 
 /* End of lf_quote.c */
index d430cd4..9966307 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
 /* 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
   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
   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;
 {
 int sep, rc;
 uschar *server;
-uschar *serverlist;
+const uschar *serverlist;
 uschar buffer[512];
 BOOL defer_break = FALSE;
 
 uschar buffer[512];
 BOOL defer_break = FALSE;
 
@@ -68,8 +69,8 @@ if (Ustrncmp(query, "servers", 7) != 0)
 else
   {
   int qsep;
 else
   {
   int qsep;
-  uschar *s, *ss;
-  uschar *qserverlist;
+  const uschar *s, *ss;
+  const uschar *qserverlist;
   uschar *qserver;
   uschar qbuffer[512];
 
   uschar *qserver;
   uschar qbuffer[512];
 
@@ -97,7 +98,7 @@ else
     return DEFER;
     }
 
     return DEFER;
     }
 
-  qserverlist = string_sprintf("%.*s", ss - s, s);
+  qserverlist = string_sprintf("%.*s", (int)(ss - s), s);
   qsep = 0;
 
   while ((qserver = string_nextinlist(&qserverlist, &qsep, qbuffer,
   qsep = 0;
 
   while ((qserver = string_nextinlist(&qserverlist, &qsep, qbuffer,
diff --git a/src/lookups/lmdb.c b/src/lookups/lmdb.c
new file mode 100644 (file)
index 0000000..efaa71f
--- /dev/null
@@ -0,0 +1,160 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 2016 - 2018*/
+/* 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", CS 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..8b4459a 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "../exim.h"
 /* 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
 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;
   int length, uschar **result, uschar **errmsg, int type)
 {
 FILE *f = (FILE *)handle;
@@ -101,11 +101,10 @@ for (last_was_eol = TRUE;
      Ufgets(buffer, sizeof(buffer), f) != NULL;
      last_was_eol = this_is_eol)
   {
      Ufgets(buffer, sizeof(buffer), f) != NULL;
      last_was_eol = this_is_eol)
   {
-  int ptr, size;
   int p = Ustrlen(buffer);
   int linekeylength;
   BOOL this_is_comment;
   int p = Ustrlen(buffer);
   int linekeylength;
   BOOL this_is_comment;
-  uschar *yield;
+  gstring * yield;
   uschar *s = buffer;
 
   /* Check whether this the final segment of a line. If it follows an
   uschar *s = buffer;
 
   /* Check whether this the final segment of a line. If it follows an
@@ -146,7 +145,7 @@ for (last_was_eol = TRUE;
     uschar *t = s++;
     while (*s != 0 && *s != '\"')
       {
     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++;
       }
         else *t++ = *s;
       s++;
       }
@@ -181,7 +180,7 @@ for (last_was_eol = TRUE;
       {
       int rc;
       int save = buffer[linekeylength];
       {
       int rc;
       int save = buffer[linekeylength];
-      uschar *list = buffer;
+      const uschar *list = buffer;
       buffer[linekeylength] = 0;
       rc = match_isinlist(keystring,
         &list,
       buffer[linekeylength] = 0;
       rc = match_isinlist(keystring,
         &list,
@@ -240,7 +239,7 @@ for (last_was_eol = TRUE;
 
   /* Reset dynamic store, if we need to, and revert to the search pool */
 
 
   /* Reset dynamic store, if we need to, and revert to the search pool */
 
-  if (reset_point != NULL)
+  if (reset_point)
     {
     store_reset(reset_point);
     store_pool = old_pool;
     {
     store_reset(reset_point);
     store_pool = old_pool;
@@ -254,11 +253,9 @@ for (last_was_eol = TRUE;
   Initialize, and copy the first segment of data. */
 
   this_is_comment = FALSE;
   Initialize, and copy the first segment of data. */
 
   this_is_comment = FALSE;
-  size = 100;
-  ptr = 0;
-  yield = store_get(size);
+  yield = string_get(100);
   if (*s != 0)
   if (*s != 0)
-    yield = string_cat(yield, &size, &ptr, s, Ustrlen(s));
+    yield = string_cat(yield, s);
 
   /* Now handle continuations */
 
 
   /* Now handle continuations */
 
@@ -294,18 +291,17 @@ for (last_was_eol = TRUE;
 
     /* Join a physical or logical line continuation onto the result string. */
 
 
     /* Join a physical or logical line continuation onto the result string. */
 
-    yield = string_cat(yield, &size, &ptr, s, Ustrlen(s));
+    yield = string_cat(yield, s);
     }
 
     }
 
-  yield[ptr] = 0;
-  store_reset(yield + ptr + 1);
-  *result = yield;
+  store_reset(yield->s + yield->ptr + 1);
+  *result = string_from_gstring(yield);
   return OK;
   }
 
 /* Reset dynamic store, if we need to */
 
   return OK;
   }
 
 /* Reset dynamic store, if we need to */
 
-if (reset_point != NULL)
+if (reset_point)
   {
   store_reset(reset_point);
   store_pool = old_pool;
   {
   store_reset(reset_point);
   store_pool = old_pool;
@@ -322,8 +318,8 @@ return FAIL;
 /* See local README for interface description */
 
 static int
 /* 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,
 {
 do_cache = do_cache;  /* Keep picky compilers happy */
 return internal_lsearch_find(handle, filename, keystring, length, result,
@@ -339,8 +335,8 @@ return internal_lsearch_find(handle, filename, keystring, length, result,
 /* See local README for interface description */
 
 static int
 /* 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,
 {
 do_cache = do_cache;  /* Keep picky compilers happy */
 return internal_lsearch_find(handle, filename, keystring, length, result,
@@ -356,8 +352,8 @@ return internal_lsearch_find(handle, filename, keystring, length, result,
 /* See local README for interface description */
 
 static int
 /* 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,
 {
 do_cache = do_cache;  /* Keep picky compilers happy */
 return internal_lsearch_find(handle, filename, keystring, length, result,
@@ -374,8 +370,8 @@ return internal_lsearch_find(handle, filename, keystring, length, result,
 /* See local README for interface description */
 
 static int
 /* 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] == '*') ||
 {
 do_cache = do_cache;  /* Keep picky compilers happy */
 if ((length == 1 && keystring[0] == '*') ||
index 9511d2a..77027fc 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Thanks to Paul Kelly for contributing the original code for these
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Thanks to Paul Kelly for contributing the original code for these
@@ -14,6 +14,53 @@ functions. */
 
 #include <mysql.h>       /* The system header */
 
 
 #include <mysql.h>       /* The system header */
 
+/* We define symbols for *_VERSION_ID (numeric), *_VERSION_STR (char*)
+and *_BASE_STR (char*). It's a bit of guesswork. Especially for mariadb
+with versions before 10.2, as they do not define there there specific symbols.
+*/
+
+/* Newer (>= 10.2) MariaDB */
+#if defined                   MARIADB_VERSION_ID
+#define EXIM_MxSQL_VERSION_ID MARIADB_VERSION_ID
+
+/* MySQL defines MYSQL_VERSION_ID, and MariaDB does so */
+/* https://dev.mysql.com/doc/refman/5.7/en/c-api-server-client-versions.html */
+#elif defined                 LIBMYSQL_VERSION_ID
+#define EXIM_MxSQL_VERSION_ID LIBMYSQL_VERSION_ID
+#elif defined                 MYSQL_VERSION_ID
+#define EXIM_MxSQL_VERSION_ID MYSQL_VERSION_ID
+
+#else
+#define EXIM_MYSQL_VERSION_ID  0
+#endif
+
+/* Newer (>= 10.2) MariaDB */
+#ifdef                         MARIADB_CLIENT_VERSION_STR
+#define EXIM_MxSQL_VERSION_STR MARIADB_CLIENT_VERSION_STR
+
+/* Mysql uses MYSQL_SERVER_VERSION */
+#elif defined                  LIBMYSQL_VERSION
+#define EXIM_MxSQL_VERSION_STR LIBMYSQL_VERSION
+#elif defined                  MYSQL_SERVER_VERSION
+#define EXIM_MxSQL_VERSION_STR MYSQL_SERVER_VERSION
+
+#else
+#define EXIM_MxSQL_VERSION_STR  "unknown"
+#endif
+
+#if defined                 MARIADB_BASE_VERSION
+#define EXIM_MxSQL_BASE_STR MARIADB_BASE_VERSION
+
+#elif defined               MARIADB_PACKAGE_VERSION
+#define EXIM_MxSQL_BASE_STR "mariadb"
+
+#elif defined               MYSQL_BASE_VERSION
+#define EXIM_MxSQL_BASE_STR MYSQL_BASE_VERSION
+
+#else
+#define EXIM_MxSQL_BASE_STR  "n.A."
+#endif
+
 
 /* Structure and anchor for caching connections. */
 
 
 /* Structure and anchor for caching connections. */
 
@@ -74,7 +121,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
   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
 
 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 +131,8 @@ Returns:       OK, FAIL, or DEFER
 */
 
 static int
 */
 
 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;
 {
 MYSQL *mysql_handle = NULL;        /* Keep compilers happy */
 MYSQL_RES *mysql_result = NULL;
@@ -93,11 +140,9 @@ MYSQL_ROW mysql_row_data;
 MYSQL_FIELD *fields;
 
 int i;
 MYSQL_FIELD *fields;
 
 int i;
-int ssize = 0;
-int offset = 0;
 int yield = DEFER;
 unsigned int num_fields;
 int yield = DEFER;
 unsigned int num_fields;
-uschar *result = NULL;
+gstring * result = NULL;
 mysql_connection *cn;
 uschar *server_copy = NULL;
 uschar *sdata[4];
 mysql_connection *cn;
 uschar *server_copy = NULL;
 uschar *sdata[4];
@@ -125,42 +170,50 @@ sdata[0] = server;   /* What's left at the start */
 
 /* See if we have a cached connection to the server */
 
 
 /* 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 (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
 
 /* 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 *p;
   uschar *socket = NULL;
   int port = 0;
+  uschar *group = US"exim";
 
 
-  if ((p = Ustrchr(sdata[0], '(')) != NULL)
+  if ((p = Ustrchr(sdata[0], '[')))
+    {
+    *p++ = 0;
+    group = p;
+    while (*p && *p != ']') p++;
+    *p = 0;
+    }
+
+  if ((p = Ustrchr(sdata[0], '(')))
     {
     *p++ = 0;
     socket = p;
     {
     *p++ = 0;
     socket = p;
-    while (*p != 0 && *p != ')') p++;
+    while (*p && *p != ')') p++;
     *p = 0;
     }
 
     *p = 0;
     }
 
-  if ((p = Ustrchr(sdata[0], ':')) != NULL)
+  if ((p = Ustrchr(sdata[0], ':')))
     {
     *p++ = 0;
     port = Uatoi(p);
     }
 
     {
     *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]);
     {
     *errmsg = string_sprintf("unexpected slash in MySQL server hostname: %s",
       sdata[0]);
@@ -181,6 +234,7 @@ if (cn == NULL)
 
   mysql_handle = store_get(sizeof(MYSQL));
   mysql_init(mysql_handle);
 
   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],
   if (mysql_real_connect(mysql_handle,
       /*  host        user         passwd     database */
       CS sdata[0], CS sdata[2], CS sdata[3], CS sdata[1],
@@ -225,15 +279,16 @@ 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
 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)
+if (!(mysql_result = mysql_use_result(mysql_handle)))
   {
   if ( mysql_field_count(mysql_handle) == 0 )
     {
     DEBUG(D_lookup) debug_printf("MYSQL: query was not one that returns data\n");
   {
   if ( mysql_field_count(mysql_handle) == 0 )
     {
     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;
+    result = string_cat(result,
+              string_sprintf("%d", mysql_affected_rows(mysql_handle)));
+    *do_cache = 0;
     goto MYSQL_EXIT;
     }
   *errmsg = string_sprintf("MYSQL: lookup result failed: %s\n",
     goto MYSQL_EXIT;
     }
   *errmsg = string_sprintf("MYSQL: lookup result failed: %s\n",
@@ -252,56 +307,49 @@ row, we insert '\n' between them. */
 
 fields = mysql_fetch_fields(mysql_result);
 
 
 fields = mysql_fetch_fields(mysql_result);
 
-while ((mysql_row_data = mysql_fetch_row(mysql_result)) != NULL)
+while ((mysql_row_data = mysql_fetch_row(mysql_result)))
   {
   unsigned long *lengths = mysql_fetch_lengths(mysql_result);
 
   {
   unsigned long *lengths = mysql_fetch_lengths(mysql_result);
 
-  if (result != NULL)
-      result = string_cat(result, &ssize, &offset, US"\n", 1);
+  if (result)
+    result = string_catn(result, 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],
-        lengths[0]);
-    }
+  if (num_fields != 1)
+    for (i = 0; i < num_fields; i++)
+      result = lf_quote(US fields[i].name, US mysql_row_data[i], lengths[i],
+                       result);
 
 
-  else for (i = 0; i < num_fields; i++)
-    {
-    result = lf_quote(US fields[i].name, US mysql_row_data[i], lengths[i],
-      result, &ssize, &offset);
-    }
+  else if (mysql_row_data[0] != NULL)    /* NULL value yields nothing */
+      result = string_catn(result, US mysql_row_data[0], lengths[0]);
   }
 
 /* more results? -1 = no, >0 = error, 0 = yes (keep looping)
    This is needed because of the CLIENT_MULTI_RESULTS on mysql_real_connect(),
    we don't expect any more results. */
 
   }
 
 /* more results? -1 = no, >0 = error, 0 = yes (keep looping)
    This is needed because of the CLIENT_MULTI_RESULTS on mysql_real_connect(),
    we don't expect any more results. */
 
-while((i = mysql_next_result(mysql_handle)) >= 0) {
-   if(i == 0) {   /* Just ignore more results */
-     DEBUG(D_lookup) debug_printf("MYSQL: got unexpected more results\n");
-     continue;
-   }
+while((i = mysql_next_result(mysql_handle)) >= 0)
+  {
+  if(i == 0)   /* Just ignore more results */
+    {
+    DEBUG(D_lookup) debug_printf("MYSQL: got unexpected more results\n");
+    continue;
+    }
 
 
-   *errmsg = string_sprintf("MYSQL: lookup result error when checking for more results: %s\n",
-       mysql_error(mysql_handle));
-   goto MYSQL_EXIT;
-}
+  *errmsg = string_sprintf(
+       "MYSQL: lookup result error when checking for more results: %s\n",
+       mysql_error(mysql_handle));
+  goto MYSQL_EXIT;
+  }
 
 /* If result is NULL then no data has been found and so we return FAIL.
 Otherwise, we must terminate the string which has been built; string_cat()
 always leaves enough room for a terminating zero. */
 
 
 /* If result is NULL then no data has been found and so we return FAIL.
 Otherwise, we must terminate the string which has been built; string_cat()
 always leaves enough room for a terminating zero. */
 
-if (result == NULL)
+if (!result)
   {
   yield = FAIL;
   *errmsg = US"MYSQL: no data found";
   }
   {
   yield = FAIL;
   *errmsg = US"MYSQL: no data found";
   }
-else
-  {
-  result[offset] = 0;
-  store_reset(result + offset + 1);
-  }
 
 /* Get here by goto from various error checks and from the case where no data
 was read (e.g. an update query). */
 
 /* Get here by goto from various error checks and from the case where no data
 was read (e.g. an update query). */
@@ -311,13 +359,14 @@ MYSQL_EXIT:
 /* Free mysal store for any result that was got; don't close the connection, as
 it is cached. */
 
 /* Free mysal store for any result that was got; don't close the connection, as
 it is cached. */
 
-if (mysql_result != NULL) mysql_free_result(mysql_result);
+if (mysql_result) mysql_free_result(mysql_result);
 
 
-/* Non-NULL result indicates a sucessful result */
+/* Non-NULL result indicates a successful result */
 
 
-if (result != NULL)
+if (result)
   {
   {
-  *resultptr = result;
+  *resultptr = string_from_gstring(result);
+  store_reset(result->s + (result->size = result->ptr + 1));
   return OK;
   }
 else
   return OK;
   }
 else
@@ -340,8 +389,8 @@ query is deferred with a retryable error is now in a separate function that is
 shared with other SQL lookups. */
 
 static int
 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);
 {
 return lf_sqlperform(US"MySQL", US"mysql_servers", mysql_servers, query,
   result, errmsg, do_cache, perform_mysql_search);
@@ -423,10 +472,10 @@ return quoted;
 void
 mysql_version_report(FILE *f)
 {
 void
 mysql_version_report(FILE *f)
 {
-fprintf(f, "Library version: MySQL: Compile: %s [%s]\n"
-           "                        Runtime: %s\n",
-        MYSQL_SERVER_VERSION, MYSQL_COMPILATION_COMMENT,
-        mysql_get_client_info());
+fprintf(f, "Library version: MySQL: Compile: %lu %s [%s]\n"
+           "                        Runtime: %lu %s\n",
+        (long)EXIM_MxSQL_VERSION_ID, EXIM_MxSQL_VERSION_STR, EXIM_MxSQL_BASE_STR,
+        mysql_get_client_version(), mysql_get_client_info());
 #ifdef DYNLOOKUP
 fprintf(f, "                        Exim version %s\n", EXIM_VERSION_STR);
 #endif
 #ifdef DYNLOOKUP
 fprintf(f, "                        Exim version %s\n", EXIM_VERSION_STR);
 #endif
index 7b012b1..d3f0480 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     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"
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "../exim.h"
@@ -41,14 +41,14 @@ for nis0 because they are so short it isn't worth trying to use any common
 code. */
 
 static int
 code. */
 
 static int
-nis_find(void *handle, uschar *filename, uschar *keystring, int length,
-  uschar **result, uschar **errmsg, BOOL *do_cache)
+nis_find(void *handle, uschar *filename, const uschar *keystring, int length,
+  uschar **result, uschar **errmsg, uint *do_cache)
 {
 int rc;
 uschar *nis_data;
 int nis_data_length;
 do_cache = do_cache;   /* Placate picky compilers */
 {
 int rc;
 uschar *nis_data;
 int nis_data_length;
 do_cache = do_cache;   /* Placate picky compilers */
-if ((rc = yp_match(CS handle, CS filename, CS keystring, length,
+if ((rc = yp_match(CCS handle, CCS filename, CCS keystring, length,
     CSS &nis_data, &nis_data_length)) == 0)
   {
   *result = string_copy(nis_data);
     CSS &nis_data, &nis_data_length)) == 0)
   {
   *result = string_copy(nis_data);
@@ -67,14 +67,14 @@ return (rc == YPERR_KEY || rc == YPERR_MAP)? FAIL : DEFER;
 /* See local README for interface description. */
 
 static int
 /* See local README for interface description. */
 
 static int
-nis0_find(void *handle, uschar *filename, uschar *keystring, int length,
-  uschar **result, uschar **errmsg, BOOL *do_cache)
+nis0_find(void *handle, uschar *filename, const uschar *keystring, int length,
+  uschar **result, uschar **errmsg, uint *do_cache)
 {
 int rc;
 uschar *nis_data;
 int nis_data_length;
 do_cache = do_cache;   /* Placate picky compilers */
 {
 int rc;
 uschar *nis_data;
 int nis_data_length;
 do_cache = do_cache;   /* Placate picky compilers */
-if ((rc = yp_match(CS handle, CS filename, CS keystring, length + 1,
+if ((rc = yp_match(CCS handle, CCS filename, CCS keystring, length + 1,
     CSS &nis_data, &nis_data_length)) == 0)
   {
   *result = string_copy(nis_data);
     CSS &nis_data, &nis_data_length)) == 0)
   {
   *result = string_copy(nis_data);
index 8895cee..e2115a5 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "../exim.h"
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "../exim.h"
@@ -42,21 +42,19 @@ yield is the concatenation of all the fields, preceded by their names and an
 equals sign. */
 
 static int
 equals sign. */
 
 static int
-nisplus_find(void *handle, uschar *filename, uschar *query, int length,
-  uschar **result, uschar **errmsg, BOOL *do_cache)
+nisplus_find(void *handle, uschar *filename, const uschar *query, int length,
+  uschar **result, uschar **errmsg, uint *do_cache)
 {
 int i;
 {
 int i;
-int ssize = 0;
-int offset = 0;
 int error_error = FAIL;
 int error_error = FAIL;
-uschar *field_name = NULL;
+const uschar * field_name = NULL;
 nis_result *nrt = NULL;
 nis_result *nre = NULL;
 nis_object *tno, *eno;
 struct entry_obj *eo;
 struct table_obj *ta;
 nis_result *nrt = NULL;
 nis_result *nre = NULL;
 nis_object *tno, *eno;
 struct entry_obj *eo;
 struct table_obj *ta;
-uschar *p = query + length;
-uschar *yield = NULL;
+const uschar * p = query + length;
+gstring * yield = NULL;
 
 do_cache = do_cache;   /* Placate picky compilers */
 
 
 do_cache = do_cache;   /* Placate picky compilers */
 
@@ -65,12 +63,15 @@ has been given. */
 
 while (p > query && p[-1] != ':') p--;
 
 
 while (p > query && p[-1] != ':') p--;
 
-if (p > query)
+if (p > query)         /* get the query without the result-field */
   {
   {
+  uint len = p-1 - query;
   field_name = p;
   field_name = p;
-  p[-1] = 0;
+  query = string_copyn(query, len);
+  p = query + len;
   }
   }
-else p = query + length;
+else
+  p = query + length;
 
 /* Now search backwards to find the comma that starts the
 table name. */
 
 /* Now search backwards to find the comma that starts the
 table name. */
@@ -102,7 +103,7 @@ if (tno->zo_data.zo_type != TABLE_OBJ)
   *errmsg = string_sprintf("NIS+ error: %s is not a table", p);
   goto NISPLUS_EXIT;
   }
   *errmsg = string_sprintf("NIS+ error: %s is not a table", p);
   goto NISPLUS_EXIT;
   }
-ta = &(tno->zo_data.objdata_u.ta_data);
+ta = &tno->zo_data.objdata_u.ta_data;
 
 /* Now look up the entry in the table, check that we got precisely one
 object and that it is a table entry. */
 
 /* Now look up the entry in the table, check that we got precisely one
 object and that it is a table entry. */
@@ -154,36 +155,36 @@ for (i = 0; i < eo->en_cols.en_cols_len; i++)
 
   /* Concatenate all fields if no specific one selected */
 
 
   /* Concatenate all fields if no specific one selected */
 
-  if (field_name == NULL)
+  if (!field_name)
     {
     {
-    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, tc->tc_name);
+    yield = string_catn(yield, US"=", 1);
 
     /* Quote the value if it contains spaces or is empty */
 
     if (value[0] == 0 || Ustrchr(value, ' ') != NULL)
       {
       int j;
 
     /* 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, US"\"", 1);
       for (j = 0; j < len; j++)
         {
         if (value[j] == '\"' || value[j] == '\\')
       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, US"\\", 1);
+        yield = string_catn(yield, value+j, 1);
         }
         }
-      yield = string_cat(yield, &ssize, &offset, US"\"", 1);
+      yield = string_catn(yield, US"\"", 1);
       }
       }
-    else yield = string_cat(yield, &ssize, &offset, value, len);
+    else
+      yield = string_catn(yield, value, len);
 
 
-    yield = string_cat(yield, &ssize, &offset, US" ", 1);
+    yield = string_catn(yield, US" ", 1);
     }
 
   /* When the specified field is found, grab its data and finish */
 
   else if (Ustrcmp(field_name, tc->tc_name) == 0)
     {
     }
 
   /* When the specified field is found, grab its data and finish */
 
   else if (Ustrcmp(field_name, tc->tc_name) == 0)
     {
-    yield = string_copyn(value, len);
+    yield = string_catn(yield, value, len);
     goto NISPLUS_EXIT;
     }
   }
     goto NISPLUS_EXIT;
     }
   }
@@ -191,26 +192,21 @@ for (i = 0; i < eo->en_cols.en_cols_len; i++)
 /* Error if a field name was specified and we didn't find it; if no
 field name, ensure the concatenated data is zero-terminated. */
 
 /* Error if a field name was specified and we didn't find it; if no
 field name, ensure the concatenated data is zero-terminated. */
 
-if (field_name != NULL)
+if (field_name)
   *errmsg = string_sprintf("NIS+ field %s not found for %s", field_name,
     query);
 else
   *errmsg = string_sprintf("NIS+ field %s not found for %s", field_name,
     query);
 else
-  {
-  yield[offset] = 0;
-  store_reset(yield + offset + 1);
-  }
+  store_reset(yield->s + yield->ptr + 1);
 
 
-/* Restore the colon in the query, and free result store before
-finishing. */
+/* Free result store before finishing. */
 
 NISPLUS_EXIT:
 
 NISPLUS_EXIT:
-if (field_name != NULL) field_name[-1] = ':';
-if (nrt != NULL) nis_freeresult(nrt);
-if (nre != NULL) nis_freeresult(nre);
+if (nrt) nis_freeresult(nrt);
+if (nre) nis_freeresult(nre);
 
 
-if (yield != NULL)
+if (yield)
   {
   {
-  *result = yield;
+  *result = string_from_gstring(yield);
   return OK;
   }
 
   return OK;
   }
 
index 1f2520a..1b21e6a 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     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
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Interface to an Oracle database. This code was originally supplied by
@@ -255,15 +255,12 @@ Ora_Define *def = NULL;
 void *hda = NULL;
 
 int i;
 void *hda = NULL;
 
 int i;
-int ssize = 0;
-int offset = 0;
 int yield = DEFER;
 unsigned int num_fields = 0;
 int yield = DEFER;
 unsigned int num_fields = 0;
-uschar *result = NULL;
+gstring * result = NULL;
 oracle_connection *cn = NULL;
 uschar *server_copy = NULL;
 uschar *sdata[4];
 oracle_connection *cn = NULL;
 uschar *server_copy = NULL;
 uschar *sdata[4];
-uschar tmp[1024];
 
 /* Disaggregate the parameters from the server argument. The order is host,
 database, user, password. We can write to the string, since it is in a
 
 /* Disaggregate the parameters from the server argument. The order is host,
 database, user, password. We can write to the string, since it is in a
@@ -292,19 +289,17 @@ if (sdata[1][0] == 0) sdata[1] = NULL;
 
 /* See if we have a cached connection to the server */
 
 
 /* See if we have a cached connection to the server */
 
-for (cn = oracle_connections; cn != NULL; cn = cn->next)
-  {
+for (cn = oracle_connections; cn; cn = cn->next)
   if (strcmp(cn->server, server_copy) == 0)
     {
     oracle_handle = cn->handle;
     hda = cn->hda_mem;
     break;
     }
   if (strcmp(cn->server, server_copy) == 0)
     {
     oracle_handle = cn->handle;
     hda = cn->hda_mem;
     break;
     }
-  }
 
 /* If no cached connection, we must set one up */
 
 
 /* If no cached connection, we must set one up */
 
-if (cn == NULL)
+if (!cn)
   {
   DEBUG(D_lookup) debug_printf("ORACLE new connection: host=%s database=%s "
     "user=%s\n", sdata[0], sdata[1], sdata[2]);
   {
   DEBUG(D_lookup) debug_printf("ORACLE new connection: host=%s database=%s "
     "user=%s\n", sdata[0], sdata[1], sdata[2]);
@@ -400,12 +395,12 @@ while (cda->rc != NO_DATA_FOUND)  /* Loop for each row */
   ofetch(cda);
   if(cda->rc == NO_DATA_FOUND) break;
 
   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, "\n", 1);
 
   /* Single field - just add on the data */
 
   if (num_fields == 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, def[0].buf, def[0].col_retlen);
 
   /* Multiple fields - precede by file name, removing {lead,trail}ing WS */
 
 
   /* Multiple fields - precede by file name, removing {lead,trail}ing WS */
 
@@ -417,51 +412,48 @@ 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--;
     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, s, slen);
+    result = string_catn(result, US"=", 1);
 
 
-    /* int and float type wont ever need escaping. Otherwise, quote the value
+    /* int and float type won't ever need escaping. Otherwise, quote the value
     if it contains spaces or is empty. */
 
     if (desc[i].dbtype != INT_TYPE && desc[i].dbtype != FLOAT_TYPE &&
        (def[i].buf[0] == 0 || strchr(def[i].buf, ' ') != NULL))
       {
       int j;
     if it contains spaces or is empty. */
 
     if (desc[i].dbtype != INT_TYPE && desc[i].dbtype != FLOAT_TYPE &&
        (def[i].buf[0] == 0 || strchr(def[i].buf, ' ') != NULL))
       {
       int j;
-      result = string_cat(result, &ssize, &offset, "\"", 1);
+      result = string_catn(result, "\"", 1);
       for (j = 0; j < def[i].col_retlen; j++)
         {
         if (def[i].buf[j] == '\"' || def[i].buf[j] == '\\')
       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, "\\", 1);
+        result = string_catn(result, def[i].buf+j, 1);
         }
         }
-      result = string_cat(result, &ssize, &offset, "\"", 1);
+      result = string_catn(result, "\"", 1);
       }
 
     else switch(desc[i].dbtype)
       {
       case INT_TYPE:
       }
 
     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));
-      break;
+       result = string_cat(result, string_sprintf("%d", def[i].int_buf));
+       break;
 
       case FLOAT_TYPE:
 
       case FLOAT_TYPE:
-      sprintf(CS tmp, "%f", def[i].flt_buf);
-      result = string_cat(result, &ssize, &offset, tmp, Ustrlen(tmp));
-      break;
+       result = string_cat(result, string_sprintf("%f", def[i].flt_buf));
+       break;
 
       case STRING_TYPE:
 
       case STRING_TYPE:
-      result = string_cat(result, &ssize, &offset, def[i].buf,
-        def[i].col_retlen);
-      break;
+       result = string_catn(result, def[i].buf, def[i].col_retlen);
+       break;
 
       default:
 
       default:
-      *errmsg = string_sprintf("ORACLE: unknown field type %d", desc[i].dbtype);
-      *defer_break = FALSE;
-      result = NULL;
-      goto ORACLE_EXIT;
+       *errmsg = string_sprintf("ORACLE: unknown field type %d", desc[i].dbtype);
+       *defer_break = FALSE;
+       result = NULL;
+       goto ORACLE_EXIT;
       }
 
       }
 
-    result = string_cat(result, &ssize, &offset, " ", 1);
+    result = string_catn(result, " ", 1);
     }
   }
 
     }
   }
 
@@ -469,16 +461,13 @@ while (cda->rc != NO_DATA_FOUND)  /* Loop for each row */
 Otherwise, we must terminate the string which has been built; string_cat()
 always leaves enough room for a terminating zero. */
 
 Otherwise, we must terminate the string which has been built; string_cat()
 always leaves enough room for a terminating zero. */
 
-if (result == NULL)
+if (!result)
   {
   yield = FAIL;
   *errmsg = "ORACLE: no data found";
   }
 else
   {
   yield = FAIL;
   *errmsg = "ORACLE: no data found";
   }
 else
-  {
-  result[offset] = 0;
-  store_reset(result + offset + 1);
-  }
+  store_reset(result->s + result->ptr + 1);
 
 /* Get here by goto from various error checks. */
 
 
 /* Get here by goto from various error checks. */
 
@@ -490,11 +479,11 @@ oclose(cda);
 
 ORACLE_EXIT_NO_VALS:
 
 
 ORACLE_EXIT_NO_VALS:
 
-/* Non-NULL result indicates a sucessful result */
+/* Non-NULL result indicates a successful result */
 
 
-if (result != NULL)
+if (result)
   {
   {
-  *resultptr = result;
+  *resultptr = string_from_gstring(result);
   return OK;
   }
 else
   return OK;
   }
 else
@@ -517,7 +506,7 @@ deferred with a retryable error. */
 
 static int
 oracle_find(void *handle, uschar *filename, uschar *query, int length,
 
 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;
 {
 int sep = 0;
 uschar *server;
index 4690cbf..315677f 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     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"
 /* 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
 /* 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;
 
 {
 struct passwd *pw;
 
index 95b1b8c..697285a 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Thanks to Petr Cech for contributing the original code for these
 /* 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
 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).
 
 For TCP/IP connections, the server is a host name and optional port (with a
 colon separator).
@@ -118,18 +118,16 @@ Returns:       OK, FAIL, or DEFER
 */
 
 static int
 */
 
 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;
 {
 PGconn *pg_conn = NULL;
 PGresult *pg_result = NULL;
 
 int i;
-int ssize = 0;
-int offset = 0;
+gstring * result = NULL;
 int yield = DEFER;
 unsigned int num_fields, num_tuples;
 int yield = DEFER;
 unsigned int num_fields, num_tuples;
-uschar *result = NULL;
 pgsql_connection *cn;
 uschar *server_copy = NULL;
 uschar *sdata[3];
 pgsql_connection *cn;
 uschar *server_copy = NULL;
 uschar *sdata[3];
@@ -142,7 +140,7 @@ has the password removed. This copy is also used for debugging output. */
 for (i = 2; i >= 0; i--)
   {
   uschar *pp = Ustrrchr(server, '/');
 for (i = 2; i >= 0; i--)
   {
   uschar *pp = Ustrrchr(server, '/');
-  if (pp == NULL)
+  if (!pp)
     {
     *errmsg = string_sprintf("incomplete pgSQL server data: %s",
       (i == 2)? server : server_copy);
     {
     *errmsg = string_sprintf("incomplete pgSQL server data: %s",
       (i == 2)? server : server_copy);
@@ -158,18 +156,16 @@ for (i = 2; i >= 0; i--)
 start is the identification of the server (host or path). See if we have a
 cached connection to the server. */
 
 start is the identification of the server (host or path). See if we have a
 cached connection to the server. */
 
-for (cn = pgsql_connections; cn != NULL; cn = cn->next)
-  {
+for (cn = pgsql_connections; cn; cn = cn->next)
   if (Ustrcmp(cn->server, server_copy) == 0)
     {
     pg_conn = cn->handle;
     break;
     }
   if (Ustrcmp(cn->server, server_copy) == 0)
     {
     pg_conn = cn->handle;
     break;
     }
-  }
 
 /* If there is no cached connection, we must set one up. */
 
 
 /* If there is no cached connection, we must set one up. */
 
-if (cn == NULL)
+if (!cn)
   {
   uschar *port = US"";
 
   {
   uschar *port = US"";
 
@@ -180,7 +176,7 @@ if (cn == NULL)
     uschar *last_slash, *last_dot, *p;
 
     p = ++server;
     uschar *last_slash, *last_dot, *p;
 
     p = ++server;
-    while (*p != 0 && *p != ')') p++;
+    while (*p && *p != ')') p++;
     *p = 0;
 
     last_slash = Ustrrchr(server, '/');
     *p = 0;
 
     last_slash = Ustrrchr(server, '/');
@@ -193,10 +189,9 @@ if (cn == NULL)
     We have to call PQsetdbLogin with '/var/run/postgresql' as the hostname
     argument and put '5432' into the port variable. */
 
     We have to call PQsetdbLogin with '/var/run/postgresql' as the hostname
     argument and put '5432' into the port variable. */
 
-    if (last_slash == NULL || last_dot == NULL)
+    if (!last_slash || !last_dot)
       {
       {
-      *errmsg = string_sprintf("PGSQL invalid filename for socket: %s",
-        server);
+      *errmsg = string_sprintf("PGSQL invalid filename for socket: %s", server);
       *defer_break = TRUE;
       return DEFER;
       }
       *defer_break = TRUE;
       return DEFER;
       }
@@ -213,13 +208,13 @@ if (cn == NULL)
   else
     {
     uschar *p;
   else
     {
     uschar *p;
-    if ((p = Ustrchr(server, ':')) != NULL)
+    if ((p = Ustrchr(server, ':')))
       {
       *p++ = 0;
       port = p;
       }
 
       {
       *p++ = 0;
       port = p;
       }
 
-    if (Ustrchr(server, '/') != NULL)
+    if (Ustrchr(server, '/'))
       {
       *errmsg = string_sprintf("unexpected slash in pgSQL server hostname: %s",
         server);
       {
       *errmsg = string_sprintf("unexpected slash in pgSQL server hostname: %s",
         server);
@@ -282,36 +277,37 @@ else
 
 /* Run the query */
 
 
 /* Run the query */
 
-  pg_result = PQexec(pg_conn, CS query);
-  switch(PQresultStatus(pg_result))
-    {
-    case PGRES_EMPTY_QUERY:
-    case PGRES_COMMAND_OK:
+pg_result = PQexec(pg_conn, CS query);
+switch(PQresultStatus(pg_result))
+  {
+  case PGRES_EMPTY_QUERY:
+  case PGRES_COMMAND_OK:
     /* The command was successful but did not return any data since it was
     /* 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. */
-    result = string_copy(US PQcmdTuples(pg_result));
-    offset = Ustrlen(result);
-    *do_cache = FALSE;
+    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 zero. */
+
+    result = string_cat(result, US PQcmdTuples(pg_result));
+    *do_cache = 0;
     DEBUG(D_lookup) debug_printf("PGSQL: command does not return any data "
     DEBUG(D_lookup) debug_printf("PGSQL: command does not return any data "
-      "but was successful. Rows affected: %s\n", result);
+      "but was successful. Rows affected: %s\n", string_from_gstring(result));
+    break;
 
 
-    case PGRES_TUPLES_OK:
+  case PGRES_TUPLES_OK:
     break;
 
     break;
 
-    default:
+  default:
     /* This was the original code:
     *errmsg = string_sprintf("PGSQL: query failed: %s\n",
     /* This was the original code:
     *errmsg = string_sprintf("PGSQL: query failed: %s\n",
-                             PQresultErrorMessage(pg_result));
+                            PQresultErrorMessage(pg_result));
     This was suggested by a user:
     */
 
     *errmsg = string_sprintf("PGSQL: query failed: %s (%s) (%s)\n",
     This was suggested by a user:
     */
 
     *errmsg = string_sprintf("PGSQL: query failed: %s (%s) (%s)\n",
-                             PQresultErrorMessage(pg_result),
-                             PQresStatus(PQresultStatus(pg_result)), query);
+                          PQresultErrorMessage(pg_result),
+                          PQresStatus(PQresultStatus(pg_result)), query);
     goto PGSQL_EXIT;
     goto PGSQL_EXIT;
-    }
+  }
 
 /* Result is in pg_result. Find the number of fields returned. If this is one,
 we don't add field names to the data. Otherwise we do. If the query did not
 
 /* Result is in pg_result. Find the number of fields returned. If this is one,
 we don't add field names to the data. Otherwise we do. If the query did not
@@ -326,41 +322,30 @@ row, we insert '\n' between them. */
 
 for (i = 0; i < num_tuples; i++)
   {
 
 for (i = 0; i < num_tuples; i++)
   {
-  if (result != NULL)
-    result = string_cat(result, &ssize, &offset, US"\n", 1);
-
-   if (num_fields == 1)
-    {
-    result = string_cat(result, &ssize, &offset,
-      US PQgetvalue(pg_result, i, 0), PQgetlength(pg_result, i, 0));
-    }
+  if (result)
+    result = string_catn(result, US"\n", 1);
 
 
-   else
+  if (num_fields == 1)
+    result = string_catn(result,
+       US PQgetvalue(pg_result, i, 0), PQgetlength(pg_result, i, 0));
+  else
     {
     int j;
     for (j = 0; j < num_fields; j++)
       {
       uschar *tmp = US PQgetvalue(pg_result, i, j);
     {
     int j;
     for (j = 0; j < num_fields; j++)
       {
       uschar *tmp = US PQgetvalue(pg_result, i, j);
-      result = lf_quote(US PQfname(pg_result, j), tmp, Ustrlen(tmp), result,
-        &ssize, &offset);
+      result = lf_quote(US PQfname(pg_result, j), tmp, Ustrlen(tmp), result);
       }
     }
   }
 
       }
     }
   }
 
-/* If result is NULL then no data has been found and so we return FAIL.
-Otherwise, we must terminate the string which has been built; string_cat()
-always leaves enough room for a terminating zero. */
+/* If result is NULL then no data has been found and so we return FAIL. */
 
 
-if (result == NULL)
+if (!result)
   {
   yield = FAIL;
   *errmsg = US"PGSQL: no data found";
   }
   {
   yield = FAIL;
   *errmsg = US"PGSQL: no data found";
   }
-else
-  {
-  result[offset] = 0;
-  store_reset(result + offset + 1);
-  }
 
 /* Get here by goto from various error checks. */
 
 
 /* Get here by goto from various error checks. */
 
@@ -369,13 +354,14 @@ PGSQL_EXIT:
 /* Free store for any result that was got; don't close the connection, as
 it is cached. */
 
 /* Free store for any result that was got; don't close the connection, as
 it is cached. */
 
-if (pg_result != NULL) PQclear(pg_result);
+if (pg_result) PQclear(pg_result);
 
 
-/* Non-NULL result indicates a sucessful result */
+/* Non-NULL result indicates a successful result */
 
 
-if (result != NULL)
+if (result)
   {
   {
-  *resultptr = result;
+  store_reset(result->s + result->ptr + 1);
+  *resultptr = string_from_gstring(result);
   return OK;
   }
 else
   return OK;
   }
 else
@@ -398,8 +384,8 @@ query is deferred with a retryable error is now in a separate function that is
 shared with other SQL lookups. */
 
 static int
 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);
 {
 return lf_sqlperform(US"PostgreSQL", US"pgsql_servers", pgsql_servers, query,
   result, errmsg, do_cache, perform_pgsql_search);
@@ -413,12 +399,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.
 
 /* 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
 
 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 +428,7 @@ uschar *quoted;
 if (opt != NULL) return NULL;     /* No options recognized */
 
 while ((c = *t++) != 0)
 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);
 
 if (count == 0) return s;
 t = quoted = store_get(Ustrlen(s) + count + 1);
@@ -460,7 +440,7 @@ while ((c = *s++) != 0)
     *t++ = '\'';
     *t++ = '\'';
     }
     *t++ = '\'';
     *t++ = '\'';
     }
-  else if (Ustrchr("\n\t\r\b\"\\%_", c) != NULL)
+  else if (Ustrchr("\n\t\r\b\"\\", c) != NULL)
     {
     *t++ = '\\';
     switch(c)
     {
     *t++ = '\\';
     switch(c)
dissimilarity index 84%
index 87cc9fd..a4b672a 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 - 2018 */
+/* 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 yield = DEFER;
+int i, j;
+gstring * result = NULL;
+uschar *server_copy = NULL;
+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++)
+    {
+    gstring * g;
+
+    for (g = NULL; (c = *s) && !isspace(c); s++)
+      if (c != '\\' || *++s)           /* backslash protects next char */
+       g = string_catn(g, s, 1);
+    argv[i] = string_from_gstring(g);
+
+    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);
+
+    /* trap MOVED cluster responses and follow them */
+    if (Ustrncmp(redis_reply->str, "MOVED", 5) == 0)
+      {
+      DEBUG(D_lookup)
+        debug_printf("REDIS: cluster redirect %s\n", redis_reply->str);
+      /* follow redirect
+      This is cheating, we simply set defer_break = FALSE to move on to
+      the next server in the redis_servers list */
+      *defer_break = FALSE;
+      return DEFER;
+      } else {
+      *defer_break = TRUE;
+      }
+    *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_catn(result, US"", 1);
+    *do_cache = 0;
+    goto REDIS_EXIT;
+    /* NOTREACHED */
+
+  case REDIS_REPLY_INTEGER:
+    result = string_cat(result, redis_reply->integer != 0 ? US"true" : US"false");
+    break;
+
+  case REDIS_REPLY_STRING:
+  case REDIS_REPLY_STATUS:
+    result = string_catn(result, 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, US"\n", 1);
+
+      switch (entry->type)
+       {
+       case REDIS_REPLY_INTEGER:
+         result = string_fmt_append(result, "%d", entry->integer);
+         break;
+       case REDIS_REPLY_STRING:
+         result = string_catn(result, 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, US"\n", 1);
+
+           switch (tentry->type)
+             {
+             case REDIS_REPLY_INTEGER:
+               result = string_fmt_append(result, "%d", tentry->integer);
+               break;
+             case REDIS_REPLY_STRING:
+               result = string_catn(result, 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)
+  store_reset(result->s + result->ptr + 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 = string_from_gstring(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..b32a73e 100644 (file)
  * as published by the Free Software Foundation; either version 2
  * of the License, or (at your option) any later version.
  *
  * 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"
 
  */
 
 #include "../exim.h"
 
-#ifndef EXPERIMENTAL_SPF
+#ifndef SUPPORT_SPF
 static void dummy(int x);
 static void dummy2(int x) { dummy(x-1); }
 static void dummy(int x) { dummy2(x-1); }
 static void dummy(int x);
 static void dummy2(int x) { dummy(x-1); }
 static void dummy(int x) { dummy2(x-1); }
@@ -31,7 +32,9 @@ static void dummy(int x) { dummy2(x-1); }
 #include <spf2/spf_dns_resolv.h>
 #include <spf2/spf_dns_cache.h>
 
 #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) {
   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;
 }
 
   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);
 }
 
   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;
   SPF_server_t *spf_server = handle;
   SPF_request_t *spf_request = NULL;
   SPF_response_t *spf_response = NULL;
@@ -111,4 +118,4 @@ static lookup_info _lookup_info = {
 static lookup_info *_lookup_list[] = { &_lookup_info };
 lookup_module_info spf_lookup_module_info = { LOOKUP_MODULE_INFO_MAGIC, _lookup_list, 1 };
 
 static lookup_info *_lookup_list[] = { &_lookup_info };
 lookup_module_info spf_lookup_module_info = { LOOKUP_MODULE_INFO_MAGIC, _lookup_list, 1 };
 
-#endif /* EXPERIMENTAL_SPF */
+#endif /* SUPPORT_SPF */
index c0fc891..1619429 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "../exim.h"
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "../exim.h"
@@ -23,7 +23,7 @@ sqlite_open(uschar *filename, uschar **errmsg)
 sqlite3 *db = NULL;
 int ret;
 
 sqlite3 *db = NULL;
 int ret;
 
-ret = sqlite3_open((char *)filename, &db);
+ret = sqlite3_open(CS filename, &db);
 if (ret != 0)
   {
   *errmsg = (void *)sqlite3_errmsg(db);
 if (ret != 0)
   {
   *errmsg = (void *)sqlite3_errmsg(db);
@@ -41,21 +41,16 @@ return db;
 
 /* See local README for interface description. */
 
 
 /* See local README for interface description. */
 
-struct strbuf {
-  uschar *string;
-  int size;
-  int len;
-};
-
-static int sqlite_callback(void *arg, int argc, char **argv, char **azColName)
+static int
+sqlite_callback(void *arg, int argc, char **argv, char **azColName)
 {
 {
-struct strbuf *res = arg;
+gstring * res = *(gstring **)arg;
 int i;
 
 /* For second and subsequent results, insert \n */
 
 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);
+if (res)
+  res = string_catn(res, US"\n", 1);
 
 if (argc > 1)
   {
 
 if (argc > 1)
   {
@@ -63,39 +58,35 @@ if (argc > 1)
   for (i = 0; i < argc; i++)
     {
     uschar *value = US((argv[i] != NULL)? argv[i]:"<NULL>");
   for (i = 0; i < argc; i++)
     {
     uschar *value = US((argv[i] != NULL)? argv[i]:"<NULL>");
-    res->string = lf_quote(US azColName[i], value, Ustrlen(value), res->string,
-      &res->size, &res->len);
+    res = lf_quote(US azColName[i], value, Ustrlen(value), res);
     }
   }
 
 else
     }
   }
 
 else
-  {
-  res->string = string_append(res->string, &res->size, &res->len, 1,
-    (argv[0] != NULL)? argv[0]:"<NULL>");
-  }
+  res = string_cat(res, argv[0] ? US argv[0] : US "<NULL>");
 
 
-res->string[res->len] = 0;
+*(gstring **)arg = res;
 return 0;
 }
 
 
 static int
 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;
 {
 int ret;
-struct strbuf res = { NULL, 0, 0 };
+gstring * res = NULL;
 
 
-ret = sqlite3_exec(handle, (char *)query, sqlite_callback, &res, (char **)errmsg);
+ret = sqlite3_exec(handle, CS query, sqlite_callback, &res, (char **)errmsg);
 if (ret != SQLITE_OK)
   {
   debug_printf("sqlite3_exec failed: %s\n", *errmsg);
   return FAIL;
   }
 
 if (ret != SQLITE_OK)
   {
   debug_printf("sqlite3_exec failed: %s\n", *errmsg);
   return FAIL;
   }
 
-if (res.string == NULL) *do_cache = FALSE;
+if (!res) *do_cache = 0;
 
 
-*result = res.string;
+*result = string_from_gstring(res);
 return OK;
 }
 
 return OK;
 }
 
index 7a81795..401f7c8 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     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"
 /* 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
 /* 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;
 {
 handle = handle;          /* Keep picky compilers happy */
 filename = filename;
@@ -57,7 +57,7 @@ if (Ustrcmp(query, "defer") == 0)
   return DEFER;
   }
 
   return DEFER;
   }
 
-if (Ustrcmp(query, "nocache") == 0) *do_cache = FALSE;
+if (Ustrcmp(query, "nocache") == 0) *do_cache = 0;
 
 *result = string_copy(query);
 return OK;
 
 *result = string_copy(query);
 return OK;
index 4166089..8f065e6 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     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. */
 /* 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,
 
 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 */
 {
 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    *
 *************************************************/
 
 *     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
 /* 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)
 {
 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);
 }
 
   TRUE, NULL);
 }
 
@@ -49,7 +49,7 @@ Returns:         OK/FAIL/DEFER
 int
 lss_match_local_part(uschar *local_part, uschar *list, BOOL caseless)
 {
 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);
 }
 
   MCL_LOCALPART, caseless, NULL);
 }
 
@@ -71,7 +71,7 @@ Returns:         OK/FAIL/DEFER
 int
 lss_match_address(uschar *address, uschar *list, BOOL caseless)
 {
 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)
 {
 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)
 {
 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)
 {
 int
 lss_b64decode(uschar *code, uschar **ptr)
 {
-return auth_b64decode(code, ptr);
+return b64decode(code, ptr);
 }
 
 
 }
 
 
diff --git a/src/macro_predef.c b/src/macro_predef.c
new file mode 100644 (file)
index 0000000..fbc923c
--- /dev/null
@@ -0,0 +1,316 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) Jeremy Harris 1995 - 2018 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+/* Create a static data structure with the predefined macros, to be
+included in the main Exim build */
+
+#include "exim.h"
+#include "macro_predef.h"
+
+unsigned mp_index = 0;
+
+/* Global dummy variables */
+
+void fn_smtp_receive_timeout(const uschar * name, const uschar * str) {}
+uschar * syslog_facility_str;
+
+/******************************************************************************/
+
+void
+builtin_macro_create_var(const uschar * name, const uschar * val)
+{
+printf ("static macro_item p%d = { ", mp_index);
+if (mp_index == 0)
+  printf(".next=NULL,");
+else
+  printf(".next=&p%d,", mp_index-1);
+
+printf(" .command_line=FALSE, .namelen=%d, .replen=%d,"
+       " .name=US\"%s\", .replacement=US\"%s\" };\n",
+       Ustrlen(name), Ustrlen(val), CS name, CS val);
+mp_index++;
+}
+
+
+void
+builtin_macro_create(const uschar * name)
+{
+builtin_macro_create_var(name, US"y");
+}
+
+
+/* restricted snprintf */
+void
+spf(uschar * buf, int len, const uschar * fmt, ...)
+{
+va_list ap;
+va_start(ap, fmt);
+
+while (*fmt && len > 1)
+  if (*fmt == '%' && fmt[1] == 'T')
+    {
+    uschar * s = va_arg(ap, uschar *);
+    while (*s && len-- > 1)
+      *buf++ = toupper(*s++);
+    fmt += 2;
+    }
+  else
+    {
+    *buf++ = *fmt++; len--;
+    }
+*buf = '\0';
+va_end(ap);
+}
+
+void
+options_from_list(optionlist * opts, unsigned nopt,
+  const uschar * section, uschar * group)
+{
+int i;
+const uschar * s;
+uschar buf[64];
+
+/* The 'previously-defined-substring' rule for macros in config file
+lines is done thus 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)
+    spf(buf, sizeof(buf), CUS"_OPT_%T_%T_%T", section, group, s);
+  else
+    spf(buf, sizeof(buf), CUS"_OPT_%T_%T", section, s);
+  builtin_macro_create(buf);
+  }
+}
+
+
+/******************************************************************************/
+
+
+/* Create compile-time feature macros */
+static void
+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
+  builtin_macro_create(US"_HAVE_CRYPTEQ");
+#endif
+#if HAVE_ICONV
+  builtin_macro_create(US"_HAVE_ICONV");
+#endif
+#if HAVE_IPV6
+  builtin_macro_create(US"_HAVE_IPV6");
+#endif
+#ifdef HAVE_SETCLASSRESOURCES
+  builtin_macro_create(US"_HAVE_SETCLASSRESOURCES");
+#endif
+#ifdef SUPPORT_PAM
+  builtin_macro_create(US"_HAVE_PAM");
+#endif
+#ifdef EXIM_PERL
+  builtin_macro_create(US"_HAVE_PERL");
+#endif
+#ifdef EXPAND_DLFUNC
+  builtin_macro_create(US"_HAVE_DLFUNC");
+#endif
+#ifdef USE_TCP_WRAPPERS
+  builtin_macro_create(US"_HAVE_TCPWRAPPERS");
+#endif
+#ifdef SUPPORT_TLS
+  builtin_macro_create(US"_HAVE_TLS");
+# ifdef USE_GNUTLS
+  builtin_macro_create(US"_HAVE_GNUTLS");
+# else
+  builtin_macro_create(US"_HAVE_OPENSSL");
+# endif
+#endif
+#ifdef SUPPORT_TRANSLATE_IP_ADDRESS
+  builtin_macro_create(US"_HAVE_TRANSLATE_IP_ADDRESS");
+#endif
+#ifdef SUPPORT_MOVE_FROZEN_MESSAGES
+  builtin_macro_create(US"_HAVE_MOVE_FROZEN_MESSAGES");
+#endif
+#ifdef WITH_CONTENT_SCAN
+  builtin_macro_create(US"_HAVE_CONTENT_SCANNING");
+#endif
+#ifndef DISABLE_DKIM
+  builtin_macro_create(US"_HAVE_DKIM");
+#endif
+#ifndef DISABLE_DNSSEC
+  builtin_macro_create(US"_HAVE_DNSSEC");
+#endif
+#ifndef DISABLE_EVENT
+  builtin_macro_create(US"_HAVE_EVENT");
+#endif
+#ifdef SUPPORT_I18N
+  builtin_macro_create(US"_HAVE_I18N");
+#endif
+#ifndef DISABLE_OCSP
+  builtin_macro_create(US"_HAVE_OCSP");
+#endif
+#ifndef DISABLE_PRDR
+  builtin_macro_create(US"_HAVE_PRDR");
+#endif
+#ifdef SUPPORT_PROXY
+  builtin_macro_create(US"_HAVE_PROXY");
+#endif
+#ifdef SUPPORT_SOCKS
+  builtin_macro_create(US"_HAVE_SOCKS");
+#endif
+#ifdef TCP_FASTOPEN
+  builtin_macro_create(US"_HAVE_TCP_FASTOPEN");
+#endif
+#ifdef EXPERIMENTAL_LMDB
+  builtin_macro_create(US"_HAVE_LMDB");
+#endif
+#ifdef SUPPORT_SPF
+  builtin_macro_create(US"_HAVE_SPF");
+#endif
+#ifdef EXPERIMENTAL_SRS
+  builtin_macro_create(US"_HAVE_SRS");
+#endif
+#ifdef EXPERIMENTAL_ARC
+  builtin_macro_create(US"_HAVE_ARC");
+#endif
+#ifdef EXPERIMENTAL_BRIGHTMAIL
+  builtin_macro_create(US"_HAVE_BRIGHTMAIL");
+#endif
+#ifdef SUPPORT_DANE
+  builtin_macro_create(US"_HAVE_DANE");
+#endif
+#ifdef EXPERIMENTAL_DCC
+  builtin_macro_create(US"_HAVE_DCC");
+#endif
+#ifdef EXPERIMENTAL_DMARC
+  builtin_macro_create(US"_HAVE_DMARC");
+#endif
+#ifdef EXPERIMENTAL_DSN_INFO
+  builtin_macro_create(US"_HAVE_DSN_INFO");
+#endif
+#ifdef EXPERIMENTAL_REQUIRETLS
+  builtin_macro_create(US"_HAVE_REQTLS");
+#endif
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+  builtin_macro_create(US"_HAVE_PIPE_CONNECT");
+#endif
+
+#ifdef LOOKUP_LSEARCH
+  builtin_macro_create(US"_HAVE_LOOKUP_LSEARCH");
+#endif
+#ifdef LOOKUP_CDB
+  builtin_macro_create(US"_HAVE_LOOKUP_CDB");
+#endif
+#ifdef LOOKUP_DBM
+  builtin_macro_create(US"_HAVE_LOOKUP_DBM");
+#endif
+#ifdef LOOKUP_DNSDB
+  builtin_macro_create(US"_HAVE_LOOKUP_DNSDB");
+#endif
+#ifdef LOOKUP_DSEARCH
+  builtin_macro_create(US"_HAVE_LOOKUP_DSEARCH");
+#endif
+#ifdef LOOKUP_IBASE
+  builtin_macro_create(US"_HAVE_LOOKUP_IBASE");
+#endif
+#ifdef LOOKUP_LDAP
+  builtin_macro_create(US"_HAVE_LOOKUP_LDAP");
+#endif
+#ifdef EXPERIMENTAL_LMDB
+  builtin_macro_create(US"_HAVE_LOOKUP_LMDB");
+#endif
+#ifdef LOOKUP_MYSQL
+  builtin_macro_create(US"_HAVE_LOOKUP_MYSQL");
+#endif
+#ifdef LOOKUP_NIS
+  builtin_macro_create(US"_HAVE_LOOKUP_NIS");
+#endif
+#ifdef LOOKUP_NISPLUS
+  builtin_macro_create(US"_HAVE_LOOKUP_NISPLUS");
+#endif
+#ifdef LOOKUP_ORACLE
+  builtin_macro_create(US"_HAVE_LOOKUP_ORACLE");
+#endif
+#ifdef LOOKUP_PASSWD
+  builtin_macro_create(US"_HAVE_LOOKUP_PASSWD");
+#endif
+#ifdef LOOKUP_PGSQL
+  builtin_macro_create(US"_HAVE_LOOKUP_PGSQL");
+#endif
+#ifdef LOOKUP_REDIS
+  builtin_macro_create(US"_HAVE_LOOKUP_REDIS");
+#endif
+#ifdef LOOKUP_SQLITE
+  builtin_macro_create(US"_HAVE_LOOKUP_SQLITE");
+#endif
+#ifdef LOOKUP_TESTDB
+  builtin_macro_create(US"_HAVE_LOOKUP_TESTDB");
+#endif
+#ifdef LOOKUP_WHOSON
+  builtin_macro_create(US"_HAVE_LOOKUP_WHOSON");
+#endif
+
+#ifdef TRANSPORT_APPENDFILE
+# ifdef SUPPORT_MAILDIR
+  builtin_macro_create(US"_HAVE_TRANSPORT_APPEND_MAILDIR");
+# endif
+# ifdef SUPPORT_MAILSTORE
+  builtin_macro_create(US"_HAVE_TRANSPORT_APPEND_MAILSTORE");
+# endif
+# ifdef SUPPORT_MBX
+  builtin_macro_create(US"_HAVE_TRANSPORT_APPEND_MBX");
+# endif
+#endif
+
+#ifdef WITH_CONTENT_SCAN
+features_malware();
+#endif
+
+features_crypto();
+}
+
+
+static void
+options(void)
+{
+options_main();
+options_routers();
+options_transports();
+options_auths();
+options_logging();
+#if defined(SUPPORT_TLS) && !defined(USE_GNUTLS)
+options_tls();
+#endif
+}
+
+static void
+params(void)
+{
+#ifndef DISABLE_DKIM
+params_dkim();
+#endif
+}
+
+
+int
+main(void)
+{
+printf("#include \"exim.h\"\n");
+features();
+options();
+params();
+
+printf("macro_item * macros = &p%d;\n", mp_index-1);
+printf("macro_item * mlast = &p0;\n");
+exit(0);
+}
diff --git a/src/macro_predef.h b/src/macro_predef.h
new file mode 100644 (file)
index 0000000..f265750
--- /dev/null
@@ -0,0 +1,26 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) Jeremy Harris 2017 - 2018 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+/* Global functions */
+
+extern void spf(uschar *, int, const uschar *, ...);
+extern void builtin_macro_create(const uschar *);
+extern void builtin_macro_create_var(const uschar *, const uschar *);
+extern void options_from_list(optionlist *, unsigned, const uschar *, uschar *);
+
+extern void features_malware(void);
+extern void features_crypto(void);
+extern void options_main(void);
+extern void options_routers(void);
+extern void options_transports(void);
+extern void options_auths(void);
+extern void options_logging(void);
+extern void params_dkim(void);
+#if defined(SUPPORT_TLS) && !defined(USE_GNUTLS)
+extern void options_tls(void);
+#endif
+
index b7dd337..0f93543 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -12,27 +12,31 @@ 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)
 
 #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. */
 
 #define OS_GETLOADAVG() \
 
 /* When running in the test harness, the load average is fudged. */
 
 #define OS_GETLOADAVG() \
-  (running_in_test_harness? (test_harness_load_avg += 10) : os_getloadavg())
+  (f.running_in_test_harness? (test_harness_load_avg += 10) : os_getloadavg())
 
 
 
 
-/* The address_item structure has a word full of 1-bit flags. These macros
+/* The address_item structure has a struct full of 1-bit flags. These macros
 manipulate them. */
 
 manipulate them. */
 
-#define setflag(addr,flag)    addr->flags |= (flag)
-#define clearflag(addr,flag)  addr->flags &= ~(flag)
-
-#define testflag(addr,flag)       ((addr->flags & (flag)) != 0)
-#define testflagsall(addr,flag)   ((addr->flags & (flag)) == (flag))
+#define setflag(addr, flagname)    addr->flags.flagname = TRUE
+#define clearflag(addr, flagname)  addr->flags.flagname = FALSE
 
 
-#define copyflag(addrnew,addrold,flag) \
-  addrnew->flags = (addrnew->flags & ~(flag)) | (addrold->flags & (flag))
+#define testflag(addr, flagname)   (addr->flags.flagname)
 
 
-#define orflag(addrnew,addrold,flag) \
-  addrnew->flags |= addrold->flags & (flag)
+#define copyflag(addrnew, addrold, flagname) \
+  addrnew->flags.flagname = addrold->flags.flagname
 
 
 /* For almost all calls to convert things to printing characters, we want to
 
 
 /* For almost all calls to convert things to printing characters, we want to
@@ -81,7 +85,7 @@ as unsigned. */
 a no-op once an SSL session is in progress. */
 
 #ifdef SUPPORT_TLS
 a no-op once an SSL session is in progress. */
 
 #ifdef SUPPORT_TLS
-#define mac_smtp_fflush() if (tls_in.active < 0) fflush(smtp_out);
+#define mac_smtp_fflush() if (tls_in.active.sock < 0) fflush(smtp_out);
 #else
 #define mac_smtp_fflush() fflush(smtp_out);
 #endif
 #else
 #define mac_smtp_fflush() fflush(smtp_out);
 #endif
@@ -103,8 +107,15 @@ don't make the file descriptors two-way. */
 
 /* Debugging control */
 
 
 /* Debugging control */
 
-#define DEBUG(x)      if ((debug_selector & (x)) != 0)
-#define HDEBUG(x)     if (host_checking || (debug_selector & (x)) != 0)
+#define DEBUG(x)      if (debug_selector & (x))
+#define HDEBUG(x)     if (host_checking || (debug_selector & (x)))
+
+#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 */
 
 
 /* The default From: text for DSNs */
 
@@ -149,13 +160,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
 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
 #elif defined MAXPATHLEN && MAXPATHLEN > 16384
-#define BIG_BUFFER_SIZE MAXPATHLEN
+# define BIG_BUFFER_SIZE MAXPATHLEN
 #else
 #else
-#define BIG_BUFFER_SIZE 16384
+# define BIG_BUFFER_SIZE 16384
 #endif
 
 #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. */
 
 /* 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 +195,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. */
 /* 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
 #define PROXY_NEGOTIATION_TIMEOUT_SEC 3
 #define PROXY_NEGOTIATION_TIMEOUT_USEC 0
 #endif
@@ -209,9 +224,9 @@ enum { tod_log, tod_log_bare, tod_log_zone, tod_log_datestamp_daily,
 /* For identifying types of driver */
 
 enum {
 /* For identifying types of driver */
 
 enum {
-  DTYPE_NONE,
-  DTYPE_ROUTER,
-  DTYPE_TRANSPORT
+  EXIM_DTYPE_NONE,
+  EXIM_DTYPE_ROUTER,
+  EXIM_DTYPE_TRANSPORT
 };
 
 /* Error numbers for generating error messages when reading a message on the
 };
 
 /* Error numbers for generating error messages when reading a message on the
@@ -306,52 +321,100 @@ 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_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 }
 
 
-/* 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. */
+/* 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().
+These must match the debug_options table in globals.c .
+
+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 */
+
+#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(noutf8),
+  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
 
 #define D_any                        (D_all & \
                                        ~(D_v           | \
 
 #define D_all                        0xffffffff
 
 #define D_any                        (D_all & \
                                        ~(D_v           | \
+                                        D_noutf8      | \
                                          D_pid         | \
                                          D_timestamp)  )
 
                                          D_pid         | \
                                          D_timestamp)  )
 
@@ -362,87 +425,83 @@ handle this here. It is fudged externally. */
                                          D_load        | \
                                          D_local_scan  | \
                                          D_memory      | \
                                          D_load        | \
                                          D_local_scan  | \
                                          D_memory      | \
+                                        D_noutf8      | \
                                          D_pid         | \
                                          D_timestamp   | \
                                          D_resolver))
 
                                          D_pid         | \
                                          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_dkim,
+  Li_dkim_verbose,
+  Li_dnssec,
+  Li_ident_timeout,
+  Li_incoming_interface,
+  Li_incoming_port,
+  Li_millisec,
+  Li_outgoing_interface,
+  Li_outgoing_port,
+  Li_pid,
+  Li_pipelining,
+  Li_proxy,
+  Li_queue_time,
+  Li_queue_time_overall,
+  Li_receive_time,
+  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
 
 /* 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)
 
 #define ERRNO_UNKNOWNERROR    (-1)
 #define ERRNO_USERSLASH       (-2)
@@ -475,7 +534,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_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 */
 #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,15 +550,28 @@ 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_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
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+# define ERRNO_REQUIRETLS    (-50)   /* REQUIRETLS session not started */
+#endif
 
 /* These must be last, so all retry deferments can easily be identified */
 
 #define ERRNO_RETRY_BASE     (-51)   /* Base to test against */
 #define ERRNO_RRETRY         (-51)   /* Not time for routing */
 
 /* These must be last, so all retry deferments can easily be identified */
 
 #define ERRNO_RETRY_BASE     (-51)   /* Base to test against */
 #define ERRNO_RRETRY         (-51)   /* Not time for routing */
+
+#define ERRNO_WARN_BASE      (-52)   /* Base to test against */
 #define ERRNO_LRETRY         (-52)   /* Not time for local delivery */
 #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_LRETRY         (-52)   /* Not time for local delivery */
 #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. */
 
 
 /* Special actions to take after failure or deferment. */
 
@@ -623,7 +695,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,
 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
 
 /* 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
@@ -659,7 +731,8 @@ enum { v_none, v_sender, v_recipient, v_expn };
 #define vopt_callout_no_cache     0x0040   /* disable callout cache */
 #define vopt_callout_recipsender  0x0080   /* use real sender to verify recip */
 #define vopt_callout_recippmaster 0x0100   /* use postmaster to verify recip */
 #define vopt_callout_no_cache     0x0040   /* disable callout cache */
 #define vopt_callout_recipsender  0x0080   /* use real sender to verify recip */
 #define vopt_callout_recippmaster 0x0100   /* use postmaster to verify recip */
-#define vopt_success_on_redirect  0x0200
+#define vopt_callout_hold        0x0200   /* lazy close connection */
+#define vopt_success_on_redirect  0x0400
 
 /* Values for fields in callout cache records */
 
 
 /* Values for fields in callout cache records */
 
@@ -682,11 +755,17 @@ enum { hstatus_unknown, hstatus_usable, hstatus_unusable,
 
 /* Reasons why a host is unusable (for clearer log messages) */
 
 
 /* Reasons why a host is unusable (for clearer log messages) */
 
-enum { hwhy_unknown, hwhy_retry, hwhy_failed, hwhy_deferred, hwhy_ignored };
+enum { hwhy_unknown, hwhy_retry, hwhy_insecure, hwhy_failed, hwhy_deferred,
+       hwhy_ignored };
 
 /* Domain lookup types for routers */
 
 
 /* Domain lookup types for routers */
 
-enum { lk_default, lk_byname, lk_bydns };
+#define LK_DEFAULT     BIT(0)
+#define LK_BYNAME      BIT(1)
+#define LK_BYDNS       BIT(2)  /* those 3 should be mutually exclusive */
+
+#define LK_IPV4_ONLY   BIT(3)
+#define LK_IPV4_PREFER BIT(4)
 
 /* Values for the self_code fields */
 
 
 /* Values for the self_code fields */
 
@@ -737,7 +816,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". */
 
 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 };
 
        SCH_HELP, SCH_MAIL, SCH_NOOP, SCH_QUIT, SCH_RCPT, SCH_RSET, SCH_STARTTLS,
        SCH_VRFY };
 
@@ -746,6 +826,7 @@ enum { SCH_NONE, SCH_AUTH, SCH_DATA, SCH_EHLO, SCH_ETRN, SCH_EXPN, SCH_HELO,
 enum {
   HOST_FIND_FAILED,     /* failed to find the host */
   HOST_FIND_AGAIN,      /* could not resolve at this time */
 enum {
   HOST_FIND_FAILED,     /* failed to find the host */
   HOST_FIND_AGAIN,      /* could not resolve at this time */
+  HOST_FIND_SECURITY,   /* dnssec required but not acheived */
   HOST_FOUND,           /* found host */
   HOST_FOUND_LOCAL,     /* found, but MX points to local host */
   HOST_IGNORED          /* found but ignored - used internally only */
   HOST_FOUND,           /* found host */
   HOST_FOUND_LOCAL,     /* found, but MX points to local host */
   HOST_IGNORED          /* found but ignored - used internally only */
@@ -753,11 +834,14 @@ enum {
 
 /* Flags for host_find_bydns() */
 
 
 /* Flags for host_find_bydns() */
 
-#define HOST_FIND_BY_SRV          0x0001
-#define HOST_FIND_BY_MX           0x0002
-#define HOST_FIND_BY_A            0x0004
-#define HOST_FIND_QUALIFY_SINGLE  0x0008
-#define HOST_FIND_SEARCH_PARENTS  0x0010
+#define HOST_FIND_BY_SRV          BIT(0)
+#define HOST_FIND_BY_MX           BIT(1)
+#define HOST_FIND_BY_A            BIT(2)
+#define HOST_FIND_BY_AAAA         BIT(3)
+#define HOST_FIND_QUALIFY_SINGLE  BIT(4)
+#define HOST_FIND_SEARCH_PARENTS  BIT(5)
+#define HOST_FIND_IPV4_FIRST     BIT(6)
+#define HOST_FIND_IPV4_ONLY      BIT(7)
 
 /* Actions applied to specific messages. */
 
 
 /* Actions applied to specific messages. */
 
@@ -783,12 +867,23 @@ enum {
 #define topt_add_delivery_date  0x002
 #define topt_add_envelope_to    0x004
 #define topt_use_crlf           0x008  /* Terminate lines with CRLF */
 #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_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 */
+#define topt_output_string     0x200  /* create string rather than write to fd */
+#define topt_continuation      0x400  /* do not reset buffer */
+#define topt_not_socket                0x800  /* cannot do socket-only syscalls */
+
+/* Options for smtp_write_command */
+
+enum { 
+  SCMD_FLUSH = 0,      /* write to kernel */
+  SCMD_MORE,           /* write to kernel, but likely more soon */
+  SCMD_BUFFER          /* stash in application cmd output buffer */
+};
 
 
-#ifdef EXPERIMENTAL_DSN
 /* Flags for recipient_block, used in DSN support */
 
 #define rf_dsnlasthop           0x01  /* Do not propagate DSN any further */
 /* Flags for recipient_block, used in DSN support */
 
 #define rf_dsnlasthop           0x01  /* Do not propagate DSN any further */
@@ -809,7 +904,6 @@ enum {
 #define dsn_support_yes         1
 #define dsn_support_no          2
 
 #define dsn_support_yes         1
 #define dsn_support_no          2
 
-#endif
 
 /* Codes for the host_find_failed and host_all_ignored options. */
 
 
 /* Codes for the host_find_failed and host_all_ignored options. */
 
@@ -870,6 +964,31 @@ enum { ACL_WHERE_RCPT,       /* Some controls are for RCPT only */
        ACL_WHERE_UNKNOWN     /* Currently used by a ${acl:name} expansion */
      };
 
        ACL_WHERE_UNKNOWN     /* Currently used by a ${acl:name} expansion */
      };
 
+#define ACL_BIT_RCPT           BIT(ACL_WHERE_RCPT)
+#define ACL_BIT_MAIL           BIT(ACL_WHERE_MAIL)
+#define ACL_BIT_PREDATA                BIT(ACL_WHERE_PREDATA)
+#define ACL_BIT_MIME           BIT(ACL_WHERE_MIME)
+#define ACL_BIT_DKIM           BIT(ACL_WHERE_DKIM)
+#define ACL_BIT_DATA           BIT(ACL_WHERE_DATA)
+#ifndef DISABLE_PRDR
+# define ACL_BIT_PRDR          BIT(ACL_WHERE_PRDR)
+#endif
+#define ACL_BIT_NOTSMTP                BIT(ACL_WHERE_NOTSMTP)
+#define ACL_BIT_AUTH           BIT(ACL_WHERE_AUTH)
+#define ACL_BIT_CONNECT                BIT(ACL_WHERE_CONNECT)
+#define ACL_BIT_ETRN           BIT(ACL_WHERE_ETRN)
+#define ACL_BIT_EXPN           BIT(ACL_WHERE_EXPN)
+#define ACL_BIT_HELO           BIT(ACL_WHERE_HELO)
+#define ACL_BIT_MAILAUTH       BIT(ACL_WHERE_MAILAUTH)
+#define ACL_BIT_NOTSMTP_START  BIT(ACL_WHERE_NOTSMTP_START)
+#define ACL_BIT_NOTQUIT                BIT(ACL_WHERE_NOTQUIT)
+#define ACL_BIT_QUIT           BIT(ACL_WHERE_QUIT)
+#define ACL_BIT_STARTTLS       BIT(ACL_WHERE_STARTTLS)
+#define ACL_BIT_VRFY           BIT(ACL_WHERE_VRFY)
+#define ACL_BIT_DELIVERY       BIT(ACL_WHERE_DELIVERY)
+#define ACL_BIT_UNKNOWN                BIT(ACL_WHERE_UNKNOWN)
+
+
 /* Situations for spool_write_header() */
 
 enum { SW_RECEIVING, SW_DELIVERING, SW_MODIFYING };
 /* Situations for spool_write_header() */
 
 enum { SW_RECEIVING, SW_DELIVERING, SW_MODIFYING };
@@ -895,4 +1014,62 @@ explicit port number. */
 
 enum { FILTER_UNSET, FILTER_FORWARD, FILTER_EXIM, FILTER_SIEVE };
 
 
 enum { FILTER_UNSET, FILTER_FORWARD, FILTER_EXIM, FILTER_SIEVE };
 
+/* Codes for ESMTP facilities offered by peer */
+
+#define OPTION_TLS             BIT(0)
+#define OPTION_IGNQ            BIT(1)
+#define OPTION_PRDR            BIT(2)
+#define OPTION_UTF8            BIT(3)
+#define OPTION_DSN             BIT(4)
+#define OPTION_PIPE            BIT(5)
+#define OPTION_SIZE            BIT(6)
+#define OPTION_CHUNKING                BIT(7)
+#define OPTION_REQUIRETLS      BIT(8)
+#define OPTION_EARLY_PIPE      BIT(9)
+
+/* Codes for tls_requiretls requests (usually by sender) */
+
+#define REQUIRETLS_MSG         BIT(0)  /* REQUIRETLS onward use */
+
+/* Argument for *_getc */
+
+#define GETC_BUFFER_UNLIMITED  UINT_MAX
+
+/* UTF-8 chars for line-drawing */
+
+#define UTF8_DOWN_RIGHT                "\xE2\x94\x8c"
+#define UTF8_HORIZ             "\xE2\x94\x80"
+#define UTF8_VERT_RIGHT                "\xE2\x94\x9C"
+#define UTF8_UP_RIGHT          "\xE2\x94\x94"
+#define UTF8_VERT_2DASH                "\xE2\x95\x8E"
+
+
+/* Options on tls_close */
+#define TLS_NO_SHUTDOWN                0
+#define TLS_SHUTDOWN_NOWAIT    1
+#define TLS_SHUTDOWN_WAIT      2
+
+
+#ifdef COMPILE_UTILITY
+# define ALARM(seconds) alarm(seconds);
+# define ALARM_CLR(seconds) alarm(seconds);
+#else
+/* For debugging of odd alarm-signal problems, stash caller info while the
+alarm is active.  Clear it down on cancelling the alarm so we can tell there
+should not be one active. */
+
+# define ALARM(seconds) \
+    debug_selector & D_any \
+    ? (sigalarm_setter = CUS __FUNCTION__, alarm(seconds)) : alarm(seconds);
+# define ALARM_CLR(seconds) \
+    debug_selector & D_any \
+    ? (sigalarm_setter = NULL, alarm(seconds)) : alarm(seconds);
+#endif
+
+#define AUTHS_REGEX US"\\n250[\\s\\-]AUTH\\s+([\\-\\w \\t]+)(?:\\n|$)"
+
+#define EARLY_PIPE_FEATURE_NAME "X_PIPE_CONNECT"
+#define EARLY_PIPE_FEATURE_LEN  14
+
+
 /* End of macros.h */
 /* End of macros.h */
dissimilarity index 86%
index 04b5887..6adcf9b 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 2015 - 2018
+ */
+
+/* Code for calling virus (malware) scanners. Called from acl.c. */
+
+#include "exim.h"
+#ifdef WITH_CONTENT_SCAN       /* entire file */
+
+typedef enum {
+#ifndef DISABLE_MAL_FFROTD
+       M_FPROTD,
+#endif
+#ifndef DISABLE_MAL_FFROT6D
+       M_FPROT6D,
+#endif
+#ifndef DISABLE_MAL_DRWEB
+       M_DRWEB,
+#endif
+#ifndef DISABLE_MAL_AVE
+       M_AVES,
+#endif
+#ifndef DISABLE_MAL_FSECURE
+       M_FSEC,
+#endif
+#ifndef DISABLE_MAL_KAV
+       M_KAVD,
+#endif
+#ifndef DISABLE_MAL_SOPHIE
+       M_SOPHIE,
+#endif
+#ifndef DISABLE_MAL_CLAM
+       M_CLAMD,
+#endif
+#ifndef DISABLE_MAL_MKS
+       M_MKSD,
+#endif
+#ifndef DISABLE_MAL_AVAST
+       M_AVAST,
+#endif
+#ifndef DISABLE_MAL_SOCK
+       M_SOCK,
+#endif
+#ifndef DISABLE_MAL_CMDLINE
+       M_CMDL,
+#endif
+       M_DUMMY
+       } 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[] =
+{
+#ifndef DISABLE_MAL_FFROTD
+  { M_FPROTD,  US"f-protd",    US"localhost 10200-10204",            MC_TCP },
+#endif
+#ifndef DISABLE_MAL_FFROT6D
+  { M_FPROT6D, US"f-prot6d",   US"localhost 10200",                  MC_TCP },
+#endif
+#ifndef DISABLE_MAL_DRWEB
+  { M_DRWEB,   US"drweb",      US"/usr/local/drweb/run/drwebd.sock", MC_STRM },
+#endif
+#ifndef DISABLE_MAL_AVE
+  { M_AVES,    US"aveserver",  US"/var/run/aveserver",               MC_UNIX },
+#endif
+#ifndef DISABLE_MAL_FSECURE
+  { M_FSEC,    US"fsecure",    US"/var/run/.fsav",                   MC_UNIX },
+#endif
+#ifndef DISABLE_MAL_KAV
+  { M_KAVD,    US"kavdaemon",  US"/var/run/AvpCtl",                  MC_UNIX },
+#endif
+#ifndef DISABLE_MAL_SOPHIE
+  { M_SOPHIE,  US"sophie",     US"/var/run/sophie",                  MC_UNIX },
+#endif
+#ifndef DISABLE_MAL_CLAM
+  { M_CLAMD,   US"clamd",      US"/tmp/clamd",                       MC_NONE },
+#endif
+#ifndef DISABLE_MAL_MKS
+  { M_MKSD,    US"mksd",       NULL,                                 MC_NONE },
+#endif
+#ifndef DISABLE_MAL_AVAST
+  { M_AVAST,   US"avast",      US"/var/run/avast/scan.sock",         MC_STRM },
+#endif
+#ifndef DISABLE_MAL_SOCK
+  { M_SOCK,    US"sock",       US"/tmp/malware.sock",                MC_STRM },
+#endif
+#ifndef DISABLE_MAL_CMDLINE
+  { M_CMDL,    US"cmdline",    NULL,                                 MC_NONE },
+#endif
+  { -1,                NULL,           NULL, MC_NONE }         /* end-marker */
+};
+
+/******************************************************************************/
+# ifdef MACRO_PREDEF           /* build solely to predefine macros */
+
+#  include "macro_predef.h"
+
+void
+features_malware(void)
+{
+const struct scan * sc;
+const uschar * s;
+uschar * t;
+uschar buf[64];
+
+spf(buf, sizeof(buf), US"_HAVE_MALWARE_");
+
+for (sc = m_scans; sc->scancode != -1; sc++)
+  {
+  for(s = sc->name, t = buf+14; *s; s++) if (*s != '-') *t++ = toupper(*s);
+  *t = '\0';
+  builtin_macro_create(buf);
+  }
+}
+
+/******************************************************************************/
+# else /*!MACRO_PREDEF, main build*/
+
+
+#define MALWARE_TIMEOUT 120    /* default timeout, seconds */
+
+static const uschar * malware_regex_default = US ".+";
+static const pcre * malware_default_re = NULL;
+
+
+
+#ifndef DISABLE_MAL_CLAM
+/* 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;
+#endif
+
+
+#ifndef DISABLE_MAL_DRWEB
+# 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 * drweb_re_str = US "infected\\swith\\s*(.+?)$";
+static const pcre * drweb_re = NULL;
+#endif
+
+#ifndef DISABLE_MAL_FSECURE
+static const uschar * fsec_re_str = US "\\S{0,5}INFECTED\\t[^\\t]*\\t([^\\t]+)\\t\\S*$";
+static const pcre * fsec_re = NULL;
+#endif
+
+#ifndef DISABLE_MAL_KAV
+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;
+#endif
+
+#ifndef DISABLE_MAL_AVAST
+static const uschar * ava_re_clean_str = US "(?!\\\\)\\t\\[\\+\\]";
+static const uschar * ava_re_virus_str = US "(?!\\\\)\\t\\[L\\]\\d+\\.0\\t0\\s(.*)";
+static const uschar * ava_re_error_str = US "(?!\\\\)\\t\\[E\\]\\d+\\.0\\tError\\s\\d+\\s(.*)";
+static const pcre * ava_re_clean = NULL;
+static const pcre * ava_re_virus = NULL;
+static const pcre * ava_re_error = NULL;
+#endif
+
+#ifndef DISABLE_MAL_FFROT6D
+static const uschar * fprot6d_re_error_str = US "^\\d+\\s<(.+?)>$";
+static const uschar * fprot6d_re_virus_str = US "^\\d+\\s<infected:\\s+(.+?)>\\s+.+$";
+static const pcre * fprot6d_re_error = NULL;
+static const pcre * fprot6d_re_virus = NULL;
+#endif
+
+
+
+/******************************************************************************/
+
+#ifndef DISABLE_MAL_KAV
+/* 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 = CS  &word;
+  return(byte[0] ? LITTLE_MY_ENDIAN : BIG_MY_ENDIAN);
+}
+#endif
+
+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];
+
+
+/* Some (currently avast only) use backslash escaped whitespace,
+this function undoes these escapes */
+
+static inline void
+unescape(uschar *p)
+{
+uschar *p0;
+for (; *p; ++p)
+  if (*p == '\\' && (isspace(p[1]) || p[1] == '\\'))
+    for (p0 = p; *p0; ++p0) *p0 = p0[1];
+}
+
+/* --- malware_*_defer --- */
+static inline int
+malware_panic_defer(const uschar * str)
+{
+log_write(0, LOG_MAIN|LOG_PANIC, "malware acl condition: %s", str);
+return DEFER;
+}
+static inline int
+malware_log_defer(const uschar * str)
+{
+log_write(0, LOG_MAIN, "malware acl condition: %s", str);
+return DEFER;
+}
+/* --- m_*_defer --- */
+static inline int
+m_panic_defer(struct scan * scanent, const uschar * hostport,
+  const uschar * str)
+{
+return malware_panic_defer(string_sprintf("%s %s : %s",
+  scanent->name, hostport ? hostport : CUS"", str));
+}
+static inline int
+m_log_defer(struct scan * scanent, const uschar * hostport,
+  const uschar * str)
+{
+return malware_log_defer(string_sprintf("%s %s : %s",
+  scanent->name, hostport ? hostport : CUS"", str));
+}
+/* --- m_*_defer_3 */
+static inline int
+m_panic_defer_3(struct scan * scanent, const uschar * hostport,
+  const uschar * str, int fd_to_close)
+{
+(void) close(fd_to_close);
+return m_panic_defer(scanent, hostport, str);
+}
+
+/*************************************************/
+
+#ifndef DISABLE_MAL_CLAM
+/* 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, const blob * fastopen_blob)
+{
+return ip_connectedsocket(SOCK_STREAM, hostname, port, port, 5,
+                         host, errstr, fastopen_blob);
+}
+#endif
+
+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, nelem(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
+  {
+  DEBUG(D_acl) debug_printf_indent("%15s%10s'%s'\n", "", "RE: ",
+    string_printing(list_ele));
+  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;
+}
+
+
+
+#ifndef DISABLE_MAL_MKS
+/* ============= 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_panic_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)
+{
+client_conn_ctx cctx = {.sock = sock};
+int offset = 0;
+int i;
+
+do
+  {
+  i = ip_recv(&cctx, av_buffer+offset, av_buffer_size-offset, tmo-time(NULL));
+  if (i <= 0)
+    {
+    (void) malware_panic_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_panic_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_panic_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_panic_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);
+}
+#endif /* MKSD */
+
+
+#ifndef DISABLE_MAL_CLAM
+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;
+}
+#endif
+
+
+
+/*************************************************
+*          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="
+  scan_filename  the file holding the email to be scanned, if we're faking
+               this up for the -bmalware test, else NULL
+  timeout      if nonzero, non-default timeoutl
+
+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 * scan_filename,
+  int timeout)
+{
+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;
+client_conn_ctx malware_daemon_ctx = {.sock = -1};
+time_t tmo;
+uschar * eml_filename, * eml_dir;
+
+if (!malware_re)
+  return FAIL;         /* empty means "don't match anything" */
+
+/* Ensure the eml mbox file is spooled up */
+
+if (!(mbox_file = spool_mbox(&mbox_size, scan_filename, &eml_filename)))
+  return malware_panic_defer(US"error while creating mbox spool file");
+
+/* None of our current scanners need the mbox file as a stream (they use
+the name), so we can close it right away.  Get the directory too. */
+
+(void) fclose(mbox_file);
+eml_dir = string_copyn(eml_filename, Ustrrchr(eml_filename, '/') - eml_filename);
+
+/* 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_panic_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_panic_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_panic_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_panic_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_panic_defer(string_sprintf("unknown scanner type '%s'",
+       scanner_name));
+    if (strcmpic(scanner_name, US scanent->name) != 0)
+      continue;
+    DEBUG(D_acl) debug_printf_indent("Malware scan:  %s tmo=%s\n",
+      scanner_name, readconf_printtime(timeout));
+
+    if (!(scanner_options = string_nextinlist(&av_scanner_work, &sep, NULL, 0)))
+      scanner_options = scanent->options_default;
+    if (scanent->conn == MC_NONE)
+      break;
+
+    DEBUG(D_acl) debug_printf_indent("%15s%10s%s\n", "", "socket: ", scanner_options);
+    switch(scanent->conn)
+    {
+    case MC_TCP:
+      malware_daemon_ctx.sock = ip_tcpsocket(scanner_options, &errstr, 5);     break;
+    case MC_UNIX:
+      malware_daemon_ctx.sock = ip_unixsocket(scanner_options, &errstr);       break;
+    case MC_STRM:
+      malware_daemon_ctx.sock = ip_streamsocket(scanner_options, &errstr, 5);  break;
+    default:
+      /* compiler quietening */ break;
+    }
+    if (malware_daemon_ctx.sock < 0)
+      return m_panic_defer(scanent, CUS callout_address, errstr);
+    break;
+  }
+
+  switch (scanent->scancode)
+    {
+#ifndef DISABLE_MAL_FFROTD
+    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(malware_daemon_ctx.sock, scanrequest, Ustrlen(scanrequest)+1, &errstr) < 0)
+       return m_panic_defer(scanent, CUS callout_address, errstr);
+
+      while ((len = recv_line(malware_daemon_ctx.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(malware_daemon_ctx.sock);
+       return DEFER;
+       }
+      break;
+      }        /* f-protd */
+#endif
+
+#ifndef DISABLE_MAL_FFROT6D
+    case M_FPROT6D: /* "f-prot6d" scanner type ----------------------------------- */
+      {
+      int bread;
+      uschar * e;
+      uschar * linebuffer;
+      uschar * scanrequest;
+      uschar av_buffer[1024];
+
+      if ((!fprot6d_re_virus && !(fprot6d_re_virus = m_pcre_compile(fprot6d_re_virus_str, &errstr)))
+        || (!fprot6d_re_error && !(fprot6d_re_error = m_pcre_compile(fprot6d_re_error_str, &errstr))))
+        return malware_panic_defer(errstr);
+
+      scanrequest = string_sprintf("SCAN FILE %s\n", eml_filename);
+      DEBUG(D_acl) debug_printf_indent("Malware scan: issuing %s: %s\n",
+        scanner_name, scanrequest);
+
+      if (m_sock_send(malware_daemon_ctx.sock, scanrequest, Ustrlen(scanrequest), &errstr) < 0)
+        return m_panic_defer(scanent, CUS callout_address, errstr);
+
+      bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo-time(NULL));
+
+      if (bread <= 0)
+        return m_panic_defer_3(scanent, CUS callout_address,
+          string_sprintf("unable to read from socket (%s)", strerror(errno)),
+          malware_daemon_ctx.sock);
+
+      if (bread == sizeof(av_buffer))
+        return m_panic_defer_3(scanent, CUS callout_address,
+          US"buffer too small", malware_daemon_ctx.sock);
+
+      av_buffer[bread] = '\0';
+      linebuffer = string_copy(av_buffer);
+
+      m_sock_send(malware_daemon_ctx.sock, US"QUIT\n", 5, 0);
+
+      if ((e = m_pcre_exec(fprot6d_re_error, linebuffer)))
+        return m_panic_defer_3(scanent, CUS callout_address,
+          string_sprintf("scanner reported error (%s)", e), malware_daemon_ctx.sock);
+
+      if (!(malware_name = m_pcre_exec(fprot6d_re_virus, linebuffer)))
+        malware_name = NULL;
+
+      break;
+      }  /* f-prot6d */
+#endif
+
+#ifndef DISABLE_MAL_DRWEB
+    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_panic_defer_3(scanent, NULL,
+           string_sprintf("can't open spool file %s: %s",
+             eml_filename, strerror(errno)),
+           malware_daemon_ctx.sock);
+
+       if ((fsize = lseek(drweb_fd, 0, SEEK_END)) == -1)
+         {
+         int err;
+badseek:  err = errno;
+         (void)close(drweb_fd);
+         return m_panic_defer_3(scanent, NULL,
+           string_sprintf("can't seek spool file %s: %s",
+             eml_filename, strerror(err)),
+           malware_daemon_ctx.sock);
+         }
+       fsize_uint = (unsigned int) fsize;
+       if ((off_t)fsize_uint != fsize)
+         {
+         (void)close(drweb_fd);
+         return m_panic_defer_3(scanent, NULL,
+           string_sprintf("seeking spool file %s, size overflow",
+             eml_filename),
+           malware_daemon_ctx.sock);
+         }
+       drweb_slen = htonl(fsize);
+       if (lseek(drweb_fd, 0, SEEK_SET) < 0)
+         goto badseek;
+
+       DEBUG(D_acl) debug_printf_indent("Malware scan: issuing %s remote scan [%s]\n",
+           scanner_name, scanner_options);
+
+       /* send scan request */
+       if ((send(malware_daemon_ctx.sock, &drweb_cmd, sizeof(drweb_cmd), 0) < 0) ||
+           (send(malware_daemon_ctx.sock, &drweb_flags, sizeof(drweb_flags), 0) < 0) ||
+           (send(malware_daemon_ctx.sock, &drweb_fin, sizeof(drweb_fin), 0) < 0) ||
+           (send(malware_daemon_ctx.sock, &drweb_slen, sizeof(drweb_slen), 0) < 0))
+         {
+         (void)close(drweb_fd);
+         return m_panic_defer_3(scanent, CUS callout_address, string_sprintf(
+           "unable to send commands to socket (%s)", scanner_options),
+           malware_daemon_ctx.sock);
+         }
+
+       if (!(drweb_fbuf = US malloc(fsize_uint)))
+         {
+         (void)close(drweb_fd);
+         return m_panic_defer_3(scanent, NULL,
+           string_sprintf("unable to allocate memory %u for file (%s)",
+             fsize_uint, eml_filename),
+           malware_daemon_ctx.sock);
+         }
+
+       if ((result = read (drweb_fd, drweb_fbuf, fsize)) == -1)
+         {
+         int err = errno;
+         (void)close(drweb_fd);
+         free(drweb_fbuf);
+         return m_panic_defer_3(scanent, NULL,
+           string_sprintf("can't read spool file %s: %s",
+             eml_filename, strerror(err)),
+           malware_daemon_ctx.sock);
+         }
+       (void)close(drweb_fd);
+
+       /* send file body to socket */
+       if (send(malware_daemon_ctx.sock, drweb_fbuf, fsize, 0) < 0)
+         {
+         free(drweb_fbuf);
+         return m_panic_defer_3(scanent, CUS callout_address, string_sprintf(
+           "unable to send file body to socket (%s)", scanner_options),
+           malware_daemon_ctx.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(malware_daemon_ctx.sock, &drweb_cmd, sizeof(drweb_cmd), 0) < 0) ||
+           (send(malware_daemon_ctx.sock, &drweb_flags, sizeof(drweb_flags), 0) < 0) ||
+           (send(malware_daemon_ctx.sock, &drweb_slen, sizeof(drweb_slen), 0) < 0) ||
+           (send(malware_daemon_ctx.sock, eml_filename, Ustrlen(eml_filename), 0) < 0) ||
+           (send(malware_daemon_ctx.sock, &drweb_fin, sizeof(drweb_fin), 0) < 0))
+         return m_panic_defer_3(scanent, CUS callout_address, string_sprintf(
+           "unable to send commands to socket (%s)", scanner_options),
+           malware_daemon_ctx.sock);
+       }
+
+      /* wait for result */
+      if (!recv_len(malware_daemon_ctx.sock, &drweb_rc, sizeof(drweb_rc), tmo))
+       return m_panic_defer_3(scanent, CUS callout_address,
+                   US"unable to read return code", malware_daemon_ctx.sock);
+      drweb_rc = ntohl(drweb_rc);
+
+      if (!recv_len(malware_daemon_ctx.sock, &drweb_vnum, sizeof(drweb_vnum), tmo))
+       return m_panic_defer_3(scanent, CUS callout_address,
+                           US"unable to read the number of viruses", malware_daemon_ctx.sock);
+      drweb_vnum = ntohl(drweb_vnum);
+
+      /* "virus(es) found" if virus number is > 0 */
+      if (drweb_vnum)
+       {
+       int i;
+       gstring * g = NULL;
+
+       /* 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 ovector[10*3];
+
+         /* read the size of report */
+         if (!recv_len(malware_daemon_ctx.sock, &drweb_slen, sizeof(drweb_slen), tmo))
+           return m_panic_defer_3(scanent, CUS callout_address,
+                             US"cannot read report size", malware_daemon_ctx.sock);
+         drweb_slen = ntohl(drweb_slen);
+         tmpbuf = store_get(drweb_slen);
+
+         /* read report body */
+         if (!recv_len(malware_daemon_ctx.sock, tmpbuf, drweb_slen, tmo))
+           return m_panic_defer_3(scanent, CUS callout_address,
+                             US"cannot read report string", malware_daemon_ctx.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, nelem(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 */
+             g = string_cat(NULL, US pre_malware_nb);
+
+           /*XXX could be string_append_listele? */
+           else        /* concatenate each new virus name to previous */
+             g = string_append(g, 2, "/", pre_malware_nb);
+
+           pcre_free_substring(pre_malware_nb);
+           }
+         }
+         malware_name = string_from_gstring(g);
+       }
+      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_panic_defer_3(scanent, CUS callout_address,
+           string_sprintf("drweb daemon retcode 0x%x (%s)", drweb_rc, drweb_s),
+           malware_daemon_ctx.sock);
+
+       /* no virus found */
+       malware_name = NULL;
+       }
+      break;
+      }        /* drweb */
+#endif
+
+#ifndef DISABLE_MAL_AVE
+    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(malware_daemon_ctx.sock, buf, sizeof(buf), tmo);
+
+      if (buf[0] != '2')               /* aveserver is having problems */
+       return m_panic_defer_3(scanent, CUS callout_address,
+         string_sprintf("unavailable (Responded: %s).",
+                         ((buf[0] != 0) ? buf : US "nothing") ),
+         malware_daemon_ctx.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(malware_daemon_ctx.sock, buf, Ustrlen(buf), &errstr) < 0)
+       return m_panic_defer(scanent, CUS callout_address, errstr);
+
+      malware_name = NULL;
+      result = 0;
+      /* read response lines, find malware name and final response */
+      while (recv_line(malware_daemon_ctx.sock, buf, sizeof(buf), tmo) > 0)
+       {
+       if (buf[0] == '2')
+         break;
+       if (buf[0] == '5')              /* aveserver is having problems */
+         {
+         result = m_panic_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(malware_daemon_ctx.sock, US"quit\r\n", 6, &errstr) < 0)
+       return m_panic_defer(scanent, CUS callout_address, errstr);
+
+      /* read aveserver's greeting and see if it is ready (2xx greeting) */
+      buf[0] = 0;
+      recv_line(malware_daemon_ctx.sock, buf, sizeof(buf), tmo);
+
+      if (buf[0] != '2')               /* aveserver is having problems */
+       return m_panic_defer_3(scanent, CUS callout_address,
+         string_sprintf("unable to quit dialogue (Responded: %s).",
+                       ((buf[0] != 0) ? buf : US "nothing") ),
+         malware_daemon_ctx.sock);
+
+      if (result == DEFER)
+       {
+       (void)close(malware_daemon_ctx.sock);
+       return DEFER;
+       }
+      break;
+      }        /* aveserver */
+#endif
+
+#ifndef DISABLE_MAL_FSECURE
+    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 != nelem(cmdopt); i++)
+       {
+
+       if (m_sock_send(malware_daemon_ctx.sock, cmdopt[i], Ustrlen(cmdopt[i]), &errstr) < 0)
+         return m_panic_defer(scanent, CUS callout_address, errstr);
+
+       bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo-time(NULL));
+       if (bread > 0) av_buffer[bread]='\0';
+       if (bread < 0)
+         return m_panic_defer_3(scanent, CUS callout_address,
+           string_sprintf("unable to read answer %d (%s)", i, strerror(errno)),
+           malware_daemon_ctx.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(malware_daemon_ctx.sock, file_name, Ustrlen(file_name), &errstr) < 0)
+       return m_panic_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(&malware_daemon_ctx, p, i-1, tmo-time(NULL))) < 0)
+           return m_panic_defer_3(scanent, CUS callout_address,
+             string_sprintf("unable to read result (%s)", strerror(errno)),
+             malware_daemon_ctx.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 */
+#endif
+
+#ifndef DISABLE_MAL_KAV
+    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(malware_daemon_ctx.sock, scanrequest, Ustrlen(scanrequest)+1, &errstr) < 0)
+       return m_panic_defer(scanent, CUS callout_address, errstr);
+
+      /* wait for result */
+      if (!recv_len(malware_daemon_ctx.sock, tmpbuf, 2, tmo))
+       return m_panic_defer_3(scanent, CUS callout_address,
+                           US"unable to read 2 bytes from socket.", malware_daemon_ctx.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_panic_defer_3(scanent, CUS callout_address,
+               US"please reconfigure kavdaemon to NOT disinfect or remove infected files.",
+               malware_daemon_ctx.sock);
+      case 1:
+       return m_panic_defer_3(scanent, CUS callout_address,
+               US"reported 'scanning not completed' (code 1).", malware_daemon_ctx.sock);
+      case 7:
+       return m_panic_defer_3(scanent, CUS callout_address,
+               US"reported 'kavdaemon damaged' (code 7).", malware_daemon_ctx.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(malware_daemon_ctx.sock, &kav_reportlen, 4, tmo))
+           return m_panic_defer_3(scanent, CUS callout_address,
+                 US"cannot read report size", malware_daemon_ctx.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(malware_daemon_ctx.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;
+      }
+#endif
+
+#ifndef DISABLE_MAL_CMDLINE
+    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_panic_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_panic_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_panic_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_panic_defer(scanent, NULL,
+         string_sprintf("call (%s) failed: %s.", commandline, strerror(err)));
+       }
+      scanner_fd = fileno(scanner_out);
+
+      file_name = string_sprintf("%s/%s_scanner_output", eml_dir, 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_panic_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_panic_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_panic_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_panic_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 */
+#endif
+
+#ifndef DISABLE_MAL_SOPHIE
+    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(malware_daemon_ctx.sock, file_name, Ustrlen(file_name)) < 0
+        || write(malware_daemon_ctx.sock, "\n", 1) != 1
+        )
+       return m_panic_defer_3(scanent, CUS callout_address,
+         string_sprintf("unable to write to UNIX socket (%s)", scanner_options),
+         malware_daemon_ctx.sock);
+
+      /* wait for result */
+      memset(av_buffer, 0, sizeof(av_buffer));
+      if ((bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo-time(NULL))) <= 0)
+       return m_panic_defer_3(scanent, CUS callout_address,
+         string_sprintf("unable to read from UNIX socket (%s)", scanner_options),
+         malware_daemon_ctx.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_panic_defer_3(scanent, CUS callout_address,
+               US"scanner reported error", malware_daemon_ctx.sock);
+      else /* all ok, no virus */
+       malware_name = NULL;
+
+      break;
+      }
+#endif
+
+#ifndef DISABLE_MAL_CLAM
+    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
+* See Exim bug 926 for details.  */
+
+      uschar *p, *vname, *result_tag;
+      int bread=0;
+      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;
+      uint32_t send_size, send_final_zeroblock;
+      blob cmd_str;
+
+      /*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_panic_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_panic_defer(scanent, NULL,
+                     string_sprintf("missing address: '%s'", scanner_options));
+           continue;
+           }
+         if (!(s = string_nextinlist(&sublist, &subsep, NULL, 0)))
+           {
+           (void) m_panic_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_panic_defer(scanent, NULL,
+             string_sprintf("bad option '%s'", scanner_options));
+
+         cv[num_servers++] = cd;
+         if (num_servers >= MAX_CLAMD_SERVERS)
+           {
+           (void) m_panic_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_panic_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_panic_defer(scanent, NULL,
+         string_sprintf("local/SCAN mode incompatible with" \
+           " : in path to email filename [%s]", eml_filename));
+
+      /* Set up the very first data we will be sending */
+      if (!use_scan_command)
+       { cmd_str.data = US"zINSTREAM"; cmd_str.len = 10; }
+      else
+       {
+       cmd_str.data = string_sprintf("SCAN %s\n", eml_filename);
+       cmd_str.len = Ustrlen(cmd_str.data);
+       }
+
+      /* 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 (;;)
+           {
+           /*XXX we trust that the cmd_str is ideempotent */
+           if ((malware_daemon_ctx.sock = m_tcpsocket(cd->hostspec, cd->tcp_port,
+                                   &connhost, &errstr, &cmd_str)) >= 0)
+             {
+             /* Connection successfully established with a server */
+             hostname = cd->hostspec;
+             cmd_str.len = 0;
+             break;
+             }
+           if (cd->retry <= 0) break;
+           while (cd->retry > 0) cd->retry = sleep(cd->retry);
+           }
+         if (malware_daemon_ctx.sock >= 0)
+           break;
+
+         (void) m_panic_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_panic_defer(scanent, NULL, US"all servers failed");
+       }
+      else
+       for (;;)
+         {
+         if ((malware_daemon_ctx.sock = ip_unixsocket(cv[0]->hostspec, &errstr)) >= 0)
+           {
+           hostname = cv[0]->hostspec;
+           break;
+           }
+         if (cv[0]->retry <= 0)
+           return m_panic_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)
+       {
+       /* 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 not already sent */
+       if (cmd_str.len)
+         if (send(malware_daemon_ctx.sock, cmd_str.data, cmd_str.len, 0) < 0)
+           return m_panic_defer_3(scanent, CUS hostname,
+             string_sprintf("unable to send zINSTREAM to socket (%s)",
+               strerror(errno)),
+             malware_daemon_ctx.sock);
+
+       /* calc file size */
+       if ((clam_fd = open(CS eml_filename, O_RDONLY)) < 0)
+         {
+         int err = errno;
+         return m_panic_defer_3(scanent, NULL,
+           string_sprintf("can't open spool file %s: %s",
+             eml_filename, strerror(err)),
+           malware_daemon_ctx.sock);
+         }
+       if ((fsize = lseek(clam_fd, 0, SEEK_END)) < 0)
+         {
+         int err;
+b_seek:   err = errno;
+         (void)close(clam_fd);
+         return m_panic_defer_3(scanent, NULL,
+           string_sprintf("can't seek spool file %s: %s",
+             eml_filename, strerror(err)),
+           malware_daemon_ctx.sock);
+         }
+       fsize_uint = (unsigned int) fsize;
+       if ((off_t)fsize_uint != fsize)
+         {
+         (void)close(clam_fd);
+         return m_panic_defer_3(scanent, NULL,
+           string_sprintf("seeking spool file %s, size overflow",
+             eml_filename),
+           malware_daemon_ctx.sock);
+         }
+       if (lseek(clam_fd, 0, SEEK_SET) < 0)
+         goto b_seek;
+
+       if (!(clamav_fbuf = US malloc(fsize_uint)))
+         {
+         (void)close(clam_fd);
+         return m_panic_defer_3(scanent, NULL,
+           string_sprintf("unable to allocate memory %u for file (%s)",
+             fsize_uint, eml_filename),
+           malware_daemon_ctx.sock);
+         }
+
+       if ((result = read(clam_fd, clamav_fbuf, fsize_uint)) < 0)
+         {
+         int err = errno;
+         free(clamav_fbuf); (void)close(clam_fd);
+         return m_panic_defer_3(scanent, NULL,
+           string_sprintf("can't read spool file %s: %s",
+             eml_filename, strerror(err)),
+           malware_daemon_ctx.sock);
+         }
+       (void)close(clam_fd);
+
+       /* send file body to socket */
+       send_size = htonl(fsize_uint);
+       send_final_zeroblock = 0;
+       if ((send(malware_daemon_ctx.sock, &send_size, sizeof(send_size), 0) < 0) ||
+           (send(malware_daemon_ctx.sock, clamav_fbuf, fsize_uint, 0) < 0) ||
+           (send(malware_daemon_ctx.sock, &send_final_zeroblock, sizeof(send_final_zeroblock), 0) < 0))
+         {
+         free(clamav_fbuf);
+         return m_panic_defer_3(scanent, NULL,
+           string_sprintf("unable to send file body to socket (%s)", hostname),
+           malware_daemon_ctx.sock);
+         }
+
+       free(clamav_fbuf);
+       }
+      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), if not already sent */
+
+       DEBUG(D_acl) debug_printf_indent(
+           "Malware scan: issuing %s local-path scan [%s]\n",
+           scanner_name, scanner_options);
+
+       if (cmd_str.len)
+         if (send(malware_daemon_ctx.sock, cmd_str.data, cmd_str.len, 0) < 0)
+           return m_panic_defer_3(scanent, CUS callout_address,
+             string_sprintf("unable to write to socket (%s)", strerror(errno)),
+             malware_daemon_ctx.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(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo-time(NULL));
+      (void)close(malware_daemon_ctx.sock);
+      malware_daemon_ctx.sock = -1;
+      malware_daemon_ctx.tls_ctx = NULL;
+
+      if (bread <= 0)
+       return m_panic_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_panic_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_panic_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_panic_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_panic_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_panic_defer(scanent, CUS callout_address,
+         string_sprintf("unparseable response from ClamAV: {%s}", av_buffer));
+
+      break;
+      } /* clamd */
+#endif
+
+#ifndef DISABLE_MAL_SOCK
+    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))
+        && *sockline_scanner
+        )
+      {        /* check for no expansions apart from one %s */
+       uschar * s = Ustrchr(sockline_scanner, '%');
+       if (s++)
+         if ((*s != 's' && *s != '%') || Ustrchr(s+1, '%'))
+           return m_panic_defer_3(scanent, NULL,
+                                 US"unsafe sock scanner call spec", malware_daemon_ctx.sock);
+      }
+      else
+       sockline_scanner = sockline_scanner_default;
+      DEBUG(D_acl) debug_printf_indent("%15s%10s'%s'\n", "", "cmdline: ",
+       string_printing(sockline_scanner));
+
+      /* find scanner output trigger */
+      sockline_trig_re = m_pcre_nextinlist(&av_scanner_work, &sep,
+                               "missing trigger specification", &errstr);
+      if (!sockline_trig_re)
+       return m_panic_defer_3(scanent, NULL, errstr, malware_daemon_ctx.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_panic_defer_3(scanent, NULL, errstr, malware_daemon_ctx.sock);
+
+      /* prepare scanner call - security depends on expansions check above */
+      commandline = string_sprintf( CS sockline_scanner, CS eml_filename);
+      DEBUG(D_acl) debug_printf_indent("%15s%10s'%s'\n", "", "expanded: ",
+       string_printing(commandline));
+
+      /* Pass the command string to the socket */
+      if (m_sock_send(malware_daemon_ctx.sock, commandline, Ustrlen(commandline), &errstr) < 0)
+       return m_panic_defer(scanent, CUS callout_address, errstr);
+
+      /* Read the result */
+      bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo-time(NULL));
+
+      if (bread <= 0)
+       return m_panic_defer_3(scanent, CUS callout_address,
+         string_sprintf("unable to read from socket (%s)", strerror(errno)),
+         malware_daemon_ctx.sock);
+
+      if (bread == sizeof(av_buffer))
+       return m_panic_defer_3(scanent, CUS callout_address,
+               US"buffer too small", malware_daemon_ctx.sock);
+      av_buffer[bread] = '\0';
+      linebuffer = string_copy(av_buffer);
+      DEBUG(D_acl) debug_printf_indent("%15s%10s'%s'\n", "", "answer: ",
+       string_printing(linebuffer));
+
+      /* 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";
+       DEBUG(D_acl) debug_printf_indent("%15s%10s'%s'\n", "", "name: ",
+         string_printing(malware_name));
+       }
+      else /* no virus found */
+       malware_name = NULL;
+      break;
+      }
+#endif
+
+#ifndef DISABLE_MAL_MKS
+    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_panic_defer(scanent, CUS callout_address,
+           string_sprintf("invalid option '%s'", scanner_options));
+       }
+
+      if((malware_daemon_ctx.sock = ip_unixsocket(US "/var/run/mksd/socket", &errstr)) < 0)
+       return m_panic_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, malware_daemon_ctx.sock, eml_filename, tmo)) != OK)
+       {
+       close (malware_daemon_ctx.sock);
+       return retval;
+       }
+      break;
+      }
+#endif
+
+#ifndef DISABLE_MAL_AVAST
+    case M_AVAST: /* "avast" scanner type ----------------------------------- */
+      {
+      uschar buf[1024];
+      uschar * scanrequest;
+      enum {AVA_HELO, AVA_OPT, AVA_RSP, AVA_DONE} avast_stage;
+      int nread;
+      uschar * error_message = NULL;
+      BOOL more_data = FALSE;
+      BOOL strict = TRUE;
+
+      /* 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 occurred
+      Such marker follows the first non-escaped TAB.  For more information
+      see avast-protocol(5)
+
+      We observed two cases:
+      -> SCAN /file
+      <- /file [E]0.0 Error 13 Permission denied
+      <- 451 SCAN Engine error 13 permission denied
+
+      -> SCAN /file
+      <- /file… [E]3.0 Error 41120 The file is a decompression bomb
+      <- /file… [+]2.0
+      <- /file… [+]2.0 0 Eicar Test Virus!!!
+      <- 200 SCAN OK
+
+      If the scanner returns 4xx, DEFER is a good decision, combined
+      with a panic log entry, to get the admin's attention.
+
+      If the scanner returns 200, we reject it as malware, if found any,
+      or, in case of an error, we set the malware message to the error
+      string.
+
+      Some of the >= 42000 errors are message related - usually some
+      broken archives etc, but some of them are e.g. license related.
+      Once the license expires the engine starts returning errors for
+      every scanning attempt.  I¹ have the full list of the error codes
+      but it is not a public API and is subject to change. It is hard
+      for me to say what you should do in case of an engine error. You
+      can have a “Treat * unscanned file as infection” policy or “Treat
+      unscanned file as clean” policy.  ¹) Jakub Bednar
+
+       */
+
+      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)))
+        || (  !ava_re_error
+           && !(ava_re_error = m_pcre_compile(ava_re_error_str, &errstr)))
+        )
+       return malware_panic_defer(errstr);
+
+      /* wait for result */
+      for (avast_stage = AVA_HELO;
+          (nread = recv_line(malware_daemon_ctx.sock, buf, sizeof(buf), tmo)) > 0;
+         )
+       {
+       int slen = Ustrlen(buf);
+       if (slen >= 1)
+         {
+
+          /* Multi line responses are bracketed between 210 … and nnn … */
+          if (Ustrncmp(buf, "210", 3) == 0)
+            {
+            more_data = 1;
+            continue;
+            }
+          else if (more_data && isdigit(buf[0])) more_data = 0;
+
+         switch (avast_stage)
+           {
+           case AVA_HELO:
+              if (more_data) continue;
+             if (Ustrncmp(buf, "220", 3) != 0)
+               goto endloop;                   /* require a 220 */
+             goto sendreq;
+
+           case AVA_OPT:
+              if (more_data) continue;
+             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)))
+               {
+                if (Ustrcmp(scanrequest, "pass_unscanned") == 0)
+                  {
+                  DEBUG(D_acl) debug_printf_indent("pass unscanned files as clean\n");
+                  strict = FALSE;
+                  goto sendreq;
+                  }
+               scanrequest = string_sprintf("%s\n", scanrequest);
+               avast_stage = AVA_OPT;          /* just sent option */
+               DEBUG(D_acl) debug_printf_indent("send to avast OPTION: %s", scanrequest);
+               }
+             else
+               {
+               scanrequest = string_sprintf("SCAN %s\n", eml_dir);
+               avast_stage = AVA_RSP;          /* just sent command */
+               DEBUG(D_acl) debug_printf_indent("send to avast REQUEST: SCAN %s\n", eml_dir);
+               }
+
+             /* send config-cmd or scan-request to socket */
+             len = Ustrlen(scanrequest);
+             if (send(malware_daemon_ctx.sock, scanrequest, len, 0) == -1)
+               {
+               scanrequest[len-1] = '\0';
+               return m_panic_defer_3(scanent, CUS callout_address, string_sprintf(
+                     "unable to send request '%s' to socket (%s): %s",
+                     scanrequest, scanner_options, strerror(errno)), malware_daemon_ctx.sock);
+               }
+             break;
+             }
+
+           case AVA_RSP:
+
+             if (isdigit(buf[0]))  /* We're done */
+                goto endloop;
+
+              if (malware_name)     /* Nothing else matters, just read on */
+                break;
+
+             if (pcre_exec(ava_re_clean, NULL, CS buf, slen, 0, 0, NULL, 0) == 0)
+               break;
+
+              if ((malware_name = m_pcre_exec(ava_re_virus, buf)))
+                {
+                unescape(malware_name);
+                DEBUG(D_acl)
+                  debug_printf_indent("unescaped malware name: '%s'\n", malware_name);
+                break;
+                }
+
+              if (strict)           /* treat scanner errors as malware */
+                {
+                if ((malware_name = m_pcre_exec(ava_re_error, buf)))
+                  {
+                  unescape(malware_name);
+                  DEBUG(D_acl)
+                    debug_printf_indent("unescaped error message: '%s'\n", malware_name);
+                  break;
+                  }
+                }
+              else if (pcre_exec(ava_re_error, NULL, CS buf, slen, 0, 0, NULL, 0) == 0)
+                {
+                log_write(0, LOG_MAIN, "internal scanner error (ignored): %s", buf);
+                break;
+                }
+
+             /* here also for any unexpected response from the scanner */
+              DEBUG(D_acl) debug_printf("avast response not handled: '%s'\n", buf);
+
+             goto endloop;
+
+           default:    log_write(0, LOG_PANIC, "%s:%d:%s: should not happen",
+                           __FILE__, __LINE__, __FUNCTION__);
+           }
+         }
+       }
+
+      endloop:
+
+      if (nread == -1) error_message = US"EOF from scanner";
+      else if (nread < 0) error_message = US"timeout from scanner";
+      else if (nread == 0) error_message = US"got nothing from scanner";
+      else if (buf[0] != '2') error_message = buf;
+
+      DEBUG(D_acl) debug_printf_indent("sent to avast QUIT\n");
+      if (send(malware_daemon_ctx.sock, "QUIT\n", 5, 0) == -1)
+        return m_panic_defer_3(scanent, CUS callout_address,
+          string_sprintf("unable to send quit request to socket (%s): %s",
+            scanner_options, strerror(errno)), malware_daemon_ctx.sock);
+
+      if (error_message)
+        return m_panic_defer_3(scanent, CUS callout_address, error_message, malware_daemon_ctx.sock);
+
+      }
+#endif
+  }    /* scanner type switch */
+
+  if (malware_daemon_ctx.sock >= 0)
+    (void) close (malware_daemon_ctx.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)
+{
+int ret = malware_internal(malware_re, NULL, timeout);
+
+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);
+f.enable_dollar_recipients = TRUE;
+
+ret = malware_internal(US"*", eml_filename, 0);
+
+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);
+
+#ifndef DISABLE_MAL_DRWEB
+if (!drweb_re)
+  drweb_re = regex_must_compile(drweb_re_str, FALSE, TRUE);
+#endif
+#ifndef DISABLE_MAL_FSECURE
+if (!fsec_re)
+  fsec_re = regex_must_compile(fsec_re_str, FALSE, TRUE);
+#endif
+#ifndef DISABLE_MAL_KAV
+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);
+#endif
+#ifndef DISABLE_MAL_AVAST
+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);
+if (!ava_re_error)
+  ava_re_error = regex_must_compile(ava_re_error_str, FALSE, TRUE);
+#endif
+#ifndef DISABLE_MAL_FFROT6D
+if (!fprot6d_re_error)
+  fprot6d_re_error = regex_must_compile(fprot6d_re_error_str, FALSE, TRUE);
+if (!fprot6d_re_virus)
+  fprot6d_re_virus = regex_must_compile(fprot6d_re_virus_str, FALSE, TRUE);
+#endif
+}
+
+
+void
+malware_show_supported(FILE * f)
+{
+struct scan * sc;
+fprintf(f, "Malware:");
+for (sc = m_scans; sc->scancode != (scanner_t)-1; sc++) fprintf(f, " %s", sc->name);
+fprintf(f, "\n");
+}
+
+
+# endif        /*!MACRO_PREDEF*/
+#endif /*WITH_CONTENT_SCAN*/
+/*
+ * vi: aw ai sw=2
+ */
index 97a0982..0c0f3e8 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for matching strings */
 /* 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 {
 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;
   int    expand_setup;
   BOOL   use_partial;
   BOOL   caseless;
@@ -28,7 +28,7 @@ typedef struct check_string_block {
 addresses. */
 
 typedef struct check_address_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;
   uschar *address;                   /* step with the block above */
   int    expand_setup;
   BOOL   caseless;
@@ -92,12 +92,12 @@ Returns:       OK    if matched
 */
 
 static int
 */
 
 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;
 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;
 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". */
 
 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. */
 
 /* 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)?
   {
   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)
          )?
            :
            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;
     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)
 
     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,                /* 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 */
 
       NULL,                /* no feedback FQDN */
       &removed);           /* feedback if local removed */
 
@@ -298,14 +297,13 @@ else if (!mac_islookup(search_type, lookup_querystyle))
 for; partial matching is all handled inside search_find(). Note that there is
 no search_close() because of the caching arrangements. */
 
 for; partial matching is all handled inside search_find(). 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, keyquery, partial, affix, affixlen,
   starflags, &expand_setup);
 
 result = search_find(handle, filename, keyquery, partial, affix, affixlen,
   starflags, &expand_setup);
 
-if (result == NULL) return search_find_defer? DEFER : FAIL;
-if (valueptr != NULL) *valueptr = result;
+if (!result) return f.search_find_defer? DEFER : FAIL;
+if (valueptr) *valueptr = result;
 
 expand_nmax = expand_setup;
 return OK;
 
 expand_nmax = expand_setup;
 return OK;
@@ -337,8 +335,8 @@ Returns:       OK    if matched
 */
 
 int
 */
 
 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;
 {
 check_string_block cb;
 cb.origsubject = s;
@@ -366,7 +364,7 @@ Arguments:
   type         MCL_STRING, MCL_DOMAIN, MCL_HOST, MCL_ADDRESS, or MCL_LOCALPART
 */
 
   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)
 get_check_key(void *arg, int type)
 {
 switch(type)
@@ -436,9 +434,9 @@ Returns:       OK    if matched a non-negated item
 */
 
 int
 */
 
 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;
 {
 int yield = OK;
 unsigned int *original_cache_bits = *cache_ptr;
@@ -446,7 +444,7 @@ BOOL include_unknown = FALSE;
 BOOL ignore_unknown = FALSE;
 BOOL include_defer = FALSE;
 BOOL ignore_defer = 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];
 uschar *sss;
 uschar *ot = NULL;
 uschar buffer[1024];
@@ -462,12 +460,9 @@ HDEBUG(D_any)
 /* If the list is empty, the answer is no. Skip the debugging output for
 an unnamed list. */
 
 /* If the list is empty, the answer is no. Skip the debugging output for
 an unnamed list. */
 
-if (*listptr == NULL)
+if (!*listptr)
   {
   {
-  HDEBUG(D_lists)
-    {
-    if (ot != NULL) debug_printf("%s no (option unset)\n", ot);
-    }
+  HDEBUG(D_lists) if (ot) debug_printf("%s no (option unset)\n", ot);
   return FAIL;
   }
 
   return FAIL;
   }
 
@@ -486,19 +481,19 @@ else
   /* If we are searching a domain list, and $domain is not set, set it to the
   subject that is being sought for the duration of the expansion. */
 
   /* If we are searching a domain list, and $domain is not set, set it to the
   subject that is being sought for the duration of the expansion. */
 
-  if (type == MCL_DOMAIN && deliver_domain == NULL)
+  if (type == MCL_DOMAIN && !deliver_domain)
     {
     check_string_block *cb = (check_string_block *)arg;
     {
     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;
     }
     deliver_domain = NULL;
     }
+  else
+    list = expand_cstring(*listptr);
 
 
-  else list = expand_string(*listptr);
-
-  if (list == NULL)
+  if (!list)
     {
     {
-    if (expand_string_forcedfail)
+    if (f.expand_string_forcedfail)
       {
       HDEBUG(D_lists) debug_printf("expansion of \"%s\" forced failure: "
         "assume not in this list\n", *listptr);
       {
       HDEBUG(D_lists) debug_printf("expansion of \"%s\" forced failure: "
         "assume not in this list\n", *listptr);
@@ -512,17 +507,14 @@ else
 
 /* For an unnamed list, use the expanded version in comments */
 
 
 /* For an unnamed list, use the expanded version in comments */
 
-HDEBUG(D_any)
-  {
-  if (ot == NULL) ot = string_sprintf("%s in \"%s\"?", name, list);
-  }
+HDEBUG(D_any) if (ot == NULL) ot = string_sprintf("%s in \"%s\"?", name, list);
 
 /* Now scan the list and process each item in turn, until one of them matches,
 or we hit an error. */
 
 
 /* Now scan the list and process each item in turn, until one of them matches,
 or we hit an error. */
 
-while ((sss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
+while ((sss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))))
   {
   {
-  uschar *ss = sss;
+  uschar * ss = sss;
 
   /* Address lists may contain +caseful, to restore caseful matching of the
   local part. We have to know the layout of the control block, unfortunately.
 
   /* Address lists may contain +caseful, to restore caseful matching of the
   local part. We have to know the layout of the control block, unfortunately.
@@ -535,7 +527,8 @@ while ((sss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
       {
       check_address_block *cb = (check_address_block *)arg;
       uschar *at = Ustrrchr(cb->origaddress, '@');
       {
       check_address_block *cb = (check_address_block *)arg;
       uschar *at = Ustrrchr(cb->origaddress, '@');
-      if (at != NULL)
+
+      if (at)
         Ustrncpy(cb->address, cb->origaddress, at - cb->origaddress);
       cb->caseless = FALSE;
       continue;
         Ustrncpy(cb->address, cb->origaddress, at - cb->origaddress);
       cb->caseless = FALSE;
       continue;
@@ -595,7 +588,8 @@ while ((sss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
     yield = FAIL;
     while (isspace((*(++ss))));
     }
     yield = FAIL;
     while (isspace((*(++ss))));
     }
-  else yield = OK;
+  else
+    yield = OK;
 
   /* If the item does not begin with '/', it might be a + item for a named
   list. Otherwise, it is just a single list entry that has to be matched.
 
   /* If the item does not begin with '/', it might be a + item for a named
   list. Otherwise, it is just a single list entry that has to be matched.
@@ -603,7 +597,7 @@ while ((sss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
 
   if (*ss != '/')
     {
 
   if (*ss != '/')
     {
-    if (*ss == '+' && anchorptr != NULL)
+    if (*ss == '+' && anchorptr)
       {
       int bits = 0;
       int offset = 0;
       {
       int bits = 0;
       int offset = 0;
@@ -611,15 +605,18 @@ while ((sss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
       unsigned int *use_cache_bits = original_cache_bits;
       uschar *cached = US"";
       namedlist_block *nb;
       unsigned int *use_cache_bits = original_cache_bits;
       uschar *cached = US"";
       namedlist_block *nb;
-      tree_node *t = tree_search(*anchorptr, ss+1);
-
-      if (t == NULL)
-        log_write(0, LOG_MAIN|LOG_PANIC_DIE, "unknown named%s list \"%s\"",
-          (type == MCL_DOMAIN)?    " domain" :
-          (type == MCL_HOST)?      " host" :
-          (type == MCL_ADDRESS)?   " address" :
-          (type == MCL_LOCALPART)? " local part" : "",
+      tree_node * t;
+
+      if (!(t = tree_search(*anchorptr, ss+1)))
+       {
+        log_write(0, LOG_MAIN|LOG_PANIC, "unknown named%s list \"%s\"",
+          type == MCL_DOMAIN ?    " domain" :
+          type == MCL_HOST ?      " host" :
+          type == MCL_ADDRESS ?   " address" :
+          type == MCL_LOCALPART ? " local part" : "",
           ss);
           ss);
+       return DEFER;
+       }
       nb = t->data.ptr;
 
       /* If the list number is negative, it means that this list is not
       nb = t->data.ptr;
 
       /* If the list number is negative, it means that this list is not
@@ -631,7 +628,7 @@ while ((sss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
       because the pointer may be NULL from the start if caching is not
       required. */
 
       because the pointer may be NULL from the start if caching is not
       required. */
 
-      if (use_cache_bits != NULL)
+      if (use_cache_bits)
         {
         offset = (nb->number)/16;
         shift = ((nb->number)%16)*2;
         {
         offset = (nb->number)/16;
         shift = ((nb->number)%16)*2;
@@ -655,15 +652,13 @@ while ((sss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
         wasn't before. Ensure that this is passed up to the next level.
         Otherwise, remember the result of the search in the cache. */
 
         wasn't before. Ensure that this is passed up to the next level.
         Otherwise, remember the result of the search in the cache. */
 
-        if (use_cache_bits == NULL)
-          {
+        if (!use_cache_bits)
           *cache_ptr = NULL;
           *cache_ptr = NULL;
-          }
         else
           {
           use_cache_bits[offset] |= bits << shift;
 
         else
           {
           use_cache_bits[offset] |= bits << shift;
 
-          if (valueptr != NULL)
+          if (valueptr)
             {
             int old_pool = store_pool;
             namedlist_cacheblock *p;
             {
             int old_pool = store_pool;
             namedlist_cacheblock *p;
@@ -676,16 +671,14 @@ while ((sss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
             p->key = string_copy(get_check_key(arg, type));
 
 
             p->key = string_copy(get_check_key(arg, type));
 
 
-            p->data = (*valueptr == NULL)? NULL : string_copy(*valueptr);
+            p->data = *valueptr ? string_copy(*valueptr) : NULL;
             store_pool = old_pool;
 
             p->next = nb->cache_data;
             nb->cache_data = p;
             store_pool = old_pool;
 
             p->next = nb->cache_data;
             nb->cache_data = p;
-            if (*valueptr != NULL)
-              {
+            if (*valueptr)
               DEBUG(D_lists) debug_printf("data from lookup saved for "
                 "cache for %s: %s\n", ss, *valueptr);
               DEBUG(D_lists) debug_printf("data from lookup saved for "
                 "cache for %s: %s\n", ss, *valueptr);
-              }
             }
           }
         }
             }
           }
         }
@@ -698,19 +691,18 @@ while ((sss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
         {
         DEBUG(D_lists) debug_printf("cached %s match for %s\n",
           ((bits & (-bits)) == bits)? "yes" : "no", ss);
         {
         DEBUG(D_lists) debug_printf("cached %s match for %s\n",
           ((bits & (-bits)) == bits)? "yes" : "no", ss);
+
         cached = US" - cached";
         cached = US" - cached";
-        if (valueptr != NULL)
+        if (valueptr)
           {
           {
-          uschar *key = get_check_key(arg, type);
+          const uschar *key = get_check_key(arg, type);
           namedlist_cacheblock *p;
           namedlist_cacheblock *p;
-          for (p = nb->cache_data; p != NULL; p = p->next)
-            {
+          for (p = nb->cache_data; p; p = p->next)
             if (Ustrcmp(key, p->key) == 0)
               {
               *valueptr = p->data;
               break;
               }
             if (Ustrcmp(key, p->key) == 0)
               {
               *valueptr = p->data;
               break;
               }
-            }
           DEBUG(D_lists) debug_printf("cached lookup data = %s\n", *valueptr);
           }
         }
           DEBUG(D_lists) debug_printf("cached lookup data = %s\n", *valueptr);
           }
         }
@@ -730,29 +722,30 @@ while ((sss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
 
     else
       {
 
     else
       {
-      uschar *error = NULL;
+      uschar * error = NULL;
       switch ((func)(arg, ss, valueptr, &error))
         {
         case OK:
       switch ((func)(arg, ss, valueptr, &error))
         {
         case OK:
-        HDEBUG(D_lists) debug_printf("%s %s (matched \"%s\")\n", ot,
-          (yield == OK)? "yes" : "no", sss);
-        return yield;
+         HDEBUG(D_lists) debug_printf("%s %s (matched \"%s\")\n", ot,
+           (yield == OK)? "yes" : "no", sss);
+         return yield;
 
         case DEFER:
 
         case DEFER:
-        if (error == NULL)
-          error = string_sprintf("DNS lookup of %s deferred", ss);
-        if (ignore_defer)
-          {
-          HDEBUG(D_lists) debug_printf("%s: item ignored by +ignore_defer\n",
-            error);
-          break;
-          }
-        if (include_defer)
-          {
-          log_write(0, LOG_MAIN, "%s: accepted by +include_defer", error);
-          return OK;
-          }
-        goto DEFER_RETURN;
+         if (!error)
+           error = string_sprintf("DNS lookup of \"%s\" deferred", ss);
+         if (ignore_defer)
+           {
+           HDEBUG(D_lists) debug_printf("%s: item ignored by +ignore_defer\n",
+             error);
+           break;
+           }
+         if (include_defer)
+           {
+           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
         or reverse lookup has failed. It can also occur in a match_ip list if a
 
         /* The ERROR return occurs when checking hosts, when either a forward
         or reverse lookup has failed. It can also occur in a match_ip list if a
@@ -760,24 +753,24 @@ while ((sss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
         which it was. */
 
         case ERROR:
         which it was. */
 
         case ERROR:
-        if (ignore_unknown)
-          {
-          HDEBUG(D_lists) debug_printf("%s: item ignored by +ignore_unknown\n",
-            error);
-          }
-        else
-          {
-          HDEBUG(D_lists) debug_printf("%s %s (%s)\n", ot,
-            include_unknown? "yes":"no", error);
-          if (!include_unknown)
-            {
-            if ((log_extra_selector & LX_unknown_in_list) != 0)
-              log_write(0, LOG_MAIN, "list matching forced to fail: %s", error);
-            return FAIL;
-            }
-          log_write(0, LOG_MAIN, "%s: accepted by +include_unknown", error);
-          return OK;
-          }
+         if (ignore_unknown)
+           {
+           HDEBUG(D_lists) debug_printf("%s: item ignored by +ignore_unknown\n",
+             error);
+           }
+         else
+           {
+           HDEBUG(D_lists) debug_printf("%s %s (%s)\n", ot,
+             include_unknown? "yes":"no", error);
+           if (!include_unknown)
+             {
+             if (LOGGING(unknown_in_list))
+               log_write(0, LOG_MAIN, "list matching forced to fail: %s", error);
+             return FAIL;
+             }
+           log_write(0, LOG_MAIN, "%s: accepted by +include_unknown", error);
+           return OK;
+           }
         }
       }
     }
         }
       }
     }
@@ -788,16 +781,16 @@ while ((sss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
   else
     {
     int file_yield = yield;       /* In case empty file */
   else
     {
     int file_yield = yield;       /* In case empty file */
-    uschar *filename = ss;
-    FILE *f = Ufopen(filename, "rb");
+    uschar * filename = ss;
+    FILE * f = Ufopen(filename, "rb");
     uschar filebuffer[1024];
 
     /* ot will be null in non-debugging cases, and anyway, we get better
     wording by reworking it. */
 
     uschar filebuffer[1024];
 
     /* ot will be null in non-debugging cases, and anyway, we get better
     wording by reworking it. */
 
-    if (f == NULL)
+    if (!f)
       {
       {
-      uschar *listname = readconf_find_option(listptr);
+      uschar * listname = readconf_find_option(listptr);
       if (listname[0] == 0)
         listname = string_sprintf("\"%s\"", *listptr);
       log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s",
       if (listname[0] == 0)
         listname = string_sprintf("\"%s\"", *listptr);
       log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s",
@@ -845,48 +838,48 @@ while ((sss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
       switch ((func)(arg, ss, valueptr, &error))
         {
         case OK:
       switch ((func)(arg, ss, valueptr, &error))
         {
         case OK:
-        (void)fclose(f);
-        HDEBUG(D_lists) debug_printf("%s %s (matched \"%s\" in %s)\n", ot,
-          (yield == OK)? "yes" : "no", sss, filename);
-        return file_yield;
+         (void)fclose(f);
+         HDEBUG(D_lists) debug_printf("%s %s (matched \"%s\" in %s)\n", ot,
+           yield == OK ? "yes" : "no", sss, filename);
+         return file_yield;
 
         case DEFER:
 
         case DEFER:
-        if (error == NULL)
-          error = string_sprintf("DNS lookup of %s deferred", ss);
-        if (ignore_defer)
-          {
-          HDEBUG(D_lists) debug_printf("%s: item ignored by +ignore_defer\n",
-            error);
-          break;
-          }
-        (void)fclose(f);
-        if (include_defer)
-          {
-          log_write(0, LOG_MAIN, "%s: accepted by +include_defer", error);
-          return OK;
-          }
-        goto DEFER_RETURN;
-
-        case ERROR:          /* host name lookup failed - this can only */
-        if (ignore_unknown)  /* be for an incoming host (not outgoing) */
-          {
-          HDEBUG(D_lists) debug_printf("%s: item ignored by +ignore_unknown\n",
-            error);
-          }
-        else
-         {
-          HDEBUG(D_lists) debug_printf("%s %s (%s)\n", ot,
-            include_unknown? "yes":"no", error);
-          (void)fclose(f);
-          if (!include_unknown)
-            {
-            if ((log_extra_selector & LX_unknown_in_list) != 0)
-              log_write(0, LOG_MAIN, "list matching forced to fail: %s", error);
-            return FAIL;
-            }
-          log_write(0, LOG_MAIN, "%s: accepted by +include_unknown", error);
-          return OK;
-          }
+         if (!error)
+           error = string_sprintf("DNS lookup of %s deferred", ss);
+         if (ignore_defer)
+           {
+           HDEBUG(D_lists) debug_printf("%s: item ignored by +ignore_defer\n",
+             error);
+           break;
+           }
+         (void)fclose(f);
+         if (include_defer)
+           {
+           log_write(0, LOG_MAIN, "%s: accepted by +include_defer", error);
+           return OK;
+           }
+         goto DEFER_RETURN;
+
+        case ERROR:            /* host name lookup failed - this can only */
+         if (ignore_unknown)   /* be for an incoming host (not outgoing) */
+           {
+           HDEBUG(D_lists) debug_printf("%s: item ignored by +ignore_unknown\n",
+             error);
+           }
+         else
+          {
+           HDEBUG(D_lists) debug_printf("%s %s (%s)\n", ot,
+             include_unknown? "yes":"no", error);
+           (void)fclose(f);
+           if (!include_unknown)
+             {
+             if (LOGGING(unknown_in_list))
+               log_write(0, LOG_MAIN, "list matching forced to fail: %s", error);
+             return FAIL;
+             }
+           log_write(0, LOG_MAIN, "%s: accepted by +include_unknown", error);
+           return OK;
+           }
         }
       }
 
         }
       }
 
@@ -901,8 +894,8 @@ while ((sss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
 /* End of list reached: if the last item was negated yield OK, else FAIL. */
 
 HDEBUG(D_lists)
 /* End of list reached: if the last item was negated yield OK, else FAIL. */
 
 HDEBUG(D_lists)
-  debug_printf("%s %s (end of list)\n", ot, (yield == OK)? "no":"yes");
-return (yield == OK)? FAIL : OK;
+  debug_printf("%s %s (end of list)\n", ot, yield == OK ? "no":"yes");
+return yield == OK ? FAIL : OK;
 
 /* Something deferred */
 
 
 /* Something deferred */
 
@@ -952,8 +945,9 @@ Returns:         OK    if matched a non-negated item
 */
 
 int
 */
 
 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;
 {
 unsigned int *local_cache_bits = cache_bits;
 check_string_block cb;
@@ -999,16 +993,17 @@ Returns:         OK     for a match
 */
 
 static int
 */
 
 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;
 {
 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 *subject = cb->address;
-uschar *s, *pdomain, *sdomain;
+const uschar *s;
+uschar *pdomain, *sdomain;
 
 error = error;  /* Keep clever compilers from complaining */
 
 
 error = error;  /* Keep clever compilers from complaining */
 
@@ -1070,7 +1065,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;
 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;
   uschar buffer[1024];
 
   if (sdomain == subject + 1 && *subject == '*') return FAIL;
@@ -1083,7 +1079,7 @@ if (pattern[0] == '@' && pattern[1] == '@')
     int sep = 0;
 
     if ((rc = match_check_string(key, pattern + 2, -1, TRUE, FALSE, FALSE,
     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. */
 
     /* Check for chaining from the last item; set up the next key if one
     is found. */
@@ -1102,8 +1098,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. */
 
     /* 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;
 
       {
       int local_yield;
 
@@ -1278,9 +1273,9 @@ Returns:          OK    for a positive match, or end list after a negation;
 */
 
 int
 */
 
 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;
 {
 uschar *p;
 check_address_block ab;
@@ -1324,4 +1319,25 @@ return match_check_list(listptr, sep, &addresslist_anchor, &local_cache_bits,
     valueptr);
 }
 
     valueptr);
 }
 
+/* Simpler version of match_address_list; always caseless, expanding,
+no cache bits, no value-return.
+
+Arguments:
+  address         address to test
+  listptr         list to check against
+  sep             separator character for the list;
+                  may be 0 to get separator from the list;
+                  may be UCHAR_MAX+1 for one-item list
+
+Returns:          OK    for a positive match, or end list after a negation;
+                  FAIL  for a negative match, or end list after non-negation;
+                  DEFER if a lookup deferred
+*/
+
+int
+match_address_list_basic(const uschar *address, const uschar **listptr, int sep)
+{
+return match_address_list(address, TRUE, TRUE, listptr, NULL, -1, sep, NULL);
+}
+
 /* End of match.c */
 /* End of match.c */
index bf95491..6f97b09 100644 (file)
    1. Redistributions of source code must retain the above copyright
       notice, this list of conditions and the following disclaimer.
 
    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.
 
       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
       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.
 
    the terms of the GNU General Public License, version 2.  See the
    COPYING file in the source distribution for details.
 
-   ---------------------------------------------------------------- 
+   ----------------------------------------------------------------
 */
 
 
 */
 
 
 
 #include "valgrind.h"
 
 
 #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
    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,
       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__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;
 
          = 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)
     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)          \
 /* 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..5dcbaa4 100644 (file)
@@ -2,8 +2,10 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     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 2015 - 2018
+ */
 
 #include "exim.h"
 #ifdef WITH_CONTENT_SCAN       /* entire file */
 
 #include "exim.h"
 #ifdef WITH_CONTENT_SCAN       /* entire file */
 FILE *mime_stream = NULL;
 uschar *mime_current_boundary = NULL;
 
 FILE *mime_stream = NULL;
 uschar *mime_current_boundary = NULL;
 
+static mime_header mime_header_list[] = {
+  /*   name                    namelen         value */
+  { 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[] = {
+  /*   name    namelen  value */
+  { 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
 /*************************************************
 * 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
 
 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 +69,7 @@ mime_set_anomaly(int level, const char *text)
            0-255 - char to write
 */
 
            0-255 - char to write
 */
 
-uschar *
+static uschar *
 mime_decode_qp_char(uschar *qp_p, int *c)
 {
 uschar *initial_pos = qp_p;
 mime_decode_qp_char(uschar *qp_p, int *c)
 {
 uschar *initial_pos = qp_p;
@@ -99,84 +127,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
 
 /* decode quoted-printable MIME part */
 static ssize_t
@@ -209,24 +159,16 @@ while (fgets(CS ibuf, MIME_MAX_LINE_LENGTH, in) != NULL)
        {
        /* Error from decoder. ipos is unchanged. */
        mime_set_anomaly(MIME_ANOMALY_BROKEN_QP);
        {
        /* Error from decoder. ipos is unchanged. */
        mime_set_anomaly(MIME_ANOMALY_BROKEN_QP);
-       *opos = '=';
-       ++opos;
+       *opos++ = '=';
        ++ipos;
        }
       else if (decode_qp_result == -1)
        break;
       else if (decode_qp_result >= 0)
        ++ipos;
        }
       else if (decode_qp_result == -1)
        break;
       else if (decode_qp_result >= 0)
-       {
-       *opos = decode_qp_result;
-       ++opos;
-       }
+       *opos++ = decode_qp_result;
       }
     else
       }
     else
-      {
-      *opos = *ipos;
-      ++opos;
-      ++ipos;
-      }
+      *opos++ = *ipos++;
     }
   /* something to write? */
   len = opos - obuf;
     }
   /* something to write? */
   len = opos - obuf;
@@ -240,21 +182,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)
 {
 mime_get_decode_file(uschar *pname, uschar *fname)
 {
-FILE *f = NULL;
-uschar *filename;
-
-filename = (uschar *)malloc(2048);
-
 if (pname && fname)
 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)
 else if (!pname)
-  f = modefopen(fname,"wb+",SPOOL_MODE);
+  mime_decoded_filename = string_copy(fname);
 else if (!fname)
   {
   int file_nr = 0;
 else if (!fname)
   {
   int file_nr = 0;
@@ -264,52 +202,41 @@ else if (!fname)
   do
     {
     struct stat mystat;
   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;
     /* security break */
     if (file_nr >= 1024)
       break;
-    result = stat(CS filename, &mystat);
+    result = stat(CS mime_decoded_filename, &mystat);
     } while(result != -1);
     } 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
 }
 
 
 int
-mime_decode(uschar **listptr)
+mime_decode(const uschar **listptr)
 {
 int sep = 0;
 {
 int sep = 0;
-uschar *list = *listptr;
-uschar *option;
-uschar option_buffer[1024];
-uschar decode_path[1024];
+const uschar *list = *listptr;
+uschar * option;
+uschar * decode_path;
 FILE *decode_file = NULL;
 long f_pos = 0;
 ssize_t size_counter = 0;
 ssize_t (*decode_function)(FILE*, FILE*, uschar*);
 
 FILE *decode_file = NULL;
 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;
 
   return FAIL;
 
-f_pos = ftell(mime_stream);
-
 /* build default decode path (will exist since MBOX must be spooled up) */
 /* 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);
+decode_path = string_sprintf("%s/scan/%s", spool_directory, message_id);
 
 /* try to find 1st option */
 
 /* try to find 1st option */
-if ((option = string_nextinlist(&list, &sep,
-                               option_buffer,
-                               sizeof(option_buffer))) != NULL)
+if ((option = string_nextinlist(&list, &sep, NULL, 0)))
   {
   /* parse 1st option */
   {
   /* 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;
 
     /* explicitly no decoding */
     return FAIL;
 
@@ -358,7 +285,8 @@ decode_function =
 size_counter = decode_function(mime_stream, decode_file, mime_current_boundary);
 
 clearerr(mime_stream);
 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;
 
 if (fclose(decode_file) != 0 || size_counter < 0)
   return DEFER;
@@ -369,7 +297,8 @@ mime_content_size = (size_counter + 1023) / 1024;
 return OK;
 }
 
 return OK;
 }
 
-int
+
+static int
 mime_get_header(FILE *f, uschar *header)
 {
 int c = EOF;
 mime_get_header(FILE *f, uschar *header)
 {
 int c = EOF;
@@ -409,17 +338,16 @@ while(!done)
     if ( ((c == '\t') || (c == ' ')) && (header_value_mode == 1) )
       continue;
 
     if ( ((c == '\t') || (c == ' ')) && (header_value_mode == 1) )
       continue;
 
-      /* we have hit a non-whitespace char, start copying value data */
-      header_value_mode = 2;
+    /* we have hit a non-whitespace char, start copying value data */
+    header_value_mode = 2;
 
 
-      if (c == '"')       /* flip "quoted" mode */
-        header_value_mode = header_value_mode==2 ? 3 : 2;
+    if (c == '"')       /* flip "quoted" mode */
+      header_value_mode = header_value_mode==2 ? 3 : 2;
 
 
-      /* leave value mode on unquoted ';' */
-      if (header_value_mode == 2 && c == ';') {
-        header_value_mode = 0;
-      };
-      /* -------------------------------- */
+    /* leave value mode on unquoted ';' */
+    if (header_value_mode == 2 && c == ';')
+      header_value_mode = 0;
+    /* -------------------------------- */
     }
   else
     {
     }
   else
     {
@@ -474,21 +402,108 @@ 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;
+gstring * val = NULL;
+
+/* 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, s++, 1);
+    if (*s) s++;               /* skip closing " */
+    }
+  else
+    val = string_catn(val, s++, 1);
+*sp = s;
+return string_from_gstring(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)
+{
+gstring * val = string_catn(NULL, US"=?", 2);
+uschar c;
+
+if (charset)
+  val = string_cat(val, charset);
+val = string_catn(val, US"?Q?", 3);
+
+while ((c = *fname))
+  if (c == '%' && isxdigit(fname[1]) && isxdigit(fname[2]))
+    {
+    val = string_catn(val, US"=", 1);
+    val = string_catn(val, ++fname, 2);
+    fname += 2;
+    }
+  else
+    val = string_catn(val, fname++, 1);
+
+val = string_catn(val, US"?=", 2);
+*len = val->ptr;
+return string_from_gstring(val);
+}
+
+
 int
 mime_acl_check(uschar *acl, FILE *f, struct mime_boundary_context *context,
 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;
 {
 int rc = OK;
-uschar *header = NULL;
+uschar * header = NULL;
 struct mime_boundary_context nested_context;
 
 /* reserve a line buffer to work in */
 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... */
 
 /* Not actually used at the moment, but will be vital to fixing
  * some RFC 2046 nonconformance later... */
@@ -498,26 +513,12 @@ nested_context.parent = context;
 while(1)
   {
   /* reset all per-part mime variables */
 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
 
   /* NOTE -- there's an error here -- RFC2046 specifically says to
    * check for outer boundaries.  This code doesn't do that, and
@@ -526,122 +527,186 @@ while(1)
    * (I have moved partway towards adding support, however, by adding
    * a "parent" field to my new boundary-context structure.)
    */
    * (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 */
   /* 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;
          {
          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;
 
 
-             /* yes, grab the value and copy to its corresponding expansion variable */
-             while(*q && *q != ';')            /* ; terminates */
+             /* 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);
+
+             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
                  }
                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);
+
+               if (!temp_string || (size = Ustrlen(temp_string))  == 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.
          /* 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) */
 
   /* 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.
     mime_is_multipart = 1;
 
   /* Make a copy of the boundary pointer.
@@ -672,7 +737,9 @@ NEXT_PARAM_SEARCH:
        (nested_context.boundary != NULL) &&
        (Ustrncmp(mime_content_type,"multipart",9) == 0) )
     {
        (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
 
     nested_context.context =
       context && context->context == MBC_ATTACHMENT
@@ -688,7 +755,7 @@ NEXT_PARAM_SEARCH:
   else if ( (mime_content_type != NULL) &&
          (Ustrncmp(mime_content_type,"message/rfc822",14) == 0) )
     {
   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;
     uschar filename[2048];
     int file_nr = 0;
     int result = 0;
@@ -717,20 +784,23 @@ NEXT_PARAM_SEARCH:
     if (!mime_decoded_filename)                /* decoding failed */
       {
       log_write(0, LOG_MAIN,
     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 */
       }
     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;
   }
 
 
   if (context->context == MBC_COVERLETTER_ONESHOT)
     context->context = MBC_ATTACHMENT;
   }
 
+out:
+mime_vars_reset();
 return rc;
 }
 
 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..1dcc6c4 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for sending messages to sender or to mailmaster. */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for sending messages to sender or to mailmaster. */
@@ -29,7 +29,7 @@ void
 moan_write_from(FILE *f)
 {
 uschar *s = expand_string(dsn_from);
 moan_write_from(FILE *f)
 {
 uschar *s = expand_string(dsn_from);
-if (s == NULL)
+if (!s)
   {
   log_write(0, LOG_MAIN|LOG_PANIC,
     "Failed to expand dsn_from (using default): %s", expand_string_message);
   {
   log_write(0, LOG_MAIN|LOG_PANIC,
     "Failed to expand dsn_from (using default): %s", expand_string_message);
@@ -61,7 +61,7 @@ Arguments:
 Returns:         TRUE if message successfully sent
 */
 
 Returns:         TRUE if message successfully sent
 */
 
-static BOOL
+BOOL
 moan_send_message(uschar *recipient, int ident, error_block *eblock,
   header_line *headers, FILE *message_file, uschar *firstline)
 {
 moan_send_message(uschar *recipient, int ident, error_block *eblock,
   header_line *headers, FILE *message_file, uschar *firstline)
 {
@@ -70,10 +70,32 @@ int fd;
 int status;
 int count = 0;
 int size_limit = bounce_return_size_limit;
 int status;
 int count = 0;
 int size_limit = bounce_return_size_limit;
-FILE *f;
-int pid = child_open_exim(&fd);
+FILE * fp;
+int pid;
+
+#ifdef EXPERIMENTAL_DMARC
+uschar * s, * s2;
+
+/* For DMARC if there is a specific sender set, expand the variable for the
+header From: and grab the address from that for the envelope FROM. */
+
+if (  ident == ERRMESS_DMARC_FORENSIC
+   && dmarc_forensic_sender
+   && (s = expand_string(dmarc_forensic_sender))
+   && *s
+   && (s2 = expand_string(string_sprintf("${address:%s}", s)))
+   && *s2
+   )
+  pid = child_open_exim2(&fd, s2, bounce_sender_authentication);
+else
+  {
+  s = NULL;
+  pid = child_open_exim(&fd);
+  }
 
 
-/* Creation of child failed */
+#else
+pid = child_open_exim(&fd);
+#endif
 
 if (pid < 0)
   {
 
 if (pid < 0)
   {
@@ -85,149 +107,152 @@ else DEBUG(D_any) debug_printf("Child process %d for sending message\n", pid);
 
 /* Creation of child succeeded */
 
 
 /* Creation of child succeeded */
 
-f = fdopen(fd, "wb");
-if (errors_reply_to != NULL) 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);
+fp = fdopen(fd, "wb");
+if (errors_reply_to) fprintf(fp, "Reply-To: %s\n", errors_reply_to);
+fprintf(fp, "Auto-Submitted: auto-replied\n");
+
+#ifdef EXPERIMENTAL_DMARC
+if (s)
+  fprintf(fp, "From: %s\n", s);
+else
+#endif
+  moan_write_from(fp);
+
+fprintf(fp, "To: %s\n", recipient);
 
 switch(ident)
   {
   case ERRMESS_BADARGADDRESS:
 
 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;
+    fprintf(fp,
+      "Subject: Mail failure - malformed recipient address\n\n");
+    fprintf(fp,
+      "A message that you sent contained a recipient address that was incorrectly\n"
+      "constructed:\n\n");
+    fprintf(fp, "  %s  %s\n", eblock->text1, eblock->text2);
+    count = Ustrlen(eblock->text1);
+    if (count > 0 && eblock->text1[count-1] == '.')
+      fprintf(fp,
+       "\nRecipient addresses must not end with a '.' character.\n");
+    fprintf(fp,
+      "\nThe message has not been delivered to any recipients.\n");
+    break;
 
   case ERRMESS_BADNOADDRESS:
   case ERRMESS_BADADDRESS:
 
   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(fp,
+      "Subject: Mail failure - malformed recipient address\n\n");
+    fprintf(fp,
+      "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(fp, "  %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(fp, (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(fp, (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:
 
   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(fp, "Subject: Mail failure - no recipient addresses\n\n");
+    fprintf(fp,
+      "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:
 
   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(fp, "Subject: Mail failure - no recipient addresses\n\n");
+    fprintf(fp,
+      "A message that you sent contained no recipient addresses, and therefore no\n"
+      "delivery could be attempted.\n");
+    break;
 
   case ERRMESS_IOERR:
 
   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(fp, "Subject: Mail failure - system failure\n\n");
+    fprintf(fp,
+      "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:
 
   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(fp, "Subject: Mail failure - overlong header section\n\n");
+    fprintf(fp,
+      "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:
 
   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(fp, "Subject: Mail failure - overlong header line\n\n");
+    fprintf(fp,
+      "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:
 
   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(fp, "Subject: Mail failure - message too big\n\n");
+    fprintf(fp,
+      "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:
 
   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(fp, "Subject: Mail failure - too many recipients\n\n");
+    fprintf(fp,
+      "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:
 
   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,
-    " The following error was given:\n\n  %s", eblock->text1);
-    }
-  fprintf(f, "\n");
+    fprintf(fp, "Subject: Mail failure - rejected by local scanning code\n\n");
+    fprintf(fp,
+      "A message that you sent was rejected by the local scanning code that\n"
+      "checks incoming messages on this system.");
+      if (eblock->text1)
+       fprintf(fp, " The following error was given:\n\n  %s", eblock->text1);
+  fprintf(fp, "\n");
   break;
 
 #ifdef EXPERIMENTAL_DMARC
   case ERRMESS_DMARC_FORENSIC:
   break;
 
 #ifdef EXPERIMENTAL_DMARC
   case ERRMESS_DMARC_FORENSIC:
-  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),
+    bounce_return_message = TRUE;
+    bounce_return_body    = FALSE;
+    fprintf(fp, "Subject: DMARC Forensic Report for %s from IP %s\n\n",
+         eblock ? eblock->text2 : US"Unknown",
           sender_host_address);
           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(fp,
+      "A message claiming to be from you has failed the published DMARC\n"
+      "policy for your domain.\n\n");
+    while (eblock)
+      {
+      fprintf(fp, "  %s: %s\n", eblock->text1, eblock->text2);
+      count++;
+      eblock = eblock->next;
+      }
   break;
 #endif
 
   default:
   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(fp, "Subject: Mail failure\n\n");
+    fprintf(fp,
+      "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
   }
 
 /* Now, if configured, copy the message; first the headers and then the rest of
@@ -238,7 +263,7 @@ if (bounce_return_message)
   {
   if (bounce_return_body)
     {
   {
   if (bounce_return_body)
     {
-    fprintf(f, "\n"
+    fprintf(fp, "\n"
       "------ This is a copy of your message, including all the headers.");
     if (size_limit == 0 || size_limit > thismessage_size_limit)
       size_limit = thismessage_size_limit;
       "------ This is a copy of your message, including all the headers.");
     if (size_limit == 0 || size_limit > thismessage_size_limit)
       size_limit = thismessage_size_limit;
@@ -251,15 +276,15 @@ if (bounce_return_message)
         k = US"K";
         x >>= 10;
         }
         k = US"K";
         x >>= 10;
         }
-      fprintf(f, "\n"
+      fprintf(fp, "\n"
         "------ No more than %d%s characters of the body are included.\n\n",
           x, k);
       }
         "------ No more than %d%s characters of the body are included.\n\n",
           x, k);
       }
-    else fprintf(f, " ------\n\n");
+    else fprintf(fp, " ------\n\n");
     }
   else
     {
     }
   else
     {
-    fprintf(f, "\n"
+    fprintf(fp, "\n"
       "------ This is a copy of the headers that were received before the "
       "error\n       was detected.\n\n");
     }
       "------ This is a copy of the headers that were received before the "
       "error\n       was detected.\n\n");
     }
@@ -267,53 +292,68 @@ 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. */
 
   /* 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);
+    if (headers->text != NULL) fprintf(fp, "%s", CS headers->text);
     headers = headers->next;
     }
 
   if (ident != ERRMESS_VLONGHEADER && ident != ERRMESS_VLONGHDRLINE)
     headers = headers->next;
     }
 
   if (ident != ERRMESS_VLONGHEADER && ident != ERRMESS_VLONGHDRLINE)
-    fputc('\n', f);
+    fputc('\n', fp);
 
   /* After early detection of an error, the message file may be STDIN,
   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. */
 
 
   /* After early detection of an error, the message file may be STDIN,
   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)
+    BOOL enddot = f.dot_ends && message_file == stdin;
+    uschar * buf = store_get(bounce_return_linesize_limit+2);
+
+    if (firstline) fprintf(fp, "%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('.', fp);
+       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, fp);
+       break;
+       }
+
+      fputs(CS buf, fp);
       }
     }
 #ifdef EXPERIMENTAL_DMARC
   /* Overkill, but use exact test in case future code gets inserted */
   else if (bounce_return_body && message_file == NULL)
     {
       }
     }
 #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.  */
     /* This doesn't print newlines, disable until can parse and fix
      * output to be legible.  */
-    fprintf(f, "%s", expand_string(US"$message_body"));
+    fprintf(fp, "%s", expand_string(US"$message_body"));
     }
 #endif
   }
 /* Close the file, which should send an EOF to the child process
 that is receiving the message. Wait for it to finish, without a timeout. */
 
     }
 #endif
   }
 /* Close the file, which should send an EOF to the child process
 that is receiving the message. Wait for it to finish, without a timeout. */
 
-(void)fclose(f);
+(void)fclose(fp);
 status = child_close(pid, 0);  /* Waits for child to close */
 if (status != 0)
   {
 status = child_close(pid, 0);  /* Waits for child to close */
 if (status != 0)
   {
@@ -365,24 +405,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";
 
 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 */
 
   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 && f.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);
     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 */
 
     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] && !f.local_error_message)
   return moan_send_message(sender_address, ident, eblock, headers,
     message_file, firstline);
 
   return moan_send_message(sender_address, ident, eblock, headers,
     message_file, firstline);
 
@@ -575,7 +615,7 @@ fprintf(stderr, "%d previous message%s successfully processed.\n",
 
 fprintf(stderr, "The rest of the batch was abandoned.\n");
 
 
 fprintf(stderr, "The rest of the batch was abandoned.\n");
 
-exim_exit(yield);
+exim_exit(yield, US"batch");
 }
 
 
 }
 
 
@@ -598,7 +638,7 @@ uschar *
 moan_check_errorcopy(uschar *recipient)
 {
 uschar *item, *localpart, *domain;
 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;
 uschar *yield = NULL;
 uschar buffer[256];
 int sep = 0;
@@ -619,8 +659,8 @@ llen = domain++ - recipient;
 while ((item = string_nextinlist(&listptr, &sep, buffer, sizeof(buffer)))
        != NULL)
   {
 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. */
 
 
   /* If no new address found, just skip this item. */
 
index b9aef70..ef45595 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2012 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
 /* 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 MYTYPES_H
 
 #ifndef FALSE
-#define FALSE         0
+# define FALSE         0
 #endif
 
 #ifndef TRUE
 #endif
 
 #ifndef TRUE
-#define TRUE          1
+# define TRUE          1
 #endif
 
 #ifndef TRUE_UNSET
 #endif
 
 #ifndef TRUE_UNSET
-#define TRUE_UNSET    2
+# define TRUE_UNSET    2
 #endif
 
 
 #endif
 
 
@@ -30,17 +30,19 @@ 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__)
 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__))
+# define WARN_UNUSED_RESULT    __attribute__((__warn_unused_result__))
 #else
 #else
-#define PRINTF_FUNCTION(A,B)
-#define ARG_UNUSED  /**/
+# define PRINTF_FUNCTION(A,B)
+# define ARG_UNUSED  /**/
+# define WARN_UNUSED_RESULT /**/
 #endif
 
 #ifdef WANT_DEEPER_PRINTF_CHECKS
 #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
 #else
-#define ALMOST_PRINTF(A, B)
+# define ALMOST_PRINTF(A, B)
 #endif
 
 
 #endif
 
 
@@ -49,7 +51,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;
 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 */
 
 /* We also have SIGNAL_BOOL, which requires signal.h be included, so is defined
 elsewhere */
 
index 6e02b8f..5ce56b5 100644 (file)
--- a/src/os.c
+++ b/src/os.c
@@ -2,13 +2,18 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2012 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 #ifdef STAND_ALONE
 /* See the file NOTICE for conditions of use and distribution. */
 
 #ifdef STAND_ALONE
-#include <signal.h>
-#include <stdio.h>
-#include <time.h>
+# include <signal.h>
+# include <stdio.h>
+# include <time.h>
+#endif
+
+#ifndef CS
+# define CS (char *)
+# define US (unsigned char *)
 #endif
 
 /* This source file contains "default" system-dependent functions which
 #endif
 
 /* This source file contains "default" system-dependent functions which
@@ -413,7 +418,7 @@ if (avg_kd < 0)
   }
 
 if (lseek (avg_kd, avg_offset, 0) == -1L
   }
 
 if (lseek (avg_kd, avg_offset, 0) == -1L
-    || read (avg_kd, (char *)(&avg), sizeof (avg)) != sizeof(avg))
+    || read (avg_kd, CS (&avg), sizeof (avg)) != sizeof(avg))
   return -1;
 
 return (int)(((double)avg/FSCALE)*1000.0);
   return -1;
 
 return (int)(((double)avg/FSCALE)*1000.0);
@@ -645,7 +650,7 @@ ifc.V_ifc_family = V_FAMILY_QUERY;
 ifc.V_ifc_flags = 0;
 #endif
 
 ifc.V_ifc_flags = 0;
 #endif
 
-if (ioctl(vs, V_GIFCONF, (char *)&ifc) < 0)
+if (ioctl(vs, V_GIFCONF, CS &ifc) < 0)
   log_write(0, LOG_PANIC_DIE, "Unable to get interface configuration: %d %s",
     errno, strerror(errno));
 
   log_write(0, LOG_PANIC_DIE, "Unable to get interface configuration: %d %s",
     errno, strerror(errno));
 
@@ -680,7 +685,7 @@ find its length, and then recopy the correct length. */
 
 for (cp = buf; cp < buf + ifc.V_ifc_len; cp += len)
   {
 
 for (cp = buf; cp < buf + ifc.V_ifc_len; cp += len)
   {
-  memcpy((char *)&ifreq, cp, sizeof(ifreq));
+  memcpy(CS &ifreq, cp, sizeof(ifreq));
 
   #ifndef HAVE_SA_LEN
   len = sizeof(struct V_ifreq);
 
   #ifndef HAVE_SA_LEN
   len = sizeof(struct V_ifreq);
@@ -710,7 +715,7 @@ for (cp = buf; cp < buf + ifc.V_ifc_len; cp += len)
   interface hasn't been "plumbed" to any protocol (IPv4 or IPv6). Therefore,
   we now just treat this case as "down" as well. */
 
   interface hasn't been "plumbed" to any protocol (IPv4 or IPv6). Therefore,
   we now just treat this case as "down" as well. */
 
-  if (ioctl(vs, V_GIFFLAGS, (char *)&ifreq) < 0)
+  if (ioctl(vs, V_GIFFLAGS, CS &ifreq) < 0)
     {
     continue;
     /*************
     {
     continue;
     /*************
@@ -726,7 +731,7 @@ for (cp = buf; cp < buf + ifc.V_ifc_len; cp += len)
   GIFFLAGS may have wrecked the data. */
 
   #ifndef SIOCGIFCONF_GIVES_ADDR
   GIFFLAGS may have wrecked the data. */
 
   #ifndef SIOCGIFCONF_GIVES_ADDR
-  if (ioctl(vs, V_GIFADDR, (char *)&ifreq) < 0)
+  if (ioctl(vs, V_GIFADDR, CS &ifreq) < 0)
     log_write(0, LOG_PANIC_DIE, "Unable to get IP address for %s interface: "
       "%d %s", ifreq.V_ifr_name, errno, strerror(errno));
   addrp = &ifreq.V_ifr_addr;
     log_write(0, LOG_PANIC_DIE, "Unable to get IP address for %s interface: "
       "%d %s", ifreq.V_ifr_name, errno, strerror(errno));
   addrp = &ifreq.V_ifr_addr;
@@ -833,9 +838,57 @@ os_get_dns_resolver_res(void)
 
 #endif /* OS_GET_DNS_RESOLVER_RES */
 
 
 #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(CS 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 US  getcwd(CS buffer, size);
+}
+#else
+#ifndef PATH_MAX
+# define PATH_MAX 4096
+#endif
+unsigned char *
+os_getcwd(unsigned char * buffer, size_t size)
+{
+char * b = CS 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
+
+/* ----------------------------------------------------------------------- */
 
 
 
 
 
 
@@ -869,7 +922,7 @@ int rc;
 printf("Testing restarting signal; wait for handler message, then type a line\n");
 strcpy(buffer, "*** default ***\n");
 os_restarting_signal(SIGALRM, sigalrm_handler);
 printf("Testing restarting signal; wait for handler message, then type a line\n");
 strcpy(buffer, "*** default ***\n");
 os_restarting_signal(SIGALRM, sigalrm_handler);
-alarm(2);
+ALARM(2);
 if ((rc = read(fd, buffer, sizeof(buffer))) < 0)
   printf("No data read\n");
 else
 if ((rc = read(fd, buffer, sizeof(buffer))) < 0)
   printf("No data read\n");
 else
@@ -877,12 +930,12 @@ else
   buffer[rc] = 0;
   printf("Read: %s", buffer);
   }
   buffer[rc] = 0;
   printf("Read: %s", buffer);
   }
-alarm(0);
+ALARM_CLR(0);
 
 printf("Testing non-restarting signal; should read no data after handler message\n");
 strcpy(buffer, "*** default ***\n");
 os_non_restarting_signal(SIGALRM, sigalrm_handler);
 
 printf("Testing non-restarting signal; should read no data after handler message\n");
 strcpy(buffer, "*** default ***\n");
 os_non_restarting_signal(SIGALRM, sigalrm_handler);
-alarm(2);
+ALARM(2);
 if ((rc = read(fd, buffer, sizeof(buffer))) < 0)
   printf("No data read\n");
 else
 if ((rc = read(fd, buffer, sizeof(buffer))) < 0)
   printf("No data read\n");
 else
@@ -890,7 +943,7 @@ else
   buffer[rc] = 0;
   printf("Read: %s", buffer);
   }
   buffer[rc] = 0;
   printf("Read: %s", buffer);
   }
-alarm(0);
+ALARM_CLR(0);
 
 printf("Testing load averages (last test - ^C to kill)\n");
 for (;;)
 
 printf("Testing load averages (last test - ^C to kill)\n");
 for (;;)
index 5527127..4e6e9a9 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     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
 /* 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_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 */
 
 /* End of osfunctions.h */
index 94e42c1..4b0efa0 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for parsing addresses */
 /* 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
 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 *
 */
 
 static uschar *
@@ -421,10 +421,10 @@ for (;;)
   if (*s == '\"')
     {
     *t++ = '\"';
   if (*s == '\"')
     {
     *t++ = '\"';
-    while ((c = *(++s)) != 0 && c != '\"')
+    while ((c = *++s) && c != '\"')
       {
       *t++ = c;
       {
       *t++ = c;
-      if (c == '\\' && s[1] != 0) *t++ = *(++s);
+      if (c == '\\' && s[1]) *t++ = *++s;
       }
     if (c == '\"')
       {
       }
     if (c == '\"')
       {
@@ -443,7 +443,7 @@ for (;;)
   else while (!mac_iscntrl_or_special(*s) || *s == '\\')
     {
     c = *t++ = *s++;
   else while (!mac_iscntrl_or_special(*s) || *s == '\\')
     {
     c = *t++ = *s++;
-    if (c == '\\' && *s != 0) *t++ = *s++;
+    if (c == '\\' && *s) *t++ = *s++;
     }
 
   /* Terminate the word and skip subsequent comment */
     }
 
   /* Terminate the word and skip subsequent comment */
@@ -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)
 {
 s = read_local_part(s, t, errorptr, FALSE);
 if (*errorptr == NULL)
-  {
   if (*s != term)
   if (*s != term)
-    {
     if (*s != '@')
       *errorptr = string_sprintf("\"@\" or \".\" expected after \"%s\"", t);
     else
     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);
       }
       *domainptr = t;
       s = read_domain(s, t, errorptr);
       }
-    }
-  }
 return s;
 }
 
 return s;
 }
 
@@ -624,8 +620,8 @@ parse_extract_address(uschar *mailbox, uschar **errorptr, int *start, int *end,
 {
 uschar *yield = store_get(Ustrlen(mailbox) + 1);
 uschar *startptr, *endptr;
 {
 uschar *yield = store_get(Ustrlen(mailbox) + 1);
 uschar *startptr, *endptr;
-uschar *s = (uschar *)mailbox;
-uschar *t = (uschar *)yield;
+uschar *s = US mailbox;
+uschar *t = US yield;
 
 *domain = 0;
 
 
 *domain = 0;
 
@@ -642,7 +638,7 @@ RESTART:   /* Come back here after passing a group name */
 s = skip_comment(s);
 startptr = s;                                 /* In case addr-spec */
 s = read_local_part(s, t, errorptr, TRUE);    /* Dot separated words */
 s = skip_comment(s);
 startptr = s;                                 /* In case addr-spec */
 s = read_local_part(s, t, errorptr, TRUE);    /* Dot separated words */
-if (*errorptr != NULL) goto PARSE_FAILED;
+if (*errorptr) goto PARSE_FAILED;
 
 /* If the terminator is neither < nor @ then the format of the address
 must either be a bare local-part (we are now at the end), or a phrase
 
 /* If the terminator is neither < nor @ then the format of the address
 must either be a bare local-part (we are now at the end), or a phrase
@@ -662,10 +658,10 @@ if (*s != '@' && *s != '<')
   end of string will produce a null local_part and therefore fail. We don't
   need to keep updating t, as the phrase isn't to be kept. */
 
   end of string will produce a null local_part and therefore fail. We don't
   need to keep updating t, as the phrase isn't to be kept. */
 
-  while (*s != '<' && (!parse_allow_group || *s != ':'))
+  while (*s != '<' && (!f.parse_allow_group || *s != ':'))
     {
     s = read_local_part(s, t, errorptr, FALSE);
     {
     s = read_local_part(s, t, errorptr, FALSE);
-    if (*errorptr != NULL)
+    if (*errorptr)
       {
       *errorptr = string_sprintf("%s (expected word or \"<\")", *errorptr);
       goto PARSE_FAILED;
       {
       *errorptr = string_sprintf("%s (expected word or \"<\")", *errorptr);
       goto PARSE_FAILED;
@@ -674,8 +670,8 @@ if (*s != '@' && *s != '<')
 
   if (*s == ':')
     {
 
   if (*s == ':')
     {
-    parse_found_group = TRUE;
-    parse_allow_group = FALSE;
+    f.parse_found_group = TRUE;
+    f.parse_allow_group = FALSE;
     s++;
     goto RESTART;
     }
     s++;
     goto RESTART;
     }
@@ -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 <>
 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 == '<')
   {
 
 if (*s == '<')
   {
@@ -700,8 +696,11 @@ if (*s == '<')
   int bracket_count = 1;
 
   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;
 
   t = yield;
   startptr = s;
@@ -715,7 +714,7 @@ if (*s == '<')
   if (*s == '@')
     {
     s = read_route(s, t, errorptr);
   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;
     }
     *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);
   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");
     *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++ != '>')
     {
   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, (int)(s - US mailbox - 1), mailbox);
     goto PARSE_FAILED;
     }
 
     goto PARSE_FAILED;
     }
 
@@ -790,21 +790,21 @@ move it back past white space if necessary. */
 PARSE_SUCCEEDED:
 if (*s != 0)
   {
 PARSE_SUCCEEDED:
 if (*s != 0)
   {
-  if (parse_found_group && *s == ';')
+  if (f.parse_found_group && *s == ';')
     {
     {
-    parse_found_group = FALSE;
-    parse_allow_group = TRUE;
+    f.parse_found_group = FALSE;
+    f.parse_allow_group = TRUE;
     }
   else
     {
     *errorptr = string_sprintf("malformed address: %.32s may not follow %.*s",
     }
   else
     {
     *errorptr = string_sprintf("malformed address: %.32s may not follow %.*s",
-      s, s - (uschar *)mailbox, mailbox);
+      s, (int)(s - US mailbox), mailbox);
     goto PARSE_FAILED;
     }
   }
     goto PARSE_FAILED;
     }
   }
-*start = startptr - (uschar *)mailbox;      /* Return offsets */
+*start = startptr - US mailbox;      /* Return offsets */
 while (isspace(endptr[-1])) endptr--;
 while (isspace(endptr[-1])) endptr--;
-*end = endptr - (uschar *)mailbox;
+*end = endptr - US mailbox;
 
 /* Although this code has no limitation on the length of address extracted,
 other parts of Exim may have limits, and in any case, RFC 2821 limits local
 
 /* Although this code has no limitation on the length of address extracted,
 other parts of Exim may have limits, and in any case, RFC 2821 limits local
@@ -817,17 +817,17 @@ if (*end - *start > ADDRESS_MAXLENGTH)
   return NULL;
   }
 
   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
 this. We must, however, keep the flags correct. */
 
 PARSE_FAILED:
 
 /* 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
 this. We must, however, keep the flags correct. */
 
 PARSE_FAILED:
-if (parse_found_group && *s == ';')
+if (f.parse_found_group && *s == ';')
   {
   {
-  parse_found_group = FALSE;
-  parse_allow_group = TRUE;
+  f.parse_found_group = FALSE;
+  f.parse_allow_group = TRUE;
   }
 return NULL;
 }
   }
 return NULL;
 }
@@ -866,17 +866,17 @@ Returns:       pointer to the original string, if no quoting needed, or
                the introduction
 */
 
                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)
 {
   int buffer_size, BOOL fold)
 {
-uschar *s = string;
+const uschar *s = string;
 uschar *p, *t;
 int hlen;
 BOOL coded = FALSE;
 BOOL first_byte = FALSE;
 
 uschar *p, *t;
 int hlen;
 BOOL coded = FALSE;
 BOOL first_byte = FALSE;
 
-if (charset == NULL) charset = US"iso-8859-1";
+if (!charset) charset = US"iso-8859-1";
 
 /* We don't expect this to fail! */
 
 
 /* We don't expect this to fail! */
 
@@ -910,11 +910,10 @@ for (; len > 0; len--)
       {
       *t++ = '_';
       first_byte = FALSE;
       {
       *t++ = '_';
       first_byte = FALSE;
-      } 
+      }
     else
       {
     else
       {
-      sprintf(CS t, "=%02X", ch);
-      while (*t != 0) t++;
+      t += sprintf(CS t, "=%02X", ch);
       coded = TRUE;
       first_byte = !first_byte;
       }
       coded = TRUE;
       first_byte = !first_byte;
       }
@@ -926,7 +925,7 @@ for (; len > 0; len--)
 *t++ = '=';
 *t = 0;
 
 *t++ = '=';
 *t = 0;
 
-return coded? buffer : string;
+return coded ? buffer : string;
 }
 
 
 }
 
 
@@ -985,12 +984,13 @@ Arguments:
 Returns:       the fixed RFC822 phrase
 */
 
 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;
 {
 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";
 
 while (len > 0 && isspace(*phrase)) { phrase++; len--; }
 if (len > buffer_size/4) return US"Name too long";
@@ -1119,7 +1119,7 @@ while (s < end)
 
         else if (ch == '(')
           {
 
         else if (ch == '(')
           {
-          uschar *ss = s;     /* uschar after '(' */
+          const uschar *ss = s;     /* uschar after '(' */
           int level = 1;
           while(ss < end)
             {
           int level = 1;
           while(ss < end)
             {
@@ -1245,7 +1245,7 @@ Returns:      FF_DELIVERED      addresses extracted
 
 int
 parse_forward_list(uschar *s, int options, address_item **anchor,
 
 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;
   error_block **syntax_errors)
 {
 int count = 0;
@@ -1428,7 +1428,7 @@ for (;;)
 
     /* Check file name if required */
 
 
     /* Check file name if required */
 
-    if (directory != NULL)
+    if (directory)
       {
       int len = Ustrlen(directory);
       uschar *p = filename + len;
       {
       int len = Ustrlen(directory);
       uschar *p = filename + len;
@@ -1440,16 +1440,53 @@ for (;;)
         return FF_ERROR;
         }
 
         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. */
 
       /* 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;
         {
         int temp;
-        while (*(++p) != 0 && *p != '/');
+        while (*++p && *p != '/');
         temp = *p;
         *p = 0;
         if (Ulstat(filename, &statbuf) != 0)
         temp = *p;
         *p = 0;
         if (Ulstat(filename, &statbuf) != 0)
@@ -1469,11 +1506,16 @@ for (;;)
           return FF_ERROR;
           }
         }
           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;
       {
       *error = string_open_failed(errno, "included file %s", filename);
       return FF_INCLUDEFAIL;
@@ -1489,7 +1531,7 @@ for (;;)
 
     /* If directory was checked, double check that we opened a regular file */
 
 
     /* 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);
       {
       *error = string_sprintf("included file %s is not a regular file in "
         "the %s directory", filename, directory);
@@ -1521,10 +1563,9 @@ for (;;)
       error, incoming_domain, directory, syntax_errors);
     if (frc != FF_DELIVERED && frc != FF_NOTDELIVERED) return frc;
 
       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++;
       last->next = *anchor;
       *anchor = addr;
       count++;
@@ -2108,7 +2149,7 @@ allow_utf8_domains = FALSE;
 
 printf("Testing parse_extract_address with group syntax\n");
 
 
 printf("Testing parse_extract_address with group syntax\n");
 
-parse_allow_group = TRUE;
+f.parse_allow_group = TRUE;
 while (Ufgets(buffer, sizeof(buffer), stdin) != NULL)
   {
   uschar *out;
 while (Ufgets(buffer, sizeof(buffer), stdin) != NULL)
   {
   uschar *out;
index 610c011..47f92ee 100644 (file)
@@ -1,6 +1,7 @@
 # Make file for building the pdkim library.
 # Make file for building the pdkim library.
+# Copyright (c) The Exim Maintainers 1995 - 2018
 
 
-OBJ = base64.o bignum.o pdkim.o rsa.o sha1.o sha2.o
+OBJ = pdkim.o signing.o
 
 pdkim.a:         $(OBJ)
                 @$(RM_COMMAND) -f pdkim.a
 
 pdkim.a:         $(OBJ)
                 @$(RM_COMMAND) -f pdkim.a
@@ -10,13 +11,9 @@ pdkim.a:         $(OBJ)
 
 .SUFFIXES:       .o .c
 .c.o:;           @echo "$(CC) $*.c"
 
 .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
+signing.o: $(HDRS) crypt_ver.h signing.h signing.c
 
 # End
 
 # 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>
 
 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/.
 
 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..564b66d
--- /dev/null
@@ -0,0 +1,33 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) Jeremy Harris 1995 - 2018 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+/* Signing and hashing routine selection for PDKIM */
+
+#include "../exim.h"
+#include "../sha_ver.h"
+
+
+#ifdef USE_GNUTLS
+# include <gnutls/gnutls.h>
+
+# if GNUTLS_VERSION_NUMBER >= 0x030000
+#  define SIGN_GNUTLS
+#  if GNUTLS_VERSION_NUMBER >= 0x030600
+#   define SIGN_HAVE_ED25519
+#  endif
+# else
+#  define SIGN_GCRYPT
+# endif
+
+#else
+# define SIGN_OPENSSL
+#  if !defined(LIBRESSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x10101000L
+#   define SIGN_HAVE_ED25519
+#  endif
+
+#endif
+
dissimilarity index 89%
index 4f0da3f..594af03 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 - 2018  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 SIGN_OPENSSL
+# include <openssl/rsa.h>
+# include <openssl/ssl.h>
+# include <openssl/err.h>
+#elif defined(SIGN_GNUTLS)
+# include <gnutls/gnutls.h>
+# include <gnutls/x509.h>
+#endif
+
+#include "pdkim.h"
+#include "signing.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
+
+/* -------------------------------------------------------------------------- */
+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_canons[] = {
+  US"simple",
+  US"relaxed",
+  NULL
+};
+
+const pdkim_hashtype pdkim_hashes[] = {
+  { US"sha1",   HASH_SHA1 },
+  { US"sha256", HASH_SHA2_256 },
+  { US"sha512", HASH_SHA2_512 }
+};
+
+const uschar * pdkim_keytypes[] = {
+  [KEYTYPE_RSA] =      US"rsa",
+#ifdef SIGN_HAVE_ED25519
+  [KEYTYPE_ED25519] =  US"ed25519",            /* Works for 3.6.0 GnuTLS, OpenSSL 1.1.1 */
+#endif
+
+#ifdef notyet_EC_dkim_extensions       /* https://tools.ietf.org/html/draft-srose-dkim-ecc-00 */
+  US"eccp256",
+  US"eccp348",
+  US"ed448",
+#endif
+};
+
+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 }
+};
+
+
+static blob lineending = {.data = US"\r\n", .len = 2};
+
+/* -------------------------------------------------------------------------- */
+uschar *
+dkim_sig_to_a_tag(const pdkim_signature * sig)
+{
+if (  sig->keytype < 0  || sig->keytype > nelem(pdkim_keytypes)
+   || sig->hashtype < 0 || sig->hashtype > nelem(pdkim_hashes))
+  return US"err";
+return string_sprintf("%s-%s",
+  pdkim_keytypes[sig->keytype], pdkim_hashes[sig->hashtype].dkim_hashname);
+}
+
+
+int
+pdkim_hashname_to_hashtype(const uschar * s, unsigned len)
+{
+int i;
+if (!len) len = Ustrlen(s);
+for (i = 0; i < nelem(pdkim_hashes); i++)
+  if (Ustrncmp(s, pdkim_hashes[i].dkim_hashname, len) == 0)
+    return i;
+return -1;
+}
+
+void
+pdkim_cstring_to_canons(const uschar * s, unsigned len,
+  int * canon_head, int * canon_body)
+{
+int i;
+if (!len) len = Ustrlen(s);
+for (i = 0; pdkim_combined_canons[i].str; i++)
+  if (  Ustrncmp(s, pdkim_combined_canons[i].str, len) == 0
+     && len == Ustrlen(pdkim_combined_canons[i].str))
+    {
+    *canon_head = pdkim_combined_canons[i].canon_headers;
+    *canon_body = pdkim_combined_canons[i].canon_body;
+    break;
+    }
+}
+
+
+
+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_FAIL_SIG_ALGO_MISMATCH: return "PDKIM_VERIFY_FAIL_SIG_ALGO_MISMATCH";
+  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 uschar *
+pdkim_errstr(int status)
+{
+switch(status)
+  {
+  case PDKIM_OK:               return US"OK";
+  case PDKIM_FAIL:             return US"FAIL";
+  case PDKIM_ERR_RSA_PRIVKEY:  return US"PRIVKEY";
+  case PDKIM_ERR_RSA_SIGNING:  return US"SIGNING";
+  case PDKIM_ERR_LONG_LINE:    return US"LONG_LINE";
+  case PDKIM_ERR_BUFFER_TOO_SMALL:     return US"BUFFER_TOO_SMALL";
+  case PDKIM_ERR_EXCESS_SIGS:  return US"EXCESS_SIGS";
+  case PDKIM_SIGN_PRIVKEY_WRAP:        return US"PRIVKEY_WRAP";
+  case PDKIM_SIGN_PRIVKEY_B64D:        return US"PRIVKEY_B64D";
+  default: return US"(unknown)";
+  }
+}
+
+
+/* -------------------------------------------------------------------------- */
+/* Print debugging functions */
+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");
+}
+
+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(gstring * str)
+{
+uschar * p = str->s;
+uschar * q;
+
+while (*p == '\t' || *p == ' ')                /* dump the leading whitespace */
+  { str->size--; str->ptr--; str->s++; }
+
+while (  str->ptr > 0
+      && ((q = str->s + str->ptr - 1),  (*q == '\t' || *q == ' '))
+      )
+  str->ptr--;                          /* dump trailing whitespace */
+
+(void) string_from_gstring(str);
+}
+
+
+
+/* -------------------------------------------------------------------------- */
+
+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.  Entries can be prefixed for multi- or over-signing,
+   in which case do not invalidate.
+
+   Returns OK for a match, or fail-code
+*/
+
+static int
+header_name_match(const uschar * header, uschar * tick)
+{
+const uschar * ticklist = tick;
+int sep = ':';
+BOOL multisign;
+uschar * hname, * p, * ele;
+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);
+
+while (p = US ticklist, ele = string_nextinlist(&ticklist, &sep, NULL, 0))
+  {
+  switch (*ele)
+  {
+  case '=': case '+':  multisign = TRUE; ele++; break;
+  default:             multisign = FALSE; break;
+  }
+
+  if (strcmpic(ele, hname) == 0)
+    {
+    if (!multisign)
+      *p = '_';        /* Invalidate this header name instance in tick-off list */
+    return PDKIM_OK;
+    }
+  }
+return PDKIM_FAIL;
+}
+
+
+/* -------------------------------------------------------------------------- */
+/* Performs "relaxed" canonicalization of a header. */
+
+uschar *
+pdkim_relax_header_n(const uschar * header, int len, BOOL append_crlf)
+{
+BOOL past_field_name = FALSE;
+BOOL seen_wsp = FALSE;
+const uschar * p;
+uschar * relaxed = store_get(len+3);
+uschar * q = relaxed;
+
+for (p = header; p - header < len; p++)
+  {
+  uschar c = *p;
+
+  if (c == '\r' || c == '\n')  /* Ignore CR & LF */
+    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 immediately before the colon */
+      seen_wsp = TRUE;         /* This removes WSP immediately 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 (append_crlf) { *q++ = '\r'; *q++ = '\n'; }
+*q = '\0';
+return relaxed;
+}
+
+
+uschar *
+pdkim_relax_header(const uschar * header, BOOL append_crlf)
+{
+return pdkim_relax_header_n(header, Ustrlen(header), append_crlf);
+}
+
+
+/* -------------------------------------------------------------------------- */
+#define PDKIM_QP_ERROR_DECODE -1
+
+static const uschar *
+pdkim_decode_qp_char(const uschar *qp_p, int *c)
+{
+const 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(const uschar * str)
+{
+int nchar = 0;
+uschar * q;
+const 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;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+void
+pdkim_decode_base64(const uschar * str, blob * b)
+{
+int dlen = b64decode(str, &b->data);
+if (dlen < 0) b->data = NULL;
+b->len = dlen;
+}
+
+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;
+gstring * cur_tag = NULL;
+gstring * cur_val = NULL;
+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->version = 0;
+sig->keytype = -1;
+sig->hashtype = -1;
+
+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, p, 1);
+
+    if (c == '=')
+      {
+      if (Ustrcmp(string_from_gstring(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')
+      {
+      /* We must have both tag and value, and tags must be one char except
+      for the possibility of "bh". */
+
+      if (  cur_tag && cur_val
+        && (cur_tag->ptr == 1 || *cur_tag->s == 'b')
+        )
+        {
+       (void) string_from_gstring(cur_val);
+       pdkim_strtrim(cur_val);
+
+       DEBUG(D_acl) debug_printf(" %s=%s\n", cur_tag->s, cur_val->s);
+
+       switch (*cur_tag->s)
+         {
+         case 'b':                             /* sig-data or body-hash */
+           switch (cur_tag->s[1])
+             {
+             case '\0': pdkim_decode_base64(cur_val->s, &sig->sighash); break;
+             case 'h':  if (cur_tag->ptr == 2)
+                          pdkim_decode_base64(cur_val->s, &sig->bodyhash);
+                        break;
+             default:   break;
+             }
+           break;
+         case 'v':                                     /* version */
+             /* We only support version 1, and that is currently the
+                only version there is. */
+           sig->version =
+             Ustrcmp(cur_val->s, PDKIM_SIGNATURE_VERSION) == 0 ? 1 : -1;
+           break;
+         case 'a':                                     /* algorithm */
+           {
+           const uschar * list = cur_val->s;
+           int sep = '-';
+           uschar * elem;
+
+           if ((elem = string_nextinlist(&list, &sep, NULL, 0)))
+             for(i = 0; i < nelem(pdkim_keytypes); i++)
+               if (Ustrcmp(elem, pdkim_keytypes[i]) == 0)
+                 { sig->keytype = i; break; }
+           if ((elem = string_nextinlist(&list, &sep, NULL, 0)))
+             for (i = 0; i < nelem(pdkim_hashes); i++)
+               if (Ustrcmp(elem, pdkim_hashes[i].dkim_hashname) == 0)
+                 { sig->hashtype = i; break; }
+           }
+
+         case 'c':                                     /* canonicalization */
+           pdkim_cstring_to_canons(cur_val->s, 0,
+                                   &sig->canon_headers, &sig->canon_body);
+           break;
+         case 'q':                             /* Query method (for pubkey)*/
+           for (i = 0; pdkim_querymethods[i]; i++)
+             if (Ustrcmp(cur_val->s, pdkim_querymethods[i]) == 0)
+               {
+               sig->querymethod = i;   /* we never actually use this */
+               break;
+               }
+           break;
+         case 's':                                     /* Selector */
+           sig->selector = string_copyn(cur_val->s, cur_val->ptr); break;
+         case 'd':                                     /* SDID */
+           sig->domain = string_copyn(cur_val->s, cur_val->ptr); break;
+         case 'i':                                     /* AUID */
+           sig->identity = pdkim_decode_qp(cur_val->s); break;
+         case 't':                                     /* Timestamp */
+           sig->created = strtoul(CS cur_val->s, NULL, 10); break;
+         case 'x':                                     /* Expiration */
+           sig->expires = strtoul(CS cur_val->s, NULL, 10); break;
+         case 'l':                                     /* Body length count */
+           sig->bodylength = strtol(CS cur_val->s, NULL, 10); break;
+         case 'h':                                     /* signed header fields */
+           sig->headernames = string_copyn(cur_val->s, cur_val->ptr); break;
+         case 'z':                                     /* Copied headfields */
+           sig->copiedheaders = pdkim_decode_qp(cur_val->s); break;
+/*XXX draft-ietf-dcrup-dkim-crypto-05 would need 'p' tag support
+for rsafp signatures.  But later discussion is dropping those. */
+         default:
+           DEBUG(D_acl) debug_printf(" Unknown tag encountered\n");
+           break;
+         }
+       }
+      cur_tag = cur_val = NULL;
+      in_b_val = FALSE;
+      where = PDKIM_HDR_LIMBO;
+      }
+    else
+      cur_val = string_catn(cur_val, p, 1);
+    }
+
+NEXT_CHAR:
+  if (c == '\0')
+    break;
+
+  if (!in_b_val)
+    *q++ = c;
+  }
+
+if (sig->keytype < 0 || sig->hashtype < 0)     /* Cannot verify this signature */
+  return NULL;
+
+*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 (!pdkim_set_sig_bodyhash(ctx, sig))
+  return NULL;
+
+return sig;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+pdkim_pubkey *
+pdkim_parse_pubkey_record(const uschar *raw_record)
+{
+const uschar * ele;
+int sep = ';';
+pdkim_pubkey * pub;
+
+pub = store_get(sizeof(pdkim_pubkey));
+memset(pub, 0, sizeof(pdkim_pubkey));
+
+while ((ele = string_nextinlist(&raw_record, &sep, NULL, 0)))
+  {
+  const uschar * val;
+
+  if ((val = Ustrchr(ele, '=')))
+    {
+    int taglen = val++ - ele;
+
+    DEBUG(D_acl) debug_printf(" %.*s=%s\n", taglen, ele, val);
+    switch (ele[0])
+      {
+      case 'v': pub->version = val;                    break;
+      case 'h': pub->hashes = val;                     break;
+      case 'k': pub->keytype = val;                    break;
+      case 'g': pub->granularity = val;                        break;
+      case 'n': pub->notes = pdkim_decode_qp(val);     break;
+      case 'p': pdkim_decode_base64(val, &pub->key);   break;
+      case 's': pub->srvtype = val;                    break;
+      case 't': if (Ustrchr(val, 'y')) pub->testing = 1;
+               if (Ustrchr(val, 's')) pub->no_subdomaining = 1;
+               break;
+      default:  DEBUG(D_acl) debug_printf(" Unknown tag encountered\n"); 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)
+  {
+  DEBUG(D_acl) debug_printf(" Bad v= field\n");
+  return NULL;
+  }
+
+if (!pub->granularity) pub->granularity = US"*";
+if (!pub->keytype    ) pub->keytype     = US"rsa";
+if (!pub->srvtype    ) pub->srvtype     = US"*";
+
+/* p= is required */
+if (pub->key.data)
+  return pub;
+
+DEBUG(D_acl) debug_printf(" Missing p= field\n");
+return NULL;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+/* Update one bodyhash with some additional data.
+If we have to relax the data for this sig, return our copy of it. */
+
+static blob *
+pdkim_update_ctx_bodyhash(pdkim_bodyhash * b, blob * orig_data, blob * relaxed_data)
+{
+blob * canon_data = orig_data;
+/* Defaults to simple canon (no further treatment necessary) */
+
+if (b->canon_method == PDKIM_CANON_RELAXED)
+  {
+  /* Relax the line if not done already */
+  if (!relaxed_data)
+    {
+    BOOL seen_wsp = FALSE;
+    const uschar * p, * r;
+    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(sizeof(blob) + orig_data->len+1);
+    relaxed_data->data = US (relaxed_data+1);
+
+    for (p = orig_data->data, r = p + orig_data->len; p < r; p++)
+      {
+      char c = *p;
+      if (c == '\r')
+       {
+       if (q > 0 && relaxed_data->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->data[q++] = c;
+      }
+    relaxed_data->data[q] = '\0';
+    relaxed_data->len = q;
+    }
+  canon_data = relaxed_data;
+  }
+
+/* Make sure we don't exceed the to-be-signed body length */
+if (  b->bodylength >= 0
+   && b->signed_body_bytes + (unsigned long)canon_data->len > b->bodylength
+   )
+  canon_data->len = b->bodylength - b->signed_body_bytes;
+
+if (canon_data->len > 0)
+  {
+  exim_sha_update(&b->body_hash_ctx, CUS canon_data->data, canon_data->len);
+  b->signed_body_bytes += canon_data->len;
+  DEBUG(D_acl) pdkim_quoteprint(canon_data->data, canon_data->len);
+  }
+
+return relaxed_data;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+static void
+pdkim_finish_bodyhash(pdkim_ctx * ctx)
+{
+pdkim_bodyhash * b;
+pdkim_signature * sig;
+
+for (b = ctx->bodyhash; b; b = b->next)                /* Finish hashes */
+  {
+  DEBUG(D_acl) debug_printf("PDKIM: finish bodyhash %d/%d/%ld len %ld\n",
+           b->hashtype, b->canon_method, b->bodylength, b->signed_body_bytes);
+  exim_sha_finish(&b->body_hash_ctx, &b->bh);
+  }
+
+/* Traverse all signatures */
+for (sig = ctx->sig; sig; sig = sig->next)
+  {
+  b = sig->calc_body_hash;
+
+  DEBUG(D_acl)
+    {
+    debug_printf("PDKIM [%s] Body bytes (%s) hashed: %lu\n"
+                "PDKIM [%s] Body %s computed: ",
+       sig->domain, pdkim_canons[b->canon_method], b->signed_body_bytes,
+       sig->domain, pdkim_hashes[b->hashtype].dkim_hashname);
+    pdkim_hexprint(CUS b->bh.data, b->bh.len);
+    }
+
+  /* SIGNING -------------------------------------------------------------- */
+  if (ctx->flags & PDKIM_MODE_SIGN)
+    {
+    /* If bodylength limit is set, and we have received less bytes
+       than the requested amount, effectively remove the limit tag. */
+    if (b->signed_body_bytes < sig->bodylength)
+      sig->bodylength = -1;
+    }
+
+  else
+  /* VERIFICATION --------------------------------------------------------- */
+  /* Be careful that the header sig included a bodyash */
+
+    if (  sig->bodyhash.data
+       && memcmp(b->bh.data, sig->bodyhash.data, b->bh.len) == 0)
+      {
+      DEBUG(D_acl) debug_printf("PDKIM [%s] Body hash compared OK\n", sig->domain);
+      }
+    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 void
+pdkim_body_complete(pdkim_ctx * ctx)
+{
+pdkim_bodyhash * b;
+
+/* 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 */
+
+for (b = ctx->bodyhash; b; b = b->next)
+  if (  b->canon_method == PDKIM_CANON_SIMPLE
+     && b->signed_body_bytes == 0
+     && b->num_buffered_blanklines > 0
+     )
+    (void) pdkim_update_ctx_bodyhash(b, &lineending, NULL);
+
+ctx->flags |= PDKIM_SEEN_EOD;
+ctx->linebuf_offset = 0;
+}
+
+
+
+/* -------------------------------------------------------------------------- */
+/* Call from pdkim_feed below for processing complete body lines */
+/* NOTE: the line is not NUL-terminated; but we have a count */
+
+static void
+pdkim_bodyline_complete(pdkim_ctx * ctx)
+{
+blob line = {.data = ctx->linebuf, .len = ctx->linebuf_offset};
+pdkim_bodyhash * b;
+blob * rnl = NULL;
+blob * rline = NULL;
+
+/* Ignore extra data if we've seen the end-of-data marker */
+if (ctx->flags & PDKIM_SEEN_EOD) goto all_skip;
+
+/* We've always got one extra byte to stuff a zero ... */
+ctx->linebuf[line.len] = '\0';
+
+/* Terminate on EOD marker */
+if (ctx->flags & PDKIM_DOT_TERM)
+  {
+  if (memcmp(line.data, ".\r\n", 3) == 0)
+    { pdkim_body_complete(ctx); return; }
+
+  /* Unstuff dots */
+  if (memcmp(line.data, "..", 2) == 0)
+    { line.data++; line.len--; }
+  }
+
+/* Empty lines need to be buffered until we find a non-empty line */
+if (memcmp(line.data, "\r\n", 2) == 0)
+  {
+  for (b = ctx->bodyhash; b; b = b->next) b->num_buffered_blanklines++;
+  goto all_skip;
+  }
+
+/* Process line for each bodyhash separately */
+for (b = ctx->bodyhash; b; b = b->next)
+  {
+  if (b->canon_method == PDKIM_CANON_RELAXED)
+    {
+    /* Lines with just spaces need to be buffered too */
+    uschar * cp = line.data;
+    char c;
+
+    while ((c = *cp))
+      {
+      if (c == '\r' && cp[1] == '\n') break;
+      if (c != ' ' && c != '\t') goto hash_process;
+      cp++;
+      }
+
+    b->num_buffered_blanklines++;
+    goto hash_skip;
+    }
+
+hash_process:
+  /* At this point, we have a non-empty line, so release the buffered ones. */
+
+  while (b->num_buffered_blanklines)
+    {
+    rnl = pdkim_update_ctx_bodyhash(b, &lineending, rnl);
+    b->num_buffered_blanklines--;
+    }
+
+  rline = pdkim_update_ctx_bodyhash(b, &line, rline);
+hash_skip: ;
+  }
+
+if (rnl) store_free(rnl);
+if (rline) store_free(rline);
+
+all_skip:
+
+ctx->linebuf_offset = 0;
+return;
+}
+
+
+/* -------------------------------------------------------------------------- */
+/* Callback from pdkim_feed below for processing complete headers */
+#define DKIM_SIGNATURE_HEADERNAME "DKIM-Signature:"
+
+static int
+pdkim_header_complete(pdkim_ctx * ctx)
+{
+pdkim_signature * sig, * last_sig;
+
+/* Special case: The last header can have an extra \r appended */
+if ( (ctx->cur_header->ptr > 1) &&
+     (ctx->cur_header->s[ctx->cur_header->ptr-1] == '\r') )
+  --ctx->cur_header->ptr;
+(void) string_from_gstring(ctx->cur_header);
+
+#ifdef EXPERIMENTAL_ARC
+/* Feed the header line to ARC processing */
+(void) arc_header_feed(ctx->cur_header, !(ctx->flags & PDKIM_MODE_SIGN));
+#endif
+
+if (++ctx->num_headers > PDKIM_MAX_HEADERS) goto BAIL;
+
+/* SIGNING -------------------------------------------------------------- */
+if (ctx->flags & PDKIM_MODE_SIGN)
+  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->s);
+
+/* 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->s, ctx->cur_header->ptr);
+    }
+#endif
+  if (strncasecmp(CCS ctx->cur_header->s,
+                 DKIM_SIGNATURE_HEADERNAME,
+                 Ustrlen(DKIM_SIGNATURE_HEADERNAME)) == 0)
+    {
+    /* 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");
+
+    sig = pdkim_parse_sig_header(ctx, ctx->cur_header->s);
+
+    if (!(last_sig = ctx->sig))
+      ctx->sig = sig;
+    else
+      {
+      while (last_sig->next) last_sig = last_sig->next;
+      last_sig->next = sig;
+      }
+
+    if (--dkim_collect_input == 0)
+      {
+      ctx->headers = pdkim_prepend_stringlist(ctx->headers, ctx->cur_header->s);
+      ctx->cur_header->s[ctx->cur_header->ptr = 0] = '\0';
+      return PDKIM_ERR_EXCESS_SIGS;
+      }
+    }
+
+  /* all headers are stored for signature verification */
+  ctx->headers = pdkim_prepend_stringlist(ctx->headers, ctx->cur_header->s);
+  }
+
+BAIL:
+ctx->cur_header->s[ctx->cur_header->ptr = 0] = '\0';   /* leave buffer for reuse */
+return PDKIM_OK;
+}
+
+
+
+/* -------------------------------------------------------------------------- */
+#define HEADER_BUFFER_FRAG_SIZE 256
+
+DLLEXPORT int
+pdkim_feed(pdkim_ctx * ctx, uschar * 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;
+      pdkim_bodyline_complete(ctx);
+      }
+
+    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, 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 || ctx->cur_header->ptr < PDKIM_MAX_HEADER_LEN)
+      ctx->cur_header = string_catn(ctx->cur_header, CUS &data[p], 1);
+    }
+  }
+return PDKIM_OK;
+}
+
+
+
+/* Extend a growing header with a continuation-linebreak */
+static gstring *
+pdkim_hdr_cont(gstring * str, int * col)
+{
+*col = 1;
+return string_catn(str, 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
+ * 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 gstring *
+pdkim_headcat(int * col, gstring * str,
+  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, col);
+  str = string_catn(str, 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, 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, US" ", 1);
+    *col += 1;
+    pad = NULL; /* only want this once */
+    l--;
+    }
+
+  if (intro)
+    {
+    size_t sl = Ustrlen(intro);
+
+    str = string_catn(str, 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, payload, chomp);
+    *col += chomp;
+    payload += chomp;
+    l -= chomp-1;
+    }
+
+  /* the while precondition tells us it didn't fit. */
+  str = pdkim_hdr_cont(str, col);
+  }
+
+if (*col + l > 78)
+  {
+  str = pdkim_hdr_cont(str, col);
+  pad = NULL;
+  }
+
+if (pad)
+  {
+  str = string_catn(str, US" ", 1);
+  *col += 1;
+  pad = NULL;
+  }
+
+if (intro)
+  {
+  size_t sl = Ustrlen(intro);
+
+  str = string_catn(str, intro, sl);
+  *col += sl;
+  l -= sl;
+  intro = NULL;
+  }
+
+if (payload)
+  {
+  size_t sl = Ustrlen(payload);
+
+  str = string_catn(str, payload, sl);
+  *col += sl;
+  }
+
+return str;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+/* Signing: create signature header
+*/
+static uschar *
+pdkim_create_header(pdkim_signature * sig, BOOL final)
+{
+uschar * base64_bh;
+uschar * base64_b;
+int col = 0;
+gstring * hdr;
+gstring * canon_all;
+
+canon_all = string_cat (NULL, pdkim_canons[sig->canon_headers]);
+canon_all = string_catn(canon_all, US"/", 1);
+canon_all = string_cat (canon_all, pdkim_canons[sig->canon_body]);
+(void) string_from_gstring(canon_all);
+
+hdr = string_cat(NULL, US"DKIM-Signature: v="PDKIM_SIGNATURE_VERSION);
+col = hdr->ptr;
+
+/* Required and static bits */
+hdr = pdkim_headcat(&col, hdr, US";", US"a=", dkim_sig_to_a_tag(sig));
+hdr = pdkim_headcat(&col, hdr, US";", US"q=", pdkim_querymethods[sig->querymethod]);
+hdr = pdkim_headcat(&col, hdr, US";", US"c=", canon_all->s);
+hdr = pdkim_headcat(&col, hdr, US";", US"d=", sig->domain);
+hdr = pdkim_headcat(&col, hdr, 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, NULL, NULL, US":");
+
+    hdr = pdkim_headcat(&col, hdr, s, i, n);
+
+    if (!c)
+      break;
+
+    n = c+1;
+    s = NULL;
+    i = NULL;
+    }
+  }
+
+base64_bh = pdkim_encode_base64(&sig->calc_body_hash->bh);
+hdr = pdkim_headcat(&col, hdr, US";", US"bh=", base64_bh);
+
+/* Optional bits */
+if (sig->identity)
+  hdr = pdkim_headcat(&col, hdr, 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, US";", US"t=", minibuf);
+}
+
+if (sig->expires > 0)
+  {
+  uschar minibuf[20];
+
+  snprintf(CS minibuf, sizeof(minibuf), "%lu", sig->expires);
+  hdr = pdkim_headcat(&col, hdr, US";", US"x=", minibuf);
+  }
+
+if (sig->bodylength >= 0)
+  {
+  uschar minibuf[20];
+
+  snprintf(CS minibuf, sizeof(minibuf), "%lu", sig->bodylength);
+  hdr = pdkim_headcat(&col, hdr, US";", US"l=", minibuf);
+  }
+
+/* Preliminary or final version? */
+if (final)
+  {
+  base64_b = pdkim_encode_base64(&sig->sighash);
+  hdr = pdkim_headcat(&col, hdr, US";", US"b=", base64_b);
+
+  /* add trailing semicolon: I'm not sure if this is actually needed */
+  hdr = pdkim_headcat(&col, hdr, NULL, US";", US"");
+  }
+else
+  {
+  /* To satisfy the rule "all surrounding whitespace [...] deleted"
+  ( RFC 6376 section 3.7 ) we ensure there is no whitespace here.  Otherwise
+  the headcat routine could insert a linebreak which the relaxer would reduce
+  to a single space preceding the terminating semicolon, resulting in an
+  incorrect header-hash. */
+  hdr = pdkim_headcat(&col, hdr, US";", US"b=;", US"");
+  }
+
+return string_from_gstring(hdr);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+/* According to draft-ietf-dcrup-dkim-crypto-07 "keys are 256 bits" (referring
+to DNS, hence the pubkey).  Check for more than 32 bytes; if so assume the
+alternate possible representation (still) being discussed: a
+SubjectPublickeyInfo wrapped key - and drop all but the trailing 32-bytes (it
+should be a DER, with exactly 12 leading bytes - but we could accept a BER also,
+which could be any size).  We still rely on the crypto library for checking for
+undersize.
+
+When the RFC is published this should be re-addressed. */
+
+static void
+check_bare_ed25519_pubkey(pdkim_pubkey * p)
+{
+int excess = p->key.len - 32;
+if (excess > 0)
+  {
+  DEBUG(D_acl) debug_printf("PDKIM: unexpected pubkey len %lu\n", p->key.len);
+  p->key.data += excess; p->key.len = 32;
+  }
+}
+
+
+static pdkim_pubkey *
+pdkim_key_from_dns(pdkim_ctx * ctx, pdkim_signature * sig, ev_ctx * vctx,
+  const uschar ** errstr)
+{
+uschar * dns_txt_name, * dns_txt_reply;
+pdkim_pubkey * p;
+
+/* Fetch public key for signing domain, from DNS */
+
+dns_txt_name = string_sprintf("%s._domainkey.%s.", sig->selector, sig->domain);
+
+if (  !(dns_txt_reply = ctx->dns_txt_callback(dns_txt_name))
+   || 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"
+    " %s\n"
+    " Raw record: ",
+    dns_txt_name);
+  pdkim_quoteprint(CUS dns_txt_reply, Ustrlen(dns_txt_reply));
+  }
+
+if (  !(p = pdkim_parse_pubkey_record(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 */
+
+/* Normally we use the signature a= tag to tell us the pubkey format.
+When signing under debug we do a test-import of the pubkey, and at that
+time we do not have a signature so we must interpret the pubkey k= tag
+instead.  Assume writing on the sig is ok in that case. */
+
+if (sig->keytype < 0)
+  {
+  int i;
+  for(i = 0; i < nelem(pdkim_keytypes); i++)
+    if (Ustrcmp(p->keytype, pdkim_keytypes[i]) == 0)
+      { sig->keytype = i; goto k_ok; }
+  DEBUG(D_acl) debug_printf("verify_init: unhandled keytype %s\n", p->keytype);
+  sig->verify_status =      PDKIM_VERIFY_INVALID;
+  sig->verify_ext_status =  PDKIM_VERIFY_INVALID_PUBKEY_IMPORT;
+  return NULL;
+  }
+k_ok:
+
+if (sig->keytype == KEYTYPE_ED25519)
+  check_bare_ed25519_pubkey(p);
+
+if ((*errstr = exim_dkim_verify_init(&p->key,
+           sig->keytype == KEYTYPE_ED25519 ? KEYFMT_ED25519_BARE : KEYFMT_DER,
+           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;
+  }
+
+vctx->keytype = sig->keytype;
+return p;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+DLLEXPORT int
+pdkim_feed_finish(pdkim_ctx * ctx, pdkim_signature ** return_signatures,
+  const uschar ** err)
+{
+pdkim_bodyhash * b;
+pdkim_signature * sig;
+BOOL verify_pass = FALSE;
+
+/* 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->ptr > 0)
+  {
+  blob * rnl = NULL;
+  int rc;
+
+  if ((rc = pdkim_header_complete(ctx)) != PDKIM_OK)
+    return rc;
+
+  for (b = ctx->bodyhash; b; b = b->next)
+    rnl = pdkim_update_ctx_bodyhash(b, &lineending, rnl);
+  if (rnl) store_free(rnl);
+  }
+else
+  DEBUG(D_acl) debug_printf(
+      "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+
+/* Build (and/or evaluate) body hash.  Do this even if no DKIM sigs, in case we
+have a hash to do for ARC. */
+
+pdkim_finish_bodyhash(ctx);
+
+if (!ctx->sig)
+  {
+  DEBUG(D_acl) debug_printf("PDKIM: no signatures\n");
+  *return_signatures = NULL;
+  return PDKIM_OK;
+  }
+
+for (sig = ctx->sig; sig; sig = sig->next)
+  {
+  hctx hhash_ctx;
+  uschar * sig_hdr = US"";
+  blob hhash;
+  gstring * hdata = NULL;
+  es_ctx sctx;
+
+  if (  !(ctx->flags & PDKIM_MODE_SIGN)
+     && sig->verify_status == PDKIM_VERIFY_FAIL)
+    {
+    DEBUG(D_acl)
+       debug_printf("PDKIM: [%s] abandoning this signature\n", sig->domain);
+    continue;
+    }
+
+  /*XXX The hash of the headers is needed for GCrypt (for which we can do RSA
+  suging only, as it happens) and for either GnuTLS and OpenSSL when we are
+  signing with EC (specifically, Ed25519).  The former is because the GCrypt
+  signing operation is pure (does not do its own hash) so we must hash.  The
+  latter is because we (stupidly, but this is what the IETF draft is saying)
+  must hash with the declared hash method, then pass the result to the library
+  hash-and-sign routine (because that's all the libraries are providing.  And
+  we're stuck with whatever that hidden hash method is, too).  We may as well
+  do this hash incrementally.
+  We don't need the hash we're calculating here for the GnuTLS and OpenSSL
+  cases of RSA signing, since those library routines can do hash-and-sign.
+  Some time in the future we could easily avoid doing the hash here for those
+  cases (which will be common for a long while.  We could also change from
+  the current copy-all-the-headers-into-one-block, then call the hash-and-sign
+  implementation  - to a proper incremental one.  Unfortunately, GnuTLS just
+  cannot do incremental - either signing or verification.  Unsure about GCrypt.
+  */
+
+  /*XXX The header hash is also used (so far) by the verify operation */
+
+  if (!exim_sha_init(&hhash_ctx, pdkim_hashes[sig->hashtype].exim_hashmethod))
+    {
+    log_write(0, LOG_MAIN|LOG_PANIC,
+      "PDKIM: hash setup error, possibly nonhandled hashtype");
+    break;
+    }
+
+  if (ctx->flags & PDKIM_MODE_SIGN)
+    DEBUG(D_acl) debug_printf(
+       "PDKIM >> Headers to be signed:                            >>>>>>>>>>>>\n"
+       " %s\n",
+       sig->sign_headers);
+
+  DEBUG(D_acl) debug_printf(
+      "PDKIM >> Header data for hash, canonicalized (%-7s), in sequence >>\n",
+       pdkim_canons[sig->canon_headers]);
+
+
+  /* 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)
+    {
+    gstring * g = NULL;
+    pdkim_stringlist *p;
+    const uschar * l;
+    uschar * s;
+    int sep = 0;
+
+    /* Import private key, including the keytype which we need for building
+    the signature header  */
+
+/*XXX extend for non-RSA algos */
+    if ((*err = exim_dkim_signing_init(CUS sig->privkey, &sctx)))
+      {
+      log_write(0, LOG_MAIN|LOG_PANIC, "signing_init: %s", *err);
+      return PDKIM_ERR_RSA_PRIVKEY;
+      }
+    sig->keytype = sctx.keytype;
+
+    for (sig->headernames = NULL,              /* Collected signed header names */
+         p = sig->headers; p; p = p->next)
+      {
+      uschar * rh = p->value;
+
+      if (header_name_match(rh, sig->sign_headers) == PDKIM_OK)
+       {
+       /* Collect header names (Note: colon presence is guaranteed here) */
+       g = string_append_listele_n(g, ':', rh, Ustrchr(rh, ':') - rh);
+
+       if (sig->canon_headers == PDKIM_CANON_RELAXED)
+         rh = pdkim_relax_header(rh, TRUE);    /* cook header for relaxed 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)  */
+       /*XXX we could avoid doing this for all but the GnuTLS/RSA case */
+       hdata = exim_dkim_data_append(hdata, rh);
+
+       DEBUG(D_acl) pdkim_quoteprint(rh, Ustrlen(rh));
+       }
+      }
+
+    /* Any headers we wanted to sign but were not present must also be listed.
+    Ignore elements that have been ticked-off or are marked as never-oversign. */
+
+    l = sig->sign_headers;
+    while((s = string_nextinlist(&l, &sep, NULL, 0)))
+      {
+      if (*s == '+')                   /* skip oversigning marker */
+        s++;
+      if (*s != '_' && *s != '=')
+       g = string_append_listele(g, ':', s);
+      }
+    sig->headernames = string_from_gstring(g);
+
+    /* 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, TRUE)
+             : 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");
+
+  DEBUG(D_acl)
+    {
+    debug_printf(
+           "PDKIM >> Signed DKIM-Signature header, pre-canonicalized >>>>>>>>>>>>>\n");
+    pdkim_quoteprint(CUS sig_hdr, Ustrlen(sig_hdr));
+    debug_printf(
+           "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+    }
+
+  /* Relax header if necessary */
+  if (sig->canon_headers == PDKIM_CANON_RELAXED)
+    sig_hdr = pdkim_relax_header(sig_hdr, FALSE);
+
+  DEBUG(D_acl)
+    {
+    debug_printf("PDKIM >> Signed DKIM-Signature header, canonicalized (%-7s) >>>>>>>\n",
+           pdkim_canons[sig->canon_headers]);
+    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 %s computed: ",
+      sig->domain, pdkim_hashes[sig->hashtype].dkim_hashname);
+    pdkim_hexprint(hhash.data, hhash.len);
+    }
+
+  /* Remember headers block for signing (when the signing library cannot do
+  incremental)  */
+  if (ctx->flags & PDKIM_MODE_SIGN)
+    hdata = exim_dkim_data_append(hdata, US sig_hdr);
+
+  /* SIGNING ---------------------------------------------------------------- */
+  if (ctx->flags & PDKIM_MODE_SIGN)
+    {
+    hashmethod hm = sig->keytype == KEYTYPE_ED25519
+#if defined(SIGN_OPENSSL)
+      ? HASH_NULL
+#else
+      ? HASH_SHA2_512
+#endif
+      : pdkim_hashes[sig->hashtype].exim_hashmethod;
+
+#ifdef SIGN_HAVE_ED25519
+    /* For GCrypt, and for EC, we pass the hash-of-headers to the signing
+    routine.  For anything else we just pass the headers. */
+
+    if (sig->keytype != KEYTYPE_ED25519)
+#endif
+      {
+      hhash.data = hdata->s;
+      hhash.len = hdata->ptr;
+      }
+
+    if ((*err = exim_dkim_sign(&sctx, hm, &hhash, &sig->sighash)))
+      {
+      log_write(0, LOG_MAIN|LOG_PANIC, "signing: %s", *err);
+      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;
+    hashmethod hm;
+
+    /* 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->keytype >= 0
+        && sig->hashtype >= 0
+        && 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 (%s)\n"
+         "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n",
+         !(sig->domain && *sig->domain) ? "d="
+         : !(sig->selector && *sig->selector) ? "s="
+         : !(sig->headernames && *sig->headernames) ? "h="
+         : !sig->bodyhash.data ? "bh="
+         : !sig->sighash.data ? "b="
+         : sig->keytype < 0 || sig->hashtype < 0 ? "a="
+         : "v="
+         );
+      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;
+      }
+
+    DEBUG(D_acl)
+      {
+      debug_printf( "PDKIM [%s] b from mail: ", sig->domain);
+      pdkim_hexprint(sig->sighash.data, sig->sighash.len);
+      }
+
+    if (!(sig->pubkey = pdkim_key_from_dns(ctx, sig, &vctx, err)))
+      {
+      log_write(0, LOG_MAIN, "PDKIM: %s%s %s%s [failed key import]",
+       sig->domain   ? "d=" : "", sig->domain   ? sig->domain   : US"",
+       sig->selector ? "s=" : "", sig->selector ? sig->selector : US"");
+      goto NEXT_VERIFY;
+      }
+
+    /* If the pubkey limits to a list of specific hashes, ignore sigs that
+    do not have the hash part of the sig algorithm matching */
+
+    if (sig->pubkey->hashes)
+      {
+      const uschar * list = sig->pubkey->hashes, * ele;
+      int sep = ':';
+      while ((ele = string_nextinlist(&list, &sep, NULL, 0)))
+       if (Ustrcmp(ele, pdkim_hashes[sig->hashtype].dkim_hashname) == 0) break;
+      if (!ele)
+       {
+       DEBUG(D_acl) debug_printf("pubkey h=%s vs. sig a=%s_%s\n",
+         sig->pubkey->hashes,
+         pdkim_keytypes[sig->keytype],
+         pdkim_hashes[sig->hashtype].dkim_hashname);
+       sig->verify_status =      PDKIM_VERIFY_FAIL;
+       sig->verify_ext_status =  PDKIM_VERIFY_FAIL_SIG_ALGO_MISMATCH;
+       goto NEXT_VERIFY;
+       }
+      }
+
+    hm = sig->keytype == KEYTYPE_ED25519
+#if defined(SIGN_OPENSSL)
+      ? HASH_NULL
+#else
+      ? HASH_SHA2_512
+#endif
+      : pdkim_hashes[sig->hashtype].exim_hashmethod;
+
+    /* Check the signature */
+
+    if ((*err = exim_dkim_verify(&vctx, hm, &hhash, &sig->sighash)))
+      {
+      DEBUG(D_acl) debug_printf("headers verify: %s\n", *err);
+      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;
+      verify_pass = TRUE;
+      }
+
+NEXT_VERIFY:
+
+    DEBUG(D_acl)
+      {
+      debug_printf("PDKIM [%s] %s signature status: %s",
+             sig->domain, dkim_sig_to_a_tag(sig),
+             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");
+      }
+    }
+  }
+
+/* If requested, set return pointer to signature(s) */
+if (return_signatures)
+  *return_signatures = ctx->sig;
+
+return ctx->flags & PDKIM_MODE_SIGN  ||  verify_pass
+  ? PDKIM_OK : PDKIM_FAIL;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+DLLEXPORT pdkim_ctx *
+pdkim_init_verify(uschar * (*dns_txt_callback)(uschar *), 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_signature *
+pdkim_init_sign(pdkim_ctx * ctx,
+  uschar * domain, uschar * selector, uschar * privkey,
+  uschar * hashname, const uschar ** errstr)
+{
+int hashtype;
+pdkim_signature * sig;
+
+if (!domain || !selector || !privkey)
+  return NULL;
+
+/* Allocate & init one signature struct */
+
+sig = store_get(sizeof(pdkim_signature));
+memset(sig, 0, sizeof(pdkim_signature));
+
+sig->bodylength = -1;
+
+sig->domain = string_copy(US domain);
+sig->selector = string_copy(US selector);
+sig->privkey = string_copy(US privkey);
+sig->keytype = -1;
+
+for (hashtype = 0; hashtype < nelem(pdkim_hashes); hashtype++)
+  if (Ustrcmp(hashname, pdkim_hashes[hashtype].dkim_hashname) == 0)
+  { sig->hashtype = hashtype; break; }
+if (hashtype >= nelem(pdkim_hashes))
+  {
+  log_write(0, LOG_MAIN|LOG_PANIC,
+    "PDKIM: unrecognised hashname '%s'", hashname);
+  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, errstr))
+    debug_printf("WARNING: bad dkim key in dns\n");
+  debug_printf("PDKIM (finished checking verify key)<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+  }
+return sig;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+DLLEXPORT void
+pdkim_set_optional(pdkim_signature * sig,
+                       char * sign_headers,
+                       char * identity,
+                       int canon_headers,
+                       int canon_body,
+                       long bodylength,
+                       unsigned long created,
+                       unsigned long expires)
+{
+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;
+}
+
+
+
+/* Set up a blob for calculating the bodyhash according to the
+given needs.  Use an existing one if possible, or create a new one.
+
+Return: hashblob pointer, or NULL on error
+*/
+pdkim_bodyhash *
+pdkim_set_bodyhash(pdkim_ctx * ctx, int hashtype, int canon_method,
+       long bodylength)
+{
+pdkim_bodyhash * b;
+
+for (b = ctx->bodyhash; b; b = b->next)
+  if (  hashtype == b->hashtype
+     && canon_method == b->canon_method
+     && bodylength == b->bodylength)
+    {
+    DEBUG(D_receive) debug_printf("PDKIM: using existing bodyhash %d/%d/%ld\n",
+                                 hashtype, canon_method, bodylength);
+    return b;
+    }
+
+DEBUG(D_receive) debug_printf("PDKIM: new bodyhash %d/%d/%ld\n",
+                             hashtype, canon_method, bodylength);
+b = store_get(sizeof(pdkim_bodyhash));
+b->next = ctx->bodyhash;
+b->hashtype = hashtype;
+b->canon_method = canon_method;
+b->bodylength = bodylength;
+if (!exim_sha_init(&b->body_hash_ctx,          /*XXX hash method: extend for sha512 */
+                 pdkim_hashes[hashtype].exim_hashmethod))
+  {
+  DEBUG(D_acl)
+    debug_printf("PDKIM: hash init error, possibly nonhandled hashtype\n");
+  return NULL;
+  }
+b->signed_body_bytes = 0;
+b->num_buffered_blanklines = 0;
+ctx->bodyhash = b;
+return b;
+}
+
+
+/* Set up a blob for calculating the bodyhash according to the
+needs of this signature.  Use an existing one if possible, or
+create a new one.
+
+Return: hashblob pointer, or NULL on error (only used as a boolean).
+*/
+pdkim_bodyhash *
+pdkim_set_sig_bodyhash(pdkim_ctx * ctx, pdkim_signature * sig)
+{
+pdkim_bodyhash * b = pdkim_set_bodyhash(ctx,
+                       sig->hashtype, sig->canon_body, sig->bodylength);
+sig->calc_body_hash = b;
+return b;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void
+pdkim_init_context(pdkim_ctx * ctx, BOOL dot_stuffed,
+  uschar * (*dns_txt_callback)(uschar *))
+{
+memset(ctx, 0, sizeof(pdkim_ctx));
+ctx->flags = dot_stuffed ? PDKIM_MODE_SIGN | PDKIM_DOT_TERM : PDKIM_MODE_SIGN;
+ctx->linebuf = store_get(PDKIM_MAX_BODY_LINE_LEN);
+DEBUG(D_acl) ctx->dns_txt_callback = dns_txt_callback;
+}
+
+
+void
+pdkim_init(void)
+{
+exim_dkim_init();
+}
+
+
+
+#endif /*DISABLE_DKIM*/
index 1d364a3..0293875 100644 (file)
@@ -2,6 +2,7 @@
  *  PDKIM - a RFC4871 (DKIM) implementation
  *
  *  Copyright (C) 2009 - 2012  Tom Kistner <tom@duncanthrax.net>
  *  PDKIM - a RFC4871 (DKIM) implementation
  *
  *  Copyright (C) 2009 - 2012  Tom Kistner <tom@duncanthrax.net>
+ *  Copyright (c) 2016 - 2018  Jeremy Harris
  *
  *  http://duncanthrax.net/pdkim/
  *
  *
  *  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.
  */
  *  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"
+
+#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"
 
 /* -------------------------------------------------------------------------- */
 /* Length of the preallocated buffer for the "answer" from the dns/txt
 
 /* -------------------------------------------------------------------------- */
 /* Length of the preallocated buffer for the "answer" from the dns/txt
 /* Function success / error codes */
 #define PDKIM_OK                      0
 #define PDKIM_FAIL                   -1
 /* 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_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_ERR_EXCESS_SIGS     -105
+#define PDKIM_SIGN_PRIVKEY_WRAP    -106
+#define PDKIM_SIGN_PRIVKEY_B64D    -107
 
 /* -------------------------------------------------------------------------- */
 /* Main/Extended verification status */
 
 /* -------------------------------------------------------------------------- */
 /* Main/Extended verification status */
 #define PDKIM_VERIFY_INVALID   1
 #define PDKIM_VERIFY_FAIL      2
 #define PDKIM_VERIFY_PASS      3
 #define PDKIM_VERIFY_INVALID   1
 #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_POLICY    BIT(31)
+
+#define PDKIM_VERIFY_FAIL_BODY                    1
+#define PDKIM_VERIFY_FAIL_MESSAGE                 2
+#define PDKIM_VERIFY_FAIL_SIG_ALGO_MISMATCH      3
+#define PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE   4
+#define PDKIM_VERIFY_INVALID_BUFFER_SIZE          5
+#define PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD     6
+#define PDKIM_VERIFY_INVALID_PUBKEY_IMPORT        7
+#define PDKIM_VERIFY_INVALID_SIGNATURE_ERROR      8
+#define PDKIM_VERIFY_INVALID_DKIM_VERSION         9
 
 /* -------------------------------------------------------------------------- */
 /* Some parameter values */
 #define PDKIM_QUERYMETHOD_DNS_TXT 0
 
 
 /* -------------------------------------------------------------------------- */
 /* Some parameter values */
 #define PDKIM_QUERYMETHOD_DNS_TXT 0
 
-#define PDKIM_ALGO_RSA_SHA256     0
-#define PDKIM_ALGO_RSA_SHA1       1
+/*#define PDKIM_ALGO_RSA_SHA256     0 */
+/*#define PDKIM_ALGO_RSA_SHA1       1 */
 
 #define PDKIM_CANON_SIMPLE        0
 #define PDKIM_CANON_RELAXED       1
 
 
 #define PDKIM_CANON_SIMPLE        0
 #define PDKIM_CANON_RELAXED       1
 
-#define PDKIM_HASH_SHA256         0
-#define PDKIM_HASH_SHA1           1
-
-#define PDKIM_KEYTYPE_RSA         0
-
 /* -------------------------------------------------------------------------- */
 /* Some required forward declarations, please ignore */
 typedef struct pdkim_stringlist pdkim_stringlist;
 /* -------------------------------------------------------------------------- */
 /* Some required forward declarations, please ignore */
 typedef struct pdkim_stringlist pdkim_stringlist;
@@ -92,33 +104,47 @@ typedef struct sha2_context sha2_context;
 /* -------------------------------------------------------------------------- */
 /* Public key as (usually) fetched from DNS */
 typedef struct pdkim_pubkey {
 /* -------------------------------------------------------------------------- */
 /* 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=  */
+  const uschar * version;         /* v=  */
+  const uschar *granularity;      /* g=  */
 
 
-  char *key;                      /* p=  */
-  int   key_len;
+  const uschar * hashes;          /* h=  */
+  const uschar * keytype;         /* k=  */
+  const uschar * srvtype;         /* s=  */
+  uschar *notes;                  /* n=  */
 
 
+  blob  key;                      /* p=  */
   int   testing;                  /* t=y */
   int   no_subdomaining;          /* t=s */
 } pdkim_pubkey;
 
   int   testing;                  /* t=y */
   int   no_subdomaining;          /* t=s */
 } pdkim_pubkey;
 
+/* -------------------------------------------------------------------------- */
+/* Body-hash to be calculated */
+typedef struct pdkim_bodyhash {
+  struct pdkim_bodyhash *      next;
+  int                          hashtype;
+  int                          canon_method;
+  long                         bodylength;
+
+  hctx                                 body_hash_ctx;
+  unsigned long                        signed_body_bytes;      /* done so far */
+  int                          num_buffered_blanklines;
+
+  blob                         bh;                     /* completed hash */
+} pdkim_bodyhash;
+
 /* -------------------------------------------------------------------------- */
 /* Signature as it appears in a DKIM-Signature header */
 typedef struct pdkim_signature {
 /* -------------------------------------------------------------------------- */
 /* Signature as it appears in a DKIM-Signature header */
 typedef struct pdkim_signature {
+  struct pdkim_signature * next;
 
   /* Bits stored in a DKIM signature header --------------------------- */
 
   /* (v=) The version, as an integer. Currently, always "1" */
   int version;
 
 
   /* Bits stored in a DKIM signature header --------------------------- */
 
   /* (v=) The version, as an integer. Currently, always "1" */
   int version;
 
-  /* (a=) The signature algorithm. Either PDKIM_ALGO_RSA_SHA256
-     or PDKIM_ALGO_RSA_SHA1 */
-  int algo;
+  /* (a=) The signature algorithm. Either PDKIM_ALGO_RSA_SHA256 */
+  int keytype; /* pdkim_keytypes index */
+  int hashtype;        /* pdkim_hashes index */
 
   /* (c=x/) Header canonicalization method. Either PDKIM_CANON_SIMPLE
      or PDKIM_CANON_RELAXED */
 
   /* (c=x/) Header canonicalization method. Either PDKIM_CANON_SIMPLE
      or PDKIM_CANON_RELAXED */
@@ -133,13 +159,13 @@ typedef struct pdkim_signature {
   int querymethod;
 
   /* (s=) The selector string as given in the signature */
   int querymethod;
 
   /* (s=) The selector string as given in the signature */
-  char *selector;
+  uschar *selector;
 
   /* (d=) The domain as given in the signature */
 
   /* (d=) The domain as given in the signature */
-  char *domain;
+  uschar *domain;
 
   /* (i=) The identity as given in the signature */
 
   /* (i=) The identity as given in the signature */
-  char *identity;
+  uschar *identity;
 
   /* (t=) Timestamp of signature creation */
   unsigned long created;
 
   /* (t=) Timestamp of signature creation */
   unsigned long created;
@@ -153,24 +179,22 @@ typedef struct pdkim_signature {
 
   /* (h=) Colon-separated list of header names that are included in the
      signature */
 
   /* (h=) Colon-separated list of header names that are included in the
      signature */
-  char *headernames;
+  uschar *headernames;
 
   /* (z=) */
 
   /* (z=) */
-  char *copiedheaders;
+  uschar *copiedheaders;
 
   /* (b=) Raw signature data, along with its length in bytes */
 
   /* (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 */
 
   /* (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.
+  /* Folded DKIM-Signature: header. Signing 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(). */
      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:
 
 
   /* The main verification status. Verification only. One of:
 
@@ -223,62 +247,59 @@ typedef struct pdkim_signature {
      Caution: is NULL if signing or if no record was retrieved. */
   pdkim_pubkey *pubkey;
 
      Caution: is NULL if signing or if no record was retrieved. */
   pdkim_pubkey *pubkey;
 
-  /* Pointer to the next pdkim_signature signature. NULL if signing or if
-     this is the last signature. */
-  void *next;
-
   /* Properties below this point are used internally only ------------- */
 
   /* Per-signature helper variables ----------------------------------- */
   /* Properties below this point are used internally only ------------- */
 
   /* Per-signature helper variables ----------------------------------- */
-  sha1_context *sha1_body; /* SHA1 block                                */
-  sha2_context *sha2_body; /* SHA256 block                              */
-  unsigned long signed_body_bytes; /* How many body bytes we hashed     */
-  pdkim_stringlist *headers; /* Raw headers included in the sig         */
+  pdkim_bodyhash *calc_body_hash;      /* hash to be / being calculated */
+
+  pdkim_stringlist *headers;           /* Raw headers included in the sig */
+
   /* Signing specific ------------------------------------------------- */
   /* 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 * privkey;         /* Private 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. */
 } 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 {
 
 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;
 
 
   /* One (signing) or several chained (verification) signatures */
   pdkim_signature *sig;
 
+  /* One (signing) or several chained (verification) bodyhashes */
+  pdkim_bodyhash *bodyhash;
+
   /* Callback for dns/txt query method (verification only) */
   /* Callback for dns/txt query method (verification only) */
-  int(*dns_txt_callback)(char *, char *);
+  uschar * (*dns_txt_callback)(uschar *);
 
   /* Coder's little helpers */
 
   /* Coder's little helpers */
-  pdkim_str *cur_header;
-  char      *linebuf;
+  gstring   *cur_header;
+  uschar    *linebuf;
   int        linebuf_offset;
   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         */
   int        num_headers;
   pdkim_stringlist *headers; /* Raw headers for verification         */
+} pdkim_ctx;
 
 
-#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;
+/******************************************************************************/
+
+typedef struct {
+  const uschar * dkim_hashname;
+  hashmethod    exim_hashmethod;
+} pdkim_hashtype;
+extern const pdkim_hashtype pdkim_hashes[];
+
+/******************************************************************************/
 
 
 /* -------------------------------------------------------------------------- */
 
 
 /* -------------------------------------------------------------------------- */
@@ -290,31 +311,51 @@ typedef struct pdkim_ctx {
 extern "C" {
 #endif
 
 extern "C" {
 #endif
 
+void      pdkim_init         (void);
+
+void      pdkim_init_context (pdkim_ctx *, BOOL, uschar * (*)(uschar *));
+
 DLLEXPORT
 DLLEXPORT
-pdkim_ctx *pdkim_init_sign    (int, char *, char *, char *);
+pdkim_signature *pdkim_init_sign    (pdkim_ctx *,
+                              uschar *, uschar *, uschar *, uschar *,
+                              const uschar **);
 
 DLLEXPORT
 
 DLLEXPORT
-pdkim_ctx *pdkim_init_verify  (int, int(*)(char *, char *));
+pdkim_ctx *pdkim_init_verify  (uschar * (*)(uschar *), BOOL);
 
 DLLEXPORT
 
 DLLEXPORT
-int        pdkim_set_optional (pdkim_ctx *, char *, char *,int, int,
-                               long, int,
+void       pdkim_set_optional (pdkim_signature *, char *, char *,int, int,
+                               long,
                                unsigned long,
                                unsigned long);
 
                                unsigned long,
                                unsigned long);
 
+int            pdkim_hashname_to_hashtype(const uschar *, unsigned);
+void           pdkim_cstring_to_canons(const uschar *, unsigned, int *, int *);
+pdkim_bodyhash *pdkim_set_bodyhash(pdkim_ctx *, int, int, long);
+pdkim_bodyhash *pdkim_set_sig_bodyhash(pdkim_ctx *, pdkim_signature *);
+
 DLLEXPORT
 DLLEXPORT
-int        pdkim_feed         (pdkim_ctx *, char *, int);
+int        pdkim_feed         (pdkim_ctx *, uschar *, int);
 DLLEXPORT
 DLLEXPORT
-int        pdkim_feed_finish  (pdkim_ctx *, pdkim_signature **);
+int        pdkim_feed_finish  (pdkim_ctx *, pdkim_signature **, const uschar **);
 
 DLLEXPORT
 void       pdkim_free_ctx     (pdkim_ctx *);
 
 
 DLLEXPORT
 void       pdkim_free_ctx     (pdkim_ctx *);
 
-#ifdef PDKIM_DEBUG
-DLLEXPORT
-void       pdkim_set_debug_stream(pdkim_ctx *, FILE *);
-#endif
+
+const uschar * pdkim_errstr(int);
+
+extern uschar *                pdkim_encode_base64(blob *);
+extern void            pdkim_decode_base64(const uschar *, blob *);
+extern void            pdkim_hexprint(const uschar *, int);
+extern void            pdkim_quoteprint(const uschar *, int);
+extern pdkim_pubkey *  pdkim_parse_pubkey_record(const uschar *);
+extern uschar *                pdkim_relax_header_n(const uschar *, int, BOOL);
+extern uschar *                pdkim_relax_header(const uschar *, BOOL);
+extern uschar *                dkim_sig_to_a_tag(const pdkim_signature *);
 
 #ifdef __cplusplus
 }
 #endif
 
 #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..6299ae2
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ *  PDKIM - a RFC4871 (DKIM) implementation
+ *
+ *  Copyright (C) 1995 - 2018  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 SIGN_OPENSSL
+# include <openssl/rsa.h>
+# include <openssl/ssl.h>
+# include <openssl/err.h>
+#elif defined(SIGN_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 */
diff --git a/src/pdkim/rsa.c b/src/pdkim/rsa.c
deleted file mode 100644 (file)
index 68b201d..0000000
+++ /dev/null
@@ -1,957 +0,0 @@
-/*
- *  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 );
-}
diff --git a/src/pdkim/rsa.h b/src/pdkim/rsa.h
deleted file mode 100644 (file)
index af6823b..0000000
+++ /dev/null
@@ -1,419 +0,0 @@
-/**
- * \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 */
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 */
diff --git a/src/pdkim/signing.c b/src/pdkim/signing.c
new file mode 100644 (file)
index 0000000..a47f824
--- /dev/null
@@ -0,0 +1,891 @@
+/*
+ *  PDKIM - a RFC4871 (DKIM) implementation
+ *
+ *  Copyright (C) 1995 - 2018  Exim maintainers
+ *
+ *  signing/verification interface
+ */
+
+#include "../exim.h"
+#include "crypt_ver.h"
+#include "signing.h"
+
+
+#ifdef MACRO_PREDEF
+# include "../macro_predef.h"
+
+void
+features_crypto(void)
+{
+# ifdef SIGN_HAVE_ED25519
+  builtin_macro_create(US"_CRYPTO_SIGN_ED25519");
+# endif
+# ifdef EXIM_HAVE_SHA3
+  builtin_macro_create(US"_CRYPTO_HASH_SHA3");
+# endif
+}
+#else
+
+#ifndef DISABLE_DKIM   /* rest of file */
+
+#ifndef SUPPORT_TLS
+# error Need SUPPORT_TLS for DKIM
+#endif
+
+
+/******************************************************************************/
+#ifdef SIGN_GNUTLS
+# define EXIM_GNUTLS_LIBRARY_LOG_LEVEL 3
+
+
+/* Logging function which can be registered with
+ *   gnutls_global_set_log_function()
+ *   gnutls_global_set_log_level() 0..9
+ */
+#if EXIM_GNUTLS_LIBRARY_LOG_LEVEL >= 0
+static void
+exim_gnutls_logger_cb(int level, const char *message)
+{
+size_t len = strlen(message);
+if (len < 1)
+  {
+  DEBUG(D_tls) debug_printf("GnuTLS<%d> empty debug message\n", level);
+  return;
+  }
+DEBUG(D_tls) debug_printf("GnuTLS<%d>: %s%s", level, message,
+    message[len-1] == '\n' ? "" : "\n");
+}
+#endif
+
+
+
+void
+exim_dkim_init(void)
+{
+#if EXIM_GNUTLS_LIBRARY_LOG_LEVEL >= 0
+DEBUG(D_tls)
+  {
+  gnutls_global_set_log_function(exim_gnutls_logger_cb);
+  /* arbitrarily chosen level; bump upto 9 for more */
+  gnutls_global_set_log_level(EXIM_GNUTLS_LIBRARY_LOG_LEVEL);
+  }
+#endif
+}
+
+
+/* accumulate data (gnutls-only).  String to be appended must be nul-terminated. */
+gstring *
+exim_dkim_data_append(gstring * g, uschar * s)
+{
+return string_cat(g, s);
+}
+
+
+
+/* import private key from PEM string in memory.
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_dkim_signing_init(const uschar * privkey_pem, es_ctx * sign_ctx)
+{
+gnutls_datum_t k = { .data = (void *)privkey_pem, .size = Ustrlen(privkey_pem) };
+gnutls_x509_privkey_t x509_key;
+const uschar * where;
+int rc;
+
+if (  (where = US"internal init", rc = gnutls_x509_privkey_init(&x509_key))
+   || (rc = gnutls_privkey_init(&sign_ctx->key))
+   || (where = US"privkey PEM-block import",
+       rc = gnutls_x509_privkey_import(x509_key, &k, GNUTLS_X509_FMT_PEM))
+   || (where = US"internal privkey transfer",
+       rc = gnutls_privkey_import_x509(sign_ctx->key, x509_key, 0))
+   )
+  return string_sprintf("%s: %s", where, gnutls_strerror(rc));
+
+switch (rc = gnutls_privkey_get_pk_algorithm(sign_ctx->key, NULL))
+  {
+  case GNUTLS_PK_RSA:          sign_ctx->keytype = KEYTYPE_RSA;     break;
+#ifdef SIGN_HAVE_ED25519
+  case GNUTLS_PK_EDDSA_ED25519:        sign_ctx->keytype = KEYTYPE_ED25519; break;
+#endif
+  default: return rc < 0
+    ? CUS gnutls_strerror(rc)
+    : string_sprintf("Unhandled key type: %d '%s'", rc, gnutls_pk_get_name(rc));
+  }
+
+return NULL;
+}
+
+
+
+/* allocate mem for signature (when signing) */
+/* hash & sign data.   No way to do incremental.
+
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_dkim_sign(es_ctx * sign_ctx, hashmethod hash, blob * data, blob * sig)
+{
+gnutls_datum_t k_data = { .data = data->data, .size = data->len };
+gnutls_digest_algorithm_t dig;
+gnutls_datum_t k_sig;
+int rc;
+
+switch (hash)
+  {
+  case HASH_SHA1:      dig = GNUTLS_DIG_SHA1; break;
+  case HASH_SHA2_256:  dig = GNUTLS_DIG_SHA256; break;
+  case HASH_SHA2_512:  dig = GNUTLS_DIG_SHA512; break;
+  default:             return US"nonhandled hash type";
+  }
+
+if ((rc = gnutls_privkey_sign_data(sign_ctx->key, dig, 0, &k_data, &k_sig)))
+  return CUS gnutls_strerror(rc);
+
+/* Don't care about deinit for the key; shortlived process */
+
+sig->data = k_sig.data;
+sig->len = k_sig.size;
+return NULL;
+}
+
+
+
+/* import public key (from blob in memory)
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_dkim_verify_init(blob * pubkey, keyformat fmt, ev_ctx * verify_ctx)
+{
+gnutls_datum_t k;
+int rc;
+const uschar * ret = NULL;
+
+gnutls_pubkey_init(&verify_ctx->key);
+k.data = pubkey->data;
+k.size = pubkey->len;
+
+switch(fmt)
+  {
+  case KEYFMT_DER:
+    if ((rc = gnutls_pubkey_import(verify_ctx->key, &k, GNUTLS_X509_FMT_DER)))
+      ret = US gnutls_strerror(rc);
+    break;
+#ifdef SIGN_HAVE_ED25519
+  case KEYFMT_ED25519_BARE:
+    if ((rc = gnutls_pubkey_import_ecc_raw(verify_ctx->key,
+                                         GNUTLS_ECC_CURVE_ED25519, &k, NULL)))
+      ret = US gnutls_strerror(rc);
+    break;
+#endif
+  default:
+    ret = US"pubkey format not handled";
+    break;
+  }
+return ret;
+}
+
+
+/* verify signature (of hash if RSA sig, of data if EC sig.  No way to do incremental)
+(given pubkey & alleged sig)
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_dkim_verify(ev_ctx * verify_ctx, hashmethod hash, blob * data_hash, blob * sig)
+{
+gnutls_datum_t k = { .data = data_hash->data, .size = data_hash->len };
+gnutls_datum_t s = { .data = sig->data,       .size = sig->len };
+int rc;
+const uschar * ret = NULL;
+
+#ifdef SIGN_HAVE_ED25519
+if (verify_ctx->keytype == KEYTYPE_ED25519)
+  {
+  if ((rc = gnutls_pubkey_verify_data2(verify_ctx->key,
+                                     GNUTLS_SIGN_EDDSA_ED25519, 0, &k, &s)) < 0)
+    ret = US gnutls_strerror(rc);
+  }
+else
+#endif
+  {
+  gnutls_sign_algorithm_t algo;
+  switch (hash)
+    {
+    case HASH_SHA1:    algo = GNUTLS_SIGN_RSA_SHA1;   break;
+    case HASH_SHA2_256:        algo = GNUTLS_SIGN_RSA_SHA256; break;
+    case HASH_SHA2_512:        algo = GNUTLS_SIGN_RSA_SHA512; break;
+    default:           return US"nonhandled hash type";
+    }
+
+  if ((rc = gnutls_pubkey_verify_hash2(verify_ctx->key, algo, 0, &k, &s)) < 0)
+    ret = US gnutls_strerror(rc);
+  }
+
+gnutls_pubkey_deinit(verify_ctx->key);
+return ret;
+}
+
+
+
+
+#elif defined(SIGN_GCRYPT)
+/******************************************************************************/
+/* This variant is used under pre-3.0.0 GnuTLS.  Only rsa-sha1 and rsa-sha256 */
+
+
+/* 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;
+
+debug_printf_indent("%s\n", __FUNCTION__);
+
+/* 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_dkim_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. */
+
+gstring *
+exim_dkim_data_append(gstring * g, uschar * s)
+{
+return g;      /*dummy*/
+}
+
+
+
+/* import private key from PEM string in memory.
+Only handles RSA keys.
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_dkim_signing_init(const uschar * privkey_pem, es_ctx * sign_ctx)
+{
+uschar * s1, * s2;
+blob der;
+long alen;
+int rc;
+
+/*XXX will need extension to _spot_ as well as handle a
+non-RSA key?  I think...
+So... this is not a PrivateKeyInfo - which would have a field
+identifying the keytype - PrivateKeyAlgorithmIdentifier -
+but a plain RSAPrivateKey (wrapped in PEM-headers.  Can we
+use those as a type tag?  What forms are there?  "BEGIN EC PRIVATE KEY" (cf. ec(1ssl))
+
+How does OpenSSL PEM_read_bio_PrivateKey() deal with it?
+gnutls_x509_privkey_import() ?
+*/
+
+/*
+ *  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
+
+ * ECPrivateKey ::= SEQUENCE {
+ *     version        INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
+ *     privateKey     OCTET STRING,
+ *     parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
+ *     publicKey  [1] BIT STRING OPTIONAL
+ *   }
+ * Hmm, only 1 useful item, and not even an integer?  Wonder how we might use it...
+
+- actually, gnutls_x509_privkey_import() appears to require a curve name parameter
+       value for that is an OID? a local-only integer (it's an enum in GnuTLS)?
+
+
+Useful cmds:
+  ssh-keygen -t ecdsa -f foo.privkey
+  ssh-keygen -t ecdsa -b384 -f foo.privkey
+  ssh-keygen -t ecdsa -b521 -f foo.privkey
+  ssh-keygen -t ed25519 -f foo.privkey
+
+  < foo openssl pkcs8 -in /dev/stdin -inform PEM -nocrypt -topk8 -outform DER | od -x
+
+  openssl asn1parse -in foo -inform PEM -dump
+  openssl asn1parse -in foo -inform PEM -dump -stroffset 24    (??)
+(not good for ed25519)
+
+ */
+
+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;
+
+#ifdef extreme_debug
+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);
+  }
+#endif
+
+sign_ctx->keytype = KEYTYPE_RSA;
+return NULL;
+
+asn_err: return US asn1_strerror(rc);
+}
+
+
+
+/* allocate mem for signature (when signing) */
+/* sign already-hashed data.
+
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_dkim_sign(es_ctx * sign_ctx, hashmethod hash, blob * data, blob * sig)
+{
+char * sexp_hash;
+gcry_sexp_t s_hash = NULL, s_key = NULL, s_sig = NULL;
+gcry_mpi_t m_sig;
+uschar * errstr;
+gcry_error_t gerr;
+
+/*XXX will need extension for hash types (though, possibly, should
+be re-specced to not rehash but take an already-hashed value? Actually
+current impl looks WRONG - it _is_ given a hash so should not be
+re-hashing.  Has this been tested?
+
+Will need extension for non-RSA sugning algos. */
+
+switch (hash)
+  {
+  case HASH_SHA1:      sexp_hash = "(data(flags pkcs1)(hash sha1 %b))"; break;
+  case HASH_SHA2_256:  sexp_hash = "(data(flags pkcs1)(hash sha256 %b))"; break;
+  default:             return US"nonhandled hash type";
+  }
+
+#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, sexp_hash,
+               (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);
+
+#ifdef extreme_debug
+DEBUG(D_acl)
+  {
+  uschar * s;
+  gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, m_sig);
+  debug_printf_indent(" SG: %s\n", s);
+  }
+#endif
+
+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 blob in memory)
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_dkim_verify_init(blob * pubkey, keyformat fmt, 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";
+
+if (fmt != KEYFMT_DER) return US"pubkey format not handled";
+
+/*
+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, 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, ASN1_CLASS_STRUCTURED, ASN1_TAG_SEQUENCE, &alen))
+   != ASN1_SUCCESS) goto asn_err;
+pubkey->data += alen; pubkey->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, 0, ASN1_TAG_BIT_STRING, &alen)) != ASN1_SUCCESS)
+  goto asn_err;
+pubkey->len = alen;
+pubkey->data++; pubkey->len--;
+
+/* sequence; just move past the header */
+DEBUG(D_acl) stage = US"S3";
+if ((rc = as_tag(pubkey, 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, &verify_ctx->n))
+   || (errstr = as_mpi(pubkey, &verify_ctx->e))
+   )
+  return errstr;
+
+#ifdef extreme_debug
+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);
+       }
+
+#endif
+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)
+XXX though we appear to be doing a hash, too!
+(given pubkey & alleged sig)
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_dkim_verify(ev_ctx * verify_ctx, hashmethod hash, blob * data_hash, blob * sig)
+{
+/*
+cf. libgnutls 2.8.5 _wrap_gcry_pk_verify()
+*/
+char * sexp_hash;
+gcry_mpi_t m_sig;
+gcry_sexp_t s_sig = NULL, s_hash = NULL, s_pkey = NULL;
+gcry_error_t gerr;
+uschar * stage;
+
+/*XXX needs extension for SHA512 */
+switch (hash)
+  {
+  case HASH_SHA1:     sexp_hash = "(data(flags pkcs1)(hash sha1 %b))"; break;
+  case HASH_SHA2_256: sexp_hash = "(data(flags pkcs1)(hash sha256 %b))"; break;
+  default:           return US"nonhandled hash type";
+  }
+
+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, sexp_hash,
+               (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(SIGN_OPENSSL)
+/******************************************************************************/
+
+void
+exim_dkim_init(void)
+{
+ERR_load_crypto_strings();
+}
+
+
+/* accumulate data (was gnutls-only but now needed for OpenSSL non-EC too
+because now using hash-and-sign interface) */
+gstring *
+exim_dkim_data_append(gstring * g, uschar * s)
+{
+return string_cat(g, s);
+}
+
+
+/* import private key from PEM string in memory.
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_dkim_signing_init(const uschar * privkey_pem, es_ctx * sign_ctx)
+{
+BIO * bp = BIO_new_mem_buf(privkey_pem, -1);
+
+if (!(sign_ctx->key = PEM_read_bio_PrivateKey(bp, NULL, NULL, NULL)))
+  return string_sprintf("privkey PEM-block import: %s",
+                       ERR_error_string(ERR_get_error(), NULL));
+
+sign_ctx->keytype =
+#ifdef SIGN_HAVE_ED25519
+       EVP_PKEY_type(EVP_PKEY_id(sign_ctx->key)) == EVP_PKEY_ED25519
+         ? KEYTYPE_ED25519 : KEYTYPE_RSA;
+#else
+       KEYTYPE_RSA;
+#endif
+return NULL;
+}
+
+
+
+/* allocate mem for signature (when signing) */
+/* hash & sign data.  Incremental not supported.
+
+Return: NULL for success with the signaature in the sig blob, or an error string */
+
+const uschar *
+exim_dkim_sign(es_ctx * sign_ctx, hashmethod hash, blob * data, blob * sig)
+{
+const EVP_MD * md;
+EVP_MD_CTX * ctx;
+size_t siglen;
+
+switch (hash)
+  {
+  case HASH_NULL:      md = NULL;         break;       /* Ed25519 signing */
+  case HASH_SHA1:      md = EVP_sha1();   break;
+  case HASH_SHA2_256:  md = EVP_sha256(); break;
+  case HASH_SHA2_512:  md = EVP_sha512(); break;
+  default:             return US"nonhandled hash type";
+  }
+
+#ifdef SIGN_HAVE_ED25519
+if (  (ctx = EVP_MD_CTX_new())
+   && EVP_DigestSignInit(ctx, NULL, md, NULL, sign_ctx->key) > 0
+   && EVP_DigestSign(ctx, NULL, &siglen, NULL, 0) > 0
+   && (sig->data = store_get(siglen))
+
+   /* Obtain the signature (slen could change here!) */
+   && EVP_DigestSign(ctx, sig->data, &siglen, data->data, data->len) > 0
+   )
+  {
+  EVP_MD_CTX_destroy(ctx);
+  sig->len = siglen;
+  return NULL;
+  }
+#else
+/*XXX renamed to EVP_MD_CTX_new() in 1.1.0 */
+if (  (ctx = EVP_MD_CTX_create())
+   && EVP_DigestSignInit(ctx, NULL, md, NULL, sign_ctx->key) > 0
+   && EVP_DigestSignUpdate(ctx, data->data, data->len) > 0
+   && EVP_DigestSignFinal(ctx, NULL, &siglen) > 0
+   && (sig->data = store_get(siglen))
+   /* Obtain the signature (slen could change here!) */
+   && EVP_DigestSignFinal(ctx, sig->data, &siglen) > 0
+   )
+  {
+  EVP_MD_CTX_destroy(ctx);
+  sig->len = siglen;
+  return NULL;
+  }
+#endif
+
+if (ctx) EVP_MD_CTX_destroy(ctx);
+return US ERR_error_string(ERR_get_error(), NULL);
+}
+
+
+
+/* import public key (from blob in memory)
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_dkim_verify_init(blob * pubkey, keyformat fmt, ev_ctx * verify_ctx)
+{
+const uschar * s = pubkey->data;
+uschar * ret = NULL;
+
+switch(fmt)
+  {
+  case KEYFMT_DER:
+    /*XXX hmm, we never free this */
+    if (!(verify_ctx->key = d2i_PUBKEY(NULL, &s, pubkey->len)))
+      ret = US ERR_error_string(ERR_get_error(), NULL);
+    break;
+#ifdef SIGN_HAVE_ED25519
+  case KEYFMT_ED25519_BARE:
+    if (!(verify_ctx->key = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL,
+                                                       s, pubkey->len)))
+      ret = US ERR_error_string(ERR_get_error(), NULL);
+    break;
+#endif
+  default:
+    ret = US"pubkey format not handled";
+    break;
+  }
+
+return ret;
+}
+
+
+
+
+/* verify signature (of hash, except Ed25519 where of-data)
+(given pubkey & alleged sig)
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_dkim_verify(ev_ctx * verify_ctx, hashmethod hash, blob * data, blob * sig)
+{
+const EVP_MD * md;
+
+switch (hash)
+  {
+  case HASH_NULL:      md = NULL;         break;
+  case HASH_SHA1:      md = EVP_sha1();   break;
+  case HASH_SHA2_256:  md = EVP_sha256(); break;
+  case HASH_SHA2_512:  md = EVP_sha512(); break;
+  default:             return US"nonhandled hash type";
+  }
+
+#ifdef SIGN_HAVE_ED25519
+if (!md)
+  {
+  EVP_MD_CTX * ctx;
+
+  if ((ctx = EVP_MD_CTX_new()))
+    {
+    if (  EVP_DigestVerifyInit(ctx, NULL, md, NULL, verify_ctx->key) > 0
+       && EVP_DigestVerify(ctx, sig->data, sig->len, data->data, data->len) > 0
+       )
+      { EVP_MD_CTX_free(ctx); return NULL; }
+    EVP_MD_CTX_free(ctx);
+    }
+  }
+else
+#endif
+  {
+  EVP_PKEY_CTX * ctx;
+
+  if ((ctx = EVP_PKEY_CTX_new(verify_ctx->key, NULL)))
+    {
+    if (  EVP_PKEY_verify_init(ctx) > 0
+       && EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) > 0
+       && EVP_PKEY_CTX_set_signature_md(ctx, md) > 0
+       && EVP_PKEY_verify(ctx, sig->data, sig->len,
+                                     data->data, data->len) == 1
+       )
+      { EVP_PKEY_CTX_free(ctx); return NULL; }
+    EVP_PKEY_CTX_free(ctx);
+
+    DEBUG(D_tls)
+      if (Ustrcmp(ERR_reason_error_string(ERR_peek_error()), "wrong signature length") == 0)
+       debug_printf("sig len (from msg hdr): %d, expected (from dns pubkey) %d\n",
+        (int) sig->len, EVP_PKEY_size(verify_ctx->key));
+    }
+  }
+
+return US ERR_error_string(ERR_get_error(), NULL);
+}
+
+
+
+#endif
+/******************************************************************************/
+
+#endif /*DISABLE_DKIM*/
+#endif /*MACRO_PREDEF*/
+/* End of File */
diff --git a/src/pdkim/signing.h b/src/pdkim/signing.h
new file mode 100644 (file)
index 0000000..96a0720
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ *  PDKIM - a RFC4871 (DKIM) implementation
+ *
+ *  Copyright (C) 1995 - 2018  Exim maintainers
+ *
+ *  RSA signing/verification interface
+ */
+
+#include "../exim.h"
+
+#ifndef DISABLE_DKIM   /* entire file */
+
+#include "crypt_ver.h"
+
+#ifdef SIGN_OPENSSL
+# include <openssl/rsa.h>
+# include <openssl/ssl.h>
+# include <openssl/err.h>
+#elif defined(SIGN_GNUTLS)
+# include <gnutls/gnutls.h>
+# include <gnutls/x509.h>
+# include <gnutls/abstract.h>
+#elif defined(SIGN_GCRYPT)
+# include <gcrypt.h>
+# include <libtasn1.h>
+#endif
+
+#include "../blob.h"
+
+typedef enum {
+  KEYTYPE_RSA,
+  KEYTYPE_ED25519
+} keytype;
+
+typedef enum {
+  KEYFMT_DER,          /* an asn.1 structure */
+  KEYFMT_ED25519_BARE  /* just the key */
+} keyformat;
+
+
+#ifdef SIGN_OPENSSL
+
+typedef struct {
+  keytype      keytype;
+  EVP_PKEY *   key;
+} es_ctx;
+
+typedef struct {
+  keytype      keytype;
+  EVP_PKEY *   key;
+} ev_ctx;
+
+#elif defined(SIGN_GNUTLS)
+
+typedef struct {
+  keytype      keytype;
+  gnutls_privkey_t key;
+} es_ctx;
+
+typedef struct {
+  keytype      keytype;
+  gnutls_pubkey_t key;
+} ev_ctx;
+
+#elif defined(SIGN_GCRYPT)
+
+typedef struct {
+  keytype      keytype;
+  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 {
+  keytype      keytype;
+  gcry_mpi_t n;
+  gcry_mpi_t e;
+} ev_ctx;
+
+#endif
+
+
+extern void exim_dkim_init(void);
+extern gstring * exim_dkim_data_append(gstring *, uschar *);
+
+extern const uschar * exim_dkim_signing_init(const uschar *, es_ctx *);
+extern const uschar * exim_dkim_sign(es_ctx *, hashmethod, blob *, blob *);
+extern const uschar * exim_dkim_verify_init(blob *, keyformat, ev_ctx *);
+extern const uschar * exim_dkim_verify(ev_ctx *, hashmethod, blob *, blob *);
+
+#endif /*DISABLE_DKIM*/
+/* End of File */
index 543b5d2..58643f0 100644 (file)
@@ -3,6 +3,7 @@
 *************************************************/
 
 /* Copyright (c) 1998 Malcolm Beattie */
 *************************************************/
 
 /* Copyright (c) 1998 Malcolm Beattie */
+/* Copyright (C) 1999 - 2018  Exim maintainers */
 
 /* Modified by PH to get rid of the "na" usage, March 1999.
    Modified further by PH for general tidying for Exim 4.
 
 /* Modified by PH to get rid of the "na" usage, March 1999.
    Modified further by PH for general tidying for Exim 4.
@@ -13,6 +14,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. */
 
 /* 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
 #include "exim.h"
 
 #define EXIM_TRUE TRUE
@@ -59,8 +61,8 @@ XS(xs_expand_string)
   str = expand_string(US SvPV(ST(0), len));
   ST(0) = sv_newmortal();
   if (str != NULL)
   str = expand_string(US SvPV(ST(0), len));
   ST(0) = sv_newmortal();
   if (str != NULL)
-    sv_setpv(ST(0), (const char *) str);
-  else if (!expand_string_forcedfail)
+    sv_setpv(ST(0), CCS  str);
+  else if (!f.expand_string_forcedfail)
     croak("syntax error in Exim::expand_string argument: %s",
       expand_string_message);
 }
     croak("syntax error in Exim::expand_string argument: %s",
       expand_string_message);
 }
@@ -95,11 +97,17 @@ static void  xs_init(pTHX)
 uschar *
 init_perl(uschar *startup_code)
 {
 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;
 
   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);
   if (interp_perl) return 0;
   interp_perl = perl_alloc();
   perl_construct(interp_perl);
@@ -143,9 +151,8 @@ cleanup_perl(void)
   interp_perl = 0;
 }
 
   interp_perl = 0;
 }
 
-uschar *
-call_perl_cat(uschar *yield, int *sizep, int *ptrp, uschar **errstrp,
-  uschar *name, uschar **arg)
+gstring *
+call_perl_cat(gstring * yield, uschar **errstrp, uschar *name, uschar **arg)
 {
   dSP;
   SV *sv;
 {
   dSP;
   SV *sv;
@@ -179,7 +186,7 @@ call_perl_cat(uschar *yield, int *sizep, int *ptrp, uschar **errstrp,
     return NULL;
     }
   str = US SvPV(sv, len);
     return NULL;
     }
   str = US SvPV(sv, len);
-  yield = string_cat(yield, sizep, ptrp, str, (int)len);
+  yield = string_catn(yield, str, (int)len);
   FREETMPS;
   LEAVE;
 
   FREETMPS;
   LEAVE;
 
index 8876e09..92109ef 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions that operate on the input queue. */
 /* 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. */
 /* 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,9 +79,12 @@ merge_queue_lists(queue_filename *a, queue_filename *b)
 queue_filename *first = NULL;
 queue_filename **append = &first;
 
 queue_filename *first = NULL;
 queue_filename **append = &first;
 
-while (a != NULL && b != NULL)
+while (a && b)
   {
   {
-  if (Ustrcmp(a->text, b->text) < 0)
+  int d;
+  if ((d = Ustrncmp(a->text, b->text, 6)) == 0)
+    d = Ustrcmp(a->text + 14, b->text + 14);
+  if (d < 0)
     {
     *append = a;
     append= &a->next;
     {
     *append = a;
     append= &a->next;
@@ -56,7 +98,7 @@ while (a != NULL && b != NULL)
     }
   }
 
     }
   }
 
-*append=((a != NULL)? a : b);
+*append = a ? a : b;
 return first;
 }
 
 return first;
 }
 
@@ -125,8 +167,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. */
 
 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,
 
 /* 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 +183,13 @@ if (subdiroffset <= 0)
   subdirs[0] = 0;
   *subcount = 0;
   }
   subdirs[0] = 0;
   *subcount = 0;
   }
-else i = subdiroffset;
+else
+  i = subdiroffset;
 
 /* Set up prototype for the directory name. */
 
 
 /* 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 */
 
 subptr = Ustrlen(buffer);
 buffer[subptr+2] = 0;               /* terminator for lengthened name */
 
@@ -161,12 +208,13 @@ for (; i <= *subcount; i++)
     buffer[subptr+1] = subdirchar;
     }
 
     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. */
 
 
   /* Now scan the directory. */
 
-  while ((ent = readdir(dd)) != NULL)
+  while ((ent = readdir(dd)))
     {
     uschar *name = US ent->d_name;
     int len = Ustrlen(name);
     {
     uschar *name = US ent->d_name;
     int len = Ustrlen(name);
@@ -202,15 +250,15 @@ for (; i <= *subcount; i++)
       to store the number with each item. */
 
       if (randomize)
       to store the number with each item. */
 
       if (randomize)
-        {
-        if (yield == NULL)
+        if (!yield)
           {
           next->next = NULL;
           yield = last = next;
           }
         else
           {
           {
           next->next = NULL;
           yield = last = next;
           }
         else
           {
-          if (flags == 0) flags = resetflags;
+          if (flags == 0)
+           flags = resetflags;
           if ((flags & 1) == 0)
             {
             next->next = yield;
           if ((flags & 1) == 0)
             {
             next->next = yield;
@@ -224,7 +272,6 @@ for (; i <= *subcount; i++)
             }
           flags = flags >> 1;
           }
             }
           flags = flags >> 1;
           }
-        }
 
       /* Otherwise do a bottom-up merge sort based on the name. */
 
 
       /* Otherwise do a bottom-up merge sort based on the name. */
 
@@ -233,18 +280,16 @@ for (; i <= *subcount; i++)
         int j;
         next->next = NULL;
         for (j = 0; j < LOG2_MAXNODES; j++)
         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]);
             {
             next = merge_queue_lists(next, root[j]);
-            root[j] = (j == LOG2_MAXNODES - 1)? next : NULL;
+            root[j] = j == LOG2_MAXNODES - 1 ? next : NULL;
             }
           else
             {
             root[j] = next;
             break;
             }
             }
           else
             {
             root[j] = next;
             break;
             }
-          }
         }
       }
     }
         }
       }
     }
@@ -264,9 +309,11 @@ for (; i <= *subcount; i++)
     {
     if (!split_spool_directory && count <= 2)
       {
     {
     if (!split_spool_directory && count <= 2)
       {
+      uschar subdir[2];
+
       rmdir(CS buffer);
       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 */
     }
       }
     if (subdiroffset > 0) break;    /* Single sub-directory */
     }
@@ -274,10 +321,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. */
 
   /* 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.
   }    /* Loop for multiple subdirectories */
 
 /* When using a bottom-up merge sort, do the final merging of the sublists.
@@ -328,7 +373,7 @@ Returns:     nothing
 void
 queue_run(uschar *start_id, uschar *stop_id, BOOL recurse)
 {
 void
 queue_run(uschar *start_id, uschar *stop_id, BOOL recurse)
 {
-BOOL force_delivery = queue_run_force || deliver_selectstring != NULL ||
+BOOL force_delivery = f.queue_run_force || deliver_selectstring != NULL ||
   deliver_selectstring_sender != NULL;
 const pcre *selectstring_regex = NULL;
 const pcre *selectstring_regex_sender = NULL;
   deliver_selectstring_sender != NULL;
 const pcre *selectstring_regex = NULL;
 const pcre *selectstring_regex_sender = NULL;
@@ -345,10 +390,10 @@ on TCP/IP channels have queue_run_pid set, but not queue_running. */
 
 queue_domains = NULL;
 queue_smtp_domains = NULL;
 
 queue_domains = NULL;
 queue_smtp_domains = NULL;
-queue_smtp = queue_2stage;
+f.queue_smtp = f.queue_2stage;
 
 queue_run_pid = getpid();
 
 queue_run_pid = getpid();
-queue_running = TRUE;
+f.queue_running = TRUE;
 
 /* Log the true start of a queue run, and fancy options */
 
 
 /* Log the true start of a queue run, and fancy options */
 
@@ -357,47 +402,41 @@ if (!recurse)
   uschar extras[8];
   uschar *p = extras;
 
   uschar extras[8];
   uschar *p = extras;
 
-  if (queue_2stage) *p++ = 'q';
-  if (queue_run_first_delivery) *p++ = 'i';
-  if (queue_run_force) *p++ = 'f';
-  if (deliver_force_thaw) *p++ = 'f';
-  if (queue_run_local) *p++ = 'l';
+  if (f.queue_2stage) *p++ = 'q';
+  if (f.queue_run_first_delivery) *p++ = 'i';
+  if (f.queue_run_force) *p++ = 'f';
+  if (f.deliver_force_thaw) *p++ = 'f';
+  if (f.queue_run_local) *p++ = 'l';
   *p = 0;
 
   p = big_buffer;
   *p = 0;
 
   p = big_buffer;
-  sprintf(CS p, "pid=%d", (int)queue_run_pid);
-  while (*p != 0) p++;
+  p += sprintf(CS p, "pid=%d", (int)queue_run_pid);
 
   if (extras[0] != 0)
 
   if (extras[0] != 0)
-    {
-    sprintf(CS p, " -q%s", extras);
-    while (*p != 0) p++;
-    }
+    p += sprintf(CS p, " -q%s", extras);
 
 
-  if (deliver_selectstring != NULL)
-    {
-    sprintf(CS p, " -R%s %s", deliver_selectstring_regex? "r" : "",
+  if (deliver_selectstring)
+    p += sprintf(CS p, " -R%s %s", f.deliver_selectstring_regex? "r" : "",
       deliver_selectstring);
       deliver_selectstring);
-    while (*p != 0) p++;
-    }
 
 
-  if (deliver_selectstring_sender != NULL)
-    {
-    sprintf(CS p, " -S%s %s", deliver_selectstring_sender_regex? "r" : "",
+  if (deliver_selectstring_sender)
+    p += sprintf(CS p, " -S%s %s", f.deliver_selectstring_sender_regex? "r" : "",
       deliver_selectstring_sender);
       deliver_selectstring_sender);
-    while (*p != 0) p++;
-    }
 
   log_detail = string_copy(big_buffer);
 
   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. */
 
   }
 
 /* If deliver_selectstring is a regex, compile it. */
 
-if (deliver_selectstring != NULL && deliver_selectstring_regex)
+if (deliver_selectstring && f.deliver_selectstring_regex)
   selectstring_regex = regex_must_compile(deliver_selectstring, TRUE, FALSE);
 
   selectstring_regex = regex_must_compile(deliver_selectstring, TRUE, FALSE);
 
-if (deliver_selectstring_sender != NULL && deliver_selectstring_sender_regex)
+if (deliver_selectstring_sender && f.deliver_selectstring_sender_regex)
   selectstring_regex_sender =
     regex_must_compile(deliver_selectstring_sender, TRUE, FALSE);
 
   selectstring_regex_sender =
     regex_must_compile(deliver_selectstring_sender, TRUE, FALSE);
 
@@ -411,16 +450,16 @@ 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
 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. */
 
 
 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. */
 
-for (i  = (queue_run_in_order? -1 : 0);
-     i <= (queue_run_in_order? -1 : subcount);
+for (i = queue_run_in_order ? -1 : 0;
+     i <= (queue_run_in_order ? -1 : subcount);
      i++)
   {
      i++)
   {
-  queue_filename *f;
+  queue_filename * fq;
   void *reset_point1 = store_get(0);
 
   DEBUG(D_queue_run)
   void *reset_point1 = store_get(0);
 
   DEBUG(D_queue_run)
@@ -433,9 +472,9 @@ for (i  = (queue_run_in_order? -1 : 0);
       debug_printf("queue running subdirectory '%c'\n", subdirs[i]);
     }
 
       debug_printf("queue running subdirectory '%c'\n", subdirs[i]);
     }
 
-  for (f = queue_get_spool_list(i, subdirs, &subcount, !queue_run_in_order);
-       f != NULL;
-       f = f->next)
+  for (fq = queue_get_spool_list(i, subdirs, &subcount, !queue_run_in_order);
+       fq;
+       fq = fq->next)
     {
     pid_t pid;
     int status;
     {
     pid_t pid;
     int status;
@@ -446,10 +485,8 @@ for (i  = (queue_run_in_order? -1 : 0);
     /* Unless deliveries are forced, if deliver_queue_load_max is non-negative,
     check that the load average is low enough to permit deliveries. */
 
     /* Unless deliveries are forced, if deliver_queue_load_max is non-negative,
     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 (!f.queue_run_force && deliver_queue_load_max >= 0)
+      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,
         {
         log_write(L_queue_run, LOG_MAIN, "Abandon queue run: %s (load %.2f, max %.2f)",
           log_detail,
@@ -459,26 +496,22 @@ for (i  = (queue_run_in_order? -1 : 0);
         break;
         }
       else
         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);
         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 */
 
 
     /* 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(fq->text, stop_id, MESSAGE_ID_LENGTH) > 0)
       continue;
       continue;
-    if (start_id != NULL && Ustrncmp(f->text, start_id, MESSAGE_ID_LENGTH) < 0)
+    if (start_id && Ustrncmp(fq->text, start_id, MESSAGE_ID_LENGTH) < 0)
       continue;
 
     /* Check that the message still exists */
 
       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;
+    message_subdir[0] = fq->dir_uschar;
+    if (Ustat(spool_fname(US"input", message_subdir, fq->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
 
     /* 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,11 +519,11 @@ 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. */
 
     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 ||
-        queue_run_first_delivery)
+    if (deliver_selectstring || deliver_selectstring_sender ||
+        f.queue_run_first_delivery)
       {
       BOOL wanted = TRUE;
       {
       BOOL wanted = TRUE;
-      BOOL orig_dont_deliver = dont_deliver;
+      BOOL orig_dont_deliver = f.dont_deliver;
       void *reset_point2 = store_get(0);
 
       /* Restore the original setting of dont_deliver after reading the header,
       void *reset_point2 = store_get(0);
 
       /* Restore the original setting of dont_deliver after reading the header,
@@ -498,14 +531,14 @@ for (i  = (queue_run_in_order? -1 : 0);
       follow. If the message is chosen for delivery, the header is read again
       in the deliver_message() function, in a subprocess. */
 
       follow. If the message is chosen for delivery, the header is read again
       in the deliver_message() function, in a subprocess. */
 
-      if (spool_read_header(f->text, FALSE, TRUE) != spool_read_OK) continue;
-      dont_deliver = orig_dont_deliver;
+      if (spool_read_header(fq->text, FALSE, TRUE) != spool_read_OK) continue;
+      f.dont_deliver = orig_dont_deliver;
 
       /* Now decide if we want to deliver this message. As we have read the
       header file, we might as well do the freeze test now, and save forking
       another process. */
 
 
       /* Now decide if we want to deliver this message. As we have read the
       header file, we might as well do the freeze test now, and save forking
       another process. */
 
-      if (deliver_freeze && !deliver_force_thaw)
+      if (f.deliver_freeze && !f.deliver_force_thaw)
         {
         log_write(L_skip_delivery, LOG_MAIN, "Message is frozen");
         wanted = FALSE;
         {
         log_write(L_skip_delivery, LOG_MAIN, "Message is frozen");
         wanted = FALSE;
@@ -513,46 +546,47 @@ for (i  = (queue_run_in_order? -1 : 0);
 
       /* Check first_delivery in the case when there are no message logs. */
 
 
       /* Check first_delivery in the case when there are no message logs. */
 
-      else if (queue_run_first_delivery && !deliver_firsttime)
+      else if (f.queue_run_first_delivery && !f.deliver_firsttime)
         {
         {
-        DEBUG(D_queue_run) debug_printf("%s: not first delivery\n", f->text);
+        DEBUG(D_queue_run) debug_printf("%s: not first delivery\n", fq->text);
         wanted = FALSE;
         }
 
         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 */
 
       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
+             && !(f.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",
         {
         DEBUG(D_queue_run) debug_printf("%s: sender address did not match %s\n",
-          f->text, deliver_selectstring_sender);
+          fq->text, deliver_selectstring_sender);
         wanted = FALSE;
         }
 
       /* Recipient matching */
 
         wanted = FALSE;
         }
 
       /* 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;
         {
         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 (  (f.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;
           }
 
             break;
           }
 
@@ -560,13 +594,14 @@ for (i  = (queue_run_in_order? -1 : 0);
           {
           DEBUG(D_queue_run)
             debug_printf("%s: no recipient address matched %s\n",
           {
           DEBUG(D_queue_run)
             debug_printf("%s: no recipient address matched %s\n",
-              f->text, deliver_selectstring);
+              fq->text, deliver_selectstring);
           wanted = FALSE;
           }
         }
 
       /* Recover store used when reading the header */
 
           wanted = FALSE;
           }
         }
 
       /* Recover store used when reading the header */
 
+      spool_clear_header_globals();
       store_reset(reset_point2);
       if (!wanted) continue;      /* With next message */
       }
       store_reset(reset_point2);
       if (!wanted) continue;      /* With next message */
       }
@@ -581,10 +616,8 @@ for (i  = (queue_run_in_order? -1 : 0);
     pretty cheap. */
 
     if (pipe(pfd) < 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));
       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
     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
@@ -609,14 +642,14 @@ for (i  = (queue_run_in_order? -1 : 0);
     /* Now deliver the message; get the id by cutting the -H off the file
     name. The return of the process is zero if a delivery was attempted. */
 
     /* Now deliver the message; get the id by cutting the -H off the file
     name. The return of the process is zero if a delivery was attempted. */
 
-    set_process_info("running queue: %s", f->text);
-    f->text[SPOOL_NAME_LENGTH-2] = 0;
+    set_process_info("running queue: %s", fq->text);
+    fq->text[SPOOL_NAME_LENGTH-2] = 0;
     if ((pid = fork()) == 0)
       {
       int rc;
     if ((pid = fork()) == 0)
       {
       int rc;
-      if (running_in_test_harness) millisleep(100);
+      if (f.running_in_test_harness) millisleep(100);
       (void)close(pfd[pipe_read]);
       (void)close(pfd[pipe_read]);
-      rc = deliver_message(f->text, force_delivery, FALSE);
+      rc = deliver_message(fq->text, force_delivery, FALSE);
       _exit(rc == DELIVER_NOT_ATTEMPTED);
       }
     if (pid < 0)
       _exit(rc == DELIVER_NOT_ATTEMPTED);
       }
     if (pid < 0)
@@ -627,22 +660,20 @@ for (i  = (queue_run_in_order? -1 : 0);
     then wait for the first level process to terminate. */
 
     (void)close(pfd[pipe_write]);
     then wait for the first level process to terminate. */
 
     (void)close(pfd[pipe_write]);
-    set_process_info("running queue: waiting for %s (%d)", f->text, pid);
+    set_process_info("running queue: waiting for %s (%d)", fq->text, pid);
     while (wait(&status) != pid);
 
     /* A zero return means a delivery was attempted; turn off the force flag
     for any subsequent calls unless queue_force is set. */
 
     while (wait(&status) != pid);
 
     /* A zero return means a delivery was attempted; turn off the force flag
     for any subsequent calls unless queue_force is set. */
 
-    if ((status & 0xffff) == 0) force_delivery = queue_run_force;
+    if ((status & 0xffff) == 0) force_delivery = f.queue_run_force;
 
     /* If the process crashed, tell somebody */
 
     else if ((status & 0x00ff) != 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",
       log_write(0, LOG_MAIN|LOG_PANIC,
         "queue run: process %d crashed with signal %d while delivering %s",
-        (int)pid, status & 0x00ff, f->text);
-      }
+        (int)pid, status & 0x00ff, fq->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
 
     /* 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,21 +681,23 @@ 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);
     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");
 
     /* If we are in the test harness, and this is not the first of a 2-stage
     queue run, update fudged queue times. */
 
     (void)close(pfd[pipe_read]);
     set_process_info("running queue");
 
     /* If we are in the test harness, and this is not the first of a 2-stage
     queue run, update fudged queue times. */
 
-    if (running_in_test_harness && !queue_2stage)
+    if (f.running_in_test_harness && !f.queue_2stage)
       {
       uschar *fqtnext = Ustrchr(fudged_queue_times, '/');
       if (fqtnext != NULL) fudged_queue_times = fqtnext + 1;
       }
     }                                  /* End loop for list of messages */
 
       {
       uschar *fqtnext = Ustrchr(fudged_queue_times, '/');
       if (fqtnext != NULL) fudged_queue_times = fqtnext + 1;
       }
     }                                  /* End loop for list of messages */
 
+  tree_nonrecipients = NULL;
   store_reset(reset_point1);           /* Scavenge list of messages */
 
   /* If this was the first time through for random order processing, and
   store_reset(reset_point1);           /* Scavenge list of messages */
 
   /* If this was the first time through for random order processing, and
@@ -672,33 +705,35 @@ for (i  = (queue_run_in_order? -1 : 0);
 
   if (i == 0 && subcount > 1 && !queue_run_in_order)
     {
 
   if (i == 0 && subcount > 1 && !queue_run_in_order)
     {
-    int j;
+    int j, r;
     for (j = 1; j <= subcount; j++)
     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;
         }
         {
         int k = (r % subcount) + 1;
         int x = subdirs[j];
         subdirs[j] = subdirs[k];
         subdirs[k] = x;
         }
-      }
     }
   }                                    /* End loop for multiple directories */
 
 /* If queue_2stage is true, we do it all again, with the 2stage flag
 turned off. */
 
     }
   }                                    /* End loop for multiple directories */
 
 /* If queue_2stage is true, we do it all again, with the 2stage flag
 turned off. */
 
-if (queue_2stage)
+if (f.queue_2stage)
   {
   {
-  queue_2stage = FALSE;
+  f.queue_2stage = FALSE;
   queue_run(start_id, stop_id, TRUE);
   }
 
 /* At top level, log the end of the run. */
 
   queue_run(start_id, stop_id, TRUE);
   }
 
 /* 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);
 }
 
 
 }
 
 
@@ -785,7 +820,7 @@ int i;
 int subcount;
 int now = (int)time(NULL);
 void *reset_point;
 int subcount;
 int now = (int)time(NULL);
 void *reset_point;
-queue_filename *f = NULL;
+queue_filename * qf = NULL;
 uschar subdirs[64];
 
 /* If given a list of messages, build a chain containing their ids. */
 uschar subdirs[64];
 
 /* If given a list of messages, build a chain containing their ids. */
@@ -800,7 +835,7 @@ if (count > 0)
     sprintf(CS next->text, "%s-H", list[i]);
     next->dir_uschar = '*';
     next->next = NULL;
     sprintf(CS next->text, "%s-H", list[i]);
     next->dir_uschar = '*';
     next->next = NULL;
-    if (i == 0) f = next; else last->next = next;
+    if (i == 0) qf = next; else last->next = next;
     last = next;
     }
   }
     last = next;
     }
   }
@@ -808,7 +843,7 @@ if (count > 0)
 /* Otherwise get a list of the entire queue, in order if necessary. */
 
 else
 /* Otherwise get a list of the entire queue, in order if necessary. */
 
 else
-  f = queue_get_spool_list(
+  qf = queue_get_spool_list(
           -1,             /* entire queue */
           subdirs,        /* for holding sub list */
           &subcount,      /* for subcount */
           -1,             /* entire queue */
           subdirs,        /* for holding sub list */
           &subcount,      /* for subcount */
@@ -819,19 +854,20 @@ if (option >= 8) option -= 8;
 /* Now scan the chain and print information, resetting store used
 each time. */
 
 /* 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);
+    qf;
+    spool_clear_header_globals(), store_reset(reset_point), qf = qf->next
+    )
   {
   int rc, save_errno;
   int size = 0;
   BOOL env_read;
 
   {
   int rc, save_errno;
   int size = 0;
   BOOL env_read;
 
-  store_reset(reset_point);
   message_size = 0;
   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;
+  message_subdir[0] = qf->dir_uschar;
+  rc = spool_read_header(qf->text, FALSE, count <= 0);
+  if (rc == spool_read_notopen && errno == ENOENT && count <= 0)
+    continue;
   save_errno = errno;
 
   env_read = (rc == spool_read_OK || rc == spool_read_hdrerror);
   save_errno = errno;
 
   env_read = (rc == spool_read_OK || rc == spool_read_hdrerror);
@@ -841,19 +877,18 @@ for (; f != NULL; f = f->next)
     int ptr;
     FILE *jread;
     struct stat statbuf;
     int ptr;
     FILE *jread;
     struct stat statbuf;
+    uschar * fname = spool_fname(US"input", message_subdir, qf->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. */
 
 
     /* 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;
       size = message_size + statbuf.st_size - SPOOL_DATA_START_OFFSET + 1;
-    i = (now - received_time)/60;  /* minutes on queue */
+    i = (now - received_time.tv_sec)/60;  /* minutes on queue */
     if (i > 90)
       {
       i = (i + 30)/60;
     if (i > 90)
       {
       i = (i + 30)/60;
@@ -863,9 +898,8 @@ for (; f != NULL; f = f->next)
 
     /* Collect delivered addresses from any J file */
 
 
     /* 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)
         {
       {
       while (Ufgets(big_buffer, big_buffer_size, jread) != NULL)
         {
@@ -878,12 +912,12 @@ for (; f != NULL; f = f->next)
     }
 
   fprintf(stdout, "%s ", string_format_size(size, big_buffer));
     }
 
   fprintf(stdout, "%s ", string_format_size(size, big_buffer));
-  for (i = 0; i < 16; i++) fputc(f->text[i], stdout);
+  for (i = 0; i < 16; i++) fputc(qf->text[i], stdout);
 
 
-  if (env_read && sender_address != NULL)
+  if (env_read && sender_address)
     {
     printf(" <%s>", sender_address);
     {
     printf(" <%s>", sender_address);
-    if (sender_set_untrusted) printf(" (%s)", originator_login);
+    if (f.sender_set_untrusted) printf(" (%s)", originator_login);
     }
 
   if (rc != spool_read_OK)
     }
 
   if (rc != spool_read_OK)
@@ -892,9 +926,9 @@ for (; f != NULL; f = f->next)
     if (save_errno == ERRNO_SPOOLFORMAT)
       {
       struct stat statbuf;
     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, qf->text, US"");
+
+      if (Ustat(fname, &statbuf) == 0)
         printf("*** spool format error: size=" OFF_T_FMT " ***",
           statbuf.st_size);
       else printf("*** spool format error ***");
         printf("*** spool format error: size=" OFF_T_FMT " ***",
           statbuf.st_size);
       else printf("*** spool format error ***");
@@ -907,22 +941,22 @@ for (; f != NULL; f = f->next)
       }
     }
 
       }
     }
 
-  if (deliver_freeze) printf(" *** frozen ***");
+  if (f.deliver_freeze) printf(" *** frozen ***");
 
   printf("\n");
 
 
   printf("\n");
 
-  if (recipients_list != NULL)
+  if (recipients_list)
     {
     for (i = 0; i < recipients_count; i++)
       {
       tree_node *delivered =
         tree_search(tree_nonrecipients, recipients_list[i].address);
       if (!delivered || option != 1)
     {
     for (i = 0; i < recipients_count; i++)
       {
       tree_node *delivered =
         tree_search(tree_nonrecipients, recipients_list[i].address);
       if (!delivered || option != 1)
-        printf("        %s %s\n", (delivered != NULL)? "D":" ",
-          recipients_list[i].address);
-      if (delivered != NULL) delivered->data.val = TRUE;
+        printf("        %s %s\n",
+         delivered ? "D" : " ", recipients_list[i].address);
+      if (delivered) delivered->data.val = TRUE;
       }
       }
-    if (option == 2 && tree_nonrecipients != NULL)
+    if (option == 2 && tree_nonrecipients)
       queue_list_extras(tree_nonrecipients);
     printf("\n");
     }
       queue_list_extras(tree_nonrecipients);
     printf("\n");
     }
@@ -959,7 +993,7 @@ struct passwd *pw;
 uschar *doing = NULL;
 uschar *username;
 uschar *errmsg;
 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. */
 
 /* Set the global message_id variable, used when re-writing spool files. This
 also causes message ids to be added to log messages. */
@@ -974,7 +1008,7 @@ if (action >= MSG_SHOW_BODY)
   int fd, i, rc;
   uschar *subdirectory, *suffix;
 
   int fd, i, rc;
   uschar *subdirectory, *suffix;
 
-  if (!admin_user)
+  if (!f.admin_user)
     {
     printf("Permission denied\n");
     return FALSE;
     {
     printf("Permission denied\n");
     return FALSE;
@@ -1004,12 +1038,13 @@ if (action >= MSG_SHOW_BODY)
 
   for (i = 0; i < 2; i++)
     {
 
   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)
     printf("Failed to open %s file for %s%s: %s\n", subdirectory, id, suffix,
       strerror(errno));
     if (action == MSG_SHOW_LOG && !message_logs)
@@ -1030,13 +1065,12 @@ 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. */
 
 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;
     printf("Spool data file for %s does not exist\n", id);
   if (errno == ENOENT)
     {
     yield = FALSE;
     printf("Spool data file for %s does not exist\n", id);
-    if (action != MSG_REMOVE || !admin_user) return FALSE;
+    if (action != MSG_REMOVE || !f.admin_user) return FALSE;
     printf("Continuing, to ensure all files removed\n");
     }
   else
     printf("Continuing, to ensure all files removed\n");
     }
   else
@@ -1046,7 +1080,6 @@ if (!spool_open_datafile(id))
         strerror(errno));
     return FALSE;
     }
         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
 
 /* 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
@@ -1062,7 +1095,7 @@ if (spool_read_header(spoolname, TRUE, FALSE) != spool_read_OK)
     printf("Spool read error for %s: %s\n", spoolname, strerror(errno));
   else
     printf("Spool format error for %s\n", spoolname);
     printf("Spool read error for %s: %s\n", spoolname, strerror(errno));
   else
     printf("Spool format error for %s\n", spoolname);
-  if (action != MSG_REMOVE || !admin_user)
+  if (action != MSG_REMOVE || !f.admin_user)
     {
     (void)close(deliver_datafile);
     deliver_datafile = -1;
     {
     (void)close(deliver_datafile);
     deliver_datafile = -1;
@@ -1076,7 +1109,7 @@ message. Only admin users may freeze/thaw, add/cancel recipients, or otherwise
 mess about, but the original sender is permitted to remove a message. That's
 why we leave this check until after the headers are read. */
 
 mess about, but the original sender is permitted to remove a message. That's
 why we leave this check until after the headers are read. */
 
-if (!admin_user && (action != MSG_REMOVE || real_uid != originator_uid))
+if (!f.admin_user && (action != MSG_REMOVE || real_uid != originator_uid))
   {
   printf("Permission denied\n");
   (void)close(deliver_datafile);
   {
   printf("Permission denied\n");
   (void)close(deliver_datafile);
@@ -1097,22 +1130,26 @@ if (action != MSG_SHOW_COPY) printf("Message %s ", id);
 switch(action)
   {
   case MSG_SHOW_COPY:
 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);
-  break;
+    {
+    transport_ctx tctx = {{0}};
+    deliver_in_buffer = store_malloc(DELIVER_IN_BUFFER_SIZE);
+    deliver_out_buffer = store_malloc(DELIVER_OUT_BUFFER_SIZE);
+    tctx.u.fd = 1;
+    (void) transport_write_message(&tctx, 0);
+    break;
+    }
 
 
   case MSG_FREEZE:
 
 
   case MSG_FREEZE:
-  if (deliver_freeze)
+  if (f.deliver_freeze)
     {
     yield = FALSE;
     printf("is already frozen\n");
     }
   else
     {
     {
     yield = FALSE;
     printf("is already frozen\n");
     }
   else
     {
-    deliver_freeze = TRUE;
-    deliver_manual_thaw = FALSE;
+    f.deliver_freeze = TRUE;
+    f.deliver_manual_thaw = FALSE;
     deliver_frozen_at = time(NULL);
     if (spool_write_header(id, SW_MODIFYING, &errmsg) >= 0)
       {
     deliver_frozen_at = time(NULL);
     if (spool_write_header(id, SW_MODIFYING, &errmsg) >= 0)
       {
@@ -1129,15 +1166,15 @@ switch(action)
 
 
   case MSG_THAW:
 
 
   case MSG_THAW:
-  if (!deliver_freeze)
+  if (!f.deliver_freeze)
     {
     yield = FALSE;
     printf("is not frozen\n");
     }
   else
     {
     {
     yield = FALSE;
     printf("is not frozen\n");
     }
   else
     {
-    deliver_freeze = FALSE;
-    deliver_manual_thaw = TRUE;
+    f.deliver_freeze = FALSE;
+    f.deliver_manual_thaw = TRUE;
     if (spool_write_header(id, SW_MODIFYING, &errmsg) >= 0)
       {
       printf("is no longer frozen\n");
     if (spool_write_header(id, SW_MODIFYING, &errmsg) >= 0)
       {
       printf("is no longer frozen\n");
@@ -1159,56 +1196,108 @@ switch(action)
   operation, just run everything twice. */
 
   case MSG_REMOVE:
   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];
+
+    suffix[0] = '-';
+    suffix[2] = 0;
+    message_subdir[0] = id[5];
 
 
-    for (i = 0; i < 3; i++)
+    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)
+      {
+#ifndef DISABLE_EVENT
+      for (i = 0; i < recipients_count; i++)
+       {
+       tree_node *delivered =
+         tree_search(tree_nonrecipients, recipients_list[i].address);
+       if (!delivered)
+         {
+         uschar * save_local = deliver_localpart;
+         const uschar * save_domain = deliver_domain;
+         uschar * addr = recipients_list[i].address, * errmsg = NULL;
+         int start, end, dom;
+
+         if (!parse_extract_address(addr, &errmsg, &start, &end, &dom, TRUE))
+           log_write(0, LOG_MAIN|LOG_PANIC,
+             "failed to parse address '%.100s'\n: %s", addr, errmsg);
+         else
+           {
+           deliver_localpart =
+             string_copyn(addr+start, dom ? (dom-1) - start : end - start);
+           deliver_domain = dom
+             ? CUS string_copyn(addr+dom, end - dom) : CUS"";
+
+           event_raise(event_action, US"msg:fail:internal",
+             string_sprintf("message removed by %s", username));
+
+           deliver_localpart = save_local;
+           deliver_domain = save_domain;
+           }
+         }
+       }
+      (void) event_raise(event_action, US"msg:complete", NULL);
+#endif
+      log_write(0, LOG_MAIN, "removed by %s", username);
+      log_write(0, LOG_MAIN, "Completed");
+      }
+    break;
     }
     }
-  break;
 
 
   case MSG_MARK_ALL_DELIVERED:
   for (i = 0; i < recipients_count; i++)
 
 
   case MSG_MARK_ALL_DELIVERED:
   for (i = 0; i < recipients_count; i++)
-    {
     tree_add_nonrecipient(recipients_list[i].address);
     tree_add_nonrecipient(recipients_list[i].address);
-    }
+
   if (spool_write_header(id, SW_MODIFYING, &errmsg) >= 0)
     {
     printf("has been modified\n");
   if (spool_write_header(id, SW_MODIFYING, &errmsg) >= 0)
     {
     printf("has been modified\n");
@@ -1274,6 +1363,9 @@ switch(action)
       {
       if (action == MSG_ADD_RECIPIENT)
         {
       {
       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);
         receive_add_recipient(recipient, -1);
         log_write(0, LOG_MAIN, "recipient <%s> added by %s",
           recipient, username);
@@ -1297,6 +1389,9 @@ switch(action)
         }
       else  /* MSG_EDIT_SENDER */
         {
         }
       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);
         sender_address = recipient;
         log_write(0, LOG_MAIN, "sender address changed to <%s> by %s",
           recipient, username);
@@ -1305,7 +1400,6 @@ switch(action)
     }
 
   if (yield)
     }
 
   if (yield)
-    {
     if (spool_write_header(id, SW_MODIFYING, &errmsg) >= 0)
       printf("has been modified\n");
     else
     if (spool_write_header(id, SW_MODIFYING, &errmsg) >= 0)
       printf("has been modified\n");
     else
@@ -1313,7 +1407,6 @@ switch(action)
       yield = FALSE;
       printf("- while %s: %s\n", doing, errmsg);
       }
       yield = FALSE;
       printf("- while %s: %s\n", doing, errmsg);
       }
-    }
 
   break;
   }
 
   break;
   }
@@ -1321,8 +1414,11 @@ switch(action)
 /* Closing the datafile releases the lock and permits other processes
 to operate on the message (if it still exists). */
 
 /* 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;
 }
 
 return yield;
 }
 
@@ -1342,10 +1438,10 @@ Returns:    nothing
 void
 queue_check_only(void)
 {
 void
 queue_check_only(void)
 {
-BOOL *set;
 int sep = 0;
 struct stat statbuf;
 int sep = 0;
 struct stat statbuf;
-uschar *s, *ss, *name;
+const uschar *s;
+uschar *ss;
 uschar buffer[1024];
 
 if (queue_only_file == NULL) return;
 uschar buffer[1024];
 
 if (queue_only_file == NULL) return;
@@ -1355,22 +1451,24 @@ while ((ss = string_nextinlist(&s, &sep, buffer, sizeof(buffer))) != NULL)
   {
   if (Ustrncmp(ss, "smtp", 4) == 0)
     {
   {
   if (Ustrncmp(ss, "smtp", 4) == 0)
     {
-    name = US"queue_smtp";
-    set = &queue_smtp;
     ss += 4;
     ss += 4;
+    if (Ustat(ss, &statbuf) == 0)
+      {
+      f.queue_smtp = TRUE;
+      DEBUG(D_receive) debug_printf("queue_smtp set because %s exists\n", ss);
+      }
     }
   else
     {
     }
   else
     {
-    name = US"queue_only";
-    set = &queue_only;
-    }
-
-  if (Ustat(ss, &statbuf) == 0)
-    {
-    *set = TRUE;
-    DEBUG(D_receive) debug_printf("%s set because %s exists\n", name, ss);
+    if (Ustat(ss, &statbuf) == 0)
+      {
+      queue_only = TRUE;
+      DEBUG(D_receive) debug_printf("queue_only set because %s exists\n", ss);
+      }
     }
   }
 }
 
     }
   }
 }
 
+#endif /*!COMPILE_UTILITY*/
+
 /* End of queue.c */
 /* End of queue.c */
index f915ce7..13f5709 100644 (file)
--- a/src/rda.c
+++ b/src/rda.c
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* This module contains code for extracting addresses from a forwarding list
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* This module contains code for extracting addresses from a forwarding list
@@ -110,7 +110,7 @@ if (saved_errno == ENOENT)
   slash = Ustrrchr(big_buffer, '/');
   Ustrcpy(slash+1, ".");
 
   slash = Ustrrchr(big_buffer, '/');
   Ustrcpy(slash+1, ".");
 
-  alarm(30);
+  ALARM(30);
   rc = Ustat(big_buffer, &statbuf);
   if (rc != 0 && errno == EACCES && !sigalrm_seen)
     {
   rc = Ustat(big_buffer, &statbuf);
   if (rc != 0 && errno == EACCES && !sigalrm_seen)
     {
@@ -118,7 +118,7 @@ if (saved_errno == ENOENT)
     rc = Ustat(big_buffer, &statbuf);
     }
   saved_errno = errno;
     rc = Ustat(big_buffer, &statbuf);
     }
   saved_errno = errno;
-  alarm(0);
+  ALARM_CLR(0);
 
   DEBUG(D_route) debug_printf("stat(%s)=%d\n", big_buffer, rc);
   }
 
   DEBUG(D_route) debug_printf("stat(%s)=%d\n", big_buffer, rc);
   }
@@ -358,7 +358,7 @@ if (rdata->isfile)
   }
 else data = rdata->string;
 
   }
 else data = rdata->string;
 
-*filtertype = system_filtering? FILTER_EXIM : rda_is_filter(data);
+*filtertype = f.system_filtering ? FILTER_EXIM : rda_is_filter(data);
 
 /* Filter interpretation is done by a general function that is also called from
 the filter testing option (-bf). There are two versions: one for Exim filtering
 
 /* Filter interpretation is done by a general function that is also called from
 the filter testing option (-bf). There are two versions: one for Exim filtering
@@ -444,7 +444,7 @@ Returns:     -1 on error, else 0
 */
 
 static int
 */
 
 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)
 {
 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;
 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;
 }
 
 return TRUE;
 }
 
@@ -491,7 +492,7 @@ return TRUE;
 /* This function is passed a forward list string (unexpanded) or the name of a
 file (unexpanded) whose contents are the forwarding list. The list may in fact
 be a filter program if it starts with "#Exim filter" or "#Sieve filter". Other
 /* This function is passed a forward list string (unexpanded) or the name of a
 file (unexpanded) whose contents are the forwarding list. The list may in fact
 be a filter program if it starts with "#Exim filter" or "#Sieve filter". Other
-types of filter, with different inital tag strings, may be introduced in due
+types of filter, with different initial tag strings, may be introduced in due
 course.
 
 The job of the function is to process the forwarding list or filter. It is
 course.
 
 The job of the function is to process the forwarding list or filter. It is
@@ -565,7 +566,7 @@ DEBUG(D_route) debug_printf("rda_interpret (%s): %s\n",
 data = expand_string(rdata->string);
 if (data == NULL)
   {
 data = expand_string(rdata->string);
 if (data == NULL)
   {
-  if (expand_string_forcedfail) return FF_NOTDELIVERED;
+  if (f.expand_string_forcedfail) return FF_NOTDELIVERED;
   *error = string_sprintf("failed to expand \"%s\": %s", rdata->string,
     expand_string_message);
   return FF_ERROR;
   *error = string_sprintf("failed to expand \"%s\": %s", rdata->string,
     expand_string_message);
   return FF_ERROR;
@@ -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");
     {
     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 */
     }
 
   /* Now do the business */
@@ -671,7 +672,7 @@ if ((pid = fork()) == 0)
   original header lines that were removed, and then any header lines that were
   added but not subsequently removed. */
 
   original header lines that were removed, and then any header lines that were
   added but not subsequently removed. */
 
-  if (system_filtering)
+  if (f.system_filtering)
     {
     int i = 0;
     header_line *h;
     {
     int i = 0;
     header_line *h;
@@ -714,30 +715,30 @@ if ((pid = fork()) == 0)
       yield == FF_FAIL || yield == FF_FREEZE)
     {
     address_item *addr;
       yield == FF_FAIL || yield == FF_FREEZE)
     {
     address_item *addr;
-    for (addr = *generated; addr != NULL; addr = addr->next)
+    for (addr = *generated; addr; addr = addr->next)
       {
       int reply_options = 0;
       {
       int reply_options = 0;
+      int ig_err = addr->prop.ignore_error ? 1 : 0;
 
       if (  rda_write_string(fd, addr->address) != 0
 
       if (  rda_write_string(fd, addr->address) != 0
-         || write(fd, &(addr->mode), sizeof(addr->mode))
-           != sizeof(addr->mode)
-         || write(fd, &(addr->flags), sizeof(addr->flags))
-           != sizeof(addr->flags)
-         || rda_write_string(fd, addr->p.errors_address) != 0
+         || write(fd, &addr->mode, sizeof(addr->mode)) != sizeof(addr->mode)
+         || write(fd, &addr->flags, sizeof(addr->flags)) != sizeof(addr->flags)
+         || rda_write_string(fd, addr->prop.errors_address) != 0
+         || write(fd, &ig_err, sizeof(ig_err)) != sizeof(ig_err)
         )
        goto bad;
 
         )
        goto bad;
 
-      if (addr->pipe_expandn != NULL)
+      if (addr->pipe_expandn)
         {
         uschar **pp;
         {
         uschar **pp;
-        for (pp = addr->pipe_expandn; *pp != NULL; pp++)
+        for (pp = addr->pipe_expandn; *pp; pp++)
           if (rda_write_string(fd, *pp) != 0)
            goto bad;
         }
       if (rda_write_string(fd, NULL) != 0)
         goto bad;
 
           if (rda_write_string(fd, *pp) != 0)
            goto bad;
         }
       if (rda_write_string(fd, NULL) != 0)
         goto bad;
 
-      if (addr->reply == NULL)
+      if (!addr->reply)
        {
         if (write(fd, &reply_options, sizeof(int)) != sizeof(int))    /* 0 means no reply */
          goto bad;
        {
         if (write(fd, &reply_options, sizeof(int)) != sizeof(int))    /* 0 means no reply */
          goto bad;
@@ -805,29 +806,28 @@ if (read(fd, filtertype, sizeof(int)) != sizeof(int) ||
 
 /* Read the contents of any syntax error blocks if we have a pointer */
 
 
 /* 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 *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 (!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;
     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);
     }
   }
 
 /* If this is a system filter, read the identify of any original header lines
 that were removed, and then read data for any new ones that were added. */
 
     }
   }
 
 /* If this is a system filter, read the identify of any original header lines
 that were removed, and then read data for any new ones that were added. */
 
-if (system_filtering)
+if (f.system_filtering)
   {
   int hn = 0;
   header_line *h = header_list;
   {
   int hn = 0;
   header_line *h = header_list;
@@ -840,8 +840,7 @@ if (system_filtering)
     while (hn < n)
       {
       hn++;
     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;
     }
       }
     h->type = htype_old;
     }
@@ -851,7 +850,7 @@ if (system_filtering)
     uschar *s;
     int type;
     if (!rda_read_string(fd, &s)) goto DISASTER;
     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);
     }
     if (read(fd, &type, sizeof(type)) != sizeof(type)) goto DISASTER;
     header_add(type, "%s", s);
     }
@@ -890,9 +889,13 @@ if (yield == FF_DELIVERED || yield == FF_NOTDELIVERED ||
 
     /* Next comes the mode and the flags fields */
 
 
     /* Next comes the mode and the flags fields */
 
-    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;
+    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->prop.errors_address)
+       || read(fd, &i, sizeof(i)) != sizeof(i)
+       )
+      goto DISASTER;
+    addr->prop.ignore_error = (i != 0);
 
     /* Next comes a possible setting for $thisaddress and any numerical
     variables for pipe expansion, terminated by a NULL string. The maximum
 
     /* Next comes a possible setting for $thisaddress and any numerical
     variables for pipe expansion, terminated by a NULL string. The maximum
@@ -911,7 +914,7 @@ if (yield == FF_DELIVERED || yield == FF_NOTDELIVERED ||
 
     if (i > 0)
       {
 
     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];
       }
       addr->pipe_expandn[i] = NULL;
       while (--i >= 0) addr->pipe_expandn[i] = expandn[i];
       }
index 6e33034..5742d10 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for reading the configuration file, and for displaying
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for reading the configuration file, and for displaying
@@ -11,116 +11,15 @@ implementation of the conditional .ifdef etc. */
 
 #include "exim.h"
 
 
 #include "exim.h"
 
-extern char **environ;
-
-
-#define CSTATE_STACK_SIZE 10
-
-
-/* Structure for chain (stack) of .included files */
-
-typedef struct config_file_item {
-  struct config_file_item *next;
-  uschar *filename;
-  FILE *file;
-  int lineno;
-} config_file_item;
-
-/* Structure of table of conditional words and their state transitions */
-
-typedef struct cond_item {
-  uschar *name;
-  int    namelen;
-  int    action1;
-  int    action2;
-  int    pushpop;
-} cond_item;
-
-/* Structure of table of syslog facility names and values */
-
-typedef struct syslog_fac_item {
-  uschar *name;
-  int    value;
-} syslog_fac_item;
-
-
-/* Static variables */
-
-static config_file_item *config_file_stack = NULL;  /* For includes */
-
-static uschar *syslog_facility_str  = NULL;
-static uschar next_section[24];
-static uschar time_buffer[24];
-
-/* State variables for conditional loading (.ifdef / .else / .endif) */
-
-static int cstate = 0;
-static int cstate_stack_ptr = -1;
-static int cstate_stack[CSTATE_STACK_SIZE];
-
-/* Table of state transitions for handling conditional inclusions. There are
-four possible state transitions:
-
-  .ifdef true
-  .ifdef false
-  .elifdef true  (or .else)
-  .elifdef false
-
-.endif just causes the previous cstate to be popped off the stack */
-
-static int next_cstate[3][4] =
-  {
-  /* State 0: reading from file, or reading until next .else or .endif */
-  { 0, 1, 2, 2 },
-  /* State 1: condition failed, skipping until next .else or .endif */
-  { 2, 2, 0, 1 },
-  /* State 2: skipping until .endif */
-  { 2, 2, 2, 2 },
-  };
-
-/* Table of conditionals and the states to set. For each name, there are four
-values: the length of the name (to save computing it each time), the state to
-set if a macro was found in the line, the state to set if a macro was not found
-in the line, and a stack manipulation setting which is:
-
-  -1   pull state value off the stack
-   0   don't alter the stack
-  +1   push value onto stack, before setting new state
-*/
-
-static cond_item cond_list[] = {
-  { US"ifdef",    5, 0, 1,  1 },
-  { US"ifndef",   6, 1, 0,  1 },
-  { US"elifdef",  7, 2, 3,  0 },
-  { US"elifndef", 8, 3, 2,  0 },
-  { US"else",     4, 2, 2,  0 },
-  { US"endif",    5, 0, 0, -1 }
-};
-
-static int cond_list_size = sizeof(cond_list)/sizeof(cond_item);
-
-/* Table of syslog facility names and their values */
-
-static syslog_fac_item syslog_list[] = {
-  { US"mail",   LOG_MAIL },
-  { US"user",   LOG_USER },
-  { US"news",   LOG_NEWS },
-  { US"uucp",   LOG_UUCP },
-  { US"local0", LOG_LOCAL0 },
-  { US"local1", LOG_LOCAL1 },
-  { US"local2", LOG_LOCAL2 },
-  { US"local3", LOG_LOCAL3 },
-  { US"local4", LOG_LOCAL4 },
-  { US"local5", LOG_LOCAL5 },
-  { US"local6", LOG_LOCAL6 },
-  { US"local7", LOG_LOCAL7 },
-  { US"daemon", LOG_DAEMON }
-};
-
-static int syslog_list_size = sizeof(syslog_list)/sizeof(syslog_fac_item);
+#ifdef MACRO_PREDEF
+# include "macro_predef.h"
+#endif
 
 
+#define READCONF_DEBUG if (FALSE)      /* Change to TRUE to enable */
 
 
 
 
+static uschar * syslog_facility_str;
+static void fn_smtp_receive_timeout(const uschar *, const uschar *);
 
 /*************************************************
 *           Main configuration options           *
 
 /*************************************************
 *           Main configuration options           *
@@ -182,6 +81,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_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 },
   { "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 },
@@ -195,6 +95,8 @@ 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 },
   { "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 },
+  { "commandline_checks_require_admin", opt_bool,&commandline_checks_require_admin },
   { "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 },
   { "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 },
@@ -204,6 +106,7 @@ static optionlist optionlist_config[] = {
   { "dccifd_address",           opt_stringptr,   &dccifd_address },
   { "dccifd_options",           opt_stringptr,   &dccifd_options },
 #endif
   { "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 },
   { "delay_warning",            opt_timelist,    &delay_warning },
   { "delay_warning_condition",  opt_stringptr,   &delay_warning_condition },
   { "deliver_drop_privilege",   opt_bool,        &deliver_drop_privilege },
@@ -223,23 +126,26 @@ static optionlist optionlist_config[] = {
 #endif
   { "dns_again_means_nonexist", opt_stringptr,   &dns_again_means_nonexist },
   { "dns_check_names_pattern",  opt_stringptr,   &check_dns_names_pattern },
 #endif
   { "dns_again_means_nonexist", opt_stringptr,   &dns_again_means_nonexist },
   { "dns_check_names_pattern",  opt_stringptr,   &check_dns_names_pattern },
+  { "dns_cname_loops",         opt_int,         &dns_cname_loops },
   { "dns_csa_search_limit",     opt_int,         &dns_csa_search_limit },
   { "dns_csa_use_reverse",      opt_bool,        &dns_csa_use_reverse },
   { "dns_dnssec_ok",            opt_int,         &dns_dnssec_ok },
   { "dns_ipv4_lookup",          opt_stringptr,   &dns_ipv4_lookup },
   { "dns_retrans",              opt_time,        &dns_retrans },
   { "dns_retry",                opt_int,         &dns_retry },
   { "dns_csa_search_limit",     opt_int,         &dns_csa_search_limit },
   { "dns_csa_use_reverse",      opt_bool,        &dns_csa_use_reverse },
   { "dns_dnssec_ok",            opt_int,         &dns_dnssec_ok },
   { "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 },
   { "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 },
 /*********************************************************/
   { "drop_cr",                  opt_bool,        &drop_cr },
 /*********************************************************/
-#ifdef EXPERIMENTAL_DSN
   { "dsn_advertise_hosts",      opt_stringptr,   &dsn_advertise_hosts },
   { "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 },
   { "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 },
   { "exim_group",               opt_gid,         &exim_gid },
   { "exim_path",                opt_stringptr,   &exim_path },
   { "exim_user",                opt_uid,         &exim_uid },
@@ -252,11 +158,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 },
 #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 },
 #endif
   { "header_line_maxsize",      opt_int,         &header_line_maxsize },
   { "header_maxsize",           opt_int,         &header_maxsize },
@@ -271,6 +172,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 },
   { "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 },
   { "hosts_treat_as_local",     opt_stringptr,   &hosts_treat_as_local },
 #ifdef LOOKUP_IBASE
   { "ibase_servers",            opt_stringptr,   &ibase_servers },
@@ -295,7 +199,9 @@ static optionlist optionlist_config[] = {
   { "local_from_prefix",        opt_stringptr,   &local_from_prefix },
   { "local_from_suffix",        opt_stringptr,   &local_from_suffix },
   { "local_interfaces",         opt_stringptr,   &local_interfaces },
   { "local_from_prefix",        opt_stringptr,   &local_from_prefix },
   { "local_from_suffix",        opt_stringptr,   &local_from_suffix },
   { "local_interfaces",         opt_stringptr,   &local_interfaces },
+#ifdef HAVE_LOCAL_SCAN
   { "local_scan_timeout",       opt_time,        &local_scan_timeout },
   { "local_scan_timeout",       opt_time,        &local_scan_timeout },
+#endif
   { "local_sender_retain",      opt_bool,        &local_sender_retain },
   { "localhost_number",         opt_stringptr,   &host_number_string },
   { "log_file_path",            opt_stringptr,   &log_file_path },
   { "local_sender_retain",      opt_bool,        &local_sender_retain },
   { "localhost_number",         opt_stringptr,   &host_number_string },
   { "log_file_path",            opt_stringptr,   &log_file_path },
@@ -327,12 +233,17 @@ static optionlist optionlist_config[] = {
 #ifdef EXIM_PERL
   { "perl_at_start",            opt_bool,        &opt_perl_at_start },
   { "perl_startup",             opt_stringptr,   &opt_perl_startup },
 #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 },
 #endif
   { "pid_file_path",            opt_stringptr,   &pid_file_path },
   { "pipelining_advertise_hosts", opt_stringptr, &pipelining_advertise_hosts },
 #endif
 #ifdef LOOKUP_PGSQL
   { "pgsql_servers",            opt_stringptr,   &pgsql_servers },
 #endif
   { "pid_file_path",            opt_stringptr,   &pid_file_path },
   { "pipelining_advertise_hosts", opt_stringptr, &pipelining_advertise_hosts },
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+  { "pipelining_connect_advertise_hosts", opt_stringptr,
+                                                &pipe_connect_advertise_hosts },
+#endif
 #ifndef DISABLE_PRDR
   { "prdr_enable",              opt_bool,        &prdr_enable },
 #endif
 #ifndef DISABLE_PRDR
   { "prdr_enable",              opt_bool,        &prdr_enable },
 #endif
@@ -341,9 +252,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 },
   { "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 },
   { "qualify_domain",           opt_stringptr,   &qualify_domain_sender },
   { "qualify_recipient",        opt_stringptr,   &qualify_domain_recipient },
   { "queue_domains",            opt_stringptr,   &queue_domains },
@@ -354,7 +262,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_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 },
   { "queue_smtp_domains",       opt_stringptr,   &queue_smtp_domains },
   { "receive_timeout",          opt_time,        &receive_timeout },
   { "received_header_text",     opt_stringptr,   &received_header_text },
@@ -362,7 +270,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 },
   { "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 },
   { "redis_servers",            opt_stringptr,   &redis_servers },
 #endif
   { "remote_max_parallel",      opt_int,         &remote_max_parallel },
@@ -374,6 +282,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 },
   { "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 },
   { "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 },
@@ -396,17 +305,21 @@ 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_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 },
   { "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
 #ifdef WITH_CONTENT_SCAN
   { "spamd_address",            opt_stringptr,   &spamd_address },
 #endif
-#ifdef EXPERIMENTAL_SPF
+#ifdef SUPPORT_SPF
   { "spf_guess",                opt_stringptr,   &spf_guess },
 #endif
   { "split_spool_directory",    opt_bool,        &split_spool_directory },
   { "spool_directory",          opt_stringptr,   &spool_directory },
   { "spf_guess",                opt_stringptr,   &spf_guess },
 #endif
   { "split_spool_directory",    opt_bool,        &split_spool_directory },
   { "spool_directory",          opt_stringptr,   &spool_directory },
+  { "spool_wireformat",         opt_bool,        &spool_wireformat },
 #ifdef LOOKUP_SQLITE
   { "sqlite_lock_timeout",      opt_int,         &sqlite_lock_timeout },
 #endif
 #ifdef LOOKUP_SQLITE
   { "sqlite_lock_timeout",      opt_int,         &sqlite_lock_timeout },
 #endif
@@ -424,6 +337,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 },
   { "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 },
   { "syslog_processname",       opt_stringptr,   &syslog_processname },
   { "syslog_timestamp",         opt_bool,        &syslog_timestamp },
   { "system_filter",            opt_stringptr,   &system_filter },
@@ -439,12 +353,16 @@ static optionlist optionlist_config[] = {
 #endif
   { "timeout_frozen_after",     opt_time,        &timeout_frozen_after },
   { "timezone",                 opt_stringptr,   &timezone_string },
 #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 },
   { "tls_advertise_hosts",      opt_stringptr,   &tls_advertise_hosts },
+#ifdef SUPPORT_TLS
+# ifdef EXPERIMENTAL_REQUIRETLS
+  { "tls_advertise_requiretls", opt_stringptr,   &tls_advertise_requiretls },
+# endif
   { "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_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
 # ifndef DISABLE_OCSP
   { "tls_ocsp_file",            opt_stringptr,   &tls_ocsp_file },
 # endif
@@ -467,8 +385,179 @@ static optionlist optionlist_config[] = {
   { "write_rejectlog",          opt_bool,        &write_rejectlog }
 };
 
   { "write_rejectlog",          opt_bool,        &write_rejectlog }
 };
 
-static int optionlist_config_size =
-  sizeof(optionlist_config)/sizeof(optionlist);
+#ifndef MACRO_PREDEF
+static int optionlist_config_size = nelem(optionlist_config);
+#endif
+
+
+#ifdef MACRO_PREDEF
+
+static void fn_smtp_receive_timeout(const uschar * name, const uschar * str) {/*Dummy*/}
+
+void
+options_main(void)
+{
+options_from_list(optionlist_config, nelem(optionlist_config), US"MAIN", NULL);
+}
+
+void
+options_auths(void)
+{
+struct auth_info * ai;
+uschar buf[64];
+
+options_from_list(optionlist_auths, optionlist_auths_size, US"AUTHENTICATORS", NULL);
+
+for (ai = auths_available; ai->driver_name[0]; ai++)
+  {
+  spf(buf, sizeof(buf), US"_DRIVER_AUTHENTICATOR_%T", ai->driver_name);
+  builtin_macro_create(buf);
+  options_from_list(ai->options, (unsigned)*ai->options_count, US"AUTHENTICATOR", ai->driver_name);
+  }
+}
+
+void
+options_logging(void)
+{
+bit_table * bp;
+uschar buf[64];
+
+for (bp = log_options; bp < log_options + log_options_count; bp++)
+  {
+  spf(buf, sizeof(buf), US"_LOG_%T", bp->name);
+  builtin_macro_create(buf);
+  }
+}
+
+
+#else  /*!MACRO_PREDEF*/
+
+extern char **environ;
+
+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);
+
+
+#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;
+  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 {
+  uschar *name;
+  int    namelen;
+  int    action1;
+  int    action2;
+  int    pushpop;
+} cond_item;
+
+/* Structure of table of syslog facility names and values */
+
+typedef struct syslog_fac_item {
+  uschar *name;
+  int    value;
+} syslog_fac_item;
+
+/* constants */
+static const char * const hidden = "<value not displayable>";
+
+/* Static variables */
+
+static config_file_item *config_file_stack = NULL;  /* For includes */
+
+static uschar *syslog_facility_str  = NULL;
+static uschar next_section[24];
+static uschar time_buffer[24];
+
+/* State variables for conditional loading (.ifdef / .else / .endif) */
+
+static int cstate = 0;
+static int cstate_stack_ptr = -1;
+static int cstate_stack[CSTATE_STACK_SIZE];
+
+/* Table of state transitions for handling conditional inclusions. There are
+four possible state transitions:
+
+  .ifdef true
+  .ifdef false
+  .elifdef true  (or .else)
+  .elifdef false
+
+.endif just causes the previous cstate to be popped off the stack */
+
+static int next_cstate[3][4] =
+  {
+  /* State 0: reading from file, or reading until next .else or .endif */
+  { 0, 1, 2, 2 },
+  /* State 1: condition failed, skipping until next .else or .endif */
+  { 2, 2, 0, 1 },
+  /* State 2: skipping until .endif */
+  { 2, 2, 2, 2 },
+  };
+
+/* Table of conditionals and the states to set. For each name, there are four
+values: the length of the name (to save computing it each time), the state to
+set if a macro was found in the line, the state to set if a macro was not found
+in the line, and a stack manipulation setting which is:
+
+  -1   pull state value off the stack
+   0   don't alter the stack
+  +1   push value onto stack, before setting new state
+*/
+
+static cond_item cond_list[] = {
+  { US"ifdef",    5, 0, 1,  1 },
+  { US"ifndef",   6, 1, 0,  1 },
+  { US"elifdef",  7, 2, 3,  0 },
+  { US"elifndef", 8, 3, 2,  0 },
+  { US"else",     4, 2, 2,  0 },
+  { US"endif",    5, 0, 0, -1 }
+};
+
+static int cond_list_size = sizeof(cond_list)/sizeof(cond_item);
+
+/* Table of syslog facility names and their values */
+
+static syslog_fac_item syslog_list[] = {
+  { US"mail",   LOG_MAIL },
+  { US"user",   LOG_USER },
+  { US"news",   LOG_NEWS },
+  { US"uucp",   LOG_UUCP },
+  { US"local0", LOG_LOCAL0 },
+  { US"local1", LOG_LOCAL1 },
+  { US"local2", LOG_LOCAL2 },
+  { US"local3", LOG_LOCAL3 },
+  { US"local4", LOG_LOCAL4 },
+  { US"local5", LOG_LOCAL5 },
+  { US"local6", LOG_LOCAL6 },
+  { US"local7", LOG_LOCAL7 },
+  { US"daemon", LOG_DAEMON }
+};
+
+static int syslog_list_size = sizeof(syslog_list)/sizeof(syslog_fac_item);
+
 
 
 
 
 
 
@@ -493,28 +582,33 @@ int i;
 router_instance *r;
 transport_instance *t;
 
 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;
 
   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;
   {
   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 ((ri->options[i].type & opt_mask) != opt_stringptr) continue;
-    if (p == (char *)(r->options_block) + (long int)(ri->options[i].value))
+    if (p == CS (r->options_block) + (long int)(ri->options[i].value))
       return US ri->options[i].name;
     }
   }
 
       return US ri->options[i].name;
     }
   }
 
-for (t = transports; t != NULL; t = t->next)
+for (t = transports; t; t = t->next)
   {
   transport_info *ti = t->info;
   {
   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
+            ? CS t
+            : CS t->options_block
+            )
+            + (long int)op->value)
+       return US op->name;
     }
   }
 
     }
   }
 
@@ -528,6 +622,36 @@ return US"";
 *       Deal with an assignment to a macro       *
 *************************************************/
 
 *       Deal with an assignment to a macro       *
 *************************************************/
 
+/* We have a new definition; append to the list.
+
+Args:
+ name  Name of the macro; will be copied
+ val   Expansion result for the macro; will be copied
+*/
+
+macro_item *
+macro_create(const uschar * name, const uschar * val, BOOL command_line)
+{
+macro_item * m = store_get(sizeof(macro_item));
+
+READCONF_DEBUG fprintf(stderr, "%s: '%s' '%s'\n", __FUNCTION__, name, val);
+m->next = NULL;
+m->command_line = command_line;
+m->namelen = Ustrlen(name);
+m->replen = Ustrlen(val);
+m->name = string_copy(name);
+m->replacement = string_copy(val);
+if (mlast)
+  mlast->next = m;
+else
+  macros = m;
+mlast = m;
+if (!macros_user)
+  macros_user = m;
+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
 /* 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
@@ -537,30 +661,35 @@ non-command line, macros is permitted using '==' instead of '='.
 Arguments:
   s            points to the start of the logical line
 
 Arguments:
   s            points to the start of the logical line
 
-Returns:       nothing
+Returns:       FALSE iff fatal error
 */
 
 */
 
-static void
-read_macro_assignment(uschar *s)
+BOOL
+macro_read_assignment(uschar *s)
 {
 uschar name[64];
 int namelen = 0;
 BOOL redef = FALSE;
 macro_item *m;
 {
 uschar name[64];
 int namelen = 0;
 BOOL redef = FALSE;
 macro_item *m;
-macro_item *mlast = NULL;
 
 while (isalnum(*s) || *s == '_')
   {
   if (namelen >= sizeof(name) - 1)
 
 while (isalnum(*s) || *s == '_')
   {
   if (namelen >= sizeof(name) - 1)
-    log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
+    {
+    log_write(0, LOG_PANIC|LOG_CONFIG_IN,
       "macro name too long (maximum is " SIZE_T_FMT " characters)", sizeof(name) - 1);
       "macro name too long (maximum is " SIZE_T_FMT " characters)", sizeof(name) - 1);
+    return FALSE;
+    }
   name[namelen++] = *s++;
   }
 name[namelen] = 0;
 
 while (isspace(*s)) s++;
 if (*s++ != '=')
   name[namelen++] = *s++;
   }
 name[namelen] = 0;
 
 while (isspace(*s)) s++;
 if (*s++ != '=')
-  log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "malformed macro definition");
+  {
+  log_write(0, LOG_PANIC|LOG_CONFIG_IN, "malformed macro definition");
+  return FALSE;
+  }
 
 if (*s == '=')
   {
 
 if (*s == '=')
   {
@@ -573,73 +702,172 @@ 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
 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)
   if (Ustrcmp(m->name, name) == 0)
     {
     if (!m->command_line && !redef)
-      log_write(0, LOG_CONFIG|LOG_PANIC_DIE, "macro \"%s\" is already "
-       "defined (use \"==\" if you want to redefine it", name);
+      {
+      log_write(0, LOG_CONFIG|LOG_PANIC, "macro \"%s\" is already "
+       "defined (use \"==\" if you want to redefine it)", name);
+      return FALSE;
+      }
     break;
     }
 
     break;
     }
 
-  if (len < namelen && Ustrstr(name, m->name) != NULL)
-    log_write(0, LOG_CONFIG|LOG_PANIC_DIE, "\"%s\" cannot be defined as "
+  if (m->namelen < namelen && Ustrstr(name, m->name) != NULL)
+    {
+    log_write(0, LOG_CONFIG|LOG_PANIC, "\"%s\" cannot be defined as "
       "a macro because previously defined macro \"%s\" is a substring",
       name, m->name);
       "a macro because previously defined macro \"%s\" is a substring",
       name, m->name);
+    return FALSE;
+    }
 
   /* We cannot have this test, because it is documented that a substring
   macro is permitted (there is even an example).
   *
 
   /* 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);
   */
   *   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. */
 
   }
 
 /* Check for an overriding command-line definition. */
 
-if (m != NULL && m->command_line) return;
+if (m && m->command_line) return TRUE;
 
 /* Redefinition must refer to an existing macro. */
 
 if (redef)
 
 /* Redefinition must refer to an existing macro. */
 
 if (redef)
-  {
-  if (m == NULL)
-    log_write(0, LOG_CONFIG|LOG_PANIC_DIE, "can't redefine an undefined macro "
+  if (m)
+    {
+    m->replen = Ustrlen(s);
+    m->replacement = string_copy(s);
+    }
+  else
+    {
+    log_write(0, LOG_CONFIG|LOG_PANIC, "can't redefine an undefined macro "
       "\"%s\"", name);
       "\"%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. */
+    return FALSE;
+    }
 
 
+/* We have a new definition. */
 else
 else
+  (void) macro_create(name, s, FALSE);
+return TRUE;
+}
+
+
+
+
+
+/* Process line for macros. The line is in big_buffer starting at offset len.
+Expand big_buffer if needed.  Handle definitions of new macros, and
+macro expansions, rewriting the line in the buffer.
+
+Arguments:
+ len           Offset in buffer of start of line
+ newlen                Pointer to offset of end of line, updated on return
+ macro_found   Pointer to return that a macro was expanded
+
+Return: pointer to first nonblank char in line
+*/
+
+uschar *
+macros_expand(int len, int * newlen, BOOL * macro_found)
+{
+uschar * ss = big_buffer + len;
+uschar * s;
+macro_item * m;
+
+/* Find the true start of the physical line - leading spaces are always
+ignored. */
+
+while (isspace(*ss)) ss++;
+
+/* Process the physical line for macros. If this is the start of the logical
+line, skip over initial text at the start of the line if it starts with an
+upper case character followed by a sequence of name characters and an equals
+sign, because that is the definition of a new macro, and we don't do
+replacement therein. */
+
+s = ss;
+if (len == 0 && isupper(*s))
   {
   {
-  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;
+  while (isalnum(*s) || *s == '_') s++;
+  while (isspace(*s)) s++;
+  if (*s != '=') s = ss;          /* Not a macro definition */
   }
 
   }
 
-/* Set the value of the new or redefined macro */
+/* Skip leading chars which cannot start a macro name, to avoid multiple
+pointless rescans in Ustrstr calls. */
 
 
-m->replacement = string_copy(s);
-}
+while (*s && !isupper(*s) && !(*s == '_' && isupper(s[1]))) s++;
 
 
+/* For each defined macro, scan the line (from after XXX= if present),
+replacing all occurrences of the macro. */
 
 
+*macro_found = FALSE;
+if (*s) for (m = *s == '_' ? macros : macros_user; m; m = m->next)
+  {
+  uschar * p, *pp;
+  uschar * t;
 
 
+  while (*s && !isupper(*s) && !(*s == '_' && isupper(s[1]))) s++;
+  if (!*s) break;
 
 
+  t = s;
+  while ((p = Ustrstr(t, m->name)) != NULL)
+    {
+    int moveby;
+
+    READCONF_DEBUG fprintf(stderr, "%s: matched '%s' in '%.*s'\n", __FUNCTION__,
+      m->name, (int) Ustrlen(ss)-1, ss);
+    /* Expand the buffer if necessary */
+
+    while (*newlen - m->namelen + m->replen + 1 > big_buffer_size)
+      {
+      int newsize = big_buffer_size + BIG_BUFFER_SIZE;
+      uschar *newbuffer = store_malloc(newsize);
+      memcpy(newbuffer, big_buffer, *newlen + 1);
+      p = newbuffer  + (p - big_buffer);
+      s = newbuffer  + (s - big_buffer);
+      ss = newbuffer + (ss - big_buffer);
+      t = newbuffer  + (t - big_buffer);
+      big_buffer_size = newsize;
+      store_free(big_buffer);
+      big_buffer = newbuffer;
+      }
+
+    /* Shuffle the remaining characters up or down in the buffer before
+    copying in the replacement text. Don't rescan the replacement for this
+    same macro. */
+
+    pp = p + m->namelen;
+    if ((moveby = m->replen - m->namelen) != 0)
+      {
+      memmove(p + m->replen, pp, (big_buffer + *newlen) - pp + 1);
+      *newlen += moveby;
+      }
+    Ustrncpy(p, m->replacement, m->replen);
+    t = p + m->replen;
+    while (*t && !isupper(*t) && !(*t == '_' && isupper(t[1]))) t++;
+    *macro_found = TRUE;
+    }
+  }
+
+/* An empty macro replacement at the start of a line could mean that ss no
+longer points to the first non-blank character. */
+
+while (isspace(*ss)) ss++;
+return ss;
+}
 
 /*************************************************
 *            Read configuration line             *
 
 /*************************************************
 *            Read configuration line             *
@@ -670,7 +898,6 @@ int startoffset = 0;         /* To first non-blank char in logical line */
 int len = 0;                 /* Of logical line so far */
 int newlen;
 uschar *s, *ss;
 int len = 0;                 /* Of logical line so far */
 int newlen;
 uschar *s, *ss;
-macro_item *m;
 BOOL macro_found;
 
 /* Loop for handling continuation lines, skipping comments, and dealing with
 BOOL macro_found;
 
 /* Loop for handling continuation lines, skipping comments, and dealing with
@@ -685,8 +912,11 @@ for (;;)
       (void)fclose(config_file);
       config_file = config_file_stack->file;
       config_filename = config_file_stack->filename;
       (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;
       config_lineno = config_file_stack->lineno;
       config_file_stack = config_file_stack->next;
+      if (config_lines)
+        save_config_position(config_filename, config_lineno);
       continue;
       }
 
       continue;
       }
 
@@ -704,6 +934,9 @@ for (;;)
   config_lineno++;
   newlen = len + Ustrlen(big_buffer + len);
 
   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. */
   /* 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. */
@@ -725,78 +958,7 @@ for (;;)
     newlen += Ustrlen(big_buffer + newlen);
     }
 
     newlen += Ustrlen(big_buffer + newlen);
     }
 
-  /* Find the true start of the physical line - leading spaces are always
-  ignored. */
-
-  ss = big_buffer + len;
-  while (isspace(*ss)) ss++;
-
-  /* Process the physical line for macros. If this is the start of the logical
-  line, skip over initial text at the start of the line if it starts with an
-  upper case character followed by a sequence of name characters and an equals
-  sign, because that is the definition of a new macro, and we don't do
-  replacement therein. */
-
-  s = ss;
-  if (len == 0 && isupper(*s))
-    {
-    while (isalnum(*s) || *s == '_') s++;
-    while (isspace(*s)) s++;
-    if (*s != '=') s = ss;          /* Not a macro definition */
-    }
-
-  /* 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)
-    {
-    uschar *p, *pp;
-    uschar *t = s;
-
-    while ((p = Ustrstr(t, m->name)) != NULL)
-      {
-      int moveby;
-      int namelen = Ustrlen(m->name);
-      int replen = Ustrlen(m->replacement);
-
-      /* Expand the buffer if necessary */
-
-      while (newlen - namelen + replen + 1 > big_buffer_size)
-        {
-        int newsize = big_buffer_size + BIG_BUFFER_SIZE;
-        uschar *newbuffer = store_malloc(newsize);
-        memcpy(newbuffer, big_buffer, newlen + 1);
-        p = newbuffer  + (p - big_buffer);
-        s = newbuffer  + (s - big_buffer);
-        ss = newbuffer + (ss - big_buffer);
-        t = newbuffer  + (t - big_buffer);
-        big_buffer_size = newsize;
-        store_free(big_buffer);
-        big_buffer = newbuffer;
-        }
-
-      /* Shuffle the remaining characters up or down in the buffer before
-      copying in the replacement text. Don't rescan the replacement for this
-      same macro. */
-
-      pp = p + namelen;
-      moveby = replen - namelen;
-      if (moveby != 0)
-        {
-        memmove(p + replen, pp, (big_buffer + newlen) - pp + 1);
-        newlen += moveby;
-        }
-      Ustrncpy(p, m->replacement, replen);
-      t = p + replen;
-      macro_found = TRUE;
-      }
-    }
-
-  /* An empty macro replacement at the start of a line could mean that ss no
-  longer points to the first non-blank character. */
-
-  while (isspace(*ss)) ss++;
+  ss = macros_expand(len, &newlen, &macro_found);
 
   /* Check for comment lines - these are physical lines. */
 
 
   /* Check for comment lines - these are physical lines. */
 
@@ -804,7 +966,7 @@ for (;;)
 
   /* Handle conditionals, which are also applied to physical lines. Conditions
   are of the form ".ifdef ANYTEXT" and are treated as true if any macro
 
   /* Handle conditionals, which are also applied to physical lines. Conditions
   are of the form ".ifdef ANYTEXT" and are treated as true if any macro
-  expansion occured on the rest of the line. A preliminary test for the leading
+  expansion occurred on the rest of the line. A preliminary test for the leading
   '.' saves effort on most lines. */
 
   if (*ss == '.')
   '.' saves effort on most lines. */
 
   if (*ss == '.')
@@ -886,24 +1048,36 @@ for (;;)
       }
     *t = 0;
 
       }
     *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 != '/')
     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
+        {
+       gstring * g = string_append(NULL, 3, config_directory, "/", ss);
+       ss = string_from_gstring(g);
+        }
 
     if (include_if_exists != 0 && (Ustat(ss, &statbuf) != 0)) continue;
 
 
     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 = 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;
 
     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);
       log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "failed to open included "
         "configuration file %s", ss);
+
     config_filename = string_copy(ss);
     config_filename = string_copy(ss);
+    config_directory = string_copyn(ss, CUstrrchr(ss, '/') - ss);
     config_lineno = 0;
     continue;
     }
     config_lineno = 0;
     continue;
     }
@@ -948,6 +1122,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 */
 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;
 if (strncmpic(s, US"begin ", 6) == 0)
   {
   s += 6;
@@ -1026,7 +1204,7 @@ Returns:        the time value, or -1 on syntax error
 */
 
 int
 */
 
 int
-readconf_readtime(uschar *s, int terminator, BOOL return_msec)
+readconf_readtime(const uschar *s, int terminator, BOOL return_msec)
 {
 int yield = 0;
 for (;;)
 {
 int yield = 0;
 for (;;)
@@ -1035,7 +1213,7 @@ for (;;)
   double fraction;
 
   if (!isdigit(*s)) return -1;
   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)
   s += count;
 
   switch (*s)
@@ -1049,7 +1227,7 @@ for (;;)
 
     case '.':
     if (!return_msec) return -1;
 
     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);
     s += count;
     if (*s++ != 's') return -1;
     yield += (int)(fraction * 1000.0);
@@ -1081,7 +1259,7 @@ Returns:      the value, or -1 on error
 */
 
 static int
 */
 
 static int
-readconf_readfixed(uschar *s, int terminator)
+readconf_readfixed(const uschar *s, int terminator)
 {
 int yield = 0;
 int value, count;
 {
 int yield = 0;
 int value, count;
@@ -1126,9 +1304,10 @@ while (last > first)
   {
   int middle = (first + last)/2;
   int c = Ustrcmp(name, ol[middle].name);
   {
   int middle = (first + last)/2;
   int c = Ustrcmp(name, ol[middle].name);
+
   if (c == 0) return ol + middle;
   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;
 }
   }
 return NULL;
 }
@@ -1166,7 +1345,7 @@ ol = find_option(name2, oltop, last);
 if (ol == NULL) log_write(0, LOG_MAIN|LOG_PANIC_DIE,
   "Exim internal error: missing set flag for %s", name);
 return (data_block == NULL)? (BOOL *)(ol->value) :
 if (ol == NULL) log_write(0, LOG_MAIN|LOG_PANIC_DIE,
   "Exim internal error: missing set flag for %s", name);
 return (data_block == NULL)? (BOOL *)(ol->value) :
-  (BOOL *)((uschar *)data_block + (long int)(ol->value));
+  (BOOL *)(US data_block + (long int)(ol->value));
 }
 
 
 }
 
 
@@ -1187,7 +1366,7 @@ Returns:     doesn't return; dies
 */
 
 static void
 */
 
 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)";
 {
 uschar *comment = US"";
 if (*s == '#') comment = US" (# is comment only at line start)";
@@ -1223,7 +1402,7 @@ Returns:      the control block for the parsed rule.
 */
 
 static rewrite_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));
 
 {
 rewrite_rule *next = store_get(sizeof(rewrite_rule));
 
@@ -1329,10 +1508,10 @@ Returns:    pointer to the string
 */
 
 static uschar *
 */
 
 static uschar *
-read_string(uschar *s, uschar *name)
+read_string(const uschar *s, const uschar *name)
 {
 uschar *yield;
 {
 uschar *yield;
-uschar *ss;
+const uschar *ss;
 
 if (*s != '\"') return string_copy(s);
 
 
 if (*s != '\"') return string_copy(s);
 
@@ -1349,6 +1528,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                  *
 *************************************************/
 /*************************************************
 *            Handle option line                  *
 *************************************************/
@@ -1407,7 +1604,6 @@ int intbase = 0;
 uschar *inttype = US"";
 uschar *sptr;
 uschar *s = buffer;
 uschar *inttype = US"";
 uschar *sptr;
 uschar *s = buffer;
-uschar *saved_condition, *strtemp;
 uschar **str_target;
 uschar name[64];
 uschar name2[64];
 uschar **str_target;
 uschar name[64];
 uschar name2[64];
@@ -1454,9 +1650,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. */
 
 /* 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);
   {
   if (unknown_txt == NULL) return FALSE;
   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, CS unknown_txt, name);
@@ -1485,7 +1679,7 @@ if (type < opt_bool || type > opt_bool_last)
   }
 
 /* If a boolean wasn't preceded by "no[t]_" it can be followed by = and
   }
 
 /* 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 != '='))
 ultimately expands to one of those values. */
 
 else if (*s != 0 && (offset != 0 || *s != '='))
@@ -1544,19 +1738,18 @@ switch (type)
     control block and flags word. */
 
     case opt_stringptr:
     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)
       {
     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. */
       /* 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
       /* 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
@@ -1573,18 +1766,23 @@ switch (type)
       }
     else if (ol->type & opt_rep_str)
       {
       }
     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;
+      gstring * list_o = NULL;
+
+      if (*str_target)
+       {
+       list_o = string_get(Ustrlen(*str_target) + Ustrlen(sptr));
+       list_o = string_cat(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(string_from_gstring(list_o));
       }
     else
       {
       }
     else
       {
@@ -1594,10 +1792,10 @@ switch (type)
     break;
 
     case opt_rewrite:
     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
     else
-      *((uschar **)((uschar *)data_block + (long int)(ol->value))) = sptr;
+      *USS (ol->value) = sptr;
     freesptr = FALSE;
     if (type == opt_rewrite)
       {
     freesptr = FALSE;
     if (type == opt_rewrite)
       {
@@ -1623,12 +1821,11 @@ switch (type)
         }
       else
         {
         }
       else
         {
-        chain = (rewrite_rule **)((uschar *)data_block + (long int)(ol2->value));
-        flagptr = (int *)((uschar *)data_block + (long int)(ol3->value));
+        chain = (rewrite_rule **)(US data_block + (long int)(ol2->value));
+        flagptr = (int *)(US 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;
         {
         rewrite_rule *next = readconf_one_rewrite(p, flagptr, FALSE);
         *chain = next;
@@ -1657,7 +1854,7 @@ switch (type)
       if (data_block == NULL)
         *((uschar **)(ol2->value)) = ss;
       else
       if (data_block == NULL)
         *((uschar **)(ol2->value)) = ss;
       else
-        *((uschar **)((uschar *)data_block + (long int)(ol2->value))) = ss;
+        *((uschar **)(US data_block + (long int)(ol2->value))) = ss;
 
       if (ss != NULL)
         {
 
       if (ss != NULL)
         {
@@ -1676,7 +1873,7 @@ switch (type)
     if (data_block == NULL)
       *((uid_t *)(ol->value)) = uid;
     else
     if (data_block == NULL)
       *((uid_t *)(ol->value)) = uid;
     else
-      *((uid_t *)((uschar *)data_block + (long int)(ol->value))) = uid;
+      *((uid_t *)(US data_block + (long int)(ol->value))) = uid;
 
     /* Set the flag indicating a fixed value is set */
 
 
     /* Set the flag indicating a fixed value is set */
 
@@ -1698,7 +1895,7 @@ switch (type)
         if (data_block == NULL)
           *((gid_t *)(ol2->value)) = pw->pw_gid;
         else
         if (data_block == NULL)
           *((gid_t *)(ol2->value)) = pw->pw_gid;
         else
-          *((gid_t *)((uschar *)data_block + (long int)(ol2->value))) = pw->pw_gid;
+          *((gid_t *)(US data_block + (long int)(ol2->value))) = pw->pw_gid;
         *set_flag = TRUE;
         }
       }
         *set_flag = TRUE;
         }
       }
@@ -1720,7 +1917,7 @@ switch (type)
       if (data_block == NULL)
         *((uschar **)(ol2->value)) = ss;
       else
       if (data_block == NULL)
         *((uschar **)(ol2->value)) = ss;
       else
-        *((uschar **)((uschar *)data_block + (long int)(ol2->value))) = ss;
+        *((uschar **)(US data_block + (long int)(ol2->value))) = ss;
 
       if (ss != NULL)
         {
 
       if (ss != NULL)
         {
@@ -1738,7 +1935,7 @@ switch (type)
     if (data_block == NULL)
       *((gid_t *)(ol->value)) = gid;
     else
     if (data_block == NULL)
       *((gid_t *)(ol->value)) = gid;
     else
-      *((gid_t *)((uschar *)data_block + (long int)(ol->value))) = gid;
+      *((gid_t *)(US data_block + (long int)(ol->value))) = gid;
     *(get_set_flag(name, oltop, last, data_block)) = TRUE;
     break;
 
     *(get_set_flag(name, oltop, last, data_block)) = TRUE;
     break;
 
@@ -1752,8 +1949,8 @@ switch (type)
       int count = 1;
       uid_t *list;
       int ptr = 0;
       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",
 
       if (op == NULL)
         log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "failed to expand %s: %s",
@@ -1768,7 +1965,7 @@ switch (type)
       if (data_block == NULL)
         *((uid_t **)(ol->value)) = list;
       else
       if (data_block == NULL)
         *((uid_t **)(ol->value)) = list;
       else
-        *((uid_t **)((uschar *)data_block + (long int)(ol->value))) = list;
+        *((uid_t **)(US data_block + (long int)(ol->value))) = list;
 
       p = op;
       while (count-- > 1)
 
       p = op;
       while (count-- > 1)
@@ -1793,8 +1990,8 @@ switch (type)
       int count = 1;
       gid_t *list;
       int ptr = 0;
       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",
 
       if (op == NULL)
         log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "failed to expand %s: %s",
@@ -1809,7 +2006,7 @@ switch (type)
       if (data_block == NULL)
         *((gid_t **)(ol->value)) = list;
       else
       if (data_block == NULL)
         *((gid_t **)(ol->value)) = list;
       else
-        *((gid_t **)((uschar *)data_block + (long int)(ol->value))) = list;
+        *((gid_t **)(US data_block + (long int)(ol->value))) = list;
 
       p = op;
       while (count-- > 1)
 
       p = op;
       while (count-- > 1)
@@ -1845,7 +2042,7 @@ switch (type)
       if (data_block == NULL)
         *((uschar **)(ol2->value)) = sptr;
       else
       if (data_block == NULL)
         *((uschar **)(ol2->value)) = sptr;
       else
-        *((uschar **)((uschar *)data_block + (long int)(ol2->value))) = sptr;
+        *((uschar **)(US data_block + (long int)(ol2->value))) = sptr;
       freesptr = FALSE;
       break;
       }
       freesptr = FALSE;
       break;
       }
@@ -1883,7 +2080,7 @@ switch (type)
     int bit = 1 << ((ol->type >> 16) & 31);
     int *ptr = (data_block == NULL)?
       (int *)(ol->value) :
     int bit = 1 << ((ol->type >> 16) & 31);
     int *ptr = (data_block == NULL)?
       (int *)(ol->value) :
-      (int *)((uschar *)data_block + (long int)ol->value);
+      (int *)(US data_block + (long int)ol->value);
     if (boolvalue) *ptr |= bit; else *ptr &= ~bit;
     break;
     }
     if (boolvalue) *ptr |= bit; else *ptr &= ~bit;
     break;
     }
@@ -1893,7 +2090,7 @@ switch (type)
   if (data_block == NULL)
     *((BOOL *)(ol->value)) = boolvalue;
   else
   if (data_block == NULL)
     *((BOOL *)(ol->value)) = boolvalue;
   else
-    *((BOOL *)((uschar *)data_block + (long int)(ol->value))) = boolvalue;
+    *((BOOL *)(US data_block + (long int)(ol->value))) = boolvalue;
 
   /* Verify fudge */
 
 
   /* Verify fudge */
 
@@ -1906,7 +2103,7 @@ switch (type)
       if (data_block == NULL)
         *((BOOL *)(ol2->value)) = boolvalue;
       else
       if (data_block == NULL)
         *((BOOL *)(ol2->value)) = boolvalue;
       else
-        *((BOOL *)((uschar *)data_block + (long int)(ol2->value))) = boolvalue;
+        *((BOOL *)(US data_block + (long int)(ol2->value))) = boolvalue;
       }
     }
 
       }
     }
 
@@ -1921,7 +2118,7 @@ switch (type)
       if (data_block == NULL)
         *((BOOL *)(ol2->value)) = TRUE;
       else
       if (data_block == NULL)
         *((BOOL *)(ol2->value)) = TRUE;
       else
-        *((BOOL *)((uschar *)data_block + (long int)(ol2->value))) = TRUE;
+        *((BOOL *)(US data_block + (long int)(ol2->value))) = TRUE;
       }
     }
   break;
       }
     }
   break;
@@ -1933,7 +2130,7 @@ switch (type)
   inttype = US"octal ";
 
   /*  Integer: a simple(ish) case; allow octal and hex formats, and
   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, G, and T.  The different types affect output, not input. */
 
   case opt_mkint:
   case opt_int:
 
   case opt_mkint:
   case opt_int:
@@ -1948,21 +2145,24 @@ switch (type)
       log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "%sinteger expected for %s",
         inttype, name);
 
       log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "%sinteger expected for %s",
         inttype, name);
 
-    if (errno != ERANGE)
+    if (errno != ERANGE && *endptr)
       {
       {
-      if (tolower(*endptr) == 'k')
-        {
-        if (lvalue > INT_MAX/1024 || lvalue < INT_MIN/1024) errno = ERANGE;
-          else lvalue *= 1024;
-        endptr++;
-        }
-      else if (tolower(*endptr) == 'm')
-        {
-        if (lvalue > INT_MAX/(1024*1024) || lvalue < INT_MIN/(1024*1024))
-          errno = ERANGE;
-        else lvalue *= 1024*1024;
-        endptr++;
-        }
+      uschar * mp = US"TtGgMmKk\0";    /* YyZzEePpTtGgMmKk */
+
+      if ((mp = Ustrchr(mp, *endptr)))
+       {
+       endptr++;
+       do
+         {
+         if (lvalue > INT_MAX/1024 || lvalue < INT_MIN/1024)
+           {
+           errno = ERANGE;
+           break;
+           }
+         lvalue *= 1024;
+         }
+       while (*(mp += 2));
+       }
       }
 
     if (errno == ERANGE || lvalue > INT_MAX || lvalue < INT_MIN)
       }
 
     if (errno == ERANGE || lvalue > INT_MAX || lvalue < INT_MIN)
@@ -1970,47 +2170,49 @@ switch (type)
         "absolute value of integer \"%s\" is too large (overflow)", s);
 
     while (isspace(*endptr)) endptr++;
         "absolute value of integer \"%s\" is too large (overflow)", s);
 
     while (isspace(*endptr)) endptr++;
-    if (*endptr != 0)
+    if (*endptr)
       extra_chars_error(endptr, inttype, US"integer value for ", name);
 
     value = (int)lvalue;
     }
       extra_chars_error(endptr, inttype, US"integer value for ", name);
 
     value = (int)lvalue;
     }
-
-  if (data_block == NULL)
-    *((int *)(ol->value)) = value;
+
+  if (data_block)
+    *(int *)(US data_block + (long int)ol->value) = value;
   else
   else
-    *((int *)((uschar *)data_block + (long int)(ol->value))) = value;
+    *(int *)ol->value = value;
   break;
 
   break;
 
-  /*  Integer held in K: again, allow octal and hex formats, and suffixes K and
-  M. */
+  /*  Integer held in K: again, allow formats and suffixes as above. */
 
   case opt_Kint:
     {
     uschar *endptr;
     errno = 0;
 
   case opt_Kint:
     {
     uschar *endptr;
     errno = 0;
-    value = strtol(CS s, CSS &endptr, intbase);
+    int_eximarith_t lvalue = strtol(CS s, CSS &endptr, intbase);
 
     if (endptr == s)
       log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "%sinteger expected for %s",
         inttype, name);
 
 
     if (endptr == s)
       log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "%sinteger expected for %s",
         inttype, name);
 
-    if (errno != ERANGE)
+    if (errno != ERANGE && *endptr)
       {
       {
-      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++;
-        }
+      uschar * mp = US"ZzEePpTtGgMmKk\0";      /* YyZzEePpTtGgMmKk */
+
+      if ((mp = Ustrchr(mp, *endptr)))
+       {
+       endptr++;
+       while (*(mp += 2))
+         {
+         if (lvalue > EXIM_ARITH_MAX/1024 || lvalue < EXIM_ARITH_MIN/1024)
+           {
+           errno = ERANGE;
+           break;
+           }
+         lvalue *= 1024;
+         }
+       }
       else
       else
-        {
-        value = (value + 512)/1024;
-        }
+       lvalue = (lvalue + 512)/1024;
       }
 
     if (errno == ERANGE) log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
       }
 
     if (errno == ERANGE) log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
@@ -2019,13 +2221,13 @@ switch (type)
     while (isspace(*endptr)) endptr++;
     if (*endptr != 0)
       extra_chars_error(endptr, inttype, US"integer value for ", name);
     while (isspace(*endptr)) endptr++;
     if (*endptr != 0)
       extra_chars_error(endptr, inttype, US"integer value for ", name);
-    }
 
 
-  if (data_block == NULL)
-    *((int *)(ol->value)) = value;
-  else
-    *((int *)((uschar *)data_block + (long int)(ol->value))) = value;
-  break;
+    if (data_block)
+      *(int_eximarith_t *)(US data_block + (long int)ol->value) = lvalue;
+    else
+      *(int_eximarith_t *)ol->value = lvalue;
+    break;
+    }
 
   /*  Fixed-point number: held to 3 decimal places. */
 
 
   /*  Fixed-point number: held to 3 decimal places. */
 
@@ -2042,6 +2244,11 @@ switch (type)
   if (value < 0) log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
     "integer \"%s\" is too large (overflow)", s);
 
   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;
   if (s[count] == '.')
     {
     int d = 100;
@@ -2060,7 +2267,7 @@ switch (type)
   if (data_block == NULL)
     *((int *)(ol->value)) = value;
   else
   if (data_block == NULL)
     *((int *)(ol->value)) = value;
   else
-    *((int *)((uschar *)data_block + (long int)(ol->value))) = value;
+    *((int *)(US data_block + (long int)(ol->value))) = value;
   break;
 
   /* There's a special routine to read time values. */
   break;
 
   /* There's a special routine to read time values. */
@@ -2073,7 +2280,7 @@ switch (type)
   if (data_block == NULL)
     *((int *)(ol->value)) = value;
   else
   if (data_block == NULL)
     *((int *)(ol->value)) = value;
   else
-    *((int *)((uschar *)data_block + (long int)(ol->value))) = value;
+    *((int *)(US data_block + (long int)(ol->value))) = value;
   break;
 
   /* A time list is a list of colon-separated times, with the first
   break;
 
   /* A time list is a list of colon-separated times, with the first
@@ -2085,7 +2292,7 @@ switch (type)
     int count = 0;
     int *list = (data_block == NULL)?
       (int *)(ol->value) :
     int count = 0;
     int *list = (data_block == NULL)?
       (int *)(ol->value) :
-      (int *)((uschar *)data_block + (long int)(ol->value));
+      (int *)(US data_block + (long int)(ol->value));
 
     if (*s != 0) for (count = 1; count <= list[0] - 2; count++)
       {
 
     if (*s != 0) for (count = 1; count <= list[0] - 2; count++)
       {
@@ -2115,9 +2322,15 @@ switch (type)
         name);
     if (count > 0 && list[2] == 0) count = 0;
     list[1] = count;
         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;
   }
 
 return TRUE;
@@ -2156,10 +2369,10 @@ t /= 24;
 d = t % 7;
 w = t/7;
 
 d = t % 7;
 w = t/7;
 
-if (w > 0) { sprintf(CS p, "%dw", w); while (*p) p++; }
-if (d > 0) { sprintf(CS p, "%dd", d); while (*p) p++; }
-if (h > 0) { sprintf(CS p, "%dh", h); while (*p) p++; }
-if (m > 0) { sprintf(CS p, "%dm", m); while (*p) p++; }
+if (w > 0) p += sprintf(CS p, "%dw", w);
+if (d > 0) p += sprintf(CS p, "%dd", d);
+if (h > 0) p += sprintf(CS p, "%dh", h);
+if (m > 0) p += sprintf(CS p, "%dm", m);
 if (s > 0 || p == time_buffer) sprintf(CS p, "%ds", s);
 
 return time_buffer;
 if (s > 0 || p == time_buffer) sprintf(CS p, "%ds", s);
 
 return time_buffer;
@@ -2188,10 +2401,10 @@ Arguments:
   last           one more than the offset of the last entry in optop
   no_labels      do not show "foo = " at the start.
 
   last           one more than the offset of the last entry in optop
   no_labels      do not show "foo = " at the start.
 
-Returns:         nothing
+Returns:         boolean success
 */
 
 */
 
-static void
+static BOOL
 print_ol(optionlist *ol, uschar *name, void *options_block,
   optionlist *oltop, int last, BOOL no_labels)
 {
 print_ol(optionlist *ol, uschar *name, void *options_block,
   optionlist *oltop, int last, BOOL no_labels)
 {
@@ -2204,48 +2417,47 @@ gid_t *gidlist;
 uschar *s;
 uschar name2[64];
 
 uschar *s;
 uschar name2[64];
 
-if (ol == NULL)
+if (!ol)
   {
   printf("%s is not a known option\n", name);
   {
   printf("%s is not a known option\n", name);
-  return;
+  return FALSE;
   }
 
 /* Non-admin callers cannot see options that have been flagged secure by the
 "hide" prefix. */
 
   }
 
 /* Non-admin callers cannot see options that have been flagged secure by the
 "hide" prefix. */
 
-if (!admin_user && (ol->type & opt_secure) != 0)
+if (!f.admin_user && ol->type & opt_secure)
   {
   {
-  const char * const hidden = "<value not displayable>";
   if (no_labels)
     printf("%s\n", hidden);
   else
     printf("%s = %s\n", name, hidden);
   if (no_labels)
     printf("%s\n", hidden);
   else
     printf("%s = %s\n", name, hidden);
-  return;
+  return TRUE;
   }
 
 /* Else show the value of the option */
 
 value = ol->value;
   }
 
 /* Else show the value of the option */
 
 value = ol->value;
-if (options_block != NULL)
+if (options_block)
   {
   {
-  if ((ol->type & opt_public) == 0)
+  if (!(ol->type & opt_public))
     options_block = (void *)(((driver_instance *)options_block)->options_block);
     options_block = (void *)(((driver_instance *)options_block)->options_block);
-  value = (void *)((uschar *)options_block + (long int)value);
+  value = (void *)(US options_block + (long int)value);
   }
 
 switch(ol->type & opt_mask)
   {
   case opt_stringptr:
   case opt_rewrite:        /* Show the text value */
   }
 
 switch(ol->type & opt_mask)
   {
   case opt_stringptr:
   case opt_rewrite:        /* Show the text value */
-  s = *((uschar **)value);
-  if (!no_labels) printf("%s = ", name);
-  printf("%s\n", (s == NULL)? US"" : string_printing2(s, FALSE));
-  break;
+    s = *(USS value);
+    if (!no_labels) printf("%s = ", name);
+    printf("%s\n", s ? string_printing2(s, FALSE) : US"");
+    break;
 
   case opt_int:
 
   case opt_int:
-  if (!no_labels) printf("%s = ", name);
-  printf("%d\n", *((int *)value));
-  break;
+    if (!no_labels) printf("%s = ", name);
+    printf("%d\n", *((int *)value));
+    break;
 
   case opt_mkint:
     {
 
   case opt_mkint:
     {
@@ -2268,22 +2480,24 @@ switch(ol->type & opt_mask)
       printf("%d\n", x);
       }
     }
       printf("%d\n", x);
       }
     }
-  break;
+    break;
 
   case opt_Kint:
     {
 
   case opt_Kint:
     {
-    int x = *((int *)value);
+    int_eximarith_t x = *((int_eximarith_t *)value);
     if (!no_labels) printf("%s = ", name);
     if (x == 0) printf("0\n");
     if (!no_labels) printf("%s = ", name);
     if (x == 0) printf("0\n");
-      else if ((x & 1023) == 0) printf("%dM\n", x >> 10);
-        else printf("%dK\n", x);
+    else if ((x & ((1<<30)-1)) == 0) printf(PR_EXIM_ARITH "T\n", x >> 30);
+    else if ((x & ((1<<20)-1)) == 0) printf(PR_EXIM_ARITH "G\n", x >> 20);
+    else if ((x & ((1<<10)-1)) == 0) printf(PR_EXIM_ARITH "M\n", x >> 10);
+    else printf(PR_EXIM_ARITH "K\n", x);
     }
     }
-  break;
+    break;
 
   case opt_octint:
 
   case opt_octint:
-  if (!no_labels) printf("%s = ", name);
-  printf("%#o\n", *((int *)value));
-  break;
+    if (!no_labels) printf("%s = ", name);
+    printf("%#o\n", *((int *)value));
+    break;
 
   /* Can be negative only when "unset", in which case integer */
 
 
   /* Can be negative only when "unset", in which case integer */
 
@@ -2306,124 +2520,115 @@ switch(ol->type & opt_mask)
       printf("\n");
       }
     }
       printf("\n");
       }
     }
-  break;
+    break;
 
   /* If the numerical value is unset, try for the string value */
 
   case opt_expand_uid:
 
   /* If the numerical value is unset, try for the string value */
 
   case opt_expand_uid:
-  if (! *get_set_flag(name, oltop, last, options_block))
-    {
-    sprintf(CS name2, "*expand_%.50s", name);
-    ol2 = find_option(name2, oltop, last);
-    if (ol2 != NULL)
+    if (! *get_set_flag(name, oltop, last, options_block))
       {
       {
-      void *value2 = ol2->value;
-      if (options_block != NULL)
-        value2 = (void *)((uschar *)options_block + (long int)value2);
-      s = *((uschar **)value2);
-      if (!no_labels) printf("%s = ", name);
-      printf("%s\n", (s == NULL)? US"" : string_printing(s));
-      break;
+      sprintf(CS name2, "*expand_%.50s", name);
+      if ((ol2 = find_option(name2, oltop, last)))
+       {
+       void *value2 = ol2->value;
+       if (options_block)
+         value2 = (void *)(US options_block + (long int)value2);
+       s = *(USS value2);
+       if (!no_labels) printf("%s = ", name);
+       printf("%s\n", s ? string_printing(s) : US"");
+       break;
+       }
       }
       }
-    }
 
 
-  /* Else fall through */
+    /* Else fall through */
 
   case opt_uid:
 
   case opt_uid:
-  if (!no_labels) printf("%s = ", name);
-  if (! *get_set_flag(name, oltop, last, options_block))
-    printf("\n");
-  else
-    {
-    pw = getpwuid(*((uid_t *)value));
-    if (pw == NULL)
-      printf("%ld\n", (long int)(*((uid_t *)value)));
-    else printf("%s\n", pw->pw_name);
-    }
-  break;
+    if (!no_labels) printf("%s = ", name);
+    if (! *get_set_flag(name, oltop, last, options_block))
+      printf("\n");
+    else
+      if ((pw = getpwuid(*((uid_t *)value))))
+       printf("%s\n", pw->pw_name);
+      else
+       printf("%ld\n", (long int)(*((uid_t *)value)));
+    break;
 
   /* If the numerical value is unset, try for the string value */
 
   case opt_expand_gid:
 
   /* If the numerical value is unset, try for the string value */
 
   case opt_expand_gid:
-  if (! *get_set_flag(name, oltop, last, options_block))
-    {
-    sprintf(CS name2, "*expand_%.50s", name);
-    ol2 = find_option(name2, oltop, last);
-    if (ol2 != NULL && (ol2->type & opt_mask) == opt_stringptr)
+    if (! *get_set_flag(name, oltop, last, options_block))
       {
       {
-      void *value2 = ol2->value;
-      if (options_block != NULL)
-        value2 = (void *)((uschar *)options_block + (long int)value2);
-      s = *((uschar **)value2);
-      if (!no_labels) printf("%s = ", name);
-      printf("%s\n", (s == NULL)? US"" : string_printing(s));
-      break;
+      sprintf(CS name2, "*expand_%.50s", name);
+      if (  (ol2 = find_option(name2, oltop, last))
+        && (ol2->type & opt_mask) == opt_stringptr)
+       {
+       void *value2 = ol2->value;
+       if (options_block)
+         value2 = (void *)(US options_block + (long int)value2);
+       s = *(USS value2);
+       if (!no_labels) printf("%s = ", name);
+       printf("%s\n", s ? string_printing(s) : US"");
+       break;
+       }
       }
       }
-    }
 
 
-  /* Else fall through */
+    /* Else fall through */
 
   case opt_gid:
 
   case opt_gid:
-  if (!no_labels) printf("%s = ", name);
-  if (! *get_set_flag(name, oltop, last, options_block))
-    printf("\n");
-  else
-    {
-    gr = getgrgid(*((int *)value));
-    if (gr == NULL)
-       printf("%ld\n", (long int)(*((int *)value)));
-    else printf("%s\n", gr->gr_name);
-    }
-  break;
+    if (!no_labels) printf("%s = ", name);
+    if (! *get_set_flag(name, oltop, last, options_block))
+      printf("\n");
+    else
+      if ((gr = getgrgid(*((int *)value))))
+       printf("%s\n", gr->gr_name);
+      else
+        printf("%ld\n", (long int)(*((int *)value)));
+    break;
 
   case opt_uidlist:
 
   case opt_uidlist:
-  uidlist = *((uid_t **)value);
-  if (!no_labels) printf("%s =", name);
-  if (uidlist != NULL)
-    {
-    int i;
-    uschar sep = ' ';
-    if (no_labels) sep = '\0';
-    for (i = 1; i <= (int)(uidlist[0]); i++)
+    uidlist = *((uid_t **)value);
+    if (!no_labels) printf("%s =", name);
+    if (uidlist)
       {
       {
-      uschar *name = NULL;
-      pw = getpwuid(uidlist[i]);
-      if (pw != NULL) name = US pw->pw_name;
-      if (sep != '\0') printf("%c", sep);
-      if (name != NULL) printf("%s", name);
-        else printf("%ld", (long int)(uidlist[i]));
-      sep = ':';
+      int i;
+      uschar sep = no_labels ? '\0' : ' ';
+      for (i = 1; i <= (int)(uidlist[0]); i++)
+       {
+       uschar *name = NULL;
+       if ((pw = getpwuid(uidlist[i]))) name = US pw->pw_name;
+       if (sep != '\0') printf("%c", sep);
+       if (name) printf("%s", name);
+       else printf("%ld", (long int)(uidlist[i]));
+       sep = ':';
+       }
       }
       }
-    }
-  printf("\n");
-  break;
+    printf("\n");
+    break;
 
   case opt_gidlist:
 
   case opt_gidlist:
-  gidlist = *((gid_t **)value);
-  if (!no_labels) printf("%s =", name);
-  if (gidlist != NULL)
-    {
-    int i;
-    uschar sep = ' ';
-    if (no_labels) sep = '\0';
-    for (i = 1; i <= (int)(gidlist[0]); i++)
+    gidlist = *((gid_t **)value);
+    if (!no_labels) printf("%s =", name);
+    if (gidlist)
       {
       {
-      uschar *name = NULL;
-      gr = getgrgid(gidlist[i]);
-      if (gr != NULL) name = US gr->gr_name;
-      if (sep != '\0') printf("%c", sep);
-      if (name != NULL) printf("%s", name);
-        else printf("%ld", (long int)(gidlist[i]));
-      sep = ':';
+      int i;
+      uschar sep = no_labels ? '\0' : ' ';
+      for (i = 1; i <= (int)(gidlist[0]); i++)
+       {
+       uschar *name = NULL;
+       if ((gr = getgrgid(gidlist[i]))) name = US gr->gr_name;
+       if (sep != '\0') printf("%c", sep);
+       if (name) printf("%s", name);
+       else printf("%ld", (long int)(gidlist[i]));
+       sep = ':';
+       }
       }
       }
-    }
-  printf("\n");
-  break;
+    printf("\n");
+    break;
 
   case opt_time:
 
   case opt_time:
-  if (!no_labels) printf("%s = ", name);
-  printf("%s\n", readconf_printtime(*((int *)value)));
-  break;
+    if (!no_labels) printf("%s = ", name);
+    printf("%s\n", readconf_printtime(*((int *)value)));
+    break;
 
   case opt_timelist:
     {
 
   case opt_timelist:
     {
@@ -2431,42 +2636,42 @@ switch(ol->type & opt_mask)
     int *list = (int *)value;
     if (!no_labels) printf("%s = ", name);
     for (i = 0; i < list[1]; i++)
     int *list = (int *)value;
     if (!no_labels) printf("%s = ", name);
     for (i = 0; i < list[1]; i++)
-      printf("%s%s", (i == 0)? "" : ":", readconf_printtime(list[i+2]));
+      printf("%s%s", i == 0 ? "" : ":", readconf_printtime(list[i+2]));
     printf("\n");
     }
     printf("\n");
     }
-  break;
+    break;
 
   case opt_bit:
 
   case opt_bit:
-  printf("%s%s\n", ((*((int *)value)) & (1 << ((ol->type >> 16) & 31)))?
-    "" : "no_", name);
-  break;
+    printf("%s%s\n", ((*((int *)value)) & (1 << ((ol->type >> 16) & 31)))?
+      "" : "no_", name);
+    break;
 
   case opt_expand_bool:
 
   case opt_expand_bool:
-  sprintf(CS name2, "*expand_%.50s", name);
-  ol2 = find_option(name2, oltop, last);
-  if (ol2 != NULL && ol2->value != NULL)
-    {
-    void *value2 = ol2->value;
-    if (options_block != NULL)
-      value2 = (void *)((uschar *)options_block + (long int)value2);
-    s = *((uschar **)value2);
-    if (s != NULL)
+    sprintf(CS name2, "*expand_%.50s", name);
+    if ((ol2 = find_option(name2, oltop, last)) && ol2->value)
       {
       {
-      if (!no_labels) printf("%s = ", name);
-      printf("%s\n", string_printing(s));
-      break;
+      void *value2 = ol2->value;
+      if (options_block)
+       value2 = (void *)(US options_block + (long int)value2);
+      s = *(USS value2);
+      if (s)
+       {
+       if (!no_labels) printf("%s = ", name);
+       printf("%s\n", string_printing(s));
+       break;
+       }
+      /* s == NULL => string not set; fall through */
       }
       }
-    /* s == NULL => string not set; fall through */
-    }
 
 
-  /* Fall through */
+    /* Fall through */
 
   case opt_bool:
   case opt_bool_verify:
   case opt_bool_set:
 
   case opt_bool:
   case opt_bool_verify:
   case opt_bool_set:
-  printf("%s%s\n", (*((BOOL *)value))? "" : "no_", name);
-  break;
+    printf("%s%s\n", (*((BOOL *)value))? "" : "no_", name);
+    break;
   }
   }
+return TRUE;
 }
 
 
 }
 
 
@@ -2480,7 +2685,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
 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
   routers            print the routers' configurations
   transports         print the transports' configuration
   authenticators     print the authenticators' configuration
@@ -2491,6 +2698,7 @@ 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
   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",
   environment        print the used execution environment
 
 If the second argument is not NULL, it must be one of "router", "transport",
@@ -2502,10 +2710,10 @@ Arguments:
   type        NULL or driver type name, as described above
   no_labels   avoid the "foo = " at the start of an item
 
   type        NULL or driver type name, as described above
   no_labels   avoid the "foo = " at the start of an item
 
-Returns:      nothing
+Returns:      Boolean success
 */
 
 */
 
-void
+BOOL
 readconf_print(uschar *name, uschar *type, BOOL no_labels)
 {
 BOOL names_only = FALSE;
 readconf_print(uschar *name, uschar *type, BOOL no_labels)
 {
 BOOL names_only = FALSE;
@@ -2515,7 +2723,7 @@ driver_instance *d = NULL;
 macro_item *m;
 int size = 0;
 
 macro_item *m;
 int size = 0;
 
-if (type == NULL)
+if (!type)
   {
   if (*name == '+')
     {
   {
   if (*name == '+')
     {
@@ -2528,9 +2736,7 @@ if (type == NULL)
       &hostlist_anchor, &localpartlist_anchor };
 
     for (i = 0; i < 4; i++)
       &hostlist_anchor, &localpartlist_anchor };
 
     for (i = 0; i < 4; i++)
-      {
-      t = tree_search(*(anchors[i]), name+1);
-      if (t != NULL)
+      if ((t = tree_search(*(anchors[i]), name+1)))
         {
         found = TRUE;
         if (no_labels)
         {
         found = TRUE;
         if (no_labels)
@@ -2539,47 +2745,50 @@ if (type == NULL)
           printf("%slist %s = %s\n", types[i], name+1,
             ((namedlist_block *)(t->data.ptr))->string);
         }
           printf("%slist %s = %s\n", types[i], name+1,
             ((namedlist_block *)(t->data.ptr))->string);
         }
-      }
 
     if (!found)
       printf("no address, domain, host, or local part list called \"%s\" "
         "exists\n", name+1);
 
 
     if (!found)
       printf("no address, domain, host, or local part list called \"%s\" "
         "exists\n", name+1);
 
-    return;
+    return found;
     }
 
     }
 
-  if (Ustrcmp(name, "configure_file") == 0)
+  if (  Ustrcmp(name, "configure_file") == 0
+     || Ustrcmp(name, "config_file") == 0)
     {
     printf("%s\n", CS config_main_filename);
     {
     printf("%s\n", CS config_main_filename);
-    return;
+    return TRUE;
     }
 
   if (Ustrcmp(name, "all") == 0)
     {
     for (ol = optionlist_config;
     }
 
   if (Ustrcmp(name, "all") == 0)
     {
     for (ol = optionlist_config;
-         ol < optionlist_config + optionlist_config_size; ol++)
-      {
-      if ((ol->type & opt_hidden) == 0)
-        print_ol(ol, US ol->name, NULL,
-            optionlist_config, optionlist_config_size,
-            no_labels);
-      }
-    return;
+         ol < optionlist_config + nelem(optionlist_config); ol++)
+      if (!(ol->type & opt_hidden))
+        (void) print_ol(ol, US ol->name, NULL,
+                 optionlist_config, nelem(optionlist_config),
+                 no_labels);
+    return TRUE;
     }
 
   if (Ustrcmp(name, "local_scan") == 0)
     {
     }
 
   if (Ustrcmp(name, "local_scan") == 0)
     {
-    #ifndef LOCAL_SCAN_HAS_OPTIONS
+#ifndef LOCAL_SCAN_HAS_OPTIONS
     printf("local_scan() options are not supported\n");
     printf("local_scan() options are not supported\n");
-    #else
+    return FALSE;
+#else
     for (ol = local_scan_options;
          ol < local_scan_options + local_scan_options_count; ol++)
     for (ol = local_scan_options;
          ol < local_scan_options + local_scan_options_count; ol++)
-      {
-      print_ol(ol, US ol->name, NULL, local_scan_options,
-        local_scan_options_count, no_labels);
-      }
-    #endif
-    return;
+      (void) print_ol(ol, US ol->name, NULL, local_scan_options,
+                 local_scan_options_count, no_labels);
+    return TRUE;
+#endif
+    }
+
+  if (Ustrcmp(name, "config") == 0)
+    {
+    print_config(f.admin_user, no_labels);
+    return TRUE;
     }
 
   if (Ustrcmp(name, "routers") == 0)
     }
 
   if (Ustrcmp(name, "routers") == 0)
@@ -2592,72 +2801,62 @@ if (type == NULL)
     type = US"transport";
     name = NULL;
     }
     type = US"transport";
     name = NULL;
     }
-
   else if (Ustrcmp(name, "authenticators") == 0)
     {
     type = US"authenticator";
     name = NULL;
     }
   else if (Ustrcmp(name, "authenticators") == 0)
     {
     type = US"authenticator";
     name = NULL;
     }
-
   else if (Ustrcmp(name, "macros") == 0)
     {
     type = US"macro";
     name = NULL;
     }
   else if (Ustrcmp(name, "macros") == 0)
     {
     type = US"macro";
     name = NULL;
     }
-
   else if (Ustrcmp(name, "router_list") == 0)
     {
     type = US"router";
     name = NULL;
     names_only = TRUE;
     }
   else if (Ustrcmp(name, "router_list") == 0)
     {
     type = US"router";
     name = NULL;
     names_only = TRUE;
     }
-
   else if (Ustrcmp(name, "transport_list") == 0)
     {
     type = US"transport";
     name = NULL;
     names_only = TRUE;
     }
   else if (Ustrcmp(name, "transport_list") == 0)
     {
     type = US"transport";
     name = NULL;
     names_only = TRUE;
     }
-
   else if (Ustrcmp(name, "authenticator_list") == 0)
     {
     type = US"authenticator";
     name = NULL;
     names_only = TRUE;
     }
   else if (Ustrcmp(name, "authenticator_list") == 0)
     {
     type = US"authenticator";
     name = NULL;
     names_only = TRUE;
     }
-
   else if (Ustrcmp(name, "macro_list") == 0)
     {
     type = US"macro";
     name = NULL;
     names_only = TRUE;
     }
   else if (Ustrcmp(name, "macro_list") == 0)
     {
     type = US"macro";
     name = NULL;
     names_only = TRUE;
     }
-
   else if (Ustrcmp(name, "environment") == 0)
     {
     if (environ)
       {
   else if (Ustrcmp(name, "environment") == 0)
     {
     if (environ)
       {
-      uschar **p;
-      size_t n;
+      uschar ** p;
       for (p = USS environ; *p; p++) ;
       for (p = USS environ; *p; p++) ;
-      n = p - USS environ;
       qsort(environ, p - USS environ, sizeof(*p), string_compare_by_pointer);
 
       for (p = USS environ; *p; p++)
         {
       qsort(environ, p - USS environ, sizeof(*p), string_compare_by_pointer);
 
       for (p = USS environ; *p; p++)
         {
-        if (no_labels) *(Ustrchr(*p, '=')) = '\0';
-        puts(*p);
+       uschar * q;
+        if (no_labels && (q = Ustrchr(*p, '='))) *q  = '\0';
+        puts(CS *p);
         }
       }
         }
       }
-    return;
+    return TRUE;
     }
 
   else
     }
 
   else
-    {
-    print_ol(find_option(name, optionlist_config, optionlist_config_size),
-      name, NULL, optionlist_config, optionlist_config_size, no_labels);
-    return;
-    }
+    return print_ol(find_option(name,
+      optionlist_config, nelem(optionlist_config)),
+      name, NULL, optionlist_config, nelem(optionlist_config), no_labels);
   }
 
 /* Handle the options for a router or transport. Skip options that are flagged
   }
 
 /* Handle the options for a router or transport. Skip options that are flagged
@@ -2689,57 +2888,60 @@ else if (Ustrcmp(type, "macro") == 0)
   {
   /* People store passwords in macros and they were previously not available
   for printing.  So we have an admin_users restriction. */
   {
   /* People store passwords in macros and they were previously not available
   for printing.  So we have an admin_users restriction. */
-  if (!admin_user)
+  if (!f.admin_user)
     {
     fprintf(stderr, "exim: permission denied\n");
     {
     fprintf(stderr, "exim: permission denied\n");
-    exit(EXIT_FAILURE);
+    return FALSE;
     }
     }
-  for (m = macros; m != NULL; m = m->next)
-    {
-    if (name == NULL || Ustrcmp(name, m->name) == 0)
+  for (m = macros; m; m = m->next)
+    if (!name || Ustrcmp(name, m->name) == 0)
       {
       if (names_only)
         printf("%s\n", CS m->name);
       {
       if (names_only)
         printf("%s\n", CS m->name);
+      else if (no_labels)
+        printf("%s\n", CS m->replacement);
       else
         printf("%s=%s\n", CS m->name, CS m->replacement);
       else
         printf("%s=%s\n", CS m->name, CS m->replacement);
-      if (name != NULL)
-        return;
+      if (name)
+        return TRUE;
       }
       }
-    }
-  if (name != NULL)
-    printf("%s %s not found\n", type, name);
-  return;
+  if (!name) return TRUE;
+
+  printf("%s %s not found\n", type, name);
+  return FALSE;
   }
 
 if (names_only)
   {
   }
 
 if (names_only)
   {
-  for (; d != NULL; d = d->next) printf("%s\n", CS d->name);
-  return;
+  for (; d; d = d->next) printf("%s\n", CS d->name);
+  return TRUE;
   }
 
 /* Either search for a given driver, or print all of them */
 
   }
 
 /* Either search for a given driver, or print all of them */
 
-for (; d != NULL; d = d->next)
+for (; d; d = d->next)
   {
   {
-  if (name == NULL)
+  BOOL rc = FALSE;
+  if (!name)
     printf("\n%s %s:\n", d->name, type);
   else if (Ustrcmp(d->name, name) != 0) continue;
 
   for (ol = ol2; ol < ol2 + size; ol++)
     printf("\n%s %s:\n", d->name, type);
   else if (Ustrcmp(d->name, name) != 0) continue;
 
   for (ol = ol2; ol < ol2 + size; ol++)
-    {
-    if ((ol->type & opt_hidden) == 0)
-      print_ol(ol, US ol->name, d, ol2, size, no_labels);
-    }
+    if (!(ol->type & opt_hidden))
+      rc |= print_ol(ol, US ol->name, d, ol2, size, no_labels);
 
   for (ol = d->info->options;
        ol < d->info->options + *(d->info->options_count); ol++)
 
   for (ol = d->info->options;
        ol < d->info->options + *(d->info->options_count); ol++)
-    {
-    if ((ol->type & opt_hidden) == 0)
-      print_ol(ol, US ol->name, d, d->info->options, *(d->info->options_count), no_labels);
-    }
-  if (name != NULL) return;
+    if (!(ol->type & opt_hidden))
+      rc |= print_ol(ol, US ol->name, d, d->info->options,
+                   *d->info->options_count, no_labels);
+
+  if (name) return rc;
   }
   }
-if (name != NULL) printf("%s %s not found\n", type, name);
+if (!name) return TRUE;
+
+printf("%s %s not found\n", type, name);
+return FALSE;
 }
 
 
 }
 
 
@@ -2877,13 +3079,25 @@ Returns:  bool for "okay"; false will cause caller to immediately exit.
 
 #ifdef SUPPORT_TLS
 static BOOL
 
 #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);
 
 {
 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);
 oldsignal = signal(SIGCHLD, SIG_DFL);
 
 fflush(NULL);
@@ -2897,12 +3111,9 @@ if (pid == 0)
     exim_setugid(exim_uid, exim_gid, FALSE,
         US"calling tls_validate_require_cipher");
 
     exim_setugid(exim_uid, exim_gid, FALSE,
         US"calling tls_validate_require_cipher");
 
-  errmsg = tls_validate_require_cipher();
-  if (errmsg)
-    {
+  if ((errmsg = tls_validate_require_cipher()))
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
         "tls_require_ciphers invalid: %s", errmsg);
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
         "tls_require_ciphers invalid: %s", errmsg);
-    }
   fflush(NULL);
   _exit(0);
   }
   fflush(NULL);
   _exit(0);
   }
@@ -2953,27 +3164,18 @@ systems. Therefore they are available only when requested by compile-time
 options. */
 
 void
 options. */
 
 void
-readconf_main(void)
+readconf_main(BOOL nowarn)
 {
 int sep = 0;
 struct stat statbuf;
 uschar *s, *filename;
 {
 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 */
 
 
 /* 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)))
   {
 
   {
 
-  /* To avoid confusion: Exim changes to / at the very beginning and
-   * and to $spool_directory later. */
-  if (filename[0] != '/')
-    {
-    fprintf(stderr, "-C %s: only absolute names are allowed\n", filename);
-    exit(EXIT_FAILURE);
-  }
-
   /* Cut out all the fancy processing unless specifically wanted */
 
   #if defined(CONFIGURE_FILE_USE_NODE) || defined(CONFIGURE_FILE_USE_EUID)
   /* Cut out all the fancy processing unless specifically wanted */
 
   #if defined(CONFIGURE_FILE_USE_NODE) || defined(CONFIGURE_FILE_USE_EUID)
@@ -3032,9 +3234,44 @@ 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. */
 
 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);
   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];
+      gstring * g;
+
+      if (os_getcwd(buf, PATH_MAX) == NULL)
+        {
+        perror("exim: getcwd");
+        exit(EXIT_FAILURE);
+        }
+      g = string_cat(NULL, buf);
+
+      /* If the dir does not end with a "/", append one */
+      if (g->s[g->ptr-1] != '/')
+        g = string_catn(g, US"/", 1);
+
+      /* If the config file contains a "/", extract the directory part */
+      if (last_slash)
+        g = string_catn(g, filename, last_slash - filename);
+
+      config_main_directory = string_from_gstring(g);
+    }
+  config_directory = config_main_directory;
   }
 else
   {
   }
 else
   {
@@ -3046,10 +3283,19 @@ else
       "configuration file %s", filename));
   }
 
       "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). */
 
 /* 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). */
 
-if (trusted_config && Ustrcmp(filename, US"/dev/null"))
+if (f.trusted_config && Ustrcmp(filename, US"/dev/null"))
   {
   if (fstat(fileno(config_file), &statbuf) != 0)
     log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to stat configuration file %s",
   {
   if (fstat(fileno(config_file), &statbuf) != 0)
     log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to stat configuration file %s",
@@ -3076,9 +3322,14 @@ if (trusted_config && Ustrcmp(filename, US"/dev/null"))
 letter. If we see something starting with an upper case letter, it is taken as
 a macro definition. */
 
 letter. If we see something starting with an upper case letter, it is taken as
 a macro definition. */
 
-while ((s = get_config_line()) != NULL)
+while ((s = get_config_line()))
   {
   {
-  if (isupper(s[0])) read_macro_assignment(s);
+  if (config_lineno == 1 && Ustrstr(s, "\xef\xbb\xbf") == s)
+    log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
+      "found unexpected BOM (Byte Order Mark)");
+
+  if (isupper(s[0]))
+    { if (!macro_read_assignment(s)) exim_exit(EXIT_FAILURE, US""); }
 
   else if (Ustrncmp(s, "domainlist", 10) == 0)
     read_named_list(&domainlist_anchor, &domainlist_count,
 
   else if (Ustrncmp(s, "domainlist", 10) == 0)
     read_named_list(&domainlist_anchor, &domainlist_count,
@@ -3134,7 +3385,7 @@ don't force the case. */
 
 if (primary_hostname == NULL)
   {
 
 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");
   struct utsname uts;
   if (uname(&uts) < 0)
     log_write(0, LOG_MAIN|LOG_PANIC_DIE, "uname() failed to yield host name");
@@ -3147,8 +3398,8 @@ if (primary_hostname == NULL)
 
     #if HAVE_IPV6
     if (!disable_ipv6 && (dns_ipv4_lookup == 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;
       af = AF_INET6;
     #else
     af = AF_INET;
@@ -3208,7 +3459,7 @@ or %M. However, it must NOT contain % followed by anything else. */
 
 if (*log_file_path != 0)
   {
 
 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)
   int sep = ':';                       /* Fixed for log file path */
   s = expand_string(log_file_path);
   if (s == NULL)
@@ -3241,7 +3492,7 @@ if (*log_file_path != 0)
 openlog(). Default is LOG_MAIL set in globals.c. Allow the user to omit the
 leading "log_". */
 
 openlog(). Default is LOG_MAIL set in globals.c. Allow the user to omit the
 leading "log_". */
 
-if (syslog_facility_str != NULL)
+if (syslog_facility_str)
   {
   int i;
   uschar *s = syslog_facility_str;
   {
   int i;
   uschar *s = syslog_facility_str;
@@ -3251,27 +3502,22 @@ if (syslog_facility_str != NULL)
     s += 4;
 
   for (i = 0; i < syslog_list_size; i++)
     s += 4;
 
   for (i = 0; i < syslog_list_size; i++)
-    {
     if (strcmpic(s, syslog_list[i].name) == 0)
       {
       syslog_facility = syslog_list[i].value;
       break;
       }
     if (strcmpic(s, syslog_list[i].name) == 0)
       {
       syslog_facility = syslog_list[i].value;
       break;
       }
-    }
 
   if (i >= syslog_list_size)
 
   if (i >= syslog_list_size)
-    {
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
       "failed to interpret syslog_facility \"%s\"", syslog_facility_str);
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
       "failed to interpret syslog_facility \"%s\"", syslog_facility_str);
-    }
   }
 
 /* Expand pid_file_path */
 
 if (*pid_file_path != 0)
   {
   }
 
 /* Expand pid_file_path */
 
 if (*pid_file_path != 0)
   {
-  s = expand_string(pid_file_path);
-  if (s == NULL)
+  if (!(s = expand_string(pid_file_path)))
     log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to expand pid_file_path "
       "\"%s\": %s", pid_file_path, expand_string_message);
   pid_file_path = s;
     log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to expand pid_file_path "
       "\"%s\": %s", pid_file_path, expand_string_message);
   pid_file_path = s;
@@ -3279,7 +3525,7 @@ if (*pid_file_path != 0)
 
 /* Set default value of process_log_path */
 
 
 /* Set default value of process_log_path */
 
-if (process_log_path == NULL || *process_log_path =='\0')
+if (!process_log_path || *process_log_path =='\0')
   process_log_path = string_sprintf("%s/exim-process.info", spool_directory);
 
 /* Compile the regex for matching a UUCP-style "From_" line in an incoming
   process_log_path = string_sprintf("%s/exim-process.info", spool_directory);
 
 /* Compile the regex for matching a UUCP-style "From_" line in an incoming
@@ -3289,23 +3535,19 @@ regex_From = regex_must_compile(uucp_from_pattern, FALSE, TRUE);
 
 /* Unpick the SMTP rate limiting options, if set */
 
 
 /* Unpick the SMTP rate limiting options, if set */
 
-if (smtp_ratelimit_mail != NULL)
-  {
+if (smtp_ratelimit_mail)
   unpick_ratelimit(smtp_ratelimit_mail, &smtp_rlm_threshold,
     &smtp_rlm_base, &smtp_rlm_factor, &smtp_rlm_limit);
   unpick_ratelimit(smtp_ratelimit_mail, &smtp_rlm_threshold,
     &smtp_rlm_base, &smtp_rlm_factor, &smtp_rlm_limit);
-  }
 
 
-if (smtp_ratelimit_rcpt != NULL)
-  {
+if (smtp_ratelimit_rcpt)
   unpick_ratelimit(smtp_ratelimit_rcpt, &smtp_rlr_threshold,
     &smtp_rlr_base, &smtp_rlr_factor, &smtp_rlr_limit);
   unpick_ratelimit(smtp_ratelimit_rcpt, &smtp_rlr_threshold,
     &smtp_rlr_base, &smtp_rlr_factor, &smtp_rlr_limit);
-  }
 
 /* The qualify domains default to the primary host name */
 
 
 /* The qualify domains default to the primary host name */
 
-if (qualify_domain_sender == NULL)
+if (!qualify_domain_sender)
   qualify_domain_sender = primary_hostname;
   qualify_domain_sender = primary_hostname;
-if (qualify_domain_recipient == NULL)
+if (!qualify_domain_recipient)
   qualify_domain_recipient = qualify_domain_sender;
 
 /* Setting system_filter_user in the configuration sets the gid as well if a
   qualify_domain_recipient = qualify_domain_sender;
 
 /* Setting system_filter_user in the configuration sets the gid as well if a
@@ -3314,7 +3556,7 @@ name is given, but a numerical value does not. */
 if (system_filter_uid_set && !system_filter_gid_set)
   {
   struct passwd *pw = getpwuid(system_filter_uid);
 if (system_filter_uid_set && !system_filter_gid_set)
   {
   struct passwd *pw = getpwuid(system_filter_uid);
-  if (pw == NULL)
+  if (!pw)
     log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Failed to look up uid %ld",
       (long int)system_filter_uid);
   system_filter_gid = pw->pw_gid;
     log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Failed to look up uid %ld",
       (long int)system_filter_uid);
   system_filter_gid = pw->pw_gid;
@@ -3324,14 +3566,14 @@ if (system_filter_uid_set && !system_filter_gid_set)
 /* If the errors_reply_to field is set, check that it is syntactically valid
 and ensure it contains a domain. */
 
 /* If the errors_reply_to field is set, check that it is syntactically valid
 and ensure it contains a domain. */
 
-if (errors_reply_to != NULL)
+if (errors_reply_to)
   {
   uschar *errmess;
   int start, end, domain;
   uschar *recipient = parse_extract_address(errors_reply_to, &errmess,
     &start, &end, &domain, FALSE);
 
   {
   uschar *errmess;
   int start, end, domain;
   uschar *recipient = parse_extract_address(errors_reply_to, &errmess,
     &start, &end, &domain, FALSE);
 
-  if (recipient == NULL)
+  if (!recipient)
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
       "error in errors_reply_to (%s): %s", errors_reply_to, errmess);
 
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
       "error in errors_reply_to (%s): %s", errors_reply_to, errmess);
 
@@ -3353,12 +3595,13 @@ if (smtp_accept_max == 0 &&
 so that it can be computed from the host name, for example. We do this last
 so as to ensure that everything else is set up before the expansion. */
 
 so that it can be computed from the host name, for example. We do this last
 so as to ensure that everything else is set up before the expansion. */
 
-if (host_number_string != NULL)
+if (host_number_string)
   {
   long int n;
   uschar *end;
   uschar *s = expand_string(host_number_string);
   {
   long int n;
   uschar *end;
   uschar *s = expand_string(host_number_string);
-  if (s == NULL)
+
+  if (!s)
     log_write(0, LOG_MAIN|LOG_PANIC_DIE,
         "failed to expand localhost_number \"%s\": %s",
         host_number_string, expand_string_message);
     log_write(0, LOG_MAIN|LOG_PANIC_DIE,
         "failed to expand localhost_number \"%s\": %s",
         host_number_string, expand_string_message);
@@ -3377,15 +3620,14 @@ if (host_number_string != NULL)
 #ifdef SUPPORT_TLS
 /* If tls_verify_hosts is set, tls_verify_certificates must also be set */
 
 #ifdef SUPPORT_TLS
 /* If tls_verify_hosts is set, tls_verify_certificates must also be set */
 
-if ((tls_verify_hosts != NULL || tls_try_verify_hosts != NULL) &&
-     tls_verify_certificates == NULL)
+if ((tls_verify_hosts || tls_try_verify_hosts) && !tls_verify_certificates)
   log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
     "tls_%sverify_hosts is set, but tls_verify_certificates is not set",
   log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
     "tls_%sverify_hosts is set, but tls_verify_certificates is not set",
-    (tls_verify_hosts != NULL)? "" : "try_");
+    tls_verify_hosts ? "" : "try_");
 
 /* 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 */
 
 /* 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
   exit(1);
 
 /* Magic number: at time of writing, 1024 has been the long-standing value
@@ -3396,29 +3638,24 @@ if (tls_dh_max_bits < 1024)
       "tls_dh_max_bits is too small, must be at least 1024 for interop");
 
 /* If openssl_options is set, validate it */
       "tls_dh_max_bits is too small, must be at least 1024 for interop");
 
 /* If openssl_options is set, validate it */
-if (openssl_options != NULL)
+if (openssl_options)
   {
 # ifdef USE_GNUTLS
   log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
     "openssl_options is set but we're using GnuTLS");
 # else
   long dummy;
   {
 # ifdef USE_GNUTLS
   log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
     "openssl_options is set but we're using GnuTLS");
 # else
   long dummy;
-  if (!(tls_openssl_options_parse(openssl_options, &dummy)))
+  if (!tls_openssl_options_parse(openssl_options, &dummy))
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
       "openssl_options parse error: %s", openssl_options);
 # endif
   }
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
       "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*/
 
 #endif /*SUPPORT_TLS*/
 
-if ((!add_environment || *add_environment == '\0') && !keep_environment)
+if (!nowarn && !keep_environment && environ && *environ)
   log_write(0, LOG_MAIN,
   log_write(0, LOG_MAIN,
-      "WARNING: purging the environment.\n"
-      " Suggested action: use keep_environment and add_environment.\n");
+      "Warning: purging the environment.\n"
+      " Suggested action: use keep_environment.");
 }
 
 
 }
 
 
@@ -3449,7 +3686,7 @@ init_driver(driver_instance *d, driver_info *drivers_available,
 driver_info *dd;
 
 for (dd = drivers_available; dd->driver_name[0] != 0;
 driver_info *dd;
 
 for (dd = drivers_available; dd->driver_name[0] != 0;
-     dd = (driver_info *)(((uschar *)dd) + size_of_info))
+     dd = (driver_info *)((US dd) + size_of_info))
   {
   if (Ustrcmp(d->driver_name, dd->driver_name) == 0)
     {
   {
   if (Ustrcmp(d->driver_name, dd->driver_name) == 0)
     {
@@ -3531,15 +3768,15 @@ while ((buffer = get_config_line()) != NULL)
 
   if (isupper(*name) && *s == '=')
     {
 
   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);
       d = NULL;
       }
         log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
           "no driver defined for %s \"%s\"", class, d->name);
       (d->info->init)(d);
       d = NULL;
       }
-    read_macro_assignment(buffer);
+    if (!macro_read_assignment(buffer)) exim_exit(EXIT_FAILURE, US"");
     continue;
     }
 
     continue;
     }
 
@@ -3553,9 +3790,9 @@ while ((buffer = get_config_line()) != NULL)
 
     /* Finish off initializing the previous driver. */
 
 
     /* 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);
         log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
           "no driver defined for %s \"%s\"", class, d->name);
       (d->info->init)(d);
@@ -3563,7 +3800,7 @@ while ((buffer = get_config_line()) != NULL)
 
     /* Check that we haven't already got a driver of this name */
 
 
     /* 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);
       if (Ustrcmp(name, d->name) == 0)
         log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
           "there are two %ss called \"%s\"", class, name);
@@ -3574,7 +3811,7 @@ while ((buffer = get_config_line()) != NULL)
     d = store_get(instance_size);
     memcpy(d, instance_default, instance_size);
     *p = d;
     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 */
     d->name = string_copy(name);
 
     /* Clear out the "set" bits in the generic options */
@@ -3592,8 +3829,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. */
 
   /* 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
 
   /* 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
@@ -3602,7 +3839,7 @@ while ((buffer = get_config_line()) != NULL)
   if (readconf_handle_option(buffer, driver_optionlist,
         driver_optionlist_count, d, 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);
     }
 
       init_driver(d, drivers_available, size_of_info, class);
     }
 
@@ -3610,11 +3847,9 @@ while ((buffer = get_config_line()) != NULL)
   live therein. A flag with each option indicates if it is in the public
   block. */
 
   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");
     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. */
 
 
   /* The option is not generic and the driver name has not yet been given. */
 
@@ -3624,9 +3859,9 @@ while ((buffer = get_config_line()) != NULL)
 
 /* Run the initialization function for the final driver. */
 
 
 /* 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);
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
       "no driver defined for %s \"%s\"", class, d->name);
   (d->info->init)(d);
@@ -3664,7 +3899,7 @@ for (ol = d->info->options; ol < d->info->options + count; ol++)
   int type = ol->type & opt_mask;
   if (type != opt_stringptr) continue;
   options_block = ((ol->type & opt_public) == 0)? d->options_block : (void *)d;
   int type = ol->type & opt_mask;
   if (type != opt_stringptr) continue;
   options_block = ((ol->type & opt_public) == 0)? d->options_block : (void *)d;
-  value = *(uschar **)((uschar *)options_block + (long int)(ol->value));
+  value = *(uschar **)(US options_block + (long int)(ol->value));
   if (value != NULL && (ss = Ustrstr(value, s)) != NULL)
     {
     if (ss <= value || (ss[-1] != '$' && ss[-1] != '{') ||
   if (value != NULL && (ss = Ustrstr(value, s)) != NULL)
     {
     if (ss <= value || (ss[-1] != '$' && ss[-1] != '{') ||
@@ -3700,10 +3935,11 @@ Returns:       NULL if decoded correctly; else points to error text
 */
 
 uschar *
 */
 
 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;
 {
 int len;
-uschar *q = pp;
+const uschar *q = pp;
 while (q < p && *q != '_') q++;
 len = q - pp;
 
 while (q < p && *q != '_') q++;
 len = q - pp;
 
@@ -3732,32 +3968,27 @@ else if (len == 7 && strncmpic(pp, US"timeout", len) == 0)
     {
     int i;
     int xlen = p - q - 1;
     {
     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" };
     static int values[] =
       { 'A',   'M',    RTEF_CTOUT,  RTEF_CTOUT|'A', RTEF_CTOUT|'M' };
 
 
     static uschar *extras[] =
       { US"A", US"MX", US"connect", US"connect_A",  US"connect_MX" };
     static int values[] =
       { 'A',   'M',    RTEF_CTOUT,  RTEF_CTOUT|'A', RTEF_CTOUT|'M' };
 
-    for (i = 0; i < sizeof(extras)/sizeof(uschar *); i++)
-      {
+    for (i = 0; i < nelem(extras); i++)
       if (strncmpic(x, extras[i], xlen) == 0)
         {
         *more_errno = values[i];
         break;
         }
       if (strncmpic(x, extras[i], xlen) == 0)
         {
         *more_errno = values[i];
         break;
         }
-      }
 
 
-    if (i >= sizeof(extras)/sizeof(uschar *))
-      {
+    if (i >= nelem(extras))
       if (strncmpic(x, US"DNS", xlen) == 0)
       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\"");
         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\"";
     }
   }
 
     }
   }
 
@@ -3784,8 +4015,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);
 
     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;
   }
 
   *more_errno = x << 8;
   }
 
@@ -3799,6 +4030,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"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);
 
 else if (len != 1 || Ustrncmp(pp, "*", 1) != 0)
   return string_sprintf("unknown or malformed retry error \"%.*s\"", (int) (p-pp), pp);
 
@@ -3836,10 +4070,10 @@ Returns:    time in seconds or fixed point number * 1000
 */
 
 static int
 */
 
 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");
 
 
 if (*p++ != ',') log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "comma expected");
 
@@ -3853,10 +4087,8 @@ if (*p != 0 && !isspace(*p) && *p != ',' && *p != ';')
 *paddr = p;
 switch (type)
   {
 *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 */
 }
   }
 return 0;    /* Keep picky compilers happy */
 }
@@ -3868,12 +4100,13 @@ readconf_retries(void)
 {
 retry_config **chain = &retries;
 retry_config *next;
 {
 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;
   {
   retry_rule **rchain;
-  uschar *pp, *error;
+  const uschar *pp;
+  uschar *error;
 
   next = store_get(sizeof(retry_config));
   next->next = NULL;
 
   next = store_get(sizeof(retry_config));
   next->next = NULL;
@@ -3893,8 +4126,8 @@ while ((p = get_config_line()) != NULL)
 
   /* Test error names for things we understand. */
 
 
   /* 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
     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
@@ -3931,18 +4164,18 @@ while ((p = get_config_line()) != NULL)
     switch (rule->rule)
       {
       case 'F':   /* Fixed interval */
     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 */
 
       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:
 
       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 ||
       }
 
     if (rule->timeout <= 0 || rule->p1 <= 0 ||
@@ -3978,6 +4211,10 @@ static void
 auths_init(void)
 {
 auth_instance *au, *bu;
 auths_init(void)
 {
 auth_instance *au, *bu;
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+int nauths = 0;
+#endif
+
 readconf_driver_init(US"authenticator",
   (driver_instance **)(&auths),      /* chain anchor */
   (driver_info *)auths_available,    /* available drivers */
 readconf_driver_init(US"authenticator",
   (driver_instance **)(&auths),      /* chain anchor */
   (driver_info *)auths_available,    /* available drivers */
@@ -3987,23 +4224,26 @@ readconf_driver_init(US"authenticator",
   optionlist_auths,                  /* generic options */
   optionlist_auths_size);
 
   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);
     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 (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)",
       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);
           au->public_name);
-      }
-    }
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+  nauths++;
+#endif
   }
   }
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+f.smtp_in_early_pipe_no_auth = nauths > 16;
+#endif
 }
 
 
 }
 
 
@@ -4046,7 +4286,7 @@ between ACLs. */
 
 acl_line = get_config_line();
 
 
 acl_line = get_config_line();
 
-while(acl_line != NULL)
+while(acl_line)
   {
   uschar name[64];
   tree_node *node;
   {
   uschar name[64];
   tree_node *node;
@@ -4055,7 +4295,7 @@ while(acl_line != NULL)
   p = readconf_readname(name, sizeof(name), acl_line);
   if (isupper(*name) && *p == '=')
     {
   p = readconf_readname(name, sizeof(name), acl_line);
   if (isupper(*name) && *p == '=')
     {
-    read_macro_assignment(acl_line);
+    if (!macro_read_assignment(acl_line)) exim_exit(EXIT_FAILURE, US"");
     acl_line = get_config_line();
     continue;
     }
     acl_line = get_config_line();
     continue;
     }
@@ -4099,7 +4339,7 @@ log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "local_scan() options not supported: "
 #else
 
 uschar *p;
 #else
 
 uschar *p;
-while ((p = get_config_line()) != NULL)
+while ((p = get_config_line()))
   {
   (void) readconf_handle_option(p, local_scan_options, local_scan_options_count,
     NULL, US"local_scan option \"%s\" unknown");
   {
   (void) readconf_handle_option(p, local_scan_options, local_scan_options_count,
     NULL, US"local_scan option \"%s\" unknown");
@@ -4147,7 +4387,7 @@ while(next_section[0] != 0)
   {
   int bit;
   int first = 0;
   {
   int bit;
   int first = 0;
-  int last = sizeof(section_list) / sizeof(uschar *);
+  int last = nelem(section_list);
   int mid = last/2;
   int n = Ustrlen(next_section);
 
   int mid = last/2;
   int n = Ustrlen(next_section);
 
@@ -4185,6 +4425,124 @@ while(next_section[0] != 0)
 (void)fclose(config_file);
 }
 
 (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)",
+  f.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);
+  }
+}
+
+#endif /*!MACRO_PREDEF*/
 /* vi: aw ai sw=2
 */
 /* End of readconf.c */
 /* vi: aw ai sw=2
 */
 /* End of readconf.c */
index f27dc42..a0467e8 100644 (file)
@@ -2,12 +2,13 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Code for receiving a message and setting up spool files. */
 
 #include "exim.h"
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Code for receiving a message and setting up spool files. */
 
 #include "exim.h"
+#include <setjmp.h>
 
 #ifdef EXPERIMENTAL_DCC
 extern int dcc_ok;
 
 #ifdef EXPERIMENTAL_DCC
 extern int dcc_ok;
@@ -21,10 +22,16 @@ extern int dcc_ok;
 *                Local static variables          *
 *************************************************/
 
 *                Local static variables          *
 *************************************************/
 
-static FILE   *data_file = NULL;
 static int     data_fd = -1;
 static int     data_fd = -1;
-static uschar  spool_name[256];
+static uschar *spool_name = US"";
 
 
+enum CH_STATE {LF_SEEN, MID_LINE, CR_SEEN};
+
+#ifdef HAVE_LOCAL_SCAN
+jmp_buf local_scan_env;                /* error-handling context for local_scan */
+unsigned had_local_scan_crash;
+unsigned had_local_scan_timeout;
+#endif
 
 
 /*************************************************
 
 
 /*************************************************
@@ -37,9 +44,29 @@ the file. (When SMTP input is occurring, different functions are used by
 changing the pointer variables.) */
 
 int
 changing the pointer variables.) */
 
 int
-stdin_getc(void)
+stdin_getc(unsigned lim)
 {
 {
-return getc(stdin);
+int c = getc(stdin);
+
+if (had_data_timeout)
+  {
+  fprintf(stderr, "exim: timed out while reading - message abandoned\n");
+  log_write(L_lost_incoming_connection,
+            LOG_MAIN, "timed out while reading local message");
+  receive_bomb_out(US"data-timeout", NULL);   /* Does not return */
+  }
+if (had_data_sigint)
+  {
+  if (filter_test == FTEST_NONE)
+    {
+    fprintf(stderr, "\nexim: %s received - message abandoned\n",
+      had_data_sigint == SIGTERM ? "SIGTERM" : "SIGINT");
+    log_write(0, LOG_MAIN, "%s received while reading local message",
+      had_data_sigint == SIGTERM ? "SIGTERM" : "SIGINT");
+    }
+  receive_bomb_out(US"signal-exit", NULL);    /* Does not return */
+  }
+return c;
 }
 
 int
 }
 
 int
@@ -82,13 +109,11 @@ BOOL
 receive_check_set_sender(uschar *newsender)
 {
 uschar *qnewsender;
 receive_check_set_sender(uschar *newsender)
 {
 uschar *qnewsender;
-if (trusted_caller) return TRUE;
-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,
-    0, NULL) == OK;
+if (f.trusted_caller) return TRUE;
+if (!newsender || !untrusted_set_sender) return FALSE;
+qnewsender = Ustrchr(newsender, '@')
+  ? newsender : string_sprintf("%s@%s", newsender, qualify_domain_sender);
+return match_address_list_basic(qnewsender, CUSS &untrusted_set_sender, 0) == OK;
 }
 
 
 }
 
 
@@ -119,11 +144,12 @@ Returns:        available on-root space, in kilobytes
 All values are -1 if the STATFS functions are not available.
 */
 
 All values are -1 if the STATFS functions are not available.
 */
 
-int
+int_eximarith_t
 receive_statvfs(BOOL isspool, int *inodeptr)
 {
 #ifdef HAVE_STATFS
 struct STATVFS statbuf;
 receive_statvfs(BOOL isspool, int *inodeptr)
 {
 #ifdef HAVE_STATFS
 struct STATVFS statbuf;
+struct stat dummy;
 uschar *path;
 uschar *name;
 uschar buffer[1024];
 uschar *path;
 uschar *name;
 uschar buffer[1024];
@@ -142,17 +168,16 @@ appearance of "syslog" in it. */
 else
   {
   int sep = ':';              /* Not variable - outside scripts use */
 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":";
   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 */
     {
 
   if (path == NULL)  /* No log files */
     {
@@ -181,22 +206,28 @@ else
 memset(&statbuf, 0, sizeof(statbuf));
 
 if (STATVFS(CS path, &statbuf) != 0)
 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, NULL);
+    }
 
 *inodeptr = (statbuf.F_FILES > 0)? statbuf.F_FAVAIL : -1;
 
 /* Disks are getting huge. Take care with computing the size in kilobytes. */
 
 
 *inodeptr = (statbuf.F_FILES > 0)? statbuf.F_FAVAIL : -1;
 
 /* Disks are getting huge. Take care with computing the size in kilobytes. */
 
-return (int)(((double)statbuf.F_BAVAIL * (double)statbuf.F_FRSIZE)/1024.0);
+return (int_eximarith_t)(((double)statbuf.F_BAVAIL * (double)statbuf.F_FRSIZE)/1024.0);
 
 
+#else
 /* Unable to find partition sizes in this environment. */
 
 /* Unable to find partition sizes in this environment. */
 
-#else
 *inodeptr = -1;
 return -1;
 #endif
 *inodeptr = -1;
 return -1;
 #endif
@@ -227,22 +258,23 @@ Returns:       FALSE if there isn't enough space, or if the information cannot
 BOOL
 receive_check_fs(int msg_size)
 {
 BOOL
 receive_check_fs(int msg_size)
 {
-int space, inodes;
+int_eximarith_t space;
+int inodes;
 
 if (check_spool_space > 0 || msg_size > 0 || check_spool_inodes > 0)
   {
   space = receive_statvfs(TRUE, &inodes);
 
   DEBUG(D_receive)
 
 if (check_spool_space > 0 || msg_size > 0 || check_spool_inodes > 0)
   {
   space = receive_statvfs(TRUE, &inodes);
 
   DEBUG(D_receive)
-    debug_printf("spool directory space = %dK inodes = %d "
-      "check_space = %dK inodes = %d msg_size = %d\n",
+    debug_printf("spool directory space = " PR_EXIM_ARITH "K inodes = %d "
+      "check_space = " PR_EXIM_ARITH "K inodes = %d msg_size = %d\n",
       space, inodes, check_spool_space, check_spool_inodes, msg_size);
 
   if ((space >= 0 && space < check_spool_space) ||
       (inodes >= 0 && inodes < check_spool_inodes))
     {
       space, inodes, check_spool_space, check_spool_inodes, msg_size);
 
   if ((space >= 0 && space < check_spool_space) ||
       (inodes >= 0 && inodes < check_spool_inodes))
     {
-    log_write(0, LOG_MAIN, "spool directory space check failed: space=%d "
-      "inodes=%d", space, inodes);
+    log_write(0, LOG_MAIN, "spool directory space check failed: space="
+      PR_EXIM_ARITH " inodes=%d", space, inodes);
     return FALSE;
     }
   }
     return FALSE;
     }
   }
@@ -252,15 +284,15 @@ if (check_log_space > 0 || check_log_inodes > 0)
   space = receive_statvfs(FALSE, &inodes);
 
   DEBUG(D_receive)
   space = receive_statvfs(FALSE, &inodes);
 
   DEBUG(D_receive)
-    debug_printf("log directory space = %dK inodes = %d "
-      "check_space = %dK inodes = %d\n",
+    debug_printf("log directory space = " PR_EXIM_ARITH "K inodes = %d "
+      "check_space = " PR_EXIM_ARITH "K inodes = %d\n",
       space, inodes, check_log_space, check_log_inodes);
 
       space, inodes, check_log_space, check_log_inodes);
 
-  if ((space >= 0 && space < check_log_space) ||
-      (inodes >= 0 && inodes < check_log_inodes))
+  if (  space >= 0 && space < check_log_space
+     || inodes >= 0 && inodes < check_log_inodes)
     {
     {
-    log_write(0, LOG_MAIN, "log directory space check failed: space=%d "
-      "inodes=%d", space, inodes);
+    log_write(0, LOG_MAIN, "log directory space check failed: space=" PR_EXIM_ARITH
+      " inodes=%d", space, inodes);
     return FALSE;
     }
   }
     return FALSE;
     }
   }
@@ -311,11 +343,13 @@ if (spool_name[0] != '\0')
 
 /* Now close the file if it is open, either as a fd or a stream. */
 
 
 /* Now close the file if it is open, either as a fd or a stream. */
 
-if (data_file != NULL)
+if (spool_data_file)
+  {
+  (void)fclose(spool_data_file);
+  spool_data_file = NULL;
+  }
+else if (data_fd >= 0)
   {
   {
-  (void)fclose(data_file);
-  data_file = NULL;
-} else if (data_fd >= 0) {
   (void)close(data_fd);
   data_fd = -1;
   }
   (void)close(data_fd);
   data_fd = -1;
   }
@@ -338,7 +372,7 @@ if (!already_bombing_out)
 
 /* Exit from the program (non-BSMTP cases) */
 
 
 /* Exit from the program (non-BSMTP cases) */
 
-exim_exit(EXIT_FAILURE);
+exim_exit(EXIT_FAILURE, NULL);
 }
 
 
 }
 
 
@@ -356,37 +390,29 @@ Returns:   nothing
 static void
 data_timeout_handler(int sig)
 {
 static void
 data_timeout_handler(int sig)
 {
-uschar *msg = NULL;
-
-sig = sig;    /* Keep picky compilers happy */
-
-if (smtp_input)
-  {
-  msg = US"SMTP incoming data timeout";
-  log_write(L_lost_incoming_connection,
-            LOG_MAIN, "SMTP data timeout (message abandoned) on connection "
-            "from %s F=<%s>",
-            (sender_fullhost != NULL)? sender_fullhost : US"local process",
-            sender_address);
-  }
-else
-  {
-  fprintf(stderr, "exim: timed out while reading - message abandoned\n");
-  log_write(L_lost_incoming_connection,
-            LOG_MAIN, "timed out while reading local message");
-  }
-
-receive_bomb_out(US"data-timeout", msg);   /* Does not return */
+had_data_timeout = sig;
 }
 
 
 
 }
 
 
 
+#ifdef HAVE_LOCAL_SCAN
 /*************************************************
 *              local_scan() timeout              *
 *************************************************/
 
 /* Handler function for timeouts that occur while running a local_scan()
 /*************************************************
 *              local_scan() timeout              *
 *************************************************/
 
 /* Handler function for timeouts that occur while running a local_scan()
-function.
+function.  Posix recommends against calling longjmp() from a signal-handler,
+but the GCC manual says you can so we will, and trust that it's better than
+calling probably non-signal-safe funxtions during logging from within the
+handler, even with other compilers.
+
+See also https://cwe.mitre.org/data/definitions/745.html which also lists
+it as unsafe.
+
+This is all because we have no control over what might be written for a
+local-scan function, so cannot sprinkle had-signal checks after each
+call-site.  At least with the default "do-nothing" function we won't
+ever get here.
 
 Argument:  the signal number
 Returns:   nothing
 
 Argument:  the signal number
 Returns:   nothing
@@ -395,11 +421,8 @@ Returns:   nothing
 static void
 local_scan_timeout_handler(int sig)
 {
 static void
 local_scan_timeout_handler(int sig)
 {
-sig = sig;    /* Keep picky compilers happy */
-log_write(0, LOG_MAIN|LOG_REJECT, "local_scan() function timed out - "
-  "message temporarily rejected (size %d)", message_size);
-/* Does not return */
-receive_bomb_out(US"local-scan-timeout", US"local verification problem");
+had_local_scan_timeout = sig;
+siglongjmp(local_scan_env, 1);
 }
 
 
 }
 
 
@@ -418,12 +441,12 @@ Returns:   nothing
 static void
 local_scan_crash_handler(int sig)
 {
 static void
 local_scan_crash_handler(int sig)
 {
-log_write(0, LOG_MAIN|LOG_REJECT, "local_scan() function crashed with "
-  "signal %d - message temporarily rejected (size %d)", sig, message_size);
-/* Does not return */
-receive_bomb_out(US"local-scan-error", US"local verification problem");
+had_local_scan_crash = sig;
+siglongjmp(local_scan_env, 1);
 }
 
 }
 
+#endif /*HAVE_LOCAL_SCAN*/
+
 
 /*************************************************
 *           SIGTERM or SIGINT received           *
 
 /*************************************************
 *           SIGTERM or SIGINT received           *
@@ -439,26 +462,7 @@ Returns:   nothing
 static void
 data_sigterm_sigint_handler(int sig)
 {
 static void
 data_sigterm_sigint_handler(int sig)
 {
-uschar *msg = NULL;
-
-if (smtp_input)
-  {
-  msg = US"Service not available - SIGTERM or SIGINT received";
-  log_write(0, LOG_MAIN, "%s closed after %s", smtp_get_connection_info(),
-    (sig == SIGTERM)? "SIGTERM" : "SIGINT");
-  }
-else
-  {
-  if (filter_test == FTEST_NONE)
-    {
-    fprintf(stderr, "\nexim: %s received - message abandoned\n",
-      (sig == SIGTERM)? "SIGTERM" : "SIGINT");
-    log_write(0, LOG_MAIN, "%s received while reading local message",
-      (sig == SIGTERM)? "SIGTERM" : "SIGINT");
-    }
-  }
-
-receive_bomb_out(US"signal-exit", msg);    /* Does not return */
+had_data_sigint = sig;
 }
 
 
 }
 
 
@@ -484,7 +488,7 @@ if (recipients_count >= recipients_list_max)
   {
   recipient_item *oldlist = recipients_list;
   int oldmax = recipients_list_max;
   {
   recipient_item *oldlist = recipients_list;
   int oldmax = recipients_list_max;
-  recipients_list_max = recipients_list_max? 2*recipients_list_max : 50;
+  recipients_list_max = recipients_list_max ? 2*recipients_list_max : 50;
   recipients_list = store_get(recipients_list_max * sizeof(recipient_item));
   if (oldlist != NULL)
     memcpy(recipients_list, oldlist, oldmax * sizeof(recipient_item));
   recipients_list = store_get(recipients_list_max * sizeof(recipient_item));
   if (oldlist != NULL)
     memcpy(recipients_list, oldlist, oldmax * sizeof(recipient_item));
@@ -497,10 +501,8 @@ recipients_list[recipients_count].bmi_optin = bmi_current_optin;
 /* reset optin string pointer for next recipient */
 bmi_current_optin = NULL;
 #endif
 /* 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;
 recipients_list[recipients_count].orcpt = NULL;
 recipients_list[recipients_count].dsn_flags = 0;
-#endif
 recipients_list[recipients_count++].errors_to = NULL;
 }
 
 recipients_list[recipients_count++].errors_to = NULL;
 }
 
@@ -528,7 +530,7 @@ static void
 smtp_user_msg(uschar *code, uschar *user_msg)
 {
 int len = 3;
 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
 smtp_respond(code, len, TRUE, user_msg);
 }
 #endif
@@ -618,11 +620,11 @@ register int linelength = 0;
 
 /* Handle the case when only EOF terminates the message */
 
 
 /* Handle the case when only EOF terminates the message */
 
-if (!dot_ends)
+if (!f.dot_ends)
   {
   register int last_ch = '\n';
 
   {
   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')
     {
     if (ch == 0) body_zerocount++;
     if (last_ch == '\r' && ch != '\n')
@@ -664,7 +666,7 @@ if (!dot_ends)
 
 ch_state = 1;
 
 
 ch_state = 1;
 
-while ((ch = (receive_getc)()) != EOF)
+while ((ch = (receive_getc)(GETC_BUFFER_UNLIMITED)) != EOF)
   {
   if (ch == 0) body_zerocount++;
   switch (ch_state)
   {
   if (ch == 0) body_zerocount++;
   switch (ch_state)
@@ -685,7 +687,8 @@ while ((ch = (receive_getc)()) != EOF)
     case 1:                         /* After written "\n" */
     if (ch == '.') { ch_state = 3; continue; }
     if (ch == '\r') { ch_state = 2; continue; }
     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:
     break;
 
     case 2:
@@ -779,9 +782,9 @@ read_message_data_smtp(FILE *fout)
 {
 int ch_state = 0;
 int ch;
 {
 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)
   {
   if (ch == 0) body_zerocount++;
   switch (ch_state)
@@ -825,7 +828,7 @@ while ((ch = (receive_getc)()) != EOF)
       {
       message_size++;
       if (fout != NULL && fputc('\n', fout) == EOF) return END_WERROR;
       {
       message_size++;
       if (fout != NULL && fputc('\n', fout) == EOF) return END_WERROR;
-      (void) cutthrough_put_nl();
+      cutthrough_data_put_nl();
       if (ch != '\r') ch_state = 1; else continue;
       }
     break;
       if (ch != '\r') ch_state = 1; else continue;
       }
     break;
@@ -838,7 +841,15 @@ while ((ch = (receive_getc)()) != EOF)
       ch_state = 4;
       continue;
       }
       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;
+      cutthrough_data_puts(&c, 1);
+      }
+    ch_state = 1;
     break;
 
     case 4:                             /* After [CR] LF . CR */
     break;
 
     case 4:                             /* After [CR] LF . CR */
@@ -846,7 +857,7 @@ while ((ch = (receive_getc)()) != EOF)
     message_size++;
     body_linecount++;
     if (fout != NULL && fputc('\n', fout) == EOF) return END_WERROR;
     message_size++;
     body_linecount++;
     if (fout != NULL && fputc('\n', fout) == EOF) return END_WERROR;
-    (void) cutthrough_put_nl();
+    cutthrough_data_put_nl();
     if (ch == '\r')
       {
       ch_state = 2;
     if (ch == '\r')
       {
       ch_state = 2;
@@ -861,17 +872,17 @@ while ((ch = (receive_getc)()) != EOF)
 
   message_size++;
   linelength++;
 
   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;
     }
   if(ch == '\n')
     {
     if (fputc(ch, fout) == EOF) return END_WERROR;
     if (message_size > thismessage_size_limit) return END_SIZE;
     }
   if(ch == '\n')
-    (void) cutthrough_put_nl();
+    cutthrough_data_put_nl();
   else
     {
   else
     {
-    uschar c= ch;
-    (void) cutthrough_puts(&c, 1);
+    uschar c = ch;
+    cutthrough_data_puts(&c, 1);
     }
   }
 
     }
   }
 
@@ -884,6 +895,173 @@ 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;
+       cutthrough_data_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')
+    cutthrough_data_put_nl();
+  else
+    {
+    uschar c = ch;
+    cutthrough_data_puts(&c, 1);
+    }
+  }
+/*NOTREACHED*/
+}
+
+static int
+read_message_bdat_smtp_wire(FILE *fout)
+{
+int ch;
+
+/* Remember that this message uses wireformat. */
+
+DEBUG(D_receive) debug_printf("CHUNKING: %s\n",
+       fout ? "writing spoolfile in wire format" : "flushing input");
+f.spool_file_wireformat = TRUE;
+
+for (;;)
+  {
+  if (chunking_data_left > 0)
+    {
+    unsigned len = MAX(chunking_data_left, thismessage_size_limit - message_size + 1);
+    uschar * buf = bdat_getbuf(&len);
+
+    if (!buf) return END_EOF;
+    message_size += len;
+    if (fout && fwrite(buf, len, 1, fout) != 1) return END_WERROR;
+    }
+  else switch (ch = bdat_getc(GETC_BUFFER_UNLIMITED))
+    {
+    case EOF: return END_EOF;
+    case EOD: return END_DOT;
+    case ERR: return END_PROTOCOL;
+
+    default:
+      message_size++;
+  /*XXX not done:
+  linelength
+  max_received_linelength
+  body_linecount
+  body_zerocount
+  */
+      if (fout && fputc(ch, fout) == EOF) return END_WERROR;
+      break;
+    }
+  if (message_size > thismessage_size_limit) return END_SIZE;
+  }
+/*NOTREACHED*/
+}
+
+
+
+
 /*************************************************
 *             Swallow SMTP message               *
 *************************************************/
 /*************************************************
 *             Swallow SMTP message               *
 *************************************************/
@@ -901,7 +1079,9 @@ void
 receive_swallow_smtp(void)
 {
 if (message_ended >= END_NOTENDED)
 receive_swallow_smtp(void)
 {
 if (message_ended >= END_NOTENDED)
-  message_ended = read_message_data_smtp(NULL);
+  message_ended = chunking_state <= CHUNKING_OFFERED
+     ? read_message_data_smtp(NULL)
+     : read_message_bdat_smtp_wire(NULL);
 }
 
 
 }
 
 
@@ -922,6 +1102,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);
 {
 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";
 }
 
 return US"421 Lost incoming connection";
 }
 
@@ -956,12 +1137,14 @@ if (error_handling == ERRORS_SENDER)
   error_block eblock;
   eblock.next = NULL;
   eblock.text1 = text1;
   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;
   }
   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);
 (void)fclose(f);
-exim_exit(error_rc);
+exim_exit(error_rc, US"");
 }
 
 
 }
 
 
@@ -999,7 +1182,8 @@ switch(where)
   case ACL_WHERE_DKIM:
   case ACL_WHERE_MIME:
   case ACL_WHERE_DATA:
   case ACL_WHERE_DKIM:
   case ACL_WHERE_MIME:
   case ACL_WHERE_DATA:
-    if (cutthrough_fd >= 0 && (acl_removed_headers || acl_added_headers))
+    if (  cutthrough.cctx.sock >= 0 && cutthrough.delivery
+       && (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");
     {
     log_write(0, LOG_MAIN|LOG_PANIC, "Header modification in data ACLs"
                        " will not take effect on cutthrough deliveries");
@@ -1007,90 +1191,81 @@ switch(where)
     }
   }
 
     }
   }
 
-if (acl_removed_headers != NULL)
+if (acl_removed_headers)
   {
   {
-  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; 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];
     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;
        {
        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;
     }
   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);
+if (!acl_added_headers) return;
+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)
+for (h = acl_added_headers; h; h = next)
   {
   next = h->next;
 
   switch(h->type)
     {
     case htype_add_top:
   {
   next = h->next;
 
   switch(h->type)
     {
     case htype_add_top:
-    h->next = header_list;
-    header_list = h;
-    DEBUG(D_receive|D_acl) debug_printf("  (at top)");
-    break;
+      h->next = header_list;
+      header_list = h;
+      DEBUG(D_receive|D_acl) debug_printf_indent("  (at top)");
+      break;
 
     case htype_add_rec:
 
     case htype_add_rec:
-    if (last_received == NULL)
-      {
-      last_received = header_list;
-      while (!header_testname(last_received, US"Received", 8, FALSE))
-        last_received = last_received->next;
-      while (last_received->next != NULL &&
-             header_testname(last_received->next, US"Received", 8, FALSE))
-        last_received = last_received->next;
-      }
-    h->next = last_received->next;
-    last_received->next = h;
-    DEBUG(D_receive|D_acl) debug_printf("  (after Received:)");
-    break;
+      if (!last_received)
+       {
+       last_received = header_list;
+       while (!header_testname(last_received, US"Received", 8, FALSE))
+         last_received = last_received->next;
+       while (last_received->next &&
+              header_testname(last_received->next, US"Received", 8, FALSE))
+         last_received = last_received->next;
+       }
+      h->next = last_received->next;
+      last_received->next = h;
+      DEBUG(D_receive|D_acl) debug_printf_indent("  (after Received:)");
+      break;
 
     case htype_add_rfc:
 
     case htype_add_rfc:
-    /* add header before any header which is NOT Received: or Resent- */
-    last_received = header_list;
-    while ( (last_received->next != NULL) &&
-            ( (header_testname(last_received->next, US"Received", 8, FALSE)) ||
-              (header_testname_incomplete(last_received->next, US"Resent-", 7, FALSE)) ) )
-              last_received = last_received->next;
-    /* last_received now points to the last Received: or Resent-* header
-       in an uninterrupted chain of those header types (seen from the beginning
-       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)");
-    break;
+      /* add header before any header which is NOT Received: or Resent- */
+      last_received = header_list;
+      while ( last_received->next &&
+             ( (header_testname(last_received->next, US"Received", 8, FALSE)) ||
+               (header_testname_incomplete(last_received->next, US"Resent-", 7, FALSE)) ) )
+               last_received = last_received->next;
+      /* last_received now points to the last Received: or Resent-* header
+        in an uninterrupted chain of those header types (seen from the beginning
+        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_indent("  (before any non-Received: or Resent-*: header)");
+      break;
 
     default:
 
     default:
-    h->next = NULL;
-    header_last->next = h;
-    break;
+      h->next = NULL;
+      header_last->next = h;
+      DEBUG(D_receive|D_acl) debug_printf_indent("  ");
+      break;
     }
 
     }
 
-  if (h->next == NULL) header_last = h;
+  if (!h->next) header_last = h;
 
   /* Check for one of the known header types (From:, To:, etc.) though in
   practice most added headers are going to be "other". Lower case
 
   /* Check for one of the known header types (From:, To:, etc.) though in
   practice most added headers are going to be "other". Lower case
@@ -1101,11 +1276,11 @@ for (h = acl_added_headers; h != NULL; h = next)
   h->type = header_checkname(h, FALSE);
   if (h->type >= 'a') h->type = htype_other;
 
   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("%s", h->text);
   }
 
 acl_added_headers = NULL;
   }
 
 acl_added_headers = NULL;
-DEBUG(D_receive|D_acl) debug_printf(">>\n");
+DEBUG(D_receive|D_acl) debug_printf_indent(">>\n");
 }
 
 
 }
 
 
@@ -1119,31 +1294,43 @@ the calling host to a string that is being built dynamically.
 
 Arguments:
   s           the dynamic string
 
 Arguments:
   s           the dynamic string
-  sizeptr     points to the size variable
-  ptrptr      points to the pointer variable
 
 Returns:      the extended string
 */
 
 
 Returns:      the extended string
 */
 
-static uschar *
-add_host_info_for_log(uschar *s, int *sizeptr, int *ptrptr)
+static gstring *
+add_host_info_for_log(gstring * g)
 {
 {
-if (sender_fullhost != NULL)
+if (sender_fullhost)
   {
   {
-  s = string_append(s, sizeptr, ptrptr, 2, US" H=", sender_fullhost);
-  if ((log_extra_selector & LX_incoming_interface) != 0 &&
-       interface_address != NULL)
-    {
-    uschar *ss = string_sprintf(" I=[%s]:%d", interface_address,
-      interface_port);
-    s = string_cat(s, sizeptr, ptrptr, ss, Ustrlen(ss));
-    }
+  if (LOGGING(dnssec) && sender_host_dnssec)   /*XXX sender_helo_dnssec? */
+    g = string_catn(g, US" DS", 3);
+  g = string_append(g, 2, US" H=", sender_fullhost);
+  if (LOGGING(incoming_interface) && interface_address)
+    g = string_fmt_append(g, " I=[%s]:%d", interface_address, interface_port);
   }
   }
-if (sender_ident != NULL)
-  s = string_append(s, sizeptr, ptrptr, 2, US" U=", sender_ident);
-if (received_protocol != NULL)
-  s = string_append(s, sizeptr, ptrptr, 2, US" P=", received_protocol);
-return s;
+if (f.tcp_in_fastopen && !f.tcp_in_fastopen_logged)
+  {
+  g = string_catn(g, US" TFO*", f.tcp_in_fastopen_data ? 5 : 4);
+  f.tcp_in_fastopen_logged = TRUE;
+  }
+if (sender_ident)
+  g = string_append(g, 2, US" U=", sender_ident);
+if (received_protocol)
+  g = string_append(g, 2, US" P=", received_protocol);
+if (LOGGING(pipelining) && f.smtp_in_pipelining_advertised)
+  {
+  g = string_catn(g, US" L", 2);
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+  if (f.smtp_in_early_pipe_used)
+    g = string_catn(g, US"*", 1);
+  else if (f.smtp_in_early_pipe_advertised)
+    g = string_catn(g, US".", 1);
+#endif
+  if (!f.smtp_in_pipelining_used)
+    g = string_catn(g, US"-", 1);
+  }
+return g;
 }
 
 
 }
 
 
@@ -1171,41 +1358,33 @@ run_mime_acl(uschar *acl, BOOL *smtp_yield_ptr, uschar **smtp_reply_ptr,
   uschar **blackholed_by_ptr)
 {
 FILE *mbox_file;
   uschar **blackholed_by_ptr)
 {
 FILE *mbox_file;
-uschar rfc822_file_path[2048];
+uschar * rfc822_file_path = NULL;
 unsigned long mbox_size;
 header_line *my_headerlist;
 uschar *user_msg, *log_msg;
 int mime_part_count_buffer = -1;
 unsigned long mbox_size;
 header_line *my_headerlist;
 uschar *user_msg, *log_msg;
 int mime_part_count_buffer = -1;
+uschar * mbox_filename;
 int rc = OK;
 
 int rc = OK;
 
-memset(CS rfc822_file_path,0,2048);
-
 /* check if it is a MIME message */
 /* check if it is a MIME message */
-my_headerlist = header_list;
-while (my_headerlist != NULL)
-  {
-  /* skip deleted headers */
-  if (my_headerlist->type == '*')
-    {
-    my_headerlist = my_headerlist->next;
-    continue;
-    }
-  if (strncmpic(my_headerlist->text, US"Content-Type:", 13) == 0)
+
+for (my_headerlist = header_list; my_headerlist; my_headerlist = my_headerlist->next)
+  if (  my_headerlist->type != '*'                     /* skip deleted headers */
+     && strncmpic(my_headerlist->text, US"Content-Type:", 13) == 0
+     )
     {
     DEBUG(D_receive) debug_printf("Found Content-Type: header - executing acl_smtp_mime.\n");
     goto DO_MIME_ACL;
     }
     {
     DEBUG(D_receive) debug_printf("Found Content-Type: header - executing acl_smtp_mime.\n");
     goto DO_MIME_ACL;
     }
-  my_headerlist = my_headerlist->next;
-  }
 
 DEBUG(D_receive) debug_printf("No Content-Type: header - presumably not a MIME message.\n");
 return TRUE;
 
 DO_MIME_ACL:
 
 DEBUG(D_receive) debug_printf("No Content-Type: header - presumably not a MIME message.\n");
 return TRUE;
 
 DO_MIME_ACL:
+
 /* make sure the eml mbox file is spooled up */
 /* make sure the eml mbox file is spooled up */
-mbox_file = spool_mbox(&mbox_size, NULL);
-if (mbox_file == NULL) {
-  /* error while spooling */
+if (!(mbox_file = spool_mbox(&mbox_size, NULL, &mbox_filename)))
+  {                                                            /* error while spooling */
   log_write(0, LOG_MAIN|LOG_PANIC,
          "acl_smtp_mime: error while creating mbox spool file, message temporarily rejected.");
   Uunlink(spool_name);
   log_write(0, LOG_MAIN|LOG_PANIC,
          "acl_smtp_mime: error while creating mbox spool file, message temporarily rejected.");
   Uunlink(spool_name);
@@ -1217,7 +1396,7 @@ if (mbox_file == NULL) {
   message_id[0] = 0;            /* Indicate no message accepted */
   *smtp_reply_ptr = US"";       /* Indicate reply already sent */
   return FALSE;                 /* Indicate skip to end of receive function */
   message_id[0] = 0;            /* Indicate no message accepted */
   *smtp_reply_ptr = US"";       /* Indicate reply already sent */
   return FALSE;                 /* Indicate skip to end of receive function */
-};
+  }
 
 mime_is_rfc822 = 0;
 
 
 mime_is_rfc822 = 0;
 
@@ -1226,7 +1405,7 @@ mime_part_count = -1;
 rc = mime_acl_check(acl, mbox_file, NULL, &user_msg, &log_msg);
 (void)fclose(mbox_file);
 
 rc = mime_acl_check(acl, mbox_file, NULL, &user_msg, &log_msg);
 (void)fclose(mbox_file);
 
-if (Ustrlen(rfc822_file_path) > 0)
+if (rfc822_file_path)
   {
   mime_part_count = mime_part_count_buffer;
 
   {
   mime_part_count = mime_part_count_buffer;
 
@@ -1234,50 +1413,42 @@ if (Ustrlen(rfc822_file_path) > 0)
     {
     log_write(0, LOG_PANIC,
          "acl_smtp_mime: can't unlink RFC822 spool file, skipping.");
     {
     log_write(0, LOG_PANIC,
          "acl_smtp_mime: can't unlink RFC822 spool file, skipping.");
-      goto END_MIME_ACL;
+    goto END_MIME_ACL;
     }
     }
+  rfc822_file_path = NULL;
   }
 
 /* check if we must check any message/rfc822 attachments */
 if (rc == OK)
   {
   }
 
 /* check if we must check any message/rfc822 attachments */
 if (rc == OK)
   {
-  uschar temp_path[1024];
-  int n;
-  struct dirent *entry;
-  DIR *tempdir;
-
-  (void)string_format(temp_path, 1024, "%s/scan/%s", spool_directory,
-    message_id);
+  uschar * scandir = string_copyn(mbox_filename,
+             Ustrrchr(mbox_filename, '/') - mbox_filename);
+  struct dirent * entry;
+  DIR * tempdir;
 
 
-  tempdir = opendir(CS temp_path);
-  n = 0;
-  do
-    {
-    entry = readdir(tempdir);
-    if (entry == NULL) break;
-    if (strncmpic(US entry->d_name,US"__rfc822_",9) == 0)
+  for (tempdir = opendir(CS scandir); entry = readdir(tempdir); )
+    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);
+      rfc822_file_path = string_sprintf("%s/%s", scandir, entry->d_name);
+      DEBUG(D_receive)
+       debug_printf("RFC822 attachment detected: running MIME ACL for '%s'\n",
+         rfc822_file_path);
       break;
       }
       break;
       }
-    } while (1);
   closedir(tempdir);
 
   closedir(tempdir);
 
-  if (entry != NULL)
+  if (rfc822_file_path)
     {
     {
-    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);
     }
   }
 
     }
   }
 
@@ -1287,18 +1458,22 @@ if (rc == DISCARD)
   {
   recipients_count = 0;
   *blackholed_by_ptr = US"MIME ACL";
   {
   recipients_count = 0;
   *blackholed_by_ptr = US"MIME ACL";
+  cancel_cutthrough_connection(TRUE, US"mime acl discard");
   }
 else if (rc != OK)
   {
   Uunlink(spool_name);
   }
 else if (rc != OK)
   {
   Uunlink(spool_name);
+  cancel_cutthrough_connection(TRUE, US"mime acl not ok");
   unspool_mbox();
 #ifdef EXPERIMENTAL_DCC
   dcc_ok = 0;
 #endif
   unspool_mbox();
 #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)
+    {
+    if (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 */
     *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 */
   }
   message_id[0] = 0;            /* Indicate no message accepted */
   return FALSE;                 /* Cause skip to end of receive function */
   }
@@ -1322,7 +1497,7 @@ if (recipients_count == 1) received_for = recipients_list[0].address;
 received = expand_string(received_header_text);
 received_for = NULL;
 
 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 */
   {
   if(spool_name[0] != 0)
     Uunlink(spool_name);           /* Lose the data file */
@@ -1452,15 +1627,15 @@ int  i;
 int  rc = FAIL;
 int  msg_size = 0;
 int  process_info_len = Ustrlen(process_info);
 int  rc = FAIL;
 int  msg_size = 0;
 int  process_info_len = Ustrlen(process_info);
-int  error_rc = (error_handling == ERRORS_SENDER)?
-       errors_sender_rc : EXIT_FAILURE;
+int  error_rc = error_handling == ERRORS_SENDER
+       errors_sender_rc : EXIT_FAILURE;
 int  header_size = 256;
 int  header_size = 256;
-int  start, end, domain, size, sptr;
-int  id_resolution;
+int  start, end, domain;
+int  id_resolution = 0;
 int  had_zero = 0;
 int  prevlines_length = 0;
 
 int  had_zero = 0;
 int  prevlines_length = 0;
 
-register int ptr = 0;
+int ptr = 0;
 
 BOOL contains_resent_headers = FALSE;
 BOOL extracted_ignored = FALSE;
 
 BOOL contains_resent_headers = FALSE;
 BOOL extracted_ignored = FALSE;
@@ -1480,7 +1655,8 @@ error_block *bad_addresses = NULL;
 uschar *frozen_by = NULL;
 uschar *queued_by = NULL;
 
 uschar *frozen_by = NULL;
 uschar *queued_by = NULL;
 
-uschar *errmsg, *s;
+uschar *errmsg;
+gstring * g;
 struct stat statbuf;
 
 /* Final message to give to SMTP caller, and messages from ACLs */
 struct stat statbuf;
 
 /* Final message to give to SMTP caller, and messages from ACLs */
@@ -1512,6 +1688,7 @@ int dmarc_up = 0;
 uschar *timestamp;
 int tslen;
 
 uschar *timestamp;
 int tslen;
 
+
 /* Release any open files that might have been cached while preparing to
 accept the message - e.g. by verifying addresses - because reading a message
 might take a fair bit of real time. */
 /* Release any open files that might have been cached while preparing to
 accept the message - e.g. by verifying addresses - because reading a message
 might take a fair bit of real time. */
@@ -1522,7 +1699,7 @@ search_tidyup();
 cutthrough delivery with the no-spool option.  It shouldn't be possible
 to set up the combination, but just in case kill any ongoing connection. */
 if (extract_recip || !smtp_input)
 cutthrough delivery with the no-spool option.  It shouldn't be possible
 to set up the combination, but just in case kill any ongoing connection. */
 if (extract_recip || !smtp_input)
-  cancel_cutthrough_connection("not smtp input");
+  cancel_cutthrough_connection(TRUE, US"not smtp input");
 
 /* Initialize the chain of headers by setting up a place-holder for Received:
 header. Temporarily mark it as "old", i.e. not to be used. We keep header_last
 
 /* Initialize the chain of headers by setting up a place-holder for Received:
 header. Temporarily mark it as "old", i.e. not to be used. We keep header_last
@@ -1544,9 +1721,9 @@ header names list to be the normal list. Indicate there is no data file open
 yet, initialize the size and warning count, and deal with no size limit. */
 
 message_id[0] = 0;
 yet, initialize the size and warning count, and deal with no size limit. */
 
 message_id[0] = 0;
-data_file = NULL;
+spool_data_file = NULL;
 data_fd = -1;
 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 */
 message_size = 0;
 warning_count = 0;
 received_count = 1;            /* For the one we will add */
@@ -1559,8 +1736,10 @@ message_linecount = body_linecount = body_zerocount =
   max_received_linelength = 0;
 
 #ifndef DISABLE_DKIM
   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 && !f.dkim_disable_verify)
+  dkim_exim_verify_init(chunking_state <= CHUNKING_OFFERED);
 #endif
 
 #ifdef EXPERIMENTAL_DMARC
 #endif
 
 #ifdef EXPERIMENTAL_DMARC
@@ -1578,12 +1757,14 @@ message id creation below. */
 second, and for that we use the global variable received_time. This is for
 things like ultimate message timeouts. */
 
 second, and for that we use the global variable received_time. This is for
 things like ultimate message timeouts. */
 
-received_time = message_id_tv.tv_sec;
+received_time = message_id_tv;
 
 /* If SMTP input, set the special handler for timeouts. The alarm() calls
 happen in the smtp_getc() function when it refills its buffer. */
 
 
 /* If SMTP input, set the special handler for timeouts. The alarm() calls
 happen in the smtp_getc() function when it refills its buffer. */
 
-if (smtp_input) os_non_restarting_signal(SIGALRM, data_timeout_handler);
+had_data_timeout = 0;
+if (smtp_input)
+  os_non_restarting_signal(SIGALRM, data_timeout_handler);
 
 /* If not SMTP input, timeout happens only if configured, and we just set a
 single timeout for the whole message. */
 
 /* If not SMTP input, timeout happens only if configured, and we just set a
 single timeout for the whole message. */
@@ -1591,11 +1772,12 @@ single timeout for the whole message. */
 else if (receive_timeout > 0)
   {
   os_non_restarting_signal(SIGALRM, data_timeout_handler);
 else if (receive_timeout > 0)
   {
   os_non_restarting_signal(SIGALRM, data_timeout_handler);
-  alarm(receive_timeout);
+  ALARM(receive_timeout);
   }
 
 /* SIGTERM and SIGINT are caught always. */
 
   }
 
 /* SIGTERM and SIGINT are caught always. */
 
+had_data_sigint = 0;
 signal(SIGTERM, data_sigterm_sigint_handler);
 signal(SIGINT, data_sigterm_sigint_handler);
 
 signal(SIGTERM, data_sigterm_sigint_handler);
 signal(SIGINT, data_sigterm_sigint_handler);
 
@@ -1616,7 +1798,7 @@ next->text. */
 
 for (;;)
   {
 
 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. */
 
   /* If we hit EOF on a SMTP connection, it's an error, since incoming
   SMTP must have a correct "." terminator. */
@@ -1639,21 +1821,19 @@ 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
   (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)
     {
     int oldsize = header_size;
 
   if (ptr >= header_size - 4)
     {
     int oldsize = header_size;
-    /* header_size += 256; */
+
+    if (header_size >= INT_MAX/2)
+      goto OVERSIZE;
     header_size *= 2;
     header_size *= 2;
+
     if (!store_extend(next->text, oldsize, header_size))
     if (!store_extend(next->text, oldsize, header_size))
-      {
-      uschar *newtext = store_get(header_size);
-      memcpy(newtext, next->text, ptr);
-      store_release(next->text);
-      next->text = newtext;
-      }
+      next->text = store_newblock(next->text, header_size, ptr);
     }
 
   /* Cope with receiving a binary zero. There is dispute about whether
     }
 
   /* Cope with receiving a binary zero. There is dispute about whether
@@ -1693,12 +1873,12 @@ for (;;)
   prevent further reading), and break out of the loop, having freed the
   empty header, and set next = NULL to indicate no data line. */
 
   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 == '.' && f.dot_ends)
     {
     {
-    ch = (receive_getc)();
+    ch = (receive_getc)(GETC_BUFFER_UNLIMITED);
     if (ch == '\r')
       {
     if (ch == '\r')
       {
-      ch = (receive_getc)();
+      ch = (receive_getc)(GETC_BUFFER_UNLIMITED);
       if (ch != '\n')
         {
         receive_ungetc(ch);
       if (ch != '\n')
         {
         receive_ungetc(ch);
@@ -1729,7 +1909,7 @@ for (;;)
 
   if (ch == '\r')
     {
 
   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;
     if (ch == '\n')
       {
       if (first_line_ended_crlf == TRUE_UNSET) first_line_ended_crlf = TRUE;
@@ -1757,6 +1937,7 @@ for (;;)
 
   if (message_size >= header_maxsize)
     {
 
   if (message_size >= header_maxsize)
     {
+OVERSIZE:
     next->text[ptr] = 0;
     next->slen = ptr;
     next->type = htype_other;
     next->text[ptr] = 0;
     next->slen = ptr;
     next->type = htype_other;
@@ -1766,7 +1947,7 @@ for (;;)
 
     log_write(0, LOG_MAIN, "ridiculously long message header received from "
       "%s (more than %d characters): message abandoned",
 
     log_write(0, LOG_MAIN, "ridiculously long message header received from "
       "%s (more than %d characters): message abandoned",
-      sender_host_unknown? sender_ident : sender_fullhost, header_maxsize);
+      f.sender_host_unknown ? sender_ident : sender_fullhost, header_maxsize);
 
     if (smtp_input)
       {
 
     if (smtp_input)
       {
@@ -1824,11 +2005,12 @@ for (;;)
 
   if (ch != EOF)
     {
 
   if (ch != EOF)
     {
-    int nextch = (receive_getc)();
+    int nextch = (receive_getc)(GETC_BUFFER_UNLIMITED);
     if (nextch == ' ' || nextch == '\t')
       {
       next->text[ptr++] = nextch;
     if (nextch == ' ' || nextch == '\t')
       {
       next->text[ptr++] = nextch;
-      message_size++;
+      if (++message_size >= header_maxsize)
+       goto OVERSIZE;
       continue;                      /* Iterate the loop */
       }
     else if (nextch != EOF) (receive_ungetc)(nextch);   /* For next time */
       continue;                      /* Iterate the loop */
       }
     else if (nextch != EOF) (receive_ungetc)(nextch);   /* For next time */
@@ -1880,32 +2062,30 @@ for (;;)
   these lines in SMTP messages. There is now an option to ignore them from
   specified hosts or networks. Sigh. */
 
   these lines in SMTP messages. There is now an option to ignore them from
   specified hosts or networks. Sigh. */
 
-  if (header_last == header_list &&
-       (!smtp_input
-         ||
-         (sender_host_address != NULL &&
-           verify_check_host(&ignore_fromline_hosts) == OK)
-         ||
-         (sender_host_address == NULL && ignore_fromline_local)
-       ) &&
-       regex_match_and_setup(regex_From, next->text, 0, -1))
+  if (  header_last == header_list
+     && (  !smtp_input
+        || (  sender_host_address
+          && verify_check_host(&ignore_fromline_hosts) == OK
+          )
+        || (!sender_host_address && ignore_fromline_local)
+        )
+     && regex_match_and_setup(regex_From, next->text, 0, -1)
+     )
     {
     {
-    if (!sender_address_forced)
+    if (!f.sender_address_forced)
       {
       uschar *uucp_sender = expand_string(uucp_from_sender);
       {
       uschar *uucp_sender = expand_string(uucp_from_sender);
-      if (uucp_sender == NULL)
-        {
+      if (!uucp_sender)
         log_write(0, LOG_MAIN|LOG_PANIC,
           "expansion of \"%s\" failed after matching "
           "\"From \" line: %s", uucp_from_sender, expand_string_message);
         log_write(0, LOG_MAIN|LOG_PANIC,
           "expansion of \"%s\" failed after matching "
           "\"From \" line: %s", uucp_from_sender, expand_string_message);
-        }
       else
         {
         int start, end, domain;
         uschar *errmess;
         uschar *newsender = parse_extract_address(uucp_sender, &errmess,
           &start, &end, &domain, TRUE);
       else
         {
         int start, end, domain;
         uschar *errmess;
         uschar *newsender = parse_extract_address(uucp_sender, &errmess,
           &start, &end, &domain, TRUE);
-        if (newsender != NULL)
+        if (newsender)
           {
           if (domain == 0 && newsender[0] != 0)
             newsender = rewrite_address_qualify(newsender, FALSE);
           {
           if (domain == 0 && newsender[0] != 0)
             newsender = rewrite_address_qualify(newsender, FALSE);
@@ -1914,11 +2094,11 @@ for (;;)
             {
             sender_address = newsender;
 
             {
             sender_address = newsender;
 
-            if (trusted_caller || filter_test != FTEST_NONE)
+            if (f.trusted_caller || filter_test != FTEST_NONE)
               {
               authenticated_sender = NULL;
               originator_name = US"";
               {
               authenticated_sender = NULL;
               originator_name = US"";
-              sender_local = FALSE;
+              f.sender_local = FALSE;
               }
 
             if (filter_test != FTEST_NONE)
               }
 
             if (filter_test != FTEST_NONE)
@@ -1989,7 +2169,7 @@ for (;;)
       {
       log_write(0, LOG_MAIN, "overlong message header line received from "
         "%s (more than %d characters): message abandoned",
       {
       log_write(0, LOG_MAIN, "overlong message header line received from "
         "%s (more than %d characters): message abandoned",
-        sender_host_unknown? sender_ident : sender_fullhost,
+        f.sender_host_unknown ? sender_ident : sender_fullhost,
         header_line_maxsize);
 
       if (smtp_input)
         header_line_maxsize);
 
       if (smtp_input)
@@ -2000,13 +2180,11 @@ for (;;)
         }
 
       else
         }
 
       else
-        {
         give_local_error(ERRMESS_VLONGHDRLINE,
           string_sprintf("message header line longer than %d characters "
            "received: message not accepted", header_line_maxsize), US"",
            error_rc, stdin, header_list->next);
         /* Does not return */
         give_local_error(ERRMESS_VLONGHDRLINE,
           string_sprintf("message header line longer than %d characters "
            "received: message not accepted", header_line_maxsize), US"",
            error_rc, stdin, header_list->next);
         /* Does not return */
-        }
       }
 
     /* Note if any resent- fields exist. */
       }
 
     /* Note if any resent- fields exist. */
@@ -2018,6 +2196,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", FALSE);
+    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. */
 
   /* The line has been handled. If we have hit EOF, break out of the loop,
   indicating no pending data line. */
 
@@ -2042,7 +2235,7 @@ normal case). */
 DEBUG(D_receive)
   {
   debug_printf(">>Headers received:\n");
 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");
   }
     debug_printf("%s", h->text);
   debug_printf("\n");
   }
@@ -2069,7 +2262,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. */
 
 /* 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;
   {
   BOOL is_resent = strncmpic(h->text, US"resent-", 7) == 0;
   if (is_resent) contains_resent_headers = TRUE;
@@ -2077,119 +2270,119 @@ for (h = header_list->next; h != NULL; h = h->next)
   switch (header_checkname(h, is_resent))
     {
     case htype_bcc:
   switch (header_checkname(h, is_resent))
     {
     case htype_bcc:
-    h->type = htype_bcc;        /* Both Bcc: and Resent-Bcc: */
-    break;
+      h->type = htype_bcc;        /* Both Bcc: and Resent-Bcc: */
+      break;
 
     case htype_cc:
 
     case htype_cc:
-    h->type = htype_cc;         /* Both Cc: and Resent-Cc: */
-    break;
+      h->type = htype_cc;         /* Both Cc: and Resent-Cc: */
+      break;
 
 
-    /* Record whether a Date: or Resent-Date: header exists, as appropriate. */
+      /* Record whether a Date: or Resent-Date: header exists, as appropriate. */
 
     case htype_date:
 
     case htype_date:
-    if (!resents_exist || is_resent) date_header_exists = TRUE;
-    break;
+      if (!resents_exist || is_resent) date_header_exists = TRUE;
+      break;
 
 
-    /* Same comments as about Return-Path: below. */
+      /* Same comments as about Return-Path: below. */
 
     case htype_delivery_date:
 
     case htype_delivery_date:
-    if (delivery_date_remove) h->type = htype_old;
-    break;
+      if (delivery_date_remove) h->type = htype_old;
+      break;
 
 
-    /* Same comments as about Return-Path: below. */
+      /* Same comments as about Return-Path: below. */
 
     case htype_envelope_to:
 
     case htype_envelope_to:
-    if (envelope_to_remove) h->type = htype_old;
-    break;
+      if (envelope_to_remove) h->type = htype_old;
+      break;
 
 
-    /* Mark all "From:" headers so they get rewritten. Save the one that is to
-    be used for Sender: checking. For Sendmail compatibility, if the "From:"
-    header consists of just the login id of the user who called Exim, rewrite
-    it with the gecos field first. Apply this rule to Resent-From: if there
-    are resent- fields. */
+      /* Mark all "From:" headers so they get rewritten. Save the one that is to
+      be used for Sender: checking. For Sendmail compatibility, if the "From:"
+      header consists of just the login id of the user who called Exim, rewrite
+      it with the gecos field first. Apply this rule to Resent-From: if there
+      are resent- fields. */
 
     case htype_from:
 
     case htype_from:
-    h->type = htype_from;
-    if (!resents_exist || is_resent)
-      {
-      from_header = h;
-      if (!smtp_input)
-        {
-        int len;
-        uschar *s = Ustrchr(h->text, ':') + 1;
-        while (isspace(*s)) s++;
-        len = h->slen - (s - h->text) - 1;
-        if (Ustrlen(originator_login) == len &&
-           strncmpic(s, originator_login, len) == 0)
-          {
-          uschar *name = is_resent? US"Resent-From" : US"From";
-          header_add(htype_from, "%s: %s <%s@%s>\n", name, originator_name,
-            originator_login, qualify_domain_sender);
-          from_header = header_last;
-          h->type = htype_old;
-          DEBUG(D_receive|D_rewrite)
-            debug_printf("rewrote \"%s:\" header using gecos\n", name);
-         }
-        }
-      }
-    break;
+      h->type = htype_from;
+      if (!resents_exist || is_resent)
+       {
+       from_header = h;
+       if (!smtp_input)
+         {
+         int len;
+         uschar *s = Ustrchr(h->text, ':') + 1;
+         while (isspace(*s)) s++;
+         len = h->slen - (s - h->text) - 1;
+         if (Ustrlen(originator_login) == len &&
+             strncmpic(s, originator_login, len) == 0)
+           {
+           uschar *name = is_resent? US"Resent-From" : US"From";
+           header_add(htype_from, "%s: %s <%s@%s>\n", name, originator_name,
+             originator_login, qualify_domain_sender);
+           from_header = header_last;
+           h->type = htype_old;
+           DEBUG(D_receive|D_rewrite)
+             debug_printf("rewrote \"%s:\" header using gecos\n", name);
+          }
+         }
+       }
+      break;
 
 
-    /* Identify the Message-id: header for generating "in-reply-to" in the
-    autoreply transport. For incoming logging, save any resent- value. In both
-    cases, take just the first of any multiples. */
+      /* Identify the Message-id: header for generating "in-reply-to" in the
+      autoreply transport. For incoming logging, save any resent- value. In both
+      cases, take just the first of any multiples. */
 
     case htype_id:
 
     case htype_id:
-    if (msgid_header == NULL && (!resents_exist || is_resent))
-      {
-      msgid_header = h;
-      h->type = htype_id;
-      }
-    break;
+      if (!msgid_header && (!resents_exist || is_resent))
+       {
+       msgid_header = h;
+       h->type = htype_id;
+       }
+      break;
 
 
-    /* Flag all Received: headers */
+      /* Flag all Received: headers */
 
     case htype_received:
 
     case htype_received:
-    h->type = htype_received;
-    received_count++;
-    break;
+      h->type = htype_received;
+      received_count++;
+      break;
 
 
-    /* "Reply-to:" is just noted (there is no resent-reply-to field) */
+      /* "Reply-to:" is just noted (there is no resent-reply-to field) */
 
     case htype_reply_to:
 
     case htype_reply_to:
-    h->type = htype_reply_to;
-    break;
+      h->type = htype_reply_to;
+      break;
 
 
-    /* The Return-path: header is supposed to be added to messages when
-    they leave the SMTP system. We shouldn't receive messages that already
-    contain Return-path. However, since Exim generates Return-path: on
-    local delivery, resent messages may well contain it. We therefore
-    provide an option (which defaults on) to remove any Return-path: headers
-    on input. Removal actually means flagging as "old", which prevents the
-    header being transmitted with the message. */
+      /* The Return-path: header is supposed to be added to messages when
+      they leave the SMTP system. We shouldn't receive messages that already
+      contain Return-path. However, since Exim generates Return-path: on
+      local delivery, resent messages may well contain it. We therefore
+      provide an option (which defaults on) to remove any Return-path: headers
+      on input. Removal actually means flagging as "old", which prevents the
+      header being transmitted with the message. */
 
     case htype_return_path:
 
     case htype_return_path:
-    if (return_path_remove) h->type = htype_old;
+      if (return_path_remove) h->type = htype_old;
 
 
-    /* If we are testing a mail filter file, use the value of the
-    Return-Path: header to set up the return_path variable, which is not
-    otherwise set. However, remove any <> that surround the address
-    because the variable doesn't have these. */
+      /* If we are testing a mail filter file, use the value of the
+      Return-Path: header to set up the return_path variable, which is not
+      otherwise set. However, remove any <> that surround the address
+      because the variable doesn't have these. */
 
 
-    if (filter_test != FTEST_NONE)
-      {
-      uschar *start = h->text + 12;
-      uschar *end = start + Ustrlen(start);
-      while (isspace(*start)) start++;
-      while (end > start && isspace(end[-1])) end--;
-      if (*start == '<' && end[-1] == '>')
-        {
-        start++;
-        end--;
-        }
-      return_path = string_copyn(start, end - start);
-      printf("Return-path taken from \"Return-path:\" header line\n");
-      }
-    break;
+      if (filter_test != FTEST_NONE)
+       {
+       uschar *start = h->text + 12;
+       uschar *end = start + Ustrlen(start);
+       while (isspace(*start)) start++;
+       while (end > start && isspace(end[-1])) end--;
+       if (*start == '<' && end[-1] == '>')
+         {
+         start++;
+         end--;
+         }
+       return_path = string_copyn(start, end - start);
+       printf("Return-path taken from \"Return-path:\" header line\n");
+       }
+      break;
 
     /* If there is a "Sender:" header and the message is locally originated,
     and from an untrusted caller and suppress_local_fixups is not set, or if we
 
     /* If there is a "Sender:" header and the message is locally originated,
     and from an untrusted caller and suppress_local_fixups is not set, or if we
@@ -2203,31 +2396,29 @@ for (h = header_list->next; h != NULL; h = h->next)
     set.) */
 
     case htype_sender:
     set.) */
 
     case htype_sender:
-    h->type = ((!active_local_sender_retain &&
-                (
-                (sender_local && !trusted_caller && !suppress_local_fixups)
-                  || submission_mode
-                )
-               ) &&
-               (!resents_exist||is_resent))?
-      htype_old : htype_sender;
-    break;
+      h->type =    !f.active_local_sender_retain
+               && (  f.sender_local && !f.trusted_caller && !f.suppress_local_fixups
+                  || f.submission_mode
+                  )
+               && (!resents_exist || is_resent)
+       ? htype_old : htype_sender;
+      break;
 
 
-    /* Remember the Subject: header for logging. There is no Resent-Subject */
+      /* Remember the Subject: header for logging. There is no Resent-Subject */
 
     case htype_subject:
 
     case htype_subject:
-    subject_header = h;
-    break;
+      subject_header = h;
+      break;
 
 
-    /* "To:" gets flagged, and the existence of a recipient header is noted,
-    whether it's resent- or not. */
+      /* "To:" gets flagged, and the existence of a recipient header is noted,
+      whether it's resent- or not. */
 
     case htype_to:
 
     case htype_to:
-    h->type = htype_to;
-    /****
-    to_or_cc_header_exists = TRUE;
-    ****/
-    break;
+      h->type = htype_to;
+      /****
+      to_or_cc_header_exists = TRUE;
+      ****/
+      break;
     }
   }
 
     }
   }
 
@@ -2285,7 +2476,7 @@ if (extract_recip)
 
   /* Now scan the headers */
 
 
   /* 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))
     {
     if ((h->type == htype_to || h->type == htype_cc || h->type == htype_bcc) &&
         (!contains_resent_headers || strncmpic(h->text, US"resent-", 7) == 0))
@@ -2293,7 +2484,7 @@ if (extract_recip)
       uschar *s = Ustrchr(h->text, ':') + 1;
       while (isspace(*s)) s++;
 
       uschar *s = Ustrchr(h->text, ':') + 1;
       while (isspace(*s)) s++;
 
-      parse_allow_group = TRUE;          /* Allow address group syntax */
+      f.parse_allow_group = TRUE;          /* Allow address group syntax */
 
       while (*s != 0)
         {
 
       while (*s != 0)
         {
@@ -2304,11 +2495,9 @@ if (extract_recip)
         /* Check on maximum */
 
         if (recipients_max > 0 && ++rcount > recipients_max)
         /* Check on maximum */
 
         if (recipients_max > 0 && ++rcount > recipients_max)
-          {
           give_local_error(ERRMESS_TOOMANYRECIP, US"too many recipients",
             US"message rejected: ", error_rc, stdin, NULL);
           /* Does not return */
           give_local_error(ERRMESS_TOOMANYRECIP, US"too many recipients",
             US"message rejected: ", error_rc, stdin, NULL);
           /* Does not return */
-          }
 
         /* Make a copy of the address, and remove any internal newlines. These
         may be present as a result of continuations of the header line. The
 
         /* Make a copy of the address, and remove any internal newlines. These
         may be present as a result of continuations of the header line. The
@@ -2318,9 +2507,23 @@ if (extract_recip)
         pp = recipient = store_get(ss - s + 1);
         for (p = s; p < ss; p++) if (*p != '\n') *pp++ = *p;
         *pp = 0;
         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);
 
         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
         /* 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
@@ -2361,8 +2564,8 @@ if (extract_recip)
         while (isspace(*s)) s++;
         }    /* Next address */
 
         while (isspace(*s)) s++;
         }    /* Next address */
 
-      parse_allow_group = FALSE;      /* Reset group syntax flags */
-      parse_found_group = FALSE;
+      f.parse_allow_group = FALSE;      /* Reset group syntax flags */
+      f.parse_found_group = FALSE;
 
       /* If this was the bcc: header, mark it "old", which means it
       will be kept on the spool, but not transmitted as part of the
 
       /* If this was the bcc: header, mark it "old", which means it
       will be kept on the spool, but not transmitted as part of the
@@ -2420,8 +2623,9 @@ letter and it is not used internally.
 NOTE: If ever the format of message ids is changed, the regular expression for
 checking that a string is in this format must be updated in a corresponding
 way. It appears in the initializing code in exim.c. The macro MESSAGE_ID_LENGTH
 NOTE: If ever the format of message ids is changed, the regular expression for
 checking that a string is in this format must be updated in a corresponding
 way. It appears in the initializing code in exim.c. The macro MESSAGE_ID_LENGTH
-must also be changed to reflect the correct string length. Then, of course,
-other programs that rely on the message id format will need updating too. */
+must also be changed to reflect the correct string length. The queue-sort code
+needs to know the layout. Then, of course, other programs that rely on the
+message id format will need updating too. */
 
 Ustrncpy(message_id, string_base62((long int)(message_id_tv.tv_sec)), 6);
 message_id[6] = '-';
 
 Ustrncpy(message_id, string_base62((long int)(message_id_tv.tv_sec)), 6);
 message_id[6] = '-';
@@ -2432,9 +2636,9 @@ checked when it was read, to ensure it isn't too big. The timing granularity is
 left in id_resolution so that an appropriate wait can be done after receiving
 the message, if necessary (we hope it won't be). */
 
 left in id_resolution so that an appropriate wait can be done after receiving
 the message, if necessary (we hope it won't be). */
 
-if (host_number_string != NULL)
+if (host_number_string)
   {
   {
-  id_resolution = (BASE_62 == 62)? 5000 : 10000;
+  id_resolution = BASE_62 == 62 ? 5000 : 10000;
   sprintf(CS(message_id + MESSAGE_ID_LENGTH - 3), "-%2s",
     string_base62((long int)(
       host_number * (1000000/id_resolution) +
   sprintf(CS(message_id + MESSAGE_ID_LENGTH - 3), "-%2s",
     string_base62((long int)(
       host_number * (1000000/id_resolution) +
@@ -2446,7 +2650,7 @@ appropriate resolution. */
 
 else
   {
 
 else
   {
-  id_resolution = (BASE_62 == 62)? 500 : 1000;
+  id_resolution = BASE_62 == 62 ? 500 : 1000;
   sprintf(CS(message_id + MESSAGE_ID_LENGTH - 3), "-%2s",
     string_base62((long int)(message_id_tv.tv_usec/id_resolution)) + 4);
   }
   sprintf(CS(message_id + MESSAGE_ID_LENGTH - 3), "-%2s",
     string_base62((long int)(message_id_tv.tv_usec/id_resolution)) + 4);
   }
@@ -2461,16 +2665,15 @@ it will fit. */
 to be the least significant base-62 digit of the time of arrival. Otherwise
 ensure that it is an empty string. */
 
 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
 messages. This can be user-configured if required, but we had better flatten
 any illegal characters therein. */
 
 
 /* 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
 messages. This can be user-configured if required, but we had better flatten
 any illegal characters therein. */
 
-if (msgid_header == NULL &&
-      ((sender_host_address == NULL && !suppress_local_fixups)
-        || submission_mode))
+if (  !msgid_header
+   && ((!sender_host_address && !f.suppress_local_fixups) || f.submission_mode))
   {
   uschar *p;
   uschar *id_text = US"";
   {
   uschar *p;
   uschar *id_text = US"";
@@ -2478,20 +2681,20 @@ if (msgid_header == NULL &&
 
   /* Permit only letters, digits, dots, and hyphens in the domain */
 
 
   /* Permit only letters, digits, dots, and hyphens in the domain */
 
-  if (message_id_domain != NULL)
+  if (message_id_domain)
     {
     uschar *new_id_domain = expand_string(message_id_domain);
     {
     uschar *new_id_domain = expand_string(message_id_domain);
-    if (new_id_domain == NULL)
+    if (!new_id_domain)
       {
       {
-      if (!expand_string_forcedfail)
+      if (!f.expand_string_forcedfail)
         log_write(0, LOG_MAIN|LOG_PANIC,
           "expansion of \"%s\" (message_id_header_domain) "
           "failed: %s", message_id_domain, expand_string_message);
       }
         log_write(0, LOG_MAIN|LOG_PANIC,
           "expansion of \"%s\" (message_id_header_domain) "
           "failed: %s", message_id_domain, expand_string_message);
       }
-    else if (*new_id_domain != 0)
+    else if (*new_id_domain)
       {
       id_domain = new_id_domain;
       {
       id_domain = new_id_domain;
-      for (p = id_domain; *p != 0; p++)
+      for (p = id_domain; *p; p++)
         if (!isalnum(*p) && *p != '.') *p = '-';  /* No need to test '-' ! */
       }
     }
         if (!isalnum(*p) && *p != '.') *p = '-';  /* No need to test '-' ! */
       }
     }
@@ -2499,21 +2702,20 @@ if (msgid_header == NULL &&
   /* Permit all characters except controls and RFC 2822 specials in the
   additional text part. */
 
   /* Permit all characters except controls and RFC 2822 specials in the
   additional text part. */
 
-  if (message_id_text != NULL)
+  if (message_id_text)
     {
     uschar *new_id_text = expand_string(message_id_text);
     {
     uschar *new_id_text = expand_string(message_id_text);
-    if (new_id_text == NULL)
+    if (!new_id_text)
       {
       {
-      if (!expand_string_forcedfail)
+      if (!f.expand_string_forcedfail)
         log_write(0, LOG_MAIN|LOG_PANIC,
           "expansion of \"%s\" (message_id_header_text) "
           "failed: %s", message_id_text, expand_string_message);
       }
         log_write(0, LOG_MAIN|LOG_PANIC,
           "expansion of \"%s\" (message_id_header_text) "
           "failed: %s", message_id_text, expand_string_message);
       }
-    else if (*new_id_text != 0)
+    else if (*new_id_text)
       {
       id_text = new_id_text;
       {
       id_text = new_id_text;
-      for (p = id_text; *p != 0; p++)
-        if (mac_iscntrl_or_special(*p)) *p = '-';
+      for (p = id_text; *p; p++) if (mac_iscntrl_or_special(*p)) *p = '-';
       }
     }
 
       }
     }
 
@@ -2530,7 +2732,7 @@ if (msgid_header == NULL &&
 rewriting. Must copy the count, because later ACLs and the local_scan()
 function may mess with the real recipients. */
 
 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++)
   {
   raw_recipients = store_get(recipients_count * sizeof(uschar *));
   for (i = 0; i < recipients_count; i++)
@@ -2556,9 +2758,8 @@ possible addition of a Sender: header, because untrusted_set_sender allows an
 untrusted user to set anything in the envelope (which might then get info
 From:) but we still want to ensure a valid Sender: if it is required. */
 
 untrusted user to set anything in the envelope (which might then get info
 From:) but we still want to ensure a valid Sender: if it is required. */
 
-if (from_header == NULL &&
-    ((sender_host_address == NULL && !suppress_local_fixups)
-      || submission_mode))
+if (  !from_header
+   && ((!sender_host_address && !f.suppress_local_fixups) || f.submission_mode))
   {
   uschar *oname = US"";
 
   {
   uschar *oname = US"";
 
@@ -2567,56 +2768,48 @@ if (from_header == NULL &&
   force its value or if we have a non-SMTP message for which -f was not used
   to set the sender. */
 
   force its value or if we have a non-SMTP message for which -f was not used
   to set the sender. */
 
-  if (sender_host_address == NULL)
+  if (!sender_host_address)
     {
     {
-    if (!trusted_caller || sender_name_forced ||
-         (!smtp_input && !sender_address_forced))
+    if (!f.trusted_caller || f.sender_name_forced ||
+         (!smtp_input && !f.sender_address_forced))
       oname = originator_name;
     }
 
   /* For non-locally submitted messages, the only time we use the originator
   name is when it was forced by the /name= option on control=submission. */
 
       oname = originator_name;
     }
 
   /* For non-locally submitted messages, the only time we use the originator
   name is when it was forced by the /name= option on control=submission. */
 
-  else
-    {
-    if (submission_name != NULL) oname = submission_name;
-    }
+  else if (submission_name) oname = submission_name;
 
   /* Envelope sender is empty */
 
 
   /* Envelope sender is empty */
 
-  if (sender_address[0] == 0)
+  if (!*sender_address)
     {
     uschar *fromstart, *fromend;
 
     {
     uschar *fromstart, *fromend;
 
-    fromstart = string_sprintf("%sFrom: %s%s", resent_prefix,
-      oname, (oname[0] == 0)? "" : " <");
-    fromend = (oname[0] == 0)? US"" : US">";
+    fromstart = string_sprintf("%sFrom: %s%s",
+      resent_prefix, oname, *oname ? " <" : "");
+    fromend = *oname ? US">" : US"";
 
 
-    if (sender_local || local_error_message)
-      {
+    if (f.sender_local || f.local_error_message)
       header_add(htype_from, "%s%s@%s%s\n", fromstart,
         local_part_quote(originator_login), qualify_domain_sender,
         fromend);
       header_add(htype_from, "%s%s@%s%s\n", fromstart,
         local_part_quote(originator_login), qualify_domain_sender,
         fromend);
-      }
-    else if (submission_mode && authenticated_id != NULL)
+
+    else if (f.submission_mode && authenticated_id)
       {
       {
-      if (submission_domain == NULL)
-        {
+      if (!submission_domain)
         header_add(htype_from, "%s%s@%s%s\n", fromstart,
           local_part_quote(authenticated_id), qualify_domain_sender,
           fromend);
         header_add(htype_from, "%s%s@%s%s\n", fromstart,
           local_part_quote(authenticated_id), qualify_domain_sender,
           fromend);
-        }
-      else if (submission_domain[0] == 0)  /* empty => whole address set */
-        {
+
+      else if (!*submission_domain)  /* empty => whole address set */
         header_add(htype_from, "%s%s%s\n", fromstart, authenticated_id,
           fromend);
         header_add(htype_from, "%s%s%s\n", fromstart, authenticated_id,
           fromend);
-        }
+
       else
       else
-        {
         header_add(htype_from, "%s%s@%s%s\n", fromstart,
         header_add(htype_from, "%s%s@%s%s\n", fromstart,
-          local_part_quote(authenticated_id), submission_domain,
-          fromend);
-        }
+          local_part_quote(authenticated_id), submission_domain, fromend);
+
       from_header = header_last;    /* To get it checked for Sender: */
       }
     }
       from_header = header_last;    /* To get it checked for Sender: */
       }
     }
@@ -2629,10 +2822,9 @@ if (from_header == NULL &&
     {
     header_add(htype_from, "%sFrom: %s%s%s%s\n", resent_prefix,
       oname,
     {
     header_add(htype_from, "%sFrom: %s%s%s%s\n", resent_prefix,
       oname,
-      (oname[0] == 0)? "" : " <",
-      (sender_address_unrewritten == NULL)?
-        sender_address : sender_address_unrewritten,
-      (oname[0] == 0)? "" : ">");
+      *oname ? " <" : "",
+      sender_address_unrewritten ? sender_address_unrewritten : sender_address,
+      *oname ? ">" : "");
 
     from_header = header_last;    /* To get it checked for Sender: */
     }
 
     from_header = header_last;    /* To get it checked for Sender: */
     }
@@ -2649,11 +2841,11 @@ trusted callers to forge From: without supplying -f, we have to test explicitly
 here. If the From: header contains more than one address, then the call to
 parse_extract_address fails, and a Sender: header is inserted, as required. */
 
 here. If the From: header contains more than one address, then the call to
 parse_extract_address fails, and a Sender: header is inserted, as required. */
 
-if (from_header != NULL &&
-     (active_local_from_check &&
-       ((sender_local && !trusted_caller && !suppress_local_fixups) ||
-        (submission_mode && authenticated_id != NULL))
-     ))
+if (  from_header
+   && (  f.active_local_from_check
+      && (  f.sender_local && !f.trusted_caller && !f.suppress_local_fixups
+        || f.submission_mode && authenticated_id
+   )  )  )
   {
   BOOL make_sender = TRUE;
   int start, end, domain;
   {
   BOOL make_sender = TRUE;
   int start, end, domain;
@@ -2663,37 +2855,26 @@ if (from_header != NULL &&
       &start, &end, &domain, FALSE);
   uschar *generated_sender_address;
 
       &start, &end, &domain, FALSE);
   uschar *generated_sender_address;
 
-  if (submission_mode)
-    {
-    if (submission_domain == NULL)
-      {
-      generated_sender_address = string_sprintf("%s@%s",
-        local_part_quote(authenticated_id), qualify_domain_sender);
-      }
-    else if (submission_domain[0] == 0)  /* empty => full address */
-      {
-      generated_sender_address = string_sprintf("%s",
-        authenticated_id);
-      }
-    else
-      {
-      generated_sender_address = string_sprintf("%s@%s",
-        local_part_quote(authenticated_id), submission_domain);
-      }
-    }
-  else
-    generated_sender_address = string_sprintf("%s@%s",
-      local_part_quote(originator_login), qualify_domain_sender);
+  generated_sender_address = f.submission_mode
+    ? !submission_domain
+    ? string_sprintf("%s@%s",
+       local_part_quote(authenticated_id), qualify_domain_sender)
+    : !*submission_domain                      /* empty => full address */
+    ? string_sprintf("%s", authenticated_id)
+    : string_sprintf("%s@%s",
+       local_part_quote(authenticated_id), submission_domain)
+    : string_sprintf("%s@%s",
+       local_part_quote(originator_login), qualify_domain_sender);
 
   /* Remove permitted prefixes and suffixes from the local part of the From:
   address before doing the comparison with the generated sender. */
 
 
   /* Remove permitted prefixes and suffixes from the local part of the From:
   address before doing the comparison with the generated sender. */
 
-  if (from_address != NULL)
+  if (from_address)
     {
     int slen;
     {
     int slen;
-    uschar *at = (domain == 0)? NULL : from_address + domain - 1;
+    uschar *at = domain ? from_address + domain - 1 : NULL;
 
 
-    if (at != NULL) *at = 0;
+    if (at) *at = 0;
     from_address += route_check_prefix(from_address, local_from_prefix);
     slen = route_check_suffix(from_address, local_from_suffix);
     if (slen > 0)
     from_address += route_check_prefix(from_address, local_from_prefix);
     slen = route_check_suffix(from_address, local_from_suffix);
     if (slen > 0)
@@ -2701,10 +2882,10 @@ if (from_header != NULL &&
       memmove(from_address+slen, from_address, Ustrlen(from_address)-slen);
       from_address += slen;
       }
       memmove(from_address+slen, from_address, Ustrlen(from_address)-slen);
       from_address += slen;
       }
-    if (at != NULL) *at = '@';
+    if (at) *at = '@';
 
 
-    if (strcmpic(generated_sender_address, from_address) == 0 ||
-      (domain == 0 && strcmpic(from_address, originator_login) == 0))
+    if (  strcmpic(generated_sender_address, from_address) == 0
+       || (!domain && strcmpic(from_address, originator_login) == 0))
         make_sender = FALSE;
     }
 
         make_sender = FALSE;
     }
 
@@ -2712,23 +2893,21 @@ if (from_header != NULL &&
   appropriate rewriting rules. */
 
   if (make_sender)
   appropriate rewriting rules. */
 
   if (make_sender)
-    {
-    if (submission_mode && submission_name == NULL)
+    if (f.submission_mode && !submission_name)
       header_add(htype_sender, "%sSender: %s\n", resent_prefix,
         generated_sender_address);
     else
       header_add(htype_sender, "%sSender: %s <%s>\n",
         resent_prefix,
       header_add(htype_sender, "%sSender: %s\n", resent_prefix,
         generated_sender_address);
     else
       header_add(htype_sender, "%sSender: %s <%s>\n",
         resent_prefix,
-        submission_mode? submission_name : originator_name,
+        f.submission_mode ? submission_name : originator_name,
         generated_sender_address);
         generated_sender_address);
-    }
 
   /* Ensure that a non-null envelope sender address corresponds to the
   submission mode sender address. */
 
 
   /* Ensure that a non-null envelope sender address corresponds to the
   submission mode sender address. */
 
-  if (submission_mode && sender_address[0] != 0)
+  if (f.submission_mode && *sender_address)
     {
     {
-    if (sender_address_unrewritten == NULL)
+    if (!sender_address_unrewritten)
       sender_address_unrewritten = sender_address;
     sender_address = generated_sender_address;
     if (Ustrcmp(sender_address_unrewritten, generated_sender_address) != 0)
       sender_address_unrewritten = sender_address;
     sender_address = generated_sender_address;
     if (Ustrcmp(sender_address_unrewritten, generated_sender_address) != 0)
@@ -2741,8 +2920,7 @@ if (from_header != NULL &&
 /* If there are any rewriting rules, apply them to the sender address, unless
 it has already been rewritten as part of verification for SMTP input. */
 
 /* If there are any rewriting rules, apply them to the sender address, unless
 it has already been rewritten as part of verification for SMTP input. */
 
-if (global_rewrite_rules != NULL && sender_address_unrewritten == NULL &&
-    sender_address[0] != 0)
+if (global_rewrite_rules && !sender_address_unrewritten && *sender_address)
   {
   sender_address = rewrite_address(sender_address, FALSE, TRUE,
     global_rewrite_rules, rewrite_existflags);
   {
   sender_address = rewrite_address(sender_address, FALSE, TRUE,
     global_rewrite_rules, rewrite_existflags);
@@ -2765,11 +2943,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. */
 
 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);
   {
   header_line *newh = rewrite_header(h, NULL, NULL, global_rewrite_rules,
     rewrite_existflags, TRUE);
-  if (newh != NULL) h = newh;
+  if (newh) h = newh;
   }
 
 
   }
 
 
@@ -2791,9 +2969,8 @@ to be more confusing if Exim adds one to all remotely-originated messages.
 As per Message-Id, we prepend if resending, else append.
 */
 
 As per Message-Id, we prepend if resending, else append.
 */
 
-if (!date_header_exists &&
-      ((sender_host_address == NULL && !suppress_local_fixups)
-        || submission_mode))
+if (  !date_header_exists
+   && ((!sender_host_address && !f.suppress_local_fixups) || f.submission_mode))
   header_add_at_position(!resents_exist, NULL, FALSE, htype_other,
     "%sDate: %s\n", resent_prefix, tod_stamp(tod_full));
 
   header_add_at_position(!resents_exist, NULL, FALSE, htype_other,
     "%sDate: %s\n", resent_prefix, tod_stamp(tod_full));
 
@@ -2805,7 +2982,7 @@ new Received:) has not yet been set. */
 DEBUG(D_receive)
   {
   debug_printf(">>Headers after rewriting and local additions:\n");
 DEBUG(D_receive)
   {
   debug_printf(">>Headers after rewriting and local additions:\n");
-  for (h = header_list->next; h != NULL; h = h->next)
+  for (h = header_list->next; h; h = h->next)
     debug_printf("%c %s", h->type, h->text);
   debug_printf("\n");
   }
     debug_printf("%c %s", h->type, h->text);
   debug_printf("\n");
   }
@@ -2820,25 +2997,30 @@ if (filter_test != FTEST_NONE)
   return message_ended == END_DOT;
   }
 
   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(FALSE, US"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.
+Having created it, send the headers to the destination. */
+
+if (cutthrough.cctx.sock >= 0 && cutthrough.delivery)
   {
   if (received_count > received_headers_max)
     {
   {
   if (received_count > received_headers_max)
     {
-    cancel_cutthrough_connection("too many headers");
+    cancel_cutthrough_connection(TRUE, US"too many headers");
     if (smtp_input) receive_swallow_smtp();  /* Swallow incoming SMTP */
     log_write(0, LOG_MAIN|LOG_REJECT, "rejected from <%s>%s%s%s%s: "
       "Too many \"Received\" headers",
       sender_address,
     if (smtp_input) receive_swallow_smtp();  /* Swallow incoming SMTP */
     log_write(0, LOG_MAIN|LOG_REJECT, "rejected from <%s>%s%s%s%s: "
       "Too many \"Received\" headers",
       sender_address,
-      (sender_fullhost == NULL)? "" : " H=",
-      (sender_fullhost == NULL)? US"" : sender_fullhost,
-      (sender_ident == NULL)? "" : " U=",
-      (sender_ident == NULL)? US"" : sender_ident);
+      sender_fullhost ? "H=" : "", sender_fullhost ? sender_fullhost : US"",
+      sender_ident ? "U=" : "", sender_ident ? sender_ident : US"");
     message_id[0] = 0;                       /* Indicate no message accepted */
     smtp_reply = US"550 Too many \"Received\" headers - suspected mail loop";
     goto TIDYUP;                             /* Skip to end of function */
     message_id[0] = 0;                       /* Indicate no message accepted */
     smtp_reply = US"550 Too many \"Received\" headers - suspected mail loop";
     goto TIDYUP;                             /* Skip to end of function */
@@ -2851,20 +3033,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
 
 /* 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. */
 
 
-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)
+spool_name = spool_fname(US"input", message_subdir, message_id, US"-D");
+DEBUG(D_receive) debug_printf("Data file name: %s\n", spool_name);
+
+if ((data_fd = Uopen(spool_name, O_RDWR|O_CREAT|O_EXCL, SPOOL_MODE)) < 0)
   {
   if (errno == ENOENT)
     {
   {
   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)
     data_fd = Uopen(spool_name, O_RDWR|O_CREAT|O_EXCL, SPOOL_MODE);
     }
   if (data_fd < 0)
@@ -2886,7 +3066,7 @@ the first line of the file (containing the message ID) because otherwise there
 are problems when Exim is run under Cygwin (I'm told). See comments in
 spool_in.c, where the same locking is done. */
 
 are problems when Exim is run under Cygwin (I'm told). See comments in
 spool_in.c, where the same locking is done. */
 
-data_file = fdopen(data_fd, "w+");
+spool_data_file = fdopen(data_fd, "w+");
 lock_data.l_type = F_WRLCK;
 lock_data.l_whence = SEEK_SET;
 lock_data.l_start = 0;
 lock_data.l_type = F_WRLCK;
 lock_data.l_whence = SEEK_SET;
 lock_data.l_start = 0;
@@ -2903,76 +3083,94 @@ data line (which was read as a header but then turned out not to have the right
 format); write it (remembering that it might contain binary zeros). The result
 of fwrite() isn't inspected; instead we call ferror() below. */
 
 format); write it (remembering that it might contain binary zeros). The result
 of fwrite() isn't inspected; instead we call ferror() below. */
 
-fprintf(data_file, "%s-D\n", message_id);
-if (next != NULL)
+fprintf(spool_data_file, "%s-D\n", message_id);
+if (next)
   {
   uschar *s = next->text;
   int len = next->slen;
   {
   uschar *s = next->text;
   int len = next->slen;
-  len = fwrite(s, 1, len, data_file);  len = len; /* compiler quietening */
-  body_linecount++;                 /* Assumes only 1 line */
+  if (fwrite(s, 1, len, spool_data_file) == len) /* "if" for compiler quietening */
+    body_linecount++;                 /* Assumes only 1 line */
   }
 
 /* Note that we might already be at end of file, or the logical end of file
 (indicated by '.'), or might have encountered an error while writing the
 message id or "next" line. */
 
   }
 
 /* Note that we might already be at end of file, or the logical end of file
 (indicated by '.'), or might have encountered an error while writing the
 message id or "next" line. */
 
-if (!ferror(data_file) && !(receive_feof)() && message_ended != END_DOT)
+if (!ferror(spool_data_file) && !(receive_feof)() && message_ended != END_DOT)
   {
   if (smtp_input)
     {
   {
   if (smtp_input)
     {
-    message_ended = read_message_data_smtp(data_file);
+    message_ended = chunking_state <= CHUNKING_OFFERED
+      ? read_message_data_smtp(spool_data_file)
+      : spool_wireformat
+      ? read_message_bdat_smtp_wire(spool_data_file)
+      : read_message_bdat_smtp(spool_data_file);
     receive_linecount++;                /* The terminating "." line */
     }
     receive_linecount++;                /* The terminating "." line */
     }
-  else message_ended = read_message_data(data_file);
+  else
+    message_ended = read_message_data(spool_data_file);
 
   receive_linecount += body_linecount;  /* For BSMTP errors mainly */
   message_linecount += body_linecount;
 
 
   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(TRUE, US"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(TRUE, US"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 ? " H=" : "",
+       sender_fullhost ? sender_fullhost : US"",
+       sender_ident ? " U=" : "",
+       sender_ident ? sender_ident : US"",
+       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(spool_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, spool_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(TRUE, US"sender protocol error");
+      smtp_reply = US"";               /* Response already sent */
+      message_id[0] = 0;               /* Indicate no message accepted */
+      goto TIDYUP;                     /* Skip to end of function */
     }
   }
 
     }
   }
 
@@ -2990,19 +3188,19 @@ we can then give up. Note that for SMTP input we must swallow the remainder of
 the input in cases of output errors, since the far end doesn't expect to see
 anything until the terminating dot line is sent. */
 
 the input in cases of output errors, since the far end doesn't expect to see
 anything until the terminating dot line is sent. */
 
-if (fflush(data_file) == EOF || ferror(data_file) ||
-    EXIMfsync(fileno(data_file)) < 0 || (receive_ferror)())
+if (fflush(spool_data_file) == EOF || ferror(spool_data_file) ||
+    EXIMfsync(fileno(spool_data_file)) < 0 || (receive_ferror)())
   {
   uschar *msg_errno = US strerror(errno);
   BOOL input_error = (receive_ferror)() != 0;
   uschar *msg = string_sprintf("%s error (%s) while receiving message from %s",
     input_error? "Input read" : "Spool write",
     msg_errno,
   {
   uschar *msg_errno = US strerror(errno);
   BOOL input_error = (receive_ferror)() != 0;
   uschar *msg = string_sprintf("%s error (%s) while receiving message from %s",
     input_error? "Input read" : "Spool write",
     msg_errno,
-    (sender_fullhost != NULL)? sender_fullhost : sender_ident);
+    sender_fullhost ? sender_fullhost : sender_ident);
 
   log_write(0, LOG_MAIN, "Message abandoned: %s", msg);
   Uunlink(spool_name);                /* Lose the data file */
 
   log_write(0, LOG_MAIN, "Message abandoned: %s", msg);
   Uunlink(spool_name);                /* Lose the data file */
-  cancel_cutthrough_connection("error writing spoolfile");
+  cancel_cutthrough_connection(TRUE, US"error writing spoolfile");
 
   if (smtp_input)
     {
 
   if (smtp_input)
     {
@@ -3019,8 +3217,8 @@ if (fflush(data_file) == EOF || ferror(data_file) ||
 
   else
     {
 
   else
     {
-    fseek(data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
-    give_local_error(ERRMESS_IOERR, msg, US"", error_rc, data_file,
+    fseek(spool_data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
+    give_local_error(ERRMESS_IOERR, msg, US"", error_rc, spool_data_file,
       header_list);
     /* Does not return */
     }
       header_list);
     /* Does not return */
     }
@@ -3030,6 +3228,7 @@ if (fflush(data_file) == EOF || ferror(data_file) ||
 /* No I/O errors were encountered while writing the data file. */
 
 DEBUG(D_receive) debug_printf("Data file written for message %s\n", message_id);
 /* No I/O errors were encountered while writing the data file. */
 
 DEBUG(D_receive) debug_printf("Data file written for message %s\n", message_id);
+if (LOGGING(receive_time)) timesince(&received_time_taken, &received_time);
 
 
 /* If there were any bad addresses extracted by -t, or there were no recipients
 
 
 /* If there were any bad addresses extracted by -t, or there were no recipients
@@ -3043,24 +3242,24 @@ recipients or stderr error writing, throw the data file away afterwards, and
 exit. (This can't be SMTP, which always ensures there's at least one
 syntactically good recipient address.) */
 
 exit. (This can't be SMTP, which always ensures there's at least one
 syntactically good recipient address.) */
 
-if (extract_recip && (bad_addresses != NULL || recipients_count == 0))
+if (extract_recip && (bad_addresses || recipients_count == 0))
   {
   DEBUG(D_receive)
     {
     if (recipients_count == 0) debug_printf("*** No recipients\n");
   {
   DEBUG(D_receive)
     {
     if (recipients_count == 0) debug_printf("*** No recipients\n");
-    if (bad_addresses != NULL)
+    if (bad_addresses)
       {
       {
-      error_block *eblock = bad_addresses;
+      error_block * eblock;
       debug_printf("*** Bad address(es)\n");
       debug_printf("*** Bad address(es)\n");
-      while (eblock != NULL)
-        {
+      for (eblock = bad_addresses; eblock; eblock = eblock->next)
         debug_printf("  %s: %s\n", eblock->text1, eblock->text2);
         debug_printf("  %s: %s\n", eblock->text1, eblock->text2);
-        eblock = eblock->next;
-        }
       }
     }
 
       }
     }
 
-  fseek(data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
+  log_write(0, LOG_MAIN|LOG_PANIC, "%s %s found in headers",
+    message_id, bad_addresses ? "bad addresses" : "no recipients");
+
+  fseek(spool_data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
 
   /* If configured to send errors to the sender, but this fails, force
   a failure error code. We use a special one for no recipients so that it
 
   /* If configured to send errors to the sender, but this fails, force
   a failure error code. We use a special one for no recipients so that it
@@ -3071,39 +3270,35 @@ if (extract_recip && (bad_addresses != NULL || recipients_count == 0))
   if (error_handling == ERRORS_SENDER)
     {
     if (!moan_to_sender(
   if (error_handling == ERRORS_SENDER)
     {
     if (!moan_to_sender(
-          (bad_addresses == NULL)?
-            (extracted_ignored? ERRMESS_IGADDRESS : ERRMESS_NOADDRESS) :
-          (recipients_list == NULL)? ERRMESS_BADNOADDRESS : ERRMESS_BADADDRESS,
-          bad_addresses, header_list, data_file, FALSE))
-      error_rc = (bad_addresses == NULL)? EXIT_NORECIPIENTS : EXIT_FAILURE;
+          bad_addresses
+         ? recipients_list ? ERRMESS_BADADDRESS : ERRMESS_BADNOADDRESS
+         : extracted_ignored ? ERRMESS_IGADDRESS : ERRMESS_NOADDRESS,
+          bad_addresses, header_list, spool_data_file, FALSE
+       )              )
+      error_rc = bad_addresses ? EXIT_FAILURE : EXIT_NORECIPIENTS;
     }
   else
     {
     }
   else
     {
-    if (bad_addresses == NULL)
-      {
+    if (!bad_addresses)
       if (extracted_ignored)
         fprintf(stderr, "exim: all -t recipients overridden by command line\n");
       else
         fprintf(stderr, "exim: no recipients in message\n");
       if (extracted_ignored)
         fprintf(stderr, "exim: all -t recipients overridden by command line\n");
       else
         fprintf(stderr, "exim: no recipients in message\n");
-      }
     else
       {
       fprintf(stderr, "exim: invalid address%s",
     else
       {
       fprintf(stderr, "exim: invalid address%s",
-        (bad_addresses->next == NULL)? ":" : "es:\n");
-      while (bad_addresses != NULL)
-        {
+        bad_addresses->next ? "es:\n" : ":");
+      for ( ; bad_addresses; bad_addresses = bad_addresses->next)
         fprintf(stderr, "  %s: %s\n", bad_addresses->text1,
           bad_addresses->text2);
         fprintf(stderr, "  %s: %s\n", bad_addresses->text1,
           bad_addresses->text2);
-        bad_addresses = bad_addresses->next;
-        }
       }
     }
 
   if (recipients_count == 0 || error_handling == ERRORS_STDERR)
     {
     Uunlink(spool_name);
       }
     }
 
   if (recipients_count == 0 || error_handling == ERRORS_STDERR)
     {
     Uunlink(spool_name);
-    (void)fclose(data_file);
-    exim_exit(error_rc);
+    (void)fclose(spool_data_file);
+    exim_exit(error_rc, US"receiving");
     }
   }
 
     }
   }
 
@@ -3124,7 +3319,7 @@ Note: the checking for too many Received: headers is handled by the delivery
 code. */
 /*XXX eventually add excess Received: check for cutthrough case back when classifying them */
 
 code. */
 /*XXX eventually add excess Received: check for cutthrough case back when classifying them */
 
-if (received_header->text == NULL)     /* Non-cutthrough case */
+if (!received_header->text)    /* Non-cutthrough case */
   {
   received_header_gen();
 
   {
   received_header_gen();
 
@@ -3152,12 +3347,11 @@ $message_body_end can be extracted if needed. Allow $recipients in expansions.
 deliver_datafile = data_fd;
 user_msg = NULL;
 
 deliver_datafile = data_fd;
 user_msg = NULL;
 
-enable_dollar_recipients = TRUE;
+f.enable_dollar_recipients = TRUE;
 
 if (recipients_count == 0)
 
 if (recipients_count == 0)
-  {
-  blackholed_by = recipients_discarded? US"MAIL ACL" : US"RCPT ACL";
-  }
+  blackholed_by = f.recipients_discarded ? US"MAIL ACL" : US"RCPT ACL";
+
 else
   {
   /* Handle interactive SMTP messages */
 else
   {
   /* Handle interactive SMTP messages */
@@ -3166,115 +3360,106 @@ else
     {
 
 #ifndef DISABLE_DKIM
     {
 
 #ifndef DISABLE_DKIM
-    if (!dkim_disable_verify)
+    if (!f.dkim_disable_verify)
       {
       {
-      /* Finish verification, this will log individual signature results to
-         the mainlog */
+      /* Finish verification */
       dkim_exim_verify_finish();
 
       /* Check if we must run the DKIM ACL */
       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 =
+        uschar * dkim_verify_signers_expanded =
           expand_string(dkim_verify_signers);
           expand_string(dkim_verify_signers);
-        if (dkim_verify_signers_expanded == NULL)
-          {
+       gstring * results = NULL;
+       int signer_sep = 0;
+       const uschar * ptr;
+       uschar * item;
+       gstring * seen_items = NULL;
+       int old_pool = store_pool;
+
+       store_pool = POOL_PERM;   /* Allow created variables to live to data ACL */
+
+        if (!(ptr = dkim_verify_signers_expanded))
           log_write(0, LOG_MAIN|LOG_PANIC,
             "expansion of dkim_verify_signers option failed: %s",
             expand_string_message);
           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;
-          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)
-            {
-            /* 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)
-              {
-              uschar *seen_item = NULL;
-              uschar seen_item_buf[256];
-              uschar *seen_items_list = seen_items;
-              int seen_this_item = 0;
-
-              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)
-                {
-                DEBUG(D_receive)
-                  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,item);
-            seen_items[seen_items_offset] = '\0';
 
 
-            DEBUG(D_receive)
-              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);
+       /* Default to OK when no items are present */
+       rc = OK;
+       while ((item = string_nextinlist(&ptr, &signer_sep, NULL, 0)))
+         {
+         /* Prevent running ACL for an empty item */
+         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;
+           const uschar * seen_items_list = string_from_gstring(seen_items);
+           int seen_sep = ':';
+           BOOL seen_this_item = FALSE;
+
+           while ((seen_item = string_nextinlist(&seen_items_list, &seen_sep,
+                                                 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);
+             continue;
+             }
 
 
-            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;
-              }
-            }
-          add_acl_headers(ACL_WHERE_DKIM, US"DKIM");
-          if (rc == DISCARD)
-            {
-            recipients_count = 0;
-            blackholed_by = US"DKIM ACL";
-            if (log_msg != NULL)
-              blackhole_log_msg = string_sprintf(": %s", log_msg);
-            }
-          else if (rc != OK)
-            {
-            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_reply = US"";       /* Indicate reply already sent */
-            message_id[0] = 0;       /* Indicate no message accepted */
-            goto TIDYUP;             /* Skip to end of function */
-            }
-          }
+           seen_items = string_catn(seen_items, US":", 1);
+           }
+         seen_items = string_cat(seen_items, item);
+
+         rc = dkim_exim_acl_run(item, &results, &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(TRUE, US"dkim acl not ok");
+           break;
+           }
+         }
+       dkim_verify_status = string_from_gstring(results);
+       store_pool = old_pool;
+       add_acl_headers(ACL_WHERE_DKIM, US"DKIM");
+       if (rc == DISCARD)
+         {
+         recipients_count = 0;
+         blackholed_by = US"DKIM ACL";
+         if (log_msg)
+           blackhole_log_msg = string_sprintf(": %s", log_msg);
+         }
+       else if (rc != OK)
+         {
+         Uunlink(spool_name);
+         if (smtp_handle_acl_fail(ACL_WHERE_DKIM, rc, user_msg, log_msg) != 0)
+           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 */
+         }
         }
         }
+      else
+       dkim_exim_verify_log_all();
       }
 #endif /* DISABLE_DKIM */
 
 #ifdef WITH_CONTENT_SCAN
       }
 #endif /* DISABLE_DKIM */
 
 #ifdef WITH_CONTENT_SCAN
-    if (recipients_count > 0 &&
-        acl_smtp_mime != NULL &&
-        !run_mime_acl(acl_smtp_mime, &smtp_yield, &smtp_reply, &blackholed_by))
+    if (  recipients_count > 0
+       && acl_smtp_mime
+       && !run_mime_acl(acl_smtp_mime, &smtp_yield, &smtp_reply, &blackholed_by)
+       )
       goto TIDYUP;
 #endif /* WITH_CONTENT_SCAN */
 
       goto TIDYUP;
 #endif /* WITH_CONTENT_SCAN */
 
@@ -3289,7 +3474,7 @@ else
       int all_pass = OK;
       int all_fail = FAIL;
 
       int all_pass = OK;
       int all_fail = FAIL;
 
-      smtp_printf("353 PRDR content analysis beginning\r\n");
+      smtp_printf("353 PRDR content analysis beginning\r\n", TRUE);
       /* Loop through recipients, responses must be in same order received */
       for (c = 0; recipients_count > c; c++)
         {
       /* Loop through recipients, responses must be in same order received */
       for (c = 0; recipients_count > c; c++)
         {
@@ -3364,14 +3549,14 @@ else
         {
         recipients_count = 0;
         blackholed_by = US"DATA ACL";
         {
         recipients_count = 0;
         blackholed_by = US"DATA ACL";
-        if (log_msg != NULL)
+        if (log_msg)
           blackhole_log_msg = string_sprintf(": %s", log_msg);
           blackhole_log_msg = string_sprintf(": %s", log_msg);
-       cancel_cutthrough_connection("data acl discard");
+       cancel_cutthrough_connection(TRUE, US"data acl discard");
         }
       else if (rc != OK)
         {
         Uunlink(spool_name);
         }
       else if (rc != OK)
         {
         Uunlink(spool_name);
-       cancel_cutthrough_connection("data acl not ok");
+       cancel_cutthrough_connection(TRUE, US"data acl not ok");
 #ifdef WITH_CONTENT_SCAN
         unspool_mbox();
 #endif
 #ifdef WITH_CONTENT_SCAN
         unspool_mbox();
 #endif
@@ -3379,7 +3564,7 @@ else
        dcc_ok = 0;
 #endif
         if (smtp_handle_acl_fail(ACL_WHERE_DATA, rc, user_msg, log_msg) != 0)
        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 */
         smtp_reply = US"";       /* Indicate reply already sent */
         message_id[0] = 0;       /* Indicate no message accepted */
         goto TIDYUP;             /* Skip to end of function */
@@ -3394,21 +3579,23 @@ else
     {
 
 #ifdef WITH_CONTENT_SCAN
     {
 
 #ifdef WITH_CONTENT_SCAN
-    if (acl_not_smtp_mime != NULL &&
-        !run_mime_acl(acl_not_smtp_mime, &smtp_yield, &smtp_reply,
-          &blackholed_by))
+    if (  acl_not_smtp_mime
+       && !run_mime_acl(acl_not_smtp_mime, &smtp_yield, &smtp_reply,
+          &blackholed_by)
+       )
       goto TIDYUP;
 #endif /* WITH_CONTENT_SCAN */
 
       goto TIDYUP;
 #endif /* WITH_CONTENT_SCAN */
 
-    if (acl_not_smtp != NULL)
+    if (acl_not_smtp)
       {
       uschar *user_msg, *log_msg;
       {
       uschar *user_msg, *log_msg;
+      f.authentication_local = TRUE;
       rc = acl_check(ACL_WHERE_NOTSMTP, NULL, acl_not_smtp, &user_msg, &log_msg);
       if (rc == DISCARD)
         {
         recipients_count = 0;
         blackholed_by = US"non-SMTP ACL";
       rc = acl_check(ACL_WHERE_NOTSMTP, NULL, acl_not_smtp, &user_msg, &log_msg);
       if (rc == DISCARD)
         {
         recipients_count = 0;
         blackholed_by = US"non-SMTP ACL";
-        if (log_msg != NULL)
+        if (log_msg)
           blackhole_log_msg = string_sprintf(": %s", log_msg);
         }
       else if (rc != OK)
           blackhole_log_msg = string_sprintf(": %s", log_msg);
         }
       else if (rc != OK)
@@ -3423,21 +3610,19 @@ else
         /* The ACL can specify where rejections are to be logged, possibly
         nowhere. The default is main and reject logs. */
 
         /* The ACL can specify where rejections are to be logged, possibly
         nowhere. The default is main and reject logs. */
 
-        if (log_reject_target != 0)
+        if (log_reject_target)
           log_write(0, log_reject_target, "F=<%s> rejected by non-SMTP ACL: %s",
             sender_address, log_msg);
 
           log_write(0, log_reject_target, "F=<%s> rejected by non-SMTP ACL: %s",
             sender_address, log_msg);
 
-        if (user_msg == NULL) user_msg = US"local configuration problem";
+        if (!user_msg) user_msg = US"local configuration problem";
         if (smtp_batched_input)
         if (smtp_batched_input)
-          {
           moan_smtp_batch(NULL, "%d %s", 550, user_msg);
           /* Does not return */
           moan_smtp_batch(NULL, "%d %s", 550, user_msg);
           /* Does not return */
-          }
         else
           {
         else
           {
-          fseek(data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
+          fseek(spool_data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
           give_local_error(ERRMESS_LOCAL_ACL, user_msg,
           give_local_error(ERRMESS_LOCAL_ACL, user_msg,
-            US"message rejected by non-SMTP ACL: ", error_rc, data_file,
+            US"message rejected by non-SMTP ACL: ", error_rc, spool_data_file,
               header_list);
           /* Does not return */
           }
               header_list);
           /* Does not return */
           }
@@ -3448,8 +3633,8 @@ else
 
   /* The applicable ACLs have been run */
 
 
   /* The applicable ACLs have been run */
 
-  if (deliver_freeze) frozen_by = US"ACL";     /* for later logging */
-  if (queue_only_policy) queued_by = US"ACL";
+  if (f.deliver_freeze) frozen_by = US"ACL";     /* for later logging */
+  if (f.queue_only_policy) queued_by = US"ACL";
   }
 
 #ifdef WITH_CONTENT_SCAN
   }
 
 #ifdef WITH_CONTENT_SCAN
@@ -3461,6 +3646,7 @@ dcc_ok = 0;
 #endif
 
 
 #endif
 
 
+#ifdef HAVE_LOCAL_SCAN
 /* The final check on the message is to run the scan_local() function. The
 version supplied with Exim always accepts, but this is a hook for sysadmins to
 supply their own checking code. The local_scan() function is run even when all
 /* The final check on the message is to run the scan_local() function. The
 version supplied with Exim always accepts, but this is a hook for sysadmins to
 supply their own checking code. The local_scan() function is run even when all
@@ -3471,36 +3657,59 @@ lseek(data_fd, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
 /* Arrange to catch crashes in local_scan(), so that the -D file gets
 deleted, and the incident gets logged. */
 
 /* Arrange to catch crashes in local_scan(), so that the -D file gets
 deleted, and the incident gets logged. */
 
-os_non_restarting_signal(SIGSEGV, local_scan_crash_handler);
-os_non_restarting_signal(SIGFPE, local_scan_crash_handler);
-os_non_restarting_signal(SIGILL, local_scan_crash_handler);
-os_non_restarting_signal(SIGBUS, local_scan_crash_handler);
-
-DEBUG(D_receive) debug_printf("calling local_scan(); timeout=%d\n",
-  local_scan_timeout);
-local_scan_data = NULL;
-
-os_non_restarting_signal(SIGALRM, local_scan_timeout_handler);
-if (local_scan_timeout > 0) alarm(local_scan_timeout);
-rc = local_scan(data_fd, &local_scan_data);
-alarm(0);
-os_non_restarting_signal(SIGALRM, sigalrm_handler);
-
-enable_dollar_recipients = FALSE;
-
-store_pool = POOL_MAIN;   /* In case changed */
-DEBUG(D_receive) debug_printf("local_scan() returned %d %s\n", rc,
-  local_scan_data);
-
-os_non_restarting_signal(SIGSEGV, SIG_DFL);
-os_non_restarting_signal(SIGFPE, SIG_DFL);
-os_non_restarting_signal(SIGILL, SIG_DFL);
-os_non_restarting_signal(SIGBUS, SIG_DFL);
+if (sigsetjmp(local_scan_env, 1) == 0)
+  {
+  had_local_scan_crash = 0;
+  os_non_restarting_signal(SIGSEGV, local_scan_crash_handler);
+  os_non_restarting_signal(SIGFPE, local_scan_crash_handler);
+  os_non_restarting_signal(SIGILL, local_scan_crash_handler);
+  os_non_restarting_signal(SIGBUS, local_scan_crash_handler);
+
+  DEBUG(D_receive) debug_printf("calling local_scan(); timeout=%d\n",
+    local_scan_timeout);
+  local_scan_data = NULL;
+
+  had_local_scan_timeout = 0;
+  os_non_restarting_signal(SIGALRM, local_scan_timeout_handler);
+  if (local_scan_timeout > 0) ALARM(local_scan_timeout);
+  rc = local_scan(data_fd, &local_scan_data);
+  ALARM_CLR(0);
+  os_non_restarting_signal(SIGALRM, sigalrm_handler);
+
+  f.enable_dollar_recipients = FALSE;
+
+  store_pool = POOL_MAIN;   /* In case changed */
+  DEBUG(D_receive) debug_printf("local_scan() returned %d %s\n", rc,
+    local_scan_data);
+
+  os_non_restarting_signal(SIGSEGV, SIG_DFL);
+  os_non_restarting_signal(SIGFPE, SIG_DFL);
+  os_non_restarting_signal(SIGILL, SIG_DFL);
+  os_non_restarting_signal(SIGBUS, SIG_DFL);
+  }
+else
+  {
+  if (had_local_scan_crash)
+    {
+    log_write(0, LOG_MAIN|LOG_REJECT, "local_scan() function crashed with "
+      "signal %d - message temporarily rejected (size %d)",
+      had_local_scan_crash, message_size);
+    receive_bomb_out(US"local-scan-error", US"local verification problem");
+    /* Does not return */
+    }
+  if (had_local_scan_timeout)
+    {
+    log_write(0, LOG_MAIN|LOG_REJECT, "local_scan() function timed out - "
+      "message temporarily rejected (size %d)", message_size);
+    receive_bomb_out(US"local-scan-timeout", US"local verification problem");
+    /* Does not return */
+    }
+  }
 
 /* The length check is paranoia against some runaway code, and also because
 (for a success return) lines in the spool file are read into big_buffer. */
 
 
 /* The length check is paranoia against some runaway code, and also because
 (for a success return) lines in the spool file are read into big_buffer. */
 
-if (local_scan_data != NULL)
+if (local_scan_data)
   {
   int len = Ustrlen(local_scan_data);
   if (len > LOCAL_SCAN_MAX_RETURN) len = LOCAL_SCAN_MAX_RETURN;
   {
   int len = Ustrlen(local_scan_data);
   if (len > LOCAL_SCAN_MAX_RETURN) len = LOCAL_SCAN_MAX_RETURN;
@@ -3509,9 +3718,9 @@ if (local_scan_data != NULL)
 
 if (rc == LOCAL_SCAN_ACCEPT_FREEZE)
   {
 
 if (rc == LOCAL_SCAN_ACCEPT_FREEZE)
   {
-  if (!deliver_freeze)         /* ACL might have already frozen */
+  if (!f.deliver_freeze)         /* ACL might have already frozen */
     {
     {
-    deliver_freeze = TRUE;
+    f.deliver_freeze = TRUE;
     deliver_frozen_at = time(NULL);
     frozen_by = US"local_scan()";
     }
     deliver_frozen_at = time(NULL);
     frozen_by = US"local_scan()";
     }
@@ -3519,9 +3728,9 @@ if (rc == LOCAL_SCAN_ACCEPT_FREEZE)
   }
 else if (rc == LOCAL_SCAN_ACCEPT_QUEUE)
   {
   }
 else if (rc == LOCAL_SCAN_ACCEPT_QUEUE)
   {
-  if (!queue_only_policy)      /* ACL might have already queued */
+  if (!f.queue_only_policy)      /* ACL might have already queued */
     {
     {
-    queue_only_policy = TRUE;
+    f.queue_only_policy = TRUE;
     queued_by = US"local_scan()";
     }
   rc = LOCAL_SCAN_ACCEPT;
     queued_by = US"local_scan()";
     }
   rc = LOCAL_SCAN_ACCEPT;
@@ -3532,7 +3741,7 @@ the spool file gets corrupted. Ensure that all recipients are qualified. */
 
 if (rc == LOCAL_SCAN_ACCEPT)
   {
 
 if (rc == LOCAL_SCAN_ACCEPT)
   {
-  if (local_scan_data != NULL)
+  if (local_scan_data)
     {
     uschar *s;
     for (s = local_scan_data; *s != 0; s++) if (*s == '\n') *s = ' ';
     {
     uschar *s;
     for (s = local_scan_data; *s != 0; s++) if (*s == '\n') *s = ' ';
@@ -3554,10 +3763,8 @@ multiline SMTP responses. */
 else
   {
   uschar *istemp = US"";
 else
   {
   uschar *istemp = US"";
-  uschar *s = NULL;
   uschar *smtp_code;
   uschar *smtp_code;
-  int size = 0;
-  int sptr = 0;
+  gstring * g;
 
   errmsg = local_scan_data;
 
 
   errmsg = local_scan_data;
 
@@ -3565,38 +3772,37 @@ else
   switch(rc)
     {
     default:
   switch(rc)
     {
     default:
-    log_write(0, LOG_MAIN, "invalid return %d from local_scan(). Temporary "
-      "rejection given", rc);
-    goto TEMPREJECT;
+      log_write(0, LOG_MAIN, "invalid return %d from local_scan(). Temporary "
+       "rejection given", rc);
+      goto TEMPREJECT;
 
     case LOCAL_SCAN_REJECT_NOLOGHDR:
 
     case LOCAL_SCAN_REJECT_NOLOGHDR:
-    log_extra_selector &= ~LX_rejected_header;
-    /* Fall through */
+      BIT_CLEAR(log_selector, log_selector_size, Li_rejected_header);
+      /* Fall through */
 
     case LOCAL_SCAN_REJECT:
 
     case LOCAL_SCAN_REJECT:
-    smtp_code = US"550";
-    if (errmsg == NULL) errmsg =  US"Administrative prohibition";
-    break;
+      smtp_code = US"550";
+      if (!errmsg) errmsg =  US"Administrative prohibition";
+      break;
 
     case LOCAL_SCAN_TEMPREJECT_NOLOGHDR:
 
     case LOCAL_SCAN_TEMPREJECT_NOLOGHDR:
-    log_extra_selector &= ~LX_rejected_header;
-    /* Fall through */
+      BIT_CLEAR(log_selector, log_selector_size, Li_rejected_header);
+      /* Fall through */
 
     case LOCAL_SCAN_TEMPREJECT:
     TEMPREJECT:
 
     case LOCAL_SCAN_TEMPREJECT:
     TEMPREJECT:
-    smtp_code = US"451";
-    if (errmsg == NULL) errmsg = US"Temporary local problem";
-    istemp = US"temporarily ";
-    break;
+      smtp_code = US"451";
+      if (!errmsg) errmsg = US"Temporary local problem";
+      istemp = US"temporarily ";
+      break;
     }
 
     }
 
-  s = string_append(s, &size, &sptr, 2, US"F=",
-    (sender_address[0] == 0)? US"<>" : sender_address);
-  s = add_host_info_for_log(s, &size, &sptr);
-  s[sptr] = 0;
+  g = string_append(NULL, 2, US"F=",
+    sender_address[0] == 0 ? US"<>" : sender_address);
+  g = add_host_info_for_log(g);
 
   log_write(0, LOG_MAIN|LOG_REJECT, "%s %srejected by local_scan(): %.256s",
 
   log_write(0, LOG_MAIN|LOG_REJECT, "%s %srejected by local_scan(): %.256s",
-    s, istemp, string_printing(errmsg));
+    string_from_gstring(g), istemp, string_printing(errmsg));
 
   if (smtp_input)
     {
 
   if (smtp_input)
     {
@@ -3608,16 +3814,14 @@ else
       goto TIDYUP;                  /* Skip to end of function */
       }
     else
       goto TIDYUP;                  /* Skip to end of function */
       }
     else
-      {
       moan_smtp_batch(NULL, "%s %s", smtp_code, errmsg);
       /* Does not return */
       moan_smtp_batch(NULL, "%s %s", smtp_code, errmsg);
       /* Does not return */
-      }
     }
   else
     {
     }
   else
     {
-    fseek(data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
+    fseek(spool_data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
     give_local_error(ERRMESS_LOCAL_SCAN, errmsg,
     give_local_error(ERRMESS_LOCAL_SCAN, errmsg,
-      US"message rejected by local scan code: ", error_rc, data_file,
+      US"message rejected by local scan code: ", error_rc, spool_data_file,
         header_list);
     /* Does not return */
     }
         header_list);
     /* Does not return */
     }
@@ -3628,21 +3832,22 @@ the message to be abandoned. */
 
 signal(SIGTERM, SIG_IGN);
 signal(SIGINT, SIG_IGN);
 
 signal(SIGTERM, SIG_IGN);
 signal(SIGINT, SIG_IGN);
+#endif /* HAVE_LOCAL_SCAN */
 
 
 /* Ensure the first time flag is set in the newly-received message. */
 
 
 
 /* Ensure the first time flag is set in the newly-received message. */
 
-deliver_firsttime = TRUE;
+f.deliver_firsttime = TRUE;
 
 #ifdef EXPERIMENTAL_BRIGHTMAIL
 
 #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);
   lseek(data_fd, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
   bmi_verdicts = bmi_process_message(header_list, data_fd);
-};
+  }
 #endif
 
 #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. */
 
 an ACL or by local_scan(). The new time is the time that all reception
 processing is complete. */
 
@@ -3656,8 +3861,8 @@ memcpy(received_header->text + received_header->slen - tslen - 1,
 
 if (mua_wrapper)
   {
 
 if (mua_wrapper)
   {
-  deliver_freeze = FALSE;
-  queue_only_policy = FALSE;
+  f.deliver_freeze = FALSE;
+  f.queue_only_policy = FALSE;
   }
 
 /* Keep the data file open until we have written the header file, in order to
   }
 
 /* Keep the data file open until we have written the header file, in order to
@@ -3665,19 +3870,18 @@ hold onto the lock. In a -bh run, or if the message is to be blackholed, we
 don't write the header file, and we unlink the data file. If writing the header
 file fails, we have failed to accept this message. */
 
 don't write the header file, and we unlink the data file. If writing the header
 file fails, we have failed to accept this message. */
 
-if (host_checking || blackholed_by != NULL)
+if (host_checking || blackholed_by)
   {
   header_line *h;
   Uunlink(spool_name);
   msg_size = 0;                                  /* Compute size for log line */
   {
   header_line *h;
   Uunlink(spool_name);
   msg_size = 0;                                  /* Compute size for log line */
-  for (h = header_list; h != NULL; h = h->next)
+  for (h = header_list; h; h = h->next)
     if (h->type != '*') msg_size += h->slen;
   }
 
 /* Write the -H file */
 
 else
     if (h->type != '*') msg_size += h->slen;
   }
 
 /* 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);
   if ((msg_size = spool_write_header(message_id, SW_RECEIVING, &errmsg)) < 0)
     {
     log_write(0, LOG_MAIN, "Message abandoned: %s", errmsg);
@@ -3691,112 +3895,133 @@ else
       }
     else
       {
       }
     else
       {
-      fseek(data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
-      give_local_error(ERRMESS_IOERR, errmsg, US"", error_rc, data_file,
+      fseek(spool_data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
+      give_local_error(ERRMESS_IOERR, errmsg, US"", error_rc, spool_data_file,
         header_list);
       /* Does not return */
       }
     }
         header_list);
       /* Does not return */
       }
     }
-  }
 
 
 /* The message has now been successfully received. */
 
 receive_messagecount++;
 
 
 
 /* The message has now been successfully received. */
 
 receive_messagecount++;
 
-/* In SMTP sessions we may receive several in one connection. After each one,
-we wait for the clock to tick at the level of message-id granularity. This is
-so that the combination of time+pid is unique, even on systems where the pid
-can be re-used within our time interval. We can't shorten the interval without
-re-designing the message-id. See comments above where the message id is
-created. This is Something For The Future. */
-
-message_id_tv.tv_usec = (message_id_tv.tv_usec/id_resolution) * id_resolution;
-exim_wait_tick(&message_id_tv, id_resolution);
-
 /* Add data size to written header size. We do not count the initial file name
 that is in the file, but we do add one extra for the notional blank line that
 precedes the data. This total differs from message_size in that it include the
 added Received: header and any other headers that got created locally. */
 
 /* Add data size to written header size. We do not count the initial file name
 that is in the file, but we do add one extra for the notional blank line that
 precedes the data. This total differs from message_size in that it include the
 added Received: header and any other headers that got created locally. */
 
-fflush(data_file);
+if (fflush(spool_data_file))
+  {
+  errmsg = string_sprintf("Spool write error: %s", strerror(errno));
+  log_write(0, LOG_MAIN, "%s\n", errmsg);
+  Uunlink(spool_name);           /* Lose the data file */
+
+  if (smtp_input)
+    {
+    smtp_reply = US"451 Error in writing spool file";
+    message_id[0] = 0;          /* Indicate no message accepted */
+    goto TIDYUP;
+    }
+  else
+    {
+    fseek(spool_data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
+    give_local_error(ERRMESS_IOERR, errmsg, US"", error_rc, spool_data_file,
+      header_list);
+    /* Does not return */
+    }
+  }
 fstat(data_fd, &statbuf);
 
 msg_size += statbuf.st_size - SPOOL_DATA_START_OFFSET + 1;
 
 /* Generate a "message received" log entry. We do this by building up a dynamic
 fstat(data_fd, &statbuf);
 
 msg_size += statbuf.st_size - SPOOL_DATA_START_OFFSET + 1;
 
 /* Generate a "message received" log entry. We do this by building up a dynamic
-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
+string as required.  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
 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. */
 
 it. */
 
-size = 256;
-sptr = 0;
-s = store_get(size);
+g = string_get(256);
 
 
-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, US" R=", message_reference);
+g = string_append(g, 2,
+  fake_response == FAIL ? US"(= " : US"<= ",
+  sender_address[0] == 0 ? US"<>" : sender_address);
+if (message_reference)
+  g = string_append(g, 2, US" R=", message_reference);
 
 
-s = add_host_info_for_log(s, &size, &sptr);
+g = add_host_info_for_log(g);
 
 #ifdef SUPPORT_TLS
 
 #ifdef SUPPORT_TLS
-if (log_extra_selector & LX_tls_cipher && tls_in.cipher)
-  s = string_append(s, &size, &sptr, 2, US" X=", tls_in.cipher);
-if (log_extra_selector & LX_tls_certificate_verified && 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)
-  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)
-  s = string_append(s, &size, &sptr, 3, US" SNI=\"",
-    string_printing(tls_in.sni), US"\"");
+if (LOGGING(tls_cipher) && tls_in.cipher)
+  g = string_append(g, 2, US" X=", tls_in.cipher);
+if (LOGGING(tls_certificate_verified) && tls_in.cipher)
+  g = string_append(g, 2, US" CV=", tls_in.certificate_verified ? "yes":"no");
+if (LOGGING(tls_peerdn) && tls_in.peerdn)
+  g = string_append(g, 3, US" DN=\"", string_printing(tls_in.peerdn), US"\"");
+if (LOGGING(tls_sni) && tls_in.sni)
+  g = string_append(g, 3, US" SNI=\"", string_printing(tls_in.sni), US"\"");
 #endif
 
 if (sender_host_authenticated)
   {
 #endif
 
 if (sender_host_authenticated)
   {
-  s = string_append(s, &size, &sptr, 2, US" A=", sender_host_authenticated);
-  if (authenticated_id != NULL)
+  g = string_append(g, 2, US" A=", sender_host_authenticated);
+  if (authenticated_id)
     {
     {
-    s = string_append(s, &size, &sptr, 2, US":", authenticated_id);
-    if (log_extra_selector & LX_smtp_mailauth  &&  authenticated_sender != NULL)
-      s = string_append(s, &size, &sptr, 2, US":", authenticated_sender);
+    g = string_append(g, 2, US":", authenticated_id);
+    if (LOGGING(smtp_mailauth) && authenticated_sender)
+      g = string_append(g, 2, US":", authenticated_sender);
     }
   }
 
 #ifndef DISABLE_PRDR
 if (prdr_requested)
     }
   }
 
 #ifndef DISABLE_PRDR
 if (prdr_requested)
-  s = string_append(s, &size, &sptr, 1, US" PRDR");
+  g = string_catn(g, US" PRDR", 5);
 #endif
 
 #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))
+  g = string_append(g, 2, US" PRX=", proxy_local_address);
 #endif
 
 #endif
 
+if (chunking_state > CHUNKING_OFFERED)
+  g = string_catn(g, US" K", 2);
+
 sprintf(CS big_buffer, "%d", msg_size);
 sprintf(CS big_buffer, "%d", msg_size);
-s = string_append(s, &size, &sptr, 2, US" S=", big_buffer);
+g = string_append(g, 2, US" S=", big_buffer);
 
 /* log 8BITMIME mode announced in MAIL_FROM
    0 ... no BODY= used
    7 ... 7BIT
    8 ... 8BITMIME */
 
 /* log 8BITMIME mode announced in MAIL_FROM
    0 ... no BODY= used
    7 ... 7BIT
    8 ... 8BITMIME */
-if (log_extra_selector & LX_8bitmime)
+if (LOGGING(8bitmime))
   {
   sprintf(CS big_buffer, "%d", body_8bitmime);
   {
   sprintf(CS big_buffer, "%d", body_8bitmime);
-  s = string_append(s, &size, &sptr, 2, US" M8S=", big_buffer);
+  g = string_append(g, 2, US" M8S=", big_buffer);
   }
 
   }
 
+#ifndef DISABLE_DKIM
+if (LOGGING(dkim) && dkim_verify_overall)
+  g = string_append(g, 2, US" DKIM=", dkim_verify_overall);
+# ifdef EXPERIMENTAL_ARC
+if (LOGGING(dkim) && arc_state && Ustrcmp(arc_state, "pass") == 0)
+  g = string_catn(g, US" ARC", 4);
+# endif
+#endif
+
+if (LOGGING(receive_time))
+  g = string_append(g, 2, US" RT=", string_timediff(&received_time_taken));
+
+if (*queue_name)
+  g = string_append(g, 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 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;
   {
   uschar *old_id;
   BOOL save_allow_domain_literals = allow_domain_literals;
@@ -3805,13 +4030,13 @@ if (msgid_header != NULL)
     &errmsg, &start, &end, &domain, FALSE);
   allow_domain_literals = save_allow_domain_literals;
   if (old_id != NULL)
     &errmsg, &start, &end, &domain, FALSE);
   allow_domain_literals = save_allow_domain_literals;
   if (old_id != NULL)
-    s = string_append(s, &size, &sptr, 2, US" id=", string_printing(old_id));
+    g = string_append(g, 2, US" id=", string_printing(old_id));
   }
 
 /* If subject logging is turned on, create suitable printing-character
 text. By expanding $h_subject: we make use of the MIME decoding. */
 
   }
 
 /* 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)
   {
   int i;
   uschar *p = big_buffer;
   {
   int i;
   uschar *p = big_buffer;
@@ -3828,59 +4053,56 @@ if ((log_extra_selector & LX_subject) != 0 && subject_header != NULL)
     }
   *p++ = '\"';
   *p = 0;
     }
   *p++ = '\"';
   *p = 0;
-  s = string_append(s, &size, &sptr, 2, US" T=", string_printing(big_buffer));
+  g = string_append(g, 2, US" T=", string_printing(big_buffer));
   }
 
 /* Terminate the string: string_cat() and string_append() leave room, but do
 not put the zero in. */
 
   }
 
 /* Terminate the string: string_cat() and string_append() leave room, but do
 not put the zero in. */
 
-s[sptr] = 0;
+(void) string_from_gstring(g);
 
 /* Create a message log file if message logs are being used and this message is
 not blackholed. Write the reception stuff to it. We used to leave message log
 creation until the first delivery, but this has proved confusing for some
 people. */
 
 
 /* Create a message log file if message logs are being used and this message is
 not blackholed. Write the reception stuff to it. We used to leave message log
 creation until the first delivery, but this has proved confusing for some
 people. */
 
-if (message_logs && blackholed_by == NULL)
+if (message_logs && !blackholed_by)
   {
   int fd;
   {
   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)
+  uschar * m_name = spool_fname(US"msglog", message_subdir, message_id, US"");
+  
+  if (  (fd = Uopen(m_name, O_WRONLY|O_APPEND|O_CREAT, SPOOL_MODE)) < 0
+     && errno == ENOENT
+     )
     {
     {
-    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(spool_name, O_WRONLY|O_APPEND|O_CREAT, SPOOL_MODE);
+    (void)directory_make(spool_directory,
+                       spool_sname(US"msglog", message_subdir),
+                       MSGLOG_DIRECTORY_MODE, TRUE);
+    fd = Uopen(m_name, O_WRONLY|O_APPEND|O_CREAT, SPOOL_MODE);
     }
 
   if (fd < 0)
     }
 
   if (fd < 0)
-    {
     log_write(0, LOG_MAIN|LOG_PANIC, "Couldn't open message log %s: %s",
     log_write(0, LOG_MAIN|LOG_PANIC, "Couldn't open message log %s: %s",
-      spool_name, strerror(errno));
-    }
-
+      m_name, strerror(errno));
   else
     {
     FILE *message_log = fdopen(fd, "a");
   else
     {
     FILE *message_log = fdopen(fd, "a");
-    if (message_log == NULL)
+    if (!message_log)
       {
       log_write(0, LOG_MAIN|LOG_PANIC, "Couldn't fdopen message log %s: %s",
       {
       log_write(0, LOG_MAIN|LOG_PANIC, "Couldn't fdopen message log %s: %s",
-        spool_name, strerror(errno));
+        m_name, strerror(errno));
       (void)close(fd);
       }
     else
       {
       uschar *now = tod_stamp(tod_log);
       (void)close(fd);
       }
     else
       {
       uschar *now = tod_stamp(tod_log);
-      fprintf(message_log, "%s Received from %s\n", now, s+3);
-      if (deliver_freeze) fprintf(message_log, "%s frozen by %s\n", now,
+      fprintf(message_log, "%s Received from %s\n", now, g->s+3);
+      if (f.deliver_freeze) fprintf(message_log, "%s frozen by %s\n", now,
         frozen_by);
         frozen_by);
-      if (queue_only_policy) fprintf(message_log,
-        "%s no immediate delivery: queued by %s\n", now, queued_by);
+      if (f.queue_only_policy) fprintf(message_log,
+        "%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);
       }
     }
       (void)fclose(message_log);
       }
     }
@@ -3890,7 +4112,7 @@ if (message_logs && blackholed_by == NULL)
 arrival, and outputting an SMTP response. While writing to the log, set a flag
 to cause a call to receive_bomb_out() if the log cannot be opened. */
 
 arrival, and outputting an SMTP response. While writing to the log, set a flag
 to cause a call to receive_bomb_out() if the log cannot be opened. */
 
-receive_call_bombout = TRUE;
+f.receive_call_bombout = TRUE;
 
 /* Before sending an SMTP response in a TCP/IP session, we check to see if the
 connection has gone away. This can only be done if there is no unconsumed input
 
 /* Before sending an SMTP response in a TCP/IP session, we check to see if the
 connection has gone away. This can only be done if there is no unconsumed input
@@ -3910,7 +4132,7 @@ Of course, since TCP/IP is asynchronous, there is always a chance that the
 connection will vanish between the time of this test and the sending of the
 response, but the chance of this happening should be small. */
 
 connection will vanish between the time of this test and the sending of the
 response, but the chance of this happening should be small. */
 
-if (smtp_input && sender_host_address != NULL && !sender_host_notsocket &&
+if (smtp_input && sender_host_address && !f.sender_host_notsocket &&
     !receive_smtp_buffered())
   {
   struct timeval tv;
     !receive_smtp_buffered())
   {
   struct timeval tv;
@@ -3922,34 +4144,25 @@ if (smtp_input && sender_host_address != NULL && !sender_host_notsocket &&
 
   if (select(fileno(smtp_in) + 1, &select_check, NULL, NULL, &tv) != 0)
     {
 
   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
       {
     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 */
 
       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 = add_host_info_for_log(s, &size, &sptr);
-      s[sptr] = 0;
-      log_write(0, LOG_MAIN, "%s", s);
+      g->ptr = 0;
+      g = string_cat(g, US"SMTP connection lost after final dot");
+      g = add_host_info_for_log(g);
+      log_write(0, LOG_MAIN, "%s", string_from_gstring(g));
 
       /* Delete the files for this aborted message. */
 
 
       /* 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_name);
+      Uunlink(spool_fname(US"input", message_subdir, message_id, US"-H"));
+      Uunlink(spool_fname(US"msglog", message_subdir, message_id, US""));
 
       goto TIDYUP;
       }
 
       goto TIDYUP;
       }
@@ -3964,16 +4177,17 @@ for this message. */
 
    Send dot onward.  If accepted, wipe the spooled files, log as delivered and accept
    the sender's dot (below).
 
    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.
 */
 
    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.cctx.sock >= 0 && cutthrough.delivery)
   {
   {
-  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])
     {
                                        /* Logging was done in finaldot() */
   switch(msg[0])
     {
@@ -3981,13 +4195,19 @@ if(cutthrough_fd >= 0)
       cutthrough_done = ACCEPTED;
       break;                                   /* message_id needed for SMTP accept below */
 
       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);
+      cutthrough_done = TMP_REJ;               /* Avoid the usual immediate delivery attempt */
+      break;                                   /* message_id needed for SMTP accept below */
+
     default:   /* Unknown response, or error.  Treat as temp-reject.         */
     default:   /* Unknown response, or error.  Treat as temp-reject.         */
-    case '4':  /* Temp-reject. Keep spoolfiles and accept. */
+      if (cutthrough.defer_pass) smtp_reply = US"450 Onward transmission not accepted";
       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 */
       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;
     }
       cutthrough_done = PERM_REJ;
       break;
     }
@@ -4000,48 +4220,76 @@ if(!smtp_reply)
 #endif
   {
   log_write(0, LOG_MAIN |
 #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),
-    "%s", s);
+    (LOGGING(received_recipients) ? LOG_RECIPIENTS : 0) |
+    (LOGGING(received_sender) ? LOG_SENDER : 0),
+    "%s", g->s);
 
   /* Log any control actions taken by an ACL or local_scan(). */
 
 
   /* 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);
+  if (f.deliver_freeze) log_write(0, LOG_MAIN, "frozen by %s", frozen_by);
+  if (f.queue_only_policy) log_write(L_delay_delivery, LOG_MAIN,
+    "no immediate delivery: queued%s%s by %s",
+    *queue_name ? " in " : "", *queue_name ? CS queue_name : "",       
+    queued_by);
   }
   }
-receive_call_bombout = FALSE;
+f.receive_call_bombout = FALSE;
 
 
-store_reset(s);   /* The store for the main log message can be reused */
+store_reset(g);   /* The store for the main log message can be reused */
 
 /* If the message is frozen, and freeze_tell is set, do the telling. */
 
 
 /* If the message is frozen, and freeze_tell is set, do the telling. */
 
-if (deliver_freeze && freeze_tell != NULL && freeze_tell[0] != 0)
-  {
+if (f.deliver_freeze && freeze_tell && freeze_tell[0])
   moan_tell_someone(freeze_tell, NULL, US"Message frozen on arrival",
     "Message %s was frozen on arrival by %s.\nThe sender is <%s>.\n",
     message_id, frozen_by, sender_address);
   moan_tell_someone(freeze_tell, NULL, US"Message frozen on arrival",
     "Message %s was frozen on arrival by %s.\nThe sender is <%s>.\n",
     message_id, frozen_by, sender_address);
-  }
 
 
 /* Either a message has been successfully received and written to the two spool
 files, or an error in writing the spool has occurred for an SMTP message, or
 
 
 /* Either a message has been successfully received and written to the two spool
 files, or an error in writing the spool has occurred for an SMTP message, or
-an SMTP message has been rejected for policy reasons. (For a non-SMTP message
-we will have already given up because there's no point in carrying on!) In
-either event, we must now close (and thereby unlock) the data file. In the
-successful case, this leaves the message on the spool, ready for delivery. In
-the error case, the spool file will be deleted. Then tidy up store, interact
-with an SMTP call if necessary, and return.
+an SMTP message has been rejected for policy reasons, or a message was passed on
+by cutthrough delivery. (For a non-SMTP message we will have already given up
+because there's no point in carrying on!) For non-cutthrough we must now close
+(and thereby unlock) the data file. In the successful case, this leaves the
+message on the spool, ready for delivery. In the error case, the spool file will
+be deleted. Then tidy up store, interact with an SMTP call if necessary, and
+return.
+
+For cutthrough we hold the data file locked until we have deleted it, otherwise
+a queue-runner could grab it in the window.
 
 A fflush() was done earlier in the expectation that any write errors on the
 data file will be flushed(!) out thereby. Nevertheless, it is theoretically
 possible for fclose() to fail - but what to do? What has happened to the lock
 
 A fflush() was done earlier in the expectation that any write errors on the
 data file will be flushed(!) out thereby. Nevertheless, it is theoretically
 possible for fclose() to fail - but what to do? What has happened to the lock
-if this happens? */
+if this happens?  We can at least log it; if it is observed on some platform
+then we can think about properly declaring the message not-received. */
 
 
 TIDYUP:
 
 
 TIDYUP:
-process_info[process_info_len] = 0;                /* Remove message id */
-if (data_file != NULL) (void)fclose(data_file);    /* Frees the lock */
+/* In SMTP sessions we may receive several messages in one connection. After
+each one, we wait for the clock to tick at the level of message-id granularity.
+This is so that the combination of time+pid is unique, even on systems where the
+pid can be re-used within our time interval. We can't shorten the interval
+without re-designing the message-id. See comments above where the message id is
+created. This is Something For The Future.
+Do this wait any time we have created a message-id, even if we rejected the
+message.  This gives unique IDs for logging done by ACLs. */
+
+if (id_resolution != 0)
+  {
+  message_id_tv.tv_usec = (message_id_tv.tv_usec/id_resolution) * id_resolution;
+  exim_wait_tick(&message_id_tv, id_resolution);
+  id_resolution = 0;
+  }
+
+
+process_info[process_info_len] = 0;                    /* Remove message id */
+if (spool_data_file && cutthrough_done == NOT_TRIED)
+  {
+  if (fclose(spool_data_file))                         /* Frees the lock */
+    log_write(0, LOG_MAIN|LOG_PANIC,
+      "spoolfile error on close: %s", strerror(errno));
+  spool_data_file = NULL;
+  }
 
 /* Now reset signal handlers to their defaults */
 
 
 /* Now reset signal handlers to their defaults */
 
@@ -4064,26 +4312,33 @@ if (smtp_input)
 
   if (!smtp_batched_input)
     {
 
   if (!smtp_batched_input)
     {
-    if (smtp_reply == NULL)
+    if (!smtp_reply)
       {
       if (fake_response != OK)
       {
       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. */
 
 
       /* 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;
         {
         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 */
 
         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", FALSE,
+           chunking_datasize, message_size+message_linecount, message_id);
+       chunking_state = CHUNKING_OFFERED;
+       }
       else
       else
-        smtp_printf("250 OK id=%s\r\n", message_id);
+        smtp_printf("250 OK id=%s\r\n", FALSE, message_id);
+
       if (host_checking)
         fprintf(stdout,
           "\n**** SMTP testing: that is not a real message id!\n\n");
       if (host_checking)
         fprintf(stdout,
           "\n**** SMTP testing: that is not a real message id!\n\n");
@@ -4092,39 +4347,52 @@ if (smtp_input)
     /* smtp_reply is set non-empty */
 
     else if (smtp_reply[0] != 0)
     /* 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,
+      if (fake_response != OK && smtp_reply[0] == '2')
+        smtp_respond(fake_response == DEFER ? US"450" : US"550", 3, TRUE,
           fake_response_text);
       else
           fake_response_text);
       else
-        smtp_printf("%.1024s\r\n", smtp_reply);
-      }
+        smtp_printf("%.1024s\r\n", FALSE, smtp_reply);
 
     switch (cutthrough_done)
       {
 
     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_name);
+       Uunlink(spool_fname(US"input", message_subdir, message_id, US"-H"));
+       Uunlink(spool_fname(US"msglog", message_subdir, message_id, US""));
+       break;
+
+      case TMP_REJ:
+       if (cutthrough.defer_pass)
+         {
+         Uunlink(spool_name);
+         Uunlink(spool_fname(US"input", message_subdir, message_id, US"-H"));
+         Uunlink(spool_fname(US"msglog", message_subdir, message_id, US""));
+         }
+      default:
+       break;
+      }
+    if (cutthrough_done != NOT_TRIED)
+      {
+      if (spool_data_file)
+       {
+       (void) fclose(spool_data_file);  /* Frees the lock; do not care if error */
+       spool_data_file = NULL;
+       }
+      message_id[0] = 0;         /* Prevent a delivery from starting */
+      cutthrough.delivery = cutthrough.callout_hold_only = FALSE;
+      cutthrough.defer_pass = FALSE;
       }
       }
-    cutthrough_delivery = 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. */
 
     }
 
   /* 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,10 +4401,12 @@ 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. */
 
 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) :
+  const uschar *detail =
+#ifdef HAVE_LOCAL_SCAN
+    local_scan_data ? string_printing(local_scan_data) :
+#endif
     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");
     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");
dissimilarity index 88%
index de8ec68..6637296 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 - 2018
+ */
+
+/* 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, 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..2404b05 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions concerned with retrying unsuccessful deliveries. */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions concerned with retrying unsuccessful deliveries. */
@@ -29,33 +29,32 @@ Returns:        TRUE if the ultimate timeout has been reached
 */
 
 BOOL
 */
 
 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;
   dbdata_retry *retry_record, time_t now)
 {
 BOOL address_timeout;
+retry_config * retry;
 
 DEBUG(D_retry)
   {
   debug_printf("retry time not reached: checking ultimate address timeout\n");
 
 DEBUG(D_retry)
   {
   debug_printf("retry time not reached: checking ultimate address timeout\n");
-  debug_printf("  now=%d first_failed=%d next_try=%d expired=%d\n",
-    (int)now, (int)retry_record->first_failed,
-    (int)retry_record->next_try, retry_record->expired);
+  debug_printf("  now=" TIME_T_FMT " first_failed=" TIME_T_FMT
+               " next_try=" TIME_T_FMT " expired=%c\n",
+               now, retry_record->first_failed,
+               retry_record->next_try, retry_record->expired ? 'T' : 'F');
   }
 
   }
 
-retry_config *retry =
-  retry_find_config(retry_key+2, domain,
+retry = retry_find_config(retry_key+2, domain,
     retry_record->basic_errno, retry_record->more_errno);
 
     retry_record->basic_errno, retry_record->more_errno);
 
-if (retry != NULL && retry->rules != NULL)
+if (retry && retry->rules)
   {
   retry_rule *last_rule;
   {
   retry_rule *last_rule;
-  for (last_rule = retry->rules;
-       last_rule->next != NULL;
-       last_rule = last_rule->next);
+  for (last_rule = retry->rules; last_rule->next; last_rule = last_rule->next) ;
   DEBUG(D_retry)
   DEBUG(D_retry)
-    debug_printf("  received_time=%d diff=%d timeout=%d\n",
-      received_time, (int)(now - received_time), last_rule->timeout);
-  address_timeout = (now - received_time > last_rule->timeout);
+    debug_printf("  received_time=" TIME_T_FMT " diff=%d timeout=%d\n",
+      received_time.tv_sec, (int)(now - received_time.tv_sec), last_rule->timeout);
+  address_timeout = (now - received_time.tv_sec > last_rule->timeout);
   }
 else
   {
   }
 else
   {
@@ -122,7 +121,7 @@ Returns:    TRUE if the host has expired but is usable because
 */
 
 BOOL
 */
 
 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;
   BOOL include_ip_address, uschar **retry_host_key, uschar **retry_message_key)
 {
 BOOL yield = FALSE;
@@ -159,8 +158,7 @@ deliveries (so as to do it all in one go). The tree records addresses that have
 become unusable during this delivery process (i.e. those that will get put into
 the retry database when it is updated). */
 
 become unusable during this delivery process (i.e. those that will get put into
 the retry database when it is updated). */
 
-node = tree_search(tree_unusable, host_key);
-if (node != NULL)
+if ((node = tree_search(tree_unusable, host_key)))
   {
   DEBUG(D_transport|D_retry) debug_printf("found in tree of unusables\n");
   host->status = (node->data.val > 255)?
   {
   DEBUG(D_transport|D_retry) debug_printf("found in tree of unusables\n");
   host->status = (node->data.val > 255)?
@@ -172,7 +170,7 @@ if (node != NULL)
 /* Open the retry database, giving up if there isn't one. Otherwise, search for
 the retry records, and then close the database again. */
 
 /* Open the retry database, giving up if there isn't one. Otherwise, search for
 the retry records, and then close the database again. */
 
-if ((dbm_file = dbfn_open(US"retry", O_RDONLY, &dbblock, FALSE)) == 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");
   {
   DEBUG(D_deliver|D_retry|D_hints_lookup)
     debug_printf("no retry data available\n");
@@ -184,7 +182,7 @@ dbfn_close(dbm_file);
 
 /* Ignore the data if it is too old - too long since it was written */
 
 
 /* Ignore the data if it is too old - too long since it was written */
 
-if (host_retry_record == NULL)
+if (!host_retry_record)
   {
   DEBUG(D_transport|D_retry) debug_printf("no host retry record\n");
   }
   {
   DEBUG(D_transport|D_retry) debug_printf("no host retry record\n");
   }
@@ -194,7 +192,7 @@ else if (now - host_retry_record->time_stamp > retry_data_expire)
   DEBUG(D_transport|D_retry) debug_printf("host retry record too old\n");
   }
 
   DEBUG(D_transport|D_retry) debug_printf("host retry record too old\n");
   }
 
-if (message_retry_record == NULL)
+if (!message_retry_record)
   {
   DEBUG(D_transport|D_retry) debug_printf("no message retry record\n");
   }
   {
   DEBUG(D_transport|D_retry) debug_printf("no message retry record\n");
   }
@@ -211,14 +209,14 @@ address. Allow the delivery if it has. Otherwise set the appropriate unusable
 flag and return FALSE. Otherwise arrange to return TRUE if this is an expired
 host. */
 
 flag and return FALSE. Otherwise arrange to return TRUE if this is an expired
 host. */
 
-if (host_retry_record != NULL)
+if (host_retry_record)
   {
   *retry_host_key = host_key;
 
   /* We have not reached the next try time. Check for the ultimate address
   timeout if the host has not expired. */
 
   {
   *retry_host_key = host_key;
 
   /* We have not reached the next try time. Check for the ultimate address
   timeout if the host has not expired. */
 
-  if (now < host_retry_record->next_try && !deliver_force)
+  if (now < host_retry_record->next_try && !f.deliver_force)
     {
     if (!host_retry_record->expired &&
         retry_ultimate_address_timeout(host_key, domain,
     {
     if (!host_retry_record->expired &&
         retry_ultimate_address_timeout(host_key, domain,
@@ -243,10 +241,10 @@ if (host_retry_record != NULL)
 for reaching its retry time (or forcing). If not, mark the host unusable,
 unless the ultimate address timeout has been reached. */
 
 for reaching its retry time (or forcing). If not, mark the host unusable,
 unless the ultimate address timeout has been reached. */
 
-if (message_retry_record != NULL)
+if (message_retry_record)
   {
   *retry_message_key = message_key;
   {
   *retry_message_key = message_key;
-  if (now < message_retry_record->next_try && !deliver_force)
+  if (now < message_retry_record->next_try && !f.deliver_force)
     {
     if (!retry_ultimate_address_timeout(host_key, domain,
         message_retry_record, now))
     {
     if (!retry_ultimate_address_timeout(host_key, domain,
         message_retry_record, now))
@@ -294,12 +292,16 @@ void
 retry_add_item(address_item *addr, uschar *key, int flags)
 {
 retry_item *rti = store_get(sizeof(retry_item));
 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->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)
 rti->flags = flags;
 
 DEBUG(D_transport|D_retry)
@@ -340,12 +342,10 @@ Returns:       pointer to retry rule, or NULL
 */
 
 retry_config *
 */
 
 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 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:
 retry_config *yield;
 
 /* If there's a colon in the key, there are two possibilities:
@@ -354,8 +354,7 @@ retry_config *yield;
 
       hostname:ip+port
 
 
       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
 
 
 (2) This is a key for a pipe, file, or autoreply delivery, in the format
 
@@ -366,28 +365,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. */
 
     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 */
 
 
 /* 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. */
 
 
 /* Scan the configured retry items. */
 
-for (yield = retries; yield != NULL; yield = yield->next)
+for (yield = retries; yield; 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
 
   /* 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
@@ -478,23 +471,22 @@ for (yield = retries; yield != NULL; yield = yield->next)
   /* If the "senders" condition is set, check it. Note that sender_address may
   be null during -brt checking, in which case we do not use this rule. */
 
   /* If the "senders" condition is set, check it. Note that sender_address may
   be null during -brt checking, in which case we do not use this rule. */
 
-  if (slist != NULL && (sender_address == NULL ||
-      match_address_list(sender_address, TRUE, TRUE, &slist, NULL, -1, 0,
-        NULL) != OK))
+  if (  slist
+     && (  !sender_address
+               || match_address_list_basic(sender_address, &slist, 0) != OK
+     )  )
     continue;
 
   /* Check for a match between the address list item at the start of this retry
   rule and either the main or alternate keys. */
 
     continue;
 
   /* 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,
-        NULL) == OK ||
-     (use_alternate != NULL &&
-      match_address_list(use_alternate, TRUE, TRUE, &plist, NULL, -1,
-        UCHAR_MAX+1, NULL) == OK))
+  if (  match_address_list_basic(key, &plist, UCHAR_MAX+1) == OK
+     || (  alternate
+       && match_address_list_basic(alternate, &plist, UCHAR_MAX+1) == OK
+     )  )
     break;
   }
 
     break;
   }
 
-*colon = replace;
 return yield;
 }
 
 return yield;
 }
 
@@ -543,12 +535,12 @@ for (i = 0; i < 3; i++)
   {
   address_item *endaddr, *addr;
   address_item *last_first = NULL;
   {
   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;
 
   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
 
   /* 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 +552,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. */
 
   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;
 
     {
     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;
 
       {
       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. */
 
 
       /* 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;
         {
         uschar *message;
         int message_length, message_space, failing_interval, next_try;
@@ -589,10 +581,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. */
 
         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);
 
           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");
           {
           DEBUG(D_deliver|D_retry|D_hints_lookup)
             debug_printf("retry database not available for updating\n");
@@ -607,13 +599,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. */
 
         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. */
 
           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)
           {
           (void)dbfn_delete(dbm_file, rti->key);
           DEBUG(D_retry)
@@ -633,36 +625,36 @@ 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. */
 
         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,
           {
           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 (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);
           else
             debug_printf("retry for %s = %s %d %d\n", rti->key, retry->pattern,
               retry->basic_errno, retry->more_errno);
             debug_printf("retry for %s (%s) = %s %d %d\n", rti->key,
               addr->domain, retry->pattern, retry->basic_errno,
               retry->more_errno);
           else
             debug_printf("retry for %s = %s %d %d\n", rti->key, retry->pattern,
               retry->basic_errno, retry->more_errno);
-          }
 
         /* Set up the message for the database retry record. Because DBM
         records have a maximum data length, we enforce a limit. There isn't
         much point in keeping a huge message here, anyway. */
 
 
         /* Set up the message for the database retry record. Because DBM
         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;
 
         message_length = Ustrlen(message);
         if (message_length > 150) message_length = 150;
 
@@ -670,11 +662,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);
         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;
 
           retry_record = NULL;
 
-        if (retry_record == NULL)
+        if (!retry_record)
           {
           retry_record = store_get(sizeof(dbdata_retry) + message_length);
           message_space = message_length;
           {
           retry_record = store_get(sizeof(dbdata_retry) + message_length);
           message_space = message_length;
@@ -698,7 +690,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. */
 
         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
           failing_interval = message_age;
 
         /* Search for the current retry rule. The cutoff time of the
@@ -709,7 +701,7 @@ for (i = 0; i < 3; i++)
         always times out, but we can't compute a retry time. */
 
         final_rule = NULL;
         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;
           {
           if (failing_interval <= rule->timeout) break;
           final_rule = rule;
@@ -721,10 +713,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). */
 
         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;
           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
 
         /* Otherwise, set the retry timeout expired, and set the final rule
         as the one from which to compute the next retry time. Subsequent
@@ -763,14 +753,15 @@ 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. */
 
         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.tv_sec <= retry_record->first_failed
+          && addr == endaddr
+          && !retry_record->expired
+          && rule)
           {
           retry_rule *last_rule;
           {
           retry_rule *last_rule;
-          for (last_rule = rule;
-               last_rule->next != NULL;
-               last_rule = last_rule->next);
-          if (now - received_time > last_rule->timeout)
+          for (last_rule = rule; last_rule->next; last_rule = last_rule->next)
+           ;
+          if (now - received_time.tv_sec > last_rule->timeout)
             {
             DEBUG(D_retry) debug_printf("on queue longer than maximum retry\n");
             timedout_count++;
             {
             DEBUG(D_retry) debug_printf("on queue longer than maximum retry\n");
             timedout_count++;
@@ -785,9 +776,12 @@ for (i = 0; i < 3; i++)
         case set the next retry time to now, so that one delivery attempt
         happens for subsequent messages. */
 
         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 =
           else  /* rule = 'G' or 'H' */
             {
             int last_predicted_gap =
@@ -797,9 +791,7 @@ for (i = 0; i < 3; i++)
               last_predicted_gap : last_actual_gap;
             int next_gap = (lastgap * rule->p2)/1000;
             if (rule->rule == 'G')
               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);
               next_try = now + ((lastgap < rule->p1)? rule->p1 : next_gap);
-              }
             else  /* The 'H' rule */
               {
               next_try = now + rule->p1;
             else  /* The 'H' rule */
               {
               next_try = now + rule->p1;
@@ -860,18 +852,14 @@ for (i = 0; i < 3; i++)
       time was not reached (or because of hosts_max_try). */
 
       if (update_count > 0 && update_count == timedout_count)
       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");
           timed_out = TRUE;
           }
         else
         if (!testflag(endaddr, af_retry_skipped))
           {
           DEBUG(D_retry) debug_printf("timed out: all retries expired\n");
           timed_out = TRUE;
           }
         else
-          {
           DEBUG(D_retry)
             debug_printf("timed out but some hosts were skipped\n");
           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
       }     /* Loop for an address and its parents */
 
     /* If this is a deferred address, and retry processing was requested by
@@ -887,7 +875,7 @@ for (i = 0; i < 3; i++)
 
     if (i == 2)   /* Handling defers */
       {
 
     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;
         {
         if (last_first == endaddr) paddr = saved_paddr;
         addr = *paddr;
@@ -899,16 +887,17 @@ for (i = 0; i < 3; i++)
         for (;; addr = addr->next)
           {
           setflag(addr, af_retry_timedout);
         for (;; addr = addr->next)
           {
           setflag(addr, af_retry_timedout);
-          addr->message = (addr->message == NULL)? US"retry timeout exceeded" :
-            string_sprintf("%s: retry timeout exceeded", addr->message);
-          addr->user_message = (addr->user_message == NULL)?
-            US"retry timeout exceeded" :
-            string_sprintf("%s: retry timeout exceeded", addr->user_message);
+          addr->message = addr->message
+            ? string_sprintf("%s: retry timeout exceeded", addr->message)
+           : US"retry timeout exceeded";
+          addr->user_message = addr->user_message
+           ? string_sprintf("%s: retry timeout exceeded", addr->user_message)
+           : US"retry timeout exceeded";
           log_write(0, LOG_MAIN, "** %s%s%s%s: retry timeout exceeded",
             addr->address,
           log_write(0, LOG_MAIN, "** %s%s%s%s: retry timeout exceeded",
             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 ? addr->parent->address : US"",
+            addr->parent ? US">" : US"");
 
           if (addr == endaddr) break;
           }
 
           if (addr == endaddr) break;
           }
@@ -935,7 +924,7 @@ for (i = 0; i < 3; i++)
 
 /* Close and unlock the database */
 
 
 /* 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");
 }
 
 DEBUG(D_retry) debug_printf("end of retry processing\n");
 }
index f9d29c0..2196bfa 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions concerned with rewriting headers */
 /* 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;
   {
   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. */
   uschar *error, *new, *newparsed;
 
   /* Ensure that the flag matches the flags in the rule. */
@@ -141,7 +142,7 @@ for (rule = rewrite_rules;
     uschar *key = expand_string(rule->key);
     if (key == NULL)
       {
     uschar *key = expand_string(rule->key);
     if (key == NULL)
       {
-      if (!expand_string_forcedfail)
+      if (!f.expand_string_forcedfail)
         log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand \"%s\" while "
           "checking for SMTP rewriting: %s", rule->key, expand_string_message);
       continue;
         log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand \"%s\" while "
           "checking for SMTP rewriting: %s", rule->key, expand_string_message);
       continue;
@@ -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). */
 
     /* 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;
 
         UCHAR_MAX + 1, NULL) != OK)
       continue;
 
@@ -202,8 +203,11 @@ for (rule = rewrite_rules;
 
   if (new == NULL)
     {
 
   if (new == NULL)
     {
-    if (expand_string_forcedfail)
+    if (f.expand_string_forcedfail)
       { if ((rule->flags & rewrite_quit) != 0) break; else continue; }
       { 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;
     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 */
 
 
   /* 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"?";
     {
     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 *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--;
         uschar buff1[256], buff2[256];
 
         while (p1 > new && p1[-1] == ' ') p1--;
@@ -306,7 +309,7 @@ for (rule = rewrite_rules;
 
         start = Ustrlen(pf1) + start + new - p1;
         end = start + Ustrlen(newparsed);
 
         start = Ustrlen(pf1) + start + new - p1;
         end = start + Ustrlen(newparsed);
-        new = string_sprintf("%s%.*s%s", pf1, p2 - p1, p1, pf2);
+        new = string_sprintf("%s%.*s%s", pf1, (int)(p2 - p1), p1, pf2);
         }
 
       /* Now accept the whole thing */
         }
 
       /* Now accept the whole thing */
@@ -449,8 +452,9 @@ Returns:         NULL if header unchanged; otherwise the rewritten header
 */
 
 static header_line *
 */
 
 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;
 {
 int lastnewline = 0;
 header_line *newh = NULL;
@@ -461,7 +465,7 @@ while (isspace(*s)) s++;
 DEBUG(D_rewrite)
   debug_printf("rewrite_one_header: type=%c:\n  %s", h->type, h->text);
 
 DEBUG(D_rewrite)
   debug_printf("rewrite_one_header: type=%c:\n  %s", h->type, h->text);
 
-parse_allow_group = TRUE;     /* Allow group syntax */
+f.parse_allow_group = TRUE;     /* Allow group syntax */
 
 /* Loop for multiple addresses in the header. We have to go through them all
 in case any need qualifying, even if there's no rewriting. Pathological headers
 
 /* Loop for multiple addresses in the header. We have to go through them all
 in case any need qualifying, even if there's no rewriting. Pathological headers
@@ -540,8 +544,8 @@ while (*s != 0)
 
     /* Can only qualify if permitted; if not, no rewrite. */
 
 
     /* Can only qualify if permitted; if not, no rewrite. */
 
-    if (changed && ((is_recipient && !allow_unqualified_recipient) ||
-                    (!is_recipient && !allow_unqualified_sender)))
+    if (changed && ((is_recipient && !f.allow_unqualified_recipient) ||
+                    (!is_recipient && !f.allow_unqualified_sender)))
       {
       store_reset(loop_reset_point);
       continue;
       {
       store_reset(loop_reset_point);
       continue;
@@ -669,8 +673,8 @@ while (*s != 0)
     }
   }
 
     }
   }
 
-parse_allow_group = FALSE;  /* Reset group flags */
-parse_found_group = FALSE;
+f.parse_allow_group = FALSE;  /* Reset group flags */
+f.parse_found_group = FALSE;
 
 /* If a rewrite happened and "replace" is true, put the new header into the
 chain following the old one, and mark the old one as replaced. */
 
 /* If a rewrite happened and "replace" is true, put the new header into the
 chain following the old one, and mark the old one as replaced. */
@@ -717,7 +721,8 @@ Returns:         NULL if header unchanged; otherwise the rewritten header
 */
 
 header_line *
 */
 
 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)
   rewrite_rule *rewrite_rules, int existflags, BOOL replace)
 {
 switch (h->type)
index 128e6b9..8d77634 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* This file contains a function for decoding message header lines that may
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* This file contains a function for decoding message header lines that may
@@ -50,7 +50,7 @@ ptr = *ptrptr = store_get(Ustrlen(string) + 1);  /* No longer than this */
 
 while (*string != 0)
   {
 
 while (*string != 0)
   {
-  register int ch = *string++;
+  int ch = *string++;
 
   if (ch == '_') *ptr++ = ' ';
   else if (ch == '=')
 
   if (ch == '_') *ptr++ = ' ';
   else if (ch == '=')
@@ -120,7 +120,7 @@ for (;; string = mimeword + 2)
   encoding = toupper((*q1ptr)[1]);
   **endptr = 0;
   if (encoding == 'B')
   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 */
   else if (encoding == 'Q')
     dlen = rfc2047_qpdecode(*q2ptr+1, dptrptr);
   **endptr = '?';   /* restore */
@@ -188,18 +188,18 @@ uschar *
 rfc2047_decode2(uschar *string, BOOL lencheck, uschar *target, int zeroval,
   int *lenptr, int *sizeptr, uschar **error)
 {
 rfc2047_decode2(uschar *string, BOOL lencheck, uschar *target, int zeroval,
   int *lenptr, int *sizeptr, uschar **error)
 {
-int ptr = 0;
 int size = Ustrlen(string);
 size_t dlen;
 int size = Ustrlen(string);
 size_t dlen;
-uschar *dptr, *yield;
+uschar *dptr;
+gstring *yield;
 uschar *mimeword, *q1, *q2, *endword;
 
 *error = NULL;
 mimeword = decode_mimeword(string, lencheck, &q1, &q2, &endword, &dlen, &dptr);
 
 uschar *mimeword, *q1, *q2, *endword;
 
 *error = NULL;
 mimeword = decode_mimeword(string, lencheck, &q1, &q2, &endword, &dlen, &dptr);
 
-if (mimeword == NULL)
+if (!mimeword)
   {
   {
-  if (lenptr != NULL) *lenptr = size;
+  if (lenptr) *lenptr = size;
   return string;
   }
 
   return string;
   }
 
@@ -208,9 +208,12 @@ building the result as we go. The result may be longer than the input if it is
 translated into a multibyte code such as UTF-8. That's why we use the dynamic
 string building code. */
 
 translated into a multibyte code such as UTF-8. That's why we use the dynamic
 string building code. */
 
-yield = store_get(++size);
+yield = store_get(sizeof(gstring) + ++size);
+yield->size = size;
+yield->ptr = 0;
+yield->s = US(yield + 1);
 
 
-while (mimeword != NULL)
+while (mimeword)
   {
 
   #if HAVE_ICONV
   {
 
   #if HAVE_ICONV
@@ -218,7 +221,7 @@ while (mimeword != NULL)
   #endif
 
   if (mimeword != string)
   #endif
 
   if (mimeword != string)
-    yield = string_cat(yield, &size, &ptr, string, mimeword - string);
+    yield = string_catn(yield, 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,
 
   /* 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 +308,7 @@ while (mimeword != NULL)
 
     /* Add the new string onto the result */
 
 
     /* Add the new string onto the result */
 
-    yield = string_cat(yield, &size, &ptr, tptr, tlen);
+    yield = string_catn(yield, tptr, tlen);
     }
 
   #if HAVE_ICONV
     }
 
   #if HAVE_ICONV
@@ -317,7 +320,7 @@ while (mimeword != NULL)
 
   string = endword + 2;
   mimeword = decode_mimeword(string, lencheck, &q1, &q2, &endword, &dlen, &dptr);
 
   string = endword + 2;
   mimeword = decode_mimeword(string, lencheck, &q1, &q2, &endword, &dlen, &dptr);
-  if (mimeword != NULL)
+  if (mimeword)
     {
     uschar *s = string;
     while (isspace(*s)) s++;
     {
     uschar *s = string;
     while (isspace(*s)) s++;
@@ -328,11 +331,11 @@ while (mimeword != NULL)
 /* Copy the remaining characters of the string, zero-terminate it, and return
 the length as well if requested. */
 
 /* 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[ptr] = 0;
-if (lenptr != NULL) *lenptr = ptr;
-if (sizeptr != NULL) *sizeptr = size;
-return yield;
+yield = string_cat(yield, string);
+
+if (lenptr) *lenptr = yield->ptr;
+if (sizeptr) *sizeptr = yield->size;
+return string_from_gstring(yield);
 }
 
 
 }
 
 
index 6ba1d9f..d419d1c 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions concerned with routing, and the list of generic router options. */
 /* 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) },
                  (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) },
   { "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) },
   { "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,
   { "errors_to",          opt_stringptr|opt_public,
                  (void *)(offsetof(router_instance, errors_to)) },
   { "expn",               opt_bool|opt_public,
@@ -138,9 +140,30 @@ optionlist optionlist_routers[] = {
                  (void *)offsetof(router_instance, verify_sender) }
 };
 
                  (void *)offsetof(router_instance, verify_sender) }
 };
 
-int optionlist_routers_size = sizeof(optionlist_routers)/sizeof(optionlist);
+int optionlist_routers_size = nelem(optionlist_routers);
+
 
 
+#ifdef MACRO_PREDEF
 
 
+# include "macro_predef.h"
+
+void
+options_routers(void)
+{
+struct router_info * ri;
+uschar buf[64];
+
+options_from_list(optionlist_routers, nelem(optionlist_routers), US"ROUTERS", NULL);
+
+for (ri = routers_available; ri->driver_name[0]; ri++)
+  {
+  spf(buf, sizeof(buf), US"_DRIVER_ROUTER_%T", ri->driver_name);
+  builtin_macro_create(buf);
+  options_from_list(ri->options, (unsigned)*ri->options_count, US"ROUTER", ri->driver_name);
+  }
+}
+
+#else  /*!MACRO_PREDEF*/
 
 /*************************************************
 *          Set router pointer from name          *
 
 /*************************************************
 *          Set router pointer from name          *
@@ -164,7 +187,7 @@ set_router(router_instance *r, uschar *name, router_instance **ptr, BOOL after)
 BOOL afterthis = FALSE;
 router_instance *rr;
 
 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)
     {
   {
   if (Ustrcmp(name, rr->name) == 0)
     {
@@ -174,7 +197,7 @@ for (rr = routers; rr != NULL; rr = rr->next)
   if (rr == r) afterthis = TRUE;
   }
 
   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);
 
   log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
     "new_router \"%s\" not found for \"%s\" router", name, r->name);
 
@@ -210,7 +233,7 @@ readconf_driver_init(US"router",
   optionlist_routers,                 /* generic options */
   optionlist_routers_size);
 
   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;
 
   {
   uschar *s = r->self;
 
@@ -221,14 +244,12 @@ for (r = routers; r != NULL; r = r->next)
 
   /* Check for transport or no transport on certain routers */
 
 
   /* Check for transport or no transport on certain routers */
 
-  if ((r->info->ri_flags & ri_yestransport) != 0 &&
-      r->transport_name == NULL &&
-      !r->verify_only)
+  if (  (r->info->ri_flags & ri_yestransport)
+     && !r->transport_name && !r->verify_only)
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG, "%s router:\n  "
       "a transport is required for this router", r->name);
 
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG, "%s router:\n  "
       "a transport is required for this router", r->name);
 
-  if ((r->info->ri_flags & ri_notransport) != 0 &&
-       r->transport_name != NULL)
+  if ((r->info->ri_flags & ri_notransport) && r->transport_name)
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG, "%s router:\n  "
       "a transport must not be defined for this router", r->name);
 
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG, "%s router:\n  "
       "a transport must not be defined for this router", r->name);
 
@@ -269,20 +290,16 @@ for (r = routers; r != NULL; r = r->next)
 
   /* Check redirect_router and pass_router are valid */
 
 
   /* Check redirect_router and pass_router are valid */
 
-  if (r->redirect_router_name != NULL)
+  if (r->redirect_router_name)
     set_router(r, r->redirect_router_name, &(r->redirect_router), FALSE);
 
     set_router(r, r->redirect_router_name, &(r->redirect_router), FALSE);
 
-  if (r->pass_router_name != NULL)
+  if (r->pass_router_name)
     set_router(r, r->pass_router_name, &(r->pass_router), TRUE);
 
     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
+#ifdef notdef
+  DEBUG(D_route) debug_printf("DSN: %s %s\n", r->name,
+       r->dsn_lasthop ? "lasthop set" : "propagating DSN");
+#endif
   }
 }
 
   }
 }
 
@@ -301,8 +318,8 @@ void
 route_tidyup(void)
 {
 router_instance *r;
 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 +340,19 @@ Returns:        length of matching prefix or zero
 */
 
 int
 */
 
 int
-route_check_prefix(uschar *local_part, uschar *prefixes)
+route_check_prefix(const uschar *local_part, const uschar *prefixes)
 {
 int sep = 0;
 uschar *prefix;
 {
 int sep = 0;
 uschar *prefix;
-uschar *listptr = prefixes;
+const uschar *listptr = prefixes;
 uschar prebuf[64];
 
 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] == '*')
     {
   {
   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--)
     prefix++;
     for (p = local_part + Ustrlen(local_part) - (--plen);
          p >= local_part; p--)
@@ -367,21 +383,20 @@ Returns:        length of matching suffix or zero
 */
 
 int
 */
 
 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;
 {
 int sep = 0;
 int alen = Ustrlen(local_part);
 uschar *suffix;
-uschar *listptr = suffixes;
+const uschar *listptr = suffixes;
 uschar sufbuf[64];
 
 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] == '*')
     {
   {
   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);
     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 +439,37 @@ Returns:         OK     item is in list
 */
 
 static int
 */
 
 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. */
 
 
 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:
   {
   case OK:
-  return OK;
+    return OK;
 
   case FAIL:
 
   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:
 
   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 +516,9 @@ uschar *sp = rp + 1;
 DEBUG(D_route) debug_printf("route_check_access(%s,%d,%d,%o)\n", path,
   (int)uid, (int)gid, bits);
 
 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);
   {
   *slash = 0;
   DEBUG(D_route) debug_printf("stat %s\n", rp);
@@ -574,23 +580,23 @@ Returns:   OK if s == NULL or all tests are as required
            SKIP otherwise
 */
 
            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;
 {
 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];
 
 uschar buffer[1024];
 
-if (s == NULL) return OK;
+if (!s) return OK;
 
 DEBUG(D_route) debug_printf("checking require_files\n");
 
 listptr = s;
 
 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;
   {
   int rc;
   int eacces_code = 0;
@@ -598,9 +604,9 @@ while ((check = string_nextinlist(&listptr, &sep, buffer, sizeof(buffer)))
   struct stat statbuf;
   uschar *ss = expand_string(check);
 
   struct stat statbuf;
   uschar *ss = expand_string(check);
 
-  if (ss == NULL)
+  if (!ss)
     {
     {
-    if (expand_string_forcedfail) continue;
+    if (f.expand_string_forcedfail) continue;
     *perror = string_sprintf("failed to expand \"%s\" for require_files: %s",
       check, expand_string_message);
     goto RETURN_DEFER;
     *perror = string_sprintf("failed to expand \"%s\" for require_files: %s",
       check, expand_string_message);
     goto RETURN_DEFER;
@@ -716,7 +722,7 @@ while ((check = string_nextinlist(&listptr, &sep, buffer, sizeof(buffer)))
     pid = fork();
 
     /* If fork() fails, reinstate the original error and behave as if
     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)
     when Exim is not running as root at this point. */
 
     if (pid < 0)
@@ -767,7 +773,7 @@ while ((check = string_nextinlist(&listptr, &sep, buffer, sizeof(buffer)))
     rc = -1;
     }
 
     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. */
   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. */
@@ -848,7 +854,7 @@ deliver_localpart_data = NULL;
 sender_data = NULL;
 local_user_gid = (gid_t)(-1);
 local_user_uid = (uid_t)(-1);
 sender_data = NULL;
 local_user_gid = (gid_t)(-1);
 local_user_uid = (uid_t)(-1);
-search_find_defer = FALSE;
+f.search_find_defer = FALSE;
 
 /* Skip this router if not verifying and it has verify_only set */
 
 
 /* Skip this router if not verifying and it has verify_only set */
 
@@ -860,7 +866,7 @@ if ((verify == v_none || verify == v_expn) && r->verify_only)
 
 /* Skip this router if testing an address (-bt) and address_test is not set */
 
 
 /* Skip this router if testing an address (-bt) and address_test is not set */
 
-if (address_test_mode && !r->address_test)
+if (f.address_test_mode && !r->address_test)
   {
   DEBUG(D_route) debug_printf("%s router skipped: address_test is unset\n",
     r->name);
   {
   DEBUG(D_route) debug_printf("%s router skipped: address_test is unset\n",
     r->name);
@@ -889,8 +895,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,
 /* 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
   return rc;
 
 /* Skip this router if there's a local part mismatch. We want to pass over the
@@ -900,7 +906,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. */
 
 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;
   {
   localpart_cache = addr->localpart_cache;
   check_local_part = addr->cc_local_part;
@@ -909,16 +915,16 @@ else
   {
   localpart_cache = NULL;
   check_local_part = string_copy(addr->cc_local_part);
   {
   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);
     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[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
   return rc;
 
 /* If the check_local_user option is set, check that the local_part is the
@@ -947,12 +953,12 @@ check_local_user before any subsequent expansions are done. Otherwise, $home
 could mean different things for different options, which would be extremely
 confusing. */
 
 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);
   {
   uschar *router_home = expand_string(r->router_home_directory);
-  if (router_home == NULL)
+  if (!router_home)
     {
     {
-    if (!expand_string_forcedfail)
+    if (!f.expand_string_forcedfail)
       {
       *perror = string_sprintf("failed to expand \"%s\" for "
         "router_home_directory: %s", r->router_home_directory,
       {
       *perror = string_sprintf("failed to expand \"%s\" for "
         "router_home_directory: %s", r->router_home_directory,
@@ -968,7 +974,7 @@ if (r->router_home_directory != NULL)
   }
 
 /* Skip if the sender condition is not met. We leave this one till after the
   }
 
 /* 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,
 individual recipients specify lists of acceptable/unacceptable senders. */
 
 if ((rc = route_check_dls(r->name, US"senders", r->senders, NULL,
@@ -992,12 +998,12 @@ if ((rc = check_files(r->require_files, perror)) != OK)
 
 /* Now the general condition test. */
 
 
 /* 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 (!expand_check_condition(r->condition, r->name, US"router"))
     {
-    if (search_find_defer)
+    if (f.search_find_defer)
       {
       *perror = US"condition check lookup defer";
       DEBUG(D_route) debug_printf("%s\n", *perror);
       {
       *perror = US"condition check lookup defer";
       DEBUG(D_route) debug_printf("%s\n", *perror);
@@ -1011,42 +1017,44 @@ if (r->condition != NULL)
 
 #ifdef EXPERIMENTAL_BRIGHTMAIL
 /* check if a specific Brightmail AntiSpam rule fired on the message */
 
 #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");
   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;
     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 */
 
 /* 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 */
 
 /* 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 */
 
 /* 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. */
 #endif
 
 /* All the checks passed. */
@@ -1092,7 +1100,7 @@ static uschar lastgecos[128];
 static uschar lastshell[128];
 
 BOOL
 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);
 
 {
 BOOL cache_set = (Ustrcmp(lastname, s) == 0);
 
@@ -1103,11 +1111,11 @@ if (!cache_set)
   {
   int i = 0;
 
   {
   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);
        s[Ustrspn(s+1, "0123456789")+1] == 0)
     {
     *return_uid = (uid_t)Uatoi(s);
-    if (pw != NULL) *pw = NULL;
+    if (pw) *pw = NULL;
     return TRUE;
     }
 
     return TRUE;
     }
 
@@ -1127,12 +1135,12 @@ if (!cache_set)
   else for (;;)
     {
     errno = 0;
   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 (++i > finduser_retries) break;
     sleep(1);
     }
 
-  if (lastpw != NULL)
+  if (lastpw)
     {
     pwcopy.pw_uid = lastpw->pw_uid;
     pwcopy.pw_gid = lastpw->pw_gid;
     {
     pwcopy.pw_uid = lastpw->pw_uid;
     pwcopy.pw_gid = lastpw->pw_gid;
@@ -1146,26 +1154,21 @@ if (!cache_set)
     lastpw = &pwcopy;
     }
 
     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;
   }
   {
   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);
     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;
 }
 
 return TRUE;
 }
@@ -1182,7 +1185,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:
 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
   return_gid  return the gid via this address
 
 Returns:      TRUE if the group was found; FALSE otherwise
@@ -1203,7 +1206,7 @@ if ((isdigit(*s) || *s == '-') && s[Ustrspn(s+1, "0123456789")+1] == 0)
 
 for (;;)
   {
 
 for (;;)
   {
-  if ((gr = getgrnam(CS s)) != NULL)
+  if ((gr = getgrnam(CS s)))
     {
     *return_gid = gr->gr_gid;
     return TRUE;
     {
     *return_gid = gr->gr_gid;
     return TRUE;
@@ -1242,7 +1245,7 @@ route_find_expanded_user(uschar *string, uschar *driver_name,
 {
 uschar *user = expand_string(string);
 
 {
 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);
   {
   *errmsg = string_sprintf("Failed to expand user string \"%s\" for the "
     "%s %s: %s", string, driver_name, driver_type, expand_string_message);
@@ -1284,7 +1287,7 @@ route_find_expanded_group(uschar *string, uschar *driver_name, uschar *driver_ty
 BOOL yield = TRUE;
 uschar *group = expand_string(string);
 
 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);
   {
   *errmsg = string_sprintf("Failed to expand group string \"%s\" for the "
     "%s %s: %s", string, driver_name, driver_type, expand_string_message);
@@ -1305,67 +1308,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            *
 *************************************************/
 /*************************************************
 *            Handle an unseen routing            *
 *************************************************/
@@ -1407,8 +1349,8 @@ from the original address' parent, if present, otherwise unset. */
 
 *parent = *addr;
 parent->child_count = 2;
 
 *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. */
 
 
 /* The routed address gets a new parent. */
 
@@ -1419,16 +1361,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;
 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. */
 
 
 /* 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.ignore_error = addr->prop.ignore_error;
+new->prop.address_data = addr->prop.address_data;
 new->dsn_flags = addr->dsn_flags;
 new->dsn_orcpt = addr->dsn_orcpt;
 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
 
 
 /* As it has turned out, we haven't set headers_add or headers_remove for the
@@ -1457,8 +1397,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. */
 
 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");
   {
   DEBUG(D_route)
     debug_printf("\"unseen\" delivery previously done - discarded\n");
@@ -1504,7 +1443,7 @@ route_address(address_item *addr, address_item **paddr_local,
 int yield = OK;
 BOOL unseen;
 router_instance *r, *nextr;
 int yield = OK;
 BOOL unseen;
 router_instance *r, *nextr;
-uschar *old_domain = addr->domain;
+const uschar *old_domain = addr->domain;
 
 HDEBUG(D_route)
   {
 
 HDEBUG(D_route)
   {
@@ -1516,8 +1455,7 @@ HDEBUG(D_route)
 encounters an error. If the address has start_router set, we begin from there
 instead of at the first router. */
 
 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;
   {
   uschar *error;
   struct passwd *pw = NULL;
@@ -1536,7 +1474,7 @@ for (r = (addr->start_router == NULL)? routers : addr->start_router;
 
   /* There are some weird cases where logging is disabled */
 
 
   /* There are some weird cases where logging is disabled */
 
-  disable_logging = r->disable_logging;
+  f.disable_logging = r->disable_logging;
 
   /* Record the last router to handle the address, and set the default
   next router. */
 
   /* Record the last router to handle the address, and set the default
   next router. */
@@ -1553,10 +1491,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
   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. */
 
   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)
       {
     {
     if (parent->router == r)
       {
@@ -1610,7 +1548,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. */
 
   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)
     {
     int plen = route_check_prefix(addr->local_part, r->prefix);
     if (plen > 0)
@@ -1629,7 +1567,7 @@ for (r = (addr->start_router == NULL)? routers : addr->start_router;
 
   /* Handle any configured suffix likewise. */
 
 
   /* 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)
     {
     int slen = route_check_suffix(addr->local_part, r->suffix);
     if (slen > 0)
@@ -1676,21 +1614,21 @@ for (r = (addr->start_router == NULL)? routers : addr->start_router;
   success, the string is attached to the address for all subsequent processing.
   */
 
   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);
     {
     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)
+      if (f.expand_string_forcedfail)
         {
         DEBUG(D_route) debug_printf("forced failure in expansion of \"%s\" "
             "(address_data): decline action taken\n", r->address_data);
 
         /* Expand "more" if necessary; DEFER => an expansion failed */
 
         {
         DEBUG(D_route) debug_printf("forced failure in expansion of \"%s\" "
             "(address_data): decline action taken\n", r->address_data);
 
         /* 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)
         if (yield != OK) goto ROUTE_EXIT;
 
         if (!more)
@@ -1712,7 +1650,7 @@ for (r = (addr->start_router == NULL)? routers : addr->start_router;
         goto ROUTE_EXIT;
         }
       }
         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
     }
 
   /* We are finally cleared for take-off with this router. Clear the the flag
@@ -1723,7 +1661,7 @@ for (r = (addr->start_router == NULL)? routers : addr->start_router;
 
   clearflag(addr, af_local_host_removed);
 
 
   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;
     {
     pwcopy.pw_name = CS string_copy(US pw->pw_name);
     pwcopy.pw_uid = pw->pw_uid;
@@ -1734,18 +1672,15 @@ for (r = (addr->start_router == NULL)? routers : addr->start_router;
     pw = &pwcopy;
     }
 
     pw = &pwcopy;
     }
 
-  /* Run the router, and handle the consequences. */
+  /* If this should be the last hop for DSN flag the addr. */
 
 
-#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))
-  {
+  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);
     addr->dsn_flags |= rf_dsnlasthop;
     HDEBUG(D_route) debug_printf("DSN: last hop for %s\n", addr->address);
-  }
-#endif
+    }
+
+  /* Run the router, and handle the consequences. */
 
   HDEBUG(D_route) debug_printf("calling %s router\n", r->name);
 
 
   HDEBUG(D_route) debug_printf("calling %s router\n", r->name);
 
@@ -1799,7 +1734,8 @@ for (r = (addr->start_router == NULL)? routers : addr->start_router;
     {
     /* Expand "more" if necessary */
 
     {
     /* 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)
     if (yield != OK) goto ROUTE_EXIT;
 
     if (!more)
@@ -1818,18 +1754,18 @@ 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. */
 
 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");
   {
   HDEBUG(D_route) debug_printf("no more routers\n");
-  if (addr->message == NULL)
+  if (!addr->message)
     {
     uschar *message = US"Unrouteable address";
     {
     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);
       {
       uschar *expmessage = expand_string(addr->router->cannot_route_message);
-      if (expmessage == NULL)
+      if (!expmessage)
         {
         {
-        if (!expand_string_forcedfail)
+        if (!f.expand_string_forcedfail)
           log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
             "cannot_route_message in %s router: %s", addr->router->name,
             expand_string_message);
           log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
             "cannot_route_message in %s router: %s", addr->router->name,
             expand_string_message);
@@ -1880,25 +1816,25 @@ networking, so it is included in the binary only if requested. */
 
 #ifdef SUPPORT_TRANSLATE_IP_ADDRESS
 
 
 #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;
   {
   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;
 
     {
     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;
 
 
     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;
+      if (f.expand_string_forcedfail) continue;
       addr->basic_errno = ERRNO_EXPANDFAIL;
       addr->message = string_sprintf("translate_ip_address expansion "
         "failed: %s", expand_string_message);
       addr->basic_errno = ERRNO_EXPANDFAIL;
       addr->message = string_sprintf("translate_ip_address expansion "
         "failed: %s", expand_string_message);
@@ -1939,17 +1875,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 */
 
 /* 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 */
 
 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)" : "");
     unseen? " (unseen)" : "");
-  }
 
 DEBUG(D_route)
   {
 
 DEBUG(D_route)
   {
@@ -1959,17 +1892,17 @@ DEBUG(D_route)
   debug_printf("  transport: %s\n", (addr->transport == NULL)?
     US"<none>" : addr->transport->name);
 
   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);
     {
     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->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");
     }
   }
     debug_printf("\n");
     }
   }
@@ -1978,35 +1911,20 @@ DEBUG(D_route)
 the "unseen" option (ignore if there are no further routers). */
 
 addr->message = NULL;
 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:
   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;
 
 deliver_set_expansions(NULL);
 router_name = NULL;
-disable_logging = FALSE;
+f.disable_logging = FALSE;
 return yield;
 }
 
 return yield;
 }
 
+#endif /*!MACRO_PREDEF*/
 /* End of route.c */
 /* End of route.c */
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")
 
 
   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.
                   routers, i.e. cause routing to fail.
 
   DEFER           retry this address later.
index 8049eaf..eb5b955 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -32,6 +32,18 @@ accept_router_options_block accept_router_option_defaults = {
 };
 
 
 };
 
 
+#ifdef MACRO_PREDEF
+
+/* Dummy entries */
+void accept_router_init(router_instance *rblock) {}
+int accept_router_entry(router_instance *rblock, address_item *addr,
+  struct passwd *pw, int verify, address_item **addr_local,
+  address_item **addr_remote, address_item **addr_new,
+  address_item **addr_succeed) {return 0;}
+
+#else  /*!MACRO_PREDEF*/
+
+
 
 /*************************************************
 *          Initialization entry point            *
 
 /*************************************************
 *          Initialization entry point            *
@@ -105,7 +117,7 @@ DEBUG(D_route) debug_printf("%s router called for %s\n  domain = %s\n",
 rc = rf_get_errors_address(addr, rblock, verify, &errors_to);
 if (rc != OK) return rc;
 
 rc = rf_get_errors_address(addr, rblock, verify, &errors_to);
 if (rc != OK) return rc;
 
-/* Set up the additional and removeable headers for the address. */
+/* Set up the additional and removable headers for the address. */
 
 rc = rf_get_munge_headers(addr, rblock, &extra_headers, &remove_headers);
 if (rc != OK) return rc;
 
 rc = rf_get_munge_headers(addr, rblock, &extra_headers, &remove_headers);
 if (rc != OK) return rc;
@@ -118,11 +130,12 @@ if (!rf_get_transport(rblock->transport_name, &(rblock->transport),
   addr, rblock->name, NULL)) return DEFER;
 
 addr->transport = 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;
 }
 
 
 return rf_queue_add(addr, addr_local, addr_remote, rblock, pw)? OK : DEFER;
 }
 
+#endif /*!MACRO_PREDEF*/
 /* End of routers/accept.c */
 /* End of routers/accept.c */
index c8fd3f9..33939be 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "../exim.h"
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "../exim.h"
@@ -18,10 +18,12 @@ 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)) },
       (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)) },
+  { "ipv4_only",          opt_stringptr,
+      (void *)(offsetof(dnslookup_router_options_block, ipv4_only)) },
+  { "ipv4_prefer",        opt_stringptr,
+      (void *)(offsetof(dnslookup_router_options_block, ipv4_prefer)) },
   { "mx_domains",         opt_stringptr,
       (void *)(offsetof(dnslookup_router_options_block, mx_domains)) },
   { "mx_fail_domains",    opt_stringptr,
   { "mx_domains",         opt_stringptr,
       (void *)(offsetof(dnslookup_router_options_block, mx_domains)) },
   { "mx_fail_domains",    opt_stringptr,
@@ -46,20 +48,37 @@ address can appear in the tables drtables.c. */
 int dnslookup_router_options_count =
   sizeof(dnslookup_router_options)/sizeof(optionlist);
 
 int dnslookup_router_options_count =
   sizeof(dnslookup_router_options)/sizeof(optionlist);
 
+
+#ifdef MACRO_PREDEF
+
+/* Dummy entries */
+dnslookup_router_options_block dnslookup_router_option_defaults = {0};
+void dnslookup_router_init(router_instance *rblock) {}
+int dnslookup_router_entry(router_instance *rblock, address_item *addr,
+  struct passwd *pw, int verify, address_item **addr_local,
+  address_item **addr_remote, address_item **addr_new,
+  address_item **addr_succeed) {return 0;}
+
+#else   /*!MACRO_PREDEF*/
+
+
+
+
 /* Default private options block for the dnslookup router. */
 
 dnslookup_router_options_block dnslookup_router_option_defaults = {
 /* Default private options block for the dnslookup router. */
 
 dnslookup_router_options_block dnslookup_router_option_defaults = {
-  FALSE,           /* check_secondary_mx */
-  TRUE,            /* qualify_single */
-  FALSE,           /* search_parents */
-  TRUE,            /* rewrite_headers */
-  NULL,            /* widen_domains */
-  NULL,            /* mx_domains */
-  NULL,            /* mx_fail_domains */
-  NULL,            /* srv_fail_domains */
-  NULL,            /* check_srv */
-  NULL,            /* dnssec_request_domains */
-  NULL             /* dnssec_require_domains */
+  .check_secondary_mx =        FALSE,
+  .qualify_single =    TRUE,
+  .search_parents =    FALSE,
+  .rewrite_headers =   TRUE,
+  .widen_domains =     NULL,
+  .mx_domains =                NULL,
+  .mx_fail_domains =   NULL,
+  .srv_fail_domains =  NULL,
+  .check_srv =         NULL,
+  .fail_defer_domains =        NULL,
+  .ipv4_only =         NULL,
+  .ipv4_prefer =       NULL,
 };
 
 
 };
 
 
@@ -141,15 +160,15 @@ dnslookup_router_entry(
 host_item h;
 int rc;
 int widen_sep = 0;
 host_item h;
 int rc;
 int widen_sep = 0;
-int whichrrs = HOST_FIND_BY_MX | HOST_FIND_BY_A;
+int whichrrs = HOST_FIND_BY_MX | HOST_FIND_BY_A | HOST_FIND_BY_AAAA;
 dnslookup_router_options_block *ob =
   (dnslookup_router_options_block *)(rblock->options_block);
 uschar *srv_service = NULL;
 uschar *widen = NULL;
 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 */
 uschar widen_buffer[256];
 
 addr_new = addr_new;          /* Keep picky compilers happy */
@@ -161,10 +180,10 @@ DEBUG(D_route)
 
 /* If an SRV check is required, expand the service name */
 
 
 /* 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))
+     && !f.expand_string_forcedfail)
     {
     addr->message = string_sprintf("%s router: failed to expand \"%s\": %s",
       rblock->name, ob->check_srv, expand_string_message);
     {
     addr->message = string_sprintf("%s router: failed to expand \"%s\": %s",
       rblock->name, ob->check_srv, expand_string_message);
@@ -195,8 +214,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. */
 
 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,
   {
   listptr = ob->widen_domains;
   widen = string_nextinlist(&listptr, &widen_sep, widen_buffer,
@@ -220,12 +239,12 @@ for (;;)
   int flags = whichrrs;
   BOOL removed = FALSE;
 
   int flags = whichrrs;
   BOOL removed = FALSE;
 
-  if (pre_widen != NULL)
+  if (pre_widen)
     {
     h.name = pre_widen;
     pre_widen = NULL;
     }
     {
     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,
     {
     h.name = string_sprintf("%s.%s", addr->domain, widen);
     widen = string_nextinlist(&listptr, &widen_sep, widen_buffer,
@@ -233,7 +252,7 @@ for (;;)
     DEBUG(D_route) debug_printf("%s router widened %s to %s\n", rblock->name,
       addr->domain, h.name);
     }
     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;
     {
     h.name = post_widen;
     post_widen = NULL;
@@ -242,6 +261,19 @@ for (;;)
     }
   else return DECLINE;
 
     }
   else return DECLINE;
 
+  /* Check if we must request only. or prefer, ipv4 */
+
+  if (  ob->ipv4_only
+     && expand_check_condition(ob->ipv4_only, rblock->name, US"router"))
+    flags = flags & ~HOST_FIND_BY_AAAA | HOST_FIND_IPV4_ONLY;
+  else if (f.search_find_defer)
+    return DEFER;
+  if (  ob->ipv4_prefer
+     && expand_check_condition(ob->ipv4_prefer, rblock->name, US"router"))
+    flags |= HOST_FIND_IPV4_FIRST;
+  else if (f.search_find_defer)
+    return DEFER;
+
   /* Set up the rest of the initial host item. Others may get chained on if
   there is more than one IP address. We set it up here instead of outside the
   loop so as to re-initialize if a previous try succeeded but was rejected
   /* Set up the rest of the initial host item. Others may get chained on if
   there is more than one IP address. We set it up here instead of outside the
   loop so as to re-initialize if a previous try succeeded but was rejected
@@ -257,19 +289,20 @@ for (;;)
 
   /* Unfortunately, we cannot set the mx_only option in advance, because the
   DNS lookup may extend an unqualified name. Therefore, we must do the test
 
   /* Unfortunately, we cannot set the mx_only option in advance, because the
   DNS lookup may extend an unqualified name. Therefore, we must do the test
-  subsequently. We use the same logic as that for widen_domains above to avoid
+  stoubsequently. We use the same logic as that for widen_domains above to avoid
   requesting a header rewrite that cannot work. */
 
   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;
     }
 
     {
     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,
-    ob->srv_fail_domains, ob->mx_fail_domains,
-    ob->dnssec_request_domains, ob->dnssec_require_domains,
+  rc = host_find_bydns(&h, CUS rblock->ignore_target_hosts, flags,
+    srv_service, ob->srv_fail_domains, ob->mx_fail_domains,
+    &rblock->dnssec,
     &fully_qualified_name, &removed);
     &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
   if (removed) setflag(addr, af_local_host_removed);
 
   /* If host found with only address records, test for the domain's being in
@@ -277,9 +310,9 @@ for (;;)
   the option is historical. */
 
   if ((rc == HOST_FOUND || rc == HOST_FOUND_LOCAL) && h.mx < 0 &&
   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:
           &domainlist_anchor, addr->domain_cache, MCL_DOMAIN, TRUE, NULL))
       {
       case DEFER:
@@ -291,11 +324,15 @@ for (;;)
         rblock->name, fully_qualified_name);
       continue;
       }
         rblock->name, fully_qualified_name);
       continue;
       }
-    }
 
   /* Deferral returns forthwith, and anything other than failure breaks the
   loop. */
 
 
   /* Deferral returns forthwith, and anything other than failure breaks the
   loop. */
 
+  if (rc == HOST_FIND_SECURITY)
+    {
+    addr->message = US"host lookup done insecurely";
+    return DEFER;
+    }
   if (rc == HOST_FIND_AGAIN)
     {
     if (rblock->pass_on_timeout)
   if (rc == HOST_FIND_AGAIN)
     {
     if (rblock->pass_on_timeout)
@@ -310,6 +347,22 @@ for (;;)
 
   if (rc != HOST_FIND_FAILED) break;
 
 
   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
   /* 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 +381,7 @@ for (;;)
       addr->message = US"an MX or SRV record indicated no SMTP service";
     else
       {
       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)
         {
       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)
         {
@@ -345,7 +399,7 @@ for (;;)
   /* If there's a syntax error, do not continue with any widening, and note
   the error. */
 
   /* If there's a syntax error, do not continue with any widening, and note
   the error. */
 
-  if (host_find_failed_syntax)
+  if (f.host_find_failed_syntax)
     {
     addr->message = string_sprintf("mail domain \"%s\" is syntactically "
       "invalid", h.name);
     {
     addr->message = string_sprintf("mail domain \"%s\" is syntactically "
       "invalid", h.name);
@@ -402,13 +456,13 @@ else if (ob->check_secondary_mx && !testflag(addr, af_local_host_removed))
 
 /* Set up the errors address, if any. */
 
 
 /* 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;
 
 if (rc != OK) return rc;
 
-/* Set up the additional and removeable headers for this address. */
+/* Set up the additional and removable 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
 if (rc != OK) return rc;
 
 /* Get store in which to preserve the original host item, chained on
@@ -429,4 +483,7 @@ return rf_queue_add(addr, addr_local, addr_remote, rblock, pw)?
   OK : DEFER;
 }
 
   OK : DEFER;
 }
 
+#endif   /*!MACRO_PREDEF*/
 /* End of routers/dnslookup.c */
 /* End of routers/dnslookup.c */
+/* vi: aw ai sw=2
+*/
index 518b7f4..b7e0915 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Private structure for the private options. */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Private structure for the private options. */
@@ -17,8 +17,9 @@ typedef struct {
   uschar *mx_fail_domains;
   uschar *srv_fail_domains;
   uschar *check_srv;
   uschar *mx_fail_domains;
   uschar *srv_fail_domains;
   uschar *check_srv;
-  uschar *dnssec_request_domains;
-  uschar *dnssec_require_domains;
+  uschar *fail_defer_domains;
+  uschar *ipv4_only;
+  uschar *ipv4_prefer;
 } dnslookup_router_options_block;
 
 /* Data for reading the private options. */
 } dnslookup_router_options_block;
 
 /* Data for reading the private options. */
index 7be96e5..ecc6042 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -30,6 +30,17 @@ value is present to keep some compilers happy. */
 ipliteral_router_options_block ipliteral_router_option_defaults = { 0 };
 
 
 ipliteral_router_options_block ipliteral_router_option_defaults = { 0 };
 
 
+#ifdef MACRO_PREDEF
+
+/* Dummy entries */
+void ipliteral_router_init(router_instance *rblock) {}
+int ipliteral_router_entry(router_instance *rblock, address_item *addr,
+  struct passwd *pw, int verify, address_item **addr_local,
+  address_item **addr_remote, address_item **addr_new,
+  address_item **addr_succeed) {return 0;}
+
+#else   /*!MACRO_PREDEF*/
+
 
 /*************************************************
 *          Initialization entry point            *
 
 /*************************************************
 *          Initialization entry point            *
@@ -99,8 +110,8 @@ ipliteral_router_options_block *ob =
   (ipliteral_router_options_block *)(rblock->options_block);
 */
 host_item *h;
   (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;
 
 int len = Ustrlen(domain);
 int rc, ipv;
 
@@ -116,29 +127,23 @@ declines. Otherwise route to the single IP address, setting the host name to
 "(unnamed)". */
 
 if (domain[0] != '[' || domain[len-1] != ']') return DECLINE;
 "(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))
 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;
   return DECLINE;
-  }
 
 /* It seems unlikely that ignore_target_hosts will be used with this router,
 but if it is set, it should probably work. */
 
 
 /* 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";
   {
   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;
   }
 
   return DECLINE;
   }
 
@@ -149,8 +154,7 @@ h = store_get(sizeof(host_item));
 h->next = NULL;
 h->address = string_copy(ip);
 h->port = PORT_NONE;
 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;
 h->mx = MX_NONE;
 h->status = hstatus_unknown;
 h->why = hwhy_unknown;
@@ -172,13 +176,13 @@ addr->host_list = h;
 
 /* Set up the errors address, if any. */
 
 
 /* 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;
 
 if (rc != OK) return rc;
 
-/* Set up the additional and removeable headers for this address. */
+/* Set up the additional and removable 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
 if (rc != OK) return rc;
 
 /* Fill in the transport, queue the address for local or remote delivery, and
@@ -197,4 +201,5 @@ return rf_queue_add(addr, addr_local, addr_remote, rblock, pw)?
   OK : DEFER;
 }
 
   OK : DEFER;
 }
 
+#endif   /*!MACRO_PREDEF*/
 /* End of routers/ipliteral.c */
 /* End of routers/ipliteral.c */
index 3728007..ff67af3 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -44,6 +44,20 @@ address can appear in the tables drtables.c. */
 int iplookup_router_options_count =
   sizeof(iplookup_router_options)/sizeof(optionlist);
 
 int iplookup_router_options_count =
   sizeof(iplookup_router_options)/sizeof(optionlist);
 
+
+#ifdef MACRO_PREDEF
+
+/* Dummy entries */
+iplookup_router_options_block iplookup_router_option_defaults = {0};
+void iplookup_router_init(router_instance *rblock) {}
+int iplookup_router_entry(router_instance *rblock, address_item *addr,
+  struct passwd *pw, int verify, address_item **addr_local,
+  address_item **addr_remote, address_item **addr_new,
+  address_item **addr_succeed) {return 0;}
+
+#else   /*!MACRO_PREDEF*/
+
+
 /* Default private options block for the iplookup router. */
 
 iplookup_router_options_block iplookup_router_option_defaults = {
 /* Default private options block for the iplookup router. */
 
 iplookup_router_options_block iplookup_router_option_defaults = {
@@ -143,7 +157,8 @@ iplookup_router_entry(
 {
 uschar *query = NULL;
 uschar *reply;
 {
 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;
 uschar host_buffer[256];
 host_item *host = store_get(sizeof(host_item));
 address_item *new_addr;
@@ -190,7 +205,7 @@ being a host list. */
 
 listptr = ob->hosts;
 while ((hostname = string_nextinlist(&listptr, &sep, host_buffer,
 
 listptr = ob->hosts;
 while ((hostname = string_nextinlist(&listptr, &sep, host_buffer,
-       sizeof(host_buffer))) != NULL)
+       sizeof(host_buffer))))
   {
   host_item *h;
 
   {
   host_item *h;
 
@@ -206,27 +221,30 @@ while ((hostname = string_nextinlist(&listptr, &sep, host_buffer,
     host->address = host->name;
   else
     {
     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. */
 
     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;
+    int host_af;
+    client_conn_ctx query_cctx = {0};
 
     /* Skip any hosts for which we have no address */
 
 
     /* 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;
 
     /* 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_cctx.sock = ip_socket(ob->protocol == ip_udp ? SOCK_DGRAM:SOCK_STREAM,
       host_af);
       host_af);
-    if (query_socket < 0)
+    if (query_cctx.sock < 0)
       {
       if (ob->optional) return PASS;
       addr->message = string_sprintf("failed to create socket in %s router",
       {
       if (ob->optional) return PASS;
       addr->message = string_sprintf("failed to create socket in %s router",
@@ -237,10 +255,12 @@ while ((hostname = string_nextinlist(&listptr, &sep, host_buffer,
     /* Connect to the remote host, under a timeout. In fact, timeouts can occur
     here only for TCP calls; for a UDP socket, "connect" always works (the
     router will timeout later on the read call). */
     /* Connect to the remote host, under a timeout. In fact, timeouts can occur
     here only for TCP calls; for a UDP socket, "connect" always works (the
     router will timeout later on the read call). */
+/*XXX could take advantage of TFO */
 
 
-    if (ip_connect(query_socket, host_af, h->address,ob->port, ob->timeout) < 0)
+    if (ip_connect(query_cctx.sock, host_af, h->address,ob->port, ob->timeout,
+               ob->protocol == ip_udp ? NULL : &tcp_fastopen_nodata) < 0)
       {
       {
-      close(query_socket);
+      close(query_cctx.sock);
       DEBUG(D_route)
         debug_printf("connection to %s failed: %s\n", h->address,
           strerror(errno));
       DEBUG(D_route)
         debug_printf("connection to %s failed: %s\n", h->address,
           strerror(errno));
@@ -249,18 +269,18 @@ while ((hostname = string_nextinlist(&listptr, &sep, host_buffer,
 
     /* Send the query. If it fails, just continue with the next address. */
 
 
     /* Send the query. If it fails, just continue with the next address. */
 
-    if (send(query_socket, query, query_len, 0) < 0)
+    if (send(query_cctx.sock, query, query_len, 0) < 0)
       {
       DEBUG(D_route) debug_printf("send to %s failed\n", h->address);
       {
       DEBUG(D_route) debug_printf("send to %s failed\n", h->address);
-      (void)close(query_socket);
+      (void)close(query_cctx.sock);
       continue;
       }
 
     /* Read the response and close the socket. If the read fails, try the
     next IP address. */
 
       continue;
       }
 
     /* Read the response and close the socket. If the read fails, try the
     next IP address. */
 
-    count = ip_recv(query_socket, reply, sizeof(reply) - 1, ob->timeout);
-    (void)close(query_socket);
+    count = ip_recv(&query_cctx, reply, sizeof(reply) - 1, ob->timeout);
+    (void)close(query_cctx.sock);
     if (count <= 0)
       {
       DEBUG(D_route) debug_printf("%s from %s\n", (errno == ETIMEDOUT)?
     if (count <= 0)
       {
       DEBUG(D_route) debug_printf("%s from %s\n", (errno == ETIMEDOUT)?
@@ -280,7 +300,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 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;
   }
 
 
   }
 
 
@@ -375,27 +395,27 @@ the chain of new addressess. */
 new_addr = deliver_make_addr(reroute, TRUE);
 new_addr->parent = addr;
 
 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 "
   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;
 
 addr->child_count++;
 new_addr->next = *addr_new;
 *addr_new = new_addr;
 
-/* Set up the errors address, if any, and the additional and removeable headers
+/* Set up the errors address, if any, and the additional and removable headers
 for this new address. */
 
 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;
 
 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;
 }
 
 if (rc != OK) return rc;
 
 return OK;
 }
 
+#endif   /*!MACRO_PREDEF*/
 /* End of routers/iplookup.c */
 /* End of routers/iplookup.c */
index cb047e2..7389a99 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -34,6 +34,21 @@ address can appear in the tables drtables.c. */
 int manualroute_router_options_count =
   sizeof(manualroute_router_options)/sizeof(optionlist);
 
 int manualroute_router_options_count =
   sizeof(manualroute_router_options)/sizeof(optionlist);
 
+
+#ifdef MACRO_PREDEF
+
+/* Dummy entries */
+manualroute_router_options_block manualroute_router_option_defaults = {0};
+void manualroute_router_init(router_instance *rblock) {}
+int manualroute_router_entry(router_instance *rblock, address_item *addr,
+  struct passwd *pw, int verify, address_item **addr_local,
+  address_item **addr_remote, address_item **addr_new,
+  address_item **addr_succeed) {return 0;}
+
+#else   /*!MACRO_PREDEF*/
+
+
+
 /* Default private options block for the manualroute router. */
 
 manualroute_router_options_block manualroute_router_option_defaults = {
 /* Default private options block for the manualroute router. */
 
 manualroute_router_options_block manualroute_router_option_defaults = {
@@ -139,20 +154,20 @@ Returns:    FALSE if domain expected and string is empty;
 */
 
 static BOOL
 */
 
 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++;
 
 {
 while (*s != 0 && isspace(*s)) s++;
 
-if (domain != NULL)
+if (domain)
   {
   {
-  if (*s == 0) return FALSE;            /* missing data */
+  if (!*s) return FALSE;            /* missing data */
   *domain = string_dequote(&s);
   *domain = string_dequote(&s);
-  while (*s != 0 && isspace(*s)) s++;
+  while (*s && isspace(*s)) s++;
   }
 
 *hostlist = string_dequote(&s);
   }
 
 *hostlist = string_dequote(&s);
-while (*s != 0 && isspace(*s)) s++;
+while (*s && isspace(*s)) s++;
 *options = s;
 return TRUE;
 }
 *options = s;
 return TRUE;
 }
@@ -226,9 +241,11 @@ manualroute_router_entry(
 {
 int rc, lookup_type;
 uschar *route_item = NULL;
 {
 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;
 manualroute_router_options_block *ob =
   (manualroute_router_options_block *)(rblock->options_block);
 transport_instance *transport = NULL;
@@ -244,7 +261,7 @@ DEBUG(D_route) debug_printf("%s router called for %s\n  domain = %s\n",
 /* The initialization check ensures that either route_list or route_data is
 set. */
 
 /* The initialization check ensures that either route_list or route_data is
 set. */
 
-if (ob->route_list != NULL)
+if (ob->route_list)
   {
   int sep = -(';');             /* Default is semicolon */
   listptr = ob->route_list;
   {
   int sep = -(';');             /* Default is semicolon */
   listptr = ob->route_list;
@@ -260,7 +277,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,
     /* 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 */
       break;
 
     /* If there was a problem doing the check, defer */
@@ -272,7 +289,7 @@ if (ob->route_list != NULL)
       }
     }
 
       }
     }
 
-  if (route_item == NULL) return DECLINE;  /* No pattern in the list matched */
+  if (!route_item) return DECLINE;  /* No pattern in the list matched */
   }
 
 /* Handle a single routing item in route_data. If it expands to an empty
   }
 
 /* Handle a single routing item in route_data. If it expands to an empty
@@ -280,17 +297,17 @@ string, decline. */
 
 else
   {
 
 else
   {
-  route_item = rf_expand_data(addr, ob->route_data, &rc);
-  if (route_item == NULL) return rc;
+  if (!(route_item = rf_expand_data(addr, ob->route_data, &rc)))
+    return rc;
   (void) parse_route_item(route_item, NULL, &hostlist, &options);
   (void) parse_route_item(route_item, NULL, &hostlist, &options);
-  if (hostlist[0] == 0) return DECLINE;
+  if (!hostlist[0]) return DECLINE;
   }
 
 /* Expand the hostlist item. It may then pointing to an empty string, or to a
 single host or a list of hosts; options is pointing to the rest of the
 routelist item, which is either empty or contains various option words. */
 
   }
 
 /* Expand the hostlist item. It may then pointing to an empty string, or to a
 single host or a list of hosts; options is pointing to the rest of the
 routelist item, which is either empty or contains various option words. */
 
-DEBUG(D_route) debug_printf("original list of hosts = \"%s\" options = %s\n",
+DEBUG(D_route) debug_printf("original list of hosts = '%s' options = '%s'\n",
   hostlist, options);
 
 newhostlist = expand_string_copy(hostlist);
   hostlist, options);
 
 newhostlist = expand_string_copy(hostlist);
@@ -300,47 +317,49 @@ expand_nmax = -1;
 /* If the expansion was forced to fail, just decline. Otherwise there is a
 configuration problem. */
 
 /* If the expansion was forced to fail, just decline. Otherwise there is a
 configuration problem. */
 
-if (newhostlist == NULL)
+if (!newhostlist)
   {
   {
-  if (expand_string_forcedfail) return DECLINE;
+  if (f.expand_string_forcedfail) return DECLINE;
   addr->message = string_sprintf("%s router: failed to expand \"%s\": %s",
     rblock->name, hostlist, expand_string_message);
   return DEFER;
   }
 else hostlist = newhostlist;
 
   addr->message = string_sprintf("%s router: failed to expand \"%s\": %s",
     rblock->name, hostlist, expand_string_message);
   return DEFER;
   }
 else hostlist = newhostlist;
 
-DEBUG(D_route) debug_printf("expanded list of hosts = \"%s\" options = %s\n",
+DEBUG(D_route) debug_printf("expanded list of hosts = '%s' options = '%s'\n",
   hostlist, options);
 
 /* Set default lookup type and scan the options */
 
   hostlist, options);
 
 /* Set default lookup type and scan the options */
 
-lookup_type = lk_default;
+lookup_type = LK_DEFAULT;
 
 
-while (*options != 0)
+while (*options)
   {
   {
-  int term;
-  uschar *s = options;
+  unsigned n;
+  const uschar *s = options;
   while (*options != 0 && !isspace(*options)) options++;
   while (*options != 0 && !isspace(*options)) options++;
-  term = *options;
-  *options = 0;
-
-  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;
+  n = options-s;
+
+  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 = lookup_type & ~(LK_DEFAULT | LK_BYDNS) | LK_BYNAME;
+  else if (Ustrncmp(s, "bydns", n) == 0)
+    lookup_type = lookup_type & ~(LK_DEFAULT | LK_BYNAME) & LK_BYDNS;
+  else if (Ustrncmp(s, "ipv4_prefer", n) == 0) lookup_type |= LK_IPV4_PREFER;
+  else if (Ustrncmp(s, "ipv4_only",   n) == 0) lookup_type |= LK_IPV4_ONLY;
   else
     {
     transport_instance *t;
   else
     {
     transport_instance *t;
-    for (t = transports; t != NULL; t = t->next)
-      {
-      if (Ustrcmp(t->name, s) == 0)
+    for (t = transports; t; t = t->next)
+      if (Ustrncmp(t->name, s, n) == 0)
         {
         transport = t;
         individual_transport_set = TRUE;
         break;
         }
         {
         transport = t;
         individual_transport_set = TRUE;
         break;
         }
-      }
-    if (t == NULL)
+
+    if (!t)
       {
       s = string_sprintf("unknown routing option or transport name \"%s\"", s);
       log_write(0, LOG_MAIN, "Error in %s router: %s", rblock->name, s);
       {
       s = string_sprintf("unknown routing option or transport name \"%s\"", s);
       log_write(0, LOG_MAIN, "Error in %s router: %s", rblock->name, s);
@@ -349,7 +368,7 @@ while (*options != 0)
       }
     }
 
       }
     }
 
-  if (term != 0)
+  if (*options)
     {
     options++;
     while (*options != 0 && isspace(*options)) options++;
     {
     options++;
     while (*options != 0 && isspace(*options)) options++;
@@ -358,13 +377,13 @@ while (*options != 0)
 
 /* Set up the errors address, if any. */
 
 
 /* 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;
 
 if (rc != OK) return rc;
 
-/* Set up the additional and removeable headers for this address. */
+/* Set up the additional and removable 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
 if (rc != OK) return rc;
 
 /* If an individual transport is not set, get the transport for this router, if
@@ -382,9 +401,9 @@ if (!individual_transport_set)
 /* Deal with the case of a local transport. The host list is passed over as a
 single text string that ends up in $host. */
 
 /* Deal with the case of a local transport. The host list is passed over as a
 single text string that ends up in $host. */
 
-if (transport != NULL && transport->info->local)
+if (transport && transport->info->local)
   {
   {
-  if (hostlist[0] != 0)
+  if (hostlist[0])
     {
     host_item *h;
     addr->host_list = h = store_get(sizeof(host_item));
     {
     host_item *h;
     addr->host_list = h = store_get(sizeof(host_item));
@@ -411,11 +430,11 @@ if (transport != NULL && transport->info->local)
 list is mandatory in either case, except when verifying, in which case the
 address is just accepted. */
 
 list is mandatory in either case, except when verifying, in which case the
 address is just accepted. */
 
-if (hostlist[0] == 0)
+if (!hostlist[0])
   {
   if (verify != v_none) goto ROUTED;
   addr->message = string_sprintf("error in %s router: no host(s) specified "
   {
   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;
   }
   log_write(0, LOG_MAIN, "%s", addr->message);
   return DEFER;
   }
@@ -423,7 +442,7 @@ if (hostlist[0] == 0)
 /* Otherwise we finish the routing here by building a chain of host items
 for the list of configured hosts, and then finding their addresses. */
 
 /* Otherwise we finish the routing here by building a chain of host items
 for the list of configured hosts, and then finding their addresses. */
 
-host_build_hostlist(&(addr->host_list), hostlist, randomize);
+host_build_hostlist(&addr->host_list, hostlist, randomize);
 rc = rf_lookup_hostlist(rblock, addr, rblock->ignore_target_hosts, lookup_type,
   ob->hff_code, addr_new);
 if (rc != OK) return rc;
 rc = rf_lookup_hostlist(rblock, addr, rblock->ignore_target_hosts, lookup_type,
   ob->hff_code, addr_new);
 if (rc != OK) return rc;
@@ -432,7 +451,7 @@ if (rc != OK) return rc;
 be ignored, in which case we will end up with an empty host list. What happens
 is controlled by host_all_ignored. */
 
 be ignored, in which case we will end up with an empty host list. What happens
 is controlled by host_all_ignored. */
 
-if (addr->host_list == NULL)
+if (!addr->host_list)
   {
   int i;
   DEBUG(D_route) debug_printf("host_find_failed ignored every host\n");
   {
   int i;
   DEBUG(D_route) debug_printf("host_find_failed ignored every host\n");
@@ -474,4 +493,5 @@ addr->transport = transport;
 return OK;
 }
 
 return OK;
 }
 
+#endif   /*!MACRO_PREDEF*/
 /* End of routers/manualroute.c */
 /* End of routers/manualroute.c */
index 11e1fdc..01191ef 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "../exim.h"
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "../exim.h"
@@ -40,6 +40,20 @@ address can appear in the tables drtables.c. */
 int queryprogram_router_options_count =
   sizeof(queryprogram_router_options)/sizeof(optionlist);
 
 int queryprogram_router_options_count =
   sizeof(queryprogram_router_options)/sizeof(optionlist);
 
+
+#ifdef MACRO_PREDEF
+
+/* Dummy entries */
+queryprogram_router_options_block queryprogram_router_option_defaults = {0};
+void queryprogram_router_init(router_instance *rblock) {}
+int queryprogram_router_entry(router_instance *rblock, address_item *addr,
+  struct passwd *pw, int verify, address_item **addr_local,
+  address_item **addr_remote, address_item **addr_new,
+  address_item **addr_succeed) {return 0;}
+
+#else   /*!MACRO_PREDEF*/
+
+
 /* Default private options block for the queryprogram router. */
 
 queryprogram_router_options_block queryprogram_router_option_defaults = {
 /* Default private options block for the queryprogram router. */
 
 queryprogram_router_options_block queryprogram_router_option_defaults = {
@@ -109,20 +123,22 @@ add_generated(router_instance *rblock, address_item **addr_new,
 {
 while (generated != NULL)
   {
 {
 while (generated != NULL)
   {
+  BOOL ignore_error = addr->prop.ignore_error;
   address_item *next = generated;
   address_item *next = generated;
+
   generated = next->next;
 
   next->parent = addr;
   generated = next->next;
 
   next->parent = addr;
-  orflag(next, addr, af_propagate);
-  next->p = *addr_prop;
+  next->prop = *addr_prop;
+  next->prop.ignore_error = next->prop.ignore_error || ignore_error;
   next->start_router = rblock->redirect_router;
 
   next->next = *addr_new;
   *addr_new = next;
 
   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 "
     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)
   addr->child_count++;
 
   DEBUG(D_route)
@@ -192,7 +208,7 @@ int fd_in, fd_out, len, rc;
 pid_t pid;
 struct passwd *upw = NULL;
 uschar buffer[1024];
 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 =
 uschar *rword, *rdata, *s;
 address_item_propagated addr_prop;
 queryprogram_router_options_block *ob =
@@ -214,15 +230,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. */
 
 /* 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;
 
 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;
 
 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;
 
 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). */
 
 /* Get the fixed or expanded uid under which the command is to run
 (initialization ensures that one or the other is set). */
 
@@ -499,14 +520,14 @@ s = expand_string(US"${extract{hosts}{$value}}");
 
 if (*s != 0)
   {
 
 if (*s != 0)
   {
-  int lookup_type = lk_default;
+  int lookup_type = LK_DEFAULT;
   uschar *ss = expand_string(US"${extract{lookup}{$value}}");
   lookup_value = NULL;
 
   if (*ss != 0)
     {
   uschar *ss = expand_string(US"${extract{lookup}{$value}}");
   lookup_value = NULL;
 
   if (*ss != 0)
     {
-    if (Ustrcmp(ss, "byname") == 0) lookup_type = lk_byname;
-    else if (Ustrcmp(ss, "bydns") == 0) lookup_type = lk_bydns;
+    if (Ustrcmp(ss, "byname") == 0) lookup_type = LK_BYNAME;
+    else if (Ustrcmp(ss, "bydns") == 0) lookup_type = LK_BYDNS;
     else
       {
       addr->message = string_sprintf("bad lookup type \"%s\" yielded by "
     else
       {
       addr->message = string_sprintf("bad lookup type \"%s\" yielded by "
@@ -526,7 +547,7 @@ lookup_value = NULL;
 
 /* Put the errors address, extra headers, and address_data into this address */
 
 
 /* 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. */
 
 
 /* Queue the address for local or remote delivery. */
 
@@ -534,4 +555,5 @@ return rf_queue_add(addr, addr_local, addr_remote, rblock, pw)?
   OK : DEFER;
 }
 
   OK : DEFER;
 }
 
+#endif   /*!MACRO_PREDEF*/
 /* End of routers/queryprogram.c */
 /* End of routers/queryprogram.c */
index c6705e5..938db36 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -133,6 +133,21 @@ address can appear in the tables drtables.c. */
 int redirect_router_options_count =
   sizeof(redirect_router_options)/sizeof(optionlist);
 
 int redirect_router_options_count =
   sizeof(redirect_router_options)/sizeof(optionlist);
 
+
+#ifdef MACRO_PREDEF
+
+/* Dummy entries */
+redirect_router_options_block redirect_router_option_defaults = {0};
+void redirect_router_init(router_instance *rblock) {}
+int redirect_router_entry(router_instance *rblock, address_item *addr,
+  struct passwd *pw, int verify, address_item **addr_local,
+  address_item **addr_remote, address_item **addr_new,
+  address_item **addr_succeed) {return 0;}
+
+#else   /*!MACRO_PREDEF*/
+
+
+
 /* Default private options block for the redirect router. */
 
 redirect_router_options_block redirect_router_option_defaults = {
 /* Default private options block for the redirect router. */
 
 redirect_router_options_block redirect_router_option_defaults = {
@@ -208,11 +223,11 @@ than false, there is likely to be a problem. */
 if (ob->one_time)
   {
   ob->forbid_pipe = ob->forbid_file = ob->forbid_filter_reply = TRUE;
 if (ob->one_time)
   {
   ob->forbid_pipe = ob->forbid_file = ob->forbid_filter_reply = TRUE;
-  if (rblock->extra_headers != NULL || rblock->remove_headers != NULL)
+  if (rblock->extra_headers || rblock->remove_headers)
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n  "
       "\"headers_add\" and \"headers_remove\" are not permitted with "
       "\"one_time\"", rblock->name);
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n  "
       "\"headers_add\" and \"headers_remove\" are not permitted with "
       "\"one_time\"", rblock->name);
-  if (rblock->unseen || rblock->expand_unseen != NULL)
+  if (rblock->unseen || rblock->expand_unseen)
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n  "
       "\"unseen\" may not be used with \"one_time\"", rblock->name);
   }
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n  "
       "\"unseen\" may not be used with \"one_time\"", rblock->name);
   }
@@ -224,7 +239,7 @@ or if owngroups is set. */
 
 if (ob->check_owner == TRUE_UNSET)
   ob->check_owner = rblock->check_local_user ||
 
 if (ob->check_owner == TRUE_UNSET)
   ob->check_owner = rblock->check_local_user ||
-                    (ob->owners != NULL && ob->owners[0] != 0);
+                    (ob->owners && ob->owners[0] != 0);
 
 if (ob->check_group == TRUE_UNSET)
   ob->check_group = (rblock->check_local_user && (ob->modemask & 020) == 0) ||
 
 if (ob->check_group == TRUE_UNSET)
   ob->check_group = (rblock->check_local_user && (ob->modemask & 020) == 0) ||
@@ -232,7 +247,7 @@ if (ob->check_group == TRUE_UNSET)
 
 /* If explicit qualify domain set, the preserve option is locked out */
 
 
 /* If explicit qualify domain set, the preserve option is locked out */
 
-if (ob->qualify_domain != NULL && ob->qualify_preserve_domain)
+if (ob->qualify_domain && ob->qualify_preserve_domain)
   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n  "
     "only one of \"qualify_domain\" or \"qualify_preserve_domain\" must be set",
     rblock->name);
   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n  "
     "only one of \"qualify_domain\" or \"qualify_preserve_domain\" must be set",
     rblock->name);
@@ -277,11 +292,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,
   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;
 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 +340,18 @@ add_generated(router_instance *rblock, address_item **addr_new,
 redirect_router_options_block *ob =
   (redirect_router_options_block *)(rblock->options_block);
 
 redirect_router_options_block *ob =
   (redirect_router_options_block *)(rblock->options_block);
 
-while (generated != NULL)
+while (generated)
   {
   address_item *parent;
   address_item *next = 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;
 
   generated = next->next;
   next->parent = addr;
-  orflag(next, addr, af_ignore_error);
   next->start_router = rblock->redirect_router;
   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 "
     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;
   addr->child_count++;
 
   next->next = *addr_new;
@@ -345,9 +359,9 @@ while (generated != NULL)
 
   /* Don't do the "one_time" thing for the first pass of a 2-stage queue run. */
 
 
   /* Don't do the "one_time" thing for the first pass of a 2-stage queue run. */
 
-  if (ob->one_time && !queue_2stage)
+  if (ob->one_time && !f.queue_2stage)
     {
     {
-    for (parent = addr; parent->parent != NULL; parent = parent->parent);
+    for (parent = addr; parent->parent; parent = parent->parent) ;
     next->onetime_parent = parent->address;
     }
 
     next->onetime_parent = parent->address;
     }
 
@@ -358,28 +372,27 @@ while (generated != NULL)
   unless the ancestor was routed by a case-sensitive router. */
 
   if (ob->check_ancestor)
   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;
         }
           ) == 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. */
 
 
   /* 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;
+    {
+    BOOL ignore_error = next->prop.ignore_error;
+    next->prop = *addr_prop;
+    next->prop.ignore_error = ignore_error || addr->prop.ignore_error;
+    }
+  if (errors_address) 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,
 
   /* 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 +464,19 @@ while (generated != NULL)
       }
     }
 
       }
     }
 
+#ifdef SUPPORT_I18N
+    if (!next->prop.utf8_msg)
+      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  " : "",
   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))
       (next->transport == NULL)? US"NULL" : next->transport->name);
 
     if (testflag(next, af_uid_set))
@@ -470,6 +489,10 @@ while (generated != NULL)
     else
       debug_printf("gid=unset ");
 
     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);
     }
   }
     debug_printf("home=%s\n", next->home_dir);
     }
   }
@@ -517,7 +540,7 @@ int redirect_router_entry(
 redirect_router_options_block *ob =
   (redirect_router_options_block *)(rblock->options_block);
 address_item *generated = NULL;
 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;
 uschar *discarded = US"discarded";
 address_item_propagated addr_prop;
 error_block *eblock = NULL;
@@ -544,11 +567,17 @@ addr_prop.remove_headers = NULL;
 #ifdef EXPERIMENTAL_SRS
 addr_prop.srs_sender = NULL;
 #endif
 #ifdef EXPERIMENTAL_SRS
 addr_prop.srs_sender = NULL;
 #endif
+#ifdef SUPPORT_I18N
+addr_prop.utf8_msg = addr->prop.utf8_msg;
+addr_prop.utf8_downcvt = addr->prop.utf8_downcvt;
+addr_prop.utf8_downcvt_maybe = addr->prop.utf8_downcvt_maybe;
+#endif
+
 
 /* When verifying and testing addresses, the "logwrite" command in filters
 must be bypassed. */
 
 
 /* When verifying and testing addresses, the "logwrite" command in filters
 must be bypassed. */
 
-if (verify == v_none && !address_test_mode) options |= RDO_REALLOG;
+if (verify == v_none && !f.address_test_mode) options |= RDO_REALLOG;
 
 /* Sort out the fixed or dynamic uid/gid. This uid is used (a) for reading the
 file (and interpreting a filter) and (b) for running the transports for
 
 /* Sort out the fixed or dynamic uid/gid. This uid is used (a) for reading the
 file (and interpreting a filter) and (b) for running the transports for
@@ -640,8 +669,8 @@ if (!ugid.gid_set && pw != NULL)
 //          eximsrs_db_set(FALSE, 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)
         {
 
         if((n_srs = eximsrs_forward(&res, sender_address, usedomain)) == OK)
         {
@@ -757,7 +786,7 @@ switch (frc)
   high so that their completion does not mark the original address done. */
 
   case FF_FREEZE:
   high so that their completion does not mark the original address done. */
 
   case FF_FREEZE:
-  if (!deliver_manual_thaw)
+  if (!f.deliver_manual_thaw)
     {
     if ((xrc = sort_errors_and_headers(rblock, addr, verify, &addr_prop))
       != OK) return xrc;
     {
     if ((xrc = sort_errors_and_headers(rblock, addr, verify, &addr_prop))
       != OK) return xrc;
@@ -826,7 +855,7 @@ if (eblock != NULL)
   if (!moan_skipped_syntax_errors(
         rblock->name,                            /* For message content */
         eblock,                                  /* Ditto */
   if (!moan_skipped_syntax_errors(
         rblock->name,                            /* For message content */
         eblock,                                  /* Ditto */
-        (verify != v_none || address_test_mode)?
+        (verify != v_none || f.address_test_mode)?
           NULL : ob->syntax_errors_to,           /* Who to mail */
         generated != NULL,                       /* True if not all failed */
         ob->syntax_errors_text))                 /* Custom message */
           NULL : ob->syntax_errors_to,           /* Who to mail */
         generated != NULL,                       /* True if not all failed */
         ob->syntax_errors_text))                 /* Custom message */
@@ -858,7 +887,7 @@ generated anything. Log what happened to this address, and return DISCARD. */
 
 if (frc == FF_DELIVERED)
   {
 
 if (frc == FF_DELIVERED)
   {
-  if (generated == NULL && verify == v_none && !address_test_mode)
+  if (generated == NULL && verify == v_none && !f.address_test_mode)
     {
     log_write(0, LOG_MAIN, "=> %s <%s> R=%s", discarded, addr->address,
       rblock->name);
     {
     log_write(0, LOG_MAIN, "=> %s <%s> R=%s", discarded, addr->address,
       rblock->name);
@@ -887,11 +916,9 @@ else
   next->next = *addr_new;
   *addr_new = next;
 
   next->next = *addr_new;
   *addr_new = next;
 
-  /* Copy relevant flags (af_propagate is a name for the set), and set the
-  data that propagates. */
+  /* Set the 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,
 
   DEBUG(D_route) debug_printf("%s router autogenerated %s\n%s%s%s",
     rblock->name,
@@ -911,4 +938,5 @@ addr->next = *addr_succeed;
 return yield;
 }
 
 return yield;
 }
 
+#endif   /*!MACRO_PREDEF*/
 /* End of routers/redirect.c */
 /* End of routers/redirect.c */
index 2343e3e..9f50957 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -32,13 +32,13 @@ Returns:      nothing
 */
 
 void
 */
 
 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));
 uschar *at = Ustrrchr(addr->address, '@');
   address_item **addr_new)
 {
 address_item *parent = store_get(sizeof(address_item));
 uschar *at = Ustrrchr(addr->address, '@');
-uschar *address = string_sprintf("%.*s@%s", at - addr->address, addr->address,
-  domain);
+uschar *address = string_sprintf("%.*s@%s",
+  (int)(at - addr->address), addr->address, domain);
 
 DEBUG(D_route) debug_printf("domain changed to %s\n", domain);
 
 
 DEBUG(D_route) debug_printf("domain changed to %s\n", domain);
 
@@ -52,11 +52,12 @@ domain cache. Then copy over the propagating fields from the parent. Then set
 up the new fields. */
 
 *addr = address_defaults;
 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;
 
 addr->address = address;
 addr->unique = string_copy(address);
 addr->parent = parent;
+parent->child_count = 1;
 
 addr->next = *addr_new;
 *addr_new = addr;
 
 addr->next = *addr_new;
 *addr_new = addr;
@@ -75,7 +76,7 @@ if (rewrite)
     if (newh != NULL)
       {
       h = newh;
     if (newh != NULL)
       {
       h = newh;
-      header_rewritten = TRUE;
+      f.header_rewritten = TRUE;
       }
     }
   }
       }
     }
   }
index cf5b9cb..6a8ad17 100644 (file)
@@ -31,7 +31,7 @@ rf_expand_data(address_item *addr, uschar *s, int *prc)
 {
 uschar *yield = expand_string(s);
 if (yield != NULL) return yield;
 {
 uschar *yield = expand_string(s);
 if (yield != NULL) return yield;
-if (expand_string_forcedfail)
+if (f.expand_string_forcedfail)
   {
   DEBUG(D_route) debug_printf("forced failure for expansion of \"%s\"\n", s);
   *prc = DECLINE;
   {
   DEBUG(D_route) debug_printf("forced failure for expansion of \"%s\"\n", s);
   *prc = DECLINE;
index 29f37cf..f310d5a 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     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 */
 /* 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_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 *,
 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 *,
 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..858c806 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "../exim.h"
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "../exim.h"
@@ -38,14 +38,14 @@ rf_get_errors_address(address_item *addr, router_instance *rblock,
 {
 uschar *s;
 
 {
 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);
 
 if (s == NULL)
   {
 if (rblock->errors_to == NULL) return OK;
 
 s = expand_string(rblock->errors_to);
 
 if (s == NULL)
   {
-  if (expand_string_forcedfail)
+  if (f.expand_string_forcedfail)
     {
     DEBUG(D_route)
       debug_printf("forced expansion failure - ignoring errors_to\n");
     {
     DEBUG(D_route)
       debug_printf("forced expansion failure - ignoring errors_to\n");
@@ -60,7 +60,7 @@ if (s == NULL)
 
 if (*s == 0)
   {
 
 if (*s == 0)
   {
-  setflag(addr, af_ignore_error);      /* For locally detected errors */
+  addr->prop.ignore_error = TRUE;   /* For locally detected errors */
   *errors_to = US"";                   /* Return path for SMTP */
   return OK;
   }
   *errors_to = US"";                   /* Return path for SMTP */
   return OK;
   }
@@ -81,11 +81,11 @@ if (verify != v_none)
   }
 else
   {
   }
 else
   {
-  BOOL save_address_test_mode = address_test_mode;
+  BOOL save_address_test_mode = f.address_test_mode;
   int save1 = 0;
   int i;
   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)
   address_item *snew = deliver_make_addr(s, FALSE);
 
   if (sender_address != NULL)
@@ -96,7 +96,7 @@ else
 
   for (i = 0, p = address_expansions; *p != NULL;)
     address_expansions_save[i++] = **p++;
 
   for (i = 0, p = address_expansions; *p != NULL;)
     address_expansions_save[i++] = **p++;
-  address_test_mode = FALSE;
+  f.address_test_mode = FALSE;
 
   /* NOTE: the address is verified as a recipient, not a sender. This is
   perhaps confusing. It isn't immediately obvious what to do: we want to have
 
   /* NOTE: the address is verified as a recipient, not a sender. This is
   perhaps confusing. It isn't immediately obvious what to do: we want to have
@@ -118,7 +118,7 @@ else
   DEBUG(D_route|D_verify)
     debug_printf("------ End verifying errors address %s ------\n", s);
 
   DEBUG(D_route|D_verify)
     debug_printf("------ End verifying errors address %s ------\n", s);
 
-  address_test_mode = save_address_test_mode;
+  f.address_test_mode = save_address_test_mode;
   for (i = 0, p = address_expansions; *p != NULL;)
     **p++ = address_expansions_save[i++];
 
   for (i = 0, p = address_expansions; *p != NULL;)
     **p++ = address_expansions_save[i++];
 
index a4a13b0..f08b55a 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "../exim.h"
 /* 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 */
   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)
   {
 
 if (rblock->extra_headers)
   {
-  uschar * list = rblock->extra_headers;
+  const uschar * list = rblock->extra_headers;
   int sep = '\n';
   uschar * s;
   int slen;
   int sep = '\n';
   uschar * s;
   int slen;
@@ -44,10 +44,11 @@ if (rblock->extra_headers)
   while ((s = string_nextinlist(&list, &sep, NULL, 0)))
     if (!(s = expand_string(s)))
       {
   while ((s = string_nextinlist(&list, &sep, NULL, 0)))
     if (!(s = expand_string(s)))
       {
-      if (!expand_string_forcedfail)
+      if (!f.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;
        }
       }
        return DEFER;
        }
       }
@@ -82,31 +83,40 @@ if (rblock->extra_headers)
   }
 
 /* Default is to retain existing removes */
   }
 
 /* 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)
   {
 
 /* 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;
   int sep = ':';
   uschar * s;
-  uschar buffer[128];
+  gstring * g = NULL;
 
 
-  while ((s = string_nextinlist(&list, &sep, buffer, sizeof(buffer))))
+  if (*remove_headers)
+    g = string_cat(NULL, *remove_headers);
+
+  while ((s = string_nextinlist(&list, &sep, NULL, 0)))
     if (!(s = expand_string(s)))
       {
     if (!(s = expand_string(s)))
       {
-      if (!expand_string_forcedfail)
+      if (!f.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;
        }
       }
     else if (*s)
        return DEFER;
        }
       }
     else if (*s)
-      *remove_headers = string_append_listele(*remove_headers, ':', s);
+      g = string_append_listele(g, ':', s);
+
+  if (g)
+    *remove_headers = g->s;
   }
 
 return OK;
 }
 
   }
 
 return OK;
 }
 
+/* vi: aw ai sw=2
+*/
 /* End of rf_get_munge_headers.c */
 /* End of rf_get_munge_headers.c */
index 0eae31e..0527566 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     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. */
 
 
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -35,7 +35,8 @@ Arguments:
   rblock               the router block
   addr                 the address being routed
   ignore_target_hosts  list of hosts to ignore
   rblock               the router block
   addr                 the address being routed
   ignore_target_hosts  list of hosts to ignore
-  lookup_type          lk_default or lk_byname or lk_bydns
+  lookup_type          LK_DEFAULT or LK_BYNAME or LK_BYDNS,
+                      plus LK_IPV4_{ONLY,PREFER}
   hff_code             what to do for host find failed
   addr_new             passed to rf_self_action for self=reroute
 
   hff_code             what to do for host find failed
   addr_new             passed to rf_self_action for self=reroute
 
@@ -61,14 +62,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. */
 
 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;
 
   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);
 
   DEBUG(D_route|D_host_lookup)
     debug_printf("finding IP address for %s\n", h->name);
@@ -78,32 +78,45 @@ for (h = addr->host_list; h != NULL; h = next_h)
 
   port = host_item_get_port(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
   /* 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)
     {
 
   len = Ustrlen(h->name);
   if (len > 3 && strcmpic(h->name + len - 3, US"/mx") == 0)
     {
+    int whichrrs = lookup_type & LK_IPV4_ONLY
+      ? HOST_FIND_BY_MX | HOST_FIND_IPV4_ONLY
+      : lookup_type & LK_IPV4_PREFER
+      ? HOST_FIND_BY_MX | HOST_FIND_IPV4_FIRST
+      : HOST_FIND_BY_MX;
+
     DEBUG(D_route|D_host_lookup)
       debug_printf("doing DNS MX lookup for %s\n", h->name);
 
     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,
     h->name = string_copyn(h->name, len - 3);
     rc = host_find_bydns(h,
         ignore_target_hosts,
-        HOST_FIND_BY_MX,                /* look only for MX records */
-        NULL,                           /* SRV service not relevant */
-        NULL,                           /* failing srv domains not relevant */
-        NULL,                           /* no special mx failing domains */
-       NULL,                           /* no dnssec request XXX ? */
-       NULL,                           /* no dnssec require XXX ? */
-        NULL,                           /* fully_qualified_name */
-        NULL);                          /* indicate local host removed */
+        whichrrs,                      /* look only for MX records */
+        NULL,                          /* SRV service not relevant */
+        NULL,                          /* failing srv domains not relevant */
+        NULL,                          /* no special mx failing domains */
+        &rblock->dnssec,               /* dnssec request/require */
+        NULL,                          /* fully_qualified_name */
+        NULL);                         /* indicate local host removed */
     }
 
   /* If explicitly configured to look up by name, or if the "host name" is
   actually an IP address, do a byname lookup. */
 
     }
 
   /* If explicitly configured to look up by name, or if the "host name" is
   actually an IP address, do a byname lookup. */
 
-  else if (lookup_type == lk_byname || string_is_ip_address(h->name, NULL) != 0)
+  else if (lookup_type & LK_BYNAME || string_is_ip_address(h->name, NULL) != 0)
     {
     DEBUG(D_route|D_host_lookup) debug_printf("calling host_find_byname\n");
     rc = host_find_byname(h, ignore_target_hosts, HOST_FIND_QUALIFY_SINGLE,
     {
     DEBUG(D_route|D_host_lookup) debug_printf("calling host_find_byname\n");
     rc = host_find_byname(h, ignore_target_hosts, HOST_FIND_QUALIFY_SINGLE,
@@ -117,29 +130,41 @@ for (h = addr->host_list; h != NULL; h = next_h)
   else
     {
     BOOL removed;
   else
     {
     BOOL removed;
+    int whichrrs = lookup_type & LK_IPV4_ONLY
+      ? HOST_FIND_BY_A
+      : lookup_type & LK_IPV4_PREFER
+      ? HOST_FIND_BY_A | HOST_FIND_BY_AAAA | HOST_FIND_IPV4_FIRST
+      : HOST_FIND_BY_A | HOST_FIND_BY_AAAA;
+
     DEBUG(D_route|D_host_lookup) debug_printf("doing DNS lookup\n");
     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)
+    switch (rc = host_find_bydns(h, ignore_target_hosts, whichrrs, NULL,
+       NULL, NULL,
+       &rblock->dnssec,                        /* domains for request/require */
+       &canonical_name, &removed))
       {
       {
-      if (removed) setflag(addr, af_local_host_removed);
-      }
-    else if (rc == 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);
-        }
+      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;
       }
     }
 
   /* Temporary failure defers, unless pass_on_timeout is set */
 
       }
     }
 
   /* Temporary failure defers, unless pass_on_timeout is set */
 
+  if (rc == HOST_FIND_SECURITY)
+    {
+    addr->message = string_sprintf("host lookup for %s done insecurely" , h->name);
+    addr->basic_errno = ERRNO_DNSDEFER;
+    return DEFER;
+    }
   if (rc == HOST_FIND_AGAIN)
     {
     if (rblock->pass_on_timeout)
   if (rc == HOST_FIND_AGAIN)
     {
     if (rblock->pass_on_timeout)
@@ -168,10 +193,11 @@ for (h = addr->host_list; h != NULL; h = next_h)
     if (hff_code == hff_pass) return PASS;
     if (hff_code == hff_decline) return DECLINE;
 
     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,
     addr->message =
       string_sprintf("lookup of host \"%s\" failed in %s router%s",
         h->name, rblock->name,
-        host_find_failed_syntax? ": syntax error in name" : "");
+        f.host_find_failed_syntax? ": syntax error in name" : "");
 
     if (hff_code == hff_defer) return DEFER;
     if (hff_code == hff_fail) return FAIL;
 
     if (hff_code == hff_defer) return DEFER;
     if (hff_code == hff_fail) return FAIL;
@@ -180,7 +206,8 @@ for (h = addr->host_list; h != NULL; h = next_h)
     return DEFER;
     }
 
     return DEFER;
     }
 
-  /* Deal with a port setting */
+  /* Deal with the settings that were previously cleared:
+  port, mx and sort_key. */
 
   if (port != PORT_NONE)
     {
 
   if (port != PORT_NONE)
     {
@@ -188,13 +215,23 @@ for (h = addr->host_list; h != NULL; h = next_h)
     for (hh = h; hh != next_h; hh = hh->next) hh->port = port;
     }
 
     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)
     {
   /* 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)
         {
       {
       DEBUG(D_route)
         {
index 06cdb6c..99de7b0 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "../exim.h"
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "../exim.h"
@@ -36,12 +36,12 @@ BOOL
 rf_queue_add(address_item *addr, address_item **paddr_local,
   address_item **paddr_remote, router_instance *rblock, struct passwd *pw)
 {
 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 */
 
 
 /* Handle a local transport */
 
-if (addr->transport != NULL && addr->transport->info->local)
+if (addr->transport && addr->transport->info->local)
   {
   ugid_block ugid;
 
   {
   ugid_block ugid;
 
@@ -50,11 +50,13 @@ if (addr->transport != NULL && addr->transport->info->local)
   When getting the home directory out of the password information, set the
   flag that prevents expansion later. */
 
   When getting the home directory out of the password information, set the
   flag that prevents expansion later. */
 
-  if (pw != NULL)
+  if (pw)
     {
     addr->uid = pw->pw_uid;
     addr->gid = pw->pw_gid;
     {
     addr->uid = pw->pw_uid;
     addr->gid = pw->pw_gid;
-    setflag(addr, af_uid_set|af_gid_set|af_home_expanded);
+    setflag(addr, af_uid_set);
+    setflag(addr, af_gid_set);
+    setflag(addr, af_home_expanded);
     addr->home_dir = string_copy(US pw->pw_dir);
     }
 
     addr->home_dir = string_copy(US pw->pw_dir);
     }
 
@@ -65,12 +67,12 @@ if (addr->transport != NULL && addr->transport->info->local)
   otherwise use the expanded value of router_home_directory. The flag also
   tells the transport not to re-expand it. */
 
   otherwise use the expanded value of router_home_directory. The flag also
   tells the transport not to re-expand it. */
 
-  if (rblock->home_directory != NULL)
+  if (rblock->home_directory)
     {
     addr->home_dir = rblock->home_directory;
     clearflag(addr, af_home_expanded);
     }
     {
     addr->home_dir = rblock->home_directory;
     clearflag(addr, af_home_expanded);
     }
-  else if (addr->home_dir == NULL && testflag(addr, af_home_expanded))
+  else if (!addr->home_dir && testflag(addr, af_home_expanded))
     addr->home_dir = deliver_home;
 
   addr->current_dir = rblock->current_directory;
     addr->home_dir = deliver_home;
 
   addr->current_dir = rblock->current_directory;
@@ -95,9 +97,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,
   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;
   }
 
 return TRUE;
index 7434ddd..9a4dc3c 100644 (file)
@@ -60,79 +60,61 @@ int
 rf_self_action(address_item *addr, host_item *host, int code, BOOL rewrite,
   uschar *new, address_item **addr_new)
 {
 rf_self_action(address_item *addr, host_item *host, int code, BOOL rewrite,
   uschar *new, address_item **addr_new)
 {
-uschar *msg = (host->mx >= 0)?
-  US"lowest numbered MX record points to local host" :
-  US"remote host address is the local host";
+uschar * msg = host->mx >= 0
+  ? US"lowest numbered MX record points to local host"
+  US"remote host address is the local host";
 
 switch (code)
   {
   case self_freeze:
 
 
 switch (code)
   {
   case self_freeze:
 
-  /* If there is no message id, this is happening during an address
-  verification, so give information about the address that is being verified,
-  and where it has come from. Otherwise, during message delivery, the normal
-  logging for the address will be sufficient. */
-
-  if (message_id[0] == 0)
-    {
-    if (sender_fullhost == NULL)
-      {
-      log_write(0, LOG_MAIN, "%s: %s (while routing <%s>)", msg,
-        addr->domain, addr->address);
-      }
+    /* If there is no message id, this is happening during an address
+    verification, so give information about the address that is being verified,
+    and where it has come from. Otherwise, during message delivery, the normal
+    logging for the address will be sufficient. */
+
+    if (message_id[0] == 0)
+      if (sender_fullhost)
+       log_write(0, LOG_MAIN, "%s: %s (while verifying <%s> from host %s)",
+         msg, addr->domain, addr->address, sender_fullhost);
+      else
+       log_write(0, LOG_MAIN, "%s: %s (while routing <%s>)", msg,
+         addr->domain, addr->address);
     else
     else
-      {
-      log_write(0, LOG_MAIN, "%s: %s (while verifying <%s> from host %s)",
-        msg, addr->domain, addr->address, sender_fullhost);
-      }
-    }
-  else
-    log_write(0, LOG_MAIN, "%s: %s", msg, addr->domain);
-
-  addr->message = msg;
-  addr->special_action = SPECIAL_FREEZE;
-  return DEFER;
+      log_write(0, LOG_MAIN, "%s: %s", msg, addr->domain);
+
+    addr->message = msg;
+    addr->special_action = SPECIAL_FREEZE;
+    return DEFER;
 
   case self_defer:
 
   case self_defer:
-  addr->message = msg;
-  return DEFER;
+    addr->message = msg;
+    return DEFER;
 
   case self_reroute:
 
   case self_reroute:
-  DEBUG(D_route)
-    {
-    debug_printf("%s: %s", msg, addr->domain);
-    debug_printf(": domain changed to %s\n", new);
-    }
-  rf_change_domain(addr, new, rewrite, addr_new);
-  return REROUTED;
+    DEBUG(D_route)
+      debug_printf("%s: %s: domain changed to %s\n", msg, addr->domain, new);
+    rf_change_domain(addr, new, rewrite, addr_new);
+    return REROUTED;
 
   case self_send:
 
   case self_send:
-  DEBUG(D_route)
-    {
-    debug_printf("%s: %s", msg, addr->domain);
-    debug_printf(": configured to try delivery anyway\n");
-    }
-  return OK;
+    DEBUG(D_route)
+      debug_printf("%s: %s: configured to try delivery anyway\n", msg, addr->domain);
+    return OK;
 
   case self_pass:    /* This is soft failure; pass to next router */
 
   case self_pass:    /* This is soft failure; pass to next router */
-  DEBUG(D_route)
-    {
-    debug_printf("%s: %s", msg, addr->domain);
-    debug_printf(": passed to next router (self = pass)\n");
-    }
-  addr->message = msg;
-  addr->self_hostname = string_copy(host->name);
-  return PASS;
+    DEBUG(D_route)
+      debug_printf("%s: %s: passed to next router (self = pass)\n", msg, addr->domain);
+    addr->message = msg;
+    addr->self_hostname = string_copy(host->name);
+    return PASS;
 
   case self_fail:
 
   case self_fail:
-  DEBUG(D_route)
-    {
-    debug_printf("%s: %s", msg, addr->domain);
-    debug_printf(": address failed (self = fail)\n");
-    }
-  addr->message = msg;
-  setflag(addr, af_pass_message);
-  return FAIL;
+    DEBUG(D_route)
+      debug_printf("%s: %s: address failed (self = fail)\n", msg, addr->domain);
+    addr->message = msg;
+    setflag(addr, af_pass_message);
+    return FAIL;
   }
 
 return DEFER;   /* paranoia */
   }
 
 return DEFER;   /* paranoia */
index 15db52a..e5beaf3 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     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
 /* 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
 */
 
 int
-search_findtype(uschar *name, int len)
+search_findtype(const uschar *name, int len)
 {
 int bot = 0;
 int top = lookup_list_count;
 {
 int bot = 0;
 int top = lookup_list_count;
@@ -121,12 +121,12 @@ Returns:     +ve => valid lookup name; value is offset in lookup_list
 */
 
 int
 */
 
 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;
   int *afflen, int *starflags)
 {
 int len, stype;
 int pv = -1;
-uschar *ss = name;
+const uschar *ss = name;
 
 *starflags = 0;
 *ptypeaff = NULL;
 
 *starflags = 0;
 *ptypeaff = NULL;
@@ -464,9 +464,10 @@ Returns:       a pointer to a dynamic string containing the answer,
 static uschar *
 internal_search_find(void *handle, uschar *filename, uschar *keystring)
 {
 static uschar *
 internal_search_find(void *handle, uschar *filename, uschar *keystring)
 {
-tree_node *t = (tree_node *)handle;
-search_cache *c = (search_cache *)(t->data.ptr);
-uschar *data = NULL;
+tree_node * t = (tree_node *)handle;
+search_cache * c = (search_cache *)(t->data.ptr);
+expiring_data * e = NULL;      /* compiler quietening */
+uschar * data = NULL;
 int search_type = t->name[0] - '0';
 int old_pool = store_pool;
 
 int search_type = t->name[0] - '0';
 int old_pool = store_pool;
 
@@ -474,7 +475,7 @@ int old_pool = store_pool;
 the callers don't have to test for NULL, set an empty string. */
 
 search_error_message = US"";
 the callers don't have to test for NULL, set an empty string. */
 
 search_error_message = US"";
-search_find_defer = FALSE;
+f.search_find_defer = FALSE;
 
 DEBUG(D_lookup) debug_printf("internal_search_find: file=\"%s\"\n  "
   "type=%s key=\"%s\"\n", filename,
 
 DEBUG(D_lookup) debug_printf("internal_search_find: file=\"%s\"\n  "
   "type=%s key=\"%s\"\n", filename,
@@ -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. */
 
 /* 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)
     {
   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
     }
 
   /* 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)
 
   if (lookup_list[search_type]->find(c->handle, filename, keystring, keylength,
       &data, &search_error_message, &do_cache) == DEFER)
-    {
-    search_find_defer = TRUE;
-    }
+    f.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
 
   /* 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;
   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
     }
 
   /* 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)
   {
 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 (f.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 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 *
 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;
 {
 tree_node *t = (tree_node *)handle;
 BOOL set_null_wild = FALSE;
@@ -664,7 +669,7 @@ DEBUG(D_lookup)
 entry but could have been partial, flag to set up variables. */
 
 yield = internal_search_find(handle, filename, keystring);
 entry but could have been partial, flag to set up variables. */
 
 yield = internal_search_find(handle, filename, keystring);
-if (search_find_defer) return NULL;
+if (f.search_find_defer) return NULL;
 if (yield != NULL) { if (partial >= 0) set_null_wild = TRUE; }
 
 /* Not matched a complete entry; handle partial lookups, but only if the full
 if (yield != NULL) { if (partial >= 0) set_null_wild = TRUE; }
 
 /* Not matched a complete entry; handle partial lookups, but only if the full
@@ -687,7 +692,7 @@ else if (partial >= 0)
     Ustrcpy(keystring2 + affixlen, keystring);
     DEBUG(D_lookup) debug_printf("trying partial match %s\n", keystring2);
     yield = internal_search_find(handle, filename, keystring2);
     Ustrcpy(keystring2 + affixlen, keystring);
     DEBUG(D_lookup) debug_printf("trying partial match %s\n", keystring2);
     yield = internal_search_find(handle, filename, keystring2);
-    if (search_find_defer) return NULL;
+    if (f.search_find_defer) return NULL;
     }
 
   /* The key in its entirety did not match a wild entry; try chopping off
     }
 
   /* The key in its entirety did not match a wild entry; try chopping off
@@ -725,7 +730,7 @@ else if (partial >= 0)
 
       DEBUG(D_lookup) debug_printf("trying partial match %s\n", keystring3);
       yield = internal_search_find(handle, filename, keystring3);
 
       DEBUG(D_lookup) debug_printf("trying partial match %s\n", keystring3);
       yield = internal_search_find(handle, filename, keystring3);
-      if (search_find_defer) return NULL;
+      if (f.search_find_defer) return NULL;
       if (yield != NULL)
         {
         /* First variable is the wild part; second is the fixed part. Take care
       if (yield != NULL)
         {
         /* First variable is the wild part; second is the fixed part. Take care
@@ -752,7 +757,7 @@ else if (partial >= 0)
   }
 
 /* If nothing has been matched, but the option to look for "*@" is set, try
   }
 
 /* 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)
 is set to the string to the left of the @. */
 
 if (yield == NULL && (starflags & SEARCH_STARAT) != 0)
@@ -767,7 +772,7 @@ if (yield == NULL && (starflags & SEARCH_STARAT) != 0)
     DEBUG(D_lookup) debug_printf("trying default match %s\n", atat);
     yield = internal_search_find(handle, filename, atat);
     *atat = savechar;
     DEBUG(D_lookup) debug_printf("trying default match %s\n", atat);
     yield = internal_search_find(handle, filename, atat);
     *atat = savechar;
-    if (search_find_defer) return NULL;
+    if (f.search_find_defer) return NULL;
 
     if (yield != NULL && expand_setup != NULL && *expand_setup >= 0)
       {
 
     if (yield != NULL && expand_setup != NULL && *expand_setup >= 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..6140878
--- /dev/null
@@ -0,0 +1,46 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) Jeremy Harris 2018 */
+/* 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             /*MMMM*/
+#   endif
+#  else
+#   define SHA_GCRYPT
+#  endif
+
+# else
+#  define SHA_OPENSSL
+#  include <openssl/ssl.h>
+#  if (OPENSSL_VERSION_NUMBER >= 0x10101000L) && !defined(LIBRESSL_VERSION_NUMBER)
+#   define EXIM_HAVE_SHA3
+#  endif
+# endif
+
+#else
+# define SHA_NATIVE
+#endif
+
index 1303646..f5329da 100644 (file)
@@ -2,8 +2,10 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     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 - 2018
+ * See the file NOTICE for conditions of use and distribution.
+ */
 
 /* This code was contributed by Michael Haardt. */
 
 
 /* This code was contributed by Michael Haardt. */
 
@@ -19,7 +21,7 @@
 #include "exim.h"
 
 #if HAVE_ICONV
 #include "exim.h"
 
 #if HAVE_ICONV
-#include <iconv.h>
+# include <iconv.h>
 #endif
 
 /* Define this for RFC compliant \r\n end-of-line terminators.      */
 #endif
 
 /* Define this for RFC compliant \r\n end-of-line terminators.      */
@@ -151,8 +153,10 @@ static uschar str_cc_c[]="Cc";
 static const struct String str_cc={ str_cc_c, 2 };
 static uschar str_bcc_c[]="Bcc";
 static const struct String str_bcc={ str_bcc_c, 3 };
 static const struct String str_cc={ str_cc_c, 2 };
 static uschar str_bcc_c[]="Bcc";
 static const struct String str_bcc={ str_bcc_c, 3 };
+#ifdef ENVELOPE_AUTH
 static uschar str_auth_c[]="auth";
 static const struct String str_auth={ str_auth_c, 4 };
 static uschar str_auth_c[]="auth";
 static const struct String str_auth={ str_auth_c, 4 };
+#endif
 static uschar str_sender_c[]="Sender";
 static const struct String str_sender={ str_sender_c, 6 };
 static uschar str_resent_from_c[]="Resent-From";
 static uschar str_sender_c[]="Sender";
 static const struct String str_sender={ str_sender_c, 6 };
 static uschar str_resent_from_c[]="Resent-From";
@@ -232,6 +236,9 @@ uschar *new = NULL;
 uschar ch;
 size_t line;
 
 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;
 for (pass=0; pass<=1; ++pass)
   {
   line=0;
@@ -245,55 +252,47 @@ for (pass=0; pass<=1; ++pass)
   for (start=src->character,end=start+src->length; start<end; ++start)
     {
     ch=*start;
   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
         {
       {
       if (pass==0)
         dst->length+=2;
       else
         {
-        *new++='=';
+        *new++='=';    /* line split */
         *new++='\n';
         }
       line=0;
       }
         *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
       {
       if (pass==0)
         ++dst->length;
       else
-        *new++=*start;
+        *new++=*start; /* copy char */
       ++line;
       }
       ++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)
       {
       if (pass==0)
-        {
         ++dst->length;
         ++dst->length;
-        line=0;
-        }
       else
       else
-        *new++='\n';
-        line=0;
-      ++start;
+        *new++='\n';                                   /* NL */
+      line=0;
+      ++start; /* consume extra input char */
       }
     else
       {
       if (pass==0)
         dst->length+=3;
       else
       }
     else
       {
       if (pass==0)
         dst->length+=3;
       else
-        {
-        sprintf(CS new,"=%02X",ch);
-        new+=3;
+        {              /* encoded char */
+        new += sprintf(CS new,"=%02X",ch);
         }
       line+=3;
       }
         }
       line+=3;
       }
@@ -412,11 +411,14 @@ Returns
  -1           syntax error
 */
 
  -1           syntax error
 */
 
-static int parse_mailto_uri(struct Sieve *filter, const uschar *uri, string_item **recipient, struct String *header, struct String *subject, struct String *body)
+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;
 {
 const uschar *start;
-struct String to,hname,hvalue;
-int capacity;
+struct String to, hname;
+struct String hvalue = {.character = NULL, .length = 0};
 string_item *new;
 
 if (Ustrncmp(uri,"mailto:",7))
 string_item *new;
 
 if (Ustrncmp(uri,"mailto:",7))
@@ -424,6 +426,7 @@ if (Ustrncmp(uri,"mailto:",7))
   filter->errmsg=US "Unknown URI scheme";
   return 0;
   }
   filter->errmsg=US "Unknown URI scheme";
   return 0;
   }
+
 uri+=7;
 if (*uri && *uri!='?')
   for (;;)
 uri+=7;
 if (*uri && *uri!='?')
   for (;;)
@@ -432,22 +435,21 @@ if (*uri && *uri!='?')
     for (start=uri; *uri && *uri!='?' && (*uri!='%' || *(uri+1)!='2' || tolower(*(uri+2))!='c'); ++uri);
     if (uri>start)
       {
     for (start=uri; *uri && *uri!='?' && (*uri!='%' || *(uri+1)!='2' || tolower(*(uri+2))!='c'); ++uri);
     if (uri>start)
       {
-      capacity=0;
-      to.character=(uschar*)0;
-      to.length=0;
-      to.character=string_cat(to.character,&capacity,&to.length,start,uri-start);
-      to.character[to.length]='\0';
+      gstring * g = string_catn(NULL, start, uri-start);
+
+      to.character = string_from_gstring(g);
+      to.length = g->ptr;
       if (uri_decode(&to)==-1)
         {
         filter->errmsg=US"Invalid URI encoding";
         return -1;
         }
       if (uri_decode(&to)==-1)
         {
         filter->errmsg=US"Invalid URI encoding";
         return -1;
         }
-        new=store_get(sizeof(string_item));
-        new->text=store_get(to.length+1);
-        if (to.length) memcpy(new->text,to.character,to.length);
-        new->text[to.length]='\0';
-        new->next=*recipient;
-        *recipient=new;
+      new=store_get(sizeof(string_item));
+      new->text=store_get(to.length+1);
+      if (to.length) memcpy(new->text,to.character,to.length);
+      new->text[to.length]='\0';
+      new->next=*recipient;
+      *recipient=new;
       }
     else
       {
       }
     else
       {
@@ -466,11 +468,10 @@ if (*uri=='?')
     for (start=uri; *uri && (isalnum(*uri) || strchr("$-_.+!*'(),%",*uri)); ++uri);
     if (uri>start)
       {
     for (start=uri; *uri && (isalnum(*uri) || strchr("$-_.+!*'(),%",*uri)); ++uri);
     if (uri>start)
       {
-      capacity=0;
-      hname.character=(uschar*)0;
-      hname.length=0;
-      hname.character=string_cat(hname.character,&capacity,&hname.length,start,uri-start);
-      hname.character[hname.length]='\0';
+      gstring * g = string_catn(NULL, start, uri-start);
+
+      hname.character = string_from_gstring(g);
+      hname.length = g->ptr;
       if (uri_decode(&hname)==-1)
         {
         filter->errmsg=US"Invalid URI encoding";
       if (uri_decode(&hname)==-1)
         {
         filter->errmsg=US"Invalid URI encoding";
@@ -489,11 +490,10 @@ if (*uri=='?')
     for (start=uri; *uri && (isalnum(*uri) || strchr("$-_.+!*'(),%",*uri)); ++uri);
     if (uri>start)
       {
     for (start=uri; *uri && (isalnum(*uri) || strchr("$-_.+!*'(),%",*uri)); ++uri);
     if (uri>start)
       {
-      capacity=0;
-      hvalue.character=(uschar*)0;
-      hvalue.length=0;
-      hvalue.character=string_cat(hvalue.character,&capacity,&hvalue.length,start,uri-start);
-      hvalue.character[hvalue.length]='\0';
+      gstring * g = string_catn(NULL, start, uri-start);
+
+      hname.character = string_from_gstring(g);
+      hname.length = g->ptr;
       if (uri_decode(&hvalue)==-1)
         {
         filter->errmsg=US"Invalid URI encoding";
       if (uri_decode(&hvalue)==-1)
         {
         filter->errmsg=US"Invalid URI encoding";
@@ -529,13 +529,18 @@ if (*uri=='?')
       for (i=ignore; i<end && !eq_asciicase(&hname,i,0); ++i);
       if (i==end)
         {
       for (i=ignore; i<end && !eq_asciicase(&hname,i,0); ++i);
       if (i==end)
         {
-        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[header->length]='\0';
+       gstring * g;
+
+        if (header->length==-1) header->length = 0;
+
+       g = string_catn(NULL, header->character, header->length);
+        g = string_catn(g, hname.character, hname.length);
+        g = string_catn(g, CUS ": ", 2);
+        g = string_catn(g, hvalue.character, hvalue.length);
+        g = string_catn(g, CUS "\n", 1);
+
+       header->character = string_from_gstring(g);
+       header->length = g->ptr;
         }
       }
     if (*uri=='&') ++uri;
         }
       }
     if (*uri=='&') ++uri;
@@ -993,10 +998,10 @@ Arguments:
 Returns:      quoted string
 */
 
 Returns:      quoted string
 */
 
-static const uschar *quote(const struct String *header)
+static const uschar *
+quote(const struct String *header)
 {
 {
-uschar *quoted=NULL;
-int size=0,ptr=0;
+gstring * quoted = NULL;
 size_t l;
 const uschar *h;
 
 size_t l;
 const uschar *h;
 
@@ -1007,26 +1012,20 @@ while (l)
   switch (*h)
     {
     case '\0':
   switch (*h)
     {
     case '\0':
-      {
-      quoted=string_cat(quoted,&size,&ptr,CUS "\\0",2);
+      quoted = string_catn(quoted, CUS "\\0", 2);
       break;
       break;
-      }
     case '$':
     case '{':
     case '}':
     case '$':
     case '{':
     case '}':
-      {
-      quoted=string_cat(quoted,&size,&ptr,CUS "\\",1);
-      }
+      quoted = string_catn(quoted, CUS "\\", 1);
     default:
     default:
-      {
-      quoted=string_cat(quoted,&size,&ptr,h,1);
-      }
+      quoted = string_catn(quoted, h, 1);
     }
   ++h;
   --l;
   }
     }
   ++h;
   --l;
   }
-quoted=string_cat(quoted,&size,&ptr,CUS "",1);
-return quoted;
+quoted = string_catn(quoted, CUS "", 1);
+return string_from_gstring(quoted);
 }
 
 
 }
 
 
@@ -1046,33 +1045,36 @@ Arguments:
 Returns:      nothing
 */
 
 Returns:      nothing
 */
 
-static void add_addr(address_item **generated, uschar *addr, int file, int maxage, int maxmessages, int maxstorage)
+static void
+add_addr(address_item **generated, uschar *addr, int file, int maxage, int maxmessages, int maxstorage)
 {
 address_item *new_addr;
 
 for (new_addr=*generated; new_addr; new_addr=new_addr->next)
 {
 address_item *new_addr;
 
 for (new_addr=*generated; new_addr; new_addr=new_addr->next)
-  {
-  if (Ustrcmp(new_addr->address,addr)==0 && (file ? testflag(new_addr, af_pfr|af_file) : 1))
+  if (  Ustrcmp(new_addr->address,addr) == 0
+     && (  !file
+       || testflag(new_addr, af_pfr)
+       || testflag(new_addr, af_file)
+       )
+     )
     {
     if ((filter_test != FTEST_NONE && debug_selector != 0) || (debug_selector & D_filter) != 0)
     {
     if ((filter_test != FTEST_NONE && debug_selector != 0) || (debug_selector & D_filter) != 0)
-      {
       debug_printf("Repeated %s `%s' ignored.\n",file ? "fileinto" : "redirect", addr);
       debug_printf("Repeated %s `%s' ignored.\n",file ? "fileinto" : "redirect", addr);
-      }
+
     return;
     }
     return;
     }
-  }
 
 if ((filter_test != FTEST_NONE && debug_selector != 0) || (debug_selector & D_filter) != 0)
 
 if ((filter_test != FTEST_NONE && debug_selector != 0) || (debug_selector & D_filter) != 0)
-  {
   debug_printf("%s `%s'\n",file ? "fileinto" : "redirect", addr);
   debug_printf("%s `%s'\n",file ? "fileinto" : "redirect", addr);
-  }
-new_addr=deliver_make_addr(addr,TRUE);
+
+new_addr = deliver_make_addr(addr,TRUE);
 if (file)
   {
 if (file)
   {
-  setflag(new_addr, af_pfr|af_file);
+  setflag(new_addr, af_pfr);
+  setflag(new_addr, af_file);
   new_addr->mode = 0;
   }
   new_addr->mode = 0;
   }
-new_addr->p.errors_address = NULL;
+new_addr->prop.errors_address = NULL;
 new_addr->next = *generated;
 *generated = new_addr;
 }
 new_addr->next = *generated;
 *generated = new_addr;
 }
@@ -1472,12 +1474,14 @@ Returns:      1                success
               0                identifier not matched
 */
 
               0                identifier not matched
 */
 
-static int parse_string(struct Sieve *filter, struct String *data)
+static int
+parse_string(struct Sieve *filter, struct String *data)
 {
 {
-int dataCapacity=0;
+gstring * g = NULL;
+
+data->length = 0;
+data->character = NULL;
 
 
-data->length=0;
-data->character=(uschar*)0;
 if (*filter->pc=='"') /* quoted string */
   {
   ++filter->pc;
 if (*filter->pc=='"') /* quoted string */
   {
   ++filter->pc;
@@ -1485,11 +1489,17 @@ if (*filter->pc=='"') /* quoted string */
     {
     if (*filter->pc=='"') /* end of string */
       {
     {
     if (*filter->pc=='"') /* end of string */
       {
-      int foo=data->length;
-
       ++filter->pc;
       ++filter->pc;
+
+      if (g)
+       {
+       data->character = string_from_gstring(g);
+       data->length = g->ptr;
+       }
+      else
+       data->character = US"\0";
       /* that way, there will be at least one character allocated */
       /* that way, there will be at least one character allocated */
-      data->character=string_cat(data->character,&dataCapacity,&foo,CUS "",1);
+
 #ifdef ENCODED_CHARACTER
       if (filter->require_encoded_character
           && string_decode(filter,data)==-1)
 #ifdef ENCODED_CHARACTER
       if (filter->require_encoded_character
           && string_decode(filter,data)==-1)
@@ -1499,7 +1509,7 @@ if (*filter->pc=='"') /* quoted string */
       }
     else if (*filter->pc=='\\' && *(filter->pc+1)) /* quoted character */
       {
       }
     else if (*filter->pc=='\\' && *(filter->pc+1)) /* quoted character */
       {
-      data->character=string_cat(data->character,&dataCapacity,&data->length,filter->pc+1,1);
+      g = string_catn(g, filter->pc+1, 1);
       filter->pc+=2;
       }
     else /* regular character */
       filter->pc+=2;
       }
     else /* regular character */
@@ -1509,11 +1519,11 @@ if (*filter->pc=='"') /* quoted string */
 #else
       if (*filter->pc=='\n')
         {
 #else
       if (*filter->pc=='\n')
         {
-        data->character=string_cat(data->character,&dataCapacity,&data->length,US"\r",1);
+        g = string_catn(g, US"\r", 1);
         ++filter->line;
         }
 #endif
         ++filter->line;
         }
 #endif
-      data->character=string_cat(data->character,&dataCapacity,&data->length,filter->pc,1);
+      g = string_catn(g, filter->pc, 1);
       filter->pc++;
       }
     }
       filter->pc++;
       }
     }
@@ -1555,7 +1565,7 @@ else if (Ustrncmp(filter->pc,CUS "text:",5)==0) /* multiline string */
     if (*filter->pc=='\n') /* end of line */
 #endif
       {
     if (*filter->pc=='\n') /* end of line */
 #endif
       {
-      data->character=string_cat(data->character,&dataCapacity,&data->length,CUS "\r\n",2);
+      g = string_catn(g, CUS "\r\n", 2);
 #ifdef RFC_EOL
       filter->pc+=2;
 #else
 #ifdef RFC_EOL
       filter->pc+=2;
 #else
@@ -1568,10 +1578,15 @@ else if (Ustrncmp(filter->pc,CUS "text:",5)==0) /* multiline string */
       if (*filter->pc=='.' && *(filter->pc+1)=='\n') /* end of string */
 #endif
         {
       if (*filter->pc=='.' && *(filter->pc+1)=='\n') /* end of string */
 #endif
         {
-        int foo=data->length;
+       if (g)
+         {
+         data->character = string_from_gstring(g);
+         data->length = g->ptr;
+         }
+       else
+         data->character = US"\0";
+       /* that way, there will be at least one character allocated */
 
 
-        /* that way, there will be at least one character allocated */
-        data->character=string_cat(data->character,&dataCapacity,&foo,CUS "",1);
 #ifdef RFC_EOL
         filter->pc+=3;
 #else
 #ifdef RFC_EOL
         filter->pc+=3;
 #else
@@ -1587,13 +1602,13 @@ else if (Ustrncmp(filter->pc,CUS "text:",5)==0) /* multiline string */
         }
       else if (*filter->pc=='.' && *(filter->pc+1)=='.') /* remove dot stuffing */
         {
         }
       else if (*filter->pc=='.' && *(filter->pc+1)=='.') /* remove dot stuffing */
         {
-        data->character=string_cat(data->character,&dataCapacity,&data->length,CUS ".",1);
+        g = string_catn(g, CUS ".", 1);
         filter->pc+=2;
         }
       }
     else /* regular character */
       {
         filter->pc+=2;
         }
       }
     else /* regular character */
       {
-      data->character=string_cat(data->character,&dataCapacity,&data->length,filter->pc,1);
+      g = string_catn(g, filter->pc, 1);
       filter->pc++;
       }
     }
       filter->pc++;
       }
     }
@@ -1706,12 +1721,13 @@ Returns:      1                success
               -1               no string list found
 */
 
               -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;
 {
 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 */
 int m;
 
 if (*filter->pc=='[') /* string list */
@@ -1720,20 +1736,17 @@ if (*filter->pc=='[') /* string list */
   for (;;)
     {
     if (parse_white(filter)==-1) goto error;
   for (;;)
     {
     if (parse_white(filter)==-1) goto error;
-    if ((dataLength+1)>=dataCapacity) /* increase buffer */
+    if (dataLength+1 >= dataCapacity) /* increase buffer */
       {
       struct String *new;
       {
       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);
       if (d) memcpy(new,d,sizeof(struct String)*dataLength);
-      d=new;
-      dataCapacity=newCapacity;
+      d = new;
       }
       }
+
     m=parse_string(filter,&d[dataLength]);
     if (m==0)
       {
     m=parse_string(filter,&d[dataLength]);
     if (m==0)
       {
@@ -2124,7 +2137,7 @@ if (parse_identifier(filter,CUS "address"))
         filter->errmsg=CUS "header string expansion failed";
         return -1;
         }
         filter->errmsg=CUS "header string expansion failed";
         return -1;
         }
-      parse_allow_group = TRUE;
+      f.parse_allow_group = TRUE;
       while (*header_value && !*cond)
         {
         uschar *error;
       while (*header_value && !*cond)
         {
         uschar *error;
@@ -2170,8 +2183,8 @@ if (parse_identifier(filter,CUS "address"))
         if (saveend == 0) break;
         header_value = end_addr + 1;
         }
         if (saveend == 0) break;
         header_value = end_addr + 1;
         }
-      parse_allow_group = FALSE;
-      parse_found_group = FALSE;
+      f.parse_allow_group = FALSE;
+      f.parse_found_group = FALSE;
       }
     }
   return 1;
       }
     }
   return 1;
@@ -2737,8 +2750,8 @@ Returns:      2                success by stop
               1                other success
               -1               syntax or execution error
 */
               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)
   {
 {
 while (*filter->pc)
   {
@@ -2970,7 +2983,6 @@ while (*filter->pc)
     int m;
     struct String from;
     struct String importance;
     int m;
     struct String from;
     struct String importance;
-    struct String *options;
     struct String message;
     struct String method;
     struct Notification *already;
     struct String message;
     struct String method;
     struct Notification *already;
@@ -2991,7 +3003,6 @@ while (*filter->pc)
     from.length=-1;
     importance.character=(uschar*)0;
     importance.length=-1;
     from.length=-1;
     importance.character=(uschar*)0;
     importance.length=-1;
-    options=(struct String*)0;
     message.character=(uschar*)0;
     message.length=-1;
     recipient=NULL;
     message.character=(uschar*)0;
     message.length=-1;
     recipient=NULL;
@@ -3107,7 +3118,7 @@ while (*filter->pc)
                 message.character=US"Notification";
                 message.length=Ustrlen(message.character);
                 }
                 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));
               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));
@@ -3276,15 +3287,13 @@ while (*filter->pc)
     if (exec)
       {
       address_item *addr;
     if (exec)
       {
       address_item *addr;
-      int capacity,start;
       uschar *buffer;
       int buffer_capacity;
       uschar *buffer;
       int buffer_capacity;
-      struct String key;
       md5 base;
       uschar digest[16];
       uschar hexdigest[33];
       int i;
       md5 base;
       uschar digest[16];
       uschar hexdigest[33];
       int i;
-      uschar *once;
+      gstring * once;
 
       if (filter_personal(aliases,TRUE))
         {
 
       if (filter_personal(aliases,TRUE))
         {
@@ -3296,32 +3305,30 @@ while (*filter->pc)
           }
         /* build oncelog filename */
 
           }
         /* build oncelog filename */
 
-        key.character=(uschar*)0;
-        key.length=0;
-        capacity=0;
+        md5_start(&base);
+
         if (handle.length==-1)
           {
         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);
+         gstring * key = NULL;
+          if (subject.length!=-1) key =string_catn(key, subject.character, subject.length);
+          if (from.length!=-1) key = string_catn(key, from.character, from.length);
+          key = string_catn(key, reason_is_mime?US"1":US"0", 1);
+          key = string_catn(key, reason.character, reason.length);
+         md5_end(&base, key->s, key->ptr, digest);
           }
         else
           }
         else
-          key=handle;
-        md5_start(&base);
-        md5_end(&base, key.character, key.length, digest);
+         md5_end(&base, handle.character, handle.length, digest);
+
         for (i = 0; i < 16; i++) sprintf(CS (hexdigest+2*i), "%02X", digest[i]);
         for (i = 0; i < 16; i++) sprintf(CS (hexdigest+2*i), "%02X", digest[i]);
+
         if ((filter_test != FTEST_NONE && debug_selector != 0) || (debug_selector & D_filter) != 0)
         if ((filter_test != FTEST_NONE && debug_selector != 0) || (debug_selector & D_filter) != 0)
-          {
           debug_printf("Sieve: mail was personal, vacation file basename: %s\n", hexdigest);
           debug_printf("Sieve: mail was personal, vacation file basename: %s\n", hexdigest);
-          }
+
         if (filter_test == FTEST_NONE)
           {
         if (filter_test == FTEST_NONE)
           {
-          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[start] = '\0';
+          once = string_cat (NULL, filter->vacation_directory);
+          once = string_catn(once, US"/", 1);
+          once = string_catn(once, hexdigest, 33);
 
           /* process subject */
 
 
           /* process subject */
 
@@ -3332,11 +3339,12 @@ while (*filter->pc)
             subject_def=expand_string(US"${if def:header_subject {true}{false}}");
             if (Ustrcmp(subject_def,"true")==0)
               {
             subject_def=expand_string(US"${if def:header_subject {true}{false}}");
             if (Ustrcmp(subject_def,"true")==0)
               {
+             gstring * g = string_catn(NULL, US"Auto: ", 6);
+
               expand_header(&subject,&str_subject);
               expand_header(&subject,&str_subject);
-              capacity=6;
-              start=6;
-              subject.character=string_cat(US"Auto: ",&capacity,&start,subject.character,subject.length);
-              subject.length=start;
+              g = string_catn(g, subject.character, subject.length);
+             subject.character = string_from_gstring(g);
+              subject.length = g->ptr;
               }
             else
               {
               }
             else
               {
@@ -3349,7 +3357,7 @@ while (*filter->pc)
 
           addr = deliver_make_addr(string_sprintf(">%.256s", sender_address), FALSE);
           setflag(addr, af_pfr);
 
           addr = deliver_make_addr(string_sprintf(">%.256s", sender_address), FALSE);
           setflag(addr, af_pfr);
-          setflag(addr, af_ignore_error);
+          addr->prop.ignore_error = TRUE;
           addr->next = *generated;
           *generated = addr;
           addr->reply = store_get(sizeof(reply_item));
           addr->next = *generated;
           *generated = addr;
           addr->reply = store_get(sizeof(reply_item));
@@ -3359,11 +3367,12 @@ while (*filter->pc)
             addr->reply->from = expand_string(US"$local_part@$domain");
           else
             addr->reply->from = from.character;
             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);
           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);
-          addr->reply->oncelog=once;
+         /* 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 = string_from_gstring(once);
           addr->reply->once_repeat=days*86400;
 
           /* build body and MIME headers */
           addr->reply->once_repeat=days*86400;
 
           /* build body and MIME headers */
@@ -3375,27 +3384,21 @@ while (*filter->pc)
 
             for
               (
 
             for
               (
-              mime_body=reason.character,reason_end=reason.character+reason.length;
-              mime_body<(reason_end-(sizeof(nlnl)-1)) && memcmp(mime_body,nlnl,(sizeof(nlnl)-1));
+              mime_body = reason.character, reason_end = reason.character + reason.length;
+              mime_body < (reason_end-(sizeof(nlnl)-1)) && memcmp(mime_body, nlnl, (sizeof(nlnl)-1));
               ++mime_body
               );
               ++mime_body
               );
-            capacity = 0;
-            start = 0;
-            addr->reply->headers = string_cat(NULL,&capacity,&start,reason.character,mime_body-reason.character);
-            addr->reply->headers[start] = '\0';
-            capacity = 0;
-            start = 0;
+
+            addr->reply->headers = string_copyn(reason.character, mime_body-reason.character);
+
             if (mime_body+(sizeof(nlnl)-1)<reason_end) mime_body+=(sizeof(nlnl)-1);
             else mime_body=reason_end-1;
             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[start] = '\0';
+            addr->reply->text = string_copyn(mime_body, reason_end-mime_body);
             }
           else
             {
             }
           else
             {
-            struct String qp = { NULL, 0 };  /* Keep compiler happy (PH) */
+            struct String qp = { .character = NULL, .length = 0 };  /* Keep compiler happy (PH) */
 
 
-            capacity = 0;
-            start = reason.length;
             addr->reply->headers = US"MIME-Version: 1.0\n"
                                    "Content-Type: text/plain;\n"
                                    "\tcharset=\"utf-8\"\n"
             addr->reply->headers = US"MIME-Version: 1.0\n"
                                    "Content-Type: text/plain;\n"
                                    "\tcharset=\"utf-8\"\n"
@@ -3405,9 +3408,7 @@ while (*filter->pc)
           }
         }
         else if ((filter_test != FTEST_NONE && debug_selector != 0) || (debug_selector & D_filter) != 0)
           }
         }
         else if ((filter_test != FTEST_NONE && debug_selector != 0) || (debug_selector & D_filter) != 0)
-          {
           debug_printf("Sieve: mail was not personal, vacation would ignore it\n");
           debug_printf("Sieve: mail was not personal, vacation would ignore it\n");
-          }
       }
     }
     else break;
       }
     }
     else break;
index dbaa328..86f87ea 100644 (file)
@@ -2,13 +2,14 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for handling an incoming SMTP call. */
 
 
 #include "exim.h"
 /* 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
 
 
 /* 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
 /* 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
 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.
 */
 
 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 */
 
 
 /* Size of buffer for reading SMTP incoming packets */
 
-#define in_buffer_size  8192
+#define IN_BUFFER_SIZE  8192
 
 /* Structure for SMTP command list */
 
 
 /* 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 */
   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 */
 
 
   /* This is a dummy to identify the non-sync commands when pipelining */
 
@@ -84,6 +86,19 @@ enum {
 
   NON_SYNC_CMD_NON_PIPELINING,
 
 
   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. */
   /* 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,
 
 
   QUIT_CMD, HELP_CMD,
 
-#ifdef EXPERIMENTAL_PROXY
+#ifdef SUPPORT_PROXY
   PROXY_FAIL_IGNORE_CMD,
 #endif
 
   PROXY_FAIL_IGNORE_CMD,
 #endif
 
@@ -116,25 +131,38 @@ to the circular buffer that holds a list of the last n received. */
 *                Local static variables          *
 *************************************************/
 
 *                Local static variables          *
 *************************************************/
 
-static auth_instance *authenticated_by;
-static BOOL auth_advertised;
+static struct {
+  BOOL auth_advertised                 :1;
 #ifdef SUPPORT_TLS
 #ifdef SUPPORT_TLS
-static BOOL tls_advertised;
+  BOOL tls_advertised                  :1;
+# ifdef EXPERIMENTAL_REQUIRETLS
+  BOOL requiretls_advertised           :1;
+# endif
+#endif
+  BOOL dsn_advertised                  :1;
+  BOOL esmtp                           :1;
+  BOOL helo_required                   :1;
+  BOOL helo_verify                     :1;
+  BOOL helo_seen                       :1;
+  BOOL helo_accept_junk                        :1;
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+  BOOL pipe_connect_acceptable         :1;
 #endif
 #endif
-#ifdef EXPERIMENTAL_DSN
-static BOOL dsn_advertised;
+  BOOL rcpt_smtp_response_same         :1;
+  BOOL rcpt_in_progress                        :1;
+  BOOL smtp_exit_function_called       :1;
+#ifdef SUPPORT_I18N
+  BOOL smtputf8_advertised             :1;
 #endif
 #endif
-static BOOL esmtp;
-static BOOL helo_required = FALSE;
-static BOOL helo_verify = FALSE;
-static BOOL helo_seen;
-static BOOL helo_accept_junk;
-static BOOL count_nonmail;
-static BOOL pipelining_advertised;
-static BOOL rcpt_smtp_response_same;
-static BOOL rcpt_in_progress;
+} fl = {
+  .helo_required = FALSE,
+  .helo_verify = FALSE,
+  .smtp_exit_function_called = FALSE,
+};
+
+static auth_instance *authenticated_by;
+static int  count_nonmail;
 static int  nonmail_command_count;
 static int  nonmail_command_count;
-static BOOL smtp_exit_function_called = 0;
 static int  synprot_error_count;
 static int  unknown_command_count;
 static int  sync_cmd_limit;
 static int  synprot_error_count;
 static int  unknown_command_count;
 static int  sync_cmd_limit;
@@ -157,22 +185,29 @@ 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
 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[] = {
 
 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  },
   { "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
+#ifdef SUPPORT_TLS
   { "starttls",   sizeof("starttls")-1,   STARTTLS_CMD, FALSE, FALSE },
   { "starttls",   sizeof("starttls")-1,   STARTTLS_CMD, FALSE, FALSE },
-  #endif
+  { "tls_auth",   0,                      TLS_AUTH_CMD, FALSE, FALSE },
+#endif
 
 /* If you change anything above here, also fix the definitions below. */
 
   { "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  },
 
 /* If you change anything above here, also fix the definitions below. */
 
   { "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 },
   { "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 +224,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_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[] =
   {
 
 /* 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 */
   US"local-smtp",        /* HELO */
   US"local-smtps",       /* The rare case EHLO->STARTTLS->HELO */
   US"local-esmtp",       /* EHLO */
@@ -207,23 +243,34 @@ static uschar *protocols[] = {
   US"local-esmtpa",      /* EHLO->AUTH */
   US"local-esmtpsa"      /* EHLO->STARTTLS->EHLO->AUTH */
   };
   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 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 {
 
 /* 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
   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,
   ENV_MAIL_OPT_RET, ENV_MAIL_OPT_ENVID,
+#ifdef SUPPORT_I18N
+  ENV_MAIL_OPT_UTF8,
+#endif
+#ifdef EXPERIMENTAL_REQUIRETLS
+  ENV_MAIL_OPT_REQTLS,
 #endif
 #endif
-  ENV_MAIL_OPT_NULL
   };
 typedef struct {
   uschar *   name;  /* option requested during MAIL cmd */
   };
 typedef struct {
   uschar *   name;  /* option requested during MAIL cmd */
@@ -238,11 +285,17 @@ static env_mail_type_t env_mail_type_list[] = {
 #ifndef DISABLE_PRDR
     { US"PRDR",   ENV_MAIL_OPT_PRDR,   FALSE },
 #endif
 #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 },
     { 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
 #endif
-    { US"NULL",   ENV_MAIL_OPT_NULL,   FALSE }
+#ifdef EXPERIMENTAL_REQUIRETLS
+    /* https://tools.ietf.org/html/draft-ietf-uta-smtp-require-tls-03 */
+    { US"REQUIRETLS",ENV_MAIL_OPT_REQTLS,  FALSE },
+#endif
+    /* 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
   };
 
 /* When reading SMTP from a remote host, we have to use our own versions of the
@@ -269,6 +322,237 @@ static int     smtp_had_eof;
 static int     smtp_had_error;
 
 
 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.  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
+wouldblock_reading(void)
+{
+int fd, rc;
+fd_set fds;
+struct timeval tzero;
+
+#ifdef SUPPORT_TLS
+if (tls_in.active.sock >= 0)
+ return !tls_could_read();
+#endif
+
+if (smtp_inptr < smtp_inend)
+  return FALSE;
+
+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);
+return FALSE;
+}
+
+static BOOL
+check_sync(void)
+{
+if (!smtp_enforce_sync || !sender_host_address || f.sender_host_notsocket)
+  return TRUE;
+
+return wouldblock_reading();
+}
+
+
+/* If there's input waiting (and we're doing pipelineing) then we can pipeline
+a reponse with the one following. */
+
+static BOOL
+pipeline_response(void)
+{
+if (  !smtp_enforce_sync || !sender_host_address
+   || f.sender_host_notsocket || !f.smtp_in_pipelining_advertised)
+  return FALSE;
+
+if (wouldblock_reading()) return FALSE;
+f.smtp_in_pipelining_used = TRUE;
+return TRUE;
+}
+
+
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+static BOOL
+pipeline_connect_sends(void)
+{
+if (!sender_host_address || f.sender_host_notsocket || !fl.pipe_connect_acceptable)
+  return FALSE;
+
+if (wouldblock_reading()) return FALSE;
+f.smtp_in_early_pipe_used = TRUE;
+return TRUE;
+}
+#endif
+
+/*************************************************
+*          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);
+}
+
+
+
+
+void
+smtp_command_timeout_exit(void)
+{
+log_write(L_lost_incoming_connection,
+         LOG_MAIN, "SMTP command timeout on%s connection from %s",
+         tls_in.active.sock >= 0 ? " TLS" : "", host_and_ident(FALSE));
+if (smtp_batched_input)
+  moan_smtp_batch(NULL, "421 SMTP command timeout"); /* Does not return */
+smtp_notquit_exit(US"command-timeout", US"421",
+  US"%s: SMTP command timeout - closing connection",
+  smtp_active_hostname);
+exim_exit(EXIT_FAILURE, US"receiving");
+}
+
+void
+smtp_command_sigterm_exit(void)
+{
+log_write(0, LOG_MAIN, "%s closed after SIGTERM", smtp_get_connection_info());
+if (smtp_batched_input)
+  moan_smtp_batch(NULL, "421 SIGTERM received");  /* Does not return */
+smtp_notquit_exit(US"signal-exit", US"421",
+  US"%s: Service not available - closing connection", smtp_active_hostname);
+exim_exit(EXIT_FAILURE, US"receiving");
+}
+
+void
+smtp_data_timeout_exit(void)
+{
+log_write(L_lost_incoming_connection,
+  LOG_MAIN, "SMTP data timeout (message abandoned) on connection from %s F=<%s>",
+  sender_fullhost ? sender_fullhost : US"local process", sender_address);
+receive_bomb_out(US"data-timeout", US"SMTP incoming data timeout");
+/* Does not return */
+}
+
+void
+smtp_data_sigint_exit(void)
+{
+log_write(0, LOG_MAIN, "%s closed after %s",
+  smtp_get_connection_info(), had_data_sigint == SIGTERM ? "SIGTERM":"SIGINT");
+receive_bomb_out(US"signal-exit",
+  US"Service not available - SIGTERM or SIGINT received");
+/* Does not return */
+}
+
+
+
+/* Refill the buffer, and notify DKIM verification code.
+Return false for error or EOF.
+*/
+
+static BOOL
+smtp_refill(unsigned lim)
+{
+int rc, save_errno;
+if (!smtp_out) return FALSE;
+fflush(smtp_out);
+if (smtp_receive_timeout > 0) ALARM(smtp_receive_timeout);
+
+/* Limit amount read, so non-message data is not fed to DKIM.
+Take care to not touch the safety NUL at the end of the buffer. */
+
+rc = read(fileno(smtp_in), smtp_inbuffer, MIN(IN_BUFFER_SIZE-1, lim));
+save_errno = errno;
+if (smtp_receive_timeout > 0) ALARM_CLR(0);
+if (rc <= 0)
+  {
+  /* Must put the error text in fixed store, because this might be during
+  header reading, where it releases unused store above the header. */
+  if (rc < 0)
+    {
+    if (had_command_timeout)           /* set by signal handler */
+      smtp_command_timeout_exit();     /* does not return */
+    if (had_command_sigterm)
+      smtp_command_sigterm_exit();
+    if (had_data_timeout)
+      smtp_data_timeout_exit();
+    if (had_data_sigint)
+      smtp_data_sigint_exit();
+
+    smtp_had_error = save_errno;
+    smtp_read_error = string_copy_malloc(
+      string_sprintf(" (error: %s)", strerror(save_errno)));
+    }
+  else
+    smtp_had_eof = 1;
+  return FALSE;
+  }
+#ifndef DISABLE_DKIM
+dkim_exim_verify_feed(smtp_inbuffer, rc);
+#endif
+smtp_inend = smtp_inbuffer + rc;
+smtp_inptr = smtp_inbuffer;
+return TRUE;
+}
+
 /*************************************************
 *          SMTP version of getc()                *
 *************************************************/
 /*************************************************
 *          SMTP version of getc()                *
 *************************************************/
@@ -276,47 +560,235 @@ 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
 /* 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
 Returns:    the next character or EOF
 */
 
 int
-smtp_getc(void)
+smtp_getc(unsigned lim)
+{
+if (smtp_inptr >= smtp_inend)
+  if (!smtp_refill(lim))
+    return EOF;
+return *smtp_inptr++;
+}
+
+uschar *
+smtp_getbuf(unsigned * len)
 {
 {
+unsigned size;
+uschar * buf;
+
 if (smtp_inptr >= smtp_inend)
 if (smtp_inptr >= smtp_inend)
+  if (!smtp_refill(*len))
+    { *len = 0; return NULL; }
+
+if ((size = smtp_inend - smtp_inptr) > *len) size = *len;
+buf = smtp_inptr;
+smtp_inptr += size;
+*len = size;
+return buf;
+}
+
+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(;;)
   {
   {
-  int rc, save_errno;
-  fflush(smtp_out);
-  if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
-  rc = read(fileno(smtp_in), smtp_inbuffer, in_buffer_size);
-  save_errno = errno;
-  alarm(0);
-  if (rc <= 0)
+#ifndef DISABLE_DKIM
+  unsigned dkim_save;
+#endif
+
+  if (chunking_data_left > 0)
+    return lwr_receive_getc(chunking_data_left--);
+
+  receive_getc = lwr_receive_getc;
+  receive_getbuf = lwr_receive_getbuf;
+  receive_ungetc = lwr_receive_ungetc;
+#ifndef DISABLE_DKIM
+  dkim_save = dkim_collect_input;
+  dkim_collect_input = 0;
+#endif
+
+  /* Unless PIPELINING was offered, there should be no next command
+  until after we ack that chunk */
+
+  if (!f.smtp_in_pipelining_advertised && !check_sync())
     {
     {
-    /* Must put the error text in fixed store, because this might be during
-    header reading, where it releases unused store above the header. */
-    if (rc < 0)
-      {
-      smtp_had_error = save_errno;
-      smtp_read_error = string_copy_malloc(
-        string_sprintf(" (error: %s)", strerror(save_errno)));
-      }
-    else smtp_had_eof = 1;
-    return EOF;
+    unsigned n = smtp_inend - smtp_inptr;
+    if (n > 32) n = 32;
+
+    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\"%s",
+      smtp_cmd_buffer, host_and_ident(TRUE),
+      string_printing(string_copyn(smtp_inptr, n)),
+      smtp_inend - smtp_inptr > n ? "..." : "");
+    (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", FALSE, 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", FALSE);
+      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_getbuf = bdat_getbuf;    /* r~getbuf is never actually used */
+      receive_ungetc = bdat_ungetc;
 #ifndef DISABLE_DKIM
 #ifndef DISABLE_DKIM
-  dkim_exim_verify_feed(smtp_inbuffer, rc);
+      dkim_collect_input = dkim_save;
 #endif
 #endif
-  smtp_inend = smtp_inbuffer + rc;
-  smtp_inptr = smtp_inbuffer;
+      break;   /* to top of main loop */
+      }
+    }
+  }
+}
+
+uschar *
+bdat_getbuf(unsigned * len)
+{
+uschar * buf;
+
+if (chunking_data_left <= 0)
+  { *len = 0; return NULL; }
+
+if (*len > chunking_data_left) *len = chunking_data_left;
+buf = lwr_receive_getbuf(len); /* Either smtp_getbuf or tls_getbuf */
+chunking_data_left -= *len;
+return buf;
+}
+
+void
+bdat_flush_data(void)
+{
+while (chunking_data_left)
+  {
+  unsigned n = chunking_data_left;
+  if (!bdat_getbuf(&n)) break;
+  }
+
+receive_getc = lwr_receive_getc;
+receive_getbuf = lwr_receive_getbuf;
+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);
   }
   }
-return *smtp_inptr++;
 }
 
 
 
 }
 
 
 
+
 /*************************************************
 *          SMTP version of ungetc()              *
 *************************************************/
 /*************************************************
 *          SMTP version of ungetc()              *
 *************************************************/
@@ -333,11 +805,18 @@ Returns:       the character
 int
 smtp_ungetc(int ch)
 {
 int
 smtp_ungetc(int ch)
 {
-*(--smtp_inptr) = ch;
+*--smtp_inptr = ch;
 return ch;
 }
 
 
 return ch;
 }
 
 
+int
+bdat_ungetc(int ch)
+{
+chunking_data_left++;
+return lwr_receive_ungetc(ch);
+}
+
 
 
 /*************************************************
 
 
 /*************************************************
@@ -411,18 +890,19 @@ they are also picked up later by smtp_fflush().
 
 Arguments:
   format      format string
 
 Arguments:
   format      format string
+  more       further data expected
   ...         optional arguments
 
 Returns:      nothing
 */
 
 void
   ...         optional arguments
 
 Returns:      nothing
 */
 
 void
-smtp_printf(const char *format, ...)
+smtp_printf(const char *format, BOOL more, ...)
 {
 va_list ap;
 
 {
 va_list ap;
 
-va_start(ap, format);
-smtp_vprintf(format, ap);
+va_start(ap, more);
+smtp_vprintf(format, more, ap);
 va_end(ap);
 }
 
 va_end(ap);
 }
 
@@ -431,20 +911,22 @@ smtp_printf(), bearing in mind that in C a vararg function can't directly
 call another vararg function, only a function which accepts a va_list. */
 
 void
 call another vararg function, only a function which accepts a va_list. */
 
 void
-smtp_vprintf(const char *format, va_list ap)
+smtp_vprintf(const char *format, BOOL more, va_list ap)
 {
 {
+gstring gs = { .size = big_buffer_size, .ptr = 0, .s = big_buffer };
 BOOL yield;
 
 BOOL yield;
 
-yield = string_vformat(big_buffer, big_buffer_size, format, ap);
+yield = !! string_vformat(&gs, FALSE, format, ap);
+string_from_gstring(&gs);
 
 DEBUG(D_receive)
   {
   void *reset_point = store_get(0);
   uschar *msg_copy, *cr, *end;
 
 DEBUG(D_receive)
   {
   void *reset_point = store_get(0);
   uschar *msg_copy, *cr, *end;
-  msg_copy = string_copy(big_buffer);
-  end = msg_copy + Ustrlen(msg_copy);
+  msg_copy = string_copy(gs.s);
+  end = msg_copy + gs.ptr;
   while ((cr = Ustrchr(msg_copy, '\r')) != NULL)   /* lose CRs */
   while ((cr = Ustrchr(msg_copy, '\r')) != NULL)   /* lose CRs */
-  memmove(cr, cr + 1, (end--) - cr);
+    memmove(cr, cr + 1, (end--) - cr);
   debug_printf("SMTP>> %s", msg_copy);
   store_reset(reset_point);
   }
   debug_printf("SMTP>> %s", msg_copy);
   store_reset(reset_point);
   }
@@ -453,7 +935,7 @@ if (!yield)
   {
   log_write(0, LOG_MAIN|LOG_PANIC, "string too large in smtp_printf()");
   smtp_closedown(US"Unexpected error");
   {
   log_write(0, LOG_MAIN|LOG_PANIC, "string too large in smtp_printf()");
   smtp_closedown(US"Unexpected error");
-  exim_exit(EXIT_FAILURE);
+  exim_exit(EXIT_FAILURE, NULL);
   }
 
 /* If this is the first output for a (non-batch) RCPT command, see if all RCPTs
   }
 
 /* If this is the first output for a (non-batch) RCPT command, see if all RCPTs
@@ -462,28 +944,28 @@ be tidier to have it only in one place, but when it was added, it was easier to
 do it that way, so as not to have to mess with the code for the RCPT command,
 which sometimes uses smtp_printf() and sometimes smtp_respond(). */
 
 do it that way, so as not to have to mess with the code for the RCPT command,
 which sometimes uses smtp_printf() and sometimes smtp_respond(). */
 
-if (rcpt_in_progress)
+if (fl.rcpt_in_progress)
   {
   if (rcpt_smtp_response == NULL)
     rcpt_smtp_response = string_copy(big_buffer);
   {
   if (rcpt_smtp_response == NULL)
     rcpt_smtp_response = string_copy(big_buffer);
-  else if (rcpt_smtp_response_same &&
+  else if (fl.rcpt_smtp_response_same &&
            Ustrcmp(rcpt_smtp_response, big_buffer) != 0)
            Ustrcmp(rcpt_smtp_response, big_buffer) != 0)
-    rcpt_smtp_response_same = FALSE;
-  rcpt_in_progress = FALSE;
+    fl.rcpt_smtp_response_same = FALSE;
+  fl.rcpt_in_progress = FALSE;
   }
 
 /* Now write the string */
 
 #ifdef SUPPORT_TLS
   }
 
 /* Now write the string */
 
 #ifdef SUPPORT_TLS
-if (tls_in.active >= 0)
+if (tls_in.active.sock >= 0)
   {
   {
-  if (tls_write(TRUE, big_buffer, Ustrlen(big_buffer)) < 0)
+  if (tls_write(NULL, gs.s, gs.ptr, more) < 0)
     smtp_write_error = -1;
   }
 else
 #endif
 
     smtp_write_error = -1;
   }
 else
 #endif
 
-if (fprintf(smtp_out, "%s", big_buffer) < 0) smtp_write_error = -1;
+if (fprintf(smtp_out, "%s", gs.s) < 0) smtp_write_error = -1;
 }
 
 
 }
 
 
@@ -504,7 +986,7 @@ Returns:    0 for no error; -1 after an error
 int
 smtp_fflush(void)
 {
 int
 smtp_fflush(void)
 {
-if (tls_in.active < 0 && fflush(smtp_out) != 0) smtp_write_error = -1;
+if (tls_in.active.sock < 0 && fflush(smtp_out) != 0) smtp_write_error = -1;
 return smtp_write_error;
 }
 
 return smtp_write_error;
 }
 
@@ -524,16 +1006,7 @@ Returns:  nothing
 static void
 command_timeout_handler(int sig)
 {
 static void
 command_timeout_handler(int sig)
 {
-sig = sig;    /* Keep picky compilers happy */
-log_write(L_lost_incoming_connection,
-          LOG_MAIN, "SMTP command timeout on%s connection from %s",
-          (tls_in.active >= 0)? " TLS" : "",
-          host_and_ident(FALSE));
-if (smtp_batched_input)
-  moan_smtp_batch(NULL, "421 SMTP command timeout");  /* Does not return */
-smtp_notquit_exit(US"command-timeout", US"421",
-  US"%s: SMTP command timeout - closing connection", smtp_active_hostname);
-exim_exit(EXIT_FAILURE);
+had_command_timeout = sig;
 }
 
 
 }
 
 
@@ -551,19 +1024,13 @@ Returns:  nothing
 static void
 command_sigterm_handler(int sig)
 {
 static void
 command_sigterm_handler(int sig)
 {
-sig = sig;    /* Keep picky compilers happy */
-log_write(0, LOG_MAIN, "%s closed after SIGTERM", smtp_get_connection_info());
-if (smtp_batched_input)
-  moan_smtp_batch(NULL, "421 SIGTERM received");  /* Does not return */
-smtp_notquit_exit(US"signal-exit", US"421",
-  US"%s: Service not available - closing connection", smtp_active_hostname);
-exim_exit(EXIT_FAILURE);
+had_command_sigterm = sig;
 }
 
 
 
 
 }
 
 
 
 
-#ifdef EXPERIMENTAL_PROXY
+#ifdef SUPPORT_PROXY
 /*************************************************
 *     Restore socket timeout to previous value   *
 *************************************************/
 /*************************************************
 *     Restore socket timeout to previous value   *
 *************************************************/
@@ -577,17 +1044,18 @@ Arguments: fd     - File descriptor for input
 Returns:   none
 */
 static void
 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)
 {
 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
 }
 
 /*************************************************
 *       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
 
 Arguments: none
 Returns:   bool
@@ -597,12 +1065,10 @@ static BOOL
 check_proxy_protocol_host()
 {
 int rc;
 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");
   {
   DEBUG(D_receive)
     debug_printf("Detected proxy protocol configured host\n");
@@ -612,6 +1078,71 @@ 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          *
 *************************************************/
 /*************************************************
 *         Setup host for proxy protocol          *
 *************************************************/
@@ -621,10 +1152,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
 includes an incorrect number of spaces separating args.
 
 Arguments: none
-Returns:   int
+Returns:   Boolean success
 */
 
 */
 
-static BOOL
+static void
 setup_proxy_protocol_host()
 {
 union {
 setup_proxy_protocol_host()
 {
 union {
@@ -664,69 +1195,133 @@ struct sockaddr_in tmpaddr;
 char tmpip6[INET6_ADDRSTRLEN];
 struct sockaddr_in6 tmpaddr6;
 
 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 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";
 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;
 struct timeval tv;
-socklen_t vslen = 0;
 struct timeval tvtmp;
 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 */
 
 /* 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;
 
 /* 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
 
 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)
   }
   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;
 
 
-  /* 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;
   ver = (hdr.v2.ver_cmd & 0xf0) >> 4;
-  cmd = (hdr.v2.ver_cmd & 0x0f);
+
+  /* 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 separate values here. */
 
   if (ver != 0x02)
     {
     DEBUG(D_receive) debug_printf("Invalid Proxy Protocol version: %d\n", ver);
     goto proxyfail;
     }
 
   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. */
   /* 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;
     }
     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 */
   switch (cmd)
     {
     case 0x01: /* PROXY command */
@@ -735,54 +1330,54 @@ if (ret >= 16 &&
         case 0x11:  /* TCPv4 address type */
           iptype = US"IPv4";
           tmpaddr.sin_addr.s_addr = hdr.v2.addr.ip4.src_addr;
         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);
             {
             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);
           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;
           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);
             {
             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);
           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);
           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);
             {
             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);
           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);
           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);
             {
             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);
           tmpport              = ntohs(hdr.v2.addr.ip6.dst_port);
-          proxy_target_port    = tmpport;
+          proxy_external_port  = tmpport;
           goto done;
         default:
           DEBUG(D_receive)
           goto done;
         default:
           DEBUG(D_receive)
@@ -794,6 +1389,7 @@ if (ret >= 16 &&
       break;
     case 0x00: /* LOCAL command */
       /* Keep local connection address for LOCAL */
       break;
     case 0x00: /* LOCAL command */
       /* Keep local connection address for LOCAL */
+      iptype = US"local";
       break;
     default:
       DEBUG(D_receive)
       break;
     default:
       DEBUG(D_receive)
@@ -801,25 +1397,35 @@ if (ret >= 16 &&
       goto proxyfail;
     }
   }
       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;
   uschar *sp;     /* Utility variables follow */
   int     tmp_port;
+  int     r2;
   char   *endc;
 
   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 */
     {
     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("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
   /* 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++)))
     {
   p += 5;
   if (!isspace(*(p++)))
     {
@@ -855,13 +1461,13 @@ else if (ret >= 8 &&
     goto proxyfail;
     }
   *sp = '\0';
     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;
     }
     {
     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)
   sender_host_address = p;
   p = sp + 1;
   if ((sp = Ustrchr(p, ' ')) == NULL)
@@ -871,13 +1477,13 @@ else if (ret >= 8 &&
     goto proxyfail;
     }
   *sp = '\0';
     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;
     }
     {
     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)
     {
   p = sp + 1;
   if ((sp = Ustrchr(p, ' ')) == NULL)
     {
@@ -885,14 +1491,14 @@ else if (ret >= 8 &&
     goto proxyfail;
     }
   *sp = '\0';
     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;
     }
   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)
   sender_host_port = tmp_port;
   p = sp + 1;
   if ((sp = Ustrchr(p, '\0')) == NULL)
@@ -900,35 +1506,50 @@ else if (ret >= 8 &&
     DEBUG(D_receive) debug_printf("Did not find proxy dest port\n");
     goto proxyfail;
     }
     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;
     }
   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. */
   /* 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");
   }
 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;
   }
 
   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:
 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
+    {
+    f.proxy_session_failed = TRUE;
+    DEBUG(D_receive)
+      debug_printf("Failure to extract proxied host, only QUIT allowed\n");
+    }
+
+return;
 }
 #endif
 
 }
 #endif
 
@@ -949,24 +1570,26 @@ signal handler that closes down the session on a timeout. Control does not
 return when it runs.
 
 Arguments:
 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
 
 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;
 smtp_cmd_list *p;
 BOOL hadnull = FALSE;
 
 {
 int c;
 int ptr = 0;
 smtp_cmd_list *p;
 BOOL hadnull = FALSE;
 
+had_command_timeout = 0;
 os_non_restarting_signal(SIGALRM, command_timeout_handler);
 
 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;
     {
     os_non_restarting_signal(SIGALRM, sigalrm_handler);
     return OTHER_CMD;
@@ -1005,25 +1628,24 @@ if required. */
 
 for (p = cmd_list; p < cmd_list_end; p++)
   {
 
 for (p = cmd_list; p < cmd_list_end; p++)
   {
-  #ifdef EXPERIMENTAL_PROXY
+#ifdef SUPPORT_PROXY
   /* Only allow QUIT command if Proxy Protocol parsing failed */
   /* 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 && f.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 */
         check_sync &&                                  /* Local flag set */
         smtp_enforce_sync &&                           /* Global flag set */
         sender_host_address != NULL &&                 /* Not local input */
     {
     if (smtp_inptr < smtp_inend &&                     /* Outstanding input */
         p->cmd < sync_cmd_limit &&                     /* Command should sync */
         check_sync &&                                  /* Local flag set */
         smtp_enforce_sync &&                           /* Global flag set */
         sender_host_address != NULL &&                 /* Not local input */
-        !sender_host_notsocket)                        /* Really is a socket */
+        !f.sender_host_notsocket)                        /* Really is a socket */
       return BADSYN_CMD;
 
     /* The variables $smtp_command and $smtp_command_argument point into the
       return BADSYN_CMD;
 
     /* The variables $smtp_command and $smtp_command_argument point into the
@@ -1060,19 +1682,19 @@ for (p = cmd_list; p < cmd_list_end; p++)
     }
   }
 
     }
   }
 
-#ifdef EXPERIMENTAL_PROXY
+#ifdef SUPPORT_PROXY
 /* Only allow QUIT command if Proxy Protocol parsing failed */
 /* Only allow QUIT command if Proxy Protocol parsing failed */
-if (proxy_session && proxy_session_failed)
+if (proxy_session && f.proxy_session_failed)
   return PROXY_FAIL_IGNORE_CMD;
 #endif
 
 /* Enforce synchronization for unknown commands */
 
   return PROXY_FAIL_IGNORE_CMD;
 #endif
 
 /* Enforce synchronization for unknown commands */
 
-if (smtp_inptr < smtp_inend &&                     /* Outstanding input */
-    check_sync &&                                  /* Local flag set */
-    smtp_enforce_sync &&                           /* Global flag set */
-    sender_host_address != NULL &&                 /* Not local input */
-    !sender_host_notsocket)                        /* Really is a socket */
+if (  smtp_inptr < smtp_inend          /* Outstanding input */
+   && check_sync                       /* Local flag set */
+   && smtp_enforce_sync                        /* Global flag set */
+   && sender_host_address              /* Not local input */
+   && !f.sender_host_notsocket)                /* Really is a socket */
   return BADSYN_CMD;
 
 return OTHER_CMD;
   return BADSYN_CMD;
 
 return OTHER_CMD;
@@ -1080,60 +1702,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              *
 *************************************************/
 /*************************************************
 *          Forced closedown of call              *
 *************************************************/
@@ -1154,30 +1722,27 @@ Returns:    nothing
 void
 smtp_closedown(uschar *message)
 {
 void
 smtp_closedown(uschar *message)
 {
-if (smtp_in == NULL || smtp_batched_input) return;
+if (!smtp_in || smtp_batched_input) return;
 receive_swallow_smtp();
 receive_swallow_smtp();
-smtp_printf("421 %s\r\n", message);
+smtp_printf("421 %s\r\n", FALSE, message);
 
 
-for (;;)
+for (;;) switch(smtp_read_command(FALSE, GETC_BUFFER_UNLIMITED))
   {
   {
-  switch(smtp_read_command(FALSE))
-    {
-    case EOF_CMD:
+  case EOF_CMD:
     return;
 
     return;
 
-    case QUIT_CMD:
-    smtp_printf("221 %s closing connection\r\n", smtp_active_hostname);
+  case QUIT_CMD:
+    smtp_printf("221 %s closing connection\r\n", FALSE, smtp_active_hostname);
     mac_smtp_fflush();
     return;
 
     mac_smtp_fflush();
     return;
 
-    case RSET_CMD:
-    smtp_printf("250 Reset OK\r\n");
+  case RSET_CMD:
+    smtp_printf("250 Reset OK\r\n", FALSE);
     break;
 
     break;
 
-    default:
-    smtp_printf("421 %s\r\n", message);
+  default:
+    smtp_printf("421 %s\r\n", FALSE, message);
     break;
     break;
-    }
   }
 }
 
   }
 }
 
@@ -1200,20 +1765,19 @@ Returns:     a string describing the connection
 uschar *
 smtp_get_connection_info(void)
 {
 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);
 
 
 if (host_checking)
   return string_sprintf("SMTP connection from %s", hostname);
 
-if (sender_host_unknown || sender_host_notsocket)
+if (f.sender_host_unknown || f.sender_host_notsocket)
   return string_sprintf("SMTP connection from %s", sender_ident);
 
   return string_sprintf("SMTP connection from %s", sender_ident);
 
-if (is_inetd)
+if (f.is_inetd)
   return string_sprintf("SMTP connection from %s (via inetd)", hostname);
 
   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)
   return string_sprintf("SMTP connection from %s I=[%s]:%d", hostname,
     interface_address, interface_port);
 
   return string_sprintf("SMTP connection from %s I=[%s]:%d", hostname,
     interface_address, interface_port);
 
@@ -1226,38 +1790,22 @@ return string_sprintf("SMTP connection from %s", hostname);
 /* Append TLS-related information to a log line
 
 Arguments:
 /* Append TLS-related information to a log line
 
 Arguments:
-  s            String under construction: allocated string to extend, or NULL
-  sizep                Pointer to current allocation size (update on return), or NULL
-  ptrp         Pointer to index for new entries in string (update on return), or NULL
+  g            String under construction: allocated string to extend, or NULL
 
 Returns:       Allocated string or NULL
 */
 
 Returns:       Allocated string or NULL
 */
-static uschar *
-s_tlslog(uschar * s, int * sizep, int * ptrp)
+static gstring *
+s_tlslog(gstring * g)
 {
 {
-  int size = sizep ? *sizep : 0;
-  int ptr = ptrp ? *ptrp : 0;
-
-  if ((log_extra_selector & LX_tls_cipher) != 0 && tls_in.cipher != NULL)
-    s = string_append(s, &size, &ptr, 2, US" X=", tls_in.cipher);
-  if ((log_extra_selector & LX_tls_certificate_verified) != 0 &&
-       tls_in.cipher != NULL)
-    s = string_append(s, &size, &ptr, 2, US" CV=",
-      tls_in.certificate_verified? "yes":"no");
-  if ((log_extra_selector & LX_tls_peerdn) != 0 && tls_in.peerdn != NULL)
-    s = string_append(s, &size, &ptr, 3, US" DN=\"",
-      string_printing(tls_in.peerdn), US"\"");
-  if ((log_extra_selector & LX_tls_sni) != 0 && tls_in.sni != NULL)
-    s = string_append(s, &size, &ptr, 3, US" SNI=\"",
-      string_printing(tls_in.sni), US"\"");
-
-  if (s)
-    {
-    s[ptr] = '\0';
-    if (sizep) *sizep = size;
-    if (ptrp) *ptrp = ptr;
-    }
-  return s;
+if (LOGGING(tls_cipher) && tls_in.cipher)
+  g = string_append(g, 2, US" X=", tls_in.cipher);
+if (LOGGING(tls_certificate_verified) && tls_in.cipher)
+  g = string_append(g, 2, US" CV=", tls_in.certificate_verified? "yes":"no");
+if (LOGGING(tls_peerdn) && tls_in.peerdn)
+  g = string_append(g, 3, US" DN=\"", string_printing(tls_in.peerdn), US"\"");
+if (LOGGING(tls_sni) && tls_in.sni)
+  g = string_append(g, 3, US" SNI=\"", string_printing(tls_in.sni), US"\"");
+return g;
 }
 #endif
 
 }
 #endif
 
@@ -1276,60 +1824,76 @@ Returns:     nothing
 void
 smtp_log_no_mail(void)
 {
 void
 smtp_log_no_mail(void)
 {
-int size, ptr, i;
-uschar *s, *sep;
+int i;
+uschar * sep, * s;
+gstring * g = NULL;
 
 
-if (smtp_mailcmd_count > 0 || (log_extra_selector & LX_smtp_no_mail) == 0)
+if (smtp_mailcmd_count > 0 || !LOGGING(smtp_no_mail))
   return;
 
   return;
 
-s = NULL;
-size = ptr = 0;
-
-if (sender_host_authenticated != NULL)
+if (sender_host_authenticated)
   {
   {
-  s = string_append(s, &size, &ptr, 2, US" A=", sender_host_authenticated);
-  if (authenticated_id != NULL)
-    s = string_append(s, &size, &ptr, 2, US":", authenticated_id);
+  g = string_append(g, 2, US" A=", sender_host_authenticated);
+  if (authenticated_id) g = string_append(g, 2, US":", authenticated_id);
   }
 
 #ifdef SUPPORT_TLS
   }
 
 #ifdef SUPPORT_TLS
-s = s_tlslog(s, &size, &ptr);
+g = s_tlslog(g);
 #endif
 
 #endif
 
-sep = (smtp_connection_had[SMTP_HBUFF_SIZE-1] != SCH_NONE)?
-  US" C=..." : US" C=";
+sep = smtp_connection_had[SMTP_HBUFF_SIZE-1] != SCH_NONE ?  US" C=..." : US" C=";
+
 for (i = smtp_ch_index; i < SMTP_HBUFF_SIZE; i++)
 for (i = smtp_ch_index; i < SMTP_HBUFF_SIZE; i++)
-  {
   if (smtp_connection_had[i] != SCH_NONE)
     {
   if (smtp_connection_had[i] != SCH_NONE)
     {
-    s = string_append(s, &size, &ptr, 2, sep,
-      smtp_names[smtp_connection_had[i]]);
+    g = string_append(g, 2, sep, smtp_names[smtp_connection_had[i]]);
     sep = US",";
     }
     sep = US",";
     }
-  }
 
 for (i = 0; i < smtp_ch_index; i++)
   {
 
 for (i = 0; i < smtp_ch_index; i++)
   {
-  s = string_append(s, &size, &ptr, 2, sep, smtp_names[smtp_connection_had[i]]);
+  g = string_append(g, 2, sep, smtp_names[smtp_connection_had[i]]);
   sep = US",";
   }
 
   sep = US",";
   }
 
-if (s != NULL) s[ptr] = 0; else s = US"";
-log_write(0, LOG_MAIN, "no MAIL in SMTP connection from %s D=%s%s",
-  host_and_ident(FALSE),
-  readconf_printtime( (int) ((long)time(NULL) - (long)smtp_connection_start)),
-  s);
+if (!(s = string_from_gstring(g))) s = US"";
+
+log_write(0, LOG_MAIN, "no MAIL in %sSMTP connection from %s D=%s%s",
+  f.tcp_in_fastopen ? f.tcp_in_fastopen_data ? US"TFO* " : US"TFO " : US"",
+  host_and_ident(FALSE), string_timesince(&smtp_connection_start), s);
+}
+
+
+/* Return list of recent smtp commands */
+
+uschar *
+smtp_cmd_hist(void)
+{
+int  i;
+gstring * list = NULL;
+uschar * s;
+
+for (i = smtp_ch_index; i < SMTP_HBUFF_SIZE; i++)
+  if (smtp_connection_had[i] != SCH_NONE)
+    list = string_append_listele(list, ',', smtp_names[smtp_connection_had[i]]);
+
+for (i = 0; i < smtp_ch_index; i++)
+  list = string_append_listele(list, ',', smtp_names[smtp_connection_had[i]]);
+
+s = string_from_gstring(list);
+return s ? s : US"";
 }
 
 
 
 }
 
 
 
+
 /*************************************************
 *   Check HELO line and set sender_helo_name     *
 *************************************************/
 
 /* 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
 /*************************************************
 *   Check HELO line and set sender_helo_name     *
 *************************************************/
 
 /* 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
 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
@@ -1346,11 +1910,11 @@ check_helo(uschar *s)
 {
 uschar *start = s;
 uschar *end = s + Ustrlen(s);
 {
 uschar *start = s;
 uschar *end = s + Ustrlen(s);
-BOOL yield = helo_accept_junk;
+BOOL yield = fl.helo_accept_junk;
 
 /* Discard any previous helo name */
 
 
 /* Discard any previous helo name */
 
-if (sender_helo_name != NULL)
+if (sender_helo_name)
   {
   store_free(sender_helo_name);
   sender_helo_name = NULL;
   {
   store_free(sender_helo_name);
   sender_helo_name = NULL;
@@ -1359,7 +1923,7 @@ if (sender_helo_name != NULL)
 /* Skip tests if junk is permitted. */
 
 if (!yield)
 /* Skip tests if junk is permitted. */
 
 if (!yield)
-  {
+
   /* Allow the new standard form for IPv6 address literals, namely,
   [IPv6:....], and because someone is bound to use it, allow an equivalent
   IPv4 form. Allow plain addresses as well. */
   /* Allow the new standard form for IPv6 address literals, namely,
   [IPv6:....], and because someone is bound to use it, allow an equivalent
   IPv4 form. Allow plain addresses as well. */
@@ -1382,21 +1946,14 @@ if (!yield)
   /* Non-literals must be alpha, dot, hyphen, plus any non-valid chars
   that have been configured (usually underscore - sigh). */
 
   /* Non-literals must be alpha, dot, hyphen, plus any non-valid chars
   that have been configured (usually underscore - sigh). */
 
-  else if (*s != 0)
-    {
-    yield = TRUE;
-    while (*s != 0)
-      {
+  else if (*s)
+    for (yield = TRUE; *s; s++)
       if (!isalnum(*s) && *s != '.' && *s != '-' &&
           Ustrchr(helo_allow_chars, *s) == NULL)
         {
         yield = FALSE;
         break;
         }
       if (!isalnum(*s) && *s != '.' && *s != '-' &&
           Ustrchr(helo_allow_chars, *s) == NULL)
         {
         yield = FALSE;
         break;
         }
-      s++;
-      }
-    }
-  }
 
 /* Save argument if OK */
 
 
 /* Save argument if OK */
 
@@ -1430,21 +1987,27 @@ uschar *n;
 uschar *v = smtp_cmd_data + Ustrlen(smtp_cmd_data) - 1;
 while (isspace(*v)) v--;
 v[1] = 0;
 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 == '=')
 
 n = v;
 if (*v == '=')
-{
+  {
   while(isalpha(n[-1])) n--;
   /* RFC says SP, but TAB seen in wild and other major MTAs accept it */
   if (!isspace(n[-1])) return FALSE;
   n[-1] = 0;
   while(isalpha(n[-1])) n--;
   /* RFC says SP, but TAB seen in wild and other major MTAs accept it */
   if (!isspace(n[-1])) return FALSE;
   n[-1] = 0;
-}
+  }
 else
 else
-{
+  {
   n++;
   if (v == smtp_cmd_data) return FALSE;
   n++;
   if (v == smtp_cmd_data) return FALSE;
-}
+  }
 *v++ = 0;
 *name = n;
 *value = v;
 *v++ = 0;
 *name = n;
 *value = v;
@@ -1460,39 +2023,42 @@ return TRUE;
 *************************************************/
 
 /* This function is called whenever the SMTP session is reset from
 *************************************************/
 
 /* This function is called whenever the SMTP session is reset from
-within either of the setup functions.
+within either of the setup functions; also from the daemon loop.
 
 Argument:   the stacking pool storage reset point
 Returns:    nothing
 */
 
 
 Argument:   the stacking pool storage reset point
 Returns:    nothing
 */
 
-static void
+void
 smtp_reset(void *reset_point)
 {
 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;
 recipients_list = NULL;
 rcpt_count = rcpt_defer_count = rcpt_fail_count =
   raw_recipients_count = recipients_count = recipients_list_max = 0;
-cancel_cutthrough_connection("smtp reset");
 message_linecount = 0;
 message_size = -1;
 acl_added_headers = NULL;
 acl_removed_headers = NULL;
 message_linecount = 0;
 message_size = -1;
 acl_added_headers = NULL;
 acl_removed_headers = NULL;
-queue_only_policy = FALSE;
+f.queue_only_policy = FALSE;
 rcpt_smtp_response = NULL;
 rcpt_smtp_response = NULL;
-rcpt_smtp_response_same = TRUE;
-rcpt_in_progress = FALSE;
-deliver_freeze = FALSE;                              /* Can be set by ACL */
+fl.rcpt_smtp_response_same = TRUE;
+fl.rcpt_in_progress = FALSE;
+f.deliver_freeze = FALSE;                              /* Can be set by ACL */
 freeze_tell = freeze_tell_config;                    /* Can be set by ACL */
 fake_response = OK;                                  /* Can be set by ACL */
 #ifdef WITH_CONTENT_SCAN
 freeze_tell = freeze_tell_config;                    /* Can be set by ACL */
 fake_response = OK;                                  /* Can be set by ACL */
 #ifdef WITH_CONTENT_SCAN
-no_mbox_unspool = FALSE;                             /* Can be set by ACL */
+f.no_mbox_unspool = FALSE;                             /* Can be set by ACL */
 #endif
 #endif
-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;
+f.submission_mode = FALSE;                             /* Can be set by ACL */
+f.suppress_local_fixups = f.suppress_local_fixups_default; /* Can be set by ACL */
+f.active_local_from_check = local_from_check;          /* Can be set by ACL */
+f.active_local_sender_retain = local_sender_retain;    /* Can be set by ACL */
+sending_ip_address = NULL;
+return_path = sender_address = NULL;
+sender_data = NULL;                                 /* Can be set by ACL */
+deliver_localpart_parent = deliver_localpart_orig = NULL;
+deliver_domain_parent = 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 */
 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,27 +2066,41 @@ 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));
 
 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
 authenticated_sender = NULL;
 #ifdef EXPERIMENTAL_BRIGHTMAIL
 bmi_run = 0;
 bmi_verdicts = NULL;
 #endif
+dnslist_domain = dnslist_matched = NULL;
+#ifdef SUPPORT_SPF
+spf_header_comment = spf_received = spf_result = spf_smtp_comment = NULL;
+spf_result_guessed = FALSE;
+#endif
 #ifndef DISABLE_DKIM
 #ifndef DISABLE_DKIM
-dkim_signers = NULL;
-dkim_disable_verify = FALSE;
-dkim_collect_input = FALSE;
+dkim_cur_signer = dkim_signers =
+dkim_signing_domain = dkim_signing_selector = dkim_signatures = NULL;
+dkim_cur_signer = dkim_signers = dkim_signing_domain = dkim_signing_selector = NULL;
+f.dkim_disable_verify = FALSE;
+dkim_collect_input = 0;
+dkim_verify_overall = dkim_verify_status = dkim_verify_reason = NULL;
+dkim_key_length = 0;
+#endif
+#ifdef EXPERIMENTAL_DMARC
+f.dmarc_has_been_checked = f.dmarc_disable_verify = f.dmarc_enable_forensic = FALSE;
+dmarc_domain_policy = dmarc_status = dmarc_status_text =
+dmarc_used_domain = NULL;
+#endif
+#ifdef EXPERIMENTAL_ARC
+arc_state = arc_state_reason = NULL;
+#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
 #endif
-#ifdef EXPERIMENTAL_SPF
-spf_header_comment = NULL;
-spf_received = NULL;
-spf_result = NULL;
-spf_smtp_comment = NULL;
+#ifdef SUPPORT_I18N
+message_smtputf8 = FALSE;
 #endif
 body_linecount = body_zerocount = 0;
 
 #endif
 body_linecount = body_zerocount = 0;
 
@@ -1536,13 +2116,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. */
 
 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;
   }
 
   {
   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;
   {
   store_free(message_body_end);
   message_body_end = NULL;
@@ -1552,12 +2132,13 @@ if (message_body_end != NULL)
 repetition in the same message, but it seems right to repeat them for different
 messages. */
 
 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);
   }
   {
   string_item *this = acl_warn_logged;
   acl_warn_logged = acl_warn_logged->next;
   store_free(this);
   }
+store_reset(reset_point);
 }
 
 
 }
 
 
@@ -1594,6 +2175,7 @@ bsmtp_transaction_linecount = receive_linecount;
 
 if ((receive_feof)()) return 0;   /* Treat EOF as QUIT */
 
 
 if ((receive_feof)()) return 0;   /* Treat EOF as QUIT */
 
+cancel_cutthrough_connection(TRUE, US"smtp_setup_batch_msg");
 smtp_reset(reset_point);                /* Reset for start of message */
 
 /* Deal with SMTP commands. This loop is exited by setting done to a POSITIVE
 smtp_reset(reset_point);                /* Reset for start of message */
 
 /* Deal with SMTP commands. This loop is exited by setting done to a POSITIVE
@@ -1605,7 +2187,7 @@ while (done <= 0)
   uschar *recipient = NULL;
   int start, end, sender_domain, recipient_domain;
 
   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
     {
     /* The HELO/EHLO commands set sender_address_helo if they have
     valid data; otherwise they are ignored, except that they do
@@ -1614,13 +2196,14 @@ while (done <= 0)
     case HELO_CMD:
     case EHLO_CMD:
 
     case HELO_CMD:
     case EHLO_CMD:
 
-    check_helo(smtp_cmd_data);
-    /* Fall through */
+      check_helo(smtp_cmd_data);
+      /* Fall through */
 
     case RSET_CMD:
 
     case RSET_CMD:
-    smtp_reset(reset_point);
-    bsmtp_transaction_linecount = receive_linecount;
-    break;
+      cancel_cutthrough_connection(TRUE, US"RSET received");
+      smtp_reset(reset_point);
+      bsmtp_transaction_linecount = receive_linecount;
+      break;
 
 
     /* The MAIL FROM command requires an address as an operand. All we
 
 
     /* The MAIL FROM command requires an address as an operand. All we
@@ -1630,51 +2213,53 @@ while (done <= 0)
     it is the canonical extracted address which is all that is kept. */
 
     case MAIL_CMD:
     it is the canonical extracted address which is all that is kept. */
 
     case MAIL_CMD:
-    if (sender_address != NULL)
-      /* The function moan_smtp_batch() does not return. */
-      moan_smtp_batch(smtp_cmd_buffer, "503 Sender already given");
+      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");
 
 
-    if (smtp_cmd_data[0] == 0)
-      /* The function moan_smtp_batch() does not return. */
-      moan_smtp_batch(smtp_cmd_buffer, "501 MAIL FROM must have an address operand");
+      if (smtp_cmd_data[0] == 0)
+       /* The function moan_smtp_batch() does not return. */
+       moan_smtp_batch(smtp_cmd_buffer, "501 MAIL FROM must have an address operand");
 
 
-    /* Reset to start of message */
+      /* Reset to start of message */
 
 
-    smtp_reset(reset_point);
+      cancel_cutthrough_connection(TRUE, US"MAIL received");
+      smtp_reset(reset_point);
 
 
-    /* Apply SMTP rewrite */
+      /* Apply SMTP rewrite */
 
 
-    raw_sender = ((rewrite_existflags & rewrite_smtp) != 0)?
-      rewrite_one(smtp_cmd_data, rewrite_smtp|rewrite_smtp_sender, NULL, FALSE,
-        US"", global_rewrite_rules) : smtp_cmd_data;
+      raw_sender = ((rewrite_existflags & rewrite_smtp) != 0)?
+       rewrite_one(smtp_cmd_data, rewrite_smtp|rewrite_smtp_sender, NULL, FALSE,
+         US"", global_rewrite_rules) : smtp_cmd_data;
 
 
-    /* Extract the address; the TRUE flag allows <> as valid */
+      /* Extract the address; the TRUE flag allows <> as valid */
 
 
-    raw_sender =
-      parse_extract_address(raw_sender, &errmess, &start, &end, &sender_domain,
-        TRUE);
+      raw_sender =
+       parse_extract_address(raw_sender, &errmess, &start, &end, &sender_domain,
+         TRUE);
 
 
-    if (raw_sender == NULL)
-      /* The function moan_smtp_batch() does not return. */
-      moan_smtp_batch(smtp_cmd_buffer, "501 %s", errmess);
+      if (!raw_sender)
+       /* The function moan_smtp_batch() does not return. */
+       moan_smtp_batch(smtp_cmd_buffer, "501 %s", errmess);
 
 
-    sender_address = string_copy(raw_sender);
+      sender_address = string_copy(raw_sender);
 
 
-    /* Qualify unqualified sender addresses if permitted to do so. */
+      /* Qualify unqualified sender addresses if permitted to do so. */
 
 
-    if (sender_domain == 0 && sender_address[0] != 0 && sender_address[0] != '@')
-      {
-      if (allow_unqualified_sender)
-        {
-        sender_address = rewrite_address_qualify(sender_address, FALSE);
-        DEBUG(D_receive) debug_printf("unqualified address %s accepted "
-          "and rewritten\n", raw_sender);
-        }
-      /* The function moan_smtp_batch() does not return. */
-      else moan_smtp_batch(smtp_cmd_buffer, "501 sender address must contain "
-        "a domain");
-      }
-    break;
+      if (  !sender_domain
+         && sender_address[0] != 0 && sender_address[0] != '@')
+       if (f.allow_unqualified_sender)
+         {
+         sender_address = rewrite_address_qualify(sender_address, FALSE);
+         DEBUG(D_receive) debug_printf("unqualified address %s accepted "
+           "and rewritten\n", raw_sender);
+         }
+       /* The function moan_smtp_batch() does not return. */
+       else
+         moan_smtp_batch(smtp_cmd_buffer, "501 sender address must contain "
+           "a domain");
+      break;
 
 
     /* The RCPT TO command requires an address as an operand. All we do
 
 
     /* The RCPT TO command requires an address as an operand. All we do
@@ -1685,54 +2270,54 @@ while (done <= 0)
     extracted address. */
 
     case RCPT_CMD:
     extracted address. */
 
     case RCPT_CMD:
-    if (sender_address == NULL)
-      /* The function moan_smtp_batch() does not return. */
-      moan_smtp_batch(smtp_cmd_buffer, "503 No sender yet given");
-
-    if (smtp_cmd_data[0] == 0)
-      /* The function moan_smtp_batch() does not return. */
-      moan_smtp_batch(smtp_cmd_buffer, "501 RCPT TO must have an address operand");
-
-    /* Check maximum number allowed */
-
-    if (recipients_max > 0 && recipients_count + 1 > recipients_max)
-      /* The function moan_smtp_batch() does not return. */
-      moan_smtp_batch(smtp_cmd_buffer, "%s too many recipients",
-        recipients_max_reject? "552": "452");
-
-    /* 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;
-
-    /* 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)
-      /* The function moan_smtp_batch() does not return. */
-      moan_smtp_batch(smtp_cmd_buffer, "501 %s", errmess);
-
-    /* If the recipient address is unqualified, qualify it if permitted. Then
-    add it to the list of recipients. */
+      if (!sender_address)
+       /* The function moan_smtp_batch() does not return. */
+       moan_smtp_batch(smtp_cmd_buffer, "503 No sender yet given");
+
+      if (smtp_cmd_data[0] == 0)
+       /* The function moan_smtp_batch() does not return. */
+       moan_smtp_batch(smtp_cmd_buffer,
+         "501 RCPT TO must have an address operand");
+
+      /* Check maximum number allowed */
+
+      if (recipients_max > 0 && recipients_count + 1 > recipients_max)
+       /* The function moan_smtp_batch() does not return. */
+       moan_smtp_batch(smtp_cmd_buffer, "%s too many recipients",
+         recipients_max_reject? "552": "452");
+
+      /* Apply SMTP rewrite, then extract address. Don't allow "<>" as a
+      recipient address */
+
+      recipient = rewrite_existflags & rewrite_smtp
+       ? rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"",
+                     global_rewrite_rules)
+       : smtp_cmd_data;
+
+      recipient = parse_extract_address(recipient, &errmess, &start, &end,
+       &recipient_domain, FALSE);
+
+      if (!recipient)
+       /* The function moan_smtp_batch() does not return. */
+       moan_smtp_batch(smtp_cmd_buffer, "501 %s", errmess);
+
+      /* If the recipient address is unqualified, qualify it if permitted. Then
+      add it to the list of recipients. */
+
+      if (!recipient_domain)
+       if (f.allow_unqualified_recipient)
+         {
+         DEBUG(D_receive) debug_printf("unqualified address %s accepted\n",
+           recipient);
+         recipient = rewrite_address_qualify(recipient, TRUE);
+         }
+       /* The function moan_smtp_batch() does not return. */
+       else
+         moan_smtp_batch(smtp_cmd_buffer,
+           "501 recipient address must contain a domain");
 
 
-    if (recipient_domain == 0)
-      {
-      if (allow_unqualified_recipient)
-        {
-        DEBUG(D_receive) debug_printf("unqualified address %s accepted\n",
-          recipient);
-        recipient = rewrite_address_qualify(recipient, TRUE);
-        }
-      /* The function moan_smtp_batch() does not return. */
-      else moan_smtp_batch(smtp_cmd_buffer, "501 recipient address must contain "
-        "a domain");
-      }
-    receive_add_recipient(recipient, -1);
-    break;
+      receive_add_recipient(recipient, -1);
+      break;
 
 
     /* The DATA command is legal only if it follows successful MAIL FROM
 
 
     /* The DATA command is legal only if it follows successful MAIL FROM
@@ -1740,22 +2325,20 @@ while (done <= 0)
     command is encountered. */
 
     case DATA_CMD:
     command is encountered. */
 
     case DATA_CMD:
-    if (sender_address == NULL || recipients_count <= 0)
-      {
-      /* The function moan_smtp_batch() does not return. */
-      if (sender_address == NULL)
-        moan_smtp_batch(smtp_cmd_buffer,
-          "503 MAIL FROM:<sender> command must precede DATA");
+      if (!sender_address || recipients_count <= 0)
+       /* The function moan_smtp_batch() does not return. */
+       if (!sender_address)
+         moan_smtp_batch(smtp_cmd_buffer,
+           "503 MAIL FROM:<sender> command must precede DATA");
+       else
+         moan_smtp_batch(smtp_cmd_buffer,
+           "503 RCPT TO:<recipient> must precede DATA");
       else
       else
-        moan_smtp_batch(smtp_cmd_buffer,
-          "503 RCPT TO:<recipient> must precede DATA");
-      }
-    else
-      {
-      done = 3;                      /* DATA successfully achieved */
-      message_ended = END_NOTENDED;  /* Indicate in middle of message */
-      }
-    break;
+       {
+       done = 3;                      /* DATA successfully achieved */
+       message_ended = END_NOTENDED;  /* Indicate in middle of message */
+       }
+      break;
 
 
     /* The VRFY, EXPN, HELP, ETRN, and NOOP commands are ignored. */
 
 
     /* The VRFY, EXPN, HELP, ETRN, and NOOP commands are ignored. */
@@ -1765,32 +2348,32 @@ while (done <= 0)
     case HELP_CMD:
     case NOOP_CMD:
     case ETRN_CMD:
     case HELP_CMD:
     case NOOP_CMD:
     case ETRN_CMD:
-    bsmtp_transaction_linecount = receive_linecount;
-    break;
+      bsmtp_transaction_linecount = receive_linecount;
+      break;
 
 
     case EOF_CMD:
     case QUIT_CMD:
 
 
     case EOF_CMD:
     case QUIT_CMD:
-    done = 2;
-    break;
+      done = 2;
+      break;
 
 
     case BADARG_CMD:
 
 
     case BADARG_CMD:
-    /* The function moan_smtp_batch() does not return. */
-    moan_smtp_batch(smtp_cmd_buffer, "501 Unexpected argument data");
-    break;
+      /* The function moan_smtp_batch() does not return. */
+      moan_smtp_batch(smtp_cmd_buffer, "501 Unexpected argument data");
+      break;
 
 
     case BADCHAR_CMD:
 
 
     case BADCHAR_CMD:
-    /* The function moan_smtp_batch() does not return. */
-    moan_smtp_batch(smtp_cmd_buffer, "501 Unexpected NULL in SMTP command");
-    break;
+      /* The function moan_smtp_batch() does not return. */
+      moan_smtp_batch(smtp_cmd_buffer, "501 Unexpected NULL in SMTP command");
+      break;
 
 
     default:
 
 
     default:
-    /* The function moan_smtp_batch() does not return. */
-    moan_smtp_batch(smtp_cmd_buffer, "500 Command unrecognized");
-    break;
+      /* The function moan_smtp_batch() does not return. */
+      moan_smtp_batch(smtp_cmd_buffer, "500 Command unrecognized");
+      break;
     }
   }
 
     }
   }
 
@@ -1800,52 +2383,95 @@ return done - 2;  /* Convert yield values */
 
 
 
 
 
 
-/*************************************************
-*          Start an SMTP session                 *
-*************************************************/
-
-/* This function is called at the start of an SMTP session. Thereafter,
-smtp_setup_msg() is called to initiate each separate message. This
-function does host-specific testing, and outputs the banner line.
+#ifdef SUPPORT_TLS
+static BOOL
+smtp_log_tls_fail(uschar * errstr)
+{
+uschar * conn_info = smtp_get_connection_info();
 
 
-Arguments:     none
-Returns:       FALSE if the session can not continue; something has
-               gone wrong, or the connection to the host is blocked
-*/
+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", conn_info, errstr);
+return FALSE;
+}
+#endif
+
+
+
+
+#ifdef TCP_FASTOPEN
+static void
+tfo_in_check(void)
+{
+# ifdef TCP_INFO
+struct tcp_info tinfo;
+socklen_t len = sizeof(tinfo);
+
+if (getsockopt(fileno(smtp_out), IPPROTO_TCP, TCP_INFO, &tinfo, &len) == 0)
+#ifdef TCPI_OPT_SYN_DATA       /* FreeBSD 11 does not seem to have this yet */
+  if (tinfo.tcpi_options & TCPI_OPT_SYN_DATA)
+    {
+    DEBUG(D_receive) debug_printf("TCP_FASTOPEN mode connection (ACKd data-on-SYN)\n");
+    f.tcp_in_fastopen_data = f.tcp_in_fastopen = TRUE;
+    }
+  else
+#endif
+    if (tinfo.tcpi_state == TCP_SYN_RECV)
+    {
+    DEBUG(D_receive) debug_printf("TCP_FASTOPEN mode connection (state TCP_SYN_RECV)\n");
+    f.tcp_in_fastopen = TRUE;
+    }
+# endif
+}
+#endif
+
+
+/*************************************************
+*          Start an SMTP session                 *
+*************************************************/
+
+/* This function is called at the start of an SMTP session. Thereafter,
+smtp_setup_msg() is called to initiate each separate message. This
+function does host-specific testing, and outputs the banner line.
+
+Arguments:     none
+Returns:       FALSE if the session can not continue; something has
+               gone wrong, or the connection to the host is blocked
+*/
 
 BOOL
 smtp_start_session(void)
 {
 
 BOOL
 smtp_start_session(void)
 {
-int size = 256;
-int ptr, esclen;
+int esclen;
 uschar *user_msg, *log_msg;
 uschar *code, *esc;
 uschar *user_msg, *log_msg;
 uschar *code, *esc;
-uschar *p, *s, *ss;
+uschar *p, *s;
+gstring * ss;
 
 
-smtp_connection_start = time(NULL);
+gettimeofday(&smtp_connection_start, NULL);
 for (smtp_ch_index = 0; smtp_ch_index < SMTP_HBUFF_SIZE; smtp_ch_index++)
   smtp_connection_had[smtp_ch_index] = SCH_NONE;
 smtp_ch_index = 0;
 
 /* Default values for certain variables */
 
 for (smtp_ch_index = 0; smtp_ch_index < SMTP_HBUFF_SIZE; smtp_ch_index++)
   smtp_connection_had[smtp_ch_index] = SCH_NONE;
 smtp_ch_index = 0;
 
 /* Default values for certain variables */
 
-helo_seen = esmtp = helo_accept_junk = FALSE;
+fl.helo_seen = fl.esmtp = fl.helo_accept_junk = FALSE;
 smtp_mailcmd_count = 0;
 count_nonmail = TRUE_UNSET;
 synprot_error_count = unknown_command_count = nonmail_command_count = 0;
 smtp_delay_mail = smtp_rlm_base;
 smtp_mailcmd_count = 0;
 count_nonmail = TRUE_UNSET;
 synprot_error_count = unknown_command_count = nonmail_command_count = 0;
 smtp_delay_mail = smtp_rlm_base;
-auth_advertised = FALSE;
-pipelining_advertised = FALSE;
-pipelining_enable = TRUE;
+fl.auth_advertised = FALSE;
+f.smtp_in_pipelining_advertised = f.smtp_in_pipelining_used = FALSE;
+f.pipelining_enable = TRUE;
 sync_cmd_limit = NON_SYNC_CMD_NON_PIPELINING;
 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));
+fl.smtp_exit_function_called = FALSE;    /* For avoiding loop in not-quit exit */
 
 /* If receiving by -bs from a trusted user, or testing with -bh, we allow
 authentication settings from -oMaa to remain in force. */
 
 
 /* If receiving by -bs from a trusted user, or testing with -bh, we allow
 authentication settings from -oMaa to remain in force. */
 
-if (!host_checking && !sender_host_notsocket) sender_host_authenticated = NULL;
+if (!host_checking && !f.sender_host_notsocket)
+  sender_host_auth_pubname = sender_host_authenticated = NULL;
 authenticated_by = NULL;
 
 #ifdef SUPPORT_TLS
 authenticated_by = NULL;
 
 #ifdef SUPPORT_TLS
@@ -1853,10 +2479,14 @@ tls_in.cipher = tls_in.peerdn = NULL;
 tls_in.ourcert = tls_in.peercert = NULL;
 tls_in.sni = NULL;
 tls_in.ocsp = OCSP_NOT_REQ;
 tls_in.ourcert = tls_in.peercert = NULL;
 tls_in.sni = NULL;
 tls_in.ocsp = OCSP_NOT_REQ;
-tls_advertised = FALSE;
+fl.tls_advertised = FALSE;
+# ifdef EXPERIMENTAL_REQUIRETLS
+fl.requiretls_advertised = FALSE;
+# endif
 #endif
 #endif
-#ifdef EXPERIMENTAL_DSN
-dsn_advertised = FALSE;
+fl.dsn_advertised = FALSE;
+#ifdef SUPPORT_I18N
+fl.smtputf8_advertised = FALSE;
 #endif
 
 /* Reset ACL connection variables */
 #endif
 
 /* Reset ACL connection variables */
@@ -1865,19 +2495,19 @@ acl_var_c = NULL;
 
 /* Allow for trailing 0 in the command and data buffers. */
 
 
 /* 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");
   log_write(0, LOG_MAIN|LOG_PANIC_DIE,
     "malloc() failed for SMTP command buffer");
+
 smtp_cmd_buffer[0] = 0;
 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)
   {
 
 /* 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
   }
 
 /* For non-batched SMTP input, the protocol setting is forced here. It will be
@@ -1885,15 +2515,19 @@ reset later if any of EHLO/AUTH/STARTTLS are received. */
 
 else
   received_protocol =
 
 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
 
 /* Set up the buffer for inputting using direct read() calls, and arrange to
-call the local functions instead of the standard C ones. */
+call the local functions instead of the standard C ones.  Place a NUL at the
+end of the buffer to safety-stop C-string reads from it. */
 
 
-smtp_inbuffer = (uschar *)malloc(in_buffer_size);
-if (smtp_inbuffer == NULL)
+if (!(smtp_inbuffer = US malloc(IN_BUFFER_SIZE)))
   log_write(0, LOG_MAIN|LOG_PANIC_DIE, "malloc() failed for SMTP input buffer");
   log_write(0, LOG_MAIN|LOG_PANIC_DIE, "malloc() failed for SMTP input buffer");
+smtp_inbuffer[IN_BUFFER_SIZE-1] = '\0';
+
 receive_getc = smtp_getc;
 receive_getc = smtp_getc;
+receive_getbuf = smtp_getbuf;
+receive_get_cache = smtp_get_cache;
 receive_ungetc = smtp_ungetc;
 receive_feof = smtp_feof;
 receive_ferror = smtp_ferror;
 receive_ungetc = smtp_ungetc;
 receive_feof = smtp_feof;
 receive_ferror = smtp_ferror;
@@ -1904,7 +2538,7 @@ smtp_had_eof = smtp_had_error = 0;
 /* Set up the message size limit; this may be host-specific */
 
 thismessage_size_limit = expand_string_integer(message_size_limit, TRUE);
 /* Set up the message size limit; this may be host-specific */
 
 thismessage_size_limit = expand_string_integer(message_size_limit, TRUE);
-if (expand_string_message != NULL)
+if (expand_string_message)
   {
   if (thismessage_size_limit == -1)
     log_write(0, LOG_MAIN|LOG_PANIC, "unable to expand message_size_limit: "
   {
   if (thismessage_size_limit == -1)
     log_write(0, LOG_MAIN|LOG_PANIC, "unable to expand message_size_limit: "
@@ -1926,7 +2560,7 @@ the flag sender_host_notsocket is used to suppress it.
 If smtp_accept_max and smtp_accept_reserve are set, keep some connections in
 reserve for certain hosts and/or networks. */
 
 If smtp_accept_max and smtp_accept_reserve are set, keep some connections in
 reserve for certain hosts and/or networks. */
 
-if (!sender_host_unknown)
+if (!f.sender_host_unknown)
   {
   int rc;
   BOOL reserved_host = FALSE;
   {
   int rc;
   BOOL reserved_host = FALSE;
@@ -1956,7 +2590,7 @@ if (!sender_host_unknown)
 
   How to do this properly in IPv6 is not yet known. */
 
 
   How to do this properly in IPv6 is not yet known. */
 
-  #if !HAVE_IPV6 && !defined(NO_IP_OPTIONS)
+#if !HAVE_IPV6 && !defined(NO_IP_OPTIONS)
 
   #ifdef GLIBC_IP_OPTIONS
     #if (!defined __GLIBC__) || (__GLIBC__ < 2)
 
   #ifdef GLIBC_IP_OPTIONS
     #if (!defined __GLIBC__) || (__GLIBC__ < 2)
@@ -1970,7 +2604,7 @@ if (!sender_host_unknown)
     #define OPTSTYLE 3
   #endif
 
     #define OPTSTYLE 3
   #endif
 
-  if (!host_checking && !sender_host_notsocket)
+  if (!host_checking && !f.sender_host_notsocket)
     {
     #if OPTSTYLE == 1
     EXIM_SOCKLEN_T optlen = sizeof(struct ip_options) + MAX_IPOPTLEN;
     {
     #if OPTSTYLE == 1
     EXIM_SOCKLEN_T optlen = sizeof(struct ip_options) + MAX_IPOPTLEN;
@@ -1994,14 +2628,14 @@ if (!sender_host_unknown)
 
     DEBUG(D_receive) debug_printf("checking for IP options\n");
 
 
     DEBUG(D_receive) debug_printf("checking for IP options\n");
 
-    if (getsockopt(fileno(smtp_out), IPPROTO_IP, IP_OPTIONS, (uschar *)(ipopt),
+    if (getsockopt(fileno(smtp_out), IPPROTO_IP, IP_OPTIONS, US (ipopt),
           &optlen) < 0)
       {
       if (errno != ENOPROTOOPT)
         {
         log_write(0, LOG_MAIN, "getsockopt() failed from %s: %s",
           host_and_ident(FALSE), strerror(errno));
           &optlen) < 0)
       {
       if (errno != ENOPROTOOPT)
         {
         log_write(0, LOG_MAIN, "getsockopt() failed from %s: %s",
           host_and_ident(FALSE), strerror(errno));
-        smtp_printf("451 SMTP service not available\r\n");
+        smtp_printf("451 SMTP service not available\r\n", FALSE);
         return FALSE;
         }
       }
         return FALSE;
         }
       }
@@ -2020,11 +2654,11 @@ if (!sender_host_unknown)
       struct in_addr addr;
 
       #if OPTSTYLE == 1
       struct in_addr addr;
 
       #if OPTSTYLE == 1
-      uschar *optstart = (uschar *)(ipopt->__data);
+      uschar *optstart = US (ipopt->__data);
       #elif OPTSTYLE == 2
       #elif OPTSTYLE == 2
-      uschar *optstart = (uschar *)(ipopt->ip_opts);
+      uschar *optstart = US (ipopt->ip_opts);
       #else
       #else
-      uschar *optstart = (uschar *)(ipopt->ipopt_list);
+      uschar *optstart = US (ipopt->ipopt_list);
       #endif
 
       DEBUG(D_receive) debug_printf("IP options exist\n");
       #endif
 
       DEBUG(D_receive) debug_printf("IP options exist\n");
@@ -2033,7 +2667,7 @@ if (!sender_host_unknown)
       p += Ustrlen(p);
 
       for (opt = optstart; opt != NULL &&
       p += Ustrlen(p);
 
       for (opt = optstart; opt != NULL &&
-           opt < (uschar *)(ipopt) + optlen;)
+           opt < US (ipopt) + optlen;)
         {
         switch (*opt)
           {
         {
         switch (*opt)
           {
@@ -2087,10 +2721,7 @@ if (!sender_host_unknown)
             Ustrcat(p, "[ ");
             p += 2;
             for (i = 0; i < opt[1]; i++)
             Ustrcat(p, "[ ");
             p += 2;
             for (i = 0; i < opt[1]; i++)
-              {
-              sprintf(CS p, "%2.2x ", opt[i]);
-              p += 3;
-              }
+              p += sprintf(CS p, "%2.2x ", opt[i]);
             *p++ = ']';
             }
           opt += opt[1];
             *p++ = ']';
             }
           opt += opt[1];
@@ -2106,7 +2737,7 @@ if (!sender_host_unknown)
       log_write(0, LOG_MAIN|LOG_REJECT,
         "connection from %s refused (IP options)", host_and_ident(FALSE));
 
       log_write(0, LOG_MAIN|LOG_REJECT,
         "connection from %s refused (IP options)", host_and_ident(FALSE));
 
-      smtp_printf("554 SMTP service not available\r\n");
+      smtp_printf("554 SMTP service not available\r\n", FALSE);
       return FALSE;
       }
 
       return FALSE;
       }
 
@@ -2114,13 +2745,13 @@ if (!sender_host_unknown)
 
     else DEBUG(D_receive) debug_printf("no IP options found\n");
     }
 
     else DEBUG(D_receive) debug_printf("no IP options found\n");
     }
-  #endif  /* HAVE_IPV6 && !defined(NO_IP_OPTIONS) */
+#endif  /* HAVE_IPV6 && !defined(NO_IP_OPTIONS) */
 
   /* Set keep-alive in socket options. The option is on by default. This
   setting is an attempt to get rid of some hanging connections that stick in
   read() when the remote end (usually a dialup) goes away. */
 
 
   /* Set keep-alive in socket options. The option is on by default. This
   setting is an attempt to get rid of some hanging connections that stick in
   read() when the remote end (usually a dialup) goes away. */
 
-  if (smtp_accept_keepalive && !sender_host_notsocket)
+  if (smtp_accept_keepalive && !f.sender_host_notsocket)
     ip_keepalive(fileno(smtp_out), sender_host_address, FALSE);
 
   /* If the current host matches host_lookup, set the name by doing a
     ip_keepalive(fileno(smtp_out), sender_host_address, FALSE);
 
   /* If the current host matches host_lookup, set the name by doing a
@@ -2139,13 +2770,18 @@ if (!sender_host_unknown)
   set_process_info("handling incoming connection from %s",
     host_and_ident(FALSE));
 
   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 */
 
 
   /* Test for explicit connection rejection */
 
@@ -2153,7 +2789,7 @@ if (!sender_host_unknown)
     {
     log_write(L_connection_reject, LOG_MAIN|LOG_REJECT, "refused connection "
       "from %s (host_reject_connection)", host_and_ident(FALSE));
     {
     log_write(L_connection_reject, LOG_MAIN|LOG_REJECT, "refused connection "
       "from %s (host_reject_connection)", host_and_ident(FALSE));
-    smtp_printf("554 SMTP service not available\r\n");
+    smtp_printf("554 SMTP service not available\r\n", FALSE);
     return FALSE;
     }
 
     return FALSE;
     }
 
@@ -2166,19 +2802,17 @@ if (!sender_host_unknown)
   value of errno is 0 or ENOENT (which happens if /etc/hosts.{allow,deny} does
   not exist). */
 
   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;
   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);
     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,
   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)
       {
     {
     if (errno == 0 || errno == ENOENT)
       {
@@ -2186,7 +2820,7 @@ if (!sender_host_unknown)
       log_write(L_connection_reject,
                 LOG_MAIN|LOG_REJECT, "refused connection from %s "
                 "(tcp wrappers)", host_and_ident(FALSE));
       log_write(L_connection_reject,
                 LOG_MAIN|LOG_REJECT, "refused connection from %s "
                 "(tcp wrappers)", host_and_ident(FALSE));
-      smtp_printf("554 SMTP service not available\r\n");
+      smtp_printf("554 SMTP service not available\r\n", FALSE);
       }
     else
       {
       }
     else
       {
@@ -2196,11 +2830,11 @@ if (!sender_host_unknown)
       log_write(L_connection_reject,
                 LOG_MAIN|LOG_REJECT, "temporarily refused connection from %s "
                 "(tcp wrappers errno=%d)", host_and_ident(FALSE), save_errno);
       log_write(L_connection_reject,
                 LOG_MAIN|LOG_REJECT, "temporarily refused connection from %s "
                 "(tcp wrappers errno=%d)", host_and_ident(FALSE), save_errno);
-      smtp_printf("451 Temporary local problem - please try later\r\n");
+      smtp_printf("451 Temporary local problem - please try later\r\n", FALSE);
       }
     return FALSE;
     }
       }
     return FALSE;
     }
-  #endif
+#endif
 
   /* Check for reserved slots. The value of smtp_accept_count has already been
   incremented to include this process. */
 
   /* Check for reserved slots. The value of smtp_accept_count has already been
   incremented to include this process. */
@@ -2216,7 +2850,7 @@ if (!sender_host_unknown)
         host_and_ident(FALSE), smtp_accept_count - 1, smtp_accept_max,
         smtp_accept_reserve, (rc == DEFER)? " (lookup deferred)" : "");
       smtp_printf("421 %s: Too many concurrent SMTP connections; "
         host_and_ident(FALSE), smtp_accept_count - 1, smtp_accept_max,
         smtp_accept_reserve, (rc == DEFER)? " (lookup deferred)" : "");
       smtp_printf("421 %s: Too many concurrent SMTP connections; "
-        "please try again later\r\n", smtp_active_hostname);
+        "please try again later\r\n", FALSE, smtp_active_hostname);
       return FALSE;
       }
     reserved_host = TRUE;
       return FALSE;
       }
     reserved_host = TRUE;
@@ -2237,7 +2871,7 @@ if (!sender_host_unknown)
       LOG_MAIN, "temporarily refused connection from %s: not in "
       "reserve list and load average = %.2f", host_and_ident(FALSE),
       (double)load_average/1000.0);
       LOG_MAIN, "temporarily refused connection from %s: not in "
       "reserve list and load average = %.2f", host_and_ident(FALSE),
       (double)load_average/1000.0);
-    smtp_printf("421 %s: Too much load; please try again later\r\n",
+    smtp_printf("421 %s: Too much load; please try again later\r\n", FALSE,
       smtp_active_hostname);
     return FALSE;
     }
       smtp_active_hostname);
     return FALSE;
     }
@@ -2248,62 +2882,61 @@ if (!sender_host_unknown)
   addresses in the headers. For a site that permits no qualification, this
   won't take long, however. */
 
   addresses in the headers. For a site that permits no qualification, this
   won't take long, however. */
 
-  allow_unqualified_sender =
+  f.allow_unqualified_sender =
     verify_check_host(&sender_unqualified_hosts) == OK;
 
     verify_check_host(&sender_unqualified_hosts) == OK;
 
-  allow_unqualified_recipient =
+  f.allow_unqualified_recipient =
     verify_check_host(&recipient_unqualified_hosts) == OK;
 
   /* Determine whether HELO/EHLO is required for this host. The requirement
   can be hard or soft. */
 
     verify_check_host(&recipient_unqualified_hosts) == OK;
 
   /* Determine whether HELO/EHLO is required for this host. The requirement
   can be hard or soft. */
 
-  helo_required = verify_check_host(&helo_verify_hosts) == OK;
-  if (!helo_required)
-    helo_verify = verify_check_host(&helo_try_verify_hosts) == OK;
+  fl.helo_required = verify_check_host(&helo_verify_hosts) == OK;
+  if (!fl.helo_required)
+    fl.helo_verify = verify_check_host(&helo_try_verify_hosts) == OK;
 
   /* Determine whether this hosts is permitted to send syntactic junk
   after a HELO or EHLO command. */
 
 
   /* Determine whether this hosts is permitted to send syntactic junk
   after a HELO or EHLO command. */
 
-  helo_accept_junk = verify_check_host(&helo_accept_junk_hosts) == OK;
+  fl.helo_accept_junk = verify_check_host(&helo_accept_junk_hosts) == OK;
   }
 
 /* For batch SMTP input we are now done. */
 
 if (smtp_batched_input) return TRUE;
 
   }
 
 /* For batch SMTP input we are now done. */
 
 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. */
 /* 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 = FALSE;
-proxy_session_failed = FALSE;
+f.proxy_session_failed = FALSE;
 if (check_proxy_protocol_host())
 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
+  setup_proxy_protocol_host();
+#endif
+
+  /* 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)
     {
     {
-    sender_host_name = NULL;
-    (void)host_name_lookup();
-    host_build_sender_fullhost();
+    if (tls_server_start(tls_require_ciphers, &user_msg) != OK)
+      return smtp_log_tls_fail(user_msg);
+    cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = TRUE;
     }
     }
-  }
 #endif
 
 #endif
 
-/* Run the ACL if it exists */
+/* Run the connect ACL if it exists */
 
 user_msg = NULL;
 
 user_msg = NULL;
-if (acl_smtp_connect != NULL)
+if (acl_smtp_connect)
   {
   int rc;
   {
   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;
     }
   }
     return FALSE;
     }
   }
@@ -2315,10 +2948,9 @@ code = US"220";   /* Default status code */
 esc = US"";       /* Default extended status code */
 esclen = 0;       /* Length of esc */
 
 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);
   }
     log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Expansion of \"%s\" (smtp_banner) "
       "failed: %s", smtp_banner, expand_string_message);
   }
@@ -2326,7 +2958,7 @@ else
   {
   int codelen = 3;
   s = user_msg;
   {
   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;
   if (codelen > 4)
     {
     esc = code + 4;
@@ -2349,51 +2981,76 @@ command. Sigh. To try to avoid this, build the complete greeting message
 first, and output it in one fell swoop. This gives a better chance of it
 ending up as a single packet. */
 
 first, and output it in one fell swoop. This gives a better chance of it
 ending up as a single packet. */
 
-ss = store_get(size);
-ptr = 0;
+ss = string_get(256);
 
 p = s;
 do       /* At least once, in case we have an empty string */
   {
   int len;
   uschar *linebreak = Ustrchr(p, '\n');
 
 p = s;
 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);
-  if (linebreak == NULL)
+  ss = string_catn(ss, code, 3);
+  if (!linebreak)
     {
     len = Ustrlen(p);
     {
     len = Ustrlen(p);
-    ss = string_cat(ss, &size, &ptr, US" ", 1);
+    ss = string_catn(ss, US" ", 1);
     }
   else
     {
     len = linebreak - p;
     }
   else
     {
     len = linebreak - p;
-    ss = string_cat(ss, &size, &ptr, US"-", 1);
+    ss = string_catn(ss, 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, esc, esclen);
+  ss = string_catn(ss, p, len);
+  ss = string_catn(ss, US"\r\n", 2);
   p += len;
   p += len;
-  if (linebreak != NULL) p++;
+  if (linebreak) p++;
   }
   }
-while (*p != 0);
-
-ss[ptr] = 0;  /* string_cat leaves room for this */
+while (*p);
 
 /* Before we write the banner, check that there is no input pending, unless
 this synchronisation check is disabled. */
 
 
 /* Before we write the banner, check that there is no input pending, unless
 this synchronisation check is disabled. */
 
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+fl.pipe_connect_acceptable =
+  sender_host_address && verify_check_host(&pipe_connect_advertise_hosts) == OK;
+
 if (!check_sync())
 if (!check_sync())
-  {
-  log_write(0, LOG_MAIN|LOG_REJECT, "SMTP protocol "
-    "synchronization error (input sent without waiting for greeting): "
-    "rejected connection from %s input=\"%s\"", host_and_ident(TRUE),
-    string_printing(smtp_inptr));
-  smtp_printf("554 SMTP synchronization error\r\n");
-  return FALSE;
-  }
+  if (fl.pipe_connect_acceptable)
+    f.smtp_in_early_pipe_used = TRUE;
+  else
+#else
+if (!check_sync())
+#endif
+    {
+    unsigned n = smtp_inend - smtp_inptr;
+    if (n > 32) n = 32;
+
+    log_write(0, LOG_MAIN|LOG_REJECT, "SMTP protocol "
+      "synchronization error (input sent without waiting for greeting): "
+      "rejected connection from %s input=\"%s\"", host_and_ident(TRUE),
+      string_printing(string_copyn(smtp_inptr, n)));
+    smtp_printf("554 SMTP synchronization error\r\n", FALSE);
+    return FALSE;
+    }
 
 /* Now output the banner */
 
 /* Now output the banner */
+/*XXX the ehlo-resp code does its own tls/nontls bit.  Maybe subroutine that? */
+
+smtp_printf("%s",
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+  fl.pipe_connect_acceptable && pipeline_connect_sends(),
+#else
+  FALSE,
+#endif
+  string_from_gstring(ss));
+
+/* Attempt to see if we sent the banner before the last ACK of the 3-way
+handshake arrived.  If so we must have managed a TFO. */
+
+#ifdef TCP_FASTOPEN
+tfo_in_check();
+#endif
 
 
-smtp_printf("%s", ss);
 return TRUE;
 }
 
 return TRUE;
 }
 
@@ -2435,15 +3092,15 @@ 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\")",
   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)
   {
   }
 
 if (code > 0)
   {
-  smtp_printf("%d%c%s%s%s\r\n", code, (yield == 1)? '-' : ' ',
-    (data == NULL)? US"" : data, (data == NULL)? US"" : US": ", errmess);
+  smtp_printf("%d%c%s%s%s\r\n", FALSE, code, yield == 1 ? '-' : ' ',
+    data ? data : US"", data ? US": " : US"", errmess);
   if (yield == 1)
   if (yield == 1)
-    smtp_printf("%d Too many syntax or protocol errors\r\n", code);
+    smtp_printf("%d Too many syntax or protocol errors\r\n", FALSE, code);
   }
 
 return yield;
   }
 
 return yield;
@@ -2452,43 +3109,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      *
 *************************************************/
 /*************************************************
 *    Send SMTP response, possibly multiline      *
 *************************************************/
@@ -2512,7 +3132,7 @@ smtp_respond(uschar* code, int codelen, BOOL final, uschar *msg)
 int esclen = 0;
 uschar *esc = US"";
 
 int esclen = 0;
 uschar *esc = US"";
 
-if (!final && no_multiline_responses) return;
+if (!final && f.no_multiline_responses) return;
 
 if (codelen > 4)
   {
 
 if (codelen > 4)
   {
@@ -2526,35 +3146,37 @@ be tidier to have it only in one place, but when it was added, it was easier to
 do it that way, so as not to have to mess with the code for the RCPT command,
 which sometimes uses smtp_printf() and sometimes smtp_respond(). */
 
 do it that way, so as not to have to mess with the code for the RCPT command,
 which sometimes uses smtp_printf() and sometimes smtp_respond(). */
 
-if (rcpt_in_progress)
+if (fl.rcpt_in_progress)
   {
   if (rcpt_smtp_response == NULL)
     rcpt_smtp_response = string_copy(msg);
   {
   if (rcpt_smtp_response == NULL)
     rcpt_smtp_response = string_copy(msg);
-  else if (rcpt_smtp_response_same &&
+  else if (fl.rcpt_smtp_response_same &&
            Ustrcmp(rcpt_smtp_response, msg) != 0)
            Ustrcmp(rcpt_smtp_response, msg) != 0)
-    rcpt_smtp_response_same = FALSE;
-  rcpt_in_progress = FALSE;
+    fl.rcpt_smtp_response_same = FALSE;
+  fl.rcpt_in_progress = FALSE;
   }
 
   }
 
-/* Not output the message, splitting it up into multiple lines if necessary. */
+/* Now output the message, splitting it up into multiple lines if necessary.
+We only handle pipelining these responses as far as nonfinal/final groups,
+not the whole MAIL/RCPT/DATA response set. */
 
 for (;;)
   {
   uschar *nl = Ustrchr(msg, '\n');
   if (nl == NULL)
     {
 
 for (;;)
   {
   uschar *nl = Ustrchr(msg, '\n');
   if (nl == NULL)
     {
-    smtp_printf("%.3s%c%.*s%s\r\n", code, final? ' ':'-', esclen, esc, msg);
+    smtp_printf("%.3s%c%.*s%s\r\n", !final, code, final ? ' ':'-', esclen, esc, msg);
     return;
     }
     return;
     }
-  else if (nl[1] == 0 || no_multiline_responses)
+  else if (nl[1] == 0 || f.no_multiline_responses)
     {
     {
-    smtp_printf("%.3s%c%.*s%.*s\r\n", code, final? ' ':'-', esclen, esc,
+    smtp_printf("%.3s%c%.*s%.*s\r\n", !final, code, final ? ' ':'-', esclen, esc,
       (int)(nl - msg), msg);
     return;
     }
   else
     {
       (int)(nl - msg), msg);
     return;
     }
   else
     {
-    smtp_printf("%.3s-%.*s%.*s\r\n", code, esclen, esc, (int)(nl - msg), msg);
+    smtp_printf("%.3s-%.*s%.*s\r\n", TRUE, code, esclen, esc, (int)(nl - msg), msg);
     msg = nl + 1;
     while (isspace(*msg)) msg++;
     }
     msg = nl + 1;
     while (isspace(*msg)) msg++;
     }
@@ -2588,23 +3210,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
   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
 
 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];
 
 {
 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);
   {
   log_write(0, LOG_MAIN|LOG_PANIC, "configured error code starts with "
     "incorrect digit (expected %c) in \"%s\"", (*code)[0], *msg);
@@ -2629,7 +3252,7 @@ return;
 
 /* This function is called when acl_check() fails. As well as calls from within
 this module, it is called from receive.c for an ACL after DATA. It sorts out
 
 /* This function is called when acl_check() fails. As well as calls from within
 this module, it is called from receive.c for an ACL after DATA. It sorts out
-logging the incident, and sets up the error response. A message containing
+logging the incident, and sends the error response. A message containing
 newlines is turned into a multiline SMTP response, but for logging, only the
 first line is used.
 
 newlines is turned into a multiline SMTP response, but for logging, only the
 first line is used.
 
@@ -2639,18 +3262,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
 (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:
 
 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
 
 Returns:     0 in most cases
              2 if the failure code was FAIL_DROP, in which case the
@@ -2668,23 +3292,24 @@ uschar *lognl;
 uschar *sender_info = US"";
 uschar *what =
 #ifdef WITH_CONTENT_SCAN
 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
 #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
 #ifndef DISABLE_PRDR
-  (where == ACL_WHERE_PRDR)? US"after DATA PRDR" :
+  where == ACL_WHERE_PRDR ? US"after DATA PRDR" :
 #endif
 #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. */
 
 
 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
 
 /* 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,15 +3337,15 @@ 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. */
 
 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))
   {
     !testflag(sender_verified_failed, af_sverify_told))
   {
-  BOOL save_rcpt_in_progress = rcpt_in_progress;
-  rcpt_in_progress = FALSE;  /* So as not to treat these as the error */
+  BOOL save_rcpt_in_progress = fl.rcpt_in_progress;
+  fl.rcpt_in_progress = FALSE;  /* So as not to treat these as the error */
 
   setflag(sender_verified_failed, af_sverify_told);
 
 
   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",
     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 +3353,7 @@ if (sender_verified_failed != NULL &&
       (sender_verified_failed->message == NULL)? US"" :
       string_sprintf(": %s", sender_verified_failed->message));
 
       (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"
     smtp_respond(smtp_code, codelen, FALSE, string_sprintf(
         testflag(sender_verified_failed, af_verify_pmfail)?
           "Postmaster verification failed while checking <%s>\n%s\n"
@@ -2748,21 +3373,21 @@ if (sender_verified_failed != NULL &&
         sender_verified_failed->address,
         sender_verified_failed->user_message));
 
         sender_verified_failed->address,
         sender_verified_failed->user_message));
 
-  rcpt_in_progress = save_rcpt_in_progress;
+  fl.rcpt_in_progress = save_rcpt_in_progress;
   }
 
 /* Sort out text for logging */
 
   }
 
 /* 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. */
 
 
 /* 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"
 
 /* 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 +3398,19 @@ interactions between temp_details and return_error_details. One day it should
 be re-implemented in a tidier fashion. */
 
 else
 be re-implemented in a tidier fashion. */
 
 else
-  {
-  if (acl_temp_details && user_msg != NULL)
+  if (f.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, 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");
     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
 
 /* 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 +3420,20 @@ is closing if required and return 2.  */
 if (log_reject_target != 0)
   {
 #ifdef SUPPORT_TLS
 if (log_reject_target != 0)
   {
 #ifdef SUPPORT_TLS
-  uschar * s = s_tlslog(NULL, NULL, NULL);
-  if (!s) s = US"";
+  gstring * g = s_tlslog(NULL);
+  uschar * tls = string_from_gstring(g);
+  if (!tls) tls = US"";
 #else
 #else
-  uschar * s = US"";
+  uschar * tls = US"";
 #endif
 #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;
   }
 
 if (!drop) return 0;
@@ -2837,7 +3466,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
 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;
 
 Arguments:
   reason          What $smtp_notquit_reason will be set to in the ACL;
@@ -2855,47 +3484,47 @@ int rc;
 uschar *user_msg = NULL;
 uschar *log_msg = NULL;
 
 uschar *user_msg = NULL;
 uschar *log_msg = NULL;
 
-/* Check for recursive acll */
+/* Check for recursive call */
 
 
-if (smtp_exit_function_called)
+if (fl.smtp_exit_function_called)
   {
   log_write(0, LOG_PANIC, "smtp_notquit_exit() called more than once (%s)",
     reason);
   return;
   }
   {
   log_write(0, LOG_PANIC, "smtp_notquit_exit() called more than once (%s)",
     reason);
   return;
   }
-smtp_exit_function_called = TRUE;
+fl.smtp_exit_function_called = TRUE;
 
 /* Call the not-QUIT ACL, if there is one, unless no reason is given. */
 
 
 /* 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;
   {
   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);
   }
 
     log_write(0, LOG_MAIN|LOG_PANIC, "ACL for not-QUIT returned ERROR: %s",
       log_msg);
   }
 
+/* If the connection was dropped, we certainly are no longer talking TLS */
+tls_in.active.sock = -1;
+
 /* Write an SMTP response if we are expected to give one. As the default
 /* Write an SMTP response if we are expected to give one. As the default
-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. */
+responses are all internal, they should be reasonable size. */
 
 
-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];
+    gstring * g;
     va_list ap;
     va_list ap;
+
     va_start(ap, defaultrespond);
     va_start(ap, defaultrespond);
-    if (!string_vformat(buffer, sizeof(buffer), CS defaultrespond, ap))
-      log_write(0, LOG_MAIN|LOG_PANIC, "string too large in smtp_notquit_exit()");
-    smtp_printf("%s %s\r\n", code, buffer);
+    g = string_vformat(NULL, TRUE, CS defaultrespond, ap);
     va_end(ap);
     va_end(ap);
+    smtp_printf("%s %s\r\n", FALSE, code, string_from_gstring(g));
     }
     }
-  else
-    smtp_respond(code, 3, TRUE, user_msg);
   mac_smtp_fflush();
   }
 }
   mac_smtp_fflush();
   }
 }
@@ -2940,27 +3569,27 @@ if (sender_helo_name == NULL)
 else if (sender_host_address == NULL)
   {
   HDEBUG(D_receive) debug_printf("no client IP address: assume success\n");
 else if (sender_host_address == NULL)
   {
   HDEBUG(D_receive) debug_printf("no client IP address: assume success\n");
-  helo_verified = TRUE;
+  f.helo_verified = TRUE;
   }
 
 /* Deal with the more common case when there is a sending IP address */
 
 else if (sender_helo_name[0] == '[')
   {
   }
 
 /* Deal with the more common case when there is a sending IP address */
 
 else if (sender_helo_name[0] == '[')
   {
-  helo_verified = Ustrncmp(sender_helo_name+1, sender_host_address,
+  f.helo_verified = Ustrncmp(sender_helo_name+1, sender_host_address,
     Ustrlen(sender_host_address)) == 0;
 
     Ustrlen(sender_host_address)) == 0;
 
-  #if HAVE_IPV6
-  if (!helo_verified)
+#if HAVE_IPV6
+  if (!f.helo_verified)
     {
     if (strncmpic(sender_host_address, US"::ffff:", 7) == 0)
     {
     if (strncmpic(sender_host_address, US"::ffff:", 7) == 0)
-      helo_verified = Ustrncmp(sender_helo_name + 1,
+      f.helo_verified = Ustrncmp(sender_helo_name + 1,
         sender_host_address + 7, Ustrlen(sender_host_address) - 7) == 0;
     }
         sender_host_address + 7, Ustrlen(sender_host_address) - 7) == 0;
     }
-  #endif
+#endif
 
   HDEBUG(D_receive)
 
   HDEBUG(D_receive)
-    { if (helo_verified) debug_printf("matched host address\n"); }
+    { if (f.helo_verified) debug_printf("matched host address\n"); }
   }
 
 /* Do a reverse lookup if one hasn't already given a positive or negative
   }
 
 /* Do a reverse lookup if one hasn't already given a positive or negative
@@ -2974,63 +3603,64 @@ else
 
   /* If a host name is known, check it and all its aliases. */
 
 
   /* 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 ((f.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;
       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 ((f.helo_verified = strcmpic(*aliases++, sender_helo_name) == 0))
+         {
+         sender_helo_dnssec = sender_host_dnssec;
+         break;
+         }
+
+      HDEBUG(D_receive) if (f.helo_verified)
           debug_printf("matched alias %s\n", *(--aliases));
           debug_printf("matched alias %s\n", *(--aliases));
-        }
       }
       }
-    }
 
   /* Final attempt: try a forward lookup of the helo name */
 
 
   /* Final attempt: try a forward lookup of the helo name */
 
-  if (!helo_verified)
+  if (!f.helo_verified)
     {
     int rc;
     host_item h;
     {
     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;
     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);
     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 | HOST_FIND_BY_AAAA,
+                         NULL, NULL, NULL, &d, NULL, NULL);
     if (rc == HOST_FOUND || rc == HOST_FOUND_LOCAL)
     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)
           {
         if (Ustrcmp(hh->address, sender_host_address) == 0)
           {
-          helo_verified = TRUE;
+          f.helo_verified = TRUE;
+         if (h.dnssec == DS_YES) sender_helo_dnssec = TRUE;
           HDEBUG(D_receive)
           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;
           }
           break;
           }
-        hh = hh->next;
-        }
-      }
     }
   }
 
     }
   }
 
-if (!helo_verified) helo_verify_failed = TRUE;  /* We've tried ... */
+if (!f.helo_verified) f.helo_verify_failed = TRUE;  /* We've tried ... */
 return yield;
 }
 
 return yield;
 }
 
@@ -3057,12 +3687,182 @@ static void
 smtp_user_msg(uschar *code, uschar *user_msg)
 {
 int len = 3;
 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);
 }
 
 
 
 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;
+    sender_host_auth_pubname  = au->public_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.sock >= 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 (f.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", FALSE,
+  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;
+}
+
+
+
+
+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", FALSE, smtp_active_hostname);
+
+#ifdef SUPPORT_TLS
+tls_close(NULL, TLS_SHUTDOWN_NOWAIT);
+#endif
+
+log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT",
+  smtp_get_connection_info());
+}
+
+
+static void
+smtp_rset_handler(void)
+{
+HAD(SCH_RSET);
+incomplete_transaction_log(US"RSET");
+smtp_printf("250 Reset OK\r\n", FALSE);
+cmd_list[CMD_LIST_RSET].is_mail_cmd = FALSE;
+}
+
+
+
 /*************************************************
 *       Initialize for SMTP incoming message     *
 *************************************************/
 /*************************************************
 *       Initialize for SMTP incoming message     *
 *************************************************/
@@ -3109,6 +3909,8 @@ for the host). Note: we do NOT reset AUTH at this point. */
 smtp_reset(reset_point);
 message_ended = END_NOTSTARTED;
 
 smtp_reset(reset_point);
 message_ended = END_NOTSTARTED;
 
+chunking_state = f.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;
 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;
@@ -3118,6 +3920,7 @@ cmd_list[CMD_LIST_STARTTLS].is_mail_cmd = TRUE;
 
 /* Set the local signal handler for SIGTERM - it tries to end off tidily */
 
 
 /* Set the local signal handler for SIGTERM - it tries to end off tidily */
 
+had_command_sigterm = 0;
 os_non_restarting_signal(SIGTERM, command_sigterm_handler);
 
 /* Batched SMTP is handled in a different function. */
 os_non_restarting_signal(SIGTERM, command_sigterm_handler);
 
 /* Batched SMTP is handled in a different function. */
@@ -3129,7 +3932,7 @@ value. The values are 2 larger than the required yield of the function. */
 
 while (done <= 0)
   {
 
 while (done <= 0)
   {
-  uschar **argv;
+  const uschar **argv;
   uschar *etrn_command;
   uschar *etrn_serialize_key;
   uschar *errmess;
   uschar *etrn_command;
   uschar *etrn_serialize_key;
   uschar *errmess;
@@ -3137,22 +3940,64 @@ while (done <= 0)
   uschar *user_msg = NULL;
   uschar *recipient = NULL;
   uschar *hello = NULL;
   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;
   void (*oldsignal)(int);
   pid_t pid;
   int start, end, sender_domain, recipient_domain;
   uschar *s, *ss;
   BOOL was_rej_mail = FALSE;
   BOOL was_rcpt = FALSE;
   void (*oldsignal)(int);
   pid_t pid;
   int start, end, sender_domain, recipient_domain;
-  int ptr, size, rc;
-  int c, i;
+  int rc;
+  int c;
   auth_instance *au;
   auth_instance *au;
-#ifdef EXPERIMENTAL_DSN
   uschar *orcpt = NULL;
   uschar *orcpt = NULL;
-  int flags;
+  int dsn_flags;
+  gstring * g;
+
+#ifdef AUTH_TLS
+  /* Check once per STARTTLS or SSL-on-connect for a TLS AUTH */
+  if (  tls_in.active.sock >= 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;
+
+    for (au = auths; au; au = au->next)
+      if (strcmpic(US"tls", au->driver_name) == 0)
+       {
+       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);
+       else
+         {
+         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
 
 #endif
 
-  switch(smtp_read_command(TRUE))
+  switch(smtp_read_command(
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+         !fl.pipe_connect_acceptable,
+#else
+         TRUE,
+#endif
+         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
     {
     /* The AUTH command is not permitted to occur inside a transaction, and may
     occur successfully only once per connection. Actually, that isn't quite
@@ -3169,567 +4014,519 @@ while (done <= 0)
     AUTHS will eventually hit the nonmail threshold. */
 
     case AUTH_CMD:
     AUTHS will eventually hit the nonmail threshold. */
 
     case AUTH_CMD:
-    HAD(SCH_AUTH);
-    authentication_failed = TRUE;
-    cmd_list[CMD_LIST_AUTH].is_mail_cmd = FALSE;
-
-    if (!auth_advertised && !allow_auth_unadvertised)
-      {
-      done = synprot_error(L_smtp_protocol_error, 503, NULL,
-        US"AUTH command used when not advertised");
-      break;
-      }
-    if (sender_host_authenticated != NULL)
-      {
-      done = synprot_error(L_smtp_protocol_error, 503, NULL,
-        US"already authenticated");
-      break;
-      }
-    if (sender_address != NULL)
-      {
-      done = synprot_error(L_smtp_protocol_error, 503, NULL,
-        US"not permitted in mail transaction");
-      break;
-      }
-
-    /* Check the ACL */
+      HAD(SCH_AUTH);
+      authentication_failed = TRUE;
+      cmd_list[CMD_LIST_AUTH].is_mail_cmd = FALSE;
+
+      if (!fl.auth_advertised && !f.allow_auth_unadvertised)
+       {
+       done = synprot_error(L_smtp_protocol_error, 503, NULL,
+         US"AUTH command used when not advertised");
+       break;
+       }
+      if (sender_host_authenticated)
+       {
+       done = synprot_error(L_smtp_protocol_error, 503, NULL,
+         US"already authenticated");
+       break;
+       }
+      if (sender_address)
+       {
+       done = synprot_error(L_smtp_protocol_error, 503, NULL,
+         US"not permitted in mail transaction");
+       break;
+       }
 
 
-    if (acl_smtp_auth != NULL)
-      {
-      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;
-        }
-      }
+      /* Check the ACL */
 
 
-    /* Find the name of the requested authentication mechanism. */
+      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);
+       break;
+       }
 
 
-    s = smtp_cmd_data;
-    while ((c = *smtp_cmd_data) != 0 && !isspace(c))
-      {
-      if (!isalnum(c) && c != '-' && c != '_')
-        {
-        done = synprot_error(L_smtp_syntax_error, 501, NULL,
-          US"invalid character in authentication mechanism name");
-        goto COMMAND_LOOP;
-        }
-      smtp_cmd_data++;
-      }
+      /* Find the name of the requested authentication mechanism. */
+
+      s = smtp_cmd_data;
+      while ((c = *smtp_cmd_data) != 0 && !isspace(c))
+       {
+       if (!isalnum(c) && c != '-' && c != '_')
+         {
+         done = synprot_error(L_smtp_syntax_error, 501, NULL,
+           US"invalid character in authentication mechanism name");
+         goto COMMAND_LOOP;
+         }
+       smtp_cmd_data++;
+       }
 
 
-    /* If not at the end of the line, we must be at white space. Terminate the
-    name and move the pointer on to any data that may be present. */
+      /* If not at the end of the line, we must be at white space. Terminate the
+      name and move the pointer on to any data that may be present. */
 
 
-    if (*smtp_cmd_data != 0)
-      {
-      *smtp_cmd_data++ = 0;
-      while (isspace(*smtp_cmd_data)) smtp_cmd_data++;
-      }
+      if (*smtp_cmd_data != 0)
+       {
+       *smtp_cmd_data++ = 0;
+       while (isspace(*smtp_cmd_data)) smtp_cmd_data++;
+       }
 
 
-    /* Search for an authentication mechanism which is configured for use
-    as a server and which has been advertised (unless, sigh, allow_auth_
-    unadvertised is set). */
+      /* Search for an authentication mechanism which is configured for use
+      as a server and which has been advertised (unless, sigh, allow_auth_
+      unadvertised is set). */
 
 
-    for (au = auths; au != NULL; au = au->next)
-      {
-      if (strcmpic(s, au->public_name) == 0 && au->server &&
-          (au->advertised || allow_auth_unadvertised)) break;
-      }
+      for (au = auths; au; au = au->next)
+       if (strcmpic(s, au->public_name) == 0 && au->server &&
+           (au->advertised || f.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;
-      }
+      if (au)
+       {
+       c = smtp_in_auth(au, &s, &ss);
 
 
-    /* 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.
+       smtp_printf("%s\r\n", FALSE, 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));
 
 
-    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. */
+      break;  /* AUTH_CMD */
 
 
-    for (i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL;
-    expand_nmax = 0;
-    expand_nlength[0] = 0;   /* $0 contains nothing */
+    /* The HELO/EHLO commands are permitted to appear in the middle of a
+    session as well as at the beginning. They have the effect of a reset in
+    addition to their other functions. Their absence at the start cannot be
+    taken to be an error.
 
 
-    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> */
+    RFC 2821 says:
 
 
-    /* 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"";
-      }
-
-    /* Switch on the result */
-
-    switch(c)
-      {
-      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;
-
-      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);
-
-    break;  /* AUTH_CMD */
-
-    /* The HELO/EHLO commands are permitted to appear in the middle of a
-    session as well as at the beginning. They have the effect of a reset in
-    addition to their other functions. Their absence at the start cannot be
-    taken to be an error.
-
-    RFC 2821 says:
-
-      If the EHLO command is not acceptable to the SMTP server, 501, 500,
-      or 502 failure replies MUST be returned as appropriate.  The SMTP
-      server MUST stay in the same state after transmitting these replies
-      that it was in before the EHLO was received.
+      If the EHLO command is not acceptable to the SMTP server, 501, 500,
+      or 502 failure replies MUST be returned as appropriate.  The SMTP
+      server MUST stay in the same state after transmitting these replies
+      that it was in before the EHLO was received.
 
     Therefore, we do not do the reset until after checking the command for
     acceptability. This change was made for Exim release 4.11. Previously
     it did the reset first. */
 
     case HELO_CMD:
 
     Therefore, we do not do the reset until after checking the command for
     acceptability. This change was made for Exim release 4.11. Previously
     it did the reset first. */
 
     case HELO_CMD:
-    HAD(SCH_HELO);
-    hello = US"HELO";
-    esmtp = FALSE;
-    goto HELO_EHLO;
+      HAD(SCH_HELO);
+      hello = US"HELO";
+      fl.esmtp = FALSE;
+      goto HELO_EHLO;
 
     case EHLO_CMD:
 
     case EHLO_CMD:
-    HAD(SCH_EHLO);
-    hello = US"EHLO";
-    esmtp = TRUE;
+      HAD(SCH_EHLO);
+      hello = US"EHLO";
+      fl.esmtp = TRUE;
 
     HELO_EHLO:      /* Common code for HELO and EHLO */
 
     HELO_EHLO:      /* Common code for HELO and EHLO */
-    cmd_list[CMD_LIST_HELO].is_mail_cmd = FALSE;
-    cmd_list[CMD_LIST_EHLO].is_mail_cmd = FALSE;
-
-    /* Reject the HELO if its argument was invalid or non-existent. A
-    successful check causes the argument to be saved in malloc store. */
-
-    if (!check_helo(smtp_cmd_data))
-      {
-      smtp_printf("501 Syntactically invalid %s argument(s)\r\n", hello);
-
-      log_write(0, LOG_MAIN|LOG_REJECT, "rejected %s from %s: syntactically "
-        "invalid argument(s): %s", hello, host_and_ident(FALSE),
-        (*smtp_cmd_argument == 0)? US"(no argument given)" :
-                           string_printing(smtp_cmd_argument));
-
-      if (++synprot_error_count > smtp_max_synprot_errors)
-        {
-        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);
-        done = 1;
-        }
-
-      break;
-      }
-
-    /* If sender_host_unknown is true, we have got here via the -bs interface,
-    not called from inetd. Otherwise, we are running an IP connection and the
-    host address will be set. If the helo name is the primary name of this
-    host and we haven't done a reverse lookup, force one now. If helo_required
-    is set, ensure that the HELO name matches the actual host. If helo_verify
-    is set, do the same check, but softly. */
-
-    if (!sender_host_unknown)
-      {
-      BOOL old_helo_verified = helo_verified;
-      uschar *p = smtp_cmd_data;
-
-      while (*p != 0 && !isspace(*p)) { *p = tolower(*p); p++; }
-      *p = 0;
-
-      /* Force a reverse lookup if HELO quoted something in helo_lookup_domains
-      because otherwise the log can be confusing. */
-
-      if (sender_host_name == NULL &&
-           (deliver_domain = sender_helo_name,  /* set $domain */
-            match_isinlist(sender_helo_name, &helo_lookup_domains, 0,
-              &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL)) == OK)
-        (void)host_name_lookup();
-
-      /* Rebuild the fullhost info to include the HELO name (and the real name
-      if it was looked up.) */
-
-      host_build_sender_fullhost();  /* Rebuild */
-      set_process_info("handling%s incoming connection from %s",
-        (tls_in.active >= 0)? " TLS" : "", host_and_ident(FALSE));
-
-      /* Verify if configured. This doesn't give much security, but it does
-      make some people happy to be able to do it. If helo_required is set,
-      (host matches helo_verify_hosts) failure forces rejection. If helo_verify
-      is set (host matches helo_try_verify_hosts), it does not. This is perhaps
-      now obsolescent, since the verification can now be requested selectively
-      at ACL time. */
+      cmd_list[CMD_LIST_HELO].is_mail_cmd = FALSE;
+      cmd_list[CMD_LIST_EHLO].is_mail_cmd = FALSE;
+
+      /* Reject the HELO if its argument was invalid or non-existent. A
+      successful check causes the argument to be saved in malloc store. */
+
+      if (!check_helo(smtp_cmd_data))
+       {
+       smtp_printf("501 Syntactically invalid %s argument(s)\r\n", FALSE, hello);
+
+       log_write(0, LOG_MAIN|LOG_REJECT, "rejected %s from %s: syntactically "
+         "invalid argument(s): %s", hello, host_and_ident(FALSE),
+         *smtp_cmd_argument == 0 ? US"(no argument given)" :
+                            string_printing(smtp_cmd_argument));
+
+       if (++synprot_error_count > smtp_max_synprot_errors)
+         {
+         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), string_printing(smtp_cmd_buffer));
+         done = 1;
+         }
+
+       break;
+       }
 
 
-      helo_verified = helo_verify_failed = FALSE;
-      if (helo_required || helo_verify)
-        {
-        BOOL tempfail = !smtp_verify_helo();
-        if (!helo_verified)
-          {
-          if (helo_required)
-            {
-            smtp_printf("%d %s argument does not match calling host\r\n",
-              tempfail? 451 : 550, hello);
-            log_write(0, LOG_MAIN|LOG_REJECT, "%srejected \"%s %s\" from %s",
-              tempfail? "temporarily " : "",
-              hello, sender_helo_name, host_and_ident(FALSE));
-            helo_verified = old_helo_verified;
-            break;                   /* End of HELO/EHLO processing */
-            }
-          HDEBUG(D_all) debug_printf("%s verification failed but host is in "
-            "helo_try_verify_hosts\n", hello);
-          }
-        }
-      }
+      /* If sender_host_unknown is true, we have got here via the -bs interface,
+      not called from inetd. Otherwise, we are running an IP connection and the
+      host address will be set. If the helo name is the primary name of this
+      host and we haven't done a reverse lookup, force one now. If helo_required
+      is set, ensure that the HELO name matches the actual host. If helo_verify
+      is set, do the same check, but softly. */
+
+      if (!f.sender_host_unknown)
+       {
+       BOOL old_helo_verified = f.helo_verified;
+       uschar *p = smtp_cmd_data;
+
+       while (*p != 0 && !isspace(*p)) { *p = tolower(*p); p++; }
+       *p = 0;
+
+       /* Force a reverse lookup if HELO quoted something in helo_lookup_domains
+       because otherwise the log can be confusing. */
+
+       if (  !sender_host_name
+          && match_isinlist(sender_helo_name, CUSS &helo_lookup_domains, 0,
+               &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL) == OK)
+         (void)host_name_lookup();
+
+       /* Rebuild the fullhost info to include the HELO name (and the real name
+       if it was looked up.) */
+
+       host_build_sender_fullhost();  /* Rebuild */
+       set_process_info("handling%s incoming connection from %s",
+         tls_in.active.sock >= 0 ? " TLS" : "", host_and_ident(FALSE));
+
+       /* Verify if configured. This doesn't give much security, but it does
+       make some people happy to be able to do it. If helo_required is set,
+       (host matches helo_verify_hosts) failure forces rejection. If helo_verify
+       is set (host matches helo_try_verify_hosts), it does not. This is perhaps
+       now obsolescent, since the verification can now be requested selectively
+       at ACL time. */
+
+       f.helo_verified = f.helo_verify_failed = sender_helo_dnssec = FALSE;
+       if (fl.helo_required || fl.helo_verify)
+         {
+         BOOL tempfail = !smtp_verify_helo();
+         if (!f.helo_verified)
+           {
+           if (fl.helo_required)
+             {
+             smtp_printf("%d %s argument does not match calling host\r\n", FALSE,
+               tempfail? 451 : 550, hello);
+             log_write(0, LOG_MAIN|LOG_REJECT, "%srejected \"%s %s\" from %s",
+               tempfail? "temporarily " : "",
+               hello, sender_helo_name, host_and_ident(FALSE));
+             f.helo_verified = old_helo_verified;
+             break;                   /* End of HELO/EHLO processing */
+             }
+           HDEBUG(D_all) debug_printf("%s verification failed but host is in "
+             "helo_try_verify_hosts\n", hello);
+           }
+         }
+       }
 
 
-#ifdef EXPERIMENTAL_SPF
-    /* set up SPF context */
-    spf_init(sender_helo_name, sender_host_address);
+#ifdef SUPPORT_SPF
+      /* set up SPF context */
+      spf_init(sender_helo_name, sender_host_address);
 #endif
 
 #endif
 
-    /* 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)
-        {
-        done = smtp_handle_acl_fail(ACL_WHERE_HELO, rc, user_msg, log_msg);
-        sender_helo_name = NULL;
-        host_build_sender_fullhost();  /* Rebuild */
-        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
-    as a deterrent to mail forgers. For maximum efficiency, and also because
-    some broken systems expect each response to be in a single packet, arrange
-    that the entire reply is sent in one write(). */
-
-    auth_advertised = FALSE;
-    pipelining_advertised = FALSE;
-    #ifdef SUPPORT_TLS
-    tls_advertised = FALSE;
-    #endif
-    #ifdef EXPERIMENTAL_DSN
-    dsn_advertised = FALSE;
-    #endif
-
-    smtp_code = US"250 ";        /* Default response code plus space*/
-    if (user_msg == NULL)
-      {
-      s = string_sprintf("%.3s %s Hello %s%s%s",
-        smtp_code,
-        smtp_active_hostname,
-        (sender_ident == NULL)?  US"" : sender_ident,
-        (sender_ident == NULL)?  US"" : US" at ",
-        (sender_host_name == NULL)? sender_helo_name : sender_host_name);
-
-      ptr = Ustrlen(s);
-      size = ptr + 1;
-
-      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);
-        }
-      }
-
-    /* A user-supplied EHLO greeting may not contain more than one line. Note
-    that the code returned by smtp_message_code() includes the terminating
-    whitespace character. */
-
-    else
-      {
-      char *ss;
-      int codelen = 4;
-      smtp_message_code(&smtp_code, &codelen, &user_msg, NULL);
-      s = string_sprintf("%.*s%s", codelen, smtp_code, user_msg);
-      if ((ss = strpbrk(CS s, "\r\n")) != NULL)
-        {
-        log_write(0, LOG_MAIN|LOG_PANIC, "EHLO/HELO response must not contain "
-          "newlines: message truncated: %s", string_printing(s));
-        *ss = 0;
-        }
-      ptr = Ustrlen(s);
-      size = ptr + 1;
-      }
+      /* Apply an ACL check if one is defined; afterwards, recheck
+      synchronization in case the client started sending in a delay. */
+
+      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);
+         if (sender_helo_name)
+           {
+           store_free(sender_helo_name);
+           sender_helo_name = NULL;
+           }
+         host_build_sender_fullhost();  /* Rebuild */
+         break;
+         }
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+       else if (!fl.pipe_connect_acceptable && !check_sync())
+#else
+       else if (!check_sync())
+#endif
+         goto SYNC_FAILURE;
 
 
-    s = string_cat(s, &size, &ptr, US"\r\n", 2);
+      /* 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
+      as a deterrent to mail forgers. For maximum efficiency, and also because
+      some broken systems expect each response to be in a single packet, arrange
+      that the entire reply is sent in one write(). */
 
 
-    /* If we received EHLO, we must create a multiline response which includes
-    the functions supported. */
+      fl.auth_advertised = FALSE;
+      f.smtp_in_pipelining_advertised = FALSE;
+#ifdef SUPPORT_TLS
+      fl.tls_advertised = FALSE;
+# ifdef EXPERIMENTAL_REQUIRETLS
+      fl.requiretls_advertised = FALSE;
+# endif
+#endif
+      fl.dsn_advertised = FALSE;
+#ifdef SUPPORT_I18N
+      fl.smtputf8_advertised = FALSE;
+#endif
 
 
-    if (esmtp)
-      {
-      s[3] = '-';
+      smtp_code = US"250 ";        /* Default response code plus space*/
+      if (!user_msg)
+       {
+       g = string_fmt_append(NULL, "%.3s %s Hello %s%s%s",
+         smtp_code,
+         smtp_active_hostname,
+         sender_ident ? sender_ident : US"",
+         sender_ident ? US" at " : US"",
+         sender_host_name ? sender_host_name : sender_helo_name);
+
+       if (sender_host_address)
+         g = string_fmt_append(g, " [%s]", sender_host_address);
+       }
 
 
-      /* I'm not entirely happy with this, as an MTA is supposed to check
-      that it has enough room to accept a message of maximum size before
-      it sends this. However, there seems little point in not sending it.
-      The actual size check happens later at MAIL FROM time. By postponing it
-      till then, VRFY and EXPN can be used after EHLO when space is short. */
+      /* A user-supplied EHLO greeting may not contain more than one line. Note
+      that the code returned by smtp_message_code() includes the terminating
+      whitespace character. */
 
 
-      if (thismessage_size_limit > 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));
-        }
       else
       else
-        {
-        s = string_cat(s, &size, &ptr, smtp_code, 3);
-        s = string_cat(s, &size, &ptr, US"-SIZE\r\n", 7);
-        }
-
-      /* Exim does not do protocol conversion or data conversion. It is 8-bit
-      clean; if it has an 8-bit character in its hand, it just sends it. It
-      cannot therefore specify 8BITMIME and remain consistent with the RFCs.
-      However, some users want this option simply in order to stop MUAs
-      mangling messages that contain top-bit-set characters. It is therefore
-      provided as an option. */
-
-      if (accept_8bitmime)
-        {
-        s = string_cat(s, &size, &ptr, smtp_code, 3);
-        s = string_cat(s, &size, &ptr, US"-8BITMIME\r\n", 11);
-        }
-
-      #ifdef EXPERIMENTAL_DSN
-      /* Advertise DSN support if configured to do so. */
-      if (verify_check_host(&dsn_advertise_hosts) != FAIL) 
-        {
-        s = string_cat(s, &size, &ptr, smtp_code, 3);
-        s = string_cat(s, &size, &ptr, US"-DSN\r\n", 6);
-        dsn_advertised = TRUE;
-        }
-      #endif
-
-      /* Advertise ETRN if there's an ACL checking whether a host is
-      permitted to issue it; a check is made when any host actually tries. */
-
-      if (acl_smtp_etrn != NULL)
-        {
-        s = string_cat(s, &size, &ptr, smtp_code, 3);
-        s = string_cat(s, &size, &ptr, US"-ETRN\r\n", 7);
-        }
+       {
+       char *ss;
+       int codelen = 4;
+       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)
+         {
+         log_write(0, LOG_MAIN|LOG_PANIC, "EHLO/HELO response must not contain "
+           "newlines: message truncated: %s", string_printing(s));
+         *ss = 0;
+         }
+       g = string_cat(NULL, s);
+       }
 
 
-      /* 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. */
+      g = string_catn(g, US"\r\n", 2);
+
+      /* If we received EHLO, we must create a multiline response which includes
+      the functions supported. */
+
+      if (fl.esmtp)
+       {
+       g->s[3] = '-';
+
+       /* I'm not entirely happy with this, as an MTA is supposed to check
+       that it has enough room to accept a message of maximum size before
+       it sends this. However, there seems little point in not sending it.
+       The actual size check happens later at MAIL FROM time. By postponing it
+       till then, VRFY and EXPN can be used after EHLO when space is short. */
+
+       if (thismessage_size_limit > 0)
+         g = string_fmt_append(g, "%.3s-SIZE %d\r\n", smtp_code,
+           thismessage_size_limit);
+       else
+         {
+         g = string_catn(g, smtp_code, 3);
+         g = string_catn(g, US"-SIZE\r\n", 7);
+         }
+
+       /* Exim does not do protocol conversion or data conversion. It is 8-bit
+       clean; if it has an 8-bit character in its hand, it just sends it. It
+       cannot therefore specify 8BITMIME and remain consistent with the RFCs.
+       However, some users want this option simply in order to stop MUAs
+       mangling messages that contain top-bit-set characters. It is therefore
+       provided as an option. */
+
+       if (accept_8bitmime)
+         {
+         g = string_catn(g, smtp_code, 3);
+         g = string_catn(g, US"-8BITMIME\r\n", 11);
+         }
+
+       /* Advertise DSN support if configured to do so. */
+       if (verify_check_host(&dsn_advertise_hosts) != FAIL)
+         {
+         g = string_catn(g, smtp_code, 3);
+         g = string_catn(g, US"-DSN\r\n", 6);
+         fl.dsn_advertised = TRUE;
+         }
+
+       /* 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)
+         {
+         g = string_catn(g, smtp_code, 3);
+         g = string_catn(g, US"-ETRN\r\n", 7);
+         }
+       if (acl_smtp_vrfy)
+         {
+         g = string_catn(g, smtp_code, 3);
+         g = string_catn(g, US"-VRFY\r\n", 7);
+         }
+       if (acl_smtp_expn)
+         {
+         g = string_catn(g, smtp_code, 3);
+         g = string_catn(g, US"-EXPN\r\n", 7);
+         }
+
+       /* Exim is quite happy with pipelining, so let the other end know that
+       it is safe to use it, unless advertising is disabled. */
+
+       if (  f.pipelining_enable
+          && verify_check_host(&pipelining_advertise_hosts) == OK)
+         {
+         g = string_catn(g, smtp_code, 3);
+         g = string_catn(g, US"-PIPELINING\r\n", 13);
+         sync_cmd_limit = NON_SYNC_CMD_PIPELINING;
+         f.smtp_in_pipelining_advertised = TRUE;
+
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+         if (fl.pipe_connect_acceptable)
+           {
+           f.smtp_in_early_pipe_advertised = TRUE;
+           g = string_catn(g, smtp_code, 3);
+           g = string_catn(g, US"-" EARLY_PIPE_FEATURE_NAME "\r\n", EARLY_PIPE_FEATURE_LEN+3);
+           }
+#endif
+         }
 
 
-      if (acl_smtp_expn != NULL)
-        {
-        s = string_cat(s, &size, &ptr, smtp_code, 3);
-        s = string_cat(s, &size, &ptr, US"-EXPN\r\n", 7);
-        }
 
 
-      /* Exim is quite happy with pipelining, so let the other end know that
-      it is safe to use it, unless advertising is disabled. */
+       /* If any server authentication mechanisms are configured, advertise
+       them if the current host is in auth_advertise_hosts. The problem with
+       advertising always is that some clients then require users to
+       authenticate (and aren't configurable otherwise) even though it may not
+       be necessary (e.g. if the host is in host_accept_relay).
 
 
-      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);
-        sync_cmd_limit = NON_SYNC_CMD_PIPELINING;
-        pipelining_advertised = TRUE;
-        }
+       RFC 2222 states that SASL mechanism names contain only upper case
+       letters, so output the names in upper case, though we actually recognize
+       them in either case in the AUTH command. */
 
 
+       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)
+           {
+           au->advertised = FALSE;
+           if (au->server)
+             {
+             DEBUG(D_auth+D_expand) debug_printf_indent(
+               "Evaluating advertise_condition for %s athenticator\n",
+               au->public_name);
+             if (  !au->advertise_condition
+                || expand_check_condition(au->advertise_condition, au->name,
+                       US"authenticator")
+                )
+               {
+               int saveptr;
+               if (first)
+                 {
+                 g = string_catn(g, smtp_code, 3);
+                 g = string_catn(g, US"-AUTH", 5);
+                 first = FALSE;
+                 fl.auth_advertised = TRUE;
+                 }
+               saveptr = g->ptr;
+               g = string_catn(g, US" ", 1);
+               g = string_cat (g, au->public_name);
+               while (++saveptr < g->ptr) g->s[saveptr] = toupper(g->s[saveptr]);
+               au->advertised = TRUE;
+               }
+             }
+           }
+
+         if (!first) g = string_catn(g, US"\r\n", 2);
+         }
+
+       /* RFC 3030 CHUNKING */
+
+       if (verify_check_host(&chunking_advertise_hosts) != FAIL)
+         {
+         g = string_catn(g, smtp_code, 3);
+         g = string_catn(g, US"-CHUNKING\r\n", 11);
+         f.chunking_offered = TRUE;
+         chunking_state = CHUNKING_OFFERED;
+         }
+
+       /* Advertise TLS (Transport Level Security) aka SSL (Secure Socket Layer)
+       if it has been included in the binary, and the host matches
+       tls_advertise_hosts. We must *not* advertise if we are already in a
+       secure connection. */
 
 
-      /* If any server authentication mechanisms are configured, advertise
-      them if the current host is in auth_advertise_hosts. The problem with
-      advertising always is that some clients then require users to
-      authenticate (and aren't configurable otherwise) even though it may not
-      be necessary (e.g. if the host is in host_accept_relay).
-
-      RFC 2222 states that SASL mechanism names contain only upper case
-      letters, so output the names in upper case, though we actually recognize
-      them in either case in the AUTH command. */
+#ifdef SUPPORT_TLS
+       if (tls_in.active.sock < 0 &&
+           verify_check_host(&tls_advertise_hosts) != FAIL)
+         {
+         g = string_catn(g, smtp_code, 3);
+         g = string_catn(g, US"-STARTTLS\r\n", 11);
+         fl.tls_advertised = TRUE;
+         }
+
+# ifdef EXPERIMENTAL_REQUIRETLS
+       /* Advertise REQUIRETLS only once we are in a secure connection */
+       if (  tls_in.active.sock >= 0
+          && verify_check_host(&tls_advertise_requiretls) != FAIL)
+         {
+         g = string_catn(g, smtp_code, 3);
+         g = string_catn(g, US"-REQUIRETLS\r\n", 13);
+         fl.requiretls_advertised = TRUE;
+         }
+# endif
+#endif
 
 
-      if (auths != NULL)
-        {
-        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);
-          }
-        }
+#ifndef DISABLE_PRDR
+       /* Per Recipient Data Response, draft by Eric A. Hall extending RFC */
+       if (prdr_enable)
+         {
+         g = string_catn(g, smtp_code, 3);
+         g = string_catn(g, US"-PRDR\r\n", 7);
+         }
+#endif
 
 
-      /* Advertise TLS (Transport Level Security) aka SSL (Secure Socket Layer)
-      if it has been included in the binary, and the host matches
-      tls_advertise_hosts. We must *not* advertise if we are already in a
-      secure connection. */
+#ifdef SUPPORT_I18N
+       if (  accept_8bitmime
+          && verify_check_host(&smtputf8_advertise_hosts) != FAIL)
+         {
+         g = string_catn(g, smtp_code, 3);
+         g = string_catn(g, US"-SMTPUTF8\r\n", 11);
+         fl.smtputf8_advertised = TRUE;
+         }
+#endif
 
 
-      #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);
-        tls_advertised = TRUE;
-        }
-      #endif
+       /* Finish off the multiline reply with one that is always available. */
 
 
-      #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);
+       g = string_catn(g, smtp_code, 3);
+       g = string_catn(g, US" HELP\r\n", 7);
        }
        }
-      #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);
-      }
-
-    /* Terminate the string (for debug), write it, and note that HELO/EHLO
-    has been seen. */
-
-    s[ptr] = 0;
-
-    #ifdef SUPPORT_TLS
-    if (tls_in.active >= 0) (void)tls_write(TRUE, s, ptr); else
-    #endif
+      /* Terminate the string (for debug), write it, and note that HELO/EHLO
+      has been seen. */
 
 
-      {
-      int i = fwrite(s, 1, ptr, smtp_out); i = i; /* compiler quietening */
-      }
-    DEBUG(D_receive)
-      {
-      uschar *cr;
-      while ((cr = Ustrchr(s, '\r')) != NULL)   /* lose CRs */
-        memmove(cr, cr + 1, (ptr--) - (cr - s));
-      debug_printf("SMTP>> %s", s);
-      }
-    helo_seen = TRUE;
-
-    /* Reset the protocol and the state, abandoning any previous message. */
+#ifdef SUPPORT_TLS
+      if (tls_in.active.sock >= 0)
+       (void)tls_write(NULL, g->s, g->ptr,
+# ifdef EXPERIMENTAL_PIPE_CONNECT
+                       fl.pipe_connect_acceptable && pipeline_connect_sends());
+# else
+                       FALSE);
+# endif
+      else
+#endif
 
 
-    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);
+       {
+       int i = fwrite(g->s, 1, g->ptr, smtp_out); i = i; /* compiler quietening */
+       }
+      DEBUG(D_receive)
+       {
+       uschar *cr;
 
 
-    smtp_reset(reset_point);
-    toomany = FALSE;
-    break;   /* HELO/EHLO */
+       (void) string_from_gstring(g);
+       while ((cr = Ustrchr(g->s, '\r')) != NULL)   /* lose CRs */
+         memmove(cr, cr + 1, (g->ptr--) - (cr - g->s));
+       debug_printf("SMTP>> %s", g->s);
+       }
+      fl.helo_seen = TRUE;
+
+      /* Reset the protocol and the state, abandoning any previous message. */
+      received_protocol =
+       (sender_host_address ? protocols : protocols_local)
+         [ (fl.esmtp
+           ? pextend + (sender_host_authenticated ? pauthed : 0)
+           : pnormal)
+         + (tls_in.active.sock >= 0 ? pcrpted : 0)
+         ];
+      cancel_cutthrough_connection(TRUE, US"sent EHLO response");
+      smtp_reset(reset_point);
+      toomany = FALSE;
+      break;   /* HELO/EHLO */
 
 
     /* The MAIL command requires an address as an operand. All we do
 
 
     /* The MAIL command requires an address as an operand. All we do
@@ -3739,385 +4536,445 @@ while (done <= 0)
     it is the canonical extracted address which is all that is kept. */
 
     case MAIL_CMD:
     it is the canonical extracted address which is all that is kept. */
 
     case MAIL_CMD:
-    HAD(SCH_MAIL);
-    smtp_mailcmd_count++;              /* Count for limit and ratelimit */
-    was_rej_mail = TRUE;               /* Reset if accepted */
-    env_mail_type_t * mail_args;       /* Sanity check & validate args */
-
-    if (helo_required && !helo_seen)
-      {
-      smtp_printf("503 HELO or EHLO required\r\n");
-      log_write(0, LOG_MAIN|LOG_REJECT, "rejected MAIL from %s: no "
-        "HELO/EHLO given", host_and_ident(FALSE));
-      break;
-      }
-
-    if (sender_address != NULL)
-      {
-      done = synprot_error(L_smtp_protocol_error, 503, NULL,
-        US"sender already given");
-      break;
-      }
+      HAD(SCH_MAIL);
+      smtp_mailcmd_count++;              /* Count for limit and ratelimit */
+      was_rej_mail = TRUE;               /* Reset if accepted */
+      env_mail_type_t * mail_args;       /* Sanity check & validate args */
+
+      if (fl.helo_required && !fl.helo_seen)
+       {
+       smtp_printf("503 HELO or EHLO required\r\n", FALSE);
+       log_write(0, LOG_MAIN|LOG_REJECT, "rejected MAIL from %s: no "
+         "HELO/EHLO given", host_and_ident(FALSE));
+       break;
+       }
 
 
-    if (smtp_cmd_data[0] == 0)
-      {
-      done = synprot_error(L_smtp_protocol_error, 501, NULL,
-        US"MAIL must have an address operand");
-      break;
-      }
+      if (sender_address)
+       {
+       done = synprot_error(L_smtp_protocol_error, 503, NULL,
+         US"sender already given");
+       break;
+       }
 
 
-    /* Check to see if the limit for messages per connection would be
-    exceeded by accepting further messages. */
+      if (!*smtp_cmd_data)
+       {
+       done = synprot_error(L_smtp_protocol_error, 501, NULL,
+         US"MAIL must have an address operand");
+       break;
+       }
 
 
-    if (smtp_accept_max_per_connection > 0 &&
-        smtp_mailcmd_count > smtp_accept_max_per_connection)
-      {
-      smtp_printf("421 too many messages in this connection\r\n");
-      log_write(0, LOG_MAIN|LOG_REJECT, "rejected MAIL command %s: too many "
-        "messages in one connection", host_and_ident(TRUE));
-      break;
-      }
+      /* Check to see if the limit for messages per connection would be
+      exceeded by accepting further messages. */
 
 
-    /* Reset for start of message - even if this is going to fail, we
-    obviously need to throw away any previous data. */
+      if (smtp_accept_max_per_connection > 0 &&
+         smtp_mailcmd_count > smtp_accept_max_per_connection)
+       {
+       smtp_printf("421 too many messages in this connection\r\n", FALSE);
+       log_write(0, LOG_MAIN|LOG_REJECT, "rejected MAIL command %s: too many "
+         "messages in one connection", host_and_ident(TRUE));
+       break;
+       }
 
 
-    smtp_reset(reset_point);
-    toomany = FALSE;
-    sender_data = recipient_data = NULL;
+      /* Reset for start of message - even if this is going to fail, we
+      obviously need to throw away any previous data. */
 
 
-    /* Loop, checking for ESMTP additions to the MAIL FROM command. */
+      cancel_cutthrough_connection(TRUE, US"MAIL received");
+      smtp_reset(reset_point);
+      toomany = FALSE;
+      sender_data = recipient_data = NULL;
 
 
-    if (esmtp) for(;;)
-      {
-      uschar *name, *value, *end;
-      unsigned long int size;
-      BOOL arg_error = FALSE;
+      /* Loop, checking for ESMTP additions to the MAIL FROM command. */
 
 
-      if (!extract_option(&name, &value)) break;
+      if (fl.esmtp) for(;;)
+       {
+       uschar *name, *value, *end;
+       unsigned long int size;
+       BOOL arg_error = FALSE;
 
 
-      for (mail_args = env_mail_type_list;
-           (char *)mail_args < (char *)env_mail_type_list + sizeof(env_mail_type_list);
-           mail_args++
-          )
-        {
-        if (strcmpic(name, mail_args->name) == 0)
-          break;
-        }
-      if (mail_args->need_value && strcmpic(value, US"") == 0)
-        break;
+       if (!extract_option(&name, &value)) break;
 
 
-      switch(mail_args->value)
-        {
-        /* Handle SIZE= by reading the value. We don't do the check till later,
-        in order to be able to log the sender address on failure. */
-        case ENV_MAIL_OPT_SIZE:
-          if (((size = Ustrtoul(value, &end, 10)), *end == 0))
-            {
-            if ((size == ULONG_MAX && errno == ERANGE) || size > INT_MAX)
-              size = INT_MAX;
-            message_size = (int)size;
-            }
-          else
-            arg_error = TRUE;
-          break;
+       for (mail_args = 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;
+
+       switch(mail_args->value)
+         {
+         /* Handle SIZE= by reading the value. We don't do the check till later,
+         in order to be able to log the sender address on failure. */
+         case ENV_MAIL_OPT_SIZE:
+           if (((size = Ustrtoul(value, &end, 10)), *end == 0))
+             {
+             if ((size == ULONG_MAX && errno == ERANGE) || size > INT_MAX)
+               size = INT_MAX;
+             message_size = (int)size;
+             }
+           else
+             arg_error = TRUE;
+           break;
 
 
-        /* If this session was initiated with EHLO and accept_8bitmime is set,
-        Exim will have indicated that it supports the BODY=8BITMIME option. In
-        fact, it does not support this according to the RFCs, in that it does not
-        take any special action for forwarding messages containing 8-bit
-        characters. That is why accept_8bitmime is not the default setting, but
-        some sites want the action that is provided. We recognize both "8BITMIME"
-        and "7BIT" as body types, but take no action. */
-        case ENV_MAIL_OPT_BODY:
-          if (accept_8bitmime) {
-            if (strcmpic(value, US"8BITMIME") == 0) {
-              body_8bitmime = 8;
-            } else if (strcmpic(value, US"7BIT") == 0) {
-              body_8bitmime = 7;
-            } 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);
+         /* If this session was initiated with EHLO and accept_8bitmime is set,
+         Exim will have indicated that it supports the BODY=8BITMIME option. In
+         fact, it does not support this according to the RFCs, in that it does not
+         take any special action for forwarding messages containing 8-bit
+         characters. That is why accept_8bitmime is not the default setting, but
+         some sites want the action that is provided. We recognize both "8BITMIME"
+         and "7BIT" as body types, but take no action. */
+         case ENV_MAIL_OPT_BODY:
+           if (accept_8bitmime) {
+             if (strcmpic(value, US"8BITMIME") == 0)
+               body_8bitmime = 8;
+             else if (strcmpic(value, US"7BIT") == 0)
+               body_8bitmime = 7;
+             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;
            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) {
-            /* Check if RET has already been set */
-            if (dsn_ret > 0) {
-              synprot_error(L_smtp_syntax_error, 501, NULL,
-                US"RET can be specified once only");
-              goto COMMAND_LOOP;
-            }
-            dsn_ret = (strcmpic(value, US"HDRS") == 0)? dsn_ret_hdrs :
-                    (strcmpic(value, US"FULL") == 0)? dsn_ret_full : 0;
-            DEBUG(D_receive) debug_printf("DSN_RET: %d\n", dsn_ret);
-            /* Check for invalid invalid value, and exit with error */
-            if (dsn_ret == 0) {
-              synprot_error(L_smtp_syntax_error, 501, NULL,
-                US"Value for RET is invalid");
-              goto COMMAND_LOOP;
-            }
-          }
-          break;
-        case ENV_MAIL_OPT_ENVID:
-          if (dsn_advertised) {
-            /* Check if the dsn envid has been already set */
-            if (dsn_envid != NULL) {
-              synprot_error(L_smtp_syntax_error, 501, NULL,
-                US"ENVID can be specified once only");
-              goto COMMAND_LOOP;
-            }
-            dsn_envid = string_copy(value);
-            DEBUG(D_receive) debug_printf("DSN_ENVID: %s\n", dsn_envid);
-          }
-          break;
-        #endif
-
-        /* Handle the AUTH extension. If the value given is not "<>" and either
-        the ACL says "yes" or there is no ACL but the sending host is
-        authenticated, we set it up as the authenticated sender. However, if the
-        authenticator set a condition to be tested, we ignore AUTH on MAIL unless
-        the condition is met. The value of AUTH is an xtext, which means that +,
-        = and cntrl chars are coded in hex; however "<>" is unaffected by this
-        coding. */
-        case ENV_MAIL_OPT_AUTH:
-          if (Ustrcmp(value, "<>") != 0)
-            {
-            int rc;
-            uschar *ignore_msg;
+         /* 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 (fl.dsn_advertised)
+             {
+             /* Check if RET has already been set */
+             if (dsn_ret > 0)
+               {
+               done = synprot_error(L_smtp_syntax_error, 501, NULL,
+                 US"RET can be specified once only");
+               goto COMMAND_LOOP;
+               }
+             dsn_ret = strcmpic(value, US"HDRS") == 0
+               ? dsn_ret_hdrs
+               : strcmpic(value, US"FULL") == 0
+               ? dsn_ret_full
+               : 0;
+             DEBUG(D_receive) debug_printf("DSN_RET: %d\n", dsn_ret);
+             /* Check for invalid invalid value, and exit with error */
+             if (dsn_ret == 0)
+               {
+               done = synprot_error(L_smtp_syntax_error, 501, NULL,
+                 US"Value for RET is invalid");
+               goto COMMAND_LOOP;
+               }
+             }
+           break;
+         case ENV_MAIL_OPT_ENVID:
+           if (fl.dsn_advertised)
+             {
+             /* Check if the dsn envid has been already set */
+             if (dsn_envid)
+               {
+               done = 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;
 
 
-            if (auth_xtextdecode(value, &authenticated_sender) < 0)
-              {
-              /* Put back terminator overrides for error message */
-              value[-1] = '=';
-              name[-1] = ' ';
-              done = synprot_error(L_smtp_syntax_error, 501, NULL,
-                US"invalid data for AUTH");
-              goto COMMAND_LOOP;
-              }
-            if (acl_smtp_mailauth == NULL)
-              {
-              ignore_msg = US"client not authenticated";
-              rc = (sender_host_authenticated != NULL)? OK : FAIL;
-              }
-            else
-              {
-              ignore_msg = US"rejected by ACL";
-              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);
-  
-              /* 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;
-  
-              /* 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;
-              }
-            }
-            break;
+         /* Handle the AUTH extension. If the value given is not "<>" and either
+         the ACL says "yes" or there is no ACL but the sending host is
+         authenticated, we set it up as the authenticated sender. However, if the
+         authenticator set a condition to be tested, we ignore AUTH on MAIL unless
+         the condition is met. The value of AUTH is an xtext, which means that +,
+         = and cntrl chars are coded in hex; however "<>" is unaffected by this
+         coding. */
+         case ENV_MAIL_OPT_AUTH:
+           if (Ustrcmp(value, "<>") != 0)
+             {
+             int rc;
+             uschar *ignore_msg;
+
+             if (auth_xtextdecode(value, &authenticated_sender) < 0)
+               {
+               /* Put back terminator overrides for error message */
+               value[-1] = '=';
+               name[-1] = ' ';
+               done = synprot_error(L_smtp_syntax_error, 501, NULL,
+                 US"invalid data for AUTH");
+               goto COMMAND_LOOP;
+               }
+             if (!acl_smtp_mailauth)
+               {
+               ignore_msg = US"client not authenticated";
+               rc = sender_host_authenticated ? OK : FAIL;
+               }
+             else
+               {
+               ignore_msg = US"rejected by ACL";
+               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);
+
+               /* 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;
+
+               /* 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;
+               }
+             }
+             break;
 
 #ifndef DISABLE_PRDR
 
 #ifndef DISABLE_PRDR
-        case ENV_MAIL_OPT_PRDR:
-          if (prdr_enable)
-            prdr_requested = TRUE;
-          break;
+         case ENV_MAIL_OPT_PRDR:
+           if (prdr_enable)
+             prdr_requested = TRUE;
+           break;
 #endif
 
 #endif
 
-        /* Unknown 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[-1] = '=';
-          name[-1] = ' ';
-          arg_error = TRUE;
-          break;
-        }
-      /* Break out of for loop if switch() had bad argument or
-         when start of the email address is reached */
-      if (arg_error) break;
-      }
+#ifdef SUPPORT_I18N
+         case ENV_MAIL_OPT_UTF8:
+           if (!fl.smtputf8_advertised)
+             {
+             done = synprot_error(L_smtp_syntax_error, 501, NULL,
+               US"SMTPUTF8 used when not advertised");
+             goto COMMAND_LOOP;
+             }
+
+           DEBUG(D_receive) debug_printf("smtputf8 requested\n");
+           message_smtputf8 = allow_utf8_domains = TRUE;
+           if (Ustrncmp(received_protocol, US"utf8", 4) != 0)
+             {
+             int old_pool = store_pool;
+             store_pool = POOL_PERM;
+             received_protocol = string_sprintf("utf8%s", received_protocol);
+             store_pool = old_pool;
+             }
+           break;
+#endif
 
 
-    /* If we have passed the threshold for rate limiting, apply the current
-    delay, and update it for next time, provided this is a limited host. */
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+         case ENV_MAIL_OPT_REQTLS:
+           {
+           uschar * r, * t;
+
+           if (!fl.requiretls_advertised)
+             {
+             done = synprot_error(L_smtp_syntax_error, 555, NULL,
+               US"unadvertised MAIL option: REQUIRETLS");
+             goto COMMAND_LOOP;
+             }
+
+           DEBUG(D_receive) debug_printf("requiretls requested\n");
+           tls_requiretls = REQUIRETLS_MSG;
+
+           r = string_copy_malloc(received_protocol);
+           if ((t = Ustrrchr(r, 's'))) *t = 'S';
+           received_protocol = r;
+           }
+           break;
+#endif
 
 
-    if (smtp_mailcmd_count > smtp_rlm_threshold &&
-        verify_check_host(&smtp_ratelimit_hosts) == OK)
-      {
-      DEBUG(D_receive) debug_printf("rate limit MAIL: delay %.3g sec\n",
-        smtp_delay_mail/1000.0);
-      millisleep((int)smtp_delay_mail);
-      smtp_delay_mail *= smtp_rlm_factor;
-      if (smtp_delay_mail > (double)smtp_rlm_limit)
-        smtp_delay_mail = (double)smtp_rlm_limit;
-      }
+         /* 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. */
+         case ENV_MAIL_OPT_NULL:
+           value[-1] = '=';
+           name[-1] = ' ';
+           arg_error = TRUE;
+           break;
 
 
-    /* Now extract the address, first applying any SMTP-time rewriting. The
-    TRUE flag allows "<>" as a sender address. */
+         default:  assert(0);
+         }
+       /* Break out of for loop if switch() had bad argument or
+          when start of the email address is reached */
+       if (arg_error) break;
+       }
 
 
-    raw_sender = ((rewrite_existflags & rewrite_smtp) != 0)?
-      rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"",
-        global_rewrite_rules) : smtp_cmd_data;
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+      if (tls_requiretls & REQUIRETLS_MSG)
+       {
+       /* Ensure headers-only bounces whether a RET option was given or not. */
 
 
-    /* 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 */
+       DEBUG(D_receive) if (dsn_ret == dsn_ret_full)
+         debug_printf("requiretls override: dsn_ret_full -> dsn_ret_hdrs\n");
+       dsn_ret = dsn_ret_hdrs;
+       }
+#endif
 
 
-    if (raw_sender == NULL)
-      {
-      done = synprot_error(L_smtp_syntax_error, 501, smtp_cmd_data, errmess);
-      break;
-      }
+      /* If we have passed the threshold for rate limiting, apply the current
+      delay, and update it for next time, provided this is a limited host. */
+
+      if (smtp_mailcmd_count > smtp_rlm_threshold &&
+         verify_check_host(&smtp_ratelimit_hosts) == OK)
+       {
+       DEBUG(D_receive) debug_printf("rate limit MAIL: delay %.3g sec\n",
+         smtp_delay_mail/1000.0);
+       millisleep((int)smtp_delay_mail);
+       smtp_delay_mail *= smtp_rlm_factor;
+       if (smtp_delay_mail > (double)smtp_rlm_limit)
+         smtp_delay_mail = (double)smtp_rlm_limit;
+       }
 
 
-    sender_address = raw_sender;
+      /* Now extract the address, first applying any SMTP-time rewriting. The
+      TRUE flag allows "<>" as a sender address. */
 
 
-    /* If there is a configured size limit for mail, check that this message
-    doesn't exceed it. The check is postponed to this point so that the sender
-    can be logged. */
+      raw_sender = rewrite_existflags & rewrite_smtp
+       ? rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"",
+                     global_rewrite_rules)
+       : smtp_cmd_data;
 
 
-    if (thismessage_size_limit > 0 && message_size > thismessage_size_limit)
-      {
-      smtp_printf("552 Message size exceeds maximum permitted\r\n");
-      log_write(L_size_reject,
-          LOG_MAIN|LOG_REJECT, "rejected MAIL FROM:<%s> %s: "
-          "message too big: size%s=%d max=%d",
-          sender_address,
-          host_and_ident(TRUE),
-          (message_size == INT_MAX)? ">" : "",
-          message_size,
-          thismessage_size_limit);
-      sender_address = NULL;
-      break;
-      }
-
-    /* Check there is enough space on the disk unless configured not to.
-    When smtp_check_spool_space is set, the check is for thismessage_size_limit
-    plus the current message - i.e. we accept the message only if it won't
-    reduce the space below the threshold. Add 5000 to the size to allow for
-    overheads such as the Received: line and storing of recipients, etc.
-    By putting the check here, even when SIZE is not given, it allow VRFY
-    and EXPN etc. to be used when space is short. */
-
-    if (!receive_check_fs(
-         (smtp_check_spool_space && message_size >= 0)?
-            message_size + 5000 : 0))
-      {
-      smtp_printf("452 Space shortage, please try later\r\n");
-      sender_address = NULL;
-      break;
-      }
-
-    /* If sender_address is unqualified, reject it, unless this is a locally
-    generated message, or the sending host or net is permitted to send
-    unqualified addresses - typically local machines behaving as MUAs -
-    in which case just qualify the address. The flag is set above at the start
-    of the SMTP connection. */
+      raw_sender =
+       parse_extract_address(raw_sender, &errmess, &start, &end, &sender_domain,
+         TRUE);
 
 
-    if (sender_domain == 0 && sender_address[0] != 0)
-      {
-      if (allow_unqualified_sender)
-        {
-        sender_domain = Ustrlen(sender_address) + 1;
-        sender_address = rewrite_address_qualify(sender_address, FALSE);
-        DEBUG(D_receive) debug_printf("unqualified address %s accepted\n",
-          raw_sender);
-        }
-      else
-        {
-        smtp_printf("501 %s: sender address must contain a domain\r\n",
-          smtp_cmd_data);
-        log_write(L_smtp_syntax_error,
-          LOG_MAIN|LOG_REJECT,
-          "unqualified sender rejected: <%s> %s%s",
-          raw_sender,
-          host_and_ident(TRUE),
-          host_lookup_msg);
-        sender_address = NULL;
-        break;
-        }
-      }
+      if (!raw_sender)
+       {
+       done = synprot_error(L_smtp_syntax_error, 501, smtp_cmd_data, errmess);
+       break;
+       }
 
 
-    /* Apply an ACL check if one is defined, before responding. Afterwards,
-    when pipelining is not advertised, do another sync check in case the ACL
-    delayed and the client started sending in the meantime. */
+      sender_address = raw_sender;
+
+      /* If there is a configured size limit for mail, check that this message
+      doesn't exceed it. The check is postponed to this point so that the sender
+      can be logged. */
+
+      if (thismessage_size_limit > 0 && message_size > thismessage_size_limit)
+       {
+       smtp_printf("552 Message size exceeds maximum permitted\r\n", FALSE);
+       log_write(L_size_reject,
+           LOG_MAIN|LOG_REJECT, "rejected MAIL FROM:<%s> %s: "
+           "message too big: size%s=%d max=%d",
+           sender_address,
+           host_and_ident(TRUE),
+           (message_size == INT_MAX)? ">" : "",
+           message_size,
+           thismessage_size_limit);
+       sender_address = NULL;
+       break;
+       }
 
 
-    if (acl_smtp_mail)
-      {
-      rc = acl_check(ACL_WHERE_MAIL, NULL, acl_smtp_mail, &user_msg, &log_msg);
-      if (rc == OK && !pipelining_advertised && !check_sync())
-        goto SYNC_FAILURE;
-      }
-    else
-      rc = OK;
+      /* Check there is enough space on the disk unless configured not to.
+      When smtp_check_spool_space is set, the check is for thismessage_size_limit
+      plus the current message - i.e. we accept the message only if it won't
+      reduce the space below the threshold. Add 5000 to the size to allow for
+      overheads such as the Received: line and storing of recipients, etc.
+      By putting the check here, even when SIZE is not given, it allow VRFY
+      and EXPN etc. to be used when space is short. */
+
+      if (!receive_check_fs(
+          (smtp_check_spool_space && message_size >= 0)?
+             message_size + 5000 : 0))
+       {
+       smtp_printf("452 Space shortage, please try later\r\n", FALSE);
+       sender_address = NULL;
+       break;
+       }
 
 
-    if (rc == OK || rc == DISCARD)
-      {
-      if (!user_msg)
-        smtp_printf("%s%s%s", US"250 OK",
-                  #ifndef DISABLE_PRDR
-                    prdr_requested ? US", PRDR Requested" : US"",
-                 #else
-                    US"",
-                  #endif
-                    US"\r\n");
-      else 
-        {
-      #ifndef DISABLE_PRDR
-        if (prdr_requested)
-           user_msg = string_sprintf("%s%s", user_msg, US", PRDR Requested");
-      #endif
-        smtp_user_msg(US"250", user_msg);
-        }
-      smtp_delay_rcpt = smtp_rlr_base;
-      recipients_discarded = (rc == DISCARD);
-      was_rej_mail = FALSE;
-      }
-    else
-      {
-      done = smtp_handle_acl_fail(ACL_WHERE_MAIL, rc, user_msg, log_msg);
-      sender_address = NULL;
-      }
-    break;
+      /* If sender_address is unqualified, reject it, unless this is a locally
+      generated message, or the sending host or net is permitted to send
+      unqualified addresses - typically local machines behaving as MUAs -
+      in which case just qualify the address. The flag is set above at the start
+      of the SMTP connection. */
+
+      if (!sender_domain && *sender_address)
+       if (f.allow_unqualified_sender)
+         {
+         sender_domain = Ustrlen(sender_address) + 1;
+         sender_address = rewrite_address_qualify(sender_address, FALSE);
+         DEBUG(D_receive) debug_printf("unqualified address %s accepted\n",
+           raw_sender);
+         }
+       else
+         {
+         smtp_printf("501 %s: sender address must contain a domain\r\n", FALSE,
+           smtp_cmd_data);
+         log_write(L_smtp_syntax_error,
+           LOG_MAIN|LOG_REJECT,
+           "unqualified sender rejected: <%s> %s%s",
+           raw_sender,
+           host_and_ident(TRUE),
+           host_lookup_msg);
+         sender_address = NULL;
+         break;
+         }
+
+      /* Apply an ACL check if one is defined, before responding. Afterwards,
+      when pipelining is not advertised, do another sync check in case the ACL
+      delayed and the client started sending in the meantime. */
+
+      if (acl_smtp_mail)
+       {
+       rc = acl_check(ACL_WHERE_MAIL, NULL, acl_smtp_mail, &user_msg, &log_msg);
+       if (rc == OK && !f.smtp_in_pipelining_advertised && !check_sync())
+         goto SYNC_FAILURE;
+       }
+      else
+       rc = OK;
+
+      if (rc == OK || rc == DISCARD)
+       {
+       BOOL more = pipeline_response();
+
+       if (!user_msg)
+         smtp_printf("%s%s%s", more, US"250 OK",
+                   #ifndef DISABLE_PRDR
+                     prdr_requested ? US", PRDR Requested" : US"",
+                   #else
+                     US"",
+                   #endif
+                     US"\r\n");
+       else
+         {
+       #ifndef DISABLE_PRDR
+         if (prdr_requested)
+            user_msg = string_sprintf("%s%s", user_msg, US", PRDR Requested");
+       #endif
+         smtp_user_msg(US"250", user_msg);
+         }
+       smtp_delay_rcpt = smtp_rlr_base;
+       f.recipients_discarded = (rc == DISCARD);
+       was_rej_mail = FALSE;
+       }
+      else
+       {
+       done = smtp_handle_acl_fail(ACL_WHERE_MAIL, rc, user_msg, log_msg);
+       sender_address = NULL;
+       }
+      break;
 
 
     /* The RCPT command requires an address as an operand. There may be any
 
 
     /* The RCPT command requires an address as an operand. There may be any
@@ -4126,277 +4983,259 @@ while (done <= 0)
     are not used, as we keep only the extracted address. */
 
     case RCPT_CMD:
     are not used, as we keep only the extracted address. */
 
     case RCPT_CMD:
-    HAD(SCH_RCPT);
-    rcpt_count++;
-    was_rcpt = rcpt_in_progress = TRUE;
-
-    /* There must be a sender address; if the sender was rejected and
-    pipelining was advertised, we assume the client was pipelining, and do not
-    count this as a protocol error. Reset was_rej_mail so that further RCPTs
-    get the same treatment. */
-
-    if (sender_address == NULL)
-      {
-      if (pipelining_advertised && last_was_rej_mail)
-        {
-        smtp_printf("503 sender not yet given\r\n");
-        was_rej_mail = TRUE;
-        }
-      else
-        {
-        done = synprot_error(L_smtp_protocol_error, 503, NULL,
-          US"sender not yet given");
-        was_rcpt = FALSE;             /* Not a valid RCPT */
-        }
-      rcpt_fail_count++;
-      break;
-      }
-
-    /* Check for an operand */
-
-    if (smtp_cmd_data[0] == 0)
-      {
-      done = synprot_error(L_smtp_syntax_error, 501, NULL,
-        US"RCPT must have an address operand");
-      rcpt_fail_count++;
-      break;
-      }
-    
-    #ifdef EXPERIMENTAL_DSN
-    /* Set the DSN flags orcpt and dsn_flags from the session*/
-    orcpt = NULL;
-    flags = 0;
-
-    if (esmtp) for(;;)
-      {
-      uschar *name, *value, *end;
-      int size;
-
-      if (!extract_option(&name, &value))
-        {
-        break;
-        }
-
-      if (dsn_advertised && strcmpic(name, US"ORCPT") == 0)
-        {
-        /* Check whether orcpt has been already set */
-        if (orcpt != NULL) {
-          synprot_error(L_smtp_syntax_error, 501, NULL,
-            US"ORCPT can be specified once only");
-          goto COMMAND_LOOP;
-          }
-        orcpt = string_copy(value);
-        DEBUG(D_receive) debug_printf("DSN orcpt: %s\n", orcpt);
-        }
-
-      else if (dsn_advertised && strcmpic(name, US"NOTIFY") == 0)
-        {
-        /* Check if the notify flags have been already set */
-        if (flags > 0) {
-          synprot_error(L_smtp_syntax_error, 501, NULL,
-              US"NOTIFY can be specified once only");
-          goto COMMAND_LOOP;
-          }
-        if (strcmpic(value, US"NEVER") == 0) flags |= rf_notify_never; else
-          {
-          uschar *p = value;
-          while (*p != 0)
-            {
-            uschar *pp = p;
-            while (*pp != 0 && *pp != ',') pp++;
-              if (*pp == ',') *pp++ = 0;
-            if (strcmpic(p, US"SUCCESS") == 0) {
-                DEBUG(D_receive) debug_printf("DSN: Setting notify success\n");
-                flags |= rf_notify_success;
-              }
-            else if (strcmpic(p, US"FAILURE") == 0) {
-                DEBUG(D_receive) debug_printf("DSN: Setting notify failure\n");
-                flags |= rf_notify_failure;
-              }
-            else if (strcmpic(p, US"DELAY") == 0) {
-                DEBUG(D_receive) debug_printf("DSN: Setting notify delay\n");
-                flags |= rf_notify_delay;
-              }
-            else {
-              /* Catch any strange values */
-              synprot_error(L_smtp_syntax_error, 501, NULL,
-                US"Invalid value for NOTIFY parameter");
-              goto COMMAND_LOOP;
-              }
-            p = pp;
-            }
-            DEBUG(D_receive) debug_printf("DSN Flags: %x\n", flags);
-          }
-        }
-
-      /* Unknown option. Stick back the terminator characters and break
-      the loop. An error for a malformed address will occur. */
+      HAD(SCH_RCPT);
+      rcpt_count++;
+      was_rcpt = fl.rcpt_in_progress = TRUE;
 
 
-      else
-        {
-        DEBUG(D_receive) debug_printf("Invalid RCPT option: %s : %s\n", name, value);
-        name[-1] = ' ';
-        value[-1] = '=';
-        break;
-        }
-      }
-    #endif
+      /* There must be a sender address; if the sender was rejected and
+      pipelining was advertised, we assume the client was pipelining, and do not
+      count this as a protocol error. Reset was_rej_mail so that further RCPTs
+      get the same treatment. */
 
 
-    /* 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 */
-
-    if (recipient == NULL)
-      {
-      done = synprot_error(L_smtp_syntax_error, 501, smtp_cmd_data, errmess);
-      rcpt_fail_count++;
-      break;
-      }
-
-    /* If the recipient address is unqualified, reject it, unless this is a
-    locally generated message. However, unqualified addresses are permitted
-    from a configured list of hosts and nets - typically when behaving as
-    MUAs rather than MTAs. Sad that SMTP is used for both types of traffic,
-    really. The flag is set at the start of the SMTP connection.
-
-    RFC 1123 talks about supporting "the reserved mailbox postmaster"; I always
-    assumed this meant "reserved local part", but the revision of RFC 821 and
-    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
-        {
-        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 */
-
-    if (rcpt_count > recipients_max && recipients_max > 0)
-      {
-      if (recipients_max_reject)
-        {
-        rcpt_fail_count++;
-        smtp_printf("552 too many recipients\r\n");
-        if (!toomany)
-          log_write(0, LOG_MAIN|LOG_REJECT, "too many recipients: message "
-            "rejected: sender=<%s> %s", sender_address, host_and_ident(TRUE));
-        }
-      else
-        {
-        rcpt_defer_count++;
-        smtp_printf("452 too many recipients\r\n");
-        if (!toomany)
-          log_write(0, LOG_MAIN|LOG_REJECT, "too many recipients: excess "
-            "temporarily rejected: sender=<%s> %s", sender_address,
-            host_and_ident(TRUE));
-        }
+      if (sender_address == NULL)
+       {
+       if (f.smtp_in_pipelining_advertised && last_was_rej_mail)
+         {
+         smtp_printf("503 sender not yet given\r\n", FALSE);
+         was_rej_mail = TRUE;
+         }
+       else
+         {
+         done = synprot_error(L_smtp_protocol_error, 503, NULL,
+           US"sender not yet given");
+         was_rcpt = FALSE;             /* Not a valid RCPT */
+         }
+       rcpt_fail_count++;
+       break;
+       }
 
 
-      toomany = TRUE;
-      break;
-      }
+      /* Check for an operand */
 
 
-    /* If we have passed the threshold for rate limiting, apply the current
-    delay, and update it for next time, provided this is a limited host. */
+      if (smtp_cmd_data[0] == 0)
+       {
+       done = synprot_error(L_smtp_syntax_error, 501, NULL,
+         US"RCPT must have an address operand");
+       rcpt_fail_count++;
+       break;
+       }
 
 
-    if (rcpt_count > smtp_rlr_threshold &&
-        verify_check_host(&smtp_ratelimit_hosts) == OK)
-      {
-      DEBUG(D_receive) debug_printf("rate limit RCPT: delay %.3g sec\n",
-        smtp_delay_rcpt/1000.0);
-      millisleep((int)smtp_delay_rcpt);
-      smtp_delay_rcpt *= smtp_rlr_factor;
-      if (smtp_delay_rcpt > (double)smtp_rlr_limit)
-        smtp_delay_rcpt = (double)smtp_rlr_limit;
-      }
+      /* Set the DSN flags orcpt and dsn_flags from the session*/
+      orcpt = NULL;
+      dsn_flags = 0;
+
+      if (fl.esmtp) for(;;)
+       {
+       uschar *name, *value;
+
+       if (!extract_option(&name, &value))
+         break;
+
+       if (fl.dsn_advertised && strcmpic(name, US"ORCPT") == 0)
+         {
+         /* Check whether orcpt has been already set */
+         if (orcpt)
+           {
+           done = synprot_error(L_smtp_syntax_error, 501, NULL,
+             US"ORCPT can be specified once only");
+           goto COMMAND_LOOP;
+           }
+         orcpt = string_copy(value);
+         DEBUG(D_receive) debug_printf("DSN orcpt: %s\n", orcpt);
+         }
+
+       else if (fl.dsn_advertised && strcmpic(name, US"NOTIFY") == 0)
+         {
+         /* Check if the notify flags have been already set */
+         if (dsn_flags > 0)
+           {
+           done = synprot_error(L_smtp_syntax_error, 501, NULL,
+               US"NOTIFY can be specified once only");
+           goto COMMAND_LOOP;
+           }
+         if (strcmpic(value, US"NEVER") == 0)
+           dsn_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");
+               dsn_flags |= rf_notify_success;
+               }
+             else if (strcmpic(p, US"FAILURE") == 0)
+               {
+               DEBUG(D_receive) debug_printf("DSN: Setting notify failure\n");
+               dsn_flags |= rf_notify_failure;
+               }
+             else if (strcmpic(p, US"DELAY") == 0)
+               {
+               DEBUG(D_receive) debug_printf("DSN: Setting notify delay\n");
+               dsn_flags |= rf_notify_delay;
+               }
+             else
+               {
+               /* Catch any strange values */
+               done = synprot_error(L_smtp_syntax_error, 501, NULL,
+                 US"Invalid value for NOTIFY parameter");
+               goto COMMAND_LOOP;
+               }
+             p = pp;
+             }
+             DEBUG(D_receive) debug_printf("DSN Flags: %x\n", dsn_flags);
+           }
+         }
+
+       /* Unknown option. Stick back the terminator characters and break
+       the loop. An error for a malformed address will occur. */
+
+       else
+         {
+         DEBUG(D_receive) debug_printf("Invalid RCPT option: %s : %s\n", name, value);
+         name[-1] = ' ';
+         value[-1] = '=';
+         break;
+         }
+       }
 
 
-    /* If the MAIL ACL discarded all the recipients, we bypass ACL checking
-    for them. Otherwise, check the access control list for this recipient. As
-    there may be a delay in this, re-check for a synchronization error
-    afterwards, unless pipelining was advertised. */
+      /* Apply SMTP rewriting then extract the working address. Don't allow "<>"
+      as a recipient address */
 
 
-    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())
-        goto SYNC_FAILURE;
-      }
+      recipient = rewrite_existflags & rewrite_smtp
+       ? rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"",
+           global_rewrite_rules)
+       : smtp_cmd_data;
 
 
-    /* The ACL was happy */
+      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++;
+       break;
+       }
 
 
-    if (rc == OK)
-      {
-      if (user_msg == NULL) smtp_printf("250 Accepted\r\n");
-        else smtp_user_msg(US"250", user_msg);
-      receive_add_recipient(recipient, -1);
-      
-      #ifdef EXPERIMENTAL_DSN
-      /* Set the dsn flags in the recipients_list */
-      if (orcpt != NULL)
-        recipients_list[recipients_count-1].orcpt = orcpt;
-      else
-        recipients_list[recipients_count-1].orcpt = NULL;
+      /* If the recipient address is unqualified, reject it, unless this is a
+      locally generated message. However, unqualified addresses are permitted
+      from a configured list of hosts and nets - typically when behaving as
+      MUAs rather than MTAs. Sad that SMTP is used for both types of traffic,
+      really. The flag is set at the start of the SMTP connection.
+
+      RFC 1123 talks about supporting "the reserved mailbox postmaster"; I always
+      assumed this meant "reserved local part", but the revision of RFC 821 and
+      friends now makes it absolutely clear that it means *mailbox*. Consequently
+      we must always qualify this address, regardless. */
+
+      if (!recipient_domain)
+       if (!(recipient_domain = qualify_recipient(&recipient, smtp_cmd_data,
+                                   US"recipient")))
+         {
+         rcpt_fail_count++;
+         break;
+         }
+
+      /* Check maximum allowed */
+
+      if (rcpt_count > recipients_max && recipients_max > 0)
+       {
+       if (recipients_max_reject)
+         {
+         rcpt_fail_count++;
+         smtp_printf("552 too many recipients\r\n", FALSE);
+         if (!toomany)
+           log_write(0, LOG_MAIN|LOG_REJECT, "too many recipients: message "
+             "rejected: sender=<%s> %s", sender_address, host_and_ident(TRUE));
+         }
+       else
+         {
+         rcpt_defer_count++;
+         smtp_printf("452 too many recipients\r\n", FALSE);
+         if (!toomany)
+           log_write(0, LOG_MAIN|LOG_REJECT, "too many recipients: excess "
+             "temporarily rejected: sender=<%s> %s", sender_address,
+             host_and_ident(TRUE));
+         }
+
+       toomany = TRUE;
+       break;
+       }
 
 
-      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
-      
-      }
+      /* If we have passed the threshold for rate limiting, apply the current
+      delay, and update it for next time, provided this is a limited host. */
+
+      if (rcpt_count > smtp_rlr_threshold &&
+         verify_check_host(&smtp_ratelimit_hosts) == OK)
+       {
+       DEBUG(D_receive) debug_printf("rate limit RCPT: delay %.3g sec\n",
+         smtp_delay_rcpt/1000.0);
+       millisleep((int)smtp_delay_rcpt);
+       smtp_delay_rcpt *= smtp_rlr_factor;
+       if (smtp_delay_rcpt > (double)smtp_rlr_limit)
+         smtp_delay_rcpt = (double)smtp_rlr_limit;
+       }
 
 
-    /* The recipient was discarded */
+      /* If the MAIL ACL discarded all the recipients, we bypass ACL checking
+      for them. Otherwise, check the access control list for this recipient. As
+      there may be a delay in this, re-check for a synchronization error
+      afterwards, unless pipelining was advertised. */
 
 
-    else if (rc == DISCARD)
-      {
-      if (user_msg == NULL) smtp_printf("250 Accepted\r\n");
-        else smtp_user_msg(US"250", user_msg);
-      rcpt_fail_count++;
-      discarded = TRUE;
-      log_write(0, LOG_MAIN|LOG_REJECT, "%s F=<%s> rejected RCPT %s: "
-        "discarded by %s ACL%s%s", host_and_ident(TRUE),
-        (sender_address_unrewritten != NULL)?
-        sender_address_unrewritten : sender_address,
-        smtp_cmd_argument, recipients_discarded? "MAIL" : "RCPT",
-        (log_msg == NULL)? US"" : US": ",
-        (log_msg == NULL)? US"" : log_msg);
-      }
+      if (f.recipients_discarded)
+       rc = DISCARD;
+      else
+       if (  (rc = acl_check(ACL_WHERE_RCPT, recipient, acl_smtp_rcpt, &user_msg,
+                     &log_msg)) == OK
+          && !f.smtp_in_pipelining_advertised && !check_sync())
+         goto SYNC_FAILURE;
+
+      /* The ACL was happy */
+
+      if (rc == OK)
+       {
+       BOOL more = pipeline_response();
+
+       if (user_msg)
+         smtp_user_msg(US"250", user_msg);
+       else
+         smtp_printf("250 Accepted\r\n", more);
+       receive_add_recipient(recipient, -1);
+
+       /* Set the dsn flags in the recipients_list */
+       recipients_list[recipients_count-1].orcpt = orcpt;
+       recipients_list[recipients_count-1].dsn_flags = dsn_flags;
+
+       DEBUG(D_receive) debug_printf("DSN: orcpt: %s  flags: %d\n",
+         recipients_list[recipients_count-1].orcpt,
+         recipients_list[recipients_count-1].dsn_flags);
+       }
 
 
-    /* Either the ACL failed the address, or it was deferred. */
+      /* The recipient was discarded */
+
+      else if (rc == DISCARD)
+       {
+       if (user_msg)
+         smtp_user_msg(US"250", user_msg);
+       else
+         smtp_printf("250 Accepted\r\n", FALSE);
+       rcpt_fail_count++;
+       discarded = TRUE;
+       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? sender_address_unrewritten : sender_address,
+         smtp_cmd_argument, f.recipients_discarded? "MAIL" : "RCPT",
+         log_msg ? US": " : US"", log_msg ? log_msg : US"");
+       }
 
 
-    else
-      {
-      if (rc == FAIL) rcpt_fail_count++; else rcpt_defer_count++;
-      done = smtp_handle_acl_fail(ACL_WHERE_RCPT, rc, user_msg, log_msg);
-      }
-    break;
+      /* Either the ACL failed the address, or it was deferred. */
+
+      else
+       {
+       if (rc == FAIL) rcpt_fail_count++; else rcpt_defer_count++;
+       done = smtp_handle_acl_fail(ACL_WHERE_RCPT, rc, user_msg, log_msg);
+       }
+      break;
 
 
     /* The DATA command is legal only if it follows successful MAIL FROM
 
 
     /* The DATA command is legal only if it follows successful MAIL FROM
@@ -4419,288 +5258,359 @@ 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). */
 
     (often indicating some kind of system error), it is helpful to include it
     with the DATA rejection (an idea suggested by Tony Finch). */
 
-    case DATA_CMD:
-    HAD(SCH_DATA);
-    if (!discarded && recipients_count <= 0)
+    case BDAT_CMD:
       {
       {
-      if (rcpt_smtp_response_same && rcpt_smtp_response != NULL)
-        {
-        uschar *code = US"503";
-        int len = Ustrlen(rcpt_smtp_response);
-        smtp_respond(code, 3, FALSE, US"All RCPT commands were rejected with "
-          "this error:");
-        /* Responses from smtp_printf() will have \r\n on the end */
-        if (len > 2 && rcpt_smtp_response[len-2] == '\r')
-          rcpt_smtp_response[len-2] = 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");
-      else
-        done = synprot_error(L_smtp_protocol_error, 503, NULL,
-          US"valid RCPT command must precede DATA");
-      break;
-      }
+      int n;
+
+      HAD(SCH_BDAT);
+      if (chunking_state != CHUNKING_OFFERED)
+       {
+       done = synprot_error(L_smtp_protocol_error, 503, NULL,
+         US"BDAT command used when CHUNKING not advertised");
+       break;
+       }
 
 
-    if (toomany && recipients_max_reject)
-      {
-      sender_address = NULL;  /* This will allow a new MAIL without RSET */
-      sender_address_unrewritten = NULL;
-      smtp_printf("554 Too many recipients\r\n");
-      break;
-      }
+      /* grab size, endmarker */
 
 
-    /* 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 (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);
 
 
-    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;
-      }
+      /* 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_getbuf = receive_getbuf;
+      lwr_receive_ungetc = receive_ungetc;
 
 
-    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 */
-      }
+      receive_getc = bdat_getc;
+      receive_ungetc = bdat_ungetc;
 
 
-    /* Either the ACL failed the address, or it was deferred. */
+      f.dot_ends = FALSE;
 
 
-    else
-      done = smtp_handle_acl_fail(ACL_WHERE_PREDATA, rc, user_msg, log_msg);
-    break;
+      goto DATA_BDAT;
+      }
 
 
+    case DATA_CMD:
+      HAD(SCH_DATA);
+      f.dot_ends = TRUE;
 
 
-    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;
+    DATA_BDAT:         /* Common code for DATA and BDAT */
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+      fl.pipe_connect_acceptable = FALSE;
+#endif
+      if (!discarded && recipients_count <= 0)
+       {
+       if (fl.rcpt_smtp_response_same && rcpt_smtp_response != NULL)
+         {
+         uschar *code = US"503";
+         int len = Ustrlen(rcpt_smtp_response);
+         smtp_respond(code, 3, FALSE, US"All RCPT commands were rejected with "
+           "this error:");
+         /* Responses from smtp_printf() will have \r\n on the end */
+         if (len > 2 && rcpt_smtp_response[len-2] == '\r')
+           rcpt_smtp_response[len-2] = 0;
+         smtp_respond(code, 3, FALSE, rcpt_smtp_response);
+         }
+       if (f.smtp_in_pipelining_advertised && last_was_rcpt)
+         smtp_printf("503 Valid RCPT command must precede %s\r\n", FALSE,
+           smtp_names[smtp_connection_had[smtp_ch_index-1]]);
+       else
+         done = synprot_error(L_smtp_protocol_error, 503, NULL,
+           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;
+       }
 
 
-      /* 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 */
+      if (toomany && recipients_max_reject)
+       {
+       sender_address = NULL;  /* This will allow a new MAIL without RSET */
+       sender_address_unrewritten = NULL;
+       smtp_printf("554 Too many recipients\r\n", FALSE);
+       break;
+       }
 
 
-      if (address == NULL)
-        s = string_sprintf("501 %s", errmess);
+      if (chunking_state > CHUNKING_OFFERED)
+       rc = OK;                        /* No predata ACL or go-ahead output for BDAT */
       else
       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;
-
-          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;
+       {
+       /* 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.cctx.sock < 0)
+         rc = OK;
+       else
+         {
+         uschar * acl = acl_smtp_predata ? acl_smtp_predata : US"accept";
+         f.enable_dollar_recipients = TRUE;
+         rc = acl_check(ACL_WHERE_PREDATA, NULL, acl, &user_msg,
+           &log_msg);
+         f.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", FALSE);
+       }
 
 
-          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;
-          }
-        }
+#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 */
 
 
-      smtp_printf("%s\r\n", s);
-      }
-    break;
+      break;
 
 
 
 
-    case EXPN_CMD:
-    HAD(SCH_EXPN);
-    rc = acl_check(ACL_WHERE_EXPN, NULL, acl_smtp_expn, &user_msg, &log_msg);
-    if (rc != OK)
-      done = smtp_handle_acl_fail(ACL_WHERE_EXPN, rc, user_msg, log_msg);
-    else
+    case VRFY_CMD:
       {
       {
-      BOOL save_log_testing_mode = log_testing_mode;
-      address_test_mode = log_testing_mode = TRUE;
-      (void) verify_address(deliver_make_addr(smtp_cmd_data, FALSE),
-        smtp_out, vopt_is_recipient | vopt_qualify | vopt_expn, -1, -1, -1,
-        NULL, NULL, NULL);
-      address_test_mode = FALSE;
-      log_testing_mode = save_log_testing_mode;    /* true for -bh */
-      }
-    break;
+      uschar * address;
 
 
+      HAD(SCH_VRFY);
 
 
-    #ifdef SUPPORT_TLS
+      if (!(address = parse_extract_address(smtp_cmd_data, &errmess,
+            &start, &end, &recipient_domain, FALSE)))
+       {
+       smtp_printf("501 %s\r\n", FALSE, errmess);
+       break;
+       }
 
 
-    case STARTTLS_CMD:
-    HAD(SCH_STARTTLS);
-    if (!tls_advertised)
-      {
-      done = synprot_error(L_smtp_protocol_error, 503, NULL,
-        US"STARTTLS command used when not advertised");
-      break;
-      }
+      if (!recipient_domain)
+       if (!(recipient_domain = qualify_recipient(&address, smtp_cmd_data,
+                                   US"verify")))
+         break;
 
 
-    /* Apply an ACL check if one is defined */
+      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
+       {
+       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;
 
 
-    if (acl_smtp_starttls != NULL)
-      {
-      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;
-        }
-      }
+         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;
 
 
-    /* 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
-    do an implied RSET when STARTTLS is received. */
-
-    incomplete_transaction_log(US"STARTTLS");
-    smtp_reset(reset_point);
-    toomany = FALSE;
-    cmd_list[CMD_LIST_STARTTLS].is_mail_cmd = FALSE;
-
-    /* There's an attack where more data is read in past the STARTTLS command
-    before TLS is negotiated, then assumed to be part of the secure session
-    when used afterwards; we use segregated input buffers, so are not
-    vulnerable, but we want to note when it happens and, for sheer paranoia,
-    ensure that the buffer is "wiped".
-    Pipelining sync checks will normally have protected us too, unless disabled
-    by configuration. */
-
-    if (receive_smtp_buffered())
-      {
-      DEBUG(D_any)
-        debug_printf("Non-empty input buffer after STARTTLS; naive attack?");
-      if (tls_in.active < 0)
-        smtp_inend = smtp_inptr = smtp_inbuffer;
-      /* and if TLS is already active, tls_server_start() should fail */
+         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", FALSE, s);
+       }
+      break;
       }
 
       }
 
-    /* There is nothing we value in the input buffer and if TLS is succesfully
-    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);
+    case EXPN_CMD:
+      HAD(SCH_EXPN);
+      rc = acl_check(ACL_WHERE_EXPN, NULL, acl_smtp_expn, &user_msg, &log_msg);
+      if (rc != OK)
+       done = smtp_handle_acl_fail(ACL_WHERE_EXPN, rc, user_msg, log_msg);
+      else
+       {
+       BOOL save_log_testing_mode = f.log_testing_mode;
+       f.address_test_mode = f.log_testing_mode = TRUE;
+       (void) verify_address(deliver_make_addr(smtp_cmd_data, FALSE),
+         smtp_out, vopt_is_recipient | vopt_qualify | vopt_expn, -1, -1, -1,
+         NULL, NULL, NULL);
+       f.address_test_mode = FALSE;
+       f.log_testing_mode = save_log_testing_mode;    /* true for -bh */
+       }
+      break;
 
 
-    /* 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,
-    and that's what happens by default. However, in order to work round YAEB,
-    there is an option to remember the esmtp state. Sigh.
 
 
-    We must allow for an extra EHLO command and an extra AUTH command after
-    STARTTLS that don't add to the nonmail command count. */
+    #ifdef SUPPORT_TLS
 
 
-    if ((rc = tls_server_start(tls_require_ciphers)) == OK)
-      {
-      if (!tls_remember_esmtp)
-        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;
-      if (sender_helo_name != NULL)
-        {
-        store_free(sender_helo_name);
-        sender_helo_name = NULL;
-        host_build_sender_fullhost();  /* Rebuild */
-        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);
-
-      sender_host_authenticated = NULL;
-      authenticated_id = NULL;
-      sync_cmd_limit = NON_SYNC_CMD_NON_PIPELINING;
-      DEBUG(D_tls) debug_printf("TLS active\n");
-      break;     /* Successful STARTTLS */
-      }
+    case STARTTLS_CMD:
+      HAD(SCH_STARTTLS);
+      if (!fl.tls_advertised)
+       {
+       done = synprot_error(L_smtp_protocol_error, 503, NULL,
+         US"STARTTLS command used when not advertised");
+       break;
+       }
 
 
-    /* Some local configuration problem was discovered before actually trying
-    to do a TLS handshake; give a temporary error. */
+      /* Apply an ACL check if one is defined */
 
 
-    else if (rc == DEFER)
-      {
-      smtp_printf("454 TLS currently unavailable\r\n");
-      break;
-      }
+      if (  acl_smtp_starttls
+        && (rc = acl_check(ACL_WHERE_STARTTLS, NULL, acl_smtp_starttls,
+                   &user_msg, &log_msg)) != OK
+        )
+       {
+       done = smtp_handle_acl_fail(ACL_WHERE_STARTTLS, rc, user_msg, log_msg);
+       break;
+       }
 
 
-    /* Hard failure. Reject everything except QUIT or closed connection. One
-    cause for failure is a nested STARTTLS, in which case tls_in.active remains
-    set, but we must still reject all incoming commands. */
+      /* 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 reasonable to
+      do an implied RSET when STARTTLS is received. */
+
+      incomplete_transaction_log(US"STARTTLS");
+      cancel_cutthrough_connection(TRUE, US"STARTTLS received");
+      smtp_reset(reset_point);
+      toomany = FALSE;
+      cmd_list[CMD_LIST_STARTTLS].is_mail_cmd = FALSE;
+
+      /* There's an attack where more data is read in past the STARTTLS command
+      before TLS is negotiated, then assumed to be part of the secure session
+      when used afterwards; we use segregated input buffers, so are not
+      vulnerable, but we want to note when it happens and, for sheer paranoia,
+      ensure that the buffer is "wiped".
+      Pipelining sync checks will normally have protected us too, unless disabled
+      by configuration. */
+
+      if (receive_smtp_buffered())
+       {
+       DEBUG(D_any)
+         debug_printf("Non-empty input buffer after STARTTLS; naive attack?\n");
+       if (tls_in.active.sock < 0)
+         smtp_inend = smtp_inptr = smtp_inbuffer;
+       /* and if TLS is already active, tls_server_start() should fail */
+       }
 
 
-    DEBUG(D_tls) debug_printf("TLS failed to start\n");
-    while (done <= 0)
-      {
-      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;
+      /* 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);
+
+      /* 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,
+      and that's what happens by default. However, in order to work round YAEB,
+      there is an option to remember the esmtp state. Sigh.
+
+      We must allow for an extra EHLO command and an extra AUTH command after
+      STARTTLS that don't add to the nonmail command count. */
+
+      s = NULL;
+      if ((rc = tls_server_start(tls_require_ciphers, &s)) == OK)
+       {
+       if (!tls_remember_esmtp)
+         fl.helo_seen = fl.esmtp = fl.auth_advertised = f.smtp_in_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)
+         {
+         store_free(sender_helo_name);
+         sender_helo_name = NULL;
+         host_build_sender_fullhost();  /* Rebuild */
+         set_process_info("handling incoming TLS connection from %s",
+           host_and_ident(FALSE));
+         }
+       received_protocol =
+         (sender_host_address ? protocols : protocols_local)
+           [ (fl.esmtp
+             ? pextend + (sender_host_authenticated ? pauthed : 0)
+             : pnormal)
+           + (tls_in.active.sock >= 0 ? pcrpted : 0)
+           ];
+
+       sender_host_auth_pubname = sender_host_authenticated = NULL;
+       authenticated_id = NULL;
+       sync_cmd_limit = NON_SYNC_CMD_NON_PIPELINING;
+       DEBUG(D_tls) debug_printf("TLS active\n");
+       break;     /* Successful STARTTLS */
+       }
+      else
+       (void) smtp_log_tls_fail(s);
 
 
-        /* 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". */
+      /* Some local configuration problem was discovered before actually trying
+      to do a TLS handshake; give a temporary error. */
 
 
-        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;
+      if (rc == DEFER)
+       {
+       smtp_printf("454 TLS currently unavailable\r\n", FALSE);
+       break;
+       }
 
 
-        default:
-        smtp_printf("554 Security failure\r\n");
-        break;
-        }
-      }
-    tls_close(TRUE, TRUE);
-    break;
+      /* Hard failure. Reject everything except QUIT or closed connection. One
+      cause for failure is a nested STARTTLS, in which case tls_in.active remains
+      set, but we must still reject all incoming commands.  Another is a handshake
+      failure - and there may some encrypted data still in the pipe to us, which we
+      see as garbage commands. */
+
+      DEBUG(D_tls) debug_printf("TLS failed to start\n");
+      while (done <= 0) switch(smtp_read_command(FALSE, GETC_BUFFER_UNLIMITED))
+       {
+       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", FALSE, 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", FALSE);
+         break;
+       }
+      tls_close(NULL, TLS_SHUTDOWN_NOWAIT);
+      break;
     #endif
 
 
     #endif
 
 
@@ -4709,44 +5619,23 @@ while (done <= 0)
     message. */
 
     case QUIT_CMD:
     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
-
-    done = 2;
-    log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT",
-      smtp_get_connection_info());
-    break;
+      smtp_quit_handler(&user_msg, &log_msg);
+      done = 2;
+      break;
 
 
     case RSET_CMD:
 
 
     case RSET_CMD:
-    HAD(SCH_RSET);
-    incomplete_transaction_log(US"RSET");
-    smtp_reset(reset_point);
-    toomany = FALSE;
-    smtp_printf("250 Reset OK\r\n");
-    cmd_list[CMD_LIST_RSET].is_mail_cmd = FALSE;
-    break;
+      smtp_rset_handler();
+      cancel_cutthrough_connection(TRUE, US"RSET received");
+      smtp_reset(reset_point);
+      toomany = FALSE;
+      break;
 
 
     case NOOP_CMD:
 
 
     case NOOP_CMD:
-    HAD(SCH_NOOP);
-    smtp_printf("250 OK\r\n");
-    break;
+      HAD(SCH_NOOP);
+      smtp_printf("250 OK\r\n", FALSE);
+      break;
 
 
     /* Show ETRN/EXPN/VRFY if there's an ACL for checking hosts; if actually
 
 
     /* Show ETRN/EXPN/VRFY if there's an ACL for checking hosts; if actually
@@ -4755,278 +5644,292 @@ while (done <= 0)
     response. */
 
     case HELP_CMD:
     response. */
 
     case HELP_CMD:
-    HAD(SCH_HELP);
-    smtp_printf("214-Commands supported:\r\n");
-      {
-      uschar buffer[256];
-      buffer[0] = 0;
-      Ustrcat(buffer, " AUTH");
-      #ifdef SUPPORT_TLS
-      if (tls_in.active < 0 &&
-          verify_check_host(&tls_advertise_hosts) != FAIL)
-        Ustrcat(buffer, " STARTTLS");
-      #endif
-      Ustrcat(buffer, " HELO EHLO MAIL RCPT DATA");
-      Ustrcat(buffer, " NOOP QUIT RSET HELP");
-      if (acl_smtp_etrn != NULL) Ustrcat(buffer, " ETRN");
-      if (acl_smtp_expn != NULL) Ustrcat(buffer, " EXPN");
-      if (acl_smtp_vrfy != NULL) Ustrcat(buffer, " VRFY");
-      smtp_printf("214%s\r\n", buffer);
-      }
-    break;
+      HAD(SCH_HELP);
+      smtp_printf("214-Commands supported:\r\n", TRUE);
+       {
+       uschar buffer[256];
+       buffer[0] = 0;
+       Ustrcat(buffer, " AUTH");
+       #ifdef SUPPORT_TLS
+       if (tls_in.active.sock < 0 &&
+           verify_check_host(&tls_advertise_hosts) != FAIL)
+         Ustrcat(buffer, " STARTTLS");
+       #endif
+       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");
+       if (acl_smtp_vrfy != NULL) Ustrcat(buffer, " VRFY");
+       smtp_printf("214%s\r\n", FALSE, buffer);
+       }
+      break;
 
 
     case EOF_CMD:
 
 
     case EOF_CMD:
-    incomplete_transaction_log(US"connection lost");
-    smtp_notquit_exit(US"connection-lost", US"421",
-      US"%s lost input connection", smtp_active_hostname);
-
-    /* Don't log by default unless in the middle of a message, as some mailers
-    just drop the call rather than sending QUIT, and it clutters up the logs.
-    */
-
-    if (sender_address != NULL || recipients_count > 0)
-      log_write(L_lost_incoming_connection,
-          LOG_MAIN,
-          "unexpected %s while reading SMTP command from %s%s",
-          sender_host_unknown? "EOF" : "disconnection",
-          host_and_ident(FALSE), smtp_read_error);
-
-    else log_write(L_smtp_connection, LOG_MAIN, "%s lost%s",
-      smtp_get_connection_info(), smtp_read_error);
-
-    done = 1;
-    break;
-
-
-    case ETRN_CMD:
-    HAD(SCH_ETRN);
-    if (sender_address != NULL)
-      {
-      done = synprot_error(L_smtp_protocol_error, 503, NULL,
-        US"ETRN is not permitted inside a transaction");
-      break;
-      }
-
-    log_write(L_etrn, LOG_MAIN, "ETRN %s received from %s", smtp_cmd_argument,
-      host_and_ident(FALSE));
+      incomplete_transaction_log(US"connection lost");
+      smtp_notquit_exit(US"connection-lost", US"421",
+       US"%s lost input connection", smtp_active_hostname);
+
+      /* Don't log by default unless in the middle of a message, as some mailers
+      just drop the call rather than sending QUIT, and it clutters up the logs.
+      */
+
+      if (sender_address || recipients_count > 0)
+       log_write(L_lost_incoming_connection, LOG_MAIN,
+         "unexpected %s while reading SMTP command from %s%s%s D=%s",
+         f.sender_host_unknown ? "EOF" : "disconnection",
+         f.tcp_in_fastopen_logged
+         ? US""
+         : f.tcp_in_fastopen
+         ? f.tcp_in_fastopen_data ? US"TFO* " : US"TFO "
+         : US"",
+         host_and_ident(FALSE), smtp_read_error,
+         string_timesince(&smtp_connection_start)
+         );
 
 
-    rc = acl_check(ACL_WHERE_ETRN, NULL, acl_smtp_etrn, &user_msg, &log_msg);
-    if (rc != OK)
-      {
-      done = smtp_handle_acl_fail(ACL_WHERE_ETRN, rc, user_msg, log_msg);
+      else
+       log_write(L_smtp_connection, LOG_MAIN, "%s %slost%s D=%s",
+         smtp_get_connection_info(),
+         f.tcp_in_fastopen && !f.tcp_in_fastopen_logged ? US"TFO " : US"",
+         smtp_read_error,
+         string_timesince(&smtp_connection_start)
+         );
+
+      done = 1;
       break;
       break;
-      }
-
-    /* Compute the serialization key for this command. */
-
-    etrn_serialize_key = string_sprintf("etrn-%s\n", smtp_cmd_data);
-
-    /* If a command has been specified for running as a result of ETRN, we
-    permit any argument to ETRN. If not, only the # standard form is permitted,
-    since that is strictly the only kind of ETRN that can be implemented
-    according to the RFC. */
-
-    if (smtp_etrn_command != NULL)
-      {
-      uschar *error;
-      BOOL rc;
-      etrn_command = smtp_etrn_command;
-      deliver_domain = smtp_cmd_data;
-      rc = transport_set_up_command(&argv, smtp_etrn_command, TRUE, 0, NULL,
-        US"ETRN processing", &error);
-      deliver_domain = NULL;
-      if (!rc)
-        {
-        log_write(0, LOG_MAIN|LOG_PANIC, "failed to set up ETRN command: %s",
-          error);
-        smtp_printf("458 Internal failure\r\n");
-        break;
-        }
-      }
 
 
-    /* Else set up to call Exim with the -R option. */
 
 
-    else
-      {
-      if (*smtp_cmd_data++ != '#')
-        {
-        done = synprot_error(L_smtp_syntax_error, 501, NULL,
-          US"argument must begin with #");
-        break;
-        }
-      etrn_command = US"exim -R";
-      argv = child_exec_exim(CEE_RETURN_ARGV, TRUE, NULL, TRUE, 2, US"-R",
-        smtp_cmd_data);
-      }
-
-    /* If we are host-testing, don't actually do anything. */
+    case ETRN_CMD:
+      HAD(SCH_ETRN);
+      if (sender_address)
+       {
+       done = synprot_error(L_smtp_protocol_error, 503, NULL,
+         US"ETRN is not permitted inside a transaction");
+       break;
+       }
 
 
-    if (host_checking)
-      {
-      HDEBUG(D_any)
-        {
-        debug_printf("ETRN command is: %s\n", etrn_command);
-        debug_printf("ETRN command execution skipped\n");
-        }
-      if (user_msg == NULL) smtp_printf("250 OK\r\n");
-        else smtp_user_msg(US"250", user_msg);
-      break;
-      }
+      log_write(L_etrn, LOG_MAIN, "ETRN %s received from %s", smtp_cmd_argument,
+       host_and_ident(FALSE));
 
 
+      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;
+       }
 
 
-    /* If ETRN queue runs are to be serialized, check the database to
-    ensure one isn't already running. */
+      /* Compute the serialization key for this command. */
+
+      etrn_serialize_key = string_sprintf("etrn-%s\n", smtp_cmd_data);
+
+      /* If a command has been specified for running as a result of ETRN, we
+      permit any argument to ETRN. If not, only the # standard form is permitted,
+      since that is strictly the only kind of ETRN that can be implemented
+      according to the RFC. */
+
+      if (smtp_etrn_command)
+       {
+       uschar *error;
+       BOOL rc;
+       etrn_command = smtp_etrn_command;
+       deliver_domain = smtp_cmd_data;
+       rc = transport_set_up_command(&argv, smtp_etrn_command, TRUE, 0, NULL,
+         US"ETRN processing", &error);
+       deliver_domain = NULL;
+       if (!rc)
+         {
+         log_write(0, LOG_MAIN|LOG_PANIC, "failed to set up ETRN command: %s",
+           error);
+         smtp_printf("458 Internal failure\r\n", FALSE);
+         break;
+         }
+       }
 
 
-    if (smtp_etrn_serialize && !enq_start(etrn_serialize_key))
-      {
-      smtp_printf("458 Already processing %s\r\n", smtp_cmd_data);
-      break;
-      }
+      /* Else set up to call Exim with the -R option. */
 
 
-    /* Fork a child process and run the command. We don't want to have to
-    wait for the process at any point, so set SIGCHLD to SIG_IGN before
-    forking. It should be set that way anyway for external incoming SMTP,
-    but we save and restore to be tidy. If serialization is required, we
-    actually run the command in yet another process, so we can wait for it
-    to complete and then remove the serialization lock. */
+      else
+       {
+       if (*smtp_cmd_data++ != '#')
+         {
+         done = synprot_error(L_smtp_syntax_error, 501, NULL,
+           US"argument must begin with #");
+         break;
+         }
+       etrn_command = US"exim -R";
+       argv = CUSS child_exec_exim(CEE_RETURN_ARGV, TRUE, NULL, TRUE,
+         *queue_name ? 4 : 2,
+         US"-R", smtp_cmd_data,
+         US"-MCG", queue_name);
+       }
 
 
-    oldsignal = signal(SIGCHLD, SIG_IGN);
+      /* If we are host-testing, don't actually do anything. */
+
+      if (host_checking)
+       {
+       HDEBUG(D_any)
+         {
+         debug_printf("ETRN command is: %s\n", etrn_command);
+         debug_printf("ETRN command execution skipped\n");
+         }
+       if (user_msg == NULL) smtp_printf("250 OK\r\n", FALSE);
+         else smtp_user_msg(US"250", user_msg);
+       break;
+       }
 
 
-    if ((pid = fork()) == 0)
-      {
-      smtp_input = FALSE;       /* This process is not associated with the */
-      (void)fclose(smtp_in);    /* SMTP call any more. */
-      (void)fclose(smtp_out);
 
 
-      signal(SIGCHLD, SIG_DFL);      /* Want to catch child */
+      /* If ETRN queue runs are to be serialized, check the database to
+      ensure one isn't already running. */
 
 
-      /* If not serializing, do the exec right away. Otherwise, fork down
-      into another process. */
+      if (smtp_etrn_serialize && !enq_start(etrn_serialize_key, 1))
+       {
+       smtp_printf("458 Already processing %s\r\n", FALSE, smtp_cmd_data);
+       break;
+       }
 
 
-      if (!smtp_etrn_serialize || (pid = fork()) == 0)
-        {
-        DEBUG(D_exec) debug_print_argv(argv);
-        exim_nullstd();                   /* Ensure std{in,out,err} exist */
-        execv(CS argv[0], (char *const *)argv);
-        log_write(0, LOG_MAIN|LOG_PANIC_DIE, "exec of \"%s\" (ETRN) failed: %s",
-          etrn_command, strerror(errno));
-        _exit(EXIT_FAILURE);         /* paranoia */
-        }
+      /* Fork a child process and run the command. We don't want to have to
+      wait for the process at any point, so set SIGCHLD to SIG_IGN before
+      forking. It should be set that way anyway for external incoming SMTP,
+      but we save and restore to be tidy. If serialization is required, we
+      actually run the command in yet another process, so we can wait for it
+      to complete and then remove the serialization lock. */
+
+      oldsignal = signal(SIGCHLD, SIG_IGN);
+
+      if ((pid = fork()) == 0)
+       {
+       smtp_input = FALSE;       /* This process is not associated with the */
+       (void)fclose(smtp_in);    /* SMTP call any more. */
+       (void)fclose(smtp_out);
+
+       signal(SIGCHLD, SIG_DFL);      /* Want to catch child */
+
+       /* If not serializing, do the exec right away. Otherwise, fork down
+       into another process. */
+
+       if (!smtp_etrn_serialize || (pid = fork()) == 0)
+         {
+         DEBUG(D_exec) debug_print_argv(argv);
+         exim_nullstd();                   /* Ensure std{in,out,err} exist */
+         execv(CS argv[0], (char *const *)argv);
+         log_write(0, LOG_MAIN|LOG_PANIC_DIE, "exec of \"%s\" (ETRN) failed: %s",
+           etrn_command, strerror(errno));
+         _exit(EXIT_FAILURE);         /* paranoia */
+         }
+
+       /* Obey this if smtp_serialize and the 2nd fork yielded non-zero. That
+       is, we are in the first subprocess, after forking again. All we can do
+       for a failing fork is to log it. Otherwise, wait for the 2nd process to
+       complete, before removing the serialization. */
+
+       if (pid < 0)
+         log_write(0, LOG_MAIN|LOG_PANIC, "2nd fork for serialized ETRN "
+           "failed: %s", strerror(errno));
+       else
+         {
+         int status;
+         DEBUG(D_any) debug_printf("waiting for serialized ETRN process %d\n",
+           (int)pid);
+         (void)wait(&status);
+         DEBUG(D_any) debug_printf("serialized ETRN process %d ended\n",
+           (int)pid);
+         }
+
+       enq_end(etrn_serialize_key);
+       _exit(EXIT_SUCCESS);
+       }
 
 
-      /* Obey this if smtp_serialize and the 2nd fork yielded non-zero. That
-      is, we are in the first subprocess, after forking again. All we can do
-      for a failing fork is to log it. Otherwise, wait for the 2nd process to
-      complete, before removing the serialization. */
+      /* Back in the top level SMTP process. Check that we started a subprocess
+      and restore the signal state. */
 
       if (pid < 0)
 
       if (pid < 0)
-        log_write(0, LOG_MAIN|LOG_PANIC, "2nd fork for serialized ETRN "
-          "failed: %s", strerror(errno));
+       {
+       log_write(0, LOG_MAIN|LOG_PANIC, "fork of process for ETRN failed: %s",
+         strerror(errno));
+       smtp_printf("458 Unable to fork process\r\n", FALSE);
+       if (smtp_etrn_serialize) enq_end(etrn_serialize_key);
+       }
       else
       else
-        {
-        int status;
-        DEBUG(D_any) debug_printf("waiting for serialized ETRN process %d\n",
-          (int)pid);
-        (void)wait(&status);
-        DEBUG(D_any) debug_printf("serialized ETRN process %d ended\n",
-          (int)pid);
-        }
-
-      enq_end(etrn_serialize_key);
-      _exit(EXIT_SUCCESS);
-      }
-
-    /* Back in the top level SMTP process. Check that we started a subprocess
-    and restore the signal state. */
-
-    if (pid < 0)
-      {
-      log_write(0, LOG_MAIN|LOG_PANIC, "fork of process for ETRN failed: %s",
-        strerror(errno));
-      smtp_printf("458 Unable to fork process\r\n");
-      if (smtp_etrn_serialize) enq_end(etrn_serialize_key);
-      }
-    else
-      {
-      if (user_msg == NULL) smtp_printf("250 OK\r\n");
-        else smtp_user_msg(US"250", user_msg);
-      }
+       {
+       if (user_msg == NULL) smtp_printf("250 OK\r\n", FALSE);
+         else smtp_user_msg(US"250", user_msg);
+       }
 
 
-    signal(SIGCHLD, oldsignal);
-    break;
+      signal(SIGCHLD, oldsignal);
+      break;
 
 
     case BADARG_CMD:
 
 
     case BADARG_CMD:
-    done = synprot_error(L_smtp_syntax_error, 501, NULL,
-      US"unexpected argument data");
-    break;
+      done = synprot_error(L_smtp_syntax_error, 501, NULL,
+       US"unexpected argument data");
+      break;
 
 
     /* This currently happens only for NULLs, but could be extended. */
 
     case BADCHAR_CMD:
 
 
     /* This currently happens only for NULLs, but could be extended. */
 
     case BADCHAR_CMD:
-    done = synprot_error(L_smtp_syntax_error, 0, NULL,       /* Just logs */
-      US"NULL character(s) present (shown as '?')");
-    smtp_printf("501 NULL characters are not allowed in SMTP commands\r\n");
-    break;
+      done = synprot_error(L_smtp_syntax_error, 0, NULL,       /* Just logs */
+       US"NUL character(s) present (shown as '?')");
+      smtp_printf("501 NUL characters are not allowed in SMTP commands\r\n",
+                 FALSE);
+      break;
 
 
     case BADSYN_CMD:
     SYNC_FAILURE:
 
 
     case BADSYN_CMD:
     SYNC_FAILURE:
-    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;
-    incomplete_transaction_log(US"sync failure");
-    log_write(0, LOG_MAIN|LOG_REJECT, "SMTP protocol synchronization error "
-      "(next input sent too soon: pipelining was%s advertised): "
-      "rejected \"%s\" %s next input=\"%s\"",
-      pipelining_advertised? "" : " not",
-      smtp_cmd_buffer, host_and_ident(TRUE),
-      string_printing(smtp_inptr));
-    smtp_notquit_exit(US"synchronization-error", US"554",
-      US"SMTP synchronization error");
-    done = 1;   /* Pretend eof - drops connection */
-    break;
+      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;    /* limit logged amount */
+      smtp_inptr[c] = 0;
+      incomplete_transaction_log(US"sync failure");
+      log_write(0, LOG_MAIN|LOG_REJECT, "SMTP protocol synchronization error "
+       "(next input sent too soon: pipelining was%s advertised): "
+       "rejected \"%s\" %s next input=\"%s\"",
+       f.smtp_in_pipelining_advertised ? "" : " not",
+       smtp_cmd_buffer, host_and_ident(TRUE),
+       string_printing(smtp_inptr));
+      smtp_notquit_exit(US"synchronization-error", US"554",
+       US"SMTP synchronization error");
+      done = 1;   /* Pretend eof - drops connection */
+      break;
 
 
     case TOO_MANY_NONMAIL_CMD:
 
 
     case TOO_MANY_NONMAIL_CMD:
-    s = smtp_cmd_buffer;
-    while (*s != 0 && !isspace(*s)) s++;
-    incomplete_transaction_log(US"too many non-mail commands");
-    log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many "
-      "nonmail commands (last was \"%.*s\")",  host_and_ident(FALSE),
-      (int)(s - smtp_cmd_buffer), smtp_cmd_buffer);
-    smtp_notquit_exit(US"bad-commands", US"554", US"Too many nonmail commands");
-    done = 1;   /* Pretend eof - drops connection */
-    break;
+      s = smtp_cmd_buffer;
+      while (*s != 0 && !isspace(*s)) s++;
+      incomplete_transaction_log(US"too many non-mail commands");
+      log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many "
+       "nonmail commands (last was \"%.*s\")",  host_and_ident(FALSE),
+       (int)(s - smtp_cmd_buffer), smtp_cmd_buffer);
+      smtp_notquit_exit(US"bad-commands", US"554", US"Too many nonmail commands");
+      done = 1;   /* Pretend eof - drops connection */
+      break;
 
 
-    #ifdef EXPERIMENTAL_PROXY
+#ifdef SUPPORT_PROXY
     case PROXY_FAIL_IGNORE_CMD:
     case PROXY_FAIL_IGNORE_CMD:
-    smtp_printf("503 Command refused, required Proxy negotiation failed\r\n");
-    break;
-    #endif
+      smtp_printf("503 Command refused, required Proxy negotiation failed\r\n", FALSE);
+      break;
+#endif
 
     default:
 
     default:
-    if (unknown_command_count++ >= smtp_max_unknown_commands)
-      {
-      log_write(L_smtp_syntax_error, LOG_MAIN,
-        "SMTP syntax error in \"%s\" %s %s",
-        string_printing(smtp_cmd_buffer), host_and_ident(TRUE),
-        US"unrecognized command");
-      incomplete_transaction_log(US"unrecognized command");
-      smtp_notquit_exit(US"bad-commands", US"500",
-        US"Too many unrecognized commands");
-      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);
-      }
-    else
-      done = synprot_error(L_smtp_syntax_error, 500, NULL,
-        US"unrecognized command");
-    break;
+      if (unknown_command_count++ >= smtp_max_unknown_commands)
+       {
+       log_write(L_smtp_syntax_error, LOG_MAIN,
+         "SMTP syntax error in \"%s\" %s %s",
+         string_printing(smtp_cmd_buffer), host_and_ident(TRUE),
+         US"unrecognized command");
+       incomplete_transaction_log(US"unrecognized command");
+       smtp_notquit_exit(US"bad-commands", US"500",
+         US"Too many unrecognized commands");
+       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),
+         string_printing(smtp_cmd_buffer));
+       }
+      else
+       done = synprot_error(L_smtp_syntax_error, 500, NULL,
+         US"unrecognized command");
+      break;
     }
 
   /* This label is used by goto's inside loops that want to break out to
     }
 
   /* This label is used by goto's inside loops that want to break out to
@@ -5041,6 +5944,30 @@ while (done <= 0)
 return done - 2;  /* Convert yield values */
 }
 
 return done - 2;  /* Convert yield values */
 }
 
+
+
+gstring *
+authres_smtpauth(gstring * g)
+{
+if (!sender_host_authenticated)
+  return g;
+
+g = string_append(g, 2, US";\n\tauth=pass (", sender_host_auth_pubname);
+
+if (Ustrcmp(sender_host_auth_pubname, "tls") != 0)
+  g = string_append(g, 2, US") smtp.auth=", authenticated_id);
+else if (authenticated_id)
+  g = string_append(g, 2, US") x509.auth=", authenticated_id);
+else
+  g = string_catn(g, US") reason=x509.auth", 17);
+
+if (authenticated_sender)
+  g = string_append(g, 2, US" smtp.mailfrom=", authenticated_sender);
+return g;
+}
+
+
+
 /* vi: aw ai sw=2
 */
 /* End of smtp_in.c */
 /* vi: aw ai sw=2
 */
 /* End of smtp_in.c */
index b6ff511..9bd90c7 100644 (file)
@@ -2,13 +2,14 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* A number of functions for driving outgoing SMTP calls. */
 
 
 #include "exim.h"
 /* 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)
                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
 
   interface  point this to the interface
   msg        to add to any error message
 
@@ -35,31 +35,28 @@ Returns:     TRUE on success, FALSE on failure, with error message
 
 BOOL
 smtp_get_interface(uschar *istring, int host_af, address_item *addr,
 
 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;
 
 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;
+  if (f.expand_string_forcedfail) return TRUE;
   addr->transport_return = PANIC;
   addr->message = string_sprintf("failed to expand \"interface\" "
       "option for %s: %s", msg, expand_string_message);
   return FALSE;
   }
 
   addr->transport_return = PANIC;
   addr->message = string_sprintf("failed to expand \"interface\" "
       "option for %s: %s", msg, expand_string_message);
   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,
 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)
     {
   {
   if (string_is_ip_address(iface, NULL) == 0)
     {
@@ -74,7 +71,7 @@ while ((iface = string_nextinlist(&expint, &sep, big_buffer,
     break;
   }
 
     break;
   }
 
-if (iface != NULL) *interface = string_copy(iface);
+if (iface) *interface = string_copy(iface);
 return TRUE;
 }
 
 return TRUE;
 }
 
@@ -103,7 +100,7 @@ smtp_get_port(uschar *rstring, address_item *addr, int *port, uschar *msg)
 {
 uschar *pstring = expand_string(rstring);
 
 {
 uschar *pstring = expand_string(rstring);
 
-if (pstring == NULL)
+if (!pstring)
   {
   addr->transport_return = PANIC;
   addr->message = string_sprintf("failed to expand \"%s\" (\"port\" option) "
   {
   addr->transport_return = PANIC;
   addr->message = string_sprintf("failed to expand \"%s\" (\"port\" option) "
@@ -127,7 +124,7 @@ if (isdigit(*pstring))
 else
   {
   struct servent *smtp_service = getservbyname(CS pstring, "tcp");
 else
   {
   struct servent *smtp_service = getservbyname(CS pstring, "tcp");
-  if (smtp_service == NULL)
+  if (!smtp_service)
     {
     addr->transport_return = PANIC;
     addr->message = string_sprintf("TCP port \"%s\" is not defined for %s",
     {
     addr->transport_return = PANIC;
     addr->message = string_sprintf("TCP port \"%s\" is not defined for %s",
@@ -143,68 +140,98 @@ return TRUE;
 
 
 
 
 
 
-/*************************************************
-*           Connect to remote host               *
-*************************************************/
+#ifdef TCP_FASTOPEN
+static void
+tfo_out_check(int sock)
+{
+# if defined(TCP_INFO) && defined(EXIM_HAVE_TCPI_UNACKED)
+struct tcp_info tinfo;
+socklen_t len = sizeof(tinfo);
 
 
-/* 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.
+switch (tcp_out_fastopen)
+  {
+    /* This is a somewhat dubious detection method; totally undocumented so likely
+    to fail in future kernels.  There seems to be no documented way.  What we really
+    want to know is if the server sent smtp-banner data before our ACK of his SYN,ACK
+    hit him.  What this (possibly?) detects is whether we sent a TFO cookie with our
+    SYN, as distinct from a TFO request.  This gets a false-positive when the server
+    key is rotated; we send the old one (which this test sees) but the server returns
+    the new one and does not send its SMTP banner before we ACK his SYN,ACK.
+     To force that rotation case:
+     '# echo -n "00000000-00000000-00000000-0000000" >/proc/sys/net/ipv4/tcp_fastopen_key'
+    The kernel seems to be counting unack'd packets. */
+
+  case TFO_ATTEMPTED_NODATA:
+    if (  getsockopt(sock, IPPROTO_TCP, TCP_INFO, &tinfo, &len) == 0
+       && tinfo.tcpi_state == TCP_SYN_SENT
+       && tinfo.tcpi_unacked > 1
+       )
+      {
+      DEBUG(D_transport|D_v)
+       debug_printf("TCP_FASTOPEN tcpi_unacked %d\n", tinfo.tcpi_unacked);
+      tcp_out_fastopen = TFO_USED_NODATA;
+      }
+    break;
 
 
-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.
+    /* When called after waiting for received data we should be able
+    to tell if data we sent was accepted. */
+
+  case TFO_ATTEMPTED_DATA:
+    if (  getsockopt(sock, IPPROTO_TCP, TCP_INFO, &tinfo, &len) == 0
+       && tinfo.tcpi_state == TCP_ESTABLISHED
+       )
+      if (tinfo.tcpi_options & TCPI_OPT_SYN_DATA)
+       {
+       DEBUG(D_transport|D_v) debug_printf("TFO: data was acked\n");
+       tcp_out_fastopen = TFO_USED_DATA;
+       }
+      else
+       {
+       DEBUG(D_transport|D_v) debug_printf("TFO: had to retransmit\n");
+       tcp_out_fastopen = TFO_NOT_USED;
+       }
+    break;
+  }
+# endif
+}
+#endif
 
 
-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
+
+/* Arguments as for smtp_connect(), plus
+  early_data   if non-NULL, idenmpotent data to be sent -
+               preferably in the TCP SYN segment
 
 Returns:      connected socket number, or -1 with errno set
 */
 
 int
 
 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, const blob * early_data)
 {
 {
-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 dscp_value;
 int dscp_level;
 int dscp_option;
 int sock;
+int save_errno = 0;
+const blob * fastopen_blob = NULL;
 
 
-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. */
 
 
 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. */
 
 /* 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,35 +239,54 @@ 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)
 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)
   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))
   /* 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));
     (void) setsockopt(sock, dscp_level, dscp_option, &dscp_value, sizeof(dscp_value));
-    }
   }
 
 /* Bind to a specific interface if requested. Caller must ensure the interface
 is the same type (IPv4 or IPv6) as the outgoing address. */
 
   }
 
 /* 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)
   {
   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
     strerror(errno));
   }
 
 /* Connect to the remote host, and add keepalive to the socket before returning
-it, if requested. */
+it, if requested.  If the build supports TFO, request it - and if the caller
+requested some early-data then include that in the TFO request.  If there is
+early-data but no TFO support, send it after connecting. */
 
 
-else if (ip_connect(sock, host_af, host->address, port, timeout) < 0)
-  save_errno = errno;
+else
+  {
+#ifdef TCP_FASTOPEN
+  if (verify_check_given_host(CUSS &ob->hosts_try_fastopen, host) == OK)
+    fastopen_blob = early_data ? early_data : &tcp_fastopen_nodata;
+#endif
+
+  if (ip_connect(sock, host_af, host->address, port, timeout, fastopen_blob) < 0)
+    save_errno = errno;
+  else if (early_data && !fastopen_blob && early_data->data && early_data->len)
+    {
+    HDEBUG(D_transport|D_acl|D_v)
+      debug_printf("sending %ld nonTFO early-data\n", (long)early_data->len);
+
+#ifdef TCP_QUICKACK
+    (void) setsockopt(sock, IPPROTO_TCP, TCP_QUICKACK, US &off, sizeof(off));
+#endif
+    if (send(sock, early_data->data, early_data->len, 0) < 0)
+      save_errno = errno;
+    }
+  }
 
 /* Either bind() or connect() failed */
 
 
 /* Either bind() or connect() failed */
 
@@ -248,7 +294,7 @@ if (save_errno != 0)
   {
   HDEBUG(D_transport|D_acl|D_v)
     {
   {
   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");
     if (save_errno == ETIMEDOUT)
       debug_printf(" (timeout=%s)", readconf_printtime(timeout));
     debug_printf("\n");
@@ -258,13 +304,14 @@ if (save_errno != 0)
   return -1;
   }
 
   return -1;
   }
 
-/* Both bind() and connect() succeeded */
+/* Both bind() and connect() succeeded, and any early-data */
 
 else
   {
   union sockaddr_46 interface_sock;
   EXIM_SOCKLEN_T size = sizeof(interface_sock);
 
 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
   if (getsockname(sock, (struct sockaddr *)(&interface_sock), &size) == 0)
     sending_ip_address = host_ntoa(-1, &interface_sock, NULL, &sending_port);
   else
@@ -274,12 +321,102 @@ else
     close(sock);
     return -1;
     }
     close(sock);
     return -1;
     }
-  if (keepalive) ip_keepalive(sock, host->address, TRUE);
+
+  if (ob->keepalive) ip_keepalive(sock, host->address, TRUE);
+#ifdef TCP_FASTOPEN
+  tfo_out_check(sock);
+#endif
   return sock;
   }
 }
 
 
   return sock;
   }
 }
 
 
+
+
+
+void
+smtp_port_for_connect(host_item * host, int port)
+{
+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 */
+}
+
+
+/*************************************************
+*           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.
+
+Arguments:
+  sc         details for making connection: host, af, interface, transport
+  early_data  if non-NULL, data to be sent - preferably in the TCP SYN segment
+
+Returns:      connected socket number, or -1 with errno set
+*/
+
+int
+smtp_connect(smtp_connect_args * sc, const blob * early_data)
+{
+int port = sc->host->port;
+smtp_transport_options_block * ob = sc->ob;
+
+callout_address = string_sprintf("[%s]:%d", sc->host->address, port);
+
+HDEBUG(D_transport|D_acl|D_v)
+  {
+  uschar * s = US" ";
+  if (sc->interface) s = string_sprintf(" from %s ", sc->interface);
+#ifdef SUPPORT_SOCKS
+  if (ob->socks_proxy) s = string_sprintf("%svia proxy ", s);
+#endif
+  debug_printf_indent("Connecting to %s %s%s... ", sc->host->name, callout_address, s);
+  }
+
+/* Create and connect the socket */
+
+#ifdef SUPPORT_SOCKS
+if (ob->socks_proxy)
+  {
+  int sock = socks_sock_connect(sc->host, sc->host_af, port, sc->interface,
+                               sc->tblock, ob->connect_timeout);
+  
+  if (sock >= 0)
+    {
+    if (early_data && early_data->data && early_data->len)
+      if (send(sock, early_data->data, early_data->len, 0) < 0)
+       {
+       int save_errno = errno;
+       HDEBUG(D_transport|D_acl|D_v)
+         {
+         debug_printf_indent("failed: %s", CUstrerror(save_errno));
+         if (save_errno == ETIMEDOUT)
+           debug_printf(" (timeout=%s)", readconf_printtime(ob->connect_timeout));
+         debug_printf("\n");
+         }
+       (void)close(sock);
+       sock = -1;
+       errno = save_errno;
+       }
+    }
+  return sock;
+  }
+#endif
+
+return smtp_sock_connect(sc->host, sc->host_af, port, sc->interface,
+                         sc->tblock, ob->connect_timeout, early_data);
+}
+
+
 /*************************************************
 *        Flush outgoing command buffer           *
 *************************************************/
 /*************************************************
 *        Flush outgoing command buffer           *
 *************************************************/
@@ -290,25 +427,57 @@ pipelining.
 
 Argument:
   outblock   the SMTP output block
 
 Argument:
   outblock   the SMTP output block
+  mode      further data expected, or plain
 
 Returns:     TRUE if OK, FALSE on error, with errno set
 */
 
 static BOOL
 
 Returns:     TRUE if OK, FALSE on error, with errno set
 */
 
 static BOOL
-flush_buffer(smtp_outblock *outblock)
+flush_buffer(smtp_outblock * outblock, int mode)
 {
 int rc;
 {
 int rc;
+int n = outblock->ptr - outblock->buffer;
+BOOL more = mode == SCMD_MORE;
+
+HDEBUG(D_transport|D_acl) debug_printf_indent("cmd buf flush %d bytes%s\n", n,
+  more ? " (more expected)" : "");
 
 #ifdef SUPPORT_TLS
 
 #ifdef SUPPORT_TLS
-if (tls_out.active == outblock->sock)
-  rc = tls_write(FALSE, outblock->buffer, outblock->ptr - outblock->buffer);
+if (outblock->cctx->tls_ctx)
+  rc = tls_write(outblock->cctx->tls_ctx, outblock->buffer, n, more);
 else
 #endif
 
 else
 #endif
 
-rc = send(outblock->sock, outblock->buffer, outblock->ptr - outblock->buffer, 0);
+  {
+  if (outblock->conn_args)
+    {
+    blob early_data = { .data = outblock->buffer, .len = n };
+
+    /* We ignore the more-flag if we're doing a connect with early-data, which
+    means we won't get BDAT+data. A pity, but wise due to the idempotency
+    requirement: TFO with data can, in rare cases, replay the data to the
+    receiver. */
+
+    if (  (outblock->cctx->sock = smtp_connect(outblock->conn_args, &early_data))
+       < 0)
+      return FALSE;
+    outblock->conn_args = NULL;
+    rc = n;
+    }
+  else
+
+    rc = send(outblock->cctx->sock, outblock->buffer, n,
+#ifdef MSG_MORE
+             more ? MSG_MORE : 0
+#else
+             0
+#endif
+            );
+  }
+
 if (rc <= 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;
   }
 
   return FALSE;
   }
 
@@ -327,10 +496,11 @@ return TRUE;
 any error message.
 
 Arguments:
 any error message.
 
 Arguments:
-  outblock   contains buffer for pipelining, and socket
-  noflush    if TRUE, save the command in the output buffer, for pipelining
+  sx        SMTP connection, contains buffer for pipelining, and socket
+  mode       buffer, write-with-more-likely, write
   format     a format, starting with one of
              of HELO, MAIL FROM, RCPT TO, DATA, ".", or QUIT.
   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
   ...        data for the format
 
 Returns:     0 if command added to pipelining buffer, with nothing transmitted
@@ -339,59 +509,63 @@ Returns:     0 if command added to pipelining buffer, with nothing transmitted
 */
 
 int
 */
 
 int
-smtp_write_command(smtp_outblock *outblock, BOOL noflush, const char *format, ...)
+smtp_write_command(void * sx, int mode, const char *format, ...)
 {
 {
-int count;
+smtp_outblock * outblock = &((smtp_context *)sx)->outblock;
 int rc = 0;
 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 (format)
+  {
+  gstring gs = { .size = big_buffer_size, .ptr = 0, .s = big_buffer };
+  va_list ap;
 
 
-if (count > outblock->buffersize)
-  log_write(0, LOG_MAIN|LOG_PANIC_DIE, "overlong write_command in outgoing "
-    "SMTP");
+  va_start(ap, format);
+  if (!string_vformat(&gs, FALSE, CS format, ap))
+    log_write(0, LOG_MAIN|LOG_PANIC_DIE, "overlong write_command in outgoing "
+      "SMTP");
+  va_end(ap);
+  string_from_gstring(&gs);
 
 
-if (count > outblock->buffersize - (outblock->ptr - outblock->buffer))
-  {
-  rc = outblock->cmd_count;                 /* flush resets */
-  if (!flush_buffer(outblock)) return -1;
-  }
+  if (gs.ptr > outblock->buffersize)
+    log_write(0, LOG_MAIN|LOG_PANIC_DIE, "overlong write_command in outgoing "
+      "SMTP");
 
 
-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 */
+  if (gs.ptr > outblock->buffersize - (outblock->ptr - outblock->buffer))
+    {
+    rc = outblock->cmd_count;                 /* flush resets */
+    if (!flush_buffer(outblock, SCMD_FLUSH)) return -1;
+    }
 
 
-/* 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. */
+  Ustrncpy(CS outblock->ptr, gs.s, gs.ptr);
+  outblock->ptr += gs.ptr;
+  outblock->cmd_count++;
+  gs.ptr -= 2; string_from_gstring(&gs); /* remove \r\n for error message */
 
 
-if (outblock->authenticating)
-  {
-  uschar *p = big_buffer;
-  if (Ustrncmp(big_buffer, "AUTH ", 5) == 0)
+  /* 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)
     {
     {
-    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)
+if (mode != SCMD_BUFFER)
   {
   rc += outblock->cmd_count;                /* flush resets */
   {
   rc += outblock->cmd_count;                /* flush resets */
-  if (!flush_buffer(outblock)) return -1;
+  if (!flush_buffer(outblock, mode)) return -1;
   }
 
 return rc;
   }
 
 return rc;
@@ -425,7 +599,7 @@ read_response_line(smtp_inblock *inblock, uschar *buffer, int size, int timeout)
 uschar *p = buffer;
 uschar *ptr = inblock->ptr;
 uschar *ptrend = inblock->ptrend;
 uschar *p = buffer;
 uschar *ptr = inblock->ptr;
 uschar *ptrend = inblock->ptrend;
-int sock = inblock->sock;
+client_conn_ctx * cctx = inblock->cctx;
 
 /* Loop for reading multiple packets or reading another packet after emptying
 a previously-read one. */
 
 /* Loop for reading multiple packets or reading another packet after emptying
 a previously-read one. */
@@ -463,15 +637,20 @@ for (;;)
 
   /* Need to read a new input packet. */
 
 
   /* Need to read a new input packet. */
 
-  rc = ip_recv(sock, inblock->buffer, inblock->buffersize, timeout);
-  if (rc <= 0) break;
+  if((rc = ip_recv(cctx, inblock->buffer, inblock->buffersize, timeout)) <= 0)
+    {
+    DEBUG(D_deliver|D_transport|D_acl)
+      debug_printf_indent(errno ? "  SMTP(%s)<<\n" : "  SMTP(closed)<<\n",
+       strerror(errno));
+    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;
 
   /* 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
   }
 
 /* Get here if there has been some kind of recv() error; errno is set, but we
@@ -497,34 +676,46 @@ also returned after a reading error. In this case buffer[0] will be zero, and
 the error code will be in errno.
 
 Arguments:
 the error code will be in errno.
 
 Arguments:
-  inblock   the SMTP input block (contains holding buffer, socket, etc.)
+  sx        the SMTP connection (contains input block with holding buffer,
+               socket, etc.)
   buffer    where to put the response
   size      the size of the buffer
   okdigit   the expected first digit of the response
   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
 */
 
 Returns:    TRUE if a valid, non-error response was received; else FALSE
 */
+/*XXX could move to smtp transport; no other users */
 
 BOOL
 
 BOOL
-smtp_read_response(smtp_inblock *inblock, uschar *buffer, int size, int okdigit,
+smtp_read_response(void * sx0, uschar *buffer, int size, int okdigit,
    int timeout)
 {
    int timeout)
 {
+smtp_context * sx = sx0;
 uschar *ptr = buffer;
 uschar *ptr = buffer;
-int count;
+int count = 0;
 
 errno = 0;  /* Ensure errno starts out zero */
 
 
 errno = 0;  /* Ensure errno starts out zero */
 
-/* This is a loop to read and concatentate the lines that make up a multi-line
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+if (sx->pending_BANNER || sx->pending_EHLO)
+  if (smtp_reap_early_pipe(sx, &count) != OK)
+    {
+    DEBUG(D_transport) debug_printf("failed reaping pipelined cmd responsess\n");
+    return FALSE;
+    }
+#endif
+
+/* This is a loop to read and concatenate the lines that make up a multi-line
 response. */
 
 for (;;)
   {
 response. */
 
 for (;;)
   {
-  if ((count = read_response_line(inblock, ptr, size, timeout)) < 0)
+  if ((count = read_response_line(&sx->inblock, ptr, size, timeout)) < 0)
     return FALSE;
 
   HDEBUG(D_transport|D_acl|D_v)
     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
 
   /* 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
@@ -558,6 +749,10 @@ for (;;)
   size -= count + 1;
   }
 
   size -= count + 1;
   }
 
+#ifdef TCP_FASTOPEN
+  tfo_out_check(sx->cctx.sock);
+#endif
+
 /* Return a value that depends on the SMTP return code. On some systems a
 non-zero value of errno has been seen at this point, so ensure it is zero,
 because the caller of this function looks at errno when FALSE is returned, to
 /* Return a value that depends on the SMTP return code. On some systems a
 non-zero value of errno has been seen at this point, so ensure it is zero,
 because the caller of this function looks at errno when FALSE is returned, to
@@ -569,3 +764,5 @@ return buffer[0] == okdigit;
 }
 
 /* End of smtp_out.c */
 }
 
 /* End of smtp_out.c */
+/* vi: aw ai sw=2
+*/
dissimilarity index 89%
index 7eb6fbf..9384bfa 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 - 2018
+ */
+
+/* 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;
+client_conn_ctx spamd_cctx = {.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 */
+
+if (!(mbox_file = spool_mbox(&mbox_size, NULL, 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)))
+    {
+    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 (;;)
+      {
+      /*XXX could potentially use TFO early-data here */
+      if (  (spamd_cctx.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_cctx.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_cctx.sock, F_SETFL, O_NONBLOCK);
+/* now we are connected to spamd on spamd_cctx.sock */
+if (sd->is_rspamd)
+  {
+  gstring * req_str;
+  const uschar * s;
+
+  req_str = string_append(NULL, 8,
+    "CHECK RSPAMC/1.3\r\nContent-length: ", string_sprintf("%lu\r\n", mbox_size),
+    "Queue-Id: ", message_id,
+    "\r\nFrom: <", sender_address,
+    ">\r\nRecipient-Number: ", string_sprintf("%d\r\n", recipients_count));
+
+  for (i = 0; i < recipients_count; i ++)
+    req_str = string_append(req_str, 3,
+      "Rcpt: <", recipients_list[i].address, ">\r\n");
+  if ((s = expand_string(US"$sender_helo_name")) && *s)
+    req_str = string_append(req_str, 3, "Helo: ", s, "\r\n");
+  if ((s = expand_string(US"$sender_host_name")) && *s)
+    req_str = string_append(req_str, 3, "Hostname: ", s, "\r\n");
+  if (sender_host_address)
+    req_str = string_append(req_str, 3, "IP: ", sender_host_address, "\r\n");
+  if ((s = expand_string(US"$authenticated_id")) && *s)
+    req_str = string_append(req_str, 3, "User: ", s, "\r\n");
+  req_str = string_catn(req_str, US"\r\n", 2);
+  wrote = send(spamd_cctx.sock, req_str->s, req_str->ptr, 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_cctx.sock, spamd_buffer, Ustrlen(spamd_buffer), 0);
+  }
+
+if (wrote == -1)
+  {
+  (void)close(spamd_cctx.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_cctx.sock;
+pollfd.events = POLLOUT;
+#endif
+(void)fcntl(spamd_cctx.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_cctx.sock, &select_fd);
+    result = select(spamd_cctx.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_cctx.sock);
+      goto defer;
+      }
+
+    wrote = send(spamd_cctx.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_cctx.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_cctx.sock);
+  goto defer;
+  }
+
+(void)fclose(mbox_file);
+
+/* we're done sending, close socket for writing */
+if (!sd->is_rspamd)
+  shutdown(spamd_cctx.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_cctx,
+                  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_cctx.sock);
+  return DEFER;
+  }
+
+/* reading done */
+(void)close(spamd_cctx.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    *
 *************************************************/
 
 *     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 */
 /* License: GPL */
 
 /* spam defines */
 /* timeout for reading and writing spamd */
 #define SPAMD_TIMEOUT 120
 
 /* 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 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
 
 #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
 } spamd_address_container;
 
 #endif
dissimilarity index 73%
index 7167f57..0b00a5c 100644 (file)
--- a/src/spf.c
+++ b/src/spf.c
-/*************************************************
-*     Exim - an Internet mail transport agent    *
-*************************************************/
-
-/* Experimental SPF support.
-   Copyright (c) Tom Kistner <tom@duncanthrax.net> 2004 - 2014
-   License: GPL */
-
-/* Code for calling spf checks via libspf-alt. Called from acl.c. */
-
-#include "exim.h"
-#ifdef EXPERIMENTAL_SPF
-
-/* must be kept in numeric order */
-static spf_result_id spf_result_id_list[] = {
-  { US"invalid", 0},
-  { US"neutral", 1 },
-  { US"pass", 2 },
-  { US"fail", 3 },
-  { US"softfail", 4 },
-  { US"none", 5 },
-  { US"err_temp", 6 },  /* Deprecated Apr 2014 */
-  { US"err_perm", 7 },  /* Deprecated Apr 2014 */
-  { US"temperror", 6 }, /* RFC 4408 defined */
-  { US"permerror", 7 }  /* RFC 4408 defined */
-};
-
-SPF_server_t    *spf_server = NULL;
-SPF_request_t   *spf_request = NULL;
-SPF_response_t  *spf_response = NULL;
-SPF_response_t  *spf_response_2mx = NULL;
-
-/* spf_init sets up a context that can be re-used for several
-   messages on the same SMTP connection (that come from the
-   same host with the same HELO string) */
-
-int spf_init(uschar *spf_helo_domain, uschar *spf_remote_addr) {
-
-  spf_server = SPF_server_new(SPF_DNS_CACHE, 0);
-
-  if ( spf_server == NULL ) {
-    debug_printf("spf: SPF_server_new() failed.\n");
-    return 0;
-  }
-
-  if (SPF_server_set_rec_dom(spf_server, CS primary_hostname)) {
-    debug_printf("spf: SPF_server_set_rec_dom(\"%s\") failed.\n", primary_hostname);
-    spf_server = NULL;
-    return 0;
-  }
-
-  spf_request = SPF_request_new(spf_server);
-
-  if (SPF_request_set_ipv4_str(spf_request, CS spf_remote_addr)
-      && SPF_request_set_ipv6_str(spf_request, CS spf_remote_addr)) {
-    debug_printf("spf: SPF_request_set_ipv4_str() and SPF_request_set_ipv6_str() failed [%s]\n", spf_remote_addr);
-    spf_server = NULL;
-    spf_request = NULL;
-    return 0;
-  }
-
-  if (SPF_request_set_helo_dom(spf_request, CS spf_helo_domain)) {
-    debug_printf("spf: SPF_set_helo_dom(\"%s\") failed.\n", spf_helo_domain);
-    spf_server = NULL;
-    spf_request = NULL;
-    return 0;
-  }
-
-  return 1;
-}
-
-
-/* spf_process adds the envelope sender address to the existing
-   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 sep = 0;
-  uschar *list = *listptr;
-  uschar *spf_result_id;
-  uschar spf_result_id_buffer[128];
-  int rc = SPF_RESULT_PERMERROR;
-
-  if (!(spf_server && spf_request)) {
-    /* no global context, assume temp error and skip to evaluation */
-    rc = SPF_RESULT_PERMERROR;
-    goto SPF_EVALUATE;
-  };
-
-  if (SPF_request_set_env_from(spf_request, CS spf_envelope_sender)) {
-    /* Invalid sender address. This should be a real rare occurence */
-    rc = SPF_RESULT_PERMERROR;
-    goto SPF_EVALUATE;
-  }
-
-  /* get SPF result */
-  if (action == SPF_PROCESS_FALLBACK)
-    SPF_request_query_fallback(spf_request, &spf_response, CS spf_guess);
-  else
-    SPF_request_query_mailfrom(spf_request, &spf_response);
-
-  /* set up expansion items */
-  spf_header_comment     = (uschar *)SPF_response_get_header_comment(spf_response);
-  spf_received           = (uschar *)SPF_response_get_received_spf(spf_response);
-  spf_result             = (uschar *)SPF_strresult(SPF_response_result(spf_response));
-  spf_smtp_comment       = (uschar *)SPF_response_get_smtp_comment(spf_response);
-
-  rc = SPF_response_result(spf_response);
-
-  /* We got a result. Now see if we should return OK or FAIL for it */
-  SPF_EVALUATE:
-  debug_printf("SPF result is %s (%d)\n", SPF_strresult(rc), rc);
-
-  if (action == SPF_PROCESS_GUESS && (!strcmp (SPF_strresult(rc), "none")))
-    return spf_process(listptr, spf_envelope_sender, SPF_PROCESS_FALLBACK);
-
-  while ((spf_result_id = string_nextinlist(&list, &sep,
-                                     spf_result_id_buffer,
-                                     sizeof(spf_result_id_buffer))) != NULL) {
-    int negate = 0;
-    int result = 0;
-
-    /* Check for negation */
-    if (spf_result_id[0] == '!') {
-      negate = 1;
-      spf_result_id++;
-    };
-
-    /* Check the result identifier */
-    result = Ustrcmp(spf_result_id, spf_result_id_list[rc].name);
-    if (!negate && result==0) return OK;
-    if (negate && result!=0) return OK;
-  };
-
-  /* no match */
-  return FAIL;
-}
-
-#endif
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Experimental SPF support.
+   Copyright (c) Tom Kistner <tom@duncanthrax.net> 2004 - 2014
+   License: GPL
+   Copyright (c) The Exim Maintainers 2015 - 2018
+*/
+
+/* Code for calling spf checks via libspf-alt. Called from acl.c. */
+
+#include "exim.h"
+#ifdef SUPPORT_SPF
+
+/* must be kept in numeric order */
+static spf_result_id spf_result_id_list[] = {
+  /* name              value */
+  { US"invalid",       0},
+  { US"neutral",       1 },
+  { US"pass",          2 },
+  { US"fail",          3 },
+  { US"softfail",      4 },
+  { US"none",          5 },
+  { US"temperror",     6 }, /* RFC 4408 defined */
+  { US"permerror",     7 }  /* RFC 4408 defined */
+};
+
+SPF_server_t    *spf_server = NULL;
+SPF_request_t   *spf_request = NULL;
+SPF_response_t  *spf_response = NULL;
+SPF_response_t  *spf_response_2mx = NULL;
+
+
+/* spf_init sets up a context that can be re-used for several
+   messages on the same SMTP connection (that come from the
+   same host with the same HELO string)
+
+Return: Boolean success */
+
+BOOL
+spf_init(uschar *spf_helo_domain, uschar *spf_remote_addr)
+{
+spf_server = SPF_server_new(SPF_DNS_CACHE, 0);
+
+if (!spf_server)
+  {
+  DEBUG(D_receive) debug_printf("spf: SPF_server_new() failed.\n");
+  return FALSE;
+  }
+
+if (SPF_server_set_rec_dom(spf_server, CS primary_hostname))
+  {
+  DEBUG(D_receive) debug_printf("spf: SPF_server_set_rec_dom(\"%s\") failed.\n",
+    primary_hostname);
+  spf_server = NULL;
+  return FALSE;
+  }
+
+spf_request = SPF_request_new(spf_server);
+
+if (  SPF_request_set_ipv4_str(spf_request, CS spf_remote_addr)
+   && SPF_request_set_ipv6_str(spf_request, CS spf_remote_addr)
+   )
+  {
+  DEBUG(D_receive)
+    debug_printf("spf: SPF_request_set_ipv4_str() and "
+      "SPF_request_set_ipv6_str() failed [%s]\n", spf_remote_addr);
+  spf_server = NULL;
+  spf_request = NULL;
+  return FALSE;
+  }
+
+if (SPF_request_set_helo_dom(spf_request, CS spf_helo_domain))
+  {
+  DEBUG(D_receive) debug_printf("spf: SPF_set_helo_dom(\"%s\") failed.\n",
+    spf_helo_domain);
+  spf_server = NULL;
+  spf_request = NULL;
+  return FALSE;
+  }
+
+return TRUE;
+}
+
+
+/* spf_process adds the envelope sender address to the existing
+   context (if any), retrieves the result, sets up expansion
+   strings and evaluates the condition outcome.
+
+Return: OK/FAIL  */
+
+int
+spf_process(const uschar **listptr, uschar *spf_envelope_sender, int action)
+{
+int sep = 0;
+const uschar *list = *listptr;
+uschar *spf_result_id;
+int rc = SPF_RESULT_PERMERROR;
+
+if (!(spf_server && spf_request))
+  /* no global context, assume temp error and skip to evaluation */
+  rc = SPF_RESULT_PERMERROR;
+
+else if (SPF_request_set_env_from(spf_request, CS spf_envelope_sender))
+  /* Invalid sender address. This should be a real rare occurrence */
+  rc = SPF_RESULT_PERMERROR;
+
+else
+  {
+  /* get SPF result */
+  if (action == SPF_PROCESS_FALLBACK)
+    {
+    SPF_request_query_fallback(spf_request, &spf_response, CS spf_guess);
+    spf_result_guessed = TRUE;
+    }
+  else
+    SPF_request_query_mailfrom(spf_request, &spf_response);
+
+  /* set up expansion items */
+  spf_header_comment     = US SPF_response_get_header_comment(spf_response);
+  spf_received           = US SPF_response_get_received_spf(spf_response);
+  spf_result             = US SPF_strresult(SPF_response_result(spf_response));
+  spf_smtp_comment       = US SPF_response_get_smtp_comment(spf_response);
+
+  rc = SPF_response_result(spf_response);
+  }
+
+/* We got a result. Now see if we should return OK or FAIL for it */
+DEBUG(D_acl) debug_printf("SPF result is %s (%d)\n", SPF_strresult(rc), rc);
+
+if (action == SPF_PROCESS_GUESS && (!strcmp (SPF_strresult(rc), "none")))
+  return spf_process(listptr, spf_envelope_sender, SPF_PROCESS_FALLBACK);
+
+while ((spf_result_id = string_nextinlist(&list, &sep, NULL, 0)))
+  {
+  BOOL negate, result;
+
+  if ((negate = spf_result_id[0] == '!'))
+    spf_result_id++;
+
+  result = Ustrcmp(spf_result_id, spf_result_id_list[rc].name) == 0;
+  if (negate != result) return OK;
+  }
+
+/* no match */
+return FAIL;
+}
+
+
+
+gstring *
+authres_spf(gstring * g)
+{
+uschar * s;
+if (!spf_result) return g;
+
+g = string_append(g, 2, US";\n\tspf=", spf_result);
+if (spf_result_guessed)
+  g = string_cat(g, US" (best guess record for domain)");
+
+s = expand_string(US"$sender_address_domain");
+return s && *s
+  ? string_append(g, 2, US" smtp.mailfrom=", s)
+  : string_cat(g, US" smtp.mailfrom=<>");
+}
+
+
+#endif
index 0ce5d00..23ad325 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
 
 /* Experimental SPF support.
    Copyright (c) Tom Kistner <tom@duncanthrax.net> 2004
-   License: GPL */
+   License: GPL
+   Copyright (c) The Exim Maintainers 2016
+*/
 
 
-#ifdef EXPERIMENTAL_SPF
+#ifdef SUPPORT_SPF
 
 /* Yes, we do have ns_type. spf.h redefines it if we don't set this. Doh */
 #ifndef HAVE_NS_TYPE
 
 /* 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>
 
 #endif
 #include <spf2/spf.h>
 
@@ -23,8 +25,8 @@ typedef struct spf_result_id {
 } spf_result_id;
 
 /* prototypes */
 } spf_result_id;
 
 /* prototypes */
-int spf_init(uschar *,uschar *);
-int spf_process(uschar **, uschar *, int);
+BOOL spf_init(uschar *,uschar *);
+int  spf_process(const uschar **, uschar *, int);
 
 #define SPF_PROCESS_NORMAL  0
 #define SPF_PROCESS_GUESS   1
 
 #define SPF_PROCESS_NORMAL  0
 #define SPF_PROCESS_GUESS   1
index 6dcb512..2d34977 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2012 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for reading spool files. When compiling for a utility (eximon),
 /* 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.
 
 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
 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;
 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
 
 /* 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,41 @@ splitting state. */
 
 for (i = 0; i < 2; i++)
   {
 
 for (i = 0; i < 2; i++)
   {
+  uschar * fname;
   int save_errno;
   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);
+
+  /* We protect against symlink attacks both in not propagating the
+   * file-descriptor to other processes as we exec, and also ensuring that we
+   * don't even open symlinks.
+   * No -D file inside the spool area should be a symlink.
+   */
+  if ((fd = Uopen(fname,
+#ifdef O_CLOEXEC
+                     O_CLOEXEC |
+#endif
+#ifdef O_NOFOLLOW
+                     O_NOFOLLOW |
+#endif
+                     O_RDWR | O_APPEND, 0)) >= 0)
+    break;
   save_errno = errno;
   if (errno == ENOENT)
     {
     if (i == 0) continue;
   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);
+    if (!f.queue_running)
+      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;
   errno = save_errno;
-  return FALSE;
+  return -1;
   }
 
 /* File is open and message_subdir is set. Set the close-on-exec flag, and lock
   }
 
 /* File is open and message_subdir is set. Set the close-on-exec flag, and lock
@@ -74,35 +95,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. */
 
 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;
 
 
 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)");
   {
   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;
   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. */
 
   }
 
 /* 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;
   }
 
   {
   message_body_size = statbuf.st_size - SPOOL_DATA_START_OFFSET;
   message_size = message_body_size + 1;
   }
 
-return TRUE;
+return fd;
 }
 #endif  /* COMPILE_UTILITY */
 
 }
 #endif  /* COMPILE_UTILITY */
 
@@ -193,66 +214,34 @@ return TRUE;
 
 
 
 
 
 
-/*************************************************
-*             Read spool header file             *
-*************************************************/
-
-/* This function reads a spool header file and places the data into the
-appropriate global variables. The header portion is always read, but header
-structures are built only if read_headers is set true. It isn't, for example,
-while generating -bp output.
-
-It may be possible for blocks of nulls (binary zeroes) to get written on the
-end of a file if there is a system crash during writing. It was observed on an
-earlier version of Exim that omitted to fsync() the files - this is thought to
-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.
-
-Arguments:
-  name          name of the header file, including the -H
-  read_headers  TRUE if in-store header structures are to be built
-  subdir_set    TRUE is message_subdir is already set
-
-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
-*/
-
-int
-spool_read_header(uschar *name, BOOL read_headers, BOOL subdir_set)
-{
-FILE *f = NULL;
-int n;
-int rcount = 0;
-long int uid, gid;
-BOOL inheader = FALSE;
-uschar *p;
-
 /* Reset all the global variables to their default values. However, there is
 one exception. DO NOT change the default value of dont_deliver, because it may
 be forced by an external setting. */
 
 /* Reset all the global variables to their default values. However, there is
 one exception. DO NOT change the default value of dont_deliver, because it may
 be forced by an external setting. */
 
+void
+spool_clear_header_globals(void)
+{
 acl_var_c = acl_var_m = NULL;
 authenticated_id = NULL;
 authenticated_sender = NULL;
 acl_var_c = acl_var_m = NULL;
 authenticated_id = NULL;
 authenticated_sender = NULL;
-allow_unqualified_recipient = FALSE;
-allow_unqualified_sender = FALSE;
+f.allow_unqualified_recipient = FALSE;
+f.allow_unqualified_sender = FALSE;
 body_linecount = 0;
 body_zerocount = 0;
 body_linecount = 0;
 body_zerocount = 0;
-deliver_firsttime = FALSE;
-deliver_freeze = FALSE;
+f.deliver_firsttime = FALSE;
+f.deliver_freeze = FALSE;
 deliver_frozen_at = 0;
 deliver_frozen_at = 0;
-deliver_manual_thaw = FALSE;
-/* dont_deliver must NOT be reset */
+f.deliver_manual_thaw = FALSE;
+/* f.dont_deliver must NOT be reset */
 header_list = header_last = NULL;
 host_lookup_deferred = FALSE;
 host_lookup_failed = FALSE;
 interface_address = NULL;
 interface_port = 0;
 header_list = header_last = NULL;
 host_lookup_deferred = FALSE;
 host_lookup_failed = FALSE;
 interface_address = NULL;
 interface_port = 0;
-local_error_message = FALSE;
+f.local_error_message = FALSE;
+#ifdef HAVE_LOCAL_SCAN
 local_scan_data = NULL;
 local_scan_data = NULL;
+#endif
 max_received_linelength = 0;
 message_linecount = 0;
 received_protocol = NULL;
 max_received_linelength = 0;
 message_linecount = 0;
 received_protocol = NULL;
@@ -266,9 +255,12 @@ sender_host_name = NULL;
 sender_host_port = 0;
 sender_host_authenticated = NULL;
 sender_ident = NULL;
 sender_host_port = 0;
 sender_host_authenticated = NULL;
 sender_ident = NULL;
-sender_local = FALSE;
-sender_set_untrusted = FALSE;
+f.sender_local = FALSE;
+f.sender_set_untrusted = FALSE;
 smtp_active_hostname = primary_hostname;
 smtp_active_hostname = primary_hostname;
+#ifndef COMPILE_UTILITY
+f.spool_file_wireformat = FALSE;
+#endif
 tree_nonrecipients = NULL;
 
 #ifdef EXPERIMENTAL_BRIGHTMAIL
 tree_nonrecipients = NULL;
 
 #ifdef EXPERIMENTAL_BRIGHTMAIL
@@ -278,28 +270,88 @@ bmi_verdicts = NULL;
 
 #ifndef DISABLE_DKIM
 dkim_signers = NULL;
 
 #ifndef DISABLE_DKIM
 dkim_signers = NULL;
-dkim_disable_verify = FALSE;
-dkim_collect_input = FALSE;
+f.dkim_disable_verify = FALSE;
+dkim_collect_input = 0;
 #endif
 
 #ifdef SUPPORT_TLS
 tls_in.certificate_verified = FALSE;
 #endif
 
 #ifdef SUPPORT_TLS
 tls_in.certificate_verified = FALSE;
+# ifdef SUPPORT_DANE
+tls_in.dane_verified = FALSE;
+# endif
 tls_in.cipher = NULL;
 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;
 tls_in.peerdn = NULL;
 tls_in.sni = NULL;
 tls_in.ocsp = OCSP_NOT_REQ;
+# if defined(EXPERIMENTAL_REQUIRETLS) && !defined(COMPILE_UTILITY)
+tls_requiretls = 0;
+# endif
 #endif
 
 #ifdef WITH_CONTENT_SCAN
 #endif
 
 #ifdef WITH_CONTENT_SCAN
+spam_bar = NULL;
+spam_score = NULL;
 spam_score_int = NULL;
 #endif
 
 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;
 dsn_ret = 0;
 dsn_envid = NULL;
-#endif
+}
+
+
+/*************************************************
+*             Read spool header file             *
+*************************************************/
+
+/* This function reads a spool header file and places the data into the
+appropriate global variables. The header portion is always read, but header
+structures are built only if read_headers is set true. It isn't, for example,
+while generating -bp output.
+
+It may be possible for blocks of nulls (binary zeroes) to get written on the
+end of a file if there is a system crash during writing. It was observed on an
+earlier version of Exim that omitted to fsync() the files - this is thought to
+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
+  subdir_set    TRUE is message_subdir is already set
+
+Returns:        spool_read_OK        success
+                spool_read_notopen   open failed
+                spool_read_enverror  error in the envelope portion
+                spool_read_hdrerror  error in the header portion
+*/
+
+int
+spool_read_header(uschar *name, BOOL read_headers, BOOL subdir_set)
+{
+FILE * fp = NULL;
+int n;
+int rcount = 0;
+long int uid, gid;
+BOOL inheader = FALSE;
+uschar *p;
+
+/* Reset all the global variables to their default values. However, there is
+one exception. DO NOT change the default value of dont_deliver, because it may
+be forced by an external setting. */
+
+spool_clear_header_globals();
 
 /* 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
 
 /* 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 +360,12 @@ and unsplit directories, as for the data file above. */
 for (n = 0; n < 2; n++)
   {
   if (!subdir_set)
 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 ((fp = Ufopen(spool_fname(US"input", message_subdir, name, US""), "rb")))
+    break;
+  if (n != 0 || subdir_set || errno != ENOENT)
+    return spool_read_notopen;
   }
 
 errno = 0;
   }
 
 errno = 0;
@@ -325,7 +377,7 @@ DEBUG(D_deliver) debug_printf("reading spool file %s\n", name);
 /* The first line of a spool file contains the message id followed by -H (i.e.
 the file name), in order to make the file self-identifying. */
 
 /* The first line of a spool file contains the message id followed by -H (i.e.
 the file name), in order to make the file self-identifying. */
 
-if (Ufgets(big_buffer, big_buffer_size, f) == NULL) goto SPOOL_READ_ERROR;
+if (Ufgets(big_buffer, big_buffer_size, fp) == NULL) goto SPOOL_READ_ERROR;
 if (Ustrlen(big_buffer) != MESSAGE_ID_LENGTH + 3 ||
     Ustrncmp(big_buffer, name, MESSAGE_ID_LENGTH + 2) != 0)
   goto SPOOL_FORMAT_ERROR;
 if (Ustrlen(big_buffer) != MESSAGE_ID_LENGTH + 3 ||
     Ustrncmp(big_buffer, name, MESSAGE_ID_LENGTH + 2) != 0)
   goto SPOOL_FORMAT_ERROR;
@@ -337,7 +389,7 @@ negative uids and gids. The second contains the mail address of the message's
 sender, enclosed in <>. The third contains the time the message was received,
 and the number of warning messages for delivery delays that have been sent. */
 
 sender, enclosed in <>. The third contains the time the message was received,
 and the number of warning messages for delivery delays that have been sent. */
 
-if (Ufgets(big_buffer, big_buffer_size, f) == NULL) goto SPOOL_READ_ERROR;
+if (Ufgets(big_buffer, big_buffer_size, fp) == NULL) goto SPOOL_READ_ERROR;
 
 p = big_buffer + Ustrlen(big_buffer);
 while (p > big_buffer && isspace(p[-1])) p--;
 
 p = big_buffer + Ustrlen(big_buffer);
 while (p > big_buffer && isspace(p[-1])) p--;
@@ -357,7 +409,8 @@ originator_login = string_copy(big_buffer);
 originator_uid = (uid_t)uid;
 originator_gid = (gid_t)gid;
 
 originator_uid = (uid_t)uid;
 originator_gid = (gid_t)gid;
 
-if (Ufgets(big_buffer, big_buffer_size, f) == NULL) goto SPOOL_READ_ERROR;
+/* envelope from */
+if (Ufgets(big_buffer, big_buffer_size, fp) == NULL) goto SPOOL_READ_ERROR;
 n = Ustrlen(big_buffer);
 if (n < 3 || big_buffer[0] != '<' || big_buffer[n-2] != '>')
   goto SPOOL_FORMAT_ERROR;
 n = Ustrlen(big_buffer);
 if (n < 3 || big_buffer[0] != '<' || big_buffer[n-2] != '>')
   goto SPOOL_FORMAT_ERROR;
@@ -366,11 +419,13 @@ sender_address = store_get(n-2);
 Ustrncpy(sender_address, big_buffer+1, n-3);
 sender_address[n-3] = 0;
 
 Ustrncpy(sender_address, big_buffer+1, n-3);
 sender_address[n-3] = 0;
 
-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)
+/* time */
+if (Ufgets(big_buffer, big_buffer_size, fp) == NULL) goto SPOOL_READ_ERROR;
+if (sscanf(CS big_buffer, TIME_T_FMT " %d", &received_time.tv_sec, &warning_count) != 2)
   goto SPOOL_FORMAT_ERROR;
   goto SPOOL_FORMAT_ERROR;
+received_time.tv_usec = 0;
 
 
-message_age = time(NULL) - received_time;
+message_age = time(NULL) - received_time.tv_sec;
 
 #ifndef COMPILE_UTILITY
 DEBUG(D_deliver) debug_printf("user=%s uid=%ld gid=%ld sender=%s\n",
 
 #ifndef COMPILE_UTILITY
 DEBUG(D_deliver) debug_printf("user=%s uid=%ld gid=%ld sender=%s\n",
@@ -394,9 +449,22 @@ version that left new-style flags written on the spool. */
 p = big_buffer + 2;
 for (;;)
   {
 p = big_buffer + 2;
 for (;;)
   {
-  if (Ufgets(big_buffer, big_buffer_size, f) == NULL) goto SPOOL_READ_ERROR;
+  int len;
+  if (Ufgets(big_buffer, big_buffer_size, fp) == NULL) goto SPOOL_READ_ERROR;
   if (big_buffer[0] != '-') break;
   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, fp) == NULL)
+      goto SPOOL_READ_ERROR;
+    }
+  big_buffer[len-1] = 0;
 
   switch(big_buffer[1])
     {
 
   switch(big_buffer[1])
     {
@@ -415,19 +483,19 @@ for (;;)
       tree_node *node;
       endptr = Ustrchr(big_buffer + 6, ' ');
       if (endptr == NULL) goto SPOOL_FORMAT_ERROR;
       tree_node *node;
       endptr = Ustrchr(big_buffer + 6, ' ');
       if (endptr == NULL) goto SPOOL_FORMAT_ERROR;
-      name = string_sprintf("%c%.*s", big_buffer[4], endptr - big_buffer - 6,
-        big_buffer + 6);
+      name = string_sprintf("%c%.*s", big_buffer[4],
+        (int)(endptr - big_buffer - 6), big_buffer + 6);
       if (sscanf(CS endptr, " %d", &count) != 1) goto SPOOL_FORMAT_ERROR;
       node = acl_var_create(name);
       node->data.ptr = store_get(count + 1);
       if (sscanf(CS endptr, " %d", &count) != 1) goto SPOOL_FORMAT_ERROR;
       node = acl_var_create(name);
       node->data.ptr = store_get(count + 1);
-      if (fread(node->data.ptr, 1, count+1, f) < count) goto SPOOL_READ_ERROR;
+      if (fread(node->data.ptr, 1, count+1, fp) < count) goto SPOOL_READ_ERROR;
       ((uschar*)node->data.ptr)[count] = 0;
       }
 
     else if (Ustrcmp(p, "llow_unqualified_recipient") == 0)
       ((uschar*)node->data.ptr)[count] = 0;
       }
 
     else if (Ustrcmp(p, "llow_unqualified_recipient") == 0)
-      allow_unqualified_recipient = TRUE;
+      f.allow_unqualified_recipient = TRUE;
     else if (Ustrcmp(p, "llow_unqualified_sender") == 0)
     else if (Ustrcmp(p, "llow_unqualified_sender") == 0)
-      allow_unqualified_sender = TRUE;
+      f.allow_unqualified_sender = TRUE;
 
     else if (Ustrncmp(p, "uth_id", 6) == 0)
       authenticated_id = string_copy(big_buffer + 9);
 
     else if (Ustrncmp(p, "uth_id", 6) == 0)
       authenticated_id = string_copy(big_buffer + 9);
@@ -445,19 +513,24 @@ for (;;)
 
     else if (Ustrncmp(p, "cl ", 3) == 0)
       {
 
     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)
         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);
       node = acl_var_create(name);
       node->data.ptr = store_get(count + 1);
-      if (fread(node->data.ptr, 1, count+1, f) < count) goto SPOOL_READ_ERROR;
-      ((uschar*)node->data.ptr)[count] = 0;
+      /* We sanity-checked the count, so disable the Coverity error */
+      /* coverity[tainted_data] */
+      if (fread(node->data.ptr, 1, count+1, fp) < count) goto SPOOL_READ_ERROR;
+      (US node->data.ptr)[count] = '\0';
       }
     break;
 
       }
     break;
 
@@ -466,33 +539,28 @@ for (;;)
       body_linecount = Uatoi(big_buffer + 15);
     else if (Ustrncmp(p, "ody_zerocount", 13) == 0)
       body_zerocount = Uatoi(big_buffer + 15);
       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);
     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)
     break;
 
     case 'd':
     if (Ustrcmp(p, "eliver_firsttime") == 0)
-      deliver_firsttime = TRUE;
-    #ifdef EXPERIMENTAL_DSN
+      f.deliver_firsttime = TRUE;
     /* Check if the dsn flags have been set in the header file */
     else if (Ustrncmp(p, "sn_ret", 6) == 0)
     /* 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)
     else if (Ustrncmp(p, "sn_envid", 8) == 0)
-      {
       dsn_envid = string_copy(big_buffer + 11);
       dsn_envid = string_copy(big_buffer + 11);
-      }
-    #endif
     break;
 
     case 'f':
     if (Ustrncmp(p, "rozen", 5) == 0)
       {
     break;
 
     case 'f':
     if (Ustrncmp(p, "rozen", 5) == 0)
       {
-      deliver_freeze = TRUE;
-      sscanf(big_buffer+7, TIME_T_FMT, &deliver_frozen_at);
+      f.deliver_freeze = TRUE;
+      if (sscanf(CS big_buffer+7, TIME_T_FMT, &deliver_frozen_at) != 1)
+       goto SPOOL_READ_ERROR;
       }
     break;
 
       }
     break;
 
@@ -530,57 +598,95 @@ for (;;)
     break;
 
     case 'l':
     break;
 
     case 'l':
-    if (Ustrcmp(p, "ocal") == 0) sender_local = TRUE;
+    if (Ustrcmp(p, "ocal") == 0)
+      f.sender_local = TRUE;
     else if (Ustrcmp(big_buffer, "-localerror") == 0)
     else if (Ustrcmp(big_buffer, "-localerror") == 0)
-      local_error_message = TRUE;
+      f.local_error_message = TRUE;
+#ifdef HAVE_LOCAL_SCAN
     else if (Ustrncmp(p, "ocal_scan ", 10) == 0)
       local_scan_data = string_copy(big_buffer + 12);
     else if (Ustrncmp(p, "ocal_scan ", 10) == 0)
       local_scan_data = string_copy(big_buffer + 12);
+#endif
     break;
 
     case 'm':
     break;
 
     case 'm':
-    if (Ustrcmp(p, "anual_thaw") == 0) deliver_manual_thaw = TRUE;
+    if (Ustrcmp(p, "anual_thaw") == 0) f.deliver_manual_thaw = TRUE;
     else if (Ustrncmp(p, "ax_received_linelength", 22) == 0)
       max_received_linelength = Uatoi(big_buffer + 24);
     break;
 
     case 'N':
     else if (Ustrncmp(p, "ax_received_linelength", 22) == 0)
       max_received_linelength = Uatoi(big_buffer + 24);
     break;
 
     case 'N':
-    if (*p == 0) dont_deliver = TRUE;   /* -N */
+    if (*p == 0) f.dont_deliver = TRUE;   /* -N */
     break;
 
     case 'r':
     if (Ustrncmp(p, "eceived_protocol", 16) == 0)
       received_protocol = string_copy(big_buffer + 19);
     break;
 
     case 'r':
     if (Ustrncmp(p, "eceived_protocol", 16) == 0)
       received_protocol = string_copy(big_buffer + 19);
+    else if (Ustrncmp(p, "eceived_time_usec", 17) == 0)
+      {
+      unsigned usec;
+      if (sscanf(CS big_buffer + 21, "%u", &usec) == 1)
+       received_time.tv_usec = usec;
+      }
     break;
 
     case 's':
     if (Ustrncmp(p, "ender_set_untrusted", 19) == 0)
     break;
 
     case 's':
     if (Ustrncmp(p, "ender_set_untrusted", 19) == 0)
-      sender_set_untrusted = TRUE;
-    #ifdef WITH_CONTENT_SCAN
+      f.sender_set_untrusted = TRUE;
+#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);
     else if (Ustrncmp(p, "pam_score_int ", 14) == 0)
       spam_score_int = string_copy(big_buffer + 16);
-    #endif
+#endif
+#ifndef COMPILE_UTILITY
+    else if (Ustrncmp(p, "pool_file_wireformat", 20) == 0)
+      f.spool_file_wireformat = TRUE;
+#endif
+#if defined(SUPPORT_I18N) && !defined(COMPILE_UTILITY)
+    else if (Ustrncmp(p, "mtputf8", 7) == 0)
+      message_smtputf8 = TRUE;
+#endif
     break;
 
     break;
 
-    #ifdef SUPPORT_TLS
+#ifdef SUPPORT_TLS
     case 't':
     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
-    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);
+    if (Ustrncmp(p, "ls_", 3) == 0)
+      {
+      uschar * q = p + 3;
+      if (Ustrncmp(q, "certificate_verified", 20) == 0)
+       tls_in.certificate_verified = TRUE;
+      else if (Ustrncmp(q, "cipher", 6) == 0)
+       tls_in.cipher = string_copy(big_buffer + 12);
+# ifndef COMPILE_UTILITY       /* tls support fns not built in */
+      else if (Ustrncmp(q, "ourcert", 7) == 0)
+       (void) tls_import_cert(big_buffer + 13, &tls_in.ourcert);
+      else if (Ustrncmp(q, "peercert", 8) == 0)
+       (void) tls_import_cert(big_buffer + 14, &tls_in.peercert);
+# endif
+      else if (Ustrncmp(q, "peerdn", 6) == 0)
+       tls_in.peerdn = string_unprinting(string_copy(big_buffer + 12));
+      else if (Ustrncmp(q, "sni", 3) == 0)
+       tls_in.sni = string_unprinting(string_copy(big_buffer + 9));
+      else if (Ustrncmp(q, "ocsp", 4) == 0)
+       tls_in.ocsp = big_buffer[10] - '0';
+# if defined(EXPERIMENTAL_REQUIRETLS) && !defined(COMPILE_UTILITY)
+      else if (Ustrncmp(q, "requiretls", 10) == 0)
+       tls_requiretls = strtol(CS big_buffer+16, NULL, 0);
+# endif
+      }
+    break;
 #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)
-      tls_in.sni = string_unprinting(string_copy(big_buffer + 9));
-    else if (Ustrncmp(p, "ls_ocsp", 7) == 0)
-      tls_in.ocsp = big_buffer[10] - '0';
+
+#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;
     break;
-    #endif
+#endif
 
     default:    /* Present because some compilers complain if all */
     break;      /* possibilities are not covered. */
 
     default:    /* Present because some compilers complain if all */
     break;      /* possibilities are not covered. */
@@ -595,7 +701,7 @@ host_build_sender_fullhost();
 
 #ifndef COMPILE_UTILITY
 DEBUG(D_deliver)
 
 #ifndef COMPILE_UTILITY
 DEBUG(D_deliver)
-  debug_printf("sender_local=%d ident=%s\n", sender_local,
+  debug_printf("sender_local=%d ident=%s\n", f.sender_local,
     (sender_ident == NULL)? US"unset" : sender_ident);
 #endif  /* COMPILE_UTILITY */
 
     (sender_ident == NULL)? US"unset" : sender_ident);
 #endif  /* COMPILE_UTILITY */
 
@@ -603,7 +709,7 @@ DEBUG(D_deliver)
 containing "XX", indicating no tree. */
 
 if (Ustrncmp(big_buffer, "XX\n", 3) != 0 &&
 containing "XX", indicating no tree. */
 
 if (Ustrncmp(big_buffer, "XX\n", 3) != 0 &&
-  !read_nonrecipients_tree(&tree_nonrecipients, f, big_buffer, big_buffer_size))
+  !read_nonrecipients_tree(&tree_nonrecipients, fp, big_buffer, big_buffer_size))
     goto SPOOL_FORMAT_ERROR;
 
 #ifndef COMPILE_UTILITY
     goto SPOOL_FORMAT_ERROR;
 
 #ifndef COMPILE_UTILITY
@@ -615,10 +721,12 @@ DEBUG(D_deliver)
 #endif  /* COMPILE_UTILITY */
 
 /* After reading the tree, the next line has not yet been read into the
 #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 (Ufgets(big_buffer, big_buffer_size, fp) == NULL) goto SPOOL_READ_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);
 
 #ifndef COMPILE_UTILITY
 DEBUG(D_deliver) debug_printf("recipients_count=%d\n", rcount);
@@ -627,18 +735,20 @@ DEBUG(D_deliver) debug_printf("recipients_count=%d\n", rcount);
 recipients_list_max = rcount;
 recipients_list = store_get(rcount * sizeof(recipient_item));
 
 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;
 for (recipients_count = 0; recipients_count < rcount; recipients_count++)
   {
   int nn;
   int pno = -1;
-  #ifdef EXPERIMENTAL_DSN
   int dsn_flags = 0;
   uschar *orcpt = NULL;
   int dsn_flags = 0;
   uschar *orcpt = NULL;
-  #endif
   uschar *errors_to = NULL;
   uschar *p;
 
   uschar *errors_to = NULL;
   uschar *p;
 
-  if (Ufgets(big_buffer, big_buffer_size, f) == NULL) goto SPOOL_READ_ERROR;
+  if (Ufgets(big_buffer, big_buffer_size, fp) == NULL) goto SPOOL_READ_ERROR;
   nn = Ustrlen(big_buffer);
   if (nn < 2) goto SPOOL_FORMAT_ERROR;
 
   nn = Ustrlen(big_buffer);
   if (nn < 2) goto SPOOL_FORMAT_ERROR;
 
@@ -711,11 +821,9 @@ for (recipients_count = 0; recipients_count < rcount; recipients_count++)
     {
     int flags;
 
     {
     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);
 
 
     (void)sscanf(CS p+1, "%d", &flags);
 
@@ -729,11 +837,10 @@ for (recipients_count = 0; recipients_count < rcount; recipients_count++)
         {
         p -= len;
         errors_to = string_copy(p);
         {
         p -= len;
         errors_to = string_copy(p);
-        }      
+        }
       }
 
     *(--p) = 0;   /* Terminate address */
       }
 
     *(--p) = 0;   /* Terminate address */
-#ifdef EXPERIMENTAL_DSN
     if ((flags & 0x02) != 0)      /* one_time data exists */
       {
       int len;
     if ((flags & 0x02) != 0)      /* one_time data exists */
       {
       int len;
@@ -744,18 +851,14 @@ for (recipients_count = 0; recipients_count < rcount; recipients_count++)
         {
         p -= len;
         orcpt = string_copy(p);
         {
         p -= len;
         orcpt = string_copy(p);
-        }      
+        }
       }
 
     *(--p) = 0;   /* Terminate address */
       }
 
     *(--p) = 0;   /* Terminate address */
-#endif  /* EXPERIMENTAL_DSN */
     }
     }
-#ifdef EXPERIMENTAL_DSN
-  #ifndef COMPILE_UTILITY
+#if !defined(COMPILE_UTILITY)
   else
   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))
     {
 
   if ((orcpt != NULL) || (dsn_flags != 0))
     {
@@ -767,16 +870,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);
     }
     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;
 
   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;
   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,
   }
 
 /* The remainder of the spool header file contains the headers for the message,
@@ -788,17 +888,17 @@ always, in order to check on the format of the file, but only create a header
 list if requested to do so. */
 
 inheader = TRUE;
 list if requested to do so. */
 
 inheader = TRUE;
-if (Ufgets(big_buffer, big_buffer_size, f) == NULL) goto SPOOL_READ_ERROR;
+if (Ufgets(big_buffer, big_buffer_size, fp) == NULL) goto SPOOL_READ_ERROR;
 if (big_buffer[0] != '\n') goto SPOOL_FORMAT_ERROR;
 
 if (big_buffer[0] != '\n') goto SPOOL_FORMAT_ERROR;
 
-while ((n = fgetc(f)) != EOF)
+while ((n = fgetc(fp)) != EOF)
   {
   header_line *h;
   uschar flag[4];
   int i;
 
   if (!isdigit(n)) goto SPOOL_FORMAT_ERROR;
   {
   header_line *h;
   uschar flag[4];
   int i;
 
   if (!isdigit(n)) goto SPOOL_FORMAT_ERROR;
-  if(ungetc(n, f) == EOF  ||  fscanf(f, "%d%c ", &n, flag) == EOF)
+  if(ungetc(n, fp) == EOF  ||  fscanf(fp, "%d%c ", &n, flag) == EOF)
     goto SPOOL_READ_ERROR;
   if (flag[0] != '*') message_size += n;  /* Omit non-transmitted headers */
 
     goto SPOOL_READ_ERROR;
   if (flag[0] != '*') message_size += n;  /* Omit non-transmitted headers */
 
@@ -818,7 +918,7 @@ while ((n = fgetc(f)) != EOF)
 
     for (i = 0; i < n; i++)
       {
 
     for (i = 0; i < n; i++)
       {
-      int c = fgetc(f);
+      int c = fgetc(fp);
       if (c == 0 || c == EOF) goto SPOOL_FORMAT_ERROR;
       if (c == '\n' && h->type != htype_old) message_linecount++;
       h->text[i] = c;
       if (c == 0 || c == EOF) goto SPOOL_FORMAT_ERROR;
       if (c == '\n' && h->type != htype_old) message_linecount++;
       h->text[i] = c;
@@ -830,7 +930,7 @@ while ((n = fgetc(f)) != EOF)
 
   else for (i = 0; i < n; i++)
     {
 
   else for (i = 0; i < n; i++)
     {
-    int c = fgetc(f);
+    int c = fgetc(fp);
     if (c == 0 || c == EOF) goto SPOOL_FORMAT_ERROR;
     }
   }
     if (c == 0 || c == EOF) goto SPOOL_FORMAT_ERROR;
     }
   }
@@ -846,7 +946,7 @@ DEBUG(D_deliver) debug_printf("body_linecount=%d message_linecount=%d\n",
 
 message_linecount += body_linecount;
 
 
 message_linecount += body_linecount;
 
-fclose(f);
+fclose(fp);
 return spool_read_OK;
 
 
 return spool_read_OK;
 
 
@@ -859,11 +959,11 @@ if (errno != 0)
   {
   n = errno;
 
   {
   n = errno;
 
-  #ifndef COMPILE_UTILITY
+#ifndef COMPILE_UTILITY
   DEBUG(D_any) debug_printf("Error while reading spool file %s\n", name);
   DEBUG(D_any) debug_printf("Error while reading spool file %s\n", name);
-  #endif  /* COMPILE_UTILITY */
+#endif  /* COMPILE_UTILITY */
 
 
-  fclose(f);
+  fclose(fp);
   errno = n;
   return inheader? spool_read_hdrerror : spool_read_enverror;
   }
   errno = n;
   return inheader? spool_read_hdrerror : spool_read_enverror;
   }
@@ -874,7 +974,7 @@ SPOOL_FORMAT_ERROR:
 DEBUG(D_any) debug_printf("Format error in spool file %s\n", name);
 #endif  /* COMPILE_UTILITY */
 
 DEBUG(D_any) debug_printf("Format error in spool file %s\n", name);
 #endif  /* COMPILE_UTILITY */
 
-fclose(f);
+fclose(fp);
 errno = ERRNO_SPOOLFORMAT;
 return inheader? spool_read_hdrerror : spool_read_enverror;
 }
 errno = ERRNO_SPOOLFORMAT;
 return inheader? spool_read_hdrerror : spool_read_enverror;
 }
dissimilarity index 88%
index 12cf3d4..2447daf 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 - 2018
+ */
+
+/* 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];
+
+/*
+Create an MBOX-style message file from the spooled files.
+
+Returns a pointer to the FILE, and puts the size in bytes into mbox_file_size.
+If mbox_fname is non-null, fill in a pointer to the name.
+Normally, source_file_override is NULL
+*/
+
+FILE *
+spool_mbox(unsigned long *mbox_file_size, const uschar *source_file_override,
+  uschar ** mbox_fname)
+{
+uschar message_subdir[2];
+uschar buffer[16384];
+uschar *temp_string;
+uschar *mbox_path;
+FILE *mbox_file = NULL, *l_data_file = NULL, *yield = NULL;
+header_line *my_headerlist;
+struct stat statbuf;
+int i, j;
+void *reset_point;
+
+mbox_path = string_sprintf("%s/scan/%s/%s.eml",
+  spool_directory, message_id, message_id);
+if (mbox_fname) *mbox_fname = mbox_path;
+
+reset_point = store_get(0);
+
+/* 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 */
+
+  if (!(mbox_file = modefopen(mbox_path, "wb", SPOOL_MODE)))
+    {
+    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)
+    if (fwrite(temp_string, Ustrlen(temp_string), 1, mbox_file) != 1)
+      {
+      log_write(0, LOG_MAIN|LOG_PANIC, "Error/short write while writing \
+         mailbox headers to %s", mbox_path);
+      goto OUT;
+      }
+
+  /* write all non-deleted header lines to mbox file */
+
+  for (my_headerlist = header_list; my_headerlist;
+      my_headerlist = my_headerlist->next)
+    if (my_headerlist->type != '*')
+      if (fwrite(my_headerlist->text, my_headerlist->slen, 1, mbox_file) != 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 the main receive still has it open then it is holding
+  a lock, and we must not close it (which releases the lock), so just use the
+  global file handle. */
+  if (source_file_override)
+    l_data_file = Ufopen(source_file_override, "rb");
+  else if (spool_data_file)
+    l_data_file = spool_data_file;
+  else
+    {
+    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 ((l_data_file = Ufopen(temp_string, "rb"))) break;
+      }
+    }
+
+  if (!l_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, l_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(l_data_file, SPOOL_DATA_START_OFFSET, SEEK_SET);
+
+  do
+    {
+    uschar * s;
+
+    if (!f.spool_file_wireformat || source_file_override)
+      j = fread(buffer, 1, sizeof(buffer), l_data_file);
+    else                                               /* needs CRLF -> NL */
+      if ((s = US fgets(CS buffer, sizeof(buffer), l_data_file)))
+       {
+       uschar * p = s + Ustrlen(s) - 1;
+
+       if (*p == '\n' && p[-1] == '\r')
+         *--p = '\n';
+       else if (*p == '\r')
+         ungetc(*p--, l_data_file);
+
+       j = p - buffer;
+       }
+      else
+       j = 0;
+
+    if (j > 0)
+      if (fwrite(buffer, j, 1, mbox_file) != 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 (l_data_file && !spool_data_file) (void)fclose(l_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 && !f.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);
+
+  if (!(tempdir = opendir(CS mbox_path)))
+    {
+    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)))
+    {
+    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); dummy = dummy;       /* compiler quietening */
+    }
+
+  closedir(tempdir);
+
+  /* remove directory */
+  rmdir(CS mbox_path);
+  store_reset(mbox_path);
+  }
+spool_mbox_ok = 0;
+}
+
+#endif
index 67ac8bc..d558952 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2012 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for writing spool files, and moving them about. */
 /* 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
 *************************************************/
 
 /* 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.
 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)
 {
 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;
   *errmsg = msg;
+else
+  log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s", msg);
 
 return -1;
 }
 
 return -1;
 }
@@ -131,18 +130,19 @@ spool_write_header(uschar *id, int where, uschar **errmsg)
 int fd;
 int i;
 int size_correction;
 int fd;
 int i;
 int size_correction;
-FILE *f;
+FILE * fp;
 header_line *h;
 struct stat statbuf;
 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);
-f = fdopen(fd, "wb");
-DEBUG(D_receive|D_deliver) debug_printf("Writing spool header file\n");
+if ((fd = spool_open_temp(tname)) < 0)
+  return spool_write_error(where, errmsg, US"open", NULL, NULL);
+fp = fdopen(fd, "wb");
+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
 
 /* 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
@@ -150,158 +150,170 @@ identity of the submitting user, followed by the sender's address. The sender's
 address is enclosed in <> because it might be the null address. Then write the
 received time and the number of warning messages that have been sent. */
 
 address is enclosed in <> because it might be the null address. Then write the
 received time and the number of warning messages that have been sent. */
 
-fprintf(f, "%s-H\n", message_id);
-fprintf(f, "%.63s %ld %ld\n", originator_login, (long int)originator_uid,
+fprintf(fp, "%s-H\n", message_id);
+fprintf(fp, "%.63s %ld %ld\n", originator_login, (long int)originator_uid,
   (long int)originator_gid);
   (long int)originator_gid);
-fprintf(f, "<%s>\n", sender_address);
-fprintf(f, "%d %d\n", received_time, warning_count);
+fprintf(fp, "<%s>\n", sender_address);
+fprintf(fp, "%d %d\n", (int)received_time.tv_sec, warning_count);
+
+fprintf(fp, "-received_time_usec .%06d\n", (int)received_time.tv_usec);
 
 /* If there is information about a sending host, remember it. The HELO
 data can be set for local SMTP as well as remote. */
 
 
 /* If there is information about a sending host, remember it. The HELO
 data can be set for local SMTP as well as remote. */
 
-if (sender_helo_name != NULL)
-  fprintf(f, "-helo_name %s\n", sender_helo_name);
+if (sender_helo_name)
+  fprintf(fp, "-helo_name %s\n", sender_helo_name);
 
 
-if (sender_host_address != NULL)
+if (sender_host_address)
   {
   {
-  fprintf(f, "-host_address %s.%d\n", sender_host_address, sender_host_port);
-  if (sender_host_name != NULL)
-    fprintf(f, "-host_name %s\n", sender_host_name);
-  if (sender_host_authenticated != NULL)
-    fprintf(f, "-host_auth %s\n", sender_host_authenticated);
+  fprintf(fp, "-host_address %s.%d\n", sender_host_address, sender_host_port);
+  if (sender_host_name)
+    fprintf(fp, "-host_name %s\n", sender_host_name);
+  if (sender_host_authenticated)
+    fprintf(fp, "-host_auth %s\n", sender_host_authenticated);
   }
 
 /* Also about the interface a message came in on */
 
   }
 
 /* Also about the interface a message came in on */
 
-if (interface_address != NULL)
-  fprintf(f, "-interface_address %s.%d\n", interface_address, interface_port);
+if (interface_address)
+  fprintf(fp, "-interface_address %s.%d\n", interface_address, interface_port);
 
 if (smtp_active_hostname != primary_hostname)
 
 if (smtp_active_hostname != primary_hostname)
-  fprintf(f, "-active_hostname %s\n", smtp_active_hostname);
+  fprintf(fp, "-active_hostname %s\n", smtp_active_hostname);
 
 /* Likewise for any ident information; for local messages this is
 likely to be the same as originator_login, but will be different if
 the originator was root, forcing a different ident. */
 
 
 /* Likewise for any ident information; for local messages this is
 likely to be the same as originator_login, but will be different if
 the originator was root, forcing a different ident. */
 
-if (sender_ident != NULL) fprintf(f, "-ident %s\n", sender_ident);
+if (sender_ident) fprintf(fp, "-ident %s\n", sender_ident);
 
 /* Ditto for the received protocol */
 
 
 /* Ditto for the received protocol */
 
-if (received_protocol != NULL)
-  fprintf(f, "-received_protocol %s\n", received_protocol);
+if (received_protocol)
+  fprintf(fp, "-received_protocol %s\n", received_protocol);
 
 /* Preserve any ACL variables that are set. */
 
 
 /* Preserve any ACL variables that are set. */
 
-tree_walk(acl_var_c, &acl_var_write, f);
-tree_walk(acl_var_m, &acl_var_write, f);
+tree_walk(acl_var_c, &acl_var_write, fp);
+tree_walk(acl_var_m, &acl_var_write, fp);
 
 /* Now any other data that needs to be remembered. */
 
 
 /* Now any other data that needs to be remembered. */
 
-fprintf(f, "-body_linecount %d\n", body_linecount);
-fprintf(f, "-max_received_linelength %d\n", max_received_linelength);
-
-if (body_zerocount > 0) fprintf(f, "-body_zerocount %d\n", body_zerocount);
-
-if (authenticated_id != NULL)
-  fprintf(f, "-auth_id %s\n", authenticated_id);
-if (authenticated_sender != NULL)
-  fprintf(f, "-auth_sender %s\n", authenticated_sender);
-
-if (allow_unqualified_recipient) fprintf(f, "-allow_unqualified_recipient\n");
-if (allow_unqualified_sender) fprintf(f, "-allow_unqualified_sender\n");
-if (deliver_firsttime) fprintf(f, "-deliver_firsttime\n");
-if (deliver_freeze) fprintf(f, "-frozen " TIME_T_FMT "\n", deliver_frozen_at);
-if (dont_deliver) fprintf(f, "-N\n");
-if (host_lookup_deferred) fprintf(f, "-host_lookup_deferred\n");
-if (host_lookup_failed) fprintf(f, "-host_lookup_failed\n");
-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);
+if (f.spool_file_wireformat)
+  fprintf(fp, "-spool_file_wireformat\n");
+else
+  fprintf(fp, "-body_linecount %d\n", body_linecount);
+fprintf(fp, "-max_received_linelength %d\n", max_received_linelength);
+
+if (body_zerocount > 0) fprintf(fp, "-body_zerocount %d\n", body_zerocount);
+
+if (authenticated_id)
+  fprintf(fp, "-auth_id %s\n", authenticated_id);
+if (authenticated_sender)
+  fprintf(fp, "-auth_sender %s\n", authenticated_sender);
+
+if (f.allow_unqualified_recipient) fprintf(fp, "-allow_unqualified_recipient\n");
+if (f.allow_unqualified_sender) fprintf(fp, "-allow_unqualified_sender\n");
+if (f.deliver_firsttime) fprintf(fp, "-deliver_firsttime\n");
+if (f.deliver_freeze) fprintf(fp, "-frozen " TIME_T_FMT "\n", deliver_frozen_at);
+if (f.dont_deliver) fprintf(fp, "-N\n");
+if (host_lookup_deferred) fprintf(fp, "-host_lookup_deferred\n");
+if (host_lookup_failed) fprintf(fp, "-host_lookup_failed\n");
+if (f.sender_local) fprintf(fp, "-local\n");
+if (f.local_error_message) fprintf(fp, "-localerror\n");
+#ifdef HAVE_LOCAL_SCAN
+if (local_scan_data) fprintf(fp, "-local_scan %s\n", local_scan_data);
+#endif
 #ifdef WITH_CONTENT_SCAN
 #ifdef WITH_CONTENT_SCAN
-if (spam_score_int != NULL) fprintf(f,"-spam_score_int %s\n", spam_score_int);
+if (spam_bar)       fprintf(fp,"-spam_bar %s\n",       spam_bar);
+if (spam_score)     fprintf(fp,"-spam_score %s\n",     spam_score);
+if (spam_score_int) fprintf(fp,"-spam_score_int %s\n", spam_score_int);
 #endif
 #endif
-if (deliver_manual_thaw) fprintf(f, "-manual_thaw\n");
-if (sender_set_untrusted) fprintf(f, "-sender_set_untrusted\n");
+if (f.deliver_manual_thaw) fprintf(fp, "-manual_thaw\n");
+if (f.sender_set_untrusted) fprintf(fp, "-sender_set_untrusted\n");
 
 #ifdef EXPERIMENTAL_BRIGHTMAIL
 
 #ifdef EXPERIMENTAL_BRIGHTMAIL
-if (bmi_verdicts != NULL) fprintf(f, "-bmi_verdicts %s\n", bmi_verdicts);
+if (bmi_verdicts) fprintf(fp, "-bmi_verdicts %s\n", bmi_verdicts);
 #endif
 
 #ifdef SUPPORT_TLS
 #endif
 
 #ifdef SUPPORT_TLS
-if (tls_in.certificate_verified) fprintf(f, "-tls_certificate_verified\n");
-if (tls_in.cipher)       fprintf(f, "-tls_cipher %s\n", tls_in.cipher);
+if (tls_in.certificate_verified) fprintf(fp, "-tls_certificate_verified\n");
+if (tls_in.cipher)       fprintf(fp, "-tls_cipher %s\n", tls_in.cipher);
 if (tls_in.peercert)
   {
   (void) tls_export_cert(big_buffer, big_buffer_size, tls_in.peercert);
 if (tls_in.peercert)
   {
   (void) tls_export_cert(big_buffer, big_buffer_size, tls_in.peercert);
-  fprintf(f, "-tls_peercert %s\n", CS big_buffer);
+  fprintf(fp, "-tls_peercert %s\n", CS big_buffer);
   }
   }
-if (tls_in.peerdn)       fprintf(f, "-tls_peerdn %s\n", string_printing(tls_in.peerdn));
-if (tls_in.sni)                 fprintf(f, "-tls_sni %s\n",    string_printing(tls_in.sni));
+if (tls_in.peerdn)       fprintf(fp, "-tls_peerdn %s\n", string_printing(tls_in.peerdn));
+if (tls_in.sni)                 fprintf(fp, "-tls_sni %s\n",    string_printing(tls_in.sni));
 if (tls_in.ourcert)
   {
   (void) tls_export_cert(big_buffer, big_buffer_size, tls_in.ourcert);
 if (tls_in.ourcert)
   {
   (void) tls_export_cert(big_buffer, big_buffer_size, tls_in.ourcert);
-  fprintf(f, "-tls_ourcert %s\n", CS big_buffer);
+  fprintf(fp, "-tls_ourcert %s\n", CS big_buffer);
+  }
+if (tls_in.ocsp)        fprintf(fp, "-tls_ocsp %d\n",   tls_in.ocsp);
+
+# ifdef EXPERIMENTAL_REQUIRETLS
+if (tls_requiretls)     fprintf(fp, "-tls_requiretls 0x%x\n", tls_requiretls);
+# endif
+#endif
+
+#ifdef SUPPORT_I18N
+if (message_smtputf8)
+  {
+  fprintf(fp, "-smtputf8\n");
+  if (message_utf8_downconvert)
+    fprintf(fp, "-utf8_%sdowncvt\n", message_utf8_downconvert < 0 ? "opt" : "");
   }
   }
-if (tls_in.ocsp)        fprintf(f, "-tls_ocsp %d\n",   tls_in.ocsp);
 #endif
 
 #endif
 
-#ifdef EXPERIMENTAL_DSN
 /* Write the dsn flags to the spool header file */
 DEBUG(D_deliver) debug_printf("DSN: Write SPOOL :-dsn_envid %s\n", dsn_envid);
 /* 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);
+if (dsn_envid) fprintf(fp, "-dsn_envid %s\n", dsn_envid);
 DEBUG(D_deliver) debug_printf("DSN: Write SPOOL :-dsn_ret %d\n", dsn_ret);
 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
+if (dsn_ret) fprintf(fp, "-dsn_ret %d\n", dsn_ret);
 
 /* 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
 checking has been done. If a recipient is a "one-time" alias, it is followed by
 a space and its parent address number (pno). */
 
 
 /* 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
 checking has been done. If a recipient is a "one-time" alias, it is followed by
 a space and its parent address number (pno). */
 
-tree_write(tree_nonrecipients, f);
-fprintf(f, "%d\n", recipients_count);
+tree_write(tree_nonrecipients, fp);
+fprintf(fp, "%d\n", recipients_count);
 for (i = 0; i < recipients_count; i++)
   {
   recipient_item *r = recipients_list + i;
 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
-    )
-    fprintf(f, "%s\n", r->address);
+
+  DEBUG(D_deliver) debug_printf("DSN: Flags :%d\n", r->dsn_flags);
+
+  if (r->pno < 0 && r->errors_to == NULL && r->dsn_flags == 0)
+    fprintf(fp, "%s\n", r->address);
   else
     {
   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
     /* 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(fp, "%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 */
 
   }
 
 /* Put a blank line before the headers */
 
-fprintf(f, "\n");
+fprintf(fp, "\n");
 
 /* Save the size of the file so far so we can subtract it from the final length
 to get the actual size of the headers. */
 
 
 /* Save the size of the file so far so we can subtract it from the final length
 to get the actual size of the headers. */
 
-fflush(f);
-fstat(fd, &statbuf);
+fflush(fp);
+if (fstat(fd, &statbuf))
+  return spool_write_error(where, errmsg, US"fstat", tname, fp);
 size_correction = statbuf.st_size;
 
 /* Finally, write out the message's headers. To make it easier to read them
 size_correction = statbuf.st_size;
 
 /* Finally, write out the message's headers. To make it easier to read them
@@ -312,38 +324,40 @@ various other headers, or an asterisk for old headers that have been rewritten.
 These are saved as a record for debugging. Don't included them in the message's
 size. */
 
 These are saved as a record for debugging. Don't included them in the message's
 size. */
 
-for (h = header_list; h != NULL; h = h->next)
+for (h = header_list; h; h = h->next)
   {
   {
-  fprintf(f, "%03d%c %s", h->slen, h->type, h->text);
+  fprintf(fp, "%03d%c %s", h->slen, h->type, h->text);
   size_correction += 5;
   if (h->type == '*') size_correction += h->slen;
   }
 
 /* Flush and check for any errors while writing */
 
   size_correction += 5;
   if (h->type == '*') size_correction += h->slen;
   }
 
 /* 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);
+if (fflush(fp) != 0 || ferror(fp))
+  return spool_write_error(where, errmsg, US"write", tname, fp);
 
 /* 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... */
 
 
 /* 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);
+if (EXIMfsync(fileno(fp)) < 0)
+  return spool_write_error(where, errmsg, US"sync", tname, fp);
 
 /* Get the size of the file, and close it. */
 
 
 /* Get the size of the file, and close it. */
 
-fstat(fd, &statbuf);
-if (fclose(f) != 0)
-  return spool_write_error(where, errmsg, US"close", temp_name, NULL);
+if (fstat(fd, &statbuf) != 0)
+  return spool_write_error(where, errmsg, US"fstat", tname, NULL);
+if (fclose(fp) != 0)
+  return spool_write_error(where, errmsg, US"close", tname, NULL);
 
 /* Rename the file to its correct name, thereby replacing any previous
 incarnation. */
 
 
 /* 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
 
 /* 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 +371,25 @@ these cases. One hack on top of another... but that's life. */
 
 #ifdef NEED_SYNC_DIRECTORY
 
 
 #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)
 
 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)
 
 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
 
 #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));
 
 DEBUG(D_receive) debug_printf("Size of headers = %d\n",
   (int)(statbuf.st_size - size_correction));
@@ -411,13 +425,12 @@ static BOOL
 make_link(uschar *dir, uschar *subdir, uschar *id, uschar *suffix, uschar *from,
   uschar *to, BOOL noentok)
 {
 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 "
   {
   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;
   return FALSE;
   }
 return TRUE;
@@ -449,12 +462,11 @@ static BOOL
 break_link(uschar *dir, uschar *subdir, uschar *id, uschar *suffix, uschar *from,
   BOOL noentok)
 {
 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 "
   {
   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;
   return FALSE;
   }
 return TRUE;
@@ -486,17 +498,19 @@ spool_move_message(uschar *id, uschar *subdir, uschar *from, uschar *to)
 {
 /* Create any output directories that do not exist. */
 
 {
 /* 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
 
 /* 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. */
 
 the mail spool, the -D file should be open and locked at the time, thus keeping
 Exim's hands off. */
 
@@ -519,3 +533,5 @@ return TRUE;
 #endif
 
 /* End of spool_out.c */
 #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>
 
 /* SRS - Sender rewriting scheme support
   (C)2004 Miles Wilton <miles@mirtol.com>
+  Copyright (c) The Exim Maintainers 2016
 
   SRS Support Version: 1.0a
 
 
   SRS Support Version: 1.0a
 
@@ -25,7 +26,7 @@ uschar   *srs_db_reverse        = NULL;
 
 int eximsrs_init()
 {
 
 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];
   uschar secret_buf[SRS_MAX_SECRET_LENGTH];
   uschar *secret = NULL;
   uschar sbuf[4];
index 3f0fec8..161052c 100644 (file)
@@ -2,7 +2,8 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) Phil Pennock 2012
+/* Copyright (c) Phil Pennock 2012, 2016
+ * Copyright (c) The Exim Maintainers 2017 - 2018
  * But almost everything here is fixed published constants from RFCs, so also:
  * Copyright (C) The Internet Society (2003)
  * Copyright (C) The IETF Trust (2008)
  * 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 +460,497 @@ static const char dh_ike_24_pem[] =
 "KM3GfrYYS1I9HbJGwy9jB4SQ8A741kfRSNR5VFFeIyfP75jFgmZLTA9sxBZZ\n"
 "-----END DH PARAMETERS-----\n";
 
 "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,18 +962,27 @@ 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[] = {
 /* 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 },
-    { "ike1", dh_ike_1_pem },
-    { "ike14", dh_ike_14_pem },
-    { "ike15", dh_ike_15_pem },
-    { "ike16", dh_ike_16_pem },
-    { "ike17", dh_ike_17_pem },
-    { "ike18", dh_ike_18_pem },
-    { "ike2", dh_ike_2_pem },
-    { "ike22", dh_ike_22_pem },
-    { "ike23", dh_ike_23_pem },
-    { "ike24", dh_ike_24_pem },
-    { "ike5", dh_ike_5_pem },
+    /*  label                  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 },
+    { "ike16",                 dh_ike_16_pem },
+    { "ike17",                 dh_ike_17_pem },
+    { "ike18",                 dh_ike_18_pem },
+    { "ike2",                  dh_ike_2_pem },
+    { "ike22",                 dh_ike_22_pem },
+    { "ike23",                 dh_ike_23_pem },
+    { "ike24",                 dh_ike_24_pem },
+    { "ike5",                  dh_ike_5_pem },
 };
 static const int dh_constants_count =
   sizeof(dh_constants) / sizeof(struct dh_constant);
 };
 static const int dh_constants_count =
   sizeof(dh_constants) / sizeof(struct dh_constant);
index 790f79d..b527991 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2012 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Exim gets and frees all its store through these functions. In the original
 /* 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;
   {
   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 */
 
 
   /* 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 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;
     {
     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] =
     }
 
   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
   }
 
 /* There's (now) enough room in the current block; the yield is the next
@@ -194,7 +194,7 @@ linenumber = linenumber;
 #else
 DEBUG(D_memory)
   {
 #else
 DEBUG(D_memory)
   {
-  if (running_in_test_harness)
+  if (f.running_in_test_harness)
     debug_printf("---%d Get %5d\n", store_pool, size);
   else
     debug_printf("---%d Get %6p %5d %-14s %4d\n", store_pool,
     debug_printf("---%d Get %5d\n", store_pool, size);
   else
     debug_printf("---%d Get %6p %5d %-14s %4d\n", store_pool,
@@ -202,10 +202,10 @@ DEBUG(D_memory)
   }
 #endif  /* COMPILE_UTILITY */
 
   }
 #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. */
 
 /* Update next pointer and number of bytes left in the current block. */
 
-next_yield[store_pool] = (void *)((char *)next_yield[store_pool] + size);
+next_yield[store_pool] = (void *)(CS next_yield[store_pool] + size);
 yield_length[store_pool] -= size;
 
 return store_last_get[store_pool];
 yield_length[store_pool] -= size;
 
 return store_last_get[store_pool];
@@ -273,7 +273,7 @@ int rounded_oldsize = oldsize;
 if (rounded_oldsize % alignment != 0)
   rounded_oldsize += alignment - (rounded_oldsize % alignment);
 
 if (rounded_oldsize % alignment != 0)
   rounded_oldsize += alignment - (rounded_oldsize % alignment);
 
-if ((char *)ptr + rounded_oldsize != (char *)(next_yield[store_pool]) ||
+if (CS ptr + rounded_oldsize != CS (next_yield[store_pool]) ||
     inc > yield_length[store_pool] + rounded_oldsize - oldsize)
   return FALSE;
 
     inc > yield_length[store_pool] + rounded_oldsize - oldsize)
   return FALSE;
 
@@ -286,7 +286,7 @@ linenumber = linenumber;
 #else
 DEBUG(D_memory)
   {
 #else
 DEBUG(D_memory)
   {
-  if (running_in_test_harness)
+  if (f.running_in_test_harness)
     debug_printf("---%d Ext %5d\n", store_pool, newsize);
   else
     debug_printf("---%d Ext %6p %5d %-14s %4d\n", store_pool, ptr, newsize,
     debug_printf("---%d Ext %5d\n", store_pool, newsize);
   else
     debug_printf("---%d Ext %6p %5d %-14s %4d\n", store_pool, ptr, newsize,
@@ -295,9 +295,9 @@ DEBUG(D_memory)
 #endif  /* COMPILE_UTILITY */
 
 if (newsize % alignment != 0) newsize += alignment - (newsize % alignment);
 #endif  /* COMPILE_UTILITY */
 
 if (newsize % alignment != 0) newsize += alignment - (newsize % alignment);
-next_yield[store_pool] = (char *)ptr + newsize;
+next_yield[store_pool] = CS ptr + newsize;
 yield_length[store_pool] -= newsize - rounded_oldsize;
 yield_length[store_pool] -= newsize - rounded_oldsize;
-VALGRIND_MAKE_MEM_UNDEFINED(ptr + oldsize, inc);
+(void) VALGRIND_MAKE_MEM_UNDEFINED(ptr + oldsize, inc);
 return TRUE;
 }
 
 return TRUE;
 }
 
@@ -325,9 +325,9 @@ Returns:      nothing
 void
 store_reset_3(void *ptr, const char *filename, int linenumber)
 {
 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 */
 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. */
 
 /* 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);
   }
     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. */
 
 /* 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
 #ifndef COMPILE_UTILITY
-if (running_in_test_harness) memset(ptr, 0xF0, newlength);
+if (debug_store)
+  {
+  assert_no_variables(ptr, newlength, filename, linenumber);
+  if (f.running_in_test_harness)
+    {
+    (void) VALGRIND_MAKE_MEM_DEFINED(ptr, newlength);
+    memset(ptr, 0xF0, newlength);
+    }
+  }
 #endif
 #endif
-VALGRIND_MAKE_MEM_NOACCESS(ptr, newlength);
+(void) VALGRIND_MAKE_MEM_NOACCESS(ptr, newlength);
 yield_length[store_pool] = newlength - (newlength % alignment);
 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
 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 &&
 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;
     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 (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;
 
   }
 
 bb = b->next;
 b->next = NULL;
 
-while (bb != NULL)
+while ((b = bb))
   {
   {
-  b = bb;
+#ifndef COMPILE_UTILITY
+  if (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);
   bb = bb->next;
   pool_malloc -= b->length + ALIGNED_SIZEOF_STOREBLOCK;
   store_free_3(b, filename, linenumber);
@@ -393,7 +411,7 @@ linenumber = linenumber;
 #else
 DEBUG(D_memory)
   {
 #else
 DEBUG(D_memory)
   {
-  if (running_in_test_harness)
+  if (f.running_in_test_harness)
     debug_printf("---%d Rst    ** %d\n", store_pool, pool_malloc);
   else
     debug_printf("---%d Rst %6p    ** %-14s %4d %d\n", store_pool, ptr,
     debug_printf("---%d Rst    ** %d\n", store_pool, pool_malloc);
   else
     debug_printf("---%d Rst %6p    ** %-14s %4d %d\n", store_pool, ptr,
@@ -410,14 +428,8 @@ DEBUG(D_memory)
 *             Release store                     *
 ************************************************/
 
 *             Release store                     *
 ************************************************/
 
-/* This function is specifically provided for use when reading very
-long strings, e.g. header lines. When the string gets longer than a
-complete block, it gets copied to a new block. It is helpful to free
-the old block iff the previous copy of the string is at its start,
-and therefore the only thing in it. Otherwise, for very long strings,
-dead store can pile up somewhat disastrously. This function checks that
-the pointer it is given is the first thing in a block, and if so,
-releases that block.
+/* This function checks that the pointer it is given is the first thing in a
+block, and if so, releases that block.
 
 Arguments:
   block       block of store to consider
 
 Arguments:
   block       block of store to consider
@@ -427,17 +439,17 @@ Arguments:
 Returns:      nothing
 */
 
 Returns:      nothing
 */
 
-void
-store_release_3(void *block, const char *filename, int linenumber)
+static void
+store_release_3(void * block, const char * filename, int linenumber)
 {
 {
-storeblock *b;
+storeblock * b;
 
 /* It will never be the first block, so no need to check that. */
 
 
 /* It will never be the first block, so no need to check that. */
 
-for (b = chainbase[store_pool]; b != NULL; b = b->next)
+for (b = chainbase[store_pool]; b; b = b->next)
   {
   {
-  storeblock *bb = b->next;
-  if (bb != NULL && (char *)block == (char *)bb + ALIGNED_SIZEOF_STOREBLOCK)
+  storeblock * bb = b->next;
+  if (bb && CS block == CS bb + ALIGNED_SIZEOF_STOREBLOCK)
     {
     b->next = bb->next;
     pool_malloc -= bb->length + ALIGNED_SIZEOF_STOREBLOCK;
     {
     b->next = bb->next;
     pool_malloc -= bb->length + ALIGNED_SIZEOF_STOREBLOCK;
@@ -445,21 +457,20 @@ for (b = chainbase[store_pool]; b != NULL; b = b->next)
     /* Cut out the debugging stuff for utilities, but stop picky compilers
     from giving warnings. */
 
     /* Cut out the debugging stuff for utilities, but stop picky compilers
     from giving warnings. */
 
-    #ifdef COMPILE_UTILITY
+#ifdef COMPILE_UTILITY
     filename = filename;
     linenumber = linenumber;
     filename = filename;
     linenumber = linenumber;
-    #else
+#else
     DEBUG(D_memory)
     DEBUG(D_memory)
-      {
-      if (running_in_test_harness)
+      if (f.running_in_test_harness)
         debug_printf("-Release       %d\n", pool_malloc);
       else
         debug_printf("-Release %6p %-20s %4d %d\n", (void *)bb, filename,
           linenumber, pool_malloc);
         debug_printf("-Release       %d\n", pool_malloc);
       else
         debug_printf("-Release %6p %-20s %4d %d\n", (void *)bb, filename,
           linenumber, pool_malloc);
-      }
-    if (running_in_test_harness)
+
+    if (f.running_in_test_harness)
       memset(bb, 0xF0, bb->length+ALIGNED_SIZEOF_STOREBLOCK);
       memset(bb, 0xF0, bb->length+ALIGNED_SIZEOF_STOREBLOCK);
-    #endif  /* COMPILE_UTILITY */
+#endif  /* COMPILE_UTILITY */
 
     free(bb);
     return;
 
     free(bb);
     return;
@@ -468,6 +479,43 @@ for (b = chainbase[store_pool]; b != NULL; b = b->next)
 }
 
 
 }
 
 
+/************************************************
+*             Move store                        *
+************************************************/
+
+/* Allocate a new block big enough to expend to the given size and
+copy the current data into it.  Free the old one if possible.
+
+This function is specifically provided for use when reading very
+long strings, e.g. header lines. When the string gets longer than a
+complete block, it gets copied to a new block. It is helpful to free
+the old block iff the previous copy of the string is at its start,
+and therefore the only thing in it. Otherwise, for very long strings,
+dead store can pile up somewhat disastrously. This function checks that
+the pointer it is given is the first thing in a block, and that nothing
+has been allocated since. If so, releases that block.
+
+Arguments:
+  block
+  newsize
+  len
+
+Returns:       new location of data
+*/
+
+void *
+store_newblock_3(void * block, int newsize, int len,
+  const char * filename, int linenumber)
+{
+BOOL release_ok = store_last_get[store_pool] == block;
+uschar * newtext = store_get(newsize);
+
+memcpy(newtext, block, len);
+if (release_ok) store_release_3(block, filename, linenumber);
+return (void *)newtext;
+}
+
+
 
 
 /*************************************************
 
 
 /*************************************************
@@ -492,9 +540,8 @@ store_malloc_3(int size, const char *filename, int linenumber)
 void *yield;
 
 if (size < 16) size = 16;
 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);
 
   log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to malloc %d bytes of memory: "
     "called from line %d of %s", size, linenumber, filename);
 
@@ -511,7 +558,7 @@ linenumber = linenumber;
 /* If running in test harness, spend time making sure all the new store
 is not filled with zeros so as to catch problems. */
 
 /* If running in test harness, spend time making sure all the new store
 is not filled with zeros so as to catch problems. */
 
-if (running_in_test_harness)
+if (f.running_in_test_harness)
   {
   memset(yield, 0xF0, (size_t)size);
   DEBUG(D_memory) debug_printf("--Malloc %5d %d %d\n", size, pool_malloc,
   {
   memset(yield, 0xF0, (size_t)size);
   DEBUG(D_memory) debug_printf("--Malloc %5d %d %d\n", size, pool_malloc,
@@ -551,7 +598,7 @@ linenumber = linenumber;
 #else
 DEBUG(D_memory)
   {
 #else
 DEBUG(D_memory)
   {
-  if (running_in_test_harness)
+  if (f.running_in_test_harness)
     debug_printf("----Free\n");
   else
     debug_printf("----Free %6p %-20s %4d\n", block, filename, linenumber);
     debug_printf("----Free\n");
   else
     debug_printf("----Free %6p %-20s %4d\n", block, filename, linenumber);
index 7c860f1..cb0a3ca 100644 (file)
@@ -34,19 +34,21 @@ tracing information for debugging. */
 #define store_get(size)      store_get_3(size, __FILE__, __LINE__)
 #define store_get_perm(size) store_get_perm_3(size, __FILE__, __LINE__)
 #define store_malloc(size)   store_malloc_3(size, __FILE__, __LINE__)
 #define store_get(size)      store_get_3(size, __FILE__, __LINE__)
 #define store_get_perm(size) store_get_perm_3(size, __FILE__, __LINE__)
 #define store_malloc(size)   store_malloc_3(size, __FILE__, __LINE__)
-#define store_release(addr)  store_release_3(addr, __FILE__, __LINE__)
+#define store_newblock(addr,newsize,datalen) \
+                            store_newblock_3(addr, newsize, datalen, __FILE__, __LINE__)
 #define store_reset(addr)    store_reset_3(addr, __FILE__, __LINE__)
 
 
 /* The real functions */
 
 #define store_reset(addr)    store_reset_3(addr, __FILE__, __LINE__)
 
 
 /* The real functions */
 
-extern BOOL    store_extend_3(void *, int, int, const char *, int);  /* The */
-extern void    store_free_3(void *, const char *, int);     /* value of the */
-extern void   *store_get_3(int, const char *, int);         /* 2nd arg is   */
-extern void   *store_get_perm_3(int, const char *, int);    /* __FILE__ in  */
-extern void   *store_malloc_3(int, const char *, int);      /* every call,  */
-extern void    store_release_3(void *, const char *, int);  /* so give its  */
-extern void    store_reset_3(void *, const char *, int);    /* correct type */
+/* The value of the 2nd arg is __FILE__ in every call, so give its correct type */
+extern BOOL    store_extend_3(void *, int, int, const char *, int);
+extern void    store_free_3(void *, const char *, int);
+extern void   *store_get_3(int, const char *, int);
+extern void   *store_get_perm_3(int, const char *, int);
+extern void   *store_malloc_3(int, const char *, int);
+extern void   *store_newblock_3(void *, int, int, const char *, int);
+extern void    store_reset_3(void *, const char *, int);
 
 #endif  /* STORE_H */
 
 
 #endif  /* STORE_H */
 
index 38e498a..5e48b44 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Miscellaneous string-handling functions. Some are not required for
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Miscellaneous string-handling functions. Some are not required for
@@ -10,6 +10,7 @@ utilities and tests, and are cut out by the COMPILE_UTILITY macro. */
 
 
 #include "exim.h"
 
 
 #include "exim.h"
+#include <assert.h>
 
 
 #ifndef COMPILE_UTILITY
 
 
 #ifndef COMPILE_UTILITY
@@ -42,7 +43,7 @@ int yield = 4;
 /* If an optional mask is permitted, check for it. If found, pass back the
 offset. */
 
 /* If an optional mask is permitted, check for it. If found, pass back the
 offset. */
 
-if (maskptr != NULL)
+if (maskptr)
   {
   const uschar *ss = s + Ustrlen(s);
   *maskptr = 0;
   {
   const uschar *ss = s + Ustrlen(s);
   *maskptr = 0;
@@ -79,7 +80,7 @@ if (Ustrchr(s, ':') != NULL)
     if we hit the / that introduces a mask or the % that introduces the
     interface specifier (scope id) of a link-local address. */
 
     if we hit the / that introduces a mask or the % that introduces the
     interface specifier (scope id) of a link-local address. */
 
-    if (*s == 0 || *s == '%' || *s == '/') return had_double_colon? yield : 0;
+    if (*s == 0 || *s == '%' || *s == '/') return had_double_colon ? yield : 0;
 
     /* If a component starts with an additional colon, we have hit a double
     colon. This is permitted to appear once only, and counts as at least
 
     /* If a component starts with an additional colon, we have hit a double
     colon. This is permitted to appear once only, and counts as at least
@@ -135,13 +136,16 @@ if (Ustrchr(s, ':') != NULL)
 
 for (i = 0; i < 4; i++)
   {
 
 for (i = 0; i < 4; i++)
   {
+  long n;
+  uschar * end;
+
   if (i != 0 && *s++ != '.') return 0;
   if (i != 0 && *s++ != '.') return 0;
-  if (!isdigit(*s++)) return 0;
-  if (isdigit(*s) && isdigit(*(++s))) s++;
+  n = strtol(CCS s, CSS &end, 10);
+  if (n > 255 || n < 0 || end <= s || end > s+3) return 0;
+  s = end;
   }
 
   }
 
-return (*s == 0 || (*s == '/' && maskptr != NULL && *maskptr != 0))?
-  yield : 0;
+return !*s || (*s == '/' && maskptr && *maskptr != 0) ? yield : 0;
 }
 #endif  /* COMPILE_UTILITY */
 
 }
 #endif  /* COMPILE_UTILITY */
 
@@ -165,7 +169,7 @@ Returns:      pointer to the buffer
 uschar *
 string_format_size(int size, uschar *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);
 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 +228,13 @@ Returns:   the value of the character escape
 */
 
 int
 */
 
 int
-string_interpret_escape(uschar **pp)
+string_interpret_escape(const uschar **pp)
 {
 #ifdef COMPILE_UTILITY
 const uschar *hex_digits= CUS"0123456789abcdef";
 #endif
 int ch;
 {
 #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')
   {
 ch = *(++p);
 if (isdigit(ch) && ch != '8' && ch != '9')
   {
@@ -284,12 +288,12 @@ Arguments:
 Returns:        string with non-printers encoded as printing sequences
 */
 
 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;
 {
 int nonprintcount = 0;
 int length = 0;
-uschar *t = s;
+const uschar *t = s;
 uschar *ss, *tt;
 
 while (*t != 0)
 uschar *ss, *tt;
 
 while (*t != 0)
@@ -306,7 +310,7 @@ expanded string. */
 
 ss = store_get(length + nonprintcount * 3 + 1);
 
 
 ss = store_get(length + nonprintcount * 3 + 1);
 
-/* Copy everying, escaping non printers. */
+/* Copy everything, escaping non printers. */
 
 t = s;
 tt = ss;
 
 t = s;
 tt = ss;
@@ -374,7 +378,7 @@ while (*p)
   {
   if (*p == '\\')
     {
   {
   if (*p == '\\')
     {
-    *q++ = string_interpret_escape(&p);
+    *q++ = string_interpret_escape((const uschar **)&p);
     p++;
     }
   else
     p++;
     }
   else
@@ -437,7 +441,7 @@ Returns:  copy of string in new store
 */
 
 uschar *
 */
 
 uschar *
-string_copy_malloc(uschar *s)
+string_copy_malloc(const uschar *s)
 {
 int len = Ustrlen(s) + 1;
 uschar *ss = store_malloc(len);
 {
 int len = Ustrlen(s) + 1;
 uschar *ss = store_malloc(len);
@@ -457,7 +461,7 @@ Returns:  copy of string in new store, with letters lowercased
 */
 
 uschar *
 */
 
 uschar *
-string_copylc(uschar *s)
+string_copylc(const uschar *s)
 {
 uschar *ss = store_get(Ustrlen(s) + 1);
 uschar *p = ss;
 {
 uschar *ss = store_get(Ustrlen(s) + 1);
 uschar *p = ss;
@@ -483,7 +487,7 @@ Returns:    copy of string in new store
 */
 
 uschar *
 */
 
 uschar *
-string_copyn(uschar *s, int n)
+string_copyn(const uschar *s, int n)
 {
 uschar *ss = store_get(n + 1);
 Ustrncpy(ss, s, n);
 {
 uschar *ss = store_get(n + 1);
 Ustrncpy(ss, s, n);
@@ -639,26 +643,24 @@ Returns:   the new string
 */
 
 uschar *
 */
 
 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 */
 
 if (*s != '\"')
 uschar *t, *yield;
 
 /* First find the end of the string */
 
 if (*s != '\"')
-  {
   while (*s != 0 && !isspace(*s)) s++;
   while (*s != 0 && !isspace(*s)) s++;
-  }
 else
   {
   s++;
 else
   {
   s++;
-  while (*s != 0 && *s != '\"')
+  while (*s && *s != '\"')
     {
     if (*s == '\\') (void)string_interpret_escape(&s);
     s++;
     }
     {
     if (*s == '\\') (void)string_interpret_escape(&s);
     s++;
     }
-  if (*s != 0) s++;
+  if (*s) s++;
   }
 
 /* Get enough store to copy into */
   }
 
 /* Get enough store to copy into */
@@ -698,7 +700,7 @@ return yield;
 *          Format a string and save it           *
 *************************************************/
 
 *          Format a string and save it           *
 *************************************************/
 
-/* The formatting is done by string_format, which checks the length of
+/* The formatting is done by string_vformat, which checks the length of
 everything.
 
 Arguments:
 everything.
 
 Arguments:
@@ -712,15 +714,33 @@ Returns:    pointer to fresh piece of store containing sprintf'ed string
 uschar *
 string_sprintf(const char *format, ...)
 {
 uschar *
 string_sprintf(const char *format, ...)
 {
-va_list ap;
+#ifdef COMPILE_UTILITY
 uschar buffer[STRING_SPRINTF_BUFFER_SIZE];
 uschar buffer[STRING_SPRINTF_BUFFER_SIZE];
+gstring g = { .size = STRING_SPRINTF_BUFFER_SIZE, .ptr = 0, .s = buffer };
+gstring * gp = &g;
+#else
+gstring * gp = string_get(STRING_SPRINTF_BUFFER_SIZE);
+#endif
+gstring * gp2;
+va_list ap;
+
 va_start(ap, format);
 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);
+gp2 = string_vformat(gp, FALSE, format, ap);
+gp->s[gp->ptr] = '\0';
 va_end(ap);
 va_end(ap);
-return string_copy(buffer);
+
+if (!gp2)
+  log_write(0, LOG_MAIN|LOG_PANIC_DIE,
+    "string_sprintf expansion was longer than %d; format string was (%s)\n"
+    "expansion started '%.32s'",
+    gp->size, format, gp->s);
+
+#ifdef COMPILE_UTILITY
+return string_copy(gp->s);
+#else
+gstring_reset_unused(gp);
+return gp->s;
+#endif
 }
 
 
 }
 
 
@@ -826,6 +846,17 @@ return NULL;
 
 
 
 
 
 
+#ifdef COMPILE_UTILITY
+/* Dummy version for this function; it should never be called */
+static void
+gstring_grow(gstring * g, int p, int count)
+{
+assert(FALSE);
+}
+#endif
+
+
+
 #ifndef COMPILE_UTILITY
 /*************************************************
 *       Get next string from separated list      *
 #ifndef COMPILE_UTILITY
 /*************************************************
 *       Get next string from separated list      *
@@ -868,13 +899,13 @@ Returns:     pointer to buffer, containing the next substring,
 */
 
 uschar *
 */
 
 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;
 
 BOOL sep_is_special;
 
-if (s == NULL) return NULL;
+if (!s) return NULL;
 
 /* This allows for a fixed specified separator to be an iscntrl() character,
 but at the time of implementation, this is never the case. However, it's best
 
 /* This allows for a fixed specified separator to be an iscntrl() character,
 but at the time of implementation, this is never the case. However, it's best
@@ -890,19 +921,17 @@ if (sep <= 0)
   if (*s == '<' && (ispunct(s[1]) || iscntrl(s[1])))
     {
     sep = s[1];
   if (*s == '<' && (ispunct(s[1]) || iscntrl(s[1])))
     {
     sep = s[1];
-    s += 2;
+    if (*++s) ++s;
     while (isspace(*s) && *s != sep) s++;
     }
   else
     while (isspace(*s) && *s != sep) s++;
     }
   else
-    {
-    sep = (sep == 0)? ':' : -sep;
-    }
+    sep = sep ? -sep : ':';
   *separator = sep;
   }
 
 /* An empty string has no list elements */
 
   *separator = sep;
   }
 
 /* An empty string has no list elements */
 
-if (*s == 0) return NULL;
+if (!*s) return NULL;
 
 /* Note whether whether or not the separator is an iscntrl() character. */
 
 
 /* Note whether whether or not the separator is an iscntrl() character. */
 
@@ -910,25 +939,24 @@ sep_is_special = iscntrl(sep);
 
 /* Handle the case when a buffer is provided. */
 
 
 /* Handle the case when a buffer is provided. */
 
-if (buffer != NULL)
+if (buffer)
   {
   {
-  register int p = 0;
-  for (; *s != 0; s++)
+  int p = 0;
+  for (; *s; s++)
     {
     if (*s == sep && (*(++s) != sep || sep_is_special)) break;
     if (p < buflen - 1) buffer[p++] = *s;
     }
   while (p > 0 && isspace(buffer[p-1])) p--;
     {
     if (*s == sep && (*(++s) != sep || sep_is_special)) break;
     if (p < buflen - 1) buffer[p++] = *s;
     }
   while (p > 0 && isspace(buffer[p-1])) p--;
-  buffer[p] = 0;
+  buffer[p] = '\0';
   }
 
 /* Handle the case when a buffer is not provided. */
 
 else
   {
   }
 
 /* Handle the case when a buffer is not provided. */
 
 else
   {
-  int size = 0;
-  int ptr = 0;
-  uschar *ss;
+  const uschar *ss;
+  gstring * g = NULL;
 
   /* 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()
 
   /* 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()
@@ -950,13 +978,14 @@ else
 
   for (;;)
     {
 
   for (;;)
     {
-    for (ss = s + 1; *ss != 0 && *ss != sep; ss++);
-    buffer = string_cat(buffer, &size, &ptr, s, ss-s);
+    for (ss = s + 1; *ss && *ss != sep; ss++) ;
+    g = string_catn(g, s, ss-s);
     s = ss;
     s = ss;
-    if (*s == 0 || *(++s) != sep || sep_is_special) break;
+    if (!*s || *++s != sep || sep_is_special) break;
     }
     }
-  while (ptr > 0 && isspace(buffer[ptr-1])) ptr--;
-  buffer[ptr] = 0;
+  while (g->ptr > 0 && isspace(g->s[g->ptr-1])) g->ptr--;
+  buffer = string_from_gstring(g);
+  gstring_reset_unused(g);
   }
 
 /* Update the current pointer and return the new string */
   }
 
 /* Update the current pointer and return the new string */
@@ -964,59 +993,178 @@ else
 *listptr = s;
 return buffer;
 }
 *listptr = s;
 return buffer;
 }
-#endif  /* COMPILE_UTILITY */
 
 
 
 
-#ifndef COMPILE_UTILITY
+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;
+}
+
+
 /************************************************
 /************************************************
-*      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
+/* This function is used to build a list, returning an allocated null-terminated
+growable string. The given element has any embedded separator characters
 doubled.
 
 doubled.
 
+Despite having the same growable-string interface as string_cat() the list is
+always returned null-terminated.
+
 Arguments:
 Arguments:
-  list points to the start of the list that is being built, or NULL
+  list expanding-string for the list that is being built, or NULL
        if this is a new list that has no contents yet
        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.
 */
 
 
 Returns:  pointer to the start of the list, changed if copied for expansion.
 */
 
-uschar *
-string_append_listele(uschar * list, uschar sep, const uschar * ele)
+gstring *
+string_append_listele(gstring * list, uschar sep, const uschar * ele)
 {
 {
-uschar * new = NULL;
-int sz = 0, off = 0;
 uschar * sp;
 
 uschar * sp;
 
-if (list)
+if (list && list->ptr)
+  list = string_catn(list, &sep, 1);
+
+while((sp = Ustrchr(ele, sep)))
   {
   {
-  new = string_cat(new, &sz, &off, list, Ustrlen(list));
-  new = string_cat(new, &sz, &off, &sep, 1);
+  list = string_catn(list, ele, sp-ele+1);
+  list = string_catn(list, &sep, 1);
+  ele = sp+1;
   }
   }
+list = string_cat(list, ele);
+(void) string_from_gstring(list);
+return list;
+}
 
 
-while((sp = Ustrchr(ele, sep)))
+
+gstring *
+string_append_listele_n(gstring * list, uschar sep, const uschar * ele,
+ unsigned len)
+{
+const uschar * sp;
+
+if (list && list->ptr)
+  list = string_catn(list, &sep, 1);
+
+while((sp = Ustrnchr(ele, sep, &len)))
   {
   {
-  new = string_cat(new, &sz, &off, ele, sp-ele+1);
-  new = string_cat(new, &sz, &off, &sep, 1);
+  list = string_catn(list, ele, sp-ele+1);
+  list = string_catn(list, &sep, 1);
   ele = sp+1;
   ele = sp+1;
+  len--;
   }
   }
-new = string_cat(new, &sz, &off, ele, Ustrlen(ele));
-new[off] = '\0';
-return new;
+list = string_catn(list, ele, len);
+(void) string_from_gstring(list);
+return list;
+}
+
+
+
+/* A slightly-bogus listmaker utility; the separator is a string so
+can be multiple chars - there is no checking for the element content
+containing any of the separator. */
+
+gstring *
+string_append2_listele_n(gstring * list, const uschar * sepstr,
+ const uschar * ele, unsigned len)
+{
+if (list && list->ptr)
+  list = string_cat(list, sepstr);
+
+list = string_catn(list, ele, len);
+(void) string_from_gstring(list);
+return list;
+}
+
+
+
+/************************************************/
+/* Create a growable-string with some preassigned space */
+
+gstring *
+string_get(unsigned size)
+{
+gstring * g = store_get(sizeof(gstring) + size);
+g->size = size;
+g->ptr = 0;
+g->s = US(g + 1);
+return g;
+}
+
+/* NUL-terminate the C string in the growable-string, and return it. */
+
+uschar *
+string_from_gstring(gstring * g)
+{
+if (!g) return NULL;
+g->s[g->ptr] = '\0';
+return g->s;
+}
+
+void
+gstring_reset_unused(gstring * g)
+{
+store_reset(g->s + (g->size = g->ptr + 1));
+}
+
+
+/* Add more space to a growable-string.
+
+Arguments:
+  g            the growable-string
+  p            current end of data
+  count                amount to grow by
+*/
+
+static void
+gstring_grow(gstring * g, int p, int count)
+{
+int oldsize = g->size;
+
+/* Mostly, string_cat() is used to build small strings of a few hundred
+characters at most. There are times, however, when the strings are very much
+longer (for example, a lookup that returns a vast number of alias addresses).
+To try to keep things reasonable, we use increments whose size depends on the
+existing length of the string. */
+
+unsigned inc = oldsize < 4096 ? 127 : 1023;
+g->size = ((p + count + inc) & ~inc) + 1;
+
+/* Try to extend an existing allocation. If the result of calling
+store_extend() is false, either there isn't room in the current memory block,
+or this string is not the top item on the dynamic store stack. We then have
+to get a new chunk of store and copy the old string. When building large
+strings, it is helpful to call store_release() on the old string, to release
+memory blocks that have become empty. (The block will be freed if the string
+is at its start.) However, we can do this only if we know that the old string
+was the last item on the dynamic memory stack. This is the case if it matches
+store_last_get. */
+
+if (!store_extend(g->s, oldsize, g->size))
+  g->s = store_newblock(g->s, g->size, p);
 }
 }
-#endif  /* COMPILE_UTILITY */
 
 
 
 
 
 
-#ifndef COMPILE_UTILITY
 /*************************************************
 *             Add chars to string                *
 *************************************************/
 /*************************************************
 *             Add chars to string                *
 *************************************************/
-
 /* This function is used when building up strings of unknown length. Room is
 always left for a terminating zero to be added to the string that is being
 built. This function does not require the string that is being added to be NUL
 /* This function is used when building up strings of unknown length. Room is
 always left for a terminating zero to be added to the string that is being
 built. This function does not require the string that is being added to be NUL
@@ -1026,77 +1174,52 @@ sometimes called to extract parts of other strings.
 Arguments:
   string   points to the start of the string that is being built, or NULL
              if this is a new string that has no contents yet
 Arguments:
   string   points to the start of the string that is being built, or NULL
              if this is a new string that has no contents yet
-  size     points to a variable that holds the current capacity of the memory
-             block (updated if changed)
-  ptr      points to a variable that holds the offset at which to add
-             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
   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
-
-If string is given as NULL, *size and *ptr should both be zero.
+             is a C string.
 
 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.
 
 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)
+gstring *
+string_catn(gstring * g, const uschar *s, int count)
 {
 {
-int p = *ptr;
+int p;
 
 
-if (p + count >= *size)
+if (!g)
   {
   {
-  int oldsize = *size;
-
-  /* Mostly, string_cat() is used to build small strings of a few hundred
-  characters at most. There are times, however, when the strings are very much
-  longer (for example, a lookup that returns a vast number of alias addresses).
-  To try to keep things reasonable, we use increments whose size depends on the
-  existing length of the string. */
-
-  int inc = (oldsize < 4096)? 100 : 1024;
-  while (*size <= p + count) *size += inc;
-
-  /* New string */
-
-  if (string == NULL) string = store_get(*size);
-
-  /* Try to extend an existing allocation. If the result of calling
-  store_extend() is false, either there isn't room in the current memory block,
-  or this string is not the top item on the dynamic store stack. We then have
-  to get a new chunk of store and copy the old string. When building large
-  strings, it is helpful to call store_release() on the old string, to release
-  memory blocks that have become empty. (The block will be freed if the string
-  is at its start.) However, we can do this only if we know that the old string
-  was the last item on the dynamic memory stack. This is the case if it matches
-  store_last_get. */
-
-  else if (!store_extend(string, oldsize, *size))
-    {
-    BOOL release_ok = store_last_get[store_pool] == string;
-    uschar *newstring = store_get(*size);
-    memcpy(newstring, string, p);
-    if (release_ok) store_release(string);
-    string = newstring;
-    }
+  unsigned inc = count < 4096 ? 127 : 1023;
+  unsigned size = ((count + inc) &  ~inc) + 1;
+  g = string_get(size);
   }
 
   }
 
+p = g->ptr;
+if (p + count >= g->size)
+  gstring_grow(g, p, count);
+
 /* 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. */
 
 /* 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. */
 
-memcpy(string + p, s, count);
-*ptr = p + count;
-return string;
+memcpy(g->s + p, s, count);
+g->ptr = p + count;
+return g;
+}
+gstring *
+string_cat(gstring *string, const uschar *s)
+{
+return string_catn(string, s, Ustrlen(s));
 }
 }
-#endif  /* COMPILE_UTILITY */
 
 
 
 
 
 
-#ifndef COMPILE_UTILITY
 /*************************************************
 *        Append strings to another string        *
 *************************************************/
 /*************************************************
 *        Append strings to another string        *
 *************************************************/
@@ -1105,12 +1228,8 @@ return string;
 It calls string_cat() to do the dirty work.
 
 Arguments:
 It calls string_cat() to do the dirty work.
 
 Arguments:
-  string   points to the start of the string that is being built, or NULL
+  string   expanding-string that is being built, or NULL
              if this is a new string that has no contents yet
              if this is a new string that has no contents yet
-  size     points to a variable that holds the current capacity of the memory
-             block (updated if changed)
-  ptr      points to a variable that holds the offset at which to add
-             characters, updated to the new offset
   count    the number of strings to append
   ...      "count" uschar* arguments, which must be valid zero-terminated
              C strings
   count    the number of strings to append
   ...      "count" uschar* arguments, which must be valid zero-terminated
              C strings
@@ -1119,17 +1238,16 @@ Returns:   pointer to the start of the string, changed if copied for expansion.
            The string is not zero-terminated - see string_cat() above.
 */
 
            The string is not zero-terminated - see string_cat() above.
 */
 
-uschar *
-string_append(uschar *string, int *size, int *ptr, int count, ...)
+__inline__ gstring *
+string_append(gstring *string, int count, ...)
 {
 va_list ap;
 {
 va_list ap;
-int i;
 
 va_start(ap, count);
 
 va_start(ap, count);
-for (i = 0; i < count; i++)
+while (count-- > 0)
   {
   uschar *t = va_arg(ap, uschar *);
   {
   uschar *t = va_arg(ap, uschar *);
-  string = string_cat(string, size, ptr, t, Ustrlen(t));
+  string = string_cat(string, t);
   }
 va_end(ap);
 
   }
 va_end(ap);
 
@@ -1151,10 +1269,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
 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 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.
 
 Arguments:
   buffer       a buffer in which to put the formatted string
 
 Arguments:
   buffer       a buffer in which to put the formatted string
@@ -1166,50 +1284,82 @@ Returns:       TRUE if the result fitted in the buffer
 */
 
 BOOL
 */
 
 BOOL
-string_format(uschar *buffer, int buflen, const char *format, ...)
+string_format(uschar * buffer, int buflen, const char * format, ...)
 {
 {
-BOOL yield;
+gstring g = { .size = buflen, .ptr = 0, .s = buffer }, *gp;
 va_list ap;
 va_start(ap, format);
 va_list ap;
 va_start(ap, format);
-yield = string_vformat(buffer, buflen, format, ap);
+gp = string_vformat(&g, FALSE, format, ap);
 va_end(ap);
 va_end(ap);
-return yield;
+g.s[g.ptr] = '\0';
+return !!gp;
 }
 
 
 }
 
 
-BOOL
-string_vformat(uschar *buffer, int buflen, const char *format, va_list ap)
+
+
+
+/* Bulid or append to a growing-string, sprintf-style.
+
+If the "extend" argument is true, the string passed in can be NULL,
+empty, or non-empty.
+
+If the "extend" argument is false, the string passed in may not be NULL,
+will not be grown, and is usable in the original place after return.
+The return value can be NULL to signify overflow.
+
+Returns the possibly-new (if copy for growth was needed) string,
+not nul-terminated.
+*/
+
+gstring *
+string_vformat(gstring * g, BOOL extend, const char *format, va_list ap)
 {
 {
-/* We assume numbered ascending order, C does not guarantee that */
-enum { L_NORMAL=1, L_SHORT=2, L_LONG=3, L_LONGLONG=4, L_LONGDOUBLE=5, L_SIZE=6 };
+enum ltypes { L_NORMAL=1, L_SHORT=2, L_LONG=3, L_LONGLONG=4, L_LONGDOUBLE=5, L_SIZE=6 };
+
+int width, precision, off, lim;
+const char * fp = format;      /* Deliberately not unsigned */
+
+string_datestamp_offset = -1;  /* Datestamp not inserted */
+string_datestamp_length = 0;   /* Datestamp not inserted */
+string_datestamp_type = 0;     /* Datestamp not inserted */
 
 
-BOOL yield = TRUE;
-int width, precision;
-const char *fp = format;       /* Deliberately not unsigned */
-uschar *p = buffer;
-uschar *last = buffer + buflen - 1;
+#ifdef COMPILE_UTILITY
+assert(!extend);
+assert(g);
+#else
+
+/* Ensure we have a string, to save on checking later */
+if (!g) g = string_get(16);
+#endif /*!COMPILE_UTILITY*/
 
 
-string_datestamp_offset = -1;  /* Datestamp not inserted */
-string_datestamp_length = 0;   /* Datestamp not inserted */
-string_datestamp_type = 0;     /* Datestamp not inserted */
+lim = g->size - 1;     /* leave one for a nul */
+off = g->ptr;          /* remember initial offset in gstring */
 
 /* Scan the format and handle the insertions */
 
 
 /* Scan the format and handle the insertions */
 
-while (*fp != 0)
+while (*fp)
   {
   int length = L_NORMAL;
   int *nptr;
   int slen;
   {
   int length = L_NORMAL;
   int *nptr;
   int slen;
-  const char *null = "NULL";   /* ) These variables */
-  const char *item_start, *s;  /* ) are deliberately */
-  char newformat[16];          /* ) not unsigned */
+  const char *null = "NULL";           /* ) These variables */
+  const char *item_start, *s;          /* ) are deliberately */
+  char newformat[16];                  /* ) not unsigned */
+  char * gp = CS g->s + g->ptr;                /* ) */
 
   /* Non-% characters just get copied verbatim */
 
   if (*fp != '%')
     {
 
   /* Non-% characters just get copied verbatim */
 
   if (*fp != '%')
     {
-    if (p >= last) { yield = FALSE; break; }
-    *p++ = (uschar)*fp++;
+    /* Avoid string_copyn() due to COMPILE_UTILITY */
+    if (g->ptr >= lim - 1)
+      {
+      if (!extend) return NULL;
+      gstring_grow(g, g->ptr, 1);
+      lim = g->size - 1;
+      }
+    g->s[g->ptr++] = (uschar) *fp++;
     continue;
     }
 
     continue;
     }
 
@@ -1237,19 +1387,14 @@ while (*fp != 0)
     }
 
   if (*fp == '.')
     }
 
   if (*fp == '.')
-    {
     if (*(++fp) == '*')
       {
       precision = va_arg(ap, int);
       fp++;
       }
     else
     if (*(++fp) == '*')
       {
       precision = va_arg(ap, int);
       fp++;
       }
     else
-      {
-      precision = 0;
-      while (isdigit((uschar)*fp))
-        precision = precision*10 + *fp++ - '0';
-      }
-    }
+      for (precision = 0; isdigit((uschar)*fp); fp++)
+        precision = precision*10 + *fp - '0';
 
   /* Skip over 'h', 'L', 'l', 'll' and 'z', remembering the item length */
 
 
   /* Skip over 'h', 'L', 'l', 'll' and 'z', remembering the item length */
 
@@ -1258,18 +1403,10 @@ while (*fp != 0)
   else if (*fp == 'L')
     { fp++; length = L_LONGDOUBLE; }
   else if (*fp == 'l')
   else if (*fp == 'L')
     { fp++; length = L_LONGDOUBLE; }
   else if (*fp == 'l')
-    {
     if (fp[1] == 'l')
     if (fp[1] == 'l')
-      {
-      fp += 2;
-      length = L_LONGLONG;
-      }
+      { fp += 2; length = L_LONGLONG; }
     else
     else
-      {
-      fp++;
-      length = L_LONG;
-      }
-    }
+      { fp++; length = L_LONG; }
   else if (*fp == 'z')
     { fp++; length = L_SIZE; }
 
   else if (*fp == 'z')
     { fp++; length = L_SIZE; }
 
@@ -1278,40 +1415,64 @@ while (*fp != 0)
   switch (*fp++)
     {
     case 'n':
   switch (*fp++)
     {
     case 'n':
-    nptr = va_arg(ap, int *);
-    *nptr = p - buffer;
-    break;
+      nptr = va_arg(ap, int *);
+      *nptr = g->ptr - off;
+      break;
 
     case 'd':
     case 'o':
     case 'u':
     case 'x':
     case 'X':
 
     case 'd':
     case 'o':
     case 'u':
     case 'x':
     case 'X':
-    if (p >= last - ((length > L_LONG)? 24 : 12))
-      { yield = FALSE; goto END_FORMAT; }
-    strncpy(newformat, item_start, fp - item_start);
-    newformat[fp - item_start] = 0;
-
-    /* Short int is promoted to int when passing through ..., so we must use
-    int for va_arg(). */
+      width = length > L_LONG ? 24 : 12;
+      if (g->ptr >= lim - width)
+       {
+       if (!extend) return NULL;
+       gstring_grow(g, g->ptr, width);
+       lim = g->size - 1;
+       gp = CS g->s + g->ptr;
+       }
+      strncpy(newformat, item_start, fp - item_start);
+      newformat[fp - item_start] = 0;
+
+      /* Short int is promoted to int when passing through ..., so we must use
+      int for va_arg(). */
+
+      switch(length)
+       {
+       case L_SHORT:
+       case L_NORMAL:
+         g->ptr += sprintf(gp, newformat, va_arg(ap, int)); break;
+       case L_LONG:
+         g->ptr += sprintf(gp, newformat, va_arg(ap, long int)); break;
+       case L_LONGLONG:
+         g->ptr += sprintf(gp, newformat, va_arg(ap, LONGLONG_T)); break;
+       case L_SIZE:
+         g->ptr += sprintf(gp, newformat, va_arg(ap, size_t)); break;
+       }
+      break;
 
 
-    switch(length)
+    case 'p':
       {
       {
-      case L_SHORT:
-      case L_NORMAL:   sprintf(CS p, newformat, va_arg(ap, int)); break;
-      case L_LONG:     sprintf(CS p, newformat, va_arg(ap, long int)); break;
-      case L_LONGLONG: sprintf(CS p, newformat, va_arg(ap, LONGLONG_T)); break;
-      case L_SIZE:     sprintf(CS p, newformat, va_arg(ap, size_t)); break;
+      void * ptr;
+      if (g->ptr >= lim - 24)
+       {
+       if (!extend) return NULL;
+       gstring_grow(g, g->ptr, 24);
+       lim = g->size - 1;
+       gp = CS g->s + g->ptr;
+       }
+      /* sprintf() saying "(nil)" for a null pointer seems unreliable.
+      Handle it explicitly. */
+      if ((ptr = va_arg(ap, void *)))
+       {
+       strncpy(newformat, item_start, fp - item_start);
+       newformat[fp - item_start] = 0;
+       g->ptr += sprintf(gp, newformat, ptr);
+       }
+      else
+       g->ptr += sprintf(gp, "(nil)");
       }
       }
-    while (*p) p++;
-    break;
-
-    case 'p':
-    if (p >= last - 24) { yield = FALSE; goto END_FORMAT; }
-    strncpy(newformat, item_start, fp - item_start);
-    newformat[fp - item_start] = 0;
-    sprintf(CS p, newformat, va_arg(ap, void *));
-    while (*p) p++;
     break;
 
     /* %f format is inherently insecure if the numbers that it may be
     break;
 
     /* %f format is inherently insecure if the numbers that it may be
@@ -1326,121 +1487,151 @@ while (*fp != 0)
     case 'E':
     case 'g':
     case 'G':
     case 'E':
     case 'g':
     case 'G':
-    if (precision < 0) precision = 6;
-    if (p >= last - precision - 8) { yield = FALSE; goto END_FORMAT; }
-    strncpy(newformat, item_start, fp - item_start);
-    newformat[fp-item_start] = 0;
-    if (length == L_LONGDOUBLE)
-      sprintf(CS p, newformat, va_arg(ap, long double));
-    else
-      sprintf(CS p, newformat, va_arg(ap, double));
-    while (*p) p++;
-    break;
+      if (precision < 0) precision = 6;
+      if (g->ptr >= lim - precision - 8)
+       {
+       if (!extend) return NULL;
+       gstring_grow(g, g->ptr, precision+8);
+       lim = g->size - 1;
+       gp = CS g->s + g->ptr;
+       }
+      strncpy(newformat, item_start, fp - item_start);
+      newformat[fp-item_start] = 0;
+      if (length == L_LONGDOUBLE)
+       g->ptr += sprintf(gp, newformat, va_arg(ap, long double));
+      else
+       g->ptr += sprintf(gp, newformat, va_arg(ap, double));
+      break;
 
     /* String types */
 
     case '%':
 
     /* String types */
 
     case '%':
-    if (p >= last) { yield = FALSE; goto END_FORMAT; }
-    *p++ = '%';
-    break;
+      if (g->ptr >= lim - 1)
+       {
+       if (!extend) return NULL;
+       gstring_grow(g, g->ptr, 1);
+       lim = g->size - 1;
+       }
+      g->s[g->ptr++] = (uschar) '%';
+      break;
 
     case 'c':
 
     case 'c':
-    if (p >= last) { yield = FALSE; goto END_FORMAT; }
-    *p++ = va_arg(ap, int);
-    break;
+      if (g->ptr >= lim - 1)
+       {
+       if (!extend) return NULL;
+       gstring_grow(g, g->ptr, 1);
+       lim = g->size - 1;
+       }
+      g->s[g->ptr++] = (uschar) va_arg(ap, int);
+      break;
 
     case 'D':                   /* Insert daily datestamp for log file names */
 
     case 'D':                   /* Insert daily datestamp for log file names */
-    s = CS tod_stamp(tod_log_datestamp_daily);
-    string_datestamp_offset = p - buffer;   /* Passed back via global */
-    string_datestamp_length = Ustrlen(s);   /* Passed back via global */
-    string_datestamp_type = tod_log_datestamp_daily;
-    slen = string_datestamp_length;
-    goto INSERT_STRING;
+      s = CS tod_stamp(tod_log_datestamp_daily);
+      string_datestamp_offset = g->ptr;                /* Passed back via global */
+      string_datestamp_length = Ustrlen(s);    /* Passed back via global */
+      string_datestamp_type = tod_log_datestamp_daily;
+      slen = string_datestamp_length;
+      goto INSERT_STRING;
 
     case 'M':                   /* Insert monthly datestamp for log file names */
 
     case 'M':                   /* Insert monthly datestamp for log file names */
-    s = CS tod_stamp(tod_log_datestamp_monthly);
-    string_datestamp_offset = p - buffer;   /* Passed back via global */
-    string_datestamp_length = Ustrlen(s);   /* Passed back via global */
-    string_datestamp_type = tod_log_datestamp_monthly;
-    slen = string_datestamp_length;
-    goto INSERT_STRING;
+      s = CS tod_stamp(tod_log_datestamp_monthly);
+      string_datestamp_offset = g->ptr;                /* Passed back via global */
+      string_datestamp_length = Ustrlen(s);    /* Passed back via global */
+      string_datestamp_type = tod_log_datestamp_monthly;
+      slen = string_datestamp_length;
+      goto INSERT_STRING;
 
     case 's':
     case 'S':                   /* Forces *lower* case */
 
     case 's':
     case 'S':                   /* Forces *lower* case */
-    s = va_arg(ap, char *);
+    case 'T':                   /* Forces *upper* case */
+      s = va_arg(ap, char *);
 
 
-    if (s == NULL) s = null;
-    slen = Ustrlen(s);
+      if (!s) s = null;
+      slen = Ustrlen(s);
 
     INSERT_STRING:              /* Come to from %D or %M above */
 
 
     INSERT_STRING:              /* Come to from %D or %M above */
 
-    /* If the width is specified, check that there is a precision
-    set; if not, set it to the width to prevent overruns of long
-    strings. */
-
-    if (width >= 0)
       {
       {
-      if (precision < 0) precision = width;
-      }
+      BOOL truncated = FALSE;
 
 
-    /* If a width is not specified and the precision is specified, set
-    the width to the precision, or the string length if shorted. */
+      /* If the width is specified, check that there is a precision
+      set; if not, set it to the width to prevent overruns of long
+      strings. */
 
 
-    else if (precision >= 0)
-      {
-      width = (precision < slen)? precision : slen;
-      }
+      if (width >= 0)
+       {
+       if (precision < 0) precision = width;
+       }
 
 
-    /* If neither are specified, set them both to the string length. */
+      /* If a width is not specified and the precision is specified, set
+      the width to the precision, or the string length if shorted. */
 
 
-    else width = precision = slen;
+      else if (precision >= 0)
+       width = precision < slen ? precision : slen;
 
 
-    /* Check string space, and add the string to the buffer if ok. If
-    not OK, add part of the string (debugging uses this to show as
-    much as possible). */
+      /* If neither are specified, set them both to the string length. */
 
 
-    if (p == last)
-      {
-      yield = FALSE;
-      goto END_FORMAT;
-      }
-    if (p >= last - width)
-      {
-      yield = FALSE;
-      width = precision = last - p - 1;
-      if (width < 0) width = 0;
-      if (precision < 0) precision = 0;
+      else
+       width = precision = slen;
+
+      if (!extend)
+       {
+       if (g->ptr == lim) return NULL;
+       if (g->ptr >= lim - width)
+         {
+         truncated = TRUE;
+         width = precision = lim - g->ptr - 1;
+         if (width < 0) width = 0;
+         if (precision < 0) precision = 0;
+         }
+       }
+      else if (g->ptr >= lim - width)
+       {
+       gstring_grow(g, g->ptr, width - (lim - g->ptr));
+       lim = g->size - 1;
+       gp = CS g->s + g->ptr;
+       }
+
+      g->ptr += sprintf(gp, "%*.*s", width, precision, s);
+      if (fp[-1] == 'S')
+       while (*gp) { *gp = tolower(*gp); gp++; }
+      else if (fp[-1] == 'T')
+       while (*gp) { *gp = toupper(*gp); gp++; }
+
+      if (truncated) return NULL;
+      break;
       }
       }
-    sprintf(CS p, "%*.*s", width, precision, s);
-    if (fp[-1] == 'S')
-      while (*p) { *p = tolower(*p); p++; }
-    else
-      while (*p) p++;
-    if (!yield) goto END_FORMAT;
-    break;
 
     /* Some things are never used in Exim; also catches junk. */
 
     default:
 
     /* Some things are never used in Exim; also catches junk. */
 
     default:
-    strncpy(newformat, item_start, fp - item_start);
-    newformat[fp-item_start] = 0;
-    log_write(0, LOG_MAIN|LOG_PANIC_DIE, "string_format: unsupported type "
-      "in \"%s\" in \"%s\"", newformat, format);
-    break;
+      strncpy(newformat, item_start, fp - item_start);
+      newformat[fp-item_start] = 0;
+      log_write(0, LOG_MAIN|LOG_PANIC_DIE, "string_format: unsupported type "
+       "in \"%s\" in \"%s\"", newformat, format);
+      break;
     }
   }
 
     }
   }
 
-/* Ensure string is complete; return TRUE if got to the end of the format */
+return g;
+}
 
 
-END_FORMAT:
 
 
-*p = 0;
-return yield;
+
+#ifndef COMPILE_UTILITY
+
+gstring *
+string_fmt_append(gstring * g, const char *format, ...)
+{
+va_list ap;
+va_start(ap, format);
+g = string_vformat(g, TRUE, format, ap);
+va_end(ap);
+return g;
 }
 
 
 
 }
 
 
 
-#ifndef COMPILE_UTILITY
 /*************************************************
 *       Generate an "open failed" message        *
 *************************************************/
 /*************************************************
 *       Generate an "open failed" message        *
 *************************************************/
@@ -1461,178 +1652,30 @@ uschar *
 string_open_failed(int eno, const char *format, ...)
 {
 va_list ap;
 string_open_failed(int eno, const char *format, ...)
 {
 va_list ap;
-uschar buffer[1024];
+gstring * g = string_get(1024);
 
 
-Ustrcpy(buffer, "failed to open ");
-va_start(ap, format);
+g = string_catn(g, US"failed to open ", 15);
 
 /* Use the checked formatting routine to ensure that the buffer
 does not overflow. It should not, since this is called only for internally
 specified messages. If it does, the message just gets truncated, and there
 doesn't seem much we can do about that. */
 
 
 /* Use the checked formatting routine to ensure that the buffer
 does not overflow. It should not, since this is called only for internally
 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_start(ap, format);
+(void) string_vformat(g, FALSE, format, ap);
+string_from_gstring(g);
+gstring_reset_unused(g);
+va_end(ap);
 
 
-return (eno == EACCES)?
-  string_sprintf("%s: %s (euid=%ld egid=%ld)", buffer, strerror(eno),
-    (long int)geteuid(), (long int)getegid()) :
-  string_sprintf("%s: %s", buffer, strerror(eno));
+return eno == EACCES
+  ? string_sprintf("%s: %s (euid=%ld egid=%ld)", g->s, strerror(eno),
+    (long int)geteuid(), (long int)getegid())
+  : string_sprintf("%s: %s", g->s, strerror(eno));
 }
 #endif  /* COMPILE_UTILITY */
 
 
 
 }
 #endif  /* COMPILE_UTILITY */
 
 
 
-#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
-*/
-
-uschar *
-string_log_address(address_item *addr, BOOL all_parents, BOOL success)
-{
-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;
-}
-#endif  /* COMPILE_UTILITY */
 
 
 #ifndef COMPILE_UTILITY
 
 
 #ifndef COMPILE_UTILITY
@@ -1649,6 +1692,7 @@ return Ustrcmp(* CUSS a, * CUSS b);
 
 
 
 
 
 
+
 /*************************************************
 **************************************************
 *             Stand-alone test program           *
 /*************************************************
 **************************************************
 *             Stand-alone test program           *
index 71ac5d8..20db0e5 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -25,20 +25,29 @@ struct smtp_outblock;
 struct transport_info;
 struct router_info;
 
 struct transport_info;
 struct router_info;
 
+/* Growable-string */
+typedef struct gstring {
+  int  size;           /* Current capacity of string memory */
+  int  ptr;            /* Offset at which to append further chars */
+  uschar * s;          /* The string memory */
+} gstring;
+
 /* Structure for remembering macros for the configuration file */
 
 typedef struct macro_item {
 /* 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;
+  unsigned     replen;
+  const uschar * name;
+  const uschar * replacement;
 } macro_item;
 
 /* Structure for bit tables for debugging and logging */
 
 typedef struct bit_table {
   uschar *name;
 } 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. */
 } bit_table;
 
 /* Block for holding a uid and gid, possibly unset, and an initgroups flag. */
@@ -51,6 +60,17 @@ typedef struct ugid_block {
   BOOL    initgroups;
 } ugid_block;
 
   BOOL    initgroups;
 } ugid_block;
 
+typedef enum { CHUNKING_NOT_OFFERED = -1,
+               CHUNKING_OFFERED,
+               CHUNKING_ACTIVE,
+               CHUNKING_LAST} chunking_state_t;
+
+typedef enum { TFO_NOT_USED = 0,
+               TFO_ATTEMPTED_NODATA,
+               TFO_ATTEMPTED_DATA,
+               TFO_USED_NODATA,
+               TFO_USED_DATA } tfo_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. */
 /* 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 +79,8 @@ typedef enum {DS_UNK=-1, DS_NO, DS_YES} dnssec_status_t;
 
 typedef struct host_item {
   struct host_item *next;
 
 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" */
   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 +168,7 @@ typedef struct transport_instance {
   uschar *home_dir;               /* ) Used only for local transports   */
   uschar *current_dir;            /* )                                  */
                                   /**************************************/
   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;          /* )                                  */
   BOOL    multi_domain;           /* )                                  */
   BOOL    overrides_hosts;        /* ) Used only for remote transports  */
   int     max_addresses;          /* )                                  */
@@ -170,6 +191,7 @@ typedef struct transport_instance {
   uschar *remove_headers;         /* Remove these headers */
   uschar *return_path;            /* Overriding (rewriting) return path */
   uschar *debug_string;           /* Debugging output */
   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 */
   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 +209,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 */
   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;
 
 #endif
 } transport_instance;
 
@@ -216,6 +238,38 @@ typedef struct transport_info {
 } 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)(struct transport_context *, unsigned, unsigned);
+
+/* Structure for information about a delivery-in-progress */
+
+typedef struct transport_context {
+  union {                      /* discriminated by option topt_output_string */
+    int                          fd;   /* file descriptor to write message to */
+    gstring *            msg;  /* allocated string with written message */
+  } u;
+  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. */
 
 
 /* Structure for holding information about the configured routers. */
 
@@ -285,9 +339,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 */
   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 */
   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 */
 
   int     self_code;              /* Encoded version of "self" */
   uid_t   uid;                    /* Fixed uid value */
@@ -297,6 +349,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 */
   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;
 
 
 } router_instance;
 
 
@@ -372,8 +426,7 @@ typedef struct auth_info {
     uschar *);                    /* rest of AUTH command */
   int (*clientcode)(              /* client function */
     struct auth_instance *,
     uschar *);                    /* rest of AUTH command */
   int (*clientcode)(              /* client function */
     struct auth_instance *,
-    struct smtp_inblock *,        /* socket and input buffer */
-    struct smtp_outblock *,       /* socket and output buffer */
+    void *,                      /* smtp conn, with socket, output and input buffers */
     int,                          /* command timeout */
     uschar *,                     /* buffer for reading response */
     int);                         /* sizeof buffer */
     int,                          /* command timeout */
     uschar *,                     /* buffer for reading response */
     int);                         /* sizeof buffer */
@@ -460,45 +513,14 @@ typedef struct address_item_propagated {
   #ifdef EXPERIMENTAL_SRS
   uschar *srs_sender;             /* Change return path when delivering */
   #endif
   #ifdef EXPERIMENTAL_SRS
   uschar *srs_sender;             /* Change return path when delivering */
   #endif
+  BOOL    ignore_error:1;        /* ignore delivery error */
+  #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;
 
 } address_item_propagated;
 
-/* Bits for the flags field below */
-
-#define af_allow_file          0x00000001 /* allow file in generated address */
-#define af_allow_pipe          0x00000002 /* allow pipe in generated address */
-#define af_allow_reply         0x00000004 /* allow autoreply in generated address */
-#define af_dr_retry_exists     0x00000008 /* router retry record exists */
-#define af_expand_pipe         0x00000010 /* expand pipe arguments */
-#define af_file                0x00000020 /* file delivery; always with pfr */
-#define af_gid_set             0x00000040 /* gid field is set */
-#define af_home_expanded       0x00000080 /* home_dir is already expanded */
-#define af_ignore_error        0x00000100 /* ignore delivery error */
-#define af_initgroups          0x00000200 /* use initgroups() for local transporting */
-#define af_local_host_removed  0x00000400 /* local host was backup */
-#define af_lt_retry_exists     0x00000800 /* local transport retry exists */
-#define af_pfr                 0x00001000 /* pipe or file or reply delivery */
-#define af_retry_skipped       0x00002000 /* true if retry caused some skipping */
-#define af_retry_timedout      0x00004000 /* true if retry timed out */
-#define af_uid_set             0x00008000 /* uid field is set */
-#define af_hide_child          0x00010000 /* hide child in bounce/defer msgs */
-#define af_sverify_told        0x00020000 /* sender verify failure notified */
-#define af_verify_pmfail       0x00040000 /* verify failure was postmaster callout */
-#define af_verify_nsfail       0x00080000 /* verify failure was null sender callout */
-#define af_homonym             0x00100000 /* an ancestor has same address */
-#define af_verify_routed       0x00200000 /* for cached sender verify: routed OK */
-#define af_verify_callout      0x00400000 /* for cached sender verify: callout was specified */
-#define af_include_affixes     0x00800000 /* delivered with affixes in RCPT */
-#define af_cert_verified       0x01000000 /* delivered with verified TLS cert */
-#define af_pass_message        0x02000000 /* pass message in bounces */
-#define af_bad_reply           0x04000000 /* filter could not generate autoreply */
-#ifndef DISABLE_PRDR
-# define af_prdr_used          0x08000000 /* delivery used SMTP PRDR */
-#endif
-#define af_force_command       0x10000000 /* force_command in pipe transport */
-
-/* These flags must be propagated when a child is created */
-
-#define af_propagate           (af_ignore_error)
 
 /* The main address structure. Note that fields that are to be copied to
 generated addresses should be put in the address_item_propagated structure (see
 
 /* The main address structure. Note that fields that are to be copied to
 generated addresses should be put in the address_item_propagated structure (see
@@ -528,7 +550,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 *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 */
 
   uschar *address_retry_key;      /* retry key including full address */
   uschar *domain_retry_key;       /* retry key for domain only */
@@ -544,36 +566,87 @@ typedef struct address_item {
   uschar *self_hostname;          /* after self=pass */
   uschar *shadow_message;         /* info about shadow transporting */
 
   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 */
   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 */
 
 
   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 */
   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 */
 
 
   uid_t   uid;                    /* uid for transporting */
   gid_t   gid;                    /* gid for transporting */
 
-  unsigned int flags;             /* a row of bits, defined above */
+                                 /* flags */
+  struct {
+    BOOL af_allow_file:1;              /* allow file in generated address */
+    BOOL af_allow_pipe:1;              /* allow pipe in generated address */
+    BOOL af_allow_reply:1;             /* allow autoreply in generated address */
+    BOOL af_dr_retry_exists:1;         /* router retry record exists */
+    BOOL af_expand_pipe:1;             /* expand pipe arguments */
+    BOOL af_file:1;                    /* file delivery; always with pfr */
+    BOOL af_gid_set:1;                 /* gid field is set */
+    BOOL af_home_expanded:1;           /* home_dir is already expanded */
+    BOOL af_initgroups:1;              /* use initgroups() for local transporting */
+    BOOL af_local_host_removed:1;      /* local host was backup */
+    BOOL af_lt_retry_exists:1;         /* local transport retry exists */
+    BOOL af_pfr:1;                     /* pipe or file or reply delivery */
+    BOOL af_retry_skipped:1;           /* true if retry caused some skipping */
+    BOOL af_retry_timedout:1;          /* true if retry timed out */
+    BOOL af_uid_set:1;                 /* uid field is set */
+    BOOL af_hide_child:1;              /* hide child in bounce/defer msgs */
+    BOOL af_sverify_told:1;            /* sender verify failure notified */
+    BOOL af_verify_pmfail:1;           /* verify failure was postmaster callout */
+    BOOL af_verify_nsfail:1;           /* verify failure was null sender callout */
+    BOOL af_homonym:1;                 /* an ancestor has same address */
+    BOOL af_verify_routed:1;           /* for cached sender verify: routed OK */
+    BOOL af_verify_callout:1;          /* for cached sender verify: callout was specified */
+    BOOL af_include_affixes:1;         /* delivered with affixes in RCPT */
+    BOOL af_cert_verified:1;           /* delivered with verified TLS cert */
+    BOOL af_pass_message:1;            /* pass message in bounces */
+    BOOL af_bad_reply:1;               /* filter could not generate autoreply */
+    BOOL af_tcp_fastopen_conn:1;       /* delivery connection used TCP Fast Open */
+    BOOL af_tcp_fastopen:1;            /* delivery usefully used TCP Fast Open */
+    BOOL af_tcp_fastopen_data:1;       /* delivery sent SMTP commands on TCP Fast Open */
+    BOOL af_pipelining:1;              /* delivery used (traditional) pipelining */
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+    BOOL af_early_pipe:1;              /* delivery used connect-time pipelining */
+#endif
+#ifndef DISABLE_PRDR
+    BOOL af_prdr_used:1;               /* delivery used SMTP PRDR */
+#endif
+    BOOL af_chunking_used:1;           /* delivery used SMTP CHUNKING */
+    BOOL af_force_command:1;           /* force_command in pipe transport */
+#ifdef SUPPORT_DANE
+    BOOL af_dane_verified:1;           /* TLS cert verify done with DANE */
+#endif
+#ifdef SUPPORT_I18N
+    BOOL af_utf8_downcvt:1;            /* downconvert was done for delivery */
+#endif
+  } flags;
+
   unsigned int domain_cache[(MAX_NAMED_LIST * 2)/32];
   unsigned int localpart_cache[(MAX_NAMED_LIST * 2)/32];
   int     mode;                   /* mode for local transporting to a file */
   int     more_errno;             /* additional error information */
                                   /* (may need to hold a timestamp) */
   unsigned int domain_cache[(MAX_NAMED_LIST * 2)/32];
   unsigned int localpart_cache[(MAX_NAMED_LIST * 2)/32];
   int     mode;                   /* mode for local transporting to a file */
   int     more_errno;             /* additional error information */
                                   /* (may need to hold a timestamp) */
+  unsigned int delivery_usec;    /* subsecond part of delivery time */
 
   short int basic_errno;          /* status after failure */
 
   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  */
   short int return_file;          /* fileno of return data file */
   short int special_action;       /* ( used when when deferred or failed */
                                   /* (  also  */
@@ -581,7 +654,7 @@ typedef struct address_item {
                                   /* (  also  */
                                   /* ( contains verify rc in sender verify cache */
   short int transport_return;     /* result of delivery attempt */
                                   /* (  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 */
 } address_item;
 
 /* The table of header names consists of items of this type */
@@ -597,7 +670,7 @@ typedef struct {
 
 typedef struct error_block {
   struct error_block *next;
 
 typedef struct error_block {
   struct error_block *next;
-  uschar *text1;
+  const uschar *text1;
   uschar *text2;
 } error_block;
 
   uschar *text2;
 } error_block;
 
@@ -642,6 +715,16 @@ typedef struct tree_node {
   uschar  name[1];                /* node name - variable length */
 } 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
 /* 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,26 +742,28 @@ typedef struct search_cache {
 uncompressed, but the data pointer is into the raw data. */
 
 typedef struct {
 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;
 
 } dns_record;
 
-/* Structure for holding the result of a DNS query. */
+/* Structure for holding the result of a DNS query.  A touch over
+64k big, so take care to release as soon as possible. */
 
 typedef struct {
   int     answerlen;              /* length of the answer */
 
 typedef struct {
   int     answerlen;              /* length of the answer */
-  uschar  answer[MAXPACKET];      /* the answer itself */
+  uschar  answer[NS_MAXMSG];      /* the answer itself */
 } dns_answer;
 
 /* Structure for holding the intermediate data while scanning a DNS answer
 block. */
 
 typedef struct {
 } dns_answer;
 
 /* Structure for holding the intermediate data while scanning a DNS answer
 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
 } dns_scan;
 
 /* Structure for holding a chain of IP addresses that are extracted from
@@ -706,15 +791,30 @@ md5;
 typedef struct sha1 {
   unsigned int H[5];
   unsigned int length;
 typedef struct sha1 {
   unsigned int H[5];
   unsigned int length;
-  }
-sha1;
+} sha1;
+
+/* Information for making an smtp connection */
+typedef struct {
+  transport_instance *  tblock;
+  void *               ob;     /* smtp_transport_options_block * */
+  host_item *           host;
+  int                   host_af;
+  uschar *              interface;
+} smtp_connect_args;
+
+/* A client-initiated connection. If TLS, the second element is non-NULL */
+typedef struct {
+  int  sock;
+  void * tls_ctx;
+} client_conn_ctx;
+
 
 /* Structure used to hold incoming packets of SMTP responses for a specific
 socket. The packets which may contain multiple lines (and in some cases,
 multiple responses). */
 
 typedef struct smtp_inblock {
 
 /* Structure used to hold incoming packets of SMTP responses for a specific
 socket. The packets which may contain multiple lines (and in some cases,
 multiple responses). */
 
 typedef struct smtp_inblock {
-  int     sock;                   /* the socket */
+  client_conn_ctx * cctx;        /* the connection */
   int     buffersize;             /* the size of the buffer */
   uschar *ptr;                    /* current position in the buffer */
   uschar *ptrend;                 /* end of data in the buffer */
   int     buffersize;             /* the size of the buffer */
   uschar *ptr;                    /* current position in the buffer */
   uschar *ptrend;                 /* end of data in the buffer */
@@ -726,12 +826,14 @@ specific socket. The packets which may contain multiple lines when pipelining
 is in use. */
 
 typedef struct smtp_outblock {
 is in use. */
 
 typedef struct smtp_outblock {
-  int     sock;                   /* the socket */
+  client_conn_ctx * cctx;        /* the connection */
   int     cmd_count;              /* count of buffered commands */
   int     buffersize;             /* the size of the buffer */
   BOOL    authenticating;         /* TRUE when authenticating */
   uschar *ptr;                    /* current position in the buffer */
   uschar *buffer;                 /* the buffer itself */
   int     cmd_count;              /* count of buffered commands */
   int     buffersize;             /* the size of the buffer */
   BOOL    authenticating;         /* TRUE when authenticating */
   uschar *ptr;                    /* current position in the buffer */
   uschar *buffer;                 /* the buffer itself */
+
+  smtp_connect_args * conn_args;  /* to make connection, if not yet made */
 } smtp_outblock;
 
 /* Structure to hold information about the source of redirection information */
 } smtp_outblock;
 
 /* Structure to hold information about the source of redirection information */
@@ -750,9 +852,9 @@ typedef struct redirect_block {
 /* Structure for passing arguments to check_host() */
 
 typedef struct check_host_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;
 
   BOOL   negative;
 } check_host_block;
 
@@ -768,7 +870,7 @@ typedef struct namedlist_cacheblock {
 /* Structure for holding data for an entry in a named list */
 
 typedef struct namedlist_block {
 /* 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;
   namedlist_cacheblock *cache_data;  /* cached domain_data or localpart_data */
   int number;                        /* the number of the list for caching */
 } namedlist_block;
@@ -791,4 +893,25 @@ typedef struct acl_block {
   int verb;
 } 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_identity;
+  uschar *dkim_private_key;
+  uschar *dkim_selector;
+  uschar *dkim_canon;
+  uschar *dkim_sign_headers;
+  uschar *dkim_strict;
+  uschar *dkim_hash;
+  uschar *dkim_timestamps;
+  BOOL    dot_stuffed;
+  BOOL    force_bodyhash;
+#ifdef EXPERIMENTAL_ARC
+  uschar *arc_signspec;
+#endif
+};
+
 /* End of structs.h */
 /* End of structs.h */
index 266ab89..c404dc2 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Copyright (c) Phil Pennock 2012 */
 /* 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
 
 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,
 appropriate.
 
 APIs current as of GnuTLS 2.12.18; note that the GnuTLS manual is for GnuTLS 3,
@@ -39,18 +39,56 @@ require current GnuTLS, then we'll drop support for the ancient libraries).
 #include <gnutls/x509.h>
 /* man-page is incorrect, gnutls_rnd() is not in gnutls.h: */
 #include <gnutls/crypto.h>
 #include <gnutls/x509.h>
 /* man-page is incorrect, gnutls_rnd() is not in gnutls.h: */
 #include <gnutls/crypto.h>
+
 /* needed to disable PKCS11 autoload unless requested */
 #if GNUTLS_VERSION_NUMBER >= 0x020c00
 # include <gnutls/pkcs11.h>
 /* 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
 #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
+#if GNUTLS_VERSION_NUMBER >= 0x030104
+# define GNUTLS_CERT_VFY_STATUS_PRINT
+#endif
+#if GNUTLS_VERSION_NUMBER >= 0x030109
+# define SUPPORT_CORK
+#endif
+#if GNUTLS_VERSION_NUMBER >= 0x030506 && !defined(DISABLE_OCSP)
+# define SUPPORT_SRV_OCSP_STACK
+#endif
+
+#ifdef SUPPORT_DANE
+# if GNUTLS_VERSION_NUMBER >= 0x030000
+#  define DANESSL_USAGE_DANE_TA 2
+#  define DANESSL_USAGE_DANE_EE 3
+# else
+#  error GnuTLS version too early for DANE
+# endif
+# if GNUTLS_VERSION_NUMBER < 0x999999
+#  define GNUTLS_BROKEN_DANE_VALIDATION
+# endif
+#endif
 
 #ifndef DISABLE_OCSP
 # include <gnutls/ocsp.h>
 #endif
 
 #ifndef DISABLE_OCSP
 # include <gnutls/ocsp.h>
 #endif
+#ifdef SUPPORT_DANE
+# include <gnutls/dane.h>
+#endif
 
 /* GnuTLS 2 vs 3
 
 
 /* GnuTLS 2 vs 3
 
@@ -66,11 +104,7 @@ Changes:
 /* Values for verify_requirement */
 
 enum peer_verify_requirement
 /* 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, VERIFY_DANE };
 
 /* 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
 
 /* 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
@@ -91,10 +125,11 @@ typedef struct exim_gnutls_state {
   int                  fd_in;
   int                  fd_out;
   BOOL                 peer_cert_verified;
   int                  fd_in;
   int                  fd_out;
   BOOL                 peer_cert_verified;
+  BOOL                 peer_dane_verified;
   BOOL                 trigger_sni_changes;
   BOOL                 have_set_peerdn;
   BOOL                 trigger_sni_changes;
   BOOL                 have_set_peerdn;
-  const struct host_item *host;
-  gnutls_x509_crt_t    peercert;
+  const struct host_item *host;                /* NULL if server */
+  gnutls_x509_crt_t    peercert;
   uschar               *peerdn;
   uschar               *ciphersuite;
   uschar               *received_sni;
   uschar               *peerdn;
   uschar               *ciphersuite;
   uschar               *received_sni;
@@ -111,9 +146,13 @@ typedef struct exim_gnutls_state {
   uschar *exp_tls_verify_certificates;
   uschar *exp_tls_crl;
   uschar *exp_tls_require_ciphers;
   uschar *exp_tls_verify_certificates;
   uschar *exp_tls_crl;
   uschar *exp_tls_require_ciphers;
-  uschar *exp_tls_ocsp_file;
-#ifdef EXPERIMENTAL_CERTNAMES
-  uschar *exp_tls_verify_cert_hostnames;
+  const uschar *exp_tls_verify_cert_hostnames;
+#ifndef DISABLE_EVENT
+  uschar *event_action;
+#endif
+#ifdef SUPPORT_DANE
+  char * const *       dane_data;
+  const int *          dane_data_len;
 #endif
 
   tls_support *tlsp;   /* set in tls_init() */
 #endif
 
   tls_support *tlsp;   /* set in tls_init() */
@@ -121,20 +160,50 @@ typedef struct exim_gnutls_state {
   uschar *xfer_buffer;
   int xfer_buffer_lwm;
   int xfer_buffer_hwm;
   uschar *xfer_buffer;
   int xfer_buffer_lwm;
   int xfer_buffer_hwm;
-  int xfer_eof;
-  int xfer_error;
+  BOOL xfer_eof;       /*XXX never gets set! */
+  BOOL xfer_error;
 } exim_gnutls_state_st;
 
 static const exim_gnutls_state_st exim_gnutls_state_init = {
 } exim_gnutls_state_st;
 
 static const exim_gnutls_state_st exim_gnutls_state_init = {
-  NULL, NULL, NULL, VERIFY_NONE, -1, -1, FALSE, FALSE, FALSE,
-  NULL, NULL, NULL, NULL,
-  NULL, NULL, NULL, NULL, NULL, NULL,
-  NULL, NULL, NULL, NULL, NULL, NULL, NULL,
-#ifdef EXPERIMENTAL_CERTNAMES
-                                            NULL,
+  .session =           NULL,
+  .x509_cred =         NULL,
+  .priority_cache =    NULL,
+  .verify_requirement =        VERIFY_NONE,
+  .fd_in =             -1,
+  .fd_out =            -1,
+  .peer_cert_verified =        FALSE,
+  .peer_dane_verified =        FALSE,
+  .trigger_sni_changes =FALSE,
+  .have_set_peerdn =   FALSE,
+  .host =              NULL,
+  .peercert =          NULL,
+  .peerdn =            NULL,
+  .ciphersuite =       NULL,
+  .received_sni =      NULL,
+
+  .tls_certificate =   NULL,
+  .tls_privatekey =    NULL,
+  .tls_sni =           NULL,
+  .tls_verify_certificates = NULL,
+  .tls_crl =           NULL,
+  .tls_require_ciphers =NULL,
+
+  .exp_tls_certificate = NULL,
+  .exp_tls_privatekey =        NULL,
+  .exp_tls_verify_certificates = NULL,
+  .exp_tls_crl =       NULL,
+  .exp_tls_require_ciphers = NULL,
+  .exp_tls_verify_cert_hostnames = NULL,
+#ifndef DISABLE_EVENT
+  .event_action =      NULL,
 #endif
 #endif
-  NULL,
-  NULL, 0, 0, 0, 0,
+  .tlsp =              NULL,
+
+  .xfer_buffer =       NULL,
+  .xfer_buffer_lwm =   0,
+  .xfer_buffer_hwm =   0,
+  .xfer_eof =          FALSE,
+  .xfer_error =                FALSE,
 };
 
 /* Not only do we have our own APIs which don't pass around state, assuming
 };
 
 /* Not only do we have our own APIs which don't pass around state, assuming
@@ -144,9 +213,11 @@ 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
 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;
+static exim_gnutls_state_st state_server;
 
 /* dh_params are initialised once within the lifetime of a process using TLS;
 if we used TLS in a long-lived daemon, we'd have to reconsider this.  But we
 
 /* dh_params are initialised once within the lifetime of a process using TLS;
 if we used TLS in a long-lived daemon, we'd have to reconsider this.  But we
@@ -164,6 +235,10 @@ static const char * const exim_default_gnutls_priority = "NORMAL";
 
 static BOOL exim_gnutls_base_init_done = FALSE;
 
 
 static BOOL exim_gnutls_base_init_done = FALSE;
 
+#ifndef DISABLE_OCSP
+static BOOL gnutls_buggy_ocsp = FALSE;
+#endif
+
 
 /* ------------------------------------------------------------------------ */
 /* macros */
 
 /* ------------------------------------------------------------------------ */
 /* macros */
@@ -172,26 +247,30 @@ static BOOL exim_gnutls_base_init_done = FALSE;
 
 /* Set this to control gnutls_global_set_log_level(); values 0 to 9 will setup
 the library logging; a value less than 0 disables the calls to set up logging
 
 /* Set this to control gnutls_global_set_log_level(); values 0 to 9 will setup
 the library logging; a value less than 0 disables the calls to set up logging
-callbacks. */
+callbacks.  Possibly GNuTLS also looks for an environment variable
+"GNUTLS_DEBUG_LEVEL". */
 #ifndef EXIM_GNUTLS_LIBRARY_LOG_LEVEL
 #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
 #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
 #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
 
 #endif
 
-#define exim_gnutls_err_check(Label) do { \
-  if (rc != GNUTLS_E_SUCCESS) { return tls_error((Label), gnutls_strerror(rc), host); } } while (0)
+#define exim_gnutls_err_check(rc, Label) do { \
+  if ((rc) != GNUTLS_E_SUCCESS) \
+    return tls_error((Label), US gnutls_strerror(rc), host, errstr); \
+  } while (0)
 
 
-#define expand_check_tlsvar(Varname) expand_check(state->Varname, US #Varname, &state->exp_##Varname)
+#define expand_check_tlsvar(Varname, errstr) \
+  expand_check(state->Varname, US #Varname, &state->exp_##Varname, errstr)
 
 #if GNUTLS_VERSION_NUMBER >= 0x020c00
 # define HAVE_GNUTLS_SESSION_CHANNEL_BINDING
 
 #if GNUTLS_VERSION_NUMBER >= 0x020c00
 # define HAVE_GNUTLS_SESSION_CHANNEL_BINDING
@@ -247,28 +326,18 @@ Argument:
             usually obtained from gnutls_strerror()
   host      NULL if setting up a server;
             the connected host if setting up a client
             usually obtained from gnutls_strerror()
   host      NULL if setting up a server;
             the connected host if setting up a client
+  errstr    pointer to returned error string
 
 Returns:    OK/DEFER/FAIL
 */
 
 static int
 
 Returns:    OK/DEFER/FAIL
 */
 
 static int
-tls_error(const uschar *prefix, const char *msg, const host_item *host)
+tls_error(const uschar *prefix, const uschar *msg, const host_item *host,
+  uschar ** errstr)
 {
 {
-if (host)
-  {
-  log_write(0, LOG_MAIN, "TLS error on connection to %s [%s] (%s)%s%s",
-      host->name, host->address, prefix, msg ? ": " : "", msg ? msg : "");
-  return FAIL;
-  }
-else
-  {
-  uschar *conn_info = smtp_get_connection_info();
-  if (Ustrncmp(conn_info, US"SMTP ", 5) == 0)
-    conn_info += 5;
-  log_write(0, LOG_MAIN, "TLS error on %s (%s)%s%s",
-      conn_info, prefix, msg ? ": " : "", msg ? msg : "");
-  return DEFER;
-  }
+if (errstr)
+  *errstr = string_sprintf("(%s)%s%s", prefix, msg ? ": " : "", msg ? msg : US"");
+return host ? FAIL : DEFER;
 }
 
 
 }
 
 
@@ -292,15 +361,27 @@ Returns:   nothing
 static void
 record_io_error(exim_gnutls_state_st *state, int rc, uschar *when, uschar *text)
 {
 static void
 record_io_error(exim_gnutls_state_st *state, int rc, uschar *when, uschar *text)
 {
-const char *msg;
+const uschar * msg;
+uschar * errstr;
 
 if (rc == GNUTLS_E_FATAL_ALERT_RECEIVED)
 
 if (rc == GNUTLS_E_FATAL_ALERT_RECEIVED)
-  msg = CS string_sprintf("%s: %s", US gnutls_strerror(rc),
+  msg = string_sprintf("%s: %s", US gnutls_strerror(rc),
     US gnutls_alert_get_name(gnutls_alert_get(state->session)));
 else
     US gnutls_alert_get_name(gnutls_alert_get(state->session)));
 else
-  msg = gnutls_strerror(rc);
+  msg = US gnutls_strerror(rc);
 
 
-tls_error(when, msg, state->host);
+(void) tls_error(when, msg, state->host, &errstr);
+
+if (state->host)
+  log_write(0, LOG_MAIN, "H=%s [%s] TLS error on connection %s",
+    state->host->name, state->host->address, errstr);
+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", conn_info, errstr);
+  }
 }
 
 
 }
 
 
@@ -322,7 +403,7 @@ tls_error(when, msg, state->host);
     } while (0)
 
 static int
     } 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;
 
 {
 int rc;
 
@@ -371,7 +452,8 @@ gnutls_datum_t channel;
 #endif
 tls_support * tlsp = state->tlsp;
 
 #endif
 tls_support * tlsp = state->tlsp;
 
-tlsp->active = state->fd_out;
+tlsp->active.sock = state->fd_out;
+tlsp->active.tls_ctx = state;
 
 cipher = gnutls_cipher_get(state->session);
 /* returns size in "bytes" */
 
 cipher = gnutls_cipher_get(state->session);
 /* returns size in "bytes" */
@@ -382,6 +464,9 @@ tlsp->cipher = state->ciphersuite;
 DEBUG(D_tls) debug_printf("cipher: %s\n", state->ciphersuite);
 
 tlsp->certificate_verified = state->peer_cert_verified;
 DEBUG(D_tls) debug_printf("cipher: %s\n", state->ciphersuite);
 
 tlsp->certificate_verified = state->peer_cert_verified;
+#ifdef SUPPORT_DANE
+tlsp->dane_verified = state->peer_dane_verified;
+#endif
 
 /* note that tls_channelbinding_b64 is not saved to the spool file, since it's
 only available for use for authenticators while this TLS session is running. */
 
 /* note that tls_channelbinding_b64 is not saved to the spool file, since it's
 only available for use for authenticators while this TLS session is running. */
@@ -396,7 +481,7 @@ if (rc) {
 } else {
   old_pool = store_pool;
   store_pool = POOL_PERM;
 } 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");
 }
   store_pool = old_pool;
   DEBUG(D_tls) debug_printf("Have channel bindings cached for possible auth usage.\n");
 }
@@ -408,7 +493,7 @@ tlsp->sni =    state->received_sni;
 
 /* record our certificate */
   {
 
 /* 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;
   gnutls_x509_crt_t crt;
 
   tlsp->ourcert = cert && import_cert(cert, &crt)==0 ? crt : NULL;
@@ -436,11 +521,11 @@ Returns:     OK/DEFER/FAIL
 */
 
 static int
 */
 
 static int
-init_server_dh(void)
+init_server_dh(uschar ** errstr)
 {
 int fd, rc;
 unsigned int dh_bits;
 {
 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;
 uschar filename_buf[PATH_MAX];
 uschar *filename = NULL;
 size_t sz;
@@ -452,12 +537,12 @@ host_item *host = NULL; /* dummy for macros */
 DEBUG(D_tls) debug_printf("Initialising GnuTLS server params.\n");
 
 rc = gnutls_dh_params_init(&dh_server_params);
 DEBUG(D_tls) debug_printf("Initialising GnuTLS server params.\n");
 
 rc = gnutls_dh_params_init(&dh_server_params);
-exim_gnutls_err_check(US"gnutls_dh_params_init");
+exim_gnutls_err_check(rc, US"gnutls_dh_params_init");
 
 m.data = NULL;
 m.size = 0;
 
 
 m.data = NULL;
 m.size = 0;
 
-if (!expand_check(tls_dhparam, US"tls_dhparam", &exp_tls_dhparam))
+if (!expand_check(tls_dhparam, US"tls_dhparam", &exp_tls_dhparam, errstr))
   return DEFER;
 
 if (!exp_tls_dhparam)
   return DEFER;
 
 if (!exp_tls_dhparam)
@@ -475,9 +560,8 @@ else if (Ustrcmp(exp_tls_dhparam, "none") == 0)
   }
 else if (exp_tls_dhparam[0] != '/')
   {
   }
 else if (exp_tls_dhparam[0] != '/')
   {
-  m.data = US std_dh_prime_named(exp_tls_dhparam);
-  if (m.data == NULL)
-    return tls_error(US"No standard prime named", CS exp_tls_dhparam, NULL);
+  if (!(m.data = US std_dh_prime_named(exp_tls_dhparam)))
+    return tls_error(US"No standard prime named", exp_tls_dhparam, NULL, errstr);
   m.size = Ustrlen(m.data);
   }
 else
   m.size = Ustrlen(m.data);
   }
 else
@@ -489,7 +573,7 @@ else
 if (m.data)
   {
   rc = gnutls_dh_params_import_pkcs3(dh_server_params, &m, GNUTLS_X509_FMT_PEM);
 if (m.data)
   {
   rc = gnutls_dh_params_import_pkcs3(dh_server_params, &m, GNUTLS_X509_FMT_PEM);
-  exim_gnutls_err_check(US"gnutls_dh_params_import_pkcs3");
+  exim_gnutls_err_check(rc, US"gnutls_dh_params_import_pkcs3");
   DEBUG(D_tls) debug_printf("Loaded fixed standard D-H parameters\n");
   return OK;
   }
   DEBUG(D_tls) debug_printf("Loaded fixed standard D-H parameters\n");
   return OK;
   }
@@ -499,7 +583,7 @@ if (m.data)
 different filename and ensure we have sufficient bits. */
 dh_bits = gnutls_sec_param_to_pk_bits(GNUTLS_PK_DH, GNUTLS_SEC_PARAM_NORMAL);
 if (!dh_bits)
 different filename and ensure we have sufficient bits. */
 dh_bits = gnutls_sec_param_to_pk_bits(GNUTLS_PK_DH, GNUTLS_SEC_PARAM_NORMAL);
 if (!dh_bits)
-  return tls_error(US"gnutls_sec_param_to_pk_bits() failed", NULL, NULL);
+  return tls_error(US"gnutls_sec_param_to_pk_bits() failed", NULL, NULL, errstr);
 DEBUG(D_tls)
   debug_printf("GnuTLS tells us that for D-H PK, NORMAL is %d bits.\n",
       dh_bits);
 DEBUG(D_tls)
   debug_printf("GnuTLS tells us that for D-H PK, NORMAL is %d bits.\n",
       dh_bits);
@@ -523,15 +607,14 @@ if (use_file_in_spool)
   {
   if (!string_format(filename_buf, sizeof(filename_buf),
         "%s/gnutls-params-%d", spool_directory, dh_bits))
   {
   if (!string_format(filename_buf, sizeof(filename_buf),
         "%s/gnutls-params-%d", spool_directory, dh_bits))
-    return tls_error(US"overlong filename", NULL, NULL);
+    return tls_error(US"overlong filename", NULL, NULL, errstr);
   filename = filename_buf;
   }
 
 /* Open the cache file for reading and if successful, read it and set up the
 parameters. */
 
   filename = filename_buf;
   }
 
 /* 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;
   {
   struct stat statbuf;
   FILE *fp;
@@ -541,42 +624,39 @@ if (fd >= 0)
     {
     saved_errno = errno;
     (void)close(fd);
     {
     saved_errno = errno;
     (void)close(fd);
-    return tls_error(US"TLS cache stat failed", strerror(saved_errno), NULL);
+    return tls_error(US"TLS cache stat failed", US strerror(saved_errno), NULL, errstr);
     }
   if (!S_ISREG(statbuf.st_mode))
     {
     (void)close(fd);
     }
   if (!S_ISREG(statbuf.st_mode))
     {
     (void)close(fd);
-    return tls_error(US"TLS cache not a file", NULL, NULL);
+    return tls_error(US"TLS cache not a file", NULL, NULL, errstr);
     }
     }
-  fp = fdopen(fd, "rb");
-  if (!fp)
+  if (!(fp = fdopen(fd, "rb")))
     {
     saved_errno = errno;
     (void)close(fd);
     return tls_error(US"fdopen(TLS cache stat fd) failed",
     {
     saved_errno = errno;
     (void)close(fd);
     return tls_error(US"fdopen(TLS cache stat fd) failed",
-        strerror(saved_errno), NULL);
+        US strerror(saved_errno), NULL, errstr);
     }
 
   m.size = statbuf.st_size;
     }
 
   m.size = statbuf.st_size;
-  m.data = malloc(m.size);
-  if (m.data == NULL)
+  if (!(m.data = malloc(m.size)))
     {
     fclose(fp);
     {
     fclose(fp);
-    return tls_error(US"malloc failed", strerror(errno), NULL);
+    return tls_error(US"malloc failed", US strerror(errno), NULL, errstr);
     }
     }
-  sz = fread(m.data, m.size, 1, fp);
-  if (!sz)
+  if (!(sz = fread(m.data, m.size, 1, fp)))
     {
     saved_errno = errno;
     fclose(fp);
     free(m.data);
     {
     saved_errno = errno;
     fclose(fp);
     free(m.data);
-    return tls_error(US"fread failed", strerror(saved_errno), NULL);
+    return tls_error(US"fread failed", US strerror(saved_errno), NULL, errstr);
     }
   fclose(fp);
 
   rc = gnutls_dh_params_import_pkcs3(dh_server_params, &m, GNUTLS_X509_FMT_PEM);
   free(m.data);
     }
   fclose(fp);
 
   rc = gnutls_dh_params_import_pkcs3(dh_server_params, &m, GNUTLS_X509_FMT_PEM);
   free(m.data);
-  exim_gnutls_err_check(US"gnutls_dh_params_import_pkcs3");
+  exim_gnutls_err_check(rc, US"gnutls_dh_params_import_pkcs3");
   DEBUG(D_tls) debug_printf("read D-H parameters from file \"%s\"\n", filename);
   }
 
   DEBUG(D_tls) debug_printf("read D-H parameters from file \"%s\"\n", filename);
   }
 
@@ -591,7 +671,7 @@ else if (errno == ENOENT)
   }
 else
   return tls_error(string_open_failed(errno, "\"%s\" for reading", filename),
   }
 else
   return tls_error(string_open_failed(errno, "\"%s\" for reading", filename),
-      NULL, NULL);
+      NULL, NULL, errstr);
 
 /* If ret < 0, either the cache file does not exist, or the data it contains
 is not useful. One particular case of this is when upgrading from an older
 
 /* If ret < 0, either the cache file does not exist, or the data it contains
 is not useful. One particular case of this is when upgrading from an older
@@ -606,12 +686,11 @@ if (rc < 0)
 
   if ((PATH_MAX - Ustrlen(filename)) < 10)
     return tls_error(US"Filename too long to generate replacement",
 
   if ((PATH_MAX - Ustrlen(filename)) < 10)
     return tls_error(US"Filename too long to generate replacement",
-        CS filename, NULL);
+        filename, NULL, errstr);
 
 
-  temp_fn = string_copy(US "%s.XXXXXXX");
-  fd = mkstemp(CS temp_fn); /* modifies temp_fn */
-  if (fd < 0)
-    return tls_error(US"Unable to open temp file", strerror(errno), NULL);
+  temp_fn = string_copy(US"%s.XXXXXXX");
+  if ((fd = mkstemp(CS temp_fn)) < 0)  /* modifies temp_fn */
+    return tls_error(US"Unable to open temp file", US strerror(errno), NULL, errstr);
   (void)fchown(fd, exim_uid, exim_gid);   /* Probably not necessary */
 
   /* GnuTLS overshoots!
   (void)fchown(fd, exim_uid, exim_gid);   /* Probably not necessary */
 
   /* GnuTLS overshoots!
@@ -634,7 +713,7 @@ if (rc < 0)
     debug_printf("requesting generation of %d bit Diffie-Hellman prime ...\n",
         dh_bits_gen);
   rc = gnutls_dh_params_generate2(dh_server_params, dh_bits_gen);
     debug_printf("requesting generation of %d bit Diffie-Hellman prime ...\n",
         dh_bits_gen);
   rc = gnutls_dh_params_generate2(dh_server_params, dh_bits_gen);
-  exim_gnutls_err_check(US"gnutls_dh_params_generate2");
+  exim_gnutls_err_check(rc, US"gnutls_dh_params_generate2");
 
   /* gnutls_dh_params_export_pkcs3() will tell us the exact size, every time,
   and I confirmed that a NULL call to get the size first is how the GnuTLS
 
   /* gnutls_dh_params_export_pkcs3() will tell us the exact size, every time,
   and I confirmed that a NULL call to get the size first is how the GnuTLS
@@ -645,42 +724,38 @@ if (rc < 0)
   rc = gnutls_dh_params_export_pkcs3(dh_server_params, GNUTLS_X509_FMT_PEM,
       m.data, &sz);
   if (rc != GNUTLS_E_SHORT_MEMORY_BUFFER)
   rc = gnutls_dh_params_export_pkcs3(dh_server_params, GNUTLS_X509_FMT_PEM,
       m.data, &sz);
   if (rc != GNUTLS_E_SHORT_MEMORY_BUFFER)
-    exim_gnutls_err_check(US"gnutls_dh_params_export_pkcs3(NULL) sizing");
+    exim_gnutls_err_check(rc, US"gnutls_dh_params_export_pkcs3(NULL) sizing");
   m.size = sz;
   m.size = sz;
-  m.data = malloc(m.size);
-  if (m.data == NULL)
-    return tls_error(US"memory allocation failed", strerror(errno), NULL);
+  if (!(m.data = malloc(m.size)))
+    return tls_error(US"memory allocation failed", US strerror(errno), NULL, errstr);
+
   /* 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);
   if (rc != GNUTLS_E_SUCCESS)
     {
     free(m.data);
   /* 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);
   if (rc != GNUTLS_E_SUCCESS)
     {
     free(m.data);
-    exim_gnutls_err_check(US"gnutls_dh_params_export_pkcs3() real");
+    exim_gnutls_err_check(rc, US"gnutls_dh_params_export_pkcs3() real");
     }
   m.size = sz; /* shrink by 1, probably */
 
     }
   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",
     {
     free(m.data);
     return tls_error(US"TLS cache write D-H params failed",
-        strerror(errno), NULL);
+        US strerror(errno), NULL, errstr);
     }
   free(m.data);
     }
   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",
     return tls_error(US"TLS cache write D-H params final newline failed",
-        strerror(errno), NULL);
+        US strerror(errno), NULL, errstr);
 
 
-  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", US strerror(errno), NULL, errstr);
 
   if (Urename(temp_fn, filename) < 0)
     return tls_error(string_sprintf("failed to rename \"%s\" as \"%s\"",
 
   if (Urename(temp_fn, filename) < 0)
     return tls_error(string_sprintf("failed to rename \"%s\" as \"%s\"",
-          temp_fn, filename), strerror(errno), NULL);
+          temp_fn, filename), US strerror(errno), NULL, errstr);
 
   DEBUG(D_tls) debug_printf("wrote D-H parameters to file \"%s\"\n", filename);
   }
 
   DEBUG(D_tls) debug_printf("wrote D-H parameters to file \"%s\"\n", filename);
   }
@@ -692,6 +767,98 @@ return OK;
 
 
 
 
 
 
+/* Create and install a selfsigned certificate, for use in server mode */
+
+static int
+tls_install_selfsign(exim_gnutls_state_st * state, uschar ** errstr)
+{
+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
+# ifndef GNUTLS_SEC_PARAM_MEDIUM
+#  define GNUTLS_SEC_PARAM_MEDIUM GNUTLS_SEC_PARAM_HIGH
+# endif
+           gnutls_sec_param_to_pk_bits(GNUTLS_PK_RSA, GNUTLS_SEC_PARAM_MEDIUM),
+#else
+           2048,
+#endif
+           0)))
+  goto err;
+
+where = US"configuring cert";
+now = 1;
+if (  (rc = gnutls_x509_crt_set_version(cert, 3))
+   || (rc = gnutls_x509_crt_set_serial(cert, &now, sizeof(now)))
+   || (rc = gnutls_x509_crt_set_activation_time(cert, now = time(NULL)))
+   || (rc = gnutls_x509_crt_set_expiration_time(cert, 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, US gnutls_strerror(rc), NULL, errstr);
+  goto out;
+}
+
+
+
+
+/* Add certificate and key, from files.
+
+Return:
+  Zero or negative: good.  Negate value for certificate index if < 0.
+  Greater than zero: FAIL or DEFER code.
+*/
+
+static int
+tls_add_certfile(exim_gnutls_state_st * state, const host_item * host,
+  uschar * certfile, uschar * keyfile, uschar ** errstr)
+{
+int rc = gnutls_certificate_set_x509_key_file(state->x509_cred,
+    CS certfile, CS keyfile, GNUTLS_X509_FMT_PEM);
+if (rc < 0)
+  return tls_error(
+    string_sprintf("cert/key setup: cert=%s key=%s", certfile, keyfile),
+    US gnutls_strerror(rc), host, errstr);
+return -rc;
+}
+
+
 /*************************************************
 *       Variables re-expanded post-SNI           *
 *************************************************/
 /*************************************************
 *       Variables re-expanded post-SNI           *
 *************************************************/
@@ -706,12 +873,13 @@ which we are responsible for setting on the first pass through.
 
 Arguments:
   state           exim_gnutls_state_st *
 
 Arguments:
   state           exim_gnutls_state_st *
+  errstr         error string pointer
 
 Returns:          OK/DEFER/FAIL
 */
 
 static int
 
 Returns:          OK/DEFER/FAIL
 */
 
 static int
-tls_expand_session_files(exim_gnutls_state_st *state)
+tls_expand_session_files(exim_gnutls_state_st * state, uschar ** errstr)
 {
 struct stat statbuf;
 int rc;
 {
 struct stat statbuf;
 int rc;
@@ -724,14 +892,13 @@ int cert_count;
 
 /* We check for tls_sni *before* expansion. */
 if (!host)     /* server */
 
 /* We check for tls_sni *before* expansion. */
 if (!host)     /* server */
-  {
   if (!state->received_sni)
     {
   if (!state->received_sni)
     {
-    if (state->tls_certificate &&
-        (Ustrstr(state->tls_certificate, US"tls_sni") ||
-         Ustrstr(state->tls_certificate, US"tls_in_sni") ||
-         Ustrstr(state->tls_certificate, US"tls_out_sni")
-       ))
+    if (  state->tls_certificate
+       && (  Ustrstr(state->tls_certificate, US"tls_sni")
+         || Ustrstr(state->tls_certificate, US"tls_in_sni")
+         || Ustrstr(state->tls_certificate, US"tls_out_sni")
+       )  )
       {
       DEBUG(D_tls) debug_printf("We will re-expand TLS session files if we receive SNI.\n");
       state->trigger_sni_changes = TRUE;
       {
       DEBUG(D_tls) debug_printf("We will re-expand TLS session files if we receive SNI.\n");
       state->trigger_sni_changes = TRUE;
@@ -745,10 +912,13 @@ if (!host)        /* server */
     saved_tls_verify_certificates = state->exp_tls_verify_certificates;
     saved_tls_crl = state->exp_tls_crl;
     }
     saved_tls_verify_certificates = state->exp_tls_verify_certificates;
     saved_tls_crl = state->exp_tls_crl;
     }
-  }
 
 rc = gnutls_certificate_allocate_credentials(&state->x509_cred);
 
 rc = gnutls_certificate_allocate_credentials(&state->x509_cred);
-exim_gnutls_err_check(US"gnutls_certificate_allocate_credentials");
+exim_gnutls_err_check(rc, US"gnutls_certificate_allocate_credentials");
+
+#ifdef SUPPORT_SRV_OCSP_STACK
+gnutls_certificate_set_flags(state->x509_cred, GNUTLS_CERTIFICATE_API_V2);
+#endif
 
 /* remember: expand_check_tlsvar() is expand_check() but fiddling with
 state members, assuming consistent naming; and expand_check() returns
 
 /* remember: expand_check_tlsvar() is expand_check() but fiddling with
 state members, assuming consistent naming; and expand_check() returns
@@ -757,21 +927,20 @@ false if expansion failed, unless expansion was forced to fail. */
 /* check if we at least have a certificate, before doing expensive
 D-H generation. */
 
 /* check if we at least have a certificate, before doing expensive
 D-H generation. */
 
-if (!expand_check_tlsvar(tls_certificate))
+if (!expand_check_tlsvar(tls_certificate, errstr))
   return DEFER;
 
 /* certificate is mandatory in server, optional in client */
 
   return DEFER;
 
 /* 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)
   if (!host)
-    return tls_error(US"no TLS server certificate is specified", NULL, NULL);
+    return tls_install_selfsign(state, errstr);
   else
     DEBUG(D_tls) debug_printf("TLS: no client certificate specified; okay\n");
   else
     DEBUG(D_tls) debug_printf("TLS: no client certificate specified; okay\n");
-  }
 
 
-if (state->tls_privatekey && !expand_check_tlsvar(tls_privatekey))
+if (state->tls_privatekey && !expand_check_tlsvar(tls_privatekey, errstr))
   return DEFER;
 
 /* tls_privatekey is optional, defaulting to same file as certificate */
   return DEFER;
 
 /* tls_privatekey is optional, defaulting to same file as certificate */
@@ -789,9 +958,9 @@ if (state->exp_tls_certificate && *state->exp_tls_certificate)
       state->exp_tls_certificate, state->exp_tls_privatekey);
 
   if (state->received_sni)
       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");
       }
       {
       DEBUG(D_tls) debug_printf("TLS SNI: cert and key unchanged\n");
       }
@@ -799,39 +968,82 @@ if (state->exp_tls_certificate && *state->exp_tls_certificate)
       {
       DEBUG(D_tls) debug_printf("TLS SNI: have a changed cert/key pair.\n");
       }
       {
       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,
-      GNUTLS_X509_FMT_PEM);
-  exim_gnutls_err_check(
-      string_sprintf("cert/key setup: cert=%s key=%s",
-        state->exp_tls_certificate, state->exp_tls_privatekey));
-  DEBUG(D_tls) debug_printf("TLS: cert/key registered\n");
-  } /* tls_certificate */
-
 
 
-/* Set the OCSP stapling server info */
+  if (!host)   /* server */
+    {
+    const uschar * clist = state->exp_tls_certificate;
+    const uschar * klist = state->exp_tls_privatekey;
+    const uschar * olist;
+    int csep = 0, ksep = 0, osep = 0, cnt = 0;
+    uschar * cfile, * kfile, * ofile;
 
 #ifndef DISABLE_OCSP
 
 #ifndef DISABLE_OCSP
-if (  !host    /* server */
-   && tls_ocsp_file
-   )
-  {
-  if (!expand_check(tls_ocsp_file, US"tls_ocsp_file",
-       &state->exp_tls_ocsp_file))
-    return DEFER;
+    if (!expand_check(tls_ocsp_file, US"tls_ocsp_file", &ofile, errstr))
+      return DEFER;
+    olist = ofile;
+#endif
 
 
-  /* 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.  */
+    while (cfile = string_nextinlist(&clist, &csep, NULL, 0))
 
 
-  gnutls_certificate_set_ocsp_status_request_function(state->x509_cred,
-    server_ocsp_stapling_cb, state->exp_tls_ocsp_file);
+      if (!(kfile = string_nextinlist(&klist, &ksep, NULL, 0)))
+       return tls_error(US"cert/key setup: out of keys", NULL, host, errstr);
+      else if (0 < (rc = tls_add_certfile(state, host, cfile, kfile, errstr)))
+       return rc;
+      else
+       {
+       int gnutls_cert_index = -rc;
+       DEBUG(D_tls) debug_printf("TLS: cert/key %s registered\n", cfile);
 
 
-  DEBUG(D_tls) debug_printf("Set OCSP response file %s\n", &state->exp_tls_ocsp_file);
-  }
+       /* Set the OCSP stapling server info */
+
+#ifndef DISABLE_OCSP
+       if (tls_ocsp_file)
+         if (gnutls_buggy_ocsp)
+           {
+           DEBUG(D_tls)
+             debug_printf("GnuTLS library is buggy for OCSP; avoiding\n");
+           }
+         else if ((ofile = string_nextinlist(&olist, &osep, NULL, 0)))
+           {
+           /* 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.  */
+
+# ifdef SUPPORT_SRV_OCSP_STACK
+           rc = gnutls_certificate_set_ocsp_status_request_function2(
+             state->x509_cred, gnutls_cert_index,
+             server_ocsp_stapling_cb, ofile);
+
+           exim_gnutls_err_check(rc,
+             US"gnutls_certificate_set_ocsp_status_request_function2");
+# else
+           if (cnt++ > 0)
+             {
+             DEBUG(D_tls)
+               debug_printf("oops; multiple OCSP files not supported\n");
+             break;
+             }
+             gnutls_certificate_set_ocsp_status_request_function(
+               state->x509_cred, server_ocsp_stapling_cb, ofile);
+# endif
+
+           DEBUG(D_tls) debug_printf("OCSP response file = %s\n", ofile);
+           }
+         else
+           DEBUG(D_tls) debug_printf("ran out of OCSP response files in list\n");
 #endif
 #endif
+       }
+    }
+  else
+    {
+    if (0 < (rc = tls_add_certfile(state, host,
+               state->exp_tls_certificate, state->exp_tls_privatekey, errstr)))
+      return rc;
+    DEBUG(D_tls) debug_printf("TLS: cert/key registered\n");
+    }
+
+  } /* tls_certificate */
 
 
 /* Set the trusted CAs file if one is provided, and then add the CRL if one is
 
 
 /* Set the trusted CAs file if one is provided, and then add the CRL if one is
@@ -842,10 +1054,14 @@ behaviour. */
 
 if (state->tls_verify_certificates && *state->tls_verify_certificates)
   {
 
 if (state->tls_verify_certificates && *state->tls_verify_certificates)
   {
-  if (!expand_check_tlsvar(tls_verify_certificates))
+  if (!expand_check_tlsvar(tls_verify_certificates, errstr))
     return DEFER;
     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 (state->tls_crl && *state->tls_crl)
-    if (!expand_check_tlsvar(tls_crl))
+    if (!expand_check_tlsvar(tls_crl, errstr))
       return DEFER;
 
   if (!(state->exp_tls_verify_certificates &&
       return DEFER;
 
   if (!(state->exp_tls_verify_certificates &&
@@ -864,46 +1080,65 @@ else
   return OK;
   }
 
   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;
 if (cert_count < 0)
   {
   rc = cert_count;
-  exim_gnutls_err_check(US"gnutls_certificate_set_x509_trust_file");
+  exim_gnutls_err_check(rc, US"setting certificate trust");
   }
 DEBUG(D_tls) debug_printf("Added %d certificate authorities.\n", cert_count);
 
   }
 DEBUG(D_tls) debug_printf("Added %d certificate authorities.\n", cert_count);
 
@@ -916,7 +1151,7 @@ if (state->tls_crl && *state->tls_crl &&
   if (cert_count < 0)
     {
     rc = cert_count;
   if (cert_count < 0)
     {
     rc = cert_count;
-    exim_gnutls_err_check(US"gnutls_certificate_set_x509_crl_file");
+    exim_gnutls_err_check(rc, US"gnutls_certificate_set_x509_crl_file");
     }
   DEBUG(D_tls) debug_printf("Processed %d CRLs.\n", cert_count);
   }
     }
   DEBUG(D_tls) debug_printf("Processed %d CRLs.\n", cert_count);
   }
@@ -939,12 +1174,13 @@ out to this.
 
 Arguments:
   state           exim_gnutls_state_st *
 
 Arguments:
   state           exim_gnutls_state_st *
+  errstr         error string pointer
 
 Returns:          OK/DEFER/FAIL
 */
 
 static int
 
 Returns:          OK/DEFER/FAIL
 */
 
 static int
-tls_set_remaining_x509(exim_gnutls_state_st *state)
+tls_set_remaining_x509(exim_gnutls_state_st *state, uschar ** errstr)
 {
 int rc;
 const host_item *host = state->host;  /* macro should be reconsidered? */
 {
 int rc;
 const host_item *host = state->host;  /* macro should be reconsidered? */
@@ -957,7 +1193,7 @@ if (!state->host)
   {
   if (!dh_server_params)
     {
   {
   if (!dh_server_params)
     {
-    rc = init_server_dh();
+    rc = init_server_dh(errstr);
     if (rc != OK) return rc;
     }
   gnutls_certificate_set_dh_params(state->x509_cred, dh_server_params);
     if (rc != OK) return rc;
     }
   gnutls_certificate_set_dh_params(state->x509_cred, dh_server_params);
@@ -966,7 +1202,7 @@ if (!state->host)
 /* Link the credentials to the session. */
 
 rc = gnutls_credentials_set(state->session, GNUTLS_CRD_CERTIFICATE, state->x509_cred);
 /* Link the credentials to the session. */
 
 rc = gnutls_credentials_set(state->session, GNUTLS_CRD_CERTIFICATE, state->x509_cred);
-exim_gnutls_err_check(US"gnutls_credentials_set");
+exim_gnutls_err_check(rc, US"gnutls_credentials_set");
 
 return OK;
 }
 
 return OK;
 }
@@ -975,6 +1211,38 @@ return OK;
 *            Initialize for GnuTLS               *
 *************************************************/
 
 *            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.
 
 /* Called from both server and client code. In the case of a server, errors
 before actual TLS negotiation return DEFER.
 
@@ -987,6 +1255,7 @@ Arguments:
   crl             CRL file
   require_ciphers tls_require_ciphers setting
   caller_state    returned state-info structure
   crl             CRL file
   require_ciphers tls_require_ciphers setting
   caller_state    returned state-info structure
+  errstr         error string pointer
 
 Returns:          OK/DEFER/FAIL
 */
 
 Returns:          OK/DEFER/FAIL
 */
@@ -1000,7 +1269,9 @@ tls_init(
     const uschar *cas,
     const uschar *crl,
     const uschar *require_ciphers,
     const uschar *cas,
     const uschar *crl,
     const uschar *require_ciphers,
-    exim_gnutls_state_st **caller_state)
+    exim_gnutls_state_st **caller_state,
+    tls_support * tlsp,
+    uschar ** errstr)
 {
 exim_gnutls_state_st *state;
 int rc;
 {
 exim_gnutls_state_st *state;
 int rc;
@@ -1022,30 +1293,41 @@ if (!exim_gnutls_base_init_done)
   if (!gnutls_allow_auto_pkcs11)
     {
     rc = gnutls_pkcs11_init(GNUTLS_PKCS11_FLAG_MANUAL, NULL);
   if (!gnutls_allow_auto_pkcs11)
     {
     rc = gnutls_pkcs11_init(GNUTLS_PKCS11_FLAG_MANUAL, NULL);
-    exim_gnutls_err_check(US"gnutls_pkcs11_init");
+    exim_gnutls_err_check(rc, US"gnutls_pkcs11_init");
     }
 #endif
 
   rc = gnutls_global_init();
     }
 #endif
 
   rc = gnutls_global_init();
-  exim_gnutls_err_check(US"gnutls_global_init");
+  exim_gnutls_err_check(rc, US"gnutls_global_init");
 
 #if EXIM_GNUTLS_LIBRARY_LOG_LEVEL >= 0
   DEBUG(D_tls)
     {
     gnutls_global_set_log_function(exim_gnutls_logger_cb);
 
 #if EXIM_GNUTLS_LIBRARY_LOG_LEVEL >= 0
   DEBUG(D_tls)
     {
     gnutls_global_set_log_function(exim_gnutls_logger_cb);
-    /* arbitrarily chosen level; bump upto 9 for more */
+    /* arbitrarily chosen level; bump up to 9 for more */
     gnutls_global_set_log_level(EXIM_GNUTLS_LIBRARY_LOG_LEVEL);
     }
 #endif
 
     gnutls_global_set_log_level(EXIM_GNUTLS_LIBRARY_LOG_LEVEL);
     }
 #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;
   }
 
 if (host)
   {
   exim_gnutls_base_init_done = TRUE;
   }
 
 if (host)
   {
-  state = &state_client;
+  /* For client-side sessions we allocate a context. This lets us run
+  several in parallel. */
+  int old_pool = store_pool;
+  store_pool = POOL_PERM;
+  state = store_get(sizeof(exim_gnutls_state_st));
+  store_pool = old_pool;
+
   memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init));
   memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init));
-  state->tlsp = &tls_out;
+  state->tlsp = tlsp;
   DEBUG(D_tls) debug_printf("initialising GnuTLS client session\n");
   rc = gnutls_init(&state->session, GNUTLS_CLIENT);
   }
   DEBUG(D_tls) debug_printf("initialising GnuTLS client session\n");
   rc = gnutls_init(&state->session, GNUTLS_CLIENT);
   }
@@ -1053,11 +1335,11 @@ else
   {
   state = &state_server;
   memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init));
   {
   state = &state_server;
   memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init));
-  state->tlsp = &tls_in;
+  state->tlsp = tlsp;
   DEBUG(D_tls) debug_printf("initialising GnuTLS server session\n");
   rc = gnutls_init(&state->session, GNUTLS_SERVER);
   }
   DEBUG(D_tls) debug_printf("initialising GnuTLS server session\n");
   rc = gnutls_init(&state->session, GNUTLS_SERVER);
   }
-exim_gnutls_err_check(US"gnutls_init");
+exim_gnutls_err_check(rc, US"gnutls_init");
 
 state->host = host;
 
 
 state->host = host;
 
@@ -1073,19 +1355,17 @@ that's tls_certificate, tls_privatekey, tls_verify_certificates, tls_crl */
 
 DEBUG(D_tls)
   debug_printf("Expanding various TLS configuration options for session credentials.\n");
 
 DEBUG(D_tls)
   debug_printf("Expanding various TLS configuration options for session credentials.\n");
-rc = tls_expand_session_files(state);
-if (rc != OK) return rc;
+if ((rc = tls_expand_session_files(state, errstr)) != OK) return rc;
 
 /* These are all other parts of the x509_cred handling, since SNI in GnuTLS
 requires a new structure afterwards. */
 
 
 /* These are all other parts of the x509_cred handling, since SNI in GnuTLS
 requires a new structure afterwards. */
 
-rc = tls_set_remaining_x509(state);
-if (rc != OK) return rc;
+if ((rc = tls_set_remaining_x509(state, errstr)) != OK) return rc;
 
 /* set SNI in client, only */
 if (host)
   {
 
 /* set SNI in client, only */
 if (host)
   {
-  if (!expand_check(sni, US"tls_out_sni", &state->tlsp->sni))
+  if (!expand_check(sni, US"tls_out_sni", &state->tlsp->sni, errstr))
     return DEFER;
   if (state->tlsp->sni && *state->tlsp->sni)
     {
     return DEFER;
   if (state->tlsp->sni && *state->tlsp->sni)
     {
@@ -1094,12 +1374,12 @@ if (host)
     sz = Ustrlen(state->tlsp->sni);
     rc = gnutls_server_name_set(state->session,
         GNUTLS_NAME_DNS, state->tlsp->sni, sz);
     sz = Ustrlen(state->tlsp->sni);
     rc = gnutls_server_name_set(state->session,
         GNUTLS_NAME_DNS, state->tlsp->sni, sz);
-    exim_gnutls_err_check(US"gnutls_server_name_set");
+    exim_gnutls_err_check(rc, US"gnutls_server_name_set");
     }
   }
 else if (state->tls_sni)
   DEBUG(D_tls) debug_printf("*** PROBABLY A BUG *** " \
     }
   }
 else if (state->tls_sni)
   DEBUG(D_tls) debug_printf("*** PROBABLY A BUG *** " \
-      "have an SNI set for a client [%s]\n", state->tls_sni);
+      "have an SNI set for a server [%s]\n", state->tls_sni);
 
 /* This is the priority string support,
 http://www.gnutls.org/manual/html_node/Priority-Strings.html
 
 /* This is the priority string support,
 http://www.gnutls.org/manual/html_node/Priority-Strings.html
@@ -1111,7 +1391,7 @@ want_default_priorities = TRUE;
 
 if (state->tls_require_ciphers && *state->tls_require_ciphers)
   {
 
 if (state->tls_require_ciphers && *state->tls_require_ciphers)
   {
-  if (!expand_check_tlsvar(tls_require_ciphers))
+  if (!expand_check_tlsvar(tls_require_ciphers, errstr))
     return DEFER;
   if (state->exp_tls_require_ciphers && *state->exp_tls_require_ciphers)
     {
     return DEFER;
   if (state->exp_tls_require_ciphers && *state->exp_tls_require_ciphers)
     {
@@ -1134,12 +1414,12 @@ if (want_default_priorities)
   p = US exim_default_gnutls_priority;
   }
 
   p = US exim_default_gnutls_priority;
   }
 
-exim_gnutls_err_check(string_sprintf(
+exim_gnutls_err_check(rc, string_sprintf(
       "gnutls_priority_init(%s) failed at offset %ld, \"%.6s..\"",
       p, errpos - CS p, errpos));
 
 rc = gnutls_priority_set(state->session, state->priority_cache);
       "gnutls_priority_init(%s) failed at offset %ld, \"%.6s..\"",
       p, errpos - CS p, errpos));
 
 rc = gnutls_priority_set(state->session, state->priority_cache);
-exim_gnutls_err_check(US"gnutls_priority_set");
+exim_gnutls_err_check(rc, US"gnutls_priority_set");
 
 gnutls_db_set_cache_expiration(state->session, ssl_session_timeout);
 
 
 gnutls_db_set_cache_expiration(state->session, ssl_session_timeout);
 
@@ -1185,15 +1465,16 @@ don't apply.
 
 Arguments:
   state           exim_gnutls_state_st *
 
 Arguments:
   state           exim_gnutls_state_st *
+  errstr         pointer to error string
 
 Returns:          OK/DEFER/FAIL
 */
 
 static int
 
 Returns:          OK/DEFER/FAIL
 */
 
 static int
-peer_status(exim_gnutls_state_st *state)
+peer_status(exim_gnutls_state_st *state, uschar ** errstr)
 {
 uschar cipherbuf[256];
 {
 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;
 int old_pool, rc;
 unsigned int cert_list_size = 0;
 gnutls_protocol_t protocol;
@@ -1244,19 +1525,19 @@ if (cert_list == NULL || cert_list_size == 0)
       cert_list, cert_list_size);
   if (state->verify_requirement >= VERIFY_REQUIRED)
     return tls_error(US"certificate verification failed",
       cert_list, cert_list_size);
   if (state->verify_requirement >= VERIFY_REQUIRED)
     return tls_error(US"certificate verification failed",
-        "no certificate received from peer", state->host);
+        US"no certificate received from peer", state->host, errstr);
   return OK;
   }
 
 ct = gnutls_certificate_type_get(state->session);
 if (ct != GNUTLS_CRT_X509)
   {
   return OK;
   }
 
 ct = gnutls_certificate_type_get(state->session);
 if (ct != GNUTLS_CRT_X509)
   {
-  const char *ctn = gnutls_certificate_type_get_name(ct);
+  const uschar *ctn = US gnutls_certificate_type_get_name(ct);
   DEBUG(D_tls)
     debug_printf("TLS: peer cert not X.509 but instead \"%s\"\n", ctn);
   if (state->verify_requirement >= VERIFY_REQUIRED)
     return tls_error(US"certificate verification not possible, unhandled type",
   DEBUG(D_tls)
     debug_printf("TLS: peer cert not X.509 but instead \"%s\"\n", ctn);
   if (state->verify_requirement >= VERIFY_REQUIRED)
     return tls_error(US"certificate verification not possible, unhandled type",
-        ctn, state->host);
+        ctn, state->host, errstr);
   return OK;
   }
 
   return OK;
   }
 
@@ -1267,7 +1548,7 @@ if (ct != GNUTLS_CRT_X509)
       DEBUG(D_tls) debug_printf("TLS: peer cert problem: %s: %s\n", \
        (Label), gnutls_strerror(rc)); \
       if (state->verify_requirement >= VERIFY_REQUIRED) \
       DEBUG(D_tls) debug_printf("TLS: peer cert problem: %s: %s\n", \
        (Label), gnutls_strerror(rc)); \
       if (state->verify_requirement >= VERIFY_REQUIRED) \
-       return tls_error((Label), gnutls_strerror(rc), state->host); \
+       return tls_error((Label), US gnutls_strerror(rc), state->host, errstr); \
       return OK; \
       } \
     } while (0)
       return OK; \
       } \
     } while (0)
@@ -1307,8 +1588,8 @@ gnutls_certificate_set_verify_function() to fail the handshake if we dislike
 the peer information, but that's too new for some OSes.
 
 Arguments:
 the peer information, but that's too new for some OSes.
 
 Arguments:
-  state           exim_gnutls_state_st *
-  error           where to put an error message
+  state                exim_gnutls_state_st *
+  errstr       where to put an error message
 
 Returns:
   FALSE     if the session should be rejected
 
 Returns:
   FALSE     if the session should be rejected
@@ -1316,76 +1597,225 @@ Returns:
 */
 
 static BOOL
 */
 
 static BOOL
-verify_certificate(exim_gnutls_state_st *state, const char **error)
+verify_certificate(exim_gnutls_state_st * state, uschar ** errstr)
 {
 int rc;
 {
 int rc;
-unsigned int verify;
+uint verify;
+
+if (state->verify_requirement == VERIFY_NONE)
+  return TRUE;
 
 
-*error = NULL;
+DEBUG(D_tls) debug_printf("TLS: checking peer certificate\n");
+*errstr = NULL;
 
 
-if ((rc = peer_status(state)) != OK)
+if ((rc = peer_status(state, errstr)) != OK)
   {
   verify = GNUTLS_CERT_INVALID;
   {
   verify = GNUTLS_CERT_INVALID;
-  *error = "certificate not supplied";
+  *errstr = US"certificate not supplied";
   }
 else
   }
 else
+
+  {
+#ifdef SUPPORT_DANE
+  if (state->verify_requirement == VERIFY_DANE && state->host)
+    {
+    /* Using dane_verify_session_crt() would be easy, as it does it all for us
+    including talking to a DNS resolver.  But we want to do that bit ourselves
+    as the testsuite intercepts and fakes its own DNS environment. */
+
+    dane_state_t s;
+    dane_query_t r;
+    uint lsize;
+    const gnutls_datum_t * certlist =
+      gnutls_certificate_get_peers(state->session, &lsize);
+    int usage = tls_out.tlsa_usage;
+
+# ifdef GNUTLS_BROKEN_DANE_VALIDATION
+    /* Split the TLSA records into two sets, TA and EE selectors.  Run the
+    dane-verification separately so that we know which selector verified;
+    then we know whether to do name-verification (needed for TA but not EE). */
+
+    if (usage == ((1<<DANESSL_USAGE_DANE_TA) | (1<<DANESSL_USAGE_DANE_EE)))
+      {                                                /* a mixed-usage bundle */
+      int i, j, nrec;
+      const char ** dd;
+      int * ddl;
+
+      for(nrec = 0; state->dane_data_len[nrec]; ) nrec++;
+      nrec++;
+
+      dd = store_get(nrec * sizeof(uschar *));
+      ddl = store_get(nrec * sizeof(int));
+      nrec--;
+
+      if ((rc = dane_state_init(&s, 0)))
+       goto tlsa_prob;
+
+      for (usage = DANESSL_USAGE_DANE_EE;
+          usage >= DANESSL_USAGE_DANE_TA; usage--)
+       {                               /* take records with this usage */
+       for (j = i = 0; i < nrec; i++)
+         if (state->dane_data[i][0] == usage)
+           {
+           dd[j] = state->dane_data[i];
+           ddl[j++] = state->dane_data_len[i];
+           }
+       if (j)
+         {
+         dd[j] = NULL;
+         ddl[j] = 0;
+
+         if ((rc = dane_raw_tlsa(s, &r, (char * const *)dd, ddl, 1, 0)))
+           goto tlsa_prob;
+
+         if ((rc = dane_verify_crt_raw(s, certlist, lsize,
+                           gnutls_certificate_type_get(state->session),
+                           r, 0,
+                           usage == DANESSL_USAGE_DANE_EE
+                           ? DANE_VFLAG_ONLY_CHECK_EE_USAGE : 0,
+                           &verify)))
+           {
+           DEBUG(D_tls)
+             debug_printf("TLSA record problem: %s\n", dane_strerror(rc));
+           }
+         else if (verify == 0) /* verification passed */
+           {
+           usage = 1 << usage;
+           break;
+           }
+         }
+       }
+
+       if (rc) goto tlsa_prob;
+      }
+    else
+# endif
+      {
+      if (  (rc = dane_state_init(&s, 0))
+        || (rc = dane_raw_tlsa(s, &r, state->dane_data, state->dane_data_len,
+                       1, 0))
+        || (rc = dane_verify_crt_raw(s, certlist, lsize,
+                       gnutls_certificate_type_get(state->session),
+                       r, 0,
+# ifdef GNUTLS_BROKEN_DANE_VALIDATION
+                       usage == (1 << DANESSL_USAGE_DANE_EE)
+                       ? DANE_VFLAG_ONLY_CHECK_EE_USAGE : 0,
+# else
+                       0,
+# endif
+                       &verify))
+        )
+       goto tlsa_prob;
+      }
+
+    if (verify != 0)           /* verification failed */
+      {
+      gnutls_datum_t str;
+      (void) dane_verification_status_print(verify, &str, 0);
+      *errstr = US str.data;   /* don't bother to free */
+      goto badcert;
+      }
+
+# ifdef GNUTLS_BROKEN_DANE_VALIDATION
+    /* If a TA-mode TLSA record was used for verification we must additionally
+    verify the cert name (but not the CA chain).  For EE-mode, skip it. */
+
+    if (usage & (1 << DANESSL_USAGE_DANE_EE))
+# endif
+      {
+      state->peer_dane_verified = state->peer_cert_verified = TRUE;
+      goto goodcert;
+      }
+# ifdef GNUTLS_BROKEN_DANE_VALIDATION
+    /* Assume that the name on the A-record is the one that should be matching
+    the cert.  An alternate view is that the domain part of the email address
+    is also permissible. */
+
+    if (gnutls_x509_crt_check_hostname(state->tlsp->peercert,
+         CS state->host->name))
+      {
+      state->peer_dane_verified = state->peer_cert_verified = TRUE;
+      goto goodcert;
+      }
+# endif
+    }
+#endif /*SUPPORT_DANE*/
+
   rc = gnutls_certificate_verify_peers2(state->session, &verify);
   rc = gnutls_certificate_verify_peers2(state->session, &verify);
+  }
 
 
-/* Handle the result of verification. INVALID seems to be set as well
-as REVOKED, but leave the test for both. */
+/* Handle the result of verification. INVALID is set if any others are. */
 
 
-if (rc < 0 ||
-    verify & (GNUTLS_CERT_INVALID|GNUTLS_CERT_REVOKED)
-   )
+if (rc < 0 || verify & (GNUTLS_CERT_INVALID|GNUTLS_CERT_REVOKED))
   {
   state->peer_cert_verified = FALSE;
   {
   state->peer_cert_verified = FALSE;
-  if (!*error)
-    *error = verify & GNUTLS_CERT_REVOKED
-      ? "certificate revoked" : "certificate invalid";
+  if (!*errstr)
+    {
+#ifdef GNUTLS_CERT_VFY_STATUS_PRINT
+    DEBUG(D_tls)
+      {
+      gnutls_datum_t txt;
+
+      if (gnutls_certificate_verification_status_print(verify,
+           gnutls_certificate_type_get(state->session), &txt, 0)
+         == GNUTLS_E_SUCCESS)
+       {
+       debug_printf("%s\n", txt.data);
+       gnutls_free(txt.data);
+       }
+      }
+#endif
+    *errstr = verify & GNUTLS_CERT_REVOKED
+      ? US"certificate revoked" : US"certificate invalid";
+    }
 
   DEBUG(D_tls)
     debug_printf("TLS certificate verification failed (%s): peerdn=\"%s\"\n",
 
   DEBUG(D_tls)
     debug_printf("TLS certificate verification failed (%s): peerdn=\"%s\"\n",
-        *error, state->peerdn ? state->peerdn : US"<unset>");
+        *errstr, state->peerdn ? state->peerdn : US"<unset>");
 
   if (state->verify_requirement >= VERIFY_REQUIRED)
 
   if (state->verify_requirement >= VERIFY_REQUIRED)
-    {
-    gnutls_alert_send(state->session,
-      GNUTLS_AL_FATAL, GNUTLS_A_BAD_CERTIFICATE);
-    return FALSE;
-    }
+    goto badcert;
   DEBUG(D_tls)
     debug_printf("TLS verify failure overridden (host in tls_try_verify_hosts)\n");
   }
 
 else
   {
   DEBUG(D_tls)
     debug_printf("TLS verify failure overridden (host in tls_try_verify_hosts)\n");
   }
 
 else
   {
-#ifdef EXPERIMENTAL_CERTNAMES
-  if (state->verify_requirement == VERIFY_WITHHOST)
+  /* Client side, check the server's certificate name versus the name on the
+  A-record for the connection we made.  What to do for server side - what name
+  to use for client?  We document that there is no such checking for server
+  side. */
+
+  if (  state->exp_tls_verify_cert_hostnames
+     && !gnutls_x509_crt_check_hostname(state->tlsp->peercert,
+               CS state->exp_tls_verify_cert_hostnames)
+     )
     {
     {
-    int sep = 0;
-    uschar * list = state->exp_tls_verify_cert_hostnames;
-    uschar * name;
-    while (name = string_nextinlist(&list, &sep, NULL, 0))
-      if (gnutls_x509_crt_check_hostname(state->tlsp->peercert, CS name))
-       break;
-    if (!name)
-      {
-      DEBUG(D_tls)
-       debug_printf("TLS certificate verification failed: cert name mismatch\n");
-      gnutls_alert_send(state->session,
-       GNUTLS_AL_FATAL, GNUTLS_A_BAD_CERTIFICATE);
-      return FALSE;
-      }
+    DEBUG(D_tls)
+      debug_printf("TLS certificate verification failed: cert name mismatch\n");
+    if (state->verify_requirement >= VERIFY_REQUIRED)
+      goto badcert;
+    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>");
   }
 
   state->peer_cert_verified = TRUE;
   DEBUG(D_tls) debug_printf("TLS certificate verified: peerdn=\"%s\"\n",
       state->peerdn ? state->peerdn : US"<unset>");
   }
 
-state->tlsp->peerdn = state->peerdn;
+goodcert:
+  state->tlsp->peerdn = state->peerdn;
+  return TRUE;
 
 
-return TRUE;
+#ifdef SUPPORT_DANE
+tlsa_prob:
+  *errstr = string_sprintf("TLSA record problem: %s",
+    rc == DANE_E_REQUESTED_DATA_NOT_AVAILABLE ? "none usable" : dane_strerror(rc));
+#endif
+
+badcert:
+  gnutls_alert_send(state->session, GNUTLS_AL_FATAL, GNUTLS_A_BAD_CERTIFICATE);
+  return FALSE;
 }
 
 
 }
 
 
@@ -1438,6 +1868,7 @@ size_t data_len = MAX_HOST_LEN;
 exim_gnutls_state_st *state = &state_server;
 unsigned int sni_type;
 int rc, old_pool;
 exim_gnutls_state_st *state = &state_server;
 unsigned int sni_type;
 int rc, old_pool;
+uschar * dummy_errstr;
 
 rc = gnutls_server_name_get(session, sni_name, &data_len, &sni_type, 0);
 if (rc != GNUTLS_E_SUCCESS)
 
 rc = gnutls_server_name_get(session, sni_name, &data_len, &sni_type, 0);
 if (rc != GNUTLS_E_SUCCESS)
@@ -1448,7 +1879,7 @@ if (rc != GNUTLS_E_SUCCESS)
     else
       debug_printf("TLS failure: gnutls_server_name_get(): %s [%d]\n",
         gnutls_strerror(rc), rc);
     else
       debug_printf("TLS failure: gnutls_server_name_get(): %s [%d]\n",
         gnutls_strerror(rc), rc);
-  };
+    }
   return 0;
   }
 
   return 0;
   }
 
@@ -1473,15 +1904,14 @@ DEBUG(D_tls) debug_printf("Received TLS SNI \"%s\"%s\n", sni_name,
 if (!state->trigger_sni_changes)
   return 0;
 
 if (!state->trigger_sni_changes)
   return 0;
 
-rc = tls_expand_session_files(state);
-if (rc != OK)
+if ((rc = tls_expand_session_files(state, &dummy_errstr)) != OK)
   {
   /* If the setup of certs/etc failed before handshake, TLS would not have
   been offered.  The best we can do now is abort. */
   return GNUTLS_E_APPLICATION_ERROR_MIN;
   }
 
   {
   /* If the setup of certs/etc failed before handshake, TLS would not have
   been offered.  The best we can do now is abort. */
   return GNUTLS_E_APPLICATION_ERROR_MIN;
   }
 
-rc = tls_set_remaining_x509(state);
+rc = tls_set_remaining_x509(state, &dummy_errstr);
 if (rc != OK) return GNUTLS_E_APPLICATION_ERROR_MIN;
 
 return 0;
 if (rc != OK) return GNUTLS_E_APPLICATION_ERROR_MIN;
 
 return 0;
@@ -1496,11 +1926,12 @@ server_ocsp_stapling_cb(gnutls_session_t session, void * ptr,
   gnutls_datum_t * ocsp_response)
 {
 int ret;
   gnutls_datum_t * ocsp_response)
 {
 int ret;
+DEBUG(D_tls) debug_printf("OCSP stapling callback: %s\n", US ptr);
 
 if ((ret = gnutls_load_file(ptr, ocsp_response)) < 0)
   {
   DEBUG(D_tls) debug_printf("Failed to load ocsp stapling file %s\n",
 
 if ((ret = gnutls_load_file(ptr, ocsp_response)) < 0)
   {
   DEBUG(D_tls) debug_printf("Failed to load ocsp stapling file %s\n",
-                             (char *)ptr);
+                             CS ptr);
   tls_in.ocsp = OCSP_NOT_RESP;
   return GNUTLS_E_NO_CERTIFICATE_STATUS;
   }
   tls_in.ocsp = OCSP_NOT_RESP;
   return GNUTLS_E_NO_CERTIFICATE_STATUS;
   }
@@ -1512,6 +1943,52 @@ return 0;
 #endif
 
 
 #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);
+
+if ((cert_list = gnutls_certificate_get_peers(session, &cert_list_size)))
+  while (cert_list_size--)
+  {
+  if ((rc = import_cert(&cert_list[cert_list_size], &crt)) != 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
 
 
 
 
 
 
@@ -1531,25 +2008,25 @@ a TLS session.
 
 Arguments:
   require_ciphers  list of allowed ciphers or NULL
 
 Arguments:
   require_ciphers  list of allowed ciphers or NULL
+  errstr          pointer to error string
 
 Returns:           OK on success
                    DEFER for errors before the start of the negotiation
 
 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.
 */
 
 int
                      continue running.
 */
 
 int
-tls_server_start(const uschar *require_ciphers)
+tls_server_start(const uschar * require_ciphers, uschar ** errstr)
 {
 int rc;
 {
 int rc;
-const char *error;
-exim_gnutls_state_st *state = NULL;
+exim_gnutls_state_st * state = NULL;
 
 /* Check for previous activation */
 
 /* Check for previous activation */
-if (tls_in.active >= 0)
+if (tls_in.active.sock >= 0)
   {
   {
-  tls_error(US"STARTTLS received after TLS started", "", NULL);
-  smtp_printf("554 Already in TLS\r\n");
+  tls_error(US"STARTTLS received after TLS started", US "", NULL, errstr);
+  smtp_printf("554 Already in TLS\r\n", FALSE);
   return FAIL;
   }
 
   return FAIL;
   }
 
@@ -1558,10 +2035,9 @@ and sent an SMTP response. */
 
 DEBUG(D_tls) debug_printf("initialising GnuTLS as a server\n");
 
 
 DEBUG(D_tls) debug_printf("initialising GnuTLS as a server\n");
 
-rc = tls_init(NULL, tls_certificate, tls_privatekey,
+if ((rc = tls_init(NULL, tls_certificate, tls_privatekey,
     NULL, tls_verify_certificates, tls_crl,
     NULL, tls_verify_certificates, tls_crl,
-    require_ciphers, &state);
-if (rc != OK) return rc;
+    require_ciphers, &state, &tls_in, errstr)) != OK) return rc;
 
 /* If this is a host for which certificate verification is mandatory or
 optional, set up appropriately. */
 
 /* If this is a host for which certificate verification is mandatory or
 optional, set up appropriately. */
@@ -1588,6 +2064,15 @@ else
   gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_IGNORE);
   }
 
   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. */
 
 /* Register SNI handling; always, even if not in tls_certificate, so that the
 expansion variable $tls_sni is always available. */
 
@@ -1602,40 +2087,52 @@ mode, the fflush() happens when smtp_getc() is called. */
 
 if (!state->tlsp->on_connect)
   {
 
 if (!state->tlsp->on_connect)
   {
-  smtp_printf("220 TLS go ahead\r\n");
+  smtp_printf("220 TLS go ahead\r\n", FALSE);
   fflush(smtp_out);
   }
 
 /* Now negotiate the TLS session. We put our own timer on it, since it seems
   fflush(smtp_out);
   }
 
 /* Now negotiate the TLS session. We put our own timer on it, since it seems
-that the GnuTLS library doesn't. */
+that the GnuTLS library doesn't.
+From 3.1.0 there is gnutls_handshake_set_timeout() - but it requires you
+to set (and clear down afterwards) up a pull-timeout callback function that does
+a select, so we're no better off unless avoiding signals becomes an issue. */
 
 gnutls_transport_set_ptr2(state->session,
 
 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;
 state->fd_in = fileno(smtp_in);
 state->fd_out = fileno(smtp_out);
 
 sigalrm_seen = FALSE;
-if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
+if (smtp_receive_timeout > 0) ALARM(smtp_receive_timeout);
 do
 do
-  {
   rc = gnutls_handshake(state->session);
   rc = gnutls_handshake(state->session);
-  } while ((rc == GNUTLS_E_AGAIN) ||
-      (rc == GNUTLS_E_INTERRUPTED && !sigalrm_seen));
-alarm(0);
+while (rc == GNUTLS_E_AGAIN ||  rc == GNUTLS_E_INTERRUPTED && !sigalrm_seen);
+ALARM_CLR(0);
 
 if (rc != GNUTLS_E_SUCCESS)
   {
 
 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. */
 
   /* 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", US"timed out", NULL, errstr);
+    gnutls_db_remove_session(state->session);
+    }
+  else
+    {
+    tls_error(US"gnutls_handshake", US gnutls_strerror(rc), NULL, errstr);
+    (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);
     (void)fclose(smtp_out);
     (void)fclose(smtp_in);
+    smtp_out = smtp_in = NULL;
     }
 
   return FAIL;
     }
 
   return FAIL;
@@ -1645,23 +2142,21 @@ DEBUG(D_tls) debug_printf("gnutls_handshake was successful\n");
 
 /* Verify after the fact */
 
 
 /* Verify after the fact */
 
-if (  state->verify_requirement != VERIFY_NONE
-   && !verify_certificate(state, &error))
+if (!verify_certificate(state, errstr))
   {
   if (state->verify_requirement != VERIFY_OPTIONAL)
     {
   {
   if (state->verify_requirement != VERIFY_OPTIONAL)
     {
-    tls_error(US"certificate verification failed", error, NULL);
+    (void) tls_error(US"certificate verification failed", *errstr, NULL, errstr);
     return FAIL;
     }
   DEBUG(D_tls)
     debug_printf("TLS: continuing on only because verification was optional, after: %s\n",
     return FAIL;
     }
   DEBUG(D_tls)
     debug_printf("TLS: continuing on only because verification was optional, after: %s\n",
-       error);
+       *errstr);
   }
 
 /* Figure out peer DN, and if authenticated, etc. */
 
   }
 
 /* Figure out peer DN, and if authenticated, etc. */
 
-rc = peer_status(state);
-if (rc != OK) return rc;
+if ((rc = peer_status(state, NULL)) != OK) return rc;
 
 /* Sets various Exim expansion variables; always safe within server */
 
 
 /* Sets various Exim expansion variables; always safe within server */
 
@@ -1673,6 +2168,8 @@ and initialize appropriately. */
 state->xfer_buffer = store_malloc(ssl_xfer_buffer_size);
 
 receive_getc = tls_getc;
 state->xfer_buffer = store_malloc(ssl_xfer_buffer_size);
 
 receive_getc = tls_getc;
+receive_getbuf = tls_getbuf;
+receive_get_cache = tls_get_cache;
 receive_ungetc = tls_ungetc;
 receive_feof = tls_feof;
 receive_ferror = tls_ferror;
 receive_ungetc = tls_ungetc;
 receive_feof = tls_feof;
 receive_ferror = tls_ferror;
@@ -1684,6 +2181,96 @@ 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(CUSS &ob->tls_verify_cert_hostnames, host) == OK)
+  {
+  state->exp_tls_verify_cert_hostnames =
+#ifdef SUPPORT_I18N
+    string_domain_utf8_to_alabel(host->name, NULL);
+#else
+    host->name;
+#endif
+  DEBUG(D_tls)
+    debug_printf("TLS: server cert verification includes hostname: \"%s\".\n",
+                   state->exp_tls_verify_cert_hostnames);
+  }
+}
+
+
+
+
+#ifdef SUPPORT_DANE
+/* Given our list of RRs from the TLSA lookup, build a lookup block in
+GnuTLS-DANE's preferred format.  Hang it on the state str for later
+use in DANE verification.
+
+We point at the dnsa data not copy it, so it must remain valid until
+after verification is done.*/
+
+static BOOL
+dane_tlsa_load(exim_gnutls_state_st * state, dns_answer * dnsa)
+{
+dns_record * rr;
+dns_scan dnss;
+int i;
+const char **  dane_data;
+int *          dane_data_len;
+
+for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS), i = 1;
+     rr;
+     rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)
+    ) if (rr->type == T_TLSA) i++;
+
+dane_data = store_get(i * sizeof(uschar *));
+dane_data_len = store_get(i * sizeof(int));
+
+for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS), i = 0;
+     rr;
+     rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)
+    ) if (rr->type == T_TLSA && rr->size > 3)
+  {
+  const uschar * p = rr->data;
+  uint8_t usage = p[0], sel = p[1], type = p[2];
+
+  DEBUG(D_tls)
+    debug_printf("TLSA: %d %d %d size %d\n", usage, sel, type, rr->size);
+
+  if (  (usage != DANESSL_USAGE_DANE_TA && usage != DANESSL_USAGE_DANE_EE)
+     || (sel != 0 && sel != 1)
+     )
+    continue;
+  switch(type)
+    {
+    case 0:    /* Full: cannot check at present */
+               break;
+    case 1:    if (rr->size != 3 + 256/8) continue;    /* sha2-256 */
+               break;
+    case 2:    if (rr->size != 3 + 512/8) continue;    /* sha2-512 */
+               break;
+    default:   continue;
+    }
+
+  tls_out.tlsa_usage |= 1<<usage;
+  dane_data[i] = CS p;
+  dane_data_len[i++] = rr->size;
+  }
+
+if (!i) return FALSE;
+
+dane_data[i] = NULL;
+dane_data_len[i] = 0;
+
+state->dane_data = (char * const *)dane_data;
+state->dane_data_len = dane_data_len;
+return TRUE;
+}
+#endif
+
+
+
 /*************************************************
 *    Start a TLS session in a client             *
 *************************************************/
 /*************************************************
 *    Start a TLS session in a client             *
 *************************************************/
@@ -1692,37 +2279,64 @@ return OK;
 
 Arguments:
   fd                the fd of the connection
 
 Arguments:
   fd                the fd of the connection
-  host              connected host (for messages)
+  host              connected host (for messages and option-tests)
   addr              the first address (not used)
   addr              the first address (not used)
-  ob                smtp transport options
-
-Returns:            OK/DEFER/FAIL (because using common functions),
-                    but for a client, DEFER and FAIL have the same meaning
+  tb                transport (always smtp)
+  tlsa_dnsa        non-NULL, either request or require dane for this host, and
+                   a TLSA record found.  Therefore, dane verify required.
+                   Which implies cert must be requested and supplied, dane
+                   verify must pass, and cert verify irrelevant (incl.
+                   hostnames), and (caller handled) require_tls
+  tlsp             record details of channel configuration
+  errstr           error string pointer
+
+Returns:            Pointer to TLS session context, or NULL on error
 */
 
 */
 
-int
+void *
 tls_client_start(int fd, host_item *host,
     address_item *addr ARG_UNUSED,
 tls_client_start(int fd, host_item *host,
     address_item *addr ARG_UNUSED,
-    void *v_ob)
+    transport_instance * tb,
+#ifdef SUPPORT_DANE
+    dns_answer * tlsa_dnsa,
+#endif
+    tls_support * tlsp, uschar ** errstr)
 {
 {
-smtp_transport_options_block *ob = v_ob;
+smtp_transport_options_block *ob = tb
+  ? (smtp_transport_options_block *)tb->options_block
+  : &smtp_transport_option_defaults;
 int rc;
 int rc;
-const char *error;
-exim_gnutls_state_st *state = NULL;
+exim_gnutls_state_st * state = NULL;
+uschar *cipher_list = NULL;
+
 #ifndef DISABLE_OCSP
 #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(CUSS &ob->hosts_require_ocsp, host) == OK;
 BOOL request_ocsp = require_ocsp ? TRUE
 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(CUSS &ob->hosts_request_ocsp, host) == OK;
 #endif
 
 DEBUG(D_tls) debug_printf("initialising GnuTLS as a client on fd %d\n", fd);
 
 #endif
 
 DEBUG(D_tls) debug_printf("initialising GnuTLS as a client on fd %d\n", fd);
 
-if ((rc = tls_init(host, ob->tls_certificate, ob->tls_privatekey,
+#ifdef SUPPORT_DANE
+if (tlsa_dnsa && ob->dane_require_tls_ciphers)
+  {
+  /* not using expand_check_tlsvar because not yet in state */
+  if (!expand_check(ob->dane_require_tls_ciphers, US"dane_require_tls_ciphers",
+      &cipher_list, errstr))
+    return NULL;
+  cipher_list = cipher_list && *cipher_list
+    ? ob->dane_require_tls_ciphers : ob->tls_require_ciphers;
+  }
+#endif
+
+if (!cipher_list)
+  cipher_list = ob->tls_require_ciphers;
+
+if (tls_init(host, ob->tls_certificate, ob->tls_privatekey,
     ob->tls_sni, ob->tls_verify_certificates, ob->tls_crl,
     ob->tls_sni, ob->tls_verify_certificates, ob->tls_crl,
-    ob->tls_require_ciphers, &state)) != OK)
-  return rc;
+    cipher_list, &state, tlsp, errstr) != OK)
+  return NULL;
 
   {
   int dh_min_bits = ob->tls_dh_min_bits;
 
   {
   int dh_min_bits = ob->tls_dh_min_bits;
@@ -1741,43 +2355,36 @@ if ((rc = tls_init(host, ob->tls_certificate, ob->tls_privatekey,
   gnutls_dh_set_prime_bits(state->session, dh_min_bits);
   }
 
   gnutls_dh_set_prime_bits(state->session, dh_min_bits);
   }
 
-/* Stick to the old behaviour for compatibility if tls_verify_certificates is 
+/* 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 */
 
 set but both tls_verify_hosts and tls_try_verify_hosts are unset. Check only
 the specified host patterns if one of them is defined */
 
-if ((  state->exp_tls_verify_certificates
-    && !ob->tls_verify_hosts
-    && !ob->tls_try_verify_hosts
-    )
-    ||
-    verify_check_host(&ob->tls_verify_hosts) == OK
-   )
+#ifdef SUPPORT_DANE
+if (tlsa_dnsa && dane_tlsa_load(state, tlsa_dnsa))
   {
   {
-#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
+  DEBUG(D_tls)
+    debug_printf("TLS: server certificate DANE required.\n");
+  state->verify_requirement = VERIFY_DANE;
+  gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUIRE);
+  }
+else
 #endif
 #endif
-    {
-    DEBUG(D_tls)
-      debug_printf("TLS: server certificate verification required.\n");
-    state->verify_requirement = VERIFY_REQUIRED;
-    }
+    if (  (  state->exp_tls_verify_certificates
+         && !ob->tls_verify_hosts
+         && (!ob->tls_try_verify_hosts || !*ob->tls_try_verify_hosts)
+         )
+       || verify_check_given_host(CUSS &ob->tls_verify_hosts, host) == OK
+       )
+  {
+  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);
   }
   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(CUSS &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;
   DEBUG(D_tls)
     debug_printf("TLS: server certificate verification optional.\n");
   state->verify_requirement = VERIFY_OPTIONAL;
@@ -1798,13 +2405,24 @@ if (request_ocsp)
   DEBUG(D_tls) debug_printf("TLS: will request OCSP stapling\n");
   if ((rc = gnutls_ocsp_status_request_enable_client(state->session,
                    NULL, 0, NULL)) != OK)
   DEBUG(D_tls) debug_printf("TLS: will request OCSP stapling\n");
   if ((rc = gnutls_ocsp_status_request_enable_client(state->session,
                    NULL, 0, NULL)) != OK)
-    return tls_error(US"cert-status-req",
-                   gnutls_strerror(rc), state->host);
-  tls_out.ocsp = OCSP_NOT_RESP;
+    {
+    tls_error(US"cert-status-req", US gnutls_strerror(rc), state->host, errstr);
+    return NULL;
+    }
+  tlsp->ocsp = OCSP_NOT_RESP;
+  }
+#endif
+
+#ifndef DISABLE_EVENT
+if (tb && 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
 
   }
 #endif
 
-gnutls_transport_set_ptr(state->session, (gnutls_transport_ptr)(long) fd);
+gnutls_transport_set_ptr(state->session, (gnutls_transport_ptr_t)(long) fd);
 state->fd_in = fd;
 state->fd_out = fd;
 
 state->fd_in = fd;
 state->fd_out = fd;
 
@@ -1812,25 +2430,33 @@ DEBUG(D_tls) debug_printf("about to gnutls_handshake\n");
 /* There doesn't seem to be a built-in timeout on connection. */
 
 sigalrm_seen = FALSE;
 /* There doesn't seem to be a built-in timeout on connection. */
 
 sigalrm_seen = FALSE;
-alarm(ob->command_timeout);
+ALARM(ob->command_timeout);
 do
 do
-  {
   rc = gnutls_handshake(state->session);
   rc = gnutls_handshake(state->session);
-  } while ((rc == GNUTLS_E_AGAIN) ||
-      (rc == GNUTLS_E_INTERRUPTED && !sigalrm_seen));
-alarm(0);
+while (rc == GNUTLS_E_AGAIN || rc == GNUTLS_E_INTERRUPTED && !sigalrm_seen);
+ALARM_CLR(0);
 
 if (rc != GNUTLS_E_SUCCESS)
 
 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);
+    tls_error(US"gnutls_handshake", US"timed out", state->host, errstr);
+    }
+  else
+    tls_error(US"gnutls_handshake", US gnutls_strerror(rc), state->host, errstr);
+  return NULL;
+  }
 
 DEBUG(D_tls) debug_printf("gnutls_handshake was successful\n");
 
 /* Verify late */
 
 
 DEBUG(D_tls) debug_printf("gnutls_handshake was successful\n");
 
 /* Verify late */
 
-if (state->verify_requirement != VERIFY_NONE &&
-    !verify_certificate(state, &error))
-  return tls_error(US"certificate verification failed", error, state->host);
+if (!verify_certificate(state, errstr))
+  {
+  tls_error(US"certificate verification failed", *errstr, state->host, errstr);
+  return NULL;
+  }
 
 #ifndef DISABLE_OCSP
 if (require_ocsp)
 
 #ifndef DISABLE_OCSP
 if (require_ocsp)
@@ -1850,29 +2476,30 @@ if (require_ocsp)
       gnutls_free(printed.data);
       }
     else
       gnutls_free(printed.data);
       }
     else
-      (void) tls_error(US"ocsp decode", gnutls_strerror(rc), state->host);
+      (void) tls_error(US"ocsp decode", US gnutls_strerror(rc), state->host, errstr);
     }
 
   if (gnutls_ocsp_status_request_is_checked(state->session, 0) == 0)
     {
     }
 
   if (gnutls_ocsp_status_request_is_checked(state->session, 0) == 0)
     {
-    tls_out.ocsp = OCSP_FAILED;
-    return tls_error(US"certificate status check failed", NULL, state->host);
+    tlsp->ocsp = OCSP_FAILED;
+    tls_error(US"certificate status check failed", NULL, state->host, errstr);
+    return NULL;
     }
   DEBUG(D_tls) debug_printf("Passed OCSP checking\n");
     }
   DEBUG(D_tls) debug_printf("Passed OCSP checking\n");
-  tls_out.ocsp = OCSP_VFIED;
+  tlsp->ocsp = OCSP_VFIED;
   }
 #endif
 
 /* Figure out peer DN, and if authenticated, etc. */
 
   }
 #endif
 
 /* Figure out peer DN, and if authenticated, etc. */
 
-if ((rc = peer_status(state)) != OK)
-  return rc;
+if (peer_status(state, errstr) != OK)
+  return NULL;
 
 /* Sets various Exim expansion variables; may need to adjust for ACL callouts */
 
 extract_exim_vars_from_tls_state(state);
 
 
 /* Sets various Exim expansion variables; may need to adjust for ACL callouts */
 
 extract_exim_vars_from_tls_state(state);
 
-return OK;
+return state;
 }
 
 
 }
 
 
@@ -1886,38 +2513,127 @@ return OK;
 daemon, to shut down the TLS library, without actually doing a shutdown (which
 would tamper with the TLS session in the parent process).
 
 daemon, to shut down the TLS library, without actually doing a shutdown (which
 would tamper with the TLS session in the parent process).
 
-Arguments:   TRUE if gnutls_bye is to be called
+Arguments:
+  ct_ctx       client context pointer, or NULL for the one global server context
+  shutdown     1 if TLS close-alert is to be sent,
+               2 if also response to be waited for
+
 Returns:     nothing
 */
 
 void
 Returns:     nothing
 */
 
 void
-tls_close(BOOL is_server, BOOL shutdown)
+tls_close(void * ct_ctx, int shutdown)
 {
 {
-exim_gnutls_state_st *state = is_server ? &state_server : &state_client;
+exim_gnutls_state_st * state = ct_ctx ? ct_ctx : &state_server;
 
 
-if (!state->tlsp || state->tlsp->active < 0) return;  /* TLS was not active */
+if (!state->tlsp || state->tlsp->active.sock < 0) return;  /* TLS was not active */
 
 if (shutdown)
   {
 
 if (shutdown)
   {
-  DEBUG(D_tls) debug_printf("tls_close(): shutting down TLS\n");
-  gnutls_bye(state->session, GNUTLS_SHUT_WR);
+  DEBUG(D_tls) debug_printf("tls_close(): shutting down TLS%s\n",
+    shutdown > 1 ? " (with response-wait)" : "");
+
+  ALARM(2);
+  gnutls_bye(state->session, shutdown > 1 ? GNUTLS_SHUT_RDWR : GNUTLS_SHUT_WR);
+  ALARM_CLR(0);
   }
 
 gnutls_deinit(state->session);
   }
 
 gnutls_deinit(state->session);
+gnutls_certificate_free_credentials(state->x509_cred);
+
 
 
-state->tlsp->active = -1;
+state->tlsp->active.sock = -1;
+state->tlsp->active.tls_ctx = NULL;
+if (state->xfer_buffer) store_free(state->xfer_buffer);
 memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init));
 memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init));
+}
+
+
+
+
+static BOOL
+tls_refill(unsigned lim)
+{
+exim_gnutls_state_st * state = &state_server;
+ssize_t inbytes;
+
+DEBUG(D_tls) debug_printf("Calling gnutls_record_recv(%p, %p, %u)\n",
+  state->session, state->xfer_buffer, ssl_xfer_buffer_size);
+
+sigalrm_seen = FALSE;
+if (smtp_receive_timeout > 0) ALARM(smtp_receive_timeout);
 
 
-if ((state_server.session == NULL) && (state_client.session == NULL))
+do
+  inbytes = gnutls_record_recv(state->session, state->xfer_buffer,
+    MIN(ssl_xfer_buffer_size, lim));
+while (inbytes == GNUTLS_E_AGAIN);
+
+if (smtp_receive_timeout > 0) ALARM_CLR(0);
+
+if (had_command_timeout)               /* set by signal handler */
+  smtp_command_timeout_exit();         /* does not return */
+if (had_command_sigterm)
+  smtp_command_sigterm_exit();
+if (had_data_timeout)
+  smtp_data_timeout_exit();
+if (had_data_sigint)
+  smtp_data_sigint_exit();
+
+/* Timeouts do not get this far.  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 (sigalrm_seen)
   {
   {
-  gnutls_global_deinit();
-  exim_gnutls_base_init_done = FALSE;
+  DEBUG(D_tls) debug_printf("Got tls read timeout\n");
+  state->xfer_error = TRUE;
+  return FALSE;
   }
 
   }
 
-}
+else if (inbytes == 0)
+  {
+  DEBUG(D_tls) debug_printf("Got TLS_EOF\n");
+
+  receive_getc = smtp_getc;
+  receive_getbuf = smtp_getbuf;
+  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.sock = -1;
+  state->tlsp->active.tls_ctx = NULL;
+  state->tlsp->bits = 0;
+  state->tlsp->certificate_verified = FALSE;
+  tls_channelbinding_b64 = NULL;
+  state->tlsp->cipher = NULL;
+  state->tlsp->peercert = NULL;
+  state->tlsp->peerdn = NULL;
 
 
+  return FALSE;
+  }
+
+/* Handle genuine errors */
 
 
+else if (inbytes < 0)
+  {
+  DEBUG(D_tls) debug_printf("%s: err from gnutls_record_recv(\n", __FUNCTION__);
+  record_io_error(state, (int) inbytes, US"recv", NULL);
+  state->xfer_error = TRUE;
+  return FALSE;
+  }
+#ifndef DISABLE_DKIM
+dkim_exim_verify_feed(state->xfer_buffer, inbytes);
+#endif
+state->xfer_buffer_hwm = (int) inbytes;
+state->xfer_buffer_lwm = 0;
+return TRUE;
+}
 
 /*************************************************
 *            TLS version of getc                 *
 
 /*************************************************
 *            TLS version of getc                 *
@@ -1929,71 +2645,65 @@ Only used by the server-side TLS.
 
 This feeds DKIM and should be used for all message-body reads.
 
 
 This feeds DKIM and should be used for all message-body reads.
 
-Arguments:  none
+Arguments:  lim                Maximum amount to read/buffer
 Returns:    the next character or EOF
 */
 
 int
 Returns:    the next character or EOF
 */
 
 int
-tls_getc(void)
+tls_getc(unsigned lim)
 {
 {
-exim_gnutls_state_st *state = &state_server;
+exim_gnutls_state_st * state = &state_server;
+
 if (state->xfer_buffer_lwm >= state->xfer_buffer_hwm)
 if (state->xfer_buffer_lwm >= state->xfer_buffer_hwm)
-  {
-  ssize_t inbytes;
+  if (!tls_refill(lim))
+    return state->xfer_error ? EOF : smtp_getc(lim);
 
 
-  DEBUG(D_tls) debug_printf("Calling gnutls_record_recv(%p, %p, %u)\n",
-    state->session, state->xfer_buffer, ssl_xfer_buffer_size);
+/* Something in the buffer; return next uschar */
 
 
-  if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
-  inbytes = gnutls_record_recv(state->session, state->xfer_buffer,
-    ssl_xfer_buffer_size);
-  alarm(0);
+return state->xfer_buffer[state->xfer_buffer_lwm++];
+}
 
 
-  /* 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. */
+uschar *
+tls_getbuf(unsigned * len)
+{
+exim_gnutls_state_st * state = &state_server;
+unsigned size;
+uschar * buf;
 
 
-  if (inbytes == 0)
+if (state->xfer_buffer_lwm >= state->xfer_buffer_hwm)
+  if (!tls_refill(*len))
     {
     {
-    DEBUG(D_tls) debug_printf("Got TLS_EOF\n");
-
-    receive_getc = smtp_getc;
-    receive_ungetc = smtp_ungetc;
-    receive_feof = smtp_feof;
-    receive_ferror = smtp_ferror;
-    receive_smtp_buffered = smtp_buffered;
-
-    gnutls_deinit(state->session);
-    state->session = NULL;
-    state->tlsp->active = -1;
-    state->tlsp->bits = 0;
-    state->tlsp->certificate_verified = FALSE;
-    tls_channelbinding_b64 = NULL;
-    state->tlsp->cipher = NULL;
-    state->tlsp->peercert = NULL;
-    state->tlsp->peerdn = NULL;
-
-    return smtp_getc();
+    if (!state->xfer_error) return smtp_getbuf(len);
+    *len = 0;
+    return NULL;
     }
 
     }
 
-  /* Handle genuine errors */
+if ((size = state->xfer_buffer_hwm - state->xfer_buffer_lwm) > *len)
+  size = *len;
+buf = &state->xfer_buffer[state->xfer_buffer_lwm];
+state->xfer_buffer_lwm += size;
+*len = size;
+return buf;
+}
 
 
-  else if (inbytes < 0)
-    {
-    record_io_error(state, (int) inbytes, US"recv", NULL);
-    state->xfer_error = 1;
-    return EOF;
-    }
+
+void
+tls_get_cache()
+{
 #ifndef DISABLE_DKIM
 #ifndef DISABLE_DKIM
-  dkim_exim_verify_feed(state->xfer_buffer, inbytes);
+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
 #endif
-  state->xfer_buffer_hwm = (int) inbytes;
-  state->xfer_buffer_lwm = 0;
-  }
+}
 
 
-/* Something in the buffer; return next uschar */
 
 
-return state->xfer_buffer[state->xfer_buffer_lwm++];
+BOOL
+tls_could_read(void)
+{
+return state_server.xfer_buffer_lwm < state_server.xfer_buffer_hwm
+ || gnutls_record_check_pending(state_server.session) > 0;
 }
 
 
 }
 
 
@@ -2007,17 +2717,18 @@ return state->xfer_buffer[state->xfer_buffer_lwm++];
 then the caller must feed DKIM.
 
 Arguments:
 then the caller must feed DKIM.
 
 Arguments:
+  ct_ctx    client context pointer, or NULL for the one global server context
   buff      buffer of data
   len       size of buffer
 
 Returns:    the number of bytes read
   buff      buffer of data
   len       size of buffer
 
 Returns:    the number of bytes read
-            -1 after a failed read
+            -1 after a failed read, including EOF
 */
 
 int
 */
 
 int
-tls_read(BOOL is_server, uschar *buff, size_t len)
+tls_read(void * ct_ctx, uschar *buff, size_t len)
 {
 {
-exim_gnutls_state_st *state = is_server ? &state_server : &state_client;
+exim_gnutls_state_st * state = ct_ctx ? ct_ctx : &state_server;
 ssize_t inbytes;
 
 if (len > INT_MAX)
 ssize_t inbytes;
 
 if (len > INT_MAX)
@@ -2033,13 +2744,20 @@ DEBUG(D_tls)
   debug_printf("Calling gnutls_record_recv(%p, %p, " SIZE_T_FMT ")\n",
       state->session, buff, len);
 
   debug_printf("Calling gnutls_record_recv(%p, %p, " SIZE_T_FMT ")\n",
       state->session, buff, len);
 
-inbytes = gnutls_record_recv(state->session, buff, len);
+do
+  inbytes = gnutls_record_recv(state->session, buff, len);
+while (inbytes == GNUTLS_E_AGAIN);
+
 if (inbytes > 0) return inbytes;
 if (inbytes == 0)
   {
   DEBUG(D_tls) debug_printf("Got TLS_EOF\n");
   }
 if (inbytes > 0) return inbytes;
 if (inbytes == 0)
   {
   DEBUG(D_tls) debug_printf("Got TLS_EOF\n");
   }
-else record_io_error(state, (int)inbytes, US"recv", NULL);
+else
+  {
+  DEBUG(D_tls) debug_printf("%s: err from gnutls_record_recv(\n", __FUNCTION__);
+  record_io_error(state, (int)inbytes, US"recv", NULL);
+  }
 
 return -1;
 }
 
 return -1;
 }
@@ -2053,31 +2771,43 @@ return -1;
 
 /*
 Arguments:
 
 /*
 Arguments:
-  is_server channel specifier
+  ct_ctx    client context pointer, or NULL for the one global server context
   buff      buffer of data
   len       number of bytes
   buff      buffer of data
   len       number of bytes
+  more     more data expected soon
 
 Returns:    the number of bytes after a successful write,
             -1 after a failed write
 */
 
 int
 
 Returns:    the number of bytes after a successful write,
             -1 after a failed write
 */
 
 int
-tls_write(BOOL is_server, const uschar *buff, size_t len)
+tls_write(void * ct_ctx, const uschar * buff, size_t len, BOOL more)
 {
 ssize_t outbytes;
 size_t left = len;
 {
 ssize_t outbytes;
 size_t left = len;
-exim_gnutls_state_st *state = is_server ? &state_server : &state_client;
+exim_gnutls_state_st * state = ct_ctx ? ct_ctx : &state_server;
+#ifdef SUPPORT_CORK
+static BOOL corked = FALSE;
+
+if (more && !corked) gnutls_record_cork(state->session);
+#endif
+
+DEBUG(D_tls) debug_printf("%s(%p, " SIZE_T_FMT "%s)\n", __FUNCTION__,
+  buff, left, more ? ", more" : "");
 
 
-DEBUG(D_tls) debug_printf("tls_do_write(%p, " SIZE_T_FMT ")\n", buff, left);
 while (left > 0)
   {
   DEBUG(D_tls) debug_printf("gnutls_record_send(SSL, %p, " SIZE_T_FMT ")\n",
       buff, left);
 while (left > 0)
   {
   DEBUG(D_tls) debug_printf("gnutls_record_send(SSL, %p, " SIZE_T_FMT ")\n",
       buff, left);
-  outbytes = gnutls_record_send(state->session, buff, left);
+
+  do
+    outbytes = gnutls_record_send(state->session, buff, left);
+  while (outbytes == GNUTLS_E_AGAIN);
 
   DEBUG(D_tls) debug_printf("outbytes=" SSIZE_T_FMT "\n", outbytes);
   if (outbytes < 0)
     {
 
   DEBUG(D_tls) debug_printf("outbytes=" SSIZE_T_FMT "\n", outbytes);
   if (outbytes < 0)
     {
+    DEBUG(D_tls) debug_printf("%s: gnutls_record_send err\n", __FUNCTION__);
     record_io_error(state, outbytes, US"send", NULL);
     return -1;
     }
     record_io_error(state, outbytes, US"send", NULL);
     return -1;
     }
@@ -2099,6 +2829,14 @@ if (len > INT_MAX)
   len = INT_MAX;
   }
 
   len = INT_MAX;
   }
 
+#ifdef SUPPORT_CORK
+if (more != corked)
+  {
+  if (!more) (void) gnutls_record_uncork(state->session, 0);
+  corked = more;
+  }
+#endif
+
 return (int) len;
 }
 
 return (int) len;
 }
 
@@ -2186,6 +2924,7 @@ int rc;
 uschar *expciphers = NULL;
 gnutls_priority_t priority_cache;
 const char *errpos;
 uschar *expciphers = NULL;
 gnutls_priority_t priority_cache;
 const char *errpos;
+uschar * dummy_errstr;
 
 #define validate_check_rc(Label) do { \
   if (rc != GNUTLS_E_SUCCESS) { if (exim_gnutls_base_init_done) gnutls_global_deinit(); \
 
 #define validate_check_rc(Label) do { \
   if (rc != GNUTLS_E_SUCCESS) { if (exim_gnutls_base_init_done) gnutls_global_deinit(); \
@@ -2210,7 +2949,8 @@ exim_gnutls_base_init_done = TRUE;
 if (!(tls_require_ciphers && *tls_require_ciphers))
   return_deinit(NULL);
 
 if (!(tls_require_ciphers && *tls_require_ciphers))
   return_deinit(NULL);
 
-if (!expand_check(tls_require_ciphers, US"tls_require_ciphers", &expciphers))
+if (!expand_check(tls_require_ciphers, US"tls_require_ciphers", &expciphers,
+                 &dummy_errstr))
   return_deinit(US"failed to expand tls_require_ciphers");
 
 if (!(expciphers && *expciphers))
   return_deinit(US"failed to expand tls_require_ciphers");
 
 if (!(expciphers && *expciphers))
index 02158be..8f4cf4d 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Portions Copyright (c) The OpenSSL Project 1999 */
 /* 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>
 #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
 #ifndef DISABLE_OCSP
 # include <openssl/ocsp.h>
 #endif
+#ifdef SUPPORT_DANE
+# include "danessl.h"
+#endif
+
 
 #ifndef DISABLE_OCSP
 # define EXIM_OCSP_SKEW_SECONDS (300L)
 
 #ifndef DISABLE_OCSP
 # define EXIM_OCSP_SKEW_SECONDS (300L)
@@ -34,12 +41,202 @@ functions from the OpenSSL library. */
 #if OPENSSL_VERSION_NUMBER >= 0x0090806fL && !defined(OPENSSL_NO_TLSEXT)
 # define EXIM_HAVE_OPENSSL_TLSEXT
 #endif
 #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
+#  define EXIM_HAVE_OPENSSL_DH_BITS
+#  define EXIM_HAVE_OPENSSL_TLS_METHOD
+# else
+#  define EXIM_NEED_OPENSSL_INIT
+# 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"
 # define DISABLE_OCSP
 #endif
 
 
 #if !defined(EXIM_HAVE_OPENSSL_TLSEXT) && !defined(DISABLE_OCSP)
 # warning "OpenSSL library version too old; define DISABLE_OCSP in Makefile"
 # define DISABLE_OCSP
 #endif
 
+#ifdef EXIM_HAVE_OPENSSL_CHECKHOST
+# include <openssl/x509v3.h>
+#endif
+
+/*************************************************
+*        OpenSSL option parse                    *
+*************************************************/
+
+typedef struct exim_openssl_option {
+  uschar *name;
+  long    value;
+} exim_openssl_option;
+/* We could use a macro to expand, but we need the ifdef and not all the
+options document which version they were introduced in.  Policylet: include
+all options unless explicitly for DTLS, let the administrator choose which
+to apply.
+
+This list is current as of:
+  ==>  1.0.1b  <==
+Plus SSL_OP_SAFARI_ECDHE_ECDSA_BUG from 2013-June patch/discussion on openssl-dev
+Plus SSL_OP_NO_TLSv1_3 for 1.1.2-dev
+*/
+static exim_openssl_option exim_openssl_options[] = {
+/* KEEP SORTED ALPHABETICALLY! */
+#ifdef SSL_OP_ALL
+  { US"all", SSL_OP_ALL },
+#endif
+#ifdef SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION
+  { US"allow_unsafe_legacy_renegotiation", SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION },
+#endif
+#ifdef SSL_OP_CIPHER_SERVER_PREFERENCE
+  { US"cipher_server_preference", SSL_OP_CIPHER_SERVER_PREFERENCE },
+#endif
+#ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS
+  { US"dont_insert_empty_fragments", SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS },
+#endif
+#ifdef SSL_OP_EPHEMERAL_RSA
+  { US"ephemeral_rsa", SSL_OP_EPHEMERAL_RSA },
+#endif
+#ifdef SSL_OP_LEGACY_SERVER_CONNECT
+  { US"legacy_server_connect", SSL_OP_LEGACY_SERVER_CONNECT },
+#endif
+#ifdef SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER
+  { US"microsoft_big_sslv3_buffer", SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER },
+#endif
+#ifdef SSL_OP_MICROSOFT_SESS_ID_BUG
+  { US"microsoft_sess_id_bug", SSL_OP_MICROSOFT_SESS_ID_BUG },
+#endif
+#ifdef SSL_OP_MSIE_SSLV2_RSA_PADDING
+  { US"msie_sslv2_rsa_padding", SSL_OP_MSIE_SSLV2_RSA_PADDING },
+#endif
+#ifdef SSL_OP_NETSCAPE_CHALLENGE_BUG
+  { US"netscape_challenge_bug", SSL_OP_NETSCAPE_CHALLENGE_BUG },
+#endif
+#ifdef SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG
+  { US"netscape_reuse_cipher_change_bug", SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG },
+#endif
+#ifdef SSL_OP_NO_COMPRESSION
+  { US"no_compression", SSL_OP_NO_COMPRESSION },
+#endif
+#ifdef SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION
+  { US"no_session_resumption_on_renegotiation", SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION },
+#endif
+#ifdef SSL_OP_NO_SSLv2
+  { US"no_sslv2", SSL_OP_NO_SSLv2 },
+#endif
+#ifdef SSL_OP_NO_SSLv3
+  { US"no_sslv3", SSL_OP_NO_SSLv3 },
+#endif
+#ifdef SSL_OP_NO_TICKET
+  { US"no_ticket", SSL_OP_NO_TICKET },
+#endif
+#ifdef SSL_OP_NO_TLSv1
+  { US"no_tlsv1", SSL_OP_NO_TLSv1 },
+#endif
+#ifdef SSL_OP_NO_TLSv1_1
+#if SSL_OP_NO_TLSv1_1 == 0x00000400L
+  /* Error in chosen value in 1.0.1a; see first item in CHANGES for 1.0.1b */
+#warning OpenSSL 1.0.1a uses a bad value for SSL_OP_NO_TLSv1_1, ignoring
+#else
+  { US"no_tlsv1_1", SSL_OP_NO_TLSv1_1 },
+#endif
+#endif
+#ifdef SSL_OP_NO_TLSv1_2
+  { US"no_tlsv1_2", SSL_OP_NO_TLSv1_2 },
+#endif
+#ifdef SSL_OP_NO_TLSv1_3
+  { US"no_tlsv1_3", SSL_OP_NO_TLSv1_3 },
+#endif
+#ifdef SSL_OP_SAFARI_ECDHE_ECDSA_BUG
+  { US"safari_ecdhe_ecdsa_bug", SSL_OP_SAFARI_ECDHE_ECDSA_BUG },
+#endif
+#ifdef SSL_OP_SINGLE_DH_USE
+  { US"single_dh_use", SSL_OP_SINGLE_DH_USE },
+#endif
+#ifdef SSL_OP_SINGLE_ECDH_USE
+  { US"single_ecdh_use", SSL_OP_SINGLE_ECDH_USE },
+#endif
+#ifdef SSL_OP_SSLEAY_080_CLIENT_DH_BUG
+  { US"ssleay_080_client_dh_bug", SSL_OP_SSLEAY_080_CLIENT_DH_BUG },
+#endif
+#ifdef SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG
+  { US"sslref2_reuse_cert_type_bug", SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG },
+#endif
+#ifdef SSL_OP_TLS_BLOCK_PADDING_BUG
+  { US"tls_block_padding_bug", SSL_OP_TLS_BLOCK_PADDING_BUG },
+#endif
+#ifdef SSL_OP_TLS_D5_BUG
+  { US"tls_d5_bug", SSL_OP_TLS_D5_BUG },
+#endif
+#ifdef SSL_OP_TLS_ROLLBACK_BUG
+  { US"tls_rollback_bug", SSL_OP_TLS_ROLLBACK_BUG },
+#endif
+};
+
+#ifndef MACRO_PREDEF
+static int exim_openssl_options_size = nelem(exim_openssl_options);
+#endif
+
+#ifdef MACRO_PREDEF
+void
+options_tls(void)
+{
+struct exim_openssl_option * o;
+uschar buf[64];
+
+for (o = exim_openssl_options;
+     o < exim_openssl_options + nelem(exim_openssl_options); o++)
+  {
+  /* Trailing X is workaround for problem with _OPT_OPENSSL_NO_TLSV1
+  being a ".ifdef _OPT_OPENSSL_NO_TLSV1_3" match */
+
+  spf(buf, sizeof(buf), US"_OPT_OPENSSL_%T_X", o->name);
+  builtin_macro_create(buf);
+  }
+}
+#else
+
+/******************************************************************************/
+
 /* Structure for collecting random data for seeding. */
 
 typedef struct randstuff {
 /* Structure for collecting random data for seeding. */
 
 typedef struct randstuff {
@@ -58,7 +255,9 @@ static const uschar *sid_ctx = US"exim";
 Simple case: client, `client_ctx`
  As a client, we can be doing a callout or cut-through delivery while receiving
  a message.  So we have a client context, which should have options initialised
 Simple case: client, `client_ctx`
  As a client, we can be doing a callout or cut-through delivery while receiving
  a message.  So we have a client context, which should have options initialised
- from the SMTP Transport.
+ from the SMTP Transport.  We may also concurrently want to make TLS connections
+ to utility daemons, so client-contexts are allocated and passed around in call
+ args rather than using a gobal.
 
 Server:
  There are two cases: with and without ServerNameIndication from the client.
 
 Server:
  There are two cases: with and without ServerNameIndication from the client.
@@ -72,9 +271,12 @@ Server:
  configuration.
 */
 
  configuration.
 */
 
-static SSL_CTX *client_ctx = NULL;
+typedef struct {
+  SSL_CTX *    ctx;
+  SSL *                ssl;
+} exim_openssl_client_tls_ctx;
+
 static SSL_CTX *server_ctx = NULL;
 static SSL_CTX *server_ctx = NULL;
-static SSL     *client_ssl = NULL;
 static SSL     *server_ssl = NULL;
 
 #ifdef EXIM_HAVE_OPENSSL_TLSEXT
 static SSL     *server_ssl = NULL;
 
 #ifdef EXIM_HAVE_OPENSSL_TLSEXT
@@ -93,8 +295,9 @@ static BOOL reexpand_tls_files_for_sni = FALSE;
 typedef struct tls_ext_ctx_cb {
   uschar *certificate;
   uschar *privatekey;
 typedef struct tls_ext_ctx_cb {
   uschar *certificate;
   uschar *privatekey;
-#ifndef DISABLE_OCSP
   BOOL is_server;
   BOOL is_server;
+#ifndef DISABLE_OCSP
+  STACK_OF(X509) *verify_stack;                /* chain for verifying the proof */
   union {
     struct {
       uschar        *file;
   union {
     struct {
       uschar        *file;
@@ -112,9 +315,9 @@ typedef struct tls_ext_ctx_cb {
   uschar *server_cipher_list;
   /* only passed down to tls_error: */
   host_item *host;
   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;
 
 #endif
 } tls_ext_ctx_cb;
 
@@ -126,7 +329,7 @@ tls_ext_ctx_cb *server_static_cbinfo = NULL;
 
 static int
 setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional,
 
 static int
 setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional,
-    int (*cert_vfy_cb)(int, X509_STORE_CTX *) );
+    int (*cert_vfy_cb)(int, X509_STORE_CTX *), uschar ** errstr );
 
 /* Callbacks */
 #ifdef EXIM_HAVE_OPENSSL_TLSEXT
 
 /* Callbacks */
 #ifdef EXIM_HAVE_OPENSSL_TLSEXT
@@ -153,34 +356,24 @@ Argument:
   host      NULL if setting up a server;
             the connected host if setting up a client
   msg       error message or NULL if we should ask OpenSSL
   host      NULL if setting up a server;
             the connected host if setting up a client
   msg       error message or NULL if we should ask OpenSSL
+  errstr    pointer to output error message
 
 Returns:    OK/DEFER/FAIL
 */
 
 static int
 
 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, uschar ** errstr)
 {
 {
-if (msg == NULL)
+if (!msg)
   {
   {
-  ERR_error_string(ERR_get_error(), ssl_errstring);
-  msg = (uschar *)ssl_errstring;
+  ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring));
+  msg = US ssl_errstring;
   }
 
   }
 
-if (host == NULL)
-  {
-  uschar *conn_info = smtp_get_connection_info();
-  if (Ustrncmp(conn_info, US"SMTP ", 5) == 0)
-    conn_info += 5;
-  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;
-  }
+msg = string_sprintf("(%s): %s", prefix, msg);
+DEBUG(D_tls) debug_printf("TLS error '%s'\n", msg);
+if (errstr) *errstr = msg;
+return host ? FAIL : DEFER;
 }
 
 
 }
 
 
@@ -191,7 +384,7 @@ else
 
 /*
 Arguments:
 
 /*
 Arguments:
-  s          SSL connection
+  s          SSL connection (not used)
   export     not used
   keylength  keylength
 
   export     not used
   keylength  keylength
 
@@ -202,12 +395,24 @@ static RSA *
 rsa_callback(SSL *s, int export, int keylength)
 {
 RSA *rsa_key;
 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);
 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);
+  ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring));
   log_write(0, LOG_MAIN|LOG_PANIC, "TLS error (RSA_generate_key): %s",
     ssl_errstring);
   return NULL;
   log_write(0, LOG_MAIN|LOG_PANIC, "TLS error (RSA_generate_key): %s",
     ssl_errstring);
   return NULL;
@@ -231,9 +436,12 @@ for(i= 0; i<sk_X509_OBJECT_num(roots); i++)
   X509_OBJECT * tmp_obj= sk_X509_OBJECT_value(roots, i);
   if(tmp_obj->type == X509_LU_X509)
     {
   X509_OBJECT * tmp_obj= sk_X509_OBJECT_value(roots, i);
   if(tmp_obj->type == X509_LU_X509)
     {
-    X509 * current_cert= tmp_obj->data.x509;
-    X509_NAME_oneline(X509_get_subject_name(current_cert), CS name, sizeof(name));
-    debug_printf(" %s\n", name);
+    X509_NAME * sn = X509_get_subject_name(tmp_obj->data.x509);
+    if (X509_NAME_oneline(sn, CS name, sizeof(name)))
+      {
+      name[sizeof(name)-1] = '\0';
+      debug_printf(" %s\n", name);
+      }
     }
   }
 }
     }
   }
 }
@@ -241,150 +449,285 @@ 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,
 /*************************************************
 *        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
 
 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.
 
 
 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:
 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
 */
 
 static int
-verify_callback(int state, X509_STORE_CTX *x509ctx,
-  tls_support *tlsp, BOOL *calledp, BOOL *optionalp)
+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);
 {
 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));
+if (!X509_NAME_oneline(X509_get_subject_name(cert), CS dn, sizeof(dn)))
+  {
+  DEBUG(D_tls) debug_printf("X509_NAME_oneline() error\n");
+  log_write(0, LOG_MAIN, "[%s] SSL verify error: internal error",
+    tlsp == &tls_out ? deliver_host_address : sender_host_address);
+  return 0;
+  }
+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),
-    X509_verify_cert_error_string(X509_STORE_CTX_get_error(x509ctx)),
-    txt);
-  tlsp->certificate_verified = FALSE;
+  uschar * extra = verify_mode ? string_sprintf(" (during %c-verify for [%s])",
+      *verify_mode, sender_host_address)
+    : US"";
+  log_write(0, LOG_MAIN, "[%s] SSL verify error%s: depth=%d error=%s cert=%s",
+    tlsp == &tls_out ? deliver_host_address : sender_host_address,
+    extra, depth,
+    X509_verify_cert_error_string(X509_STORE_CTX_get_error(x509ctx)), dn);
   *calledp = TRUE;
   if (!*optionalp)
     {
   *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");
   }
 
     }
   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. */
 #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();
     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
   {
 #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)))
   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
+       /* client, wanting hostname check */
     {
     {
+
+#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;
     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)))
     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)
          {
        {
        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",
+           tlsp == &tls_out ? deliver_host_address : sender_host_address);
          name = NULL;
          }
        break;
        }
     if (!name)
          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))
     if (!tls_is_name_for_cert(verify_cert_hostnames, cert))
+#endif
       {
       {
+      uschar * extra = verify_mode
+        ? string_sprintf(" (during %c-verify for [%s])",
+         *verify_mode, sender_host_address)
+       : US"";
       log_write(0, LOG_MAIN,
       log_write(0, LOG_MAIN,
-       "SSL verify error: certificate name mismatch: \"%s\"\n", txt);
-      return 0;                                /* reject */
+       "[%s] SSL verify error%s: certificate name mismatch: DN=\"%s\" H=\"%s\"",
+       tlsp == &tls_out ? deliver_host_address : sender_host_address,
+       extra, 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",
 #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;
   }
 
   if (!*calledp) tlsp->certificate_verified = TRUE;
   *calledp = TRUE;
   }
 
-return 1;   /* accept */
+return 1;   /* accept, at least for this level */
 }
 
 static int
 }
 
 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
 }
 
 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 SUPPORT_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
+
+if (!X509_NAME_oneline(X509_get_subject_name(cert), CS dn, sizeof(dn)))
+  {
+  DEBUG(D_tls) debug_printf("X509_NAME_oneline() error\n");
+  log_write(0, LOG_MAIN, "[%s] SSL verify error: internal error",
+    deliver_host_address);
+  return 0;
+  }
+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;
+#ifndef DISABLE_OCSP
+  if (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
+  }
+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 /*SUPPORT_DANE*/
+
 
 /*************************************************
 *           Information callback                 *
 
 /*************************************************
 *           Information callback                 *
@@ -405,9 +748,33 @@ Returns:    nothing
 static void
 info_callback(SSL *s, int where, int ret)
 {
 static void
 info_callback(SSL *s, int where, int ret)
 {
-where = where;
-ret = ret;
-DEBUG(D_tls) debug_printf("SSL info: %s\n", SSL_state_string_long(s));
+DEBUG(D_tls)
+  {
+  const uschar * str;
+
+  if (where & SSL_ST_CONNECT)
+     str = US"SSL_connect";
+  else if (where & SSL_ST_ACCEPT)
+     str = US"SSL_accept";
+  else
+     str = US"SSL info (undefined)";
+
+  if (where & SSL_CB_LOOP)
+     debug_printf("%s: %s\n", str, SSL_state_string_long(s));
+  else if (where & SSL_CB_ALERT)
+    debug_printf("SSL3 alert %s:%s:%s\n",
+         str = where & SSL_CB_READ ? US"read" : US"write",
+         SSL_alert_type_string_long(ret), SSL_alert_desc_string_long(ret));
+  else if (where & SSL_CB_EXIT)
+     if (ret == 0)
+       debug_printf("%s: failed in %s\n", str, SSL_state_string_long(s));
+     else if (ret < 0)
+       debug_printf("%s: error in %s\n", str, SSL_state_string_long(s));
+  else if (where & SSL_CB_HANDSHAKE_START)
+     debug_printf("%s: hshake start: %s\n", str, SSL_state_string_long(s));
+  else if (where & SSL_CB_HANDSHAKE_DONE)
+     debug_printf("%s: hshake done: %s\n", str, SSL_state_string_long(s));
+  }
 }
 
 
 }
 
 
@@ -419,21 +786,24 @@ 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:
 /* 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
   dhparam   DH parameter file or fixed parameter identity string
   host      connected host, if client; NULL if server
+  errstr    error string pointer
 
 Returns:    TRUE if OK (nothing to set up, or setup worked)
 */
 
 static BOOL
 
 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, uschar ** errstr)
 {
 BIO *bio;
 DH *dh;
 uschar *dhexpanded;
 const char *pem;
 {
 BIO *bio;
 DH *dh;
 uschar *dhexpanded;
 const char *pem;
+int dh_bitsize;
 
 
-if (!expand_check(dhparam, US"tls_dhparam", &dhexpanded))
+if (!expand_check(dhparam, US"tls_dhparam", &dhexpanded, errstr))
   return FALSE;
 
 if (!dhexpanded || !*dhexpanded)
   return FALSE;
 
 if (!dhexpanded || !*dhexpanded)
@@ -443,7 +813,7 @@ else if (dhexpanded[0] == '/')
   if (!(bio = BIO_new_file(CS dhexpanded, "r")))
     {
     tls_error(string_sprintf("could not read dhparams file %s", dhexpanded),
   if (!(bio = BIO_new_file(CS dhexpanded, "r")))
     {
     tls_error(string_sprintf("could not read dhparams file %s", dhexpanded),
-          host, US strerror(errno));
+          host, US strerror(errno), errstr);
     return FALSE;
     }
   }
     return FALSE;
     }
   }
@@ -458,7 +828,7 @@ else
   if (!(pem = std_dh_prime_named(dhexpanded)))
     {
     tls_error(string_sprintf("Unknown standard DH prime \"%s\"", dhexpanded),
   if (!(pem = std_dh_prime_named(dhexpanded)))
     {
     tls_error(string_sprintf("Unknown standard DH prime \"%s\"", dhexpanded),
-        host, US strerror(errno));
+        host, US strerror(errno), errstr);
     return FALSE;
     }
   bio = BIO_new_mem_buf(CS pem, -1);
     return FALSE;
     }
   bio = BIO_new_mem_buf(CS pem, -1);
@@ -468,25 +838,38 @@ if (!(dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL)))
   {
   BIO_free(bio);
   tls_error(string_sprintf("Could not read tls_dhparams \"%s\"", dhexpanded),
   {
   BIO_free(bio);
   tls_error(string_sprintf("Could not read tls_dhparams \"%s\"", dhexpanded),
-      host, NULL);
+      host, NULL, errstr);
   return FALSE;
   }
 
   return FALSE;
   }
 
+/* note: our default limit of 2236 is not a multiple of 8; the limit comes from
+ * an NSS limit, and the GnuTLS APIs handle bit-sizes fine, so we went with
+ * 2236.  But older OpenSSL can only report in bytes (octets), not bits.
+ * If someone wants to dance at the edge, then they can raise the limit or use
+ * current libraries. */
+#ifdef EXIM_HAVE_OPENSSL_DH_BITS
+/* Added in commit 26c79d5641d; `git describe --contains` says OpenSSL_1_1_0-pre1~1022
+ * This predates OpenSSL_1_1_0 (before a, b, ...) so is in all 1.1.0 */
+dh_bitsize = DH_bits(dh);
+#else
+dh_bitsize = 8 * DH_size(dh);
+#endif
+
 /* Even if it is larger, we silently return success rather than cause things
  * to fail out, so that a too-large DH will not knock out all TLS; it's a
  * debatable choice. */
 /* Even if it is larger, we silently return success rather than cause things
  * to fail out, so that a too-large DH will not knock out all TLS; it's a
  * debatable choice. */
-if ((8*DH_size(dh)) > tls_dh_max_bits)
+if (dh_bitsize > tls_dh_max_bits)
   {
   DEBUG(D_tls)
   {
   DEBUG(D_tls)
-    debug_printf("dhparams file %d bits, is > tls_dh_max_bits limit of %d",
-        8*DH_size(dh), tls_dh_max_bits);
+    debug_printf("dhparams file %d bits, is > tls_dh_max_bits limit of %d\n",
+        dh_bitsize, tls_dh_max_bits);
   }
 else
   {
   SSL_CTX_set_tmp_dh(sctx, dh);
   DEBUG(D_tls)
     debug_printf("Diffie-Hellman initialized from %s with %d-bit prime\n",
   }
 else
   {
   SSL_CTX_set_tmp_dh(sctx, dh);
   DEBUG(D_tls)
     debug_printf("Diffie-Hellman initialized from %s with %d-bit prime\n",
-      dhexpanded ? dhexpanded : US"default", 8*DH_size(dh));
+      dhexpanded ? dhexpanded : US"default", dh_bitsize);
   }
 
 DH_free(dh);
   }
 
 DH_free(dh);
@@ -498,11 +881,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
+  errstr    error string pointer
+
+Returns:    TRUE if OK (nothing to set up, or setup worked)
+*/
+
+static BOOL
+init_ecdh(SSL_CTX * sctx, host_item * host, uschar ** errstr)
+{
+#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, errstr))
+  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 = US"prime256v1";
+#else
+# if defined SSL_CTRL_SET_ECDH_AUTO
+  DEBUG(D_tls) debug_printf(
+    "ECDH OpenSSL 1.0.2+ temp key parameter settings: autoselection\n");
+  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, errstr);
+  return FALSE;
+  }
+
+if (!(ecdh = EC_KEY_new_by_curve_name(nid)))
+  {
+  tls_error(US"Unable to create ec curve", host, NULL, errstr);
+  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, errstr);
+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         *
 *************************************************/
 #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.
 /* 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 +1018,12 @@ Arguments:
 static void
 ocsp_load_response(SSL_CTX *sctx, tls_ext_ctx_cb *cbinfo, const uschar *expanded)
 {
 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;
 
 unsigned long verify_flags;
 int status, reason, i;
 
@@ -535,8 +1034,7 @@ if (cbinfo->u_ocsp.server.response)
   cbinfo->u_ocsp.server.response = NULL;
   }
 
   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);
   {
   DEBUG(D_tls) debug_printf("Failed to open OCSP response file \"%s\"\n",
       cbinfo->u_ocsp.server.file_expanded);
@@ -551,34 +1049,55 @@ if (!resp)
   return;
   }
 
   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;
   }
 
   {
   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;
   }
 
   {
   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 */
 
 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) {
-    ERR_error_string(ERR_get_error(), ssl_errstring);
+  DEBUG(D_tls)
+    {
+    ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring));
     debug_printf("OCSP response verify failure: %s\n", US ssl_errstring);
     }
   goto bad;
     debug_printf("OCSP response verify failure: %s\n", US ssl_errstring);
     }
   goto bad;
@@ -591,8 +1110,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. */
 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");
   {
   DEBUG(D_tls)
     debug_printf("Unable to get first response from OCSP basic response.\n");
@@ -615,15 +1134,15 @@ if (!OCSP_check_validity(thisupd, nextupd, EXIM_OCSP_SKEW_SECONDS, EXIM_OCSP_MAX
   }
 
 supply_response:
   }
 
 supply_response:
-  cbinfo->u_ocsp.server.response = resp;
+  cbinfo->u_ocsp.server.response = resp;       /*XXX stack?*/
 return;
 
 bad:
 return;
 
 bad:
-  if (running_in_test_harness)
+  if (f.running_in_test_harness)
     {
     extern char ** environ;
     uschar ** p;
     {
     extern char ** environ;
     uschar ** p;
-    if (environ) for (p = USS environ; *p != NULL; p++)
+    if (environ) for (p = USS environ; *p; p++)
       if (Ustrncmp(*p, "EXIM_TESTHARNESS_DISABLE_OCSPVALIDITYCHECK", 42) == 0)
        {
        DEBUG(D_tls) debug_printf("Supplying known bad OCSP response\n");
       if (Ustrncmp(*p, "EXIM_TESTHARNESS_DISABLE_OCSPVALIDITYCHECK", 42) == 0)
        {
        DEBUG(D_tls) debug_printf("Supplying known bad OCSP response\n");
@@ -637,6 +1156,96 @@ return;
 
 
 
 
 
 
+/* Create and install a selfsigned certificate, for use in server mode */
+
+static int
+tls_install_selfsign(SSL_CTX * sctx, uschar ** errstr)
+{
+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";
+if (!(rsa = rsa_callback(NULL, 0, 2048)))
+  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), 1);
+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, errstr);
+  if (x509) X509_free(x509);
+  if (pkey) EVP_PKEY_free(pkey);
+  return DEFER;
+}
+
+
+
+
+static int
+tls_add_certfile(SSL_CTX * sctx, tls_ext_ctx_cb * cbinfo, uschar * file,
+  uschar ** errstr)
+{
+DEBUG(D_tls) debug_printf("tls_certificate file %s\n", file);
+if (!SSL_CTX_use_certificate_chain_file(sctx, CS file))
+  return tls_error(string_sprintf(
+    "SSL_CTX_use_certificate_chain_file file=%s", file),
+      cbinfo->host, NULL, errstr);
+return 0;
+}
+
+static int
+tls_add_pkeyfile(SSL_CTX * sctx, tls_ext_ctx_cb * cbinfo, uschar * file,
+  uschar ** errstr)
+{
+DEBUG(D_tls) debug_printf("tls_privatekey file %s\n", file);
+if (!SSL_CTX_use_PrivateKey_file(sctx, CS file, SSL_FILETYPE_PEM))
+  return tls_error(string_sprintf(
+    "SSL_CTX_use_PrivateKey_file file=%s", file), cbinfo->host, NULL, errstr);
+return 0;
+}
+
+
 /*************************************************
 *        Expand key and cert file specs          *
 *************************************************/
 /*************************************************
 *        Expand key and cert file specs          *
 *************************************************/
@@ -648,69 +1257,94 @@ the certificate string.
 Arguments:
   sctx            the SSL_CTX* to update
   cbinfo          various parts of session state
 Arguments:
   sctx            the SSL_CTX* to update
   cbinfo          various parts of session state
+  errstr         error string pointer
 
 Returns:          OK/DEFER/FAIL
 */
 
 static int
 
 Returns:          OK/DEFER/FAIL
 */
 
 static int
-tls_expand_session_files(SSL_CTX *sctx, tls_ext_ctx_cb *cbinfo)
+tls_expand_session_files(SSL_CTX *sctx, tls_ext_ctx_cb *cbinfo,
+  uschar ** errstr)
 {
 uschar *expanded;
 
 {
 uschar *expanded;
 
-if (cbinfo->certificate == NULL)
-  return OK;
+if (!cbinfo->certificate)
+  {
+  if (!cbinfo->is_server)              /* client */
+    return OK;
+                                       /* server */
+  if (tls_install_selfsign(sctx, errstr) != OK)
+    return DEFER;
+  }
+else
+  {
+  int err;
 
 
-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 (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 (!expand_check(cbinfo->certificate, US"tls_certificate", &expanded, errstr))
+    return DEFER;
 
 
-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)
+    if (cbinfo->is_server)
+      {
+      const uschar * file_list = expanded;
+      int sep = 0;
+      uschar * file;
 
 
-if (cbinfo->privatekey != NULL &&
-    !expand_check(cbinfo->privatekey, US"tls_privatekey", &expanded))
-  return DEFER;
+      while (file = string_nextinlist(&file_list, &sep, NULL, 0))
+       if ((err = tls_add_certfile(sctx, cbinfo, file, errstr)))
+         return err;
+      }
+    else       /* would there ever be a need for multiple client certs? */
+      if ((err = tls_add_certfile(sctx, cbinfo, expanded, errstr)))
+       return err;
 
 
-/* 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 (  cbinfo->privatekey
+     && !expand_check(cbinfo->privatekey, US"tls_privatekey", &expanded, errstr))
+    return DEFER;
 
 
-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 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)
+    if (cbinfo->is_server)
+      {
+      const uschar * file_list = expanded;
+      int sep = 0;
+      uschar * file;
+
+      while (file = string_nextinlist(&file_list, &sep, NULL, 0))
+       if ((err = tls_add_pkeyfile(sctx, cbinfo, file, errstr)))
+         return err;
+      }
+    else       /* would there ever be a need for multiple client certs? */
+      if ((err = tls_add_pkeyfile(sctx, cbinfo, expanded, errstr)))
+       return err;
   }
 
 #ifndef DISABLE_OCSP
   }
 
 #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))
+  /*XXX stack*/
+  if (!expand_check(cbinfo->u_ocsp.server.file, US"tls_ocsp_file", &expanded, errstr))
     return DEFER;
 
     return DEFER;
 
-  if (expanded != NULL && *expanded != 0)
+  if (expanded && *expanded)
     {
     DEBUG(D_tls) debug_printf("tls_ocsp_file %s\n", 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
     }
   }
 #endif
@@ -746,6 +1380,7 @@ const char *servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name);
 tls_ext_ctx_cb *cbinfo = (tls_ext_ctx_cb *) arg;
 int rc;
 int old_pool = store_pool;
 tls_ext_ctx_cb *cbinfo = (tls_ext_ctx_cb *) arg;
 int rc;
 int old_pool = store_pool;
+uschar * dummy_errstr;
 
 if (!servername)
   return SSL_TLSEXT_ERR_OK;
 
 if (!servername)
   return SSL_TLSEXT_ERR_OK;
@@ -765,11 +1400,15 @@ if (!reexpand_tls_files_for_sni)
 not confident that memcpy wouldn't break some internal reference counting.
 Especially since there's a references struct member, which would be off. */
 
 not confident that memcpy wouldn't break some internal reference counting.
 Especially since there's a references struct member, which would be off. */
 
+#ifdef EXIM_HAVE_OPENSSL_TLS_METHOD
+if (!(server_sni = SSL_CTX_new(TLS_server_method())))
+#else
 if (!(server_sni = SSL_CTX_new(SSLv23_server_method())))
 if (!(server_sni = SSL_CTX_new(SSLv23_server_method())))
+#endif
   {
   {
-  ERR_error_string(ERR_get_error(), ssl_errstring);
+  ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring));
   DEBUG(D_tls) debug_printf("SSL_CTX_new() failed: %s\n", ssl_errstring);
   DEBUG(D_tls) debug_printf("SSL_CTX_new() failed: %s\n", ssl_errstring);
-  return SSL_TLSEXT_ERR_NOACK;
+  goto bad;
   }
 
 /* Not sure how many of these are actually needed, since SSL object
   }
 
 /* Not sure how many of these are actually needed, since SSL object
@@ -781,8 +1420,16 @@ 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);
 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 (cbinfo->server_cipher_list)
-  SSL_CTX_set_cipher_list(server_sni, CS cbinfo->server_cipher_list);
+
+if (  !init_dh(server_sni, cbinfo->dhparam, NULL, &dummy_errstr)
+   || !init_ecdh(server_sni, NULL, &dummy_errstr)
+   )
+  goto bad;
+
+if (  cbinfo->server_cipher_list
+   && !SSL_CTX_set_cipher_list(server_sni, CS cbinfo->server_cipher_list))
+  goto bad;
+
 #ifndef DISABLE_OCSP
 if (cbinfo->u_ocsp.server.file)
   {
 #ifndef DISABLE_OCSP
 if (cbinfo->u_ocsp.server.file)
   {
@@ -791,21 +1438,20 @@ if (cbinfo->u_ocsp.server.file)
   }
 #endif
 
   }
 #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, &dummy_errstr)) != OK)
+  goto bad;
 
 /* do this after setup_certs, because this can require the certs for verifying
 OCSP information. */
 
 /* 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))
-  return SSL_TLSEXT_ERR_NOACK;
+if ((rc = tls_expand_session_files(server_sni, cbinfo, &dummy_errstr)) != OK)
+  goto bad;
 
 DEBUG(D_tls) debug_printf("Switching SSL context.\n");
 SSL_set_SSL_CTX(s, server_sni);
 
 DEBUG(D_tls) debug_printf("Switching SSL context.\n");
 SSL_set_SSL_CTX(s, server_sni);
-
 return SSL_TLSEXT_ERR_OK;
 return SSL_TLSEXT_ERR_OK;
+
+bad: return SSL_TLSEXT_ERR_ALERT_FATAL;
 }
 #endif /* EXIM_HAVE_OPENSSL_TLSEXT */
 
 }
 #endif /* EXIM_HAVE_OPENSSL_TLSEXT */
 
@@ -830,11 +1476,17 @@ static int
 tls_server_stapling_cb(SSL *s, void *arg)
 {
 const tls_ext_ctx_cb *cbinfo = (tls_ext_ctx_cb *) arg;
 tls_server_stapling_cb(SSL *s, void *arg)
 {
 const tls_ext_ctx_cb *cbinfo = (tls_ext_ctx_cb *) arg;
-uschar *response_der;
+uschar *response_der;  /*XXX blob */
 int response_der_len;
 
 int response_der_len;
 
+/*XXX stack: use SSL_get_certificate() to see which cert; from that work
+out which ocsp blob to send.  Unfortunately, SSL_get_certificate is known
+buggy in current OpenSSL; it returns the last cert loaded always rather than
+the one actually presented.  So we can't support a stack of OCSP proofs at
+this time. */
+
 DEBUG(D_tls)
 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;
     cbinfo->u_ocsp.server.response ? "have" : "lack");
 
 tls_in.ocsp = OCSP_NOT_RESP;
@@ -842,7 +1494,7 @@ if (!cbinfo->u_ocsp.server.response)
   return SSL_TLSEXT_ERR_NOACK;
 
 response_der = NULL;
   return SSL_TLSEXT_ERR_NOACK;
 
 response_der = NULL;
-response_der_len = i2d_OCSP_RESPONSE(cbinfo->u_ocsp.server.response,
+response_der_len = i2d_OCSP_RESPONSE(cbinfo->u_ocsp.server.response,   /*XXX stack*/
                      &response_der);
 if (response_der_len <= 0)
   return SSL_TLSEXT_ERR_NOACK;
                      &response_der);
 if (response_der_len <= 0)
   return SSL_TLSEXT_ERR_NOACK;
@@ -876,8 +1528,7 @@ len = SSL_get_tlsext_status_ocsp_resp(s, &p);
 if(!p)
  {
   /* Expect this when we requested ocsp but got none */
 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");
     log_write(0, LOG_MAIN, "Received TLS status callback, null content");
   else
     DEBUG(D_tls) debug_printf(" null\n");
@@ -887,8 +1538,8 @@ if(!p)
 if(!(rsp = d2i_OCSP_RESPONSE(NULL, &p, len)))
  {
   tls_out.ocsp = OCSP_FAILED;
 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;
   else
     DEBUG(D_tls) debug_printf(" parse error\n");
   return 0;
@@ -897,8 +1548,8 @@ if(!(rsp = d2i_OCSP_RESPONSE(NULL, &p, len)))
 if(!(bs = OCSP_response_get1_basic(rsp)))
   {
   tls_out.ocsp = OCSP_FAILED;
 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);
   else
     DEBUG(D_tls) debug_printf(" error parsing response\n");
   OCSP_RESPONSE_free(rsp);
@@ -917,36 +1568,50 @@ if(!(bs = OCSP_response_get1_basic(rsp)))
     int status, reason;
     ASN1_GENERALIZEDTIME *rev, *thisupd, *nextupd;
 
     int status, reason;
     ASN1_GENERALIZEDTIME *rev, *thisupd, *nextupd;
 
-    DEBUG(D_tls) bp = BIO_new_fp(stderr, BIO_NOCLOSE);
+    DEBUG(D_tls) bp = BIO_new_fp(debug_file, BIO_NOCLOSE);
 
     /*OCSP_RESPONSE_print(bp, rsp, 0);   extreme debug: stapling content */
 
     /* 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); */
 
 
     /*OCSP_RESPONSE_print(bp, rsp, 0);   extreme debug: stapling content */
 
     /* 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;
              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: %s",
+             ERR_reason_error_string(ERR_peek_error()));
       BIO_printf(bp, "OCSP response verify failure\n");
       ERR_print_errors(bp);
       BIO_printf(bp, "OCSP response verify failure\n");
       ERR_print_errors(bp);
-      i = cbinfo->u_ocsp.client.verify_required ? 0 : 1;
-      goto out;
+      OCSP_RESPONSE_print(bp, rsp, 0);
+      goto failed;
       }
 
     BIO_printf(bp, "OCSP response well-formed and signed OK\n");
 
       }
 
     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;
 
       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)
       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");
         {
        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,
         }
       single = OCSP_resp_get0(bs, 0);
       status = OCSP_single_get0_status(single, &reason, &rev,
@@ -961,7 +1626,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");
       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
       {
       }
     else
       {
@@ -972,24 +1636,24 @@ if(!(bs = OCSP_response_get1_basic(rsp)))
        case V_OCSP_CERTSTATUS_GOOD:
          tls_out.ocsp = OCSP_VFIED;
          i = 1;
        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);
        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");
          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;
        }
       }
          break;
        }
       }
-  out:
+  failed:
+    i = cbinfo->u_ocsp.client.verify_required ? 0 : 1;
+  good:
     BIO_free(bp);
   }
 
     BIO_free(bp);
   }
 
@@ -999,7 +1663,6 @@ return i;
 #endif /*!DISABLE_OCSP*/
 
 
 #endif /*!DISABLE_OCSP*/
 
 
-
 /*************************************************
 *            Initialize for TLS                  *
 *************************************************/
 /*************************************************
 *            Initialize for TLS                  *
 *************************************************/
@@ -1008,13 +1671,15 @@ return i;
 of the library.  We allocate and return a context structure.
 
 Arguments:
 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)
   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
+  errstr         error string pointer
 
 Returns:          OK/DEFER/FAIL
 */
 
 Returns:          OK/DEFER/FAIL
 */
@@ -1023,20 +1688,22 @@ static int
 tls_init(SSL_CTX **ctxp, host_item *host, uschar *dhparam, uschar *certificate,
   uschar *privatekey,
 #ifndef DISABLE_OCSP
 tls_init(SSL_CTX **ctxp, host_item *host, uschar *dhparam, uschar *certificate,
   uschar *privatekey,
 #ifndef DISABLE_OCSP
-  uschar *ocsp_file,
+  uschar *ocsp_file,   /*XXX stack, in server*/
 #endif
 #endif
-  address_item *addr, tls_ext_ctx_cb ** cbp)
+  address_item *addr, tls_ext_ctx_cb ** cbp, uschar ** errstr)
 {
 {
+SSL_CTX * ctx;
 long init_options;
 int rc;
 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;
 
 cbinfo = store_malloc(sizeof(tls_ext_ctx_cb));
 cbinfo->certificate = certificate;
 cbinfo->privatekey = privatekey;
+cbinfo->is_server = host==NULL;
 #ifndef DISABLE_OCSP
 #ifndef DISABLE_OCSP
-if ((cbinfo->is_server = host==NULL))
+cbinfo->verify_stack = NULL;
+if (!host)
   {
   cbinfo->u_ocsp.server.file = ocsp_file;
   cbinfo->u_ocsp.server.file_expanded = NULL;
   {
   cbinfo->u_ocsp.server.file = ocsp_file;
   cbinfo->u_ocsp.server.file_expanded = NULL;
@@ -1048,11 +1715,16 @@ else
 cbinfo->dhparam = dhparam;
 cbinfo->server_cipher_list = NULL;
 cbinfo->host = host;
 cbinfo->dhparam = dhparam;
 cbinfo->server_cipher_list = NULL;
 cbinfo->host = host;
+#ifndef DISABLE_EVENT
+cbinfo->event_action = NULL;
+#endif
 
 
+#ifdef EXIM_NEED_OPENSSL_INIT
 SSL_load_error_strings();          /* basic set up */
 OpenSSL_add_ssl_algorithms();
 SSL_load_error_strings();          /* basic set up */
 OpenSSL_add_ssl_algorithms();
+#endif
 
 
-#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());
 /* 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 +1738,12 @@ when OpenSSL is built without SSLv2 support.
 By disabling with openssl_options, we can let admins re-enable with the
 existing knob. */
 
 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());
-
-if (*ctxp == NULL) return tls_error(US"SSL_CTX_new", host, NULL);
+#ifdef EXIM_HAVE_OPENSSL_TLS_METHOD
+if (!(ctx = SSL_CTX_new(host ? TLS_client_method() : TLS_server_method())))
+#else
+if (!(ctx = SSL_CTX_new(host ? SSLv23_client_method() : SSLv23_server_method())))
+#endif
+  return tls_error(US"SSL_CTX_new", host, NULL, errstr);
 
 /* 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
 
 /* 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
@@ -1085,22 +1759,22 @@ if (!RAND_status())
   gettimeofday(&r.tv, NULL);
   r.p = getpid();
 
   gettimeofday(&r.tv, NULL);
   r.p = getpid();
 
-  RAND_seed((uschar *)(&r), sizeof(r));
-  RAND_seed((uschar *)big_buffer, big_buffer_size);
-  if (addr != NULL) RAND_seed((uschar *)addr, sizeof(addr));
+  RAND_seed(US (&r), sizeof(r));
+  RAND_seed(US big_buffer, big_buffer_size);
+  if (addr != NULL) RAND_seed(US addr, sizeof(addr));
 
   if (!RAND_status())
     return tls_error(US"RAND_status", host,
 
   if (!RAND_status())
     return tls_error(US"RAND_status", host,
-      US"unable to seed random number generator");
+      US"unable to seed random number generator", errstr);
   }
 
 /* Set up the information callback, which outputs if debugging is at a suitable
 level. */
 
   }
 
 /* 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(ctx, (void (*)())info_callback);
 
 /* Automatically re-try reads/writes after renegotiation. */
 
 /* Automatically re-try reads/writes after renegotiation. */
-(void) SSL_CTX_set_mode(*ctxp, SSL_MODE_AUTO_RETRY);
+(void) SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY);
 
 /* Apply administrator-supplied work-arounds.
 Historically we applied just one requested option,
 
 /* Apply administrator-supplied work-arounds.
 Historically we applied just one requested option,
@@ -1111,32 +1785,54 @@ grandfathered in the first one as the default value for "openssl_options".
 No OpenSSL version number checks: the options we accept depend upon the
 availability of the option value macros from OpenSSL.  */
 
 No OpenSSL version number checks: the options we accept depend upon the
 availability of the option value macros from OpenSSL.  */
 
-okay = tls_openssl_options_parse(openssl_options, &init_options);
-if (!okay)
-  return tls_error(US"openssl_options parsing failed", host, NULL);
+if (!tls_openssl_options_parse(openssl_options, &init_options))
+  return tls_error(US"openssl_options parsing failed", host, NULL, errstr);
 
 if (init_options)
   {
   DEBUG(D_tls) debug_printf("setting SSL CTX options: %#lx\n", init_options);
 
 if (init_options)
   {
   DEBUG(D_tls) debug_printf("setting SSL CTX options: %#lx\n", init_options);
-  if (!(SSL_CTX_set_options(*ctxp, init_options)))
+  if (!(SSL_CTX_set_options(ctx, init_options)))
     return tls_error(string_sprintf(
     return tls_error(string_sprintf(
-          "SSL_CTX_set_option(%#lx)", init_options), host, NULL);
+          "SSL_CTX_set_option(%#lx)", init_options), host, NULL, errstr);
   }
 else
   DEBUG(D_tls) debug_printf("no SSL CTX options to set\n");
 
   }
 else
   DEBUG(D_tls) debug_printf("no SSL CTX options to set\n");
 
+/* We'd like to disable session cache unconditionally, but foolish Outlook
+Express clients then give up the first TLS connection and make a second one
+(which works).  Only when there is an IMAP service on the same machine.
+Presumably OE is trying to use the cache for A on B.  Leave it enabled for
+now, until we work out a decent way of presenting control to the config.  It
+will never be used because we use a new context every time. */
+#ifdef notdef
+(void) SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF);
+#endif
+
 /* Initialize with DH parameters if supplied */
 /* Initialize with DH parameters if supplied */
+/* Initialize ECDH temp key parameter selection */
 
 
-if (!init_dh(*ctxp, dhparam, host)) return DEFER;
+if (  !init_dh(ctx, dhparam, host, errstr)
+   || !init_ecdh(ctx, host, errstr)
+   )
+  return DEFER;
 
 /* Set up certificate and key (and perhaps OCSP info) */
 
 
 /* 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(ctx, cbinfo, errstr)) != 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
 #ifdef EXIM_HAVE_OPENSSL_TLSEXT
-if (host == NULL)              /* server */
+# 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)             /* server */
   {
 # ifndef DISABLE_OCSP
   /* We check u_ocsp.server.file, not server.response, because we care about if
   {
 # ifndef DISABLE_OCSP
   /* We check u_ocsp.server.file, not server.response, because we care about if
@@ -1145,14 +1841,14 @@ if (host == NULL)               /* server */
   callback is invoked. */
   if (cbinfo->u_ocsp.server.file)
     {
   callback is invoked. */
   if (cbinfo->u_ocsp.server.file)
     {
-    SSL_CTX_set_tlsext_status_cb(server_ctx, tls_server_stapling_cb);
-    SSL_CTX_set_tlsext_status_arg(server_ctx, cbinfo);
+    SSL_CTX_set_tlsext_status_cb(ctx, tls_server_stapling_cb);
+    SSL_CTX_set_tlsext_status_arg(ctx, cbinfo);
     }
 # endif
   /* We always do this, so that $tls_sni is available even if not used in
   tls_certificate */
     }
 # endif
   /* We always do this, so that $tls_sni is available even if not used in
   tls_certificate */
-  SSL_CTX_set_tlsext_servername_callback(*ctxp, tls_servername_cb);
-  SSL_CTX_set_tlsext_servername_arg(*ctxp, cbinfo);
+  SSL_CTX_set_tlsext_servername_callback(ctx, tls_servername_cb);
+  SSL_CTX_set_tlsext_servername_arg(ctx, cbinfo);
   }
 # ifndef DISABLE_OCSP
 else                   /* client */
   }
 # ifndef DISABLE_OCSP
 else                   /* client */
@@ -1163,26 +1859,26 @@ else                    /* client */
       DEBUG(D_tls) debug_printf("failed to create store for stapling verify\n");
       return FAIL;
       }
       DEBUG(D_tls) debug_printf("failed to create store for stapling verify\n");
       return FAIL;
       }
-    SSL_CTX_set_tlsext_status_cb(*ctxp, tls_client_stapling_cb);
-    SSL_CTX_set_tlsext_status_arg(*ctxp, cbinfo);
+    SSL_CTX_set_tlsext_status_cb(ctx, tls_client_stapling_cb);
+    SSL_CTX_set_tlsext_status_arg(ctx, cbinfo);
     }
 # endif
 #endif
 
     }
 # endif
 #endif
 
-#ifdef EXPERIMENTAL_CERTNAMES
 cbinfo->verify_cert_hostnames = NULL;
 cbinfo->verify_cert_hostnames = NULL;
-#endif
 
 
+#ifdef EXIM_HAVE_EPHEM_RSA_KEX
 /* Set up the RSA callback */
 /* Set up the RSA callback */
-
-SSL_CTX_set_tmp_rsa_callback(*ctxp, rsa_callback);
+SSL_CTX_set_tmp_rsa_callback(ctx, rsa_callback);
+#endif
 
 /* Finally, set the timeout, and we are done */
 
 
 /* Finally, set the timeout, and we are done */
 
-SSL_CTX_set_timeout(*ctxp, ssl_session_timeout);
+SSL_CTX_set_timeout(ctx, ssl_session_timeout);
 DEBUG(D_tls) debug_printf("Initialized TLS\n");
 
 *cbp = cbinfo;
 DEBUG(D_tls) debug_printf("Initialized TLS\n");
 
 *cbp = cbinfo;
+*ctxp = ctx;
 
 return OK;
 }
 
 return OK;
 }
@@ -1205,15 +1901,13 @@ Returns:    nothing
 static void
 construct_cipher_name(SSL *ssl, uschar *cipherbuf, int bsize, int *bits)
 {
 static void
 construct_cipher_name(SSL *ssl, uschar *cipherbuf, int bsize, int *bits)
 {
-/* With OpenSSL 1.0.0a, this needs to be const but the documentation doesn't
+/* With OpenSSL 1.0.0a, 'c' needs to be const but the documentation doesn't
 yet reflect that.  It should be a safe change anyway, even 0.9.8 versions have
 the accessor functions use const in the prototype. */
 yet reflect that.  It should be a safe change anyway, even 0.9.8 versions have
 the accessor functions use const in the prototype. */
-const SSL_CIPHER *c;
-const uschar *ver;
 
 
-ver = (const uschar *)SSL_get_version(ssl);
+const uschar * ver = CUS SSL_get_version(ssl);
+const SSL_CIPHER * c = (const SSL_CIPHER *) SSL_get_current_cipher(ssl);
 
 
-c = (const SSL_CIPHER *) SSL_get_current_cipher(ssl);
 SSL_CIPHER_get_bits(c, bits);
 
 string_format(cipherbuf, bsize, "%s:%s:%u", ver,
 SSL_CIPHER_get_bits(c, bits);
 
 string_format(cipherbuf, bsize, "%s:%s:%u", ver,
@@ -1223,6 +1917,31 @@ DEBUG(D_tls) debug_printf("Cipher: %s\n", cipherbuf);
 }
 
 
 }
 
 
+static void
+peer_cert(SSL * ssl, tls_support * tlsp, uschar * peerdn, unsigned siz)
+{
+/*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. */
+
+tlsp->peerdn = NULL;
+
+/* 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)
+  if (!X509_NAME_oneline(X509_get_subject_name(tlsp->peercert), CS peerdn, siz))
+    { DEBUG(D_tls) debug_printf("X509_NAME_oneline() error\n"); }
+  else
+    {
+    peerdn[siz-1] = '\0';
+    tlsp->peerdn = peerdn;             /*XXX a static buffer... */
+    }
+}
+
+
 
 
 
 
 
 
@@ -1230,7 +1949,30 @@ DEBUG(D_tls) debug_printf("Cipher: %s\n", cipherbuf);
 *        Set up for verifying certificates       *
 *************************************************/
 
 *        Set up for verifying certificates       *
 *************************************************/
 
-/* Called by both client and server startup
+#ifndef DISABLE_OCSP
+/* 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;
+
+while (sk_X509_num(verify_stack) > 0)
+  X509_free(sk_X509_pop(verify_stack));
+
+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;
+}
+#endif
+
+
+
+/* Called by both client and server startup; on the server possibly
+repeated after a Server Name Indication.
 
 Arguments:
   sctx          SSL_CTX* to initialise
 
 Arguments:
   sctx          SSL_CTX* to initialise
@@ -1240,70 +1982,111 @@ Arguments:
   optional      TRUE if called from a server for a host in tls_try_verify_hosts;
                 otherwise passed as FALSE
   cert_vfy_cb  Callback function for certificate verification
   optional      TRUE if called from a server for a host in tls_try_verify_hosts;
                 otherwise passed as FALSE
   cert_vfy_cb  Callback function for certificate verification
+  errstr       error string pointer
 
 Returns:        OK/DEFER/FAIL
 */
 
 static int
 setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional,
 
 Returns:        OK/DEFER/FAIL
 */
 
 static int
 setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional,
-    int (*cert_vfy_cb)(int, X509_STORE_CTX *) )
+    int (*cert_vfy_cb)(int, X509_STORE_CTX *), uschar ** errstr)
 {
 uschar *expcerts, *expcrl;
 
 {
 uschar *expcerts, *expcrl;
 
-if (!expand_check(certs, US"tls_verify_certificates", &expcerts))
+if (!expand_check(certs, US"tls_verify_certificates", &expcerts, errstr))
   return DEFER;
   return DEFER;
+DEBUG(D_tls) debug_printf("tls_verify_certificates: %s\n", expcerts);
 
 
-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))
   if (!SSL_CTX_set_default_verify_paths(sctx))
-    return tls_error(US"SSL_CTX_set_default_verify_paths", host, NULL);
+    return tls_error(US"SSL_CTX_set_default_verify_paths", host, NULL, errstr);
 
 
-  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 chain 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, errstr);
+
+      /* 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));
+       SSL_CTX_set_client_CA_list(sctx, names);
+       DEBUG(D_tls) debug_printf("Added %d certificate authorities.\n",
+                                   sk_X509_NAME_num(names));
+       }
       }
     }
 
   /* Handle a certificate revocation list. */
 
       }
     }
 
   /* 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
 
   /* 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 (!expand_check(crl, US"tls_crl", &expcrl, errstr)) return DEFER;
+  if (expcrl && *expcrl)
     {
     struct stat statbufcrl;
     if (Ustat(expcrl, &statbufcrl) < 0)
     {
     struct stat statbufcrl;
     if (Ustat(expcrl, &statbufcrl) < 0)
@@ -1330,7 +2113,7 @@ if (expcerts != NULL && *expcerts != '\0')
         DEBUG(D_tls) debug_printf("SSL CRL value is a file %s\n", file);
         }
       if (X509_STORE_load_locations(cvstore, CS file, CS dir) == 0)
         DEBUG(D_tls) debug_printf("SSL CRL value is a file %s\n", file);
         }
       if (X509_STORE_load_locations(cvstore, CS file, CS dir) == 0)
-        return tls_error(US"X509_STORE_load_locations", host, NULL);
+        return tls_error(US"X509_STORE_load_locations", host, NULL, errstr);
 
       /* setting the flags to check against the complete crl chain */
 
 
       /* setting the flags to check against the complete crl chain */
 
@@ -1339,12 +2122,12 @@ 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 */
 
   SSL_CTX_set_verify(sctx,
 
   /* If verification is optional, don't fail if no certificate */
 
   SSL_CTX_set_verify(sctx,
-    SSL_VERIFY_PEER | (optional? 0 : SSL_VERIFY_FAIL_IF_NO_PEER_CERT),
+    SSL_VERIFY_PEER | (optional ? 0 : SSL_VERIFY_FAIL_IF_NO_PEER_CERT),
     cert_vfy_cb);
   }
 
     cert_vfy_cb);
   }
 
@@ -1363,27 +2146,29 @@ a TLS session.
 
 Arguments:
   require_ciphers   allowed ciphers
 
 Arguments:
   require_ciphers   allowed ciphers
+  errstr           pointer to error message
 
 Returns:            OK on success
                     DEFER for errors before the start of the negotiation
 
 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.
 */
 
 int
                       continue running.
 */
 
 int
-tls_server_start(const uschar *require_ciphers)
+tls_server_start(const uschar * require_ciphers, uschar ** errstr)
 {
 int rc;
 {
 int rc;
-uschar *expciphers;
-tls_ext_ctx_cb *cbinfo;
+uschar * expciphers;
+tls_ext_ctx_cb * cbinfo;
+static uschar peerdn[256];
 static uschar cipherbuf[256];
 
 /* Check for previous activation */
 
 static uschar cipherbuf[256];
 
 /* Check for previous activation */
 
-if (tls_in.active >= 0)
+if (tls_in.active.sock >= 0)
   {
   {
-  tls_error(US"STARTTLS received after TLS started", NULL, US"");
-  smtp_printf("554 Already in TLS\r\n");
+  tls_error(US"STARTTLS received after TLS started", NULL, US"", errstr);
+  smtp_printf("554 Already in TLS\r\n", FALSE);
   return FAIL;
   }
 
   return FAIL;
   }
 
@@ -1392,27 +2177,31 @@ the error. */
 
 rc = tls_init(&server_ctx, NULL, tls_dhparam, tls_certificate, tls_privatekey,
 #ifndef DISABLE_OCSP
 
 rc = tls_init(&server_ctx, NULL, tls_dhparam, tls_certificate, tls_privatekey,
 #ifndef DISABLE_OCSP
-    tls_ocsp_file,
+    tls_ocsp_file,     /*XXX stack*/
 #endif
 #endif
-    NULL, &server_static_cbinfo);
+    NULL, &server_static_cbinfo, errstr);
 if (rc != OK) return rc;
 cbinfo = server_static_cbinfo;
 
 if (rc != OK) return rc;
 cbinfo = server_static_cbinfo;
 
-if (!expand_check(require_ciphers, US"tls_require_ciphers", &expciphers))
+if (!expand_check(require_ciphers, US"tls_require_ciphers", &expciphers, errstr))
   return FAIL;
 
 /* In OpenSSL, cipher components are separated by hyphens. In GnuTLS, they
 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.
   return FAIL;
 
 /* In OpenSSL, cipher components are separated by hyphens. In GnuTLS, they
 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.
+
+XXX SSL_CTX_set_cipher_list() is replaced by SSL_CTX_set_ciphersuites()
+for TLS 1.3 .  Since we do not call it at present we get the default list:
+TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256
 */
 
 */
 
-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))
   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))
-    return tls_error(US"SSL_CTX_set_cipher_list", NULL, NULL);
+    return tls_error(US"SSL_CTX_set_cipher_list", NULL, NULL, errstr);
   cbinfo->server_cipher_list = expciphers;
   }
 
   cbinfo->server_cipher_list = expciphers;
   }
 
@@ -1420,26 +2209,30 @@ if (expciphers != NULL)
 optional, set up appropriately. */
 
 tls_in.certificate_verified = FALSE;
 optional, set up appropriately. */
 
 tls_in.certificate_verified = FALSE;
+#ifdef SUPPORT_DANE
+tls_in.dane_verified = FALSE;
+#endif
 server_verify_callback_called = FALSE;
 
 if (verify_check_host(&tls_verify_hosts) == OK)
   {
   rc = setup_certs(server_ctx, tls_verify_certificates, tls_crl, NULL,
 server_verify_callback_called = FALSE;
 
 if (verify_check_host(&tls_verify_hosts) == OK)
   {
   rc = setup_certs(server_ctx, tls_verify_certificates, tls_crl, NULL,
-                       FALSE, verify_callback_server);
+                       FALSE, verify_callback_server, errstr);
   if (rc != OK) return rc;
   server_verify_optional = FALSE;
   }
 else if (verify_check_host(&tls_try_verify_hosts) == OK)
   {
   rc = setup_certs(server_ctx, tls_verify_certificates, tls_crl, NULL,
   if (rc != OK) return rc;
   server_verify_optional = FALSE;
   }
 else if (verify_check_host(&tls_try_verify_hosts) == OK)
   {
   rc = setup_certs(server_ctx, tls_verify_certificates, tls_crl, NULL,
-                       TRUE, verify_callback_server);
+                       TRUE, verify_callback_server, errstr);
   if (rc != OK) return rc;
   server_verify_optional = TRUE;
   }
 
 /* Prepare for new connection */
 
   if (rc != OK) return rc;
   server_verify_optional = TRUE;
   }
 
 /* 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, errstr);
 
 /* Warning: we used to SSL_clear(ssl) here, it was removed.
  *
 
 /* Warning: we used to SSL_clear(ssl) here, it was removed.
  *
@@ -1463,7 +2256,7 @@ mode, the fflush() happens when smtp_getc() is called. */
 SSL_set_session_id_context(server_ssl, sid_ctx, Ustrlen(sid_ctx));
 if (!tls_in.on_connect)
   {
 SSL_set_session_id_context(server_ssl, sid_ctx, Ustrlen(sid_ctx));
 if (!tls_in.on_connect)
   {
-  smtp_printf("220 TLS go ahead\r\n");
+  smtp_printf("220 TLS go ahead\r\n", FALSE);
   fflush(smtp_out);
   }
 
   fflush(smtp_out);
   }
 
@@ -1477,24 +2270,25 @@ SSL_set_accept_state(server_ssl);
 DEBUG(D_tls) debug_printf("Calling SSL_accept\n");
 
 sigalrm_seen = FALSE;
 DEBUG(D_tls) debug_printf("Calling SSL_accept\n");
 
 sigalrm_seen = FALSE;
-if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
+if (smtp_receive_timeout > 0) ALARM(smtp_receive_timeout);
 rc = SSL_accept(server_ssl);
 rc = SSL_accept(server_ssl);
-alarm(0);
+ALARM_CLR(0);
 
 if (rc <= 0)
   {
 
 if (rc <= 0)
   {
-  tls_error(US"SSL_accept", NULL, sigalrm_seen ? US"timed out" : NULL);
-  if (ERR_get_error() == 0)
-    log_write(0, LOG_MAIN,
-        "TLS client disconnected cleanly (rejected our certificate?)");
+  (void) tls_error(US"SSL_accept", NULL, sigalrm_seen ? US"timed out" : NULL, errstr);
   return FAIL;
   }
 
 DEBUG(D_tls) debug_printf("SSL_accept was successful\n");
   return FAIL;
   }
 
 DEBUG(D_tls) debug_printf("SSL_accept was successful\n");
+ERR_clear_error();     /* Even success can leave errors in the stack. Seen with
+                       anon-authentication ciphersuite negociated. */
 
 /* TLS has been set up. Adjust the input functions to read via TLS,
 and initialize things. */
 
 
 /* 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;
 
 construct_cipher_name(server_ssl, cipherbuf, sizeof(cipherbuf), &tls_in.bits);
 tls_in.cipher = cipherbuf;
 
@@ -1516,21 +2310,123 @@ DEBUG(D_tls)
    smtp_read_response()/ip_recv().
    Hence no need to duplicate for _in and _out.
  */
    smtp_read_response()/ip_recv().
    Hence no need to duplicate for _in and _out.
  */
-ssl_xfer_buffer = store_malloc(ssl_xfer_buffer_size);
+if (!ssl_xfer_buffer) ssl_xfer_buffer = store_malloc(ssl_xfer_buffer_size);
 ssl_xfer_buffer_lwm = ssl_xfer_buffer_hwm = 0;
 ssl_xfer_buffer_lwm = ssl_xfer_buffer_hwm = 0;
-ssl_xfer_eof = ssl_xfer_error = 0;
+ssl_xfer_eof = ssl_xfer_error = FALSE;
 
 receive_getc = tls_getc;
 
 receive_getc = tls_getc;
+receive_getbuf = tls_getbuf;
+receive_get_cache = tls_get_cache;
 receive_ungetc = tls_ungetc;
 receive_feof = tls_feof;
 receive_ferror = tls_ferror;
 receive_smtp_buffered = tls_smtp_buffered;
 
 receive_ungetc = tls_ungetc;
 receive_feof = tls_feof;
 receive_ferror = tls_ferror;
 receive_smtp_buffered = tls_smtp_buffered;
 
-tls_in.active = fileno(smtp_out);
+tls_in.active.sock = fileno(smtp_out);
+tls_in.active.tls_ctx = NULL;  /* not using explicit ctx for server-side */
+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,
+    uschar ** errstr)
+{
+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(CUSS &ob->tls_verify_hosts, host) == OK
+   )
+  client_verify_optional = FALSE;
+else if (verify_check_given_host(CUSS &ob->tls_try_verify_hosts, host) == OK)
+  client_verify_optional = TRUE;
+else
+  return OK;
+
+if ((rc = setup_certs(ctx, ob->tls_verify_certificates,
+      ob->tls_crl, host, client_verify_optional, verify_callback_client,
+      errstr)) != OK)
+  return rc;
+
+if (verify_check_given_host(CUSS &ob->tls_verify_cert_hostnames, host) == OK)
+  {
+  cbinfo->verify_cert_hostnames =
+#ifdef SUPPORT_I18N
+    string_domain_utf8_to_alabel(host->name, NULL);
+#else
+    host->name;
+#endif
+  DEBUG(D_tls) debug_printf("Cert hostname to check: \"%s\"\n",
+                   cbinfo->verify_cert_hostnames);
+  }
 return OK;
 }
 
 
 return OK;
 }
 
 
+#ifdef SUPPORT_DANE
+static int
+dane_tlsa_load(SSL * ssl, host_item * host, dns_answer * dnsa, uschar ** errstr)
+{
+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, errstr);
+
+for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS);
+     rr;
+     rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)
+    ) if (rr->type == T_TLSA && rr->size > 3)
+  {
+  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, errstr);
+    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 /*SUPPORT_DANE*/
 
 
 
 
 
 
@@ -1542,259 +2438,409 @@ return OK;
 
 Argument:
   fd               the fd of the connection
 
 Argument:
   fd               the fd of the connection
-  host             connected host (for messages)
-  addr             the first address
-  ob               smtp transport options
-
-Returns:           OK on success
-                   FAIL otherwise - note that tls_error() will not give DEFER
-                     because this is not a server
+  host             connected host (for messages and option-tests)
+  addr             the first address (for some randomness; can be NULL)
+  tb               transport (always smtp)
+  tlsa_dnsa        tlsa lookup, if DANE, else null
+  tlsp            record details of channel configuration here; must be non-NULL
+  errstr          error string pointer
+
+Returns:           Pointer to TLS session context, or NULL on error
 */
 
 */
 
-int
+void *
 tls_client_start(int fd, host_item *host, address_item *addr,
 tls_client_start(int fd, host_item *host, address_item *addr,
-  void *v_ob)
+  transport_instance * tb,
+#ifdef SUPPORT_DANE
+  dns_answer * tlsa_dnsa,
+#endif
+  tls_support * tlsp, uschar ** errstr)
 {
 {
-smtp_transport_options_block * ob = v_ob;
-static uschar txt[256];
-uschar *expciphers;
-X509* server_cert;
+smtp_transport_options_block * ob = tb
+  ? (smtp_transport_options_block *)tb->options_block
+  : &smtp_transport_option_defaults;
+exim_openssl_client_tls_ctx * exim_client_ctx;
+static uschar peerdn[256];
+uschar * expciphers;
 int rc;
 static uschar cipherbuf[256];
 int rc;
 static uschar cipherbuf[256];
+
 #ifndef DISABLE_OCSP
 #ifndef DISABLE_OCSP
-BOOL require_ocsp = verify_check_this_host(&ob->hosts_require_ocsp,
-  NULL, host->name, host->address, NULL) == OK;
-BOOL request_ocsp = require_ocsp ? TRUE
-  : verify_check_this_host(&ob->hosts_request_ocsp,
-      NULL, host->name, host->address, NULL) == OK;
+BOOL request_ocsp = FALSE;
+BOOL require_ocsp = FALSE;
+#endif
+
+rc = store_pool;
+store_pool = POOL_PERM;
+exim_client_ctx = store_get(sizeof(exim_openssl_client_tls_ctx));
+store_pool = rc;
+
+#ifdef SUPPORT_DANE
+tlsp->tlsa_usage = 0;
 #endif
 
 #endif
 
-rc = tls_init(&client_ctx, host, NULL,
+#ifndef DISABLE_OCSP
+  {
+# ifdef SUPPORT_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(CUSS &ob->hosts_require_ocsp, host) == OK))
+    request_ocsp = TRUE;
+  else
+# ifdef SUPPORT_DANE
+    if (!request_ocsp)
+# endif
+      request_ocsp =
+       verify_check_given_host(CUSS &ob->hosts_request_ocsp, host) == OK;
+  }
+#endif
+
+rc = tls_init(&exim_client_ctx->ctx, host, NULL,
     ob->tls_certificate, ob->tls_privatekey,
 #ifndef DISABLE_OCSP
     (void *)(long)request_ocsp,
 #endif
     ob->tls_certificate, ob->tls_privatekey,
 #ifndef DISABLE_OCSP
     (void *)(long)request_ocsp,
 #endif
-    addr, &client_static_cbinfo);
-if (rc != OK) return rc;
+    addr, &client_static_cbinfo, errstr);
+if (rc != OK) return NULL;
 
 
-tls_out.certificate_verified = FALSE;
+tlsp->certificate_verified = FALSE;
 client_verify_callback_called = FALSE;
 
 client_verify_callback_called = FALSE;
 
-if (!expand_check(ob->tls_require_ciphers, US"tls_require_ciphers",
-    &expciphers))
-  return FAIL;
+expciphers = NULL;
+#ifdef SUPPORT_DANE
+if (tlsa_dnsa)
+  {
+  /* We fall back to tls_require_ciphers if unset, empty or forced failure, but
+  other failures should be treated as problems. */
+  if (ob->dane_require_tls_ciphers &&
+      !expand_check(ob->dane_require_tls_ciphers, US"dane_require_tls_ciphers",
+        &expciphers, errstr))
+    return NULL;
+  if (expciphers && *expciphers == '\0')
+    expciphers = NULL;
+  }
+#endif
+if (!expciphers &&
+    !expand_check(ob->tls_require_ciphers, US"tls_require_ciphers",
+      &expciphers, errstr))
+  return NULL;
 
 /* In OpenSSL, cipher components are separated by hyphens. In GnuTLS, they
 are separated by underscores. So that I can use either form in my tests, and
 also for general convenience, we turn underscores into hyphens here. */
 
 
 /* In OpenSSL, cipher components are separated by hyphens. In GnuTLS, they
 are 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++; }
+  while (*s) { if (*s == '_') *s = '-'; s++; }
   DEBUG(D_tls) debug_printf("required ciphers: %s\n", expciphers);
   DEBUG(D_tls) debug_printf("required ciphers: %s\n", expciphers);
-  if (!SSL_CTX_set_cipher_list(client_ctx, CS expciphers))
-    return tls_error(US"SSL_CTX_set_cipher_list", host, NULL);
+  if (!SSL_CTX_set_cipher_list(exim_client_ctx->ctx, CS expciphers))
+    {
+    tls_error(US"SSL_CTX_set_cipher_list", host, NULL, errstr);
+    return 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 SUPPORT_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(exim_client_ctx->ctx,
+    SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
+    verify_callback_client_dane);
 
 
-#ifdef EXPERIMENTAL_CERTNAMES
-  if (ob->tls_verify_cert_hostnames)
+  if (!DANESSL_library_init())
     {
     {
-    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);
+    tls_error(US"library init", host, NULL, errstr);
+    return NULL;
+    }
+  if (DANESSL_CTX_init(exim_client_ctx->ctx) <= 0)
+    {
+    tls_error(US"context init", host, NULL, errstr);
+    return NULL;
     }
     }
-#endif
   }
   }
-else if (verify_check_host(&ob->tls_try_verify_hosts) == OK)
+else
+
+#endif
+
+  if (tls_client_basic_ctx_init(exim_client_ctx->ctx, host, ob,
+       client_static_cbinfo, errstr) != OK)
+    return NULL;
+
+if (!(exim_client_ctx->ssl = SSL_new(exim_client_ctx->ctx)))
   {
   {
-  if ((rc = setup_certs(client_ctx, ob->tls_verify_certificates,
-       ob->tls_crl, host, TRUE, verify_callback_client)) != OK)
-    return rc;
-  client_verify_optional = TRUE;
+  tls_error(US"SSL_new", host, NULL, errstr);
+  return NULL;
   }
   }
-
-if ((client_ssl = SSL_new(client_ctx)) == NULL)
-  return tls_error(US"SSL_new", host, NULL);
-SSL_set_session_id_context(client_ssl, sid_ctx, Ustrlen(sid_ctx));
-SSL_set_fd(client_ssl, fd);
-SSL_set_connect_state(client_ssl);
+SSL_set_session_id_context(exim_client_ctx->ssl, sid_ctx, Ustrlen(sid_ctx));
+SSL_set_fd(exim_client_ctx->ssl, fd);
+SSL_set_connect_state(exim_client_ctx->ssl);
 
 if (ob->tls_sni)
   {
 
 if (ob->tls_sni)
   {
-  if (!expand_check(ob->tls_sni, US"tls_sni", &tls_out.sni))
-    return FAIL;
-  if (tls_out.sni == NULL)
+  if (!expand_check(ob->tls_sni, US"tls_sni", &tlsp->sni, errstr))
+    return NULL;
+  if (!tlsp->sni)
     {
     DEBUG(D_tls) debug_printf("Setting TLS SNI forced to fail, not sending\n");
     }
     {
     DEBUG(D_tls) debug_printf("Setting TLS SNI forced to fail, not sending\n");
     }
-  else if (!Ustrlen(tls_out.sni))
-    tls_out.sni = NULL;
+  else if (!Ustrlen(tlsp->sni))
+    tlsp->sni = NULL;
   else
     {
 #ifdef EXIM_HAVE_OPENSSL_TLSEXT
   else
     {
 #ifdef EXIM_HAVE_OPENSSL_TLSEXT
-    DEBUG(D_tls) debug_printf("Setting TLS SNI \"%s\"\n", tls_out.sni);
-    SSL_set_tlsext_host_name(client_ssl, tls_out.sni);
+    DEBUG(D_tls) debug_printf("Setting TLS SNI \"%s\"\n", tlsp->sni);
+    SSL_set_tlsext_host_name(exim_client_ctx->ssl, tlsp->sni);
 #else
 #else
-    DEBUG(D_tls)
-      debug_printf("OpenSSL at build-time lacked SNI support, ignoring \"%s\"\n",
-          tls_out.sni);
+    log_write(0, LOG_MAIN, "SNI unusable with this OpenSSL library version; ignoring \"%s\"\n",
+          tlsp->sni);
 #endif
     }
   }
 
 #endif
     }
   }
 
+#ifdef SUPPORT_DANE
+if (tlsa_dnsa)
+  if (dane_tlsa_load(exim_client_ctx->ssl, host, tlsa_dnsa, errstr) != OK)
+    return NULL;
+#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()) */
 #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 SUPPORT_DANE
 if (request_ocsp)
   {
 if (request_ocsp)
   {
-  SSL_set_tlsext_status_type(client_ssl, TLSEXT_STATUSTYPE_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(CUSS &ob->hosts_require_ocsp, host) == OK;
+    request_ocsp = require_ocsp
+      || verify_check_given_host(CUSS &ob->hosts_request_ocsp, host) == OK;
+    }
+  }
+# endif
+
+if (request_ocsp)
+  {
+  SSL_set_tlsext_status_type(exim_client_ctx->ssl, TLSEXT_STATUSTYPE_ocsp);
   client_static_cbinfo->u_ocsp.client.verify_required = require_ocsp;
   client_static_cbinfo->u_ocsp.client.verify_required = require_ocsp;
-  tls_out.ocsp = OCSP_NOT_RESP;
+  tlsp->ocsp = OCSP_NOT_RESP;
   }
 #endif
 
   }
 #endif
 
+#ifndef DISABLE_EVENT
+client_static_cbinfo->event_action = tb ? tb->event_action : NULL;
+#endif
+
 /* There doesn't seem to be a built-in timeout on connection. */
 
 DEBUG(D_tls) debug_printf("Calling SSL_connect\n");
 sigalrm_seen = FALSE;
 /* There doesn't seem to be a built-in timeout on connection. */
 
 DEBUG(D_tls) debug_printf("Calling SSL_connect\n");
 sigalrm_seen = FALSE;
-alarm(ob->command_timeout);
-rc = SSL_connect(client_ssl);
-alarm(0);
+ALARM(ob->command_timeout);
+rc = SSL_connect(exim_client_ctx->ssl);
+ALARM_CLR(0);
+
+#ifdef SUPPORT_DANE
+if (tlsa_dnsa)
+  DANESSL_cleanup(exim_client_ctx->ssl);
+#endif
 
 if (rc <= 0)
 
 if (rc <= 0)
-  return tls_error(US"SSL_connect", host, sigalrm_seen ? US"timed out" : NULL);
+  {
+  tls_error(US"SSL_connect", host, sigalrm_seen ? US"timed out" : NULL, errstr);
+  return NULL;
+  }
 
 DEBUG(D_tls) debug_printf("SSL_connect succeeded\n");
 
 
 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(exim_client_ctx->ssl, tlsp, peerdn, sizeof(peerdn));
 
 
-construct_cipher_name(client_ssl, cipherbuf, sizeof(cipherbuf), &tls_out.bits);
-tls_out.cipher = cipherbuf;
+construct_cipher_name(exim_client_ctx->ssl, cipherbuf, sizeof(cipherbuf), &tlsp->bits);
+tlsp->cipher = cipherbuf;
 
 /* Record the certificate we presented */
   {
 
 /* Record the certificate we presented */
   {
-  X509 * crt = SSL_get_certificate(client_ssl);
-  tls_out.ourcert = crt ? X509_dup(crt) : NULL;
+  X509 * crt = SSL_get_certificate(exim_client_ctx->ssl);
+  tlsp->ourcert = crt ? X509_dup(crt) : NULL;
   }
 
   }
 
-tls_out.active = fd;
-return OK;
+tlsp->active.sock = fd;
+tlsp->active.tls_ctx = exim_client_ctx;
+return exim_client_ctx;
 }
 
 
 
 
 
 }
 
 
 
 
 
-/*************************************************
-*            TLS version of getc                 *
-*************************************************/
-
-/* 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
-Returns:    the next character or EOF
-
-Only used by the server-side TLS.
-*/
-
-int
-tls_getc(void)
+static BOOL
+tls_refill(unsigned lim)
 {
 {
-if (ssl_xfer_buffer_lwm >= ssl_xfer_buffer_hwm)
-  {
-  int error;
-  int inbytes;
-
-  DEBUG(D_tls) debug_printf("Calling SSL_read(%p, %p, %u)\n", server_ssl,
-    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);
-  error = SSL_get_error(server_ssl, inbytes);
-  alarm(0);
+int error;
+int inbytes;
 
 
-  /* SSL_ERROR_ZERO_RETURN appears to mean that the SSL session has been
-  closed down, not that the socket itself has been closed down. Revert to
-  non-SSL handling. */
+DEBUG(D_tls) debug_printf("Calling SSL_read(%p, %p, %u)\n", server_ssl,
+  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,
+                 MIN(ssl_xfer_buffer_size, lim));
+error = SSL_get_error(server_ssl, inbytes);
+if (smtp_receive_timeout > 0) ALARM_CLR(0);
+
+if (had_command_timeout)               /* set by signal handler */
+  smtp_command_timeout_exit();         /* does not return */
+if (had_command_sigterm)
+  smtp_command_sigterm_exit();
+if (had_data_timeout)
+  smtp_data_timeout_exit();
+if (had_data_sigint)
+  smtp_data_sigint_exit();
+
+/* SSL_ERROR_ZERO_RETURN appears to mean that the SSL session has been
+closed down, not that the socket itself has been closed down. Revert to
+non-SSL handling. */
+
+switch(error)
+  {
+  case SSL_ERROR_NONE:
+    break;
 
 
-  if (error == SSL_ERROR_ZERO_RETURN)
-    {
+  case SSL_ERROR_ZERO_RETURN:
     DEBUG(D_tls) debug_printf("Got SSL_ERROR_ZERO_RETURN\n");
 
     receive_getc = smtp_getc;
     DEBUG(D_tls) debug_printf("Got SSL_ERROR_ZERO_RETURN\n");
 
     receive_getc = smtp_getc;
+    receive_getbuf = smtp_getbuf;
+    receive_get_cache = smtp_get_cache;
     receive_ungetc = smtp_ungetc;
     receive_feof = smtp_feof;
     receive_ferror = smtp_ferror;
     receive_smtp_buffered = smtp_buffered;
 
     receive_ungetc = smtp_ungetc;
     receive_feof = smtp_feof;
     receive_ferror = smtp_ferror;
     receive_smtp_buffered = smtp_buffered;
 
+    if (SSL_get_shutdown(server_ssl) == SSL_RECEIVED_SHUTDOWN)
+         SSL_shutdown(server_ssl);
+
+#ifndef DISABLE_OCSP
+    sk_X509_pop_free(server_static_cbinfo->verify_stack, X509_free);
+    server_static_cbinfo->verify_stack = NULL;
+#endif
     SSL_free(server_ssl);
     SSL_free(server_ssl);
+    SSL_CTX_free(server_ctx);
+    server_ctx = NULL;
     server_ssl = NULL;
     server_ssl = NULL;
-    tls_in.active = -1;
+    tls_in.active.sock = -1;
+    tls_in.active.tls_ctx = NULL;
     tls_in.bits = 0;
     tls_in.cipher = NULL;
     tls_in.peerdn = NULL;
     tls_in.sni = NULL;
 
     tls_in.bits = 0;
     tls_in.cipher = NULL;
     tls_in.peerdn = NULL;
     tls_in.sni = NULL;
 
-    return smtp_getc();
-    }
+    return FALSE;
 
   /* Handle genuine errors */
 
   /* Handle genuine errors */
-
-  else if (error == SSL_ERROR_SSL)
-    {
-    ERR_error_string(ERR_get_error(), ssl_errstring);
+  case SSL_ERROR_SSL:
+    ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring));
     log_write(0, LOG_MAIN, "TLS error (SSL_read): %s", ssl_errstring);
     log_write(0, LOG_MAIN, "TLS error (SSL_read): %s", ssl_errstring);
-    ssl_xfer_error = 1;
-    return EOF;
-    }
+    ssl_xfer_error = TRUE;
+    return FALSE;
 
 
-  else if (error != SSL_ERROR_NONE)
-    {
+  default:
     DEBUG(D_tls) debug_printf("Got SSL error %d\n", error);
     DEBUG(D_tls) debug_printf("Got SSL error %d\n", error);
-    ssl_xfer_error = 1;
-    return EOF;
-    }
+    DEBUG(D_tls) if (error == SSL_ERROR_SYSCALL)
+      debug_printf(" - syscall %s\n", strerror(errno));
+    ssl_xfer_error = TRUE;
+    return FALSE;
+  }
 
 #ifndef DISABLE_DKIM
 
 #ifndef DISABLE_DKIM
-  dkim_exim_verify_feed(ssl_xfer_buffer, inbytes);
+dkim_exim_verify_feed(ssl_xfer_buffer, inbytes);
 #endif
 #endif
-  ssl_xfer_buffer_hwm = inbytes;
-  ssl_xfer_buffer_lwm = 0;
-  }
+ssl_xfer_buffer_hwm = inbytes;
+ssl_xfer_buffer_lwm = 0;
+return TRUE;
+}
+
+
+/*************************************************
+*            TLS version of getc                 *
+*************************************************/
+
+/* 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:  lim                Maximum amount to read/buffer
+Returns:    the next character or EOF
+
+Only used by the server-side TLS.
+*/
+
+int
+tls_getc(unsigned lim)
+{
+if (ssl_xfer_buffer_lwm >= ssl_xfer_buffer_hwm)
+  if (!tls_refill(lim))
+    return ssl_xfer_error ? EOF : smtp_getc(lim);
 
 /* Something in the buffer; return next uschar */
 
 return ssl_xfer_buffer[ssl_xfer_buffer_lwm++];
 }
 
 
 /* Something in the buffer; return next uschar */
 
 return ssl_xfer_buffer[ssl_xfer_buffer_lwm++];
 }
 
+uschar *
+tls_getbuf(unsigned * len)
+{
+unsigned size;
+uschar * buf;
+
+if (ssl_xfer_buffer_lwm >= ssl_xfer_buffer_hwm)
+  if (!tls_refill(*len))
+    {
+    if (!ssl_xfer_error) return smtp_getbuf(len);
+    *len = 0;
+    return NULL;
+    }
+
+if ((size = ssl_xfer_buffer_hwm - ssl_xfer_buffer_lwm) > *len)
+  size = *len;
+buf = &ssl_xfer_buffer[ssl_xfer_buffer_lwm];
+ssl_xfer_buffer_lwm += size;
+*len = size;
+return buf;
+}
+
+
+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
+}
+
+
+BOOL
+tls_could_read(void)
+{
+return ssl_xfer_buffer_lwm < ssl_xfer_buffer_hwm || SSL_pending(server_ssl) > 0;
+}
 
 
 /*************************************************
 
 
 /*************************************************
@@ -1803,19 +2849,20 @@ return ssl_xfer_buffer[ssl_xfer_buffer_lwm++];
 
 /*
 Arguments:
 
 /*
 Arguments:
+  ct_ctx    client context pointer, or NULL for the one global server context
   buff      buffer of data
   len       size of buffer
 
 Returns:    the number of bytes read
   buff      buffer of data
   len       size of buffer
 
 Returns:    the number of bytes read
-            -1 after a failed read
+            -1 after a failed read, including EOF
 
 Only used by the client-side TLS.
 */
 
 int
 
 Only used by the client-side TLS.
 */
 
 int
-tls_read(BOOL is_server, uschar *buff, size_t len)
+tls_read(void * ct_ctx, uschar *buff, size_t len)
 {
 {
-SSL *ssl = is_server ? server_ssl : client_ssl;
+SSL * ssl = ct_ctx ? ((exim_openssl_client_tls_ctx *)ct_ctx)->ssl : server_ssl;
 int inbytes;
 int error;
 
 int inbytes;
 int error;
 
@@ -1831,9 +2878,7 @@ if (error == SSL_ERROR_ZERO_RETURN)
   return -1;
   }
 else if (error != SSL_ERROR_NONE)
   return -1;
   }
 else if (error != SSL_ERROR_NONE)
-  {
   return -1;
   return -1;
-  }
 
 return inbytes;
 }
 
 return inbytes;
 }
@@ -1848,9 +2893,10 @@ return inbytes;
 
 /*
 Arguments:
 
 /*
 Arguments:
-  is_server channel specifier
+  ct_ctx    client context pointer, or NULL for the one global server context
   buff      buffer of data
   len       number of bytes
   buff      buffer of data
   len       number of bytes
+  more     further data expected soon
 
 Returns:    the number of bytes after a successful write,
             -1 after a failed write
 
 Returns:    the number of bytes after a successful write,
             -1 after a failed write
@@ -1859,44 +2905,73 @@ Used by both server-side and client-side TLS.
 */
 
 int
 */
 
 int
-tls_write(BOOL is_server, const uschar *buff, size_t len)
+tls_write(void * ct_ctx, const uschar *buff, size_t len, BOOL more)
 {
 {
-int outbytes;
-int error;
-int left = len;
-SSL *ssl = is_server ? server_ssl : client_ssl;
+int outbytes, error, left;
+SSL * ssl = ct_ctx ? ((exim_openssl_client_tls_ctx *)ct_ctx)->ssl : server_ssl;
+static gstring * corked = NULL;
+
+DEBUG(D_tls) debug_printf("%s(%p, %lu%s)\n", __FUNCTION__,
+  buff, (unsigned long)len, more ? ", more" : "");
+
+/* Lacking a CORK or MSG_MORE facility (such as GnuTLS has) we copy data when
+"more" is notified.  This hack is only ok if small amounts are involved AND only
+one stream does it, in one context (i.e. no store reset).  Currently it is used
+for the responses to the received SMTP MAIL , RCPT, DATA sequence, only. */
+/*XXX + if PIPE_COMMAND, banner & ehlo-resp for smmtp-on-connect. Suspect there's
+a store reset there. */
+
+if (!ct_ctx && (more || corked))
+  {
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+  int save_pool = store_pool;
+  store_pool = POOL_PERM;
+#endif
+
+  corked = string_catn(corked, buff, len);
+
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+  store_pool = save_pool;
+#endif
+
+  if (more)
+    return len;
+  buff = CUS corked->s;
+  len = corked->ptr;
+  corked = NULL;
+  }
 
 
-DEBUG(D_tls) debug_printf("tls_do_write(%p, %d)\n", buff, left);
-while (left > 0)
+for (left = len; left > 0;)
   {
   {
-  DEBUG(D_tls) debug_printf("SSL_write(SSL, %p, %d)\n", buff, left);
+  DEBUG(D_tls) debug_printf("SSL_write(%p, %p, %d)\n", ssl, buff, left);
   outbytes = SSL_write(ssl, CS buff, left);
   error = SSL_get_error(ssl, outbytes);
   DEBUG(D_tls) debug_printf("outbytes=%d error=%d\n", outbytes, error);
   switch (error)
     {
     case SSL_ERROR_SSL:
   outbytes = SSL_write(ssl, CS buff, left);
   error = SSL_get_error(ssl, outbytes);
   DEBUG(D_tls) debug_printf("outbytes=%d error=%d\n", outbytes, error);
   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_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring));
+      log_write(0, LOG_MAIN, "TLS error (SSL_write): %s", ssl_errstring);
+      return -1;
 
     case SSL_ERROR_NONE:
 
     case SSL_ERROR_NONE:
-    left -= outbytes;
-    buff += outbytes;
-    break;
+      left -= outbytes;
+      buff += outbytes;
+      break;
 
     case SSL_ERROR_ZERO_RETURN:
 
     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:
 
     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:
 
     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;
     }
   }
 return len;
@@ -1912,29 +2987,59 @@ return len;
 daemon, to shut down the TLS library, without actually doing a shutdown (which
 would tamper with the SSL session in the parent process).
 
 daemon, to shut down the TLS library, without actually doing a shutdown (which
 would tamper with the SSL session in the parent process).
 
-Arguments:   TRUE if SSL_shutdown is to be called
+Arguments:
+  ct_ctx       client TLS context pointer, or NULL for the one global server context
+  shutdown     1 if TLS close-alert is to be sent,
+               2 if also response to be waited for
+
 Returns:     nothing
 
 Used by both server-side and client-side TLS.
 */
 
 void
 Returns:     nothing
 
 Used by both server-side and client-side TLS.
 */
 
 void
-tls_close(BOOL is_server, BOOL shutdown)
+tls_close(void * ct_ctx, int shutdown)
 {
 {
-SSL **sslp = is_server ? &server_ssl : &client_ssl;
-int *fdp = is_server ? &tls_in.active : &tls_out.active;
+exim_openssl_client_tls_ctx * o_ctx = ct_ctx;
+SSL_CTX **ctxp = o_ctx ? &o_ctx->ctx : &server_ctx;
+SSL **sslp =     o_ctx ? &o_ctx->ssl : &server_ssl;
+int *fdp = o_ctx ? &tls_out.active.sock : &tls_in.active.sock;
 
 if (*fdp < 0) return;  /* TLS was not active */
 
 if (shutdown)
   {
 
 if (*fdp < 0) return;  /* TLS was not active */
 
 if (shutdown)
   {
-  DEBUG(D_tls) debug_printf("tls_close(): shutting down SSL\n");
-  SSL_shutdown(*sslp);
+  int rc;
+  DEBUG(D_tls) debug_printf("tls_close(): shutting down TLS%s\n",
+    shutdown > 1 ? " (with response-wait)" : "");
+
+  if (  (rc = SSL_shutdown(*sslp)) == 0        /* send "close notify" alert */
+     && shutdown > 1)
+    {
+    ALARM(2);
+    rc = SSL_shutdown(*sslp);          /* wait for response */
+    ALARM_CLR(0);
+    }
+
+  if (rc < 0) DEBUG(D_tls)
+    {
+    ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring));
+    debug_printf("SSL_shutdown: %s\n", ssl_errstring);
+    }
+  }
+
+#ifndef DISABLE_OCSP
+if (!o_ctx)            /* server side */
+  {
+  sk_X509_pop_free(server_static_cbinfo->verify_stack, X509_free);
+  server_static_cbinfo->verify_stack = NULL;
   }
   }
+#endif
 
 
+SSL_CTX_free(*ctxp);
 SSL_free(*sslp);
 SSL_free(*sslp);
+*ctxp = NULL;
 *sslp = NULL;
 *sslp = NULL;
-
 *fdp = -1;
 }
 
 *fdp = -1;
 }
 
@@ -1960,8 +3065,10 @@ uschar *s, *expciphers, *err;
 /* this duplicates from tls_init(), we need a better "init just global
 state, for no specific purpose" singleton function of our own */
 
 /* this duplicates from tls_init(), we need a better "init just global
 state, for no specific purpose" singleton function of our own */
 
+#ifdef EXIM_NEED_OPENSSL_INIT
 SSL_load_error_strings();
 OpenSSL_add_ssl_algorithms();
 SSL_load_error_strings();
 OpenSSL_add_ssl_algorithms();
+#endif
 #if (OPENSSL_VERSION_NUMBER >= 0x0090800fL) && !defined(OPENSSL_NO_SHA256)
 /* SHA256 is becoming ever more popular. This makes sure it gets added to the
 list of available digests. */
 #if (OPENSSL_VERSION_NUMBER >= 0x0090800fL) && !defined(OPENSSL_NO_SHA256)
 /* SHA256 is becoming ever more popular. This makes sure it gets added to the
 list of available digests. */
@@ -1971,7 +3078,8 @@ EVP_add_digest(EVP_sha256());
 if (!(tls_require_ciphers && *tls_require_ciphers))
   return NULL;
 
 if (!(tls_require_ciphers && *tls_require_ciphers))
   return NULL;
 
-if (!expand_check(tls_require_ciphers, US"tls_require_ciphers", &expciphers))
+if (!expand_check(tls_require_ciphers, US"tls_require_ciphers", &expciphers,
+                 &err))
   return US"failed to expand tls_require_ciphers";
 
 if (!(expciphers && *expciphers))
   return US"failed to expand tls_require_ciphers";
 
 if (!(expciphers && *expciphers))
@@ -1983,10 +3091,13 @@ while (*s != 0) { if (*s == '_') *s = '-'; s++; }
 
 err = NULL;
 
 
 err = NULL;
 
-ctx = SSL_CTX_new(SSLv23_server_method());
-if (!ctx)
+#ifdef EXIM_HAVE_OPENSSL_TLS_METHOD
+if (!(ctx = SSL_CTX_new(TLS_server_method())))
+#else
+if (!(ctx = SSL_CTX_new(SSLv23_server_method())))
+#endif
   {
   {
-  ERR_error_string(ERR_get_error(), ssl_errstring);
+  ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring));
   return string_sprintf("SSL_CTX_new() failed: %s", ssl_errstring);
   }
 
   return string_sprintf("SSL_CTX_new() failed: %s", ssl_errstring);
   }
 
@@ -1995,8 +3106,9 @@ DEBUG(D_tls)
 
 if (!SSL_CTX_set_cipher_list(ctx, CS expciphers))
   {
 
 if (!SSL_CTX_set_cipher_list(ctx, CS expciphers))
   {
-  ERR_error_string(ERR_get_error(), ssl_errstring);
-  err = string_sprintf("SSL_CTX_set_cipher_list(%s) failed", expciphers);
+  ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring));
+  err = string_sprintf("SSL_CTX_set_cipher_list(%s) failed: %s",
+                     expciphers, ssl_errstring);
   }
 
 SSL_CTX_free(ctx);
   }
 
 SSL_CTX_free(ctx);
@@ -2089,7 +3201,7 @@ if (!RAND_status())
   gettimeofday(&r.tv, NULL);
   r.p = getpid();
 
   gettimeofday(&r.tv, NULL);
   r.p = getpid();
 
-  RAND_seed((uschar *)(&r), sizeof(r));
+  RAND_seed(US (&r), sizeof(r));
   }
 /* We're after pseudo-random, not random; if we still don't have enough data
 in the internal PRNG then our options are limited.  We could sleep and hope
   }
 /* We're after pseudo-random, not random; if we still don't have enough data
 in the internal PRNG then our options are limited.  We could sleep and hope
@@ -2107,8 +3219,13 @@ i = (i + 7) / 8;
 if (i < needed_len)
   needed_len = i;
 
 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);
 /* 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)
 if (i < 0)
   {
   DEBUG(D_all)
@@ -2143,110 +3260,6 @@ Arguments:
 Returns   success or failure in parsing
 */
 
 Returns   success or failure in parsing
 */
 
-struct exim_openssl_option {
-  uschar *name;
-  long    value;
-};
-/* We could use a macro to expand, but we need the ifdef and not all the
-options document which version they were introduced in.  Policylet: include
-all options unless explicitly for DTLS, let the administrator choose which
-to apply.
-
-This list is current as of:
-  ==>  1.0.1b  <==
-Plus SSL_OP_SAFARI_ECDHE_ECDSA_BUG from 2013-June patch/discussion on openssl-dev
-*/
-static struct exim_openssl_option exim_openssl_options[] = {
-/* KEEP SORTED ALPHABETICALLY! */
-#ifdef SSL_OP_ALL
-  { US"all", SSL_OP_ALL },
-#endif
-#ifdef SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION
-  { US"allow_unsafe_legacy_renegotiation", SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION },
-#endif
-#ifdef SSL_OP_CIPHER_SERVER_PREFERENCE
-  { US"cipher_server_preference", SSL_OP_CIPHER_SERVER_PREFERENCE },
-#endif
-#ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS
-  { US"dont_insert_empty_fragments", SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS },
-#endif
-#ifdef SSL_OP_EPHEMERAL_RSA
-  { US"ephemeral_rsa", SSL_OP_EPHEMERAL_RSA },
-#endif
-#ifdef SSL_OP_LEGACY_SERVER_CONNECT
-  { US"legacy_server_connect", SSL_OP_LEGACY_SERVER_CONNECT },
-#endif
-#ifdef SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER
-  { US"microsoft_big_sslv3_buffer", SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER },
-#endif
-#ifdef SSL_OP_MICROSOFT_SESS_ID_BUG
-  { US"microsoft_sess_id_bug", SSL_OP_MICROSOFT_SESS_ID_BUG },
-#endif
-#ifdef SSL_OP_MSIE_SSLV2_RSA_PADDING
-  { US"msie_sslv2_rsa_padding", SSL_OP_MSIE_SSLV2_RSA_PADDING },
-#endif
-#ifdef SSL_OP_NETSCAPE_CHALLENGE_BUG
-  { US"netscape_challenge_bug", SSL_OP_NETSCAPE_CHALLENGE_BUG },
-#endif
-#ifdef SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG
-  { US"netscape_reuse_cipher_change_bug", SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG },
-#endif
-#ifdef SSL_OP_NO_COMPRESSION
-  { US"no_compression", SSL_OP_NO_COMPRESSION },
-#endif
-#ifdef SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION
-  { US"no_session_resumption_on_renegotiation", SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION },
-#endif
-#ifdef SSL_OP_NO_SSLv2
-  { US"no_sslv2", SSL_OP_NO_SSLv2 },
-#endif
-#ifdef SSL_OP_NO_SSLv3
-  { US"no_sslv3", SSL_OP_NO_SSLv3 },
-#endif
-#ifdef SSL_OP_NO_TICKET
-  { US"no_ticket", SSL_OP_NO_TICKET },
-#endif
-#ifdef SSL_OP_NO_TLSv1
-  { US"no_tlsv1", SSL_OP_NO_TLSv1 },
-#endif
-#ifdef SSL_OP_NO_TLSv1_1
-#if SSL_OP_NO_TLSv1_1 == 0x00000400L
-  /* Error in chosen value in 1.0.1a; see first item in CHANGES for 1.0.1b */
-#warning OpenSSL 1.0.1a uses a bad value for SSL_OP_NO_TLSv1_1, ignoring
-#else
-  { US"no_tlsv1_1", SSL_OP_NO_TLSv1_1 },
-#endif
-#endif
-#ifdef SSL_OP_NO_TLSv1_2
-  { US"no_tlsv1_2", SSL_OP_NO_TLSv1_2 },
-#endif
-#ifdef SSL_OP_SAFARI_ECDHE_ECDSA_BUG
-  { US"safari_ecdhe_ecdsa_bug", SSL_OP_SAFARI_ECDHE_ECDSA_BUG },
-#endif
-#ifdef SSL_OP_SINGLE_DH_USE
-  { US"single_dh_use", SSL_OP_SINGLE_DH_USE },
-#endif
-#ifdef SSL_OP_SINGLE_ECDH_USE
-  { US"single_ecdh_use", SSL_OP_SINGLE_ECDH_USE },
-#endif
-#ifdef SSL_OP_SSLEAY_080_CLIENT_DH_BUG
-  { US"ssleay_080_client_dh_bug", SSL_OP_SSLEAY_080_CLIENT_DH_BUG },
-#endif
-#ifdef SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG
-  { US"sslref2_reuse_cert_type_bug", SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG },
-#endif
-#ifdef SSL_OP_TLS_BLOCK_PADDING_BUG
-  { US"tls_block_padding_bug", SSL_OP_TLS_BLOCK_PADDING_BUG },
-#endif
-#ifdef SSL_OP_TLS_D5_BUG
-  { US"tls_d5_bug", SSL_OP_TLS_D5_BUG },
-#endif
-#ifdef SSL_OP_TLS_ROLLBACK_BUG
-  { US"tls_rollback_bug", SSL_OP_TLS_ROLLBACK_BUG },
-#endif
-};
-static int exim_openssl_options_size =
-  sizeof(exim_openssl_options)/sizeof(struct exim_openssl_option);
 
 
 static BOOL
 
 
 static BOOL
@@ -2296,14 +3309,17 @@ uschar *s, *end;
 uschar keep_c;
 BOOL adding, item_parsed;
 
 uschar keep_c;
 BOOL adding, item_parsed;
 
-result = 0L;
+result = SSL_OP_NO_TICKET;
 /* Prior to 4.80 we or'd in SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; removed
  * from default because it increases BEAST susceptibility. */
 #ifdef SSL_OP_NO_SSLv2
 result |= SSL_OP_NO_SSLv2;
 #endif
 /* Prior to 4.80 we or'd in SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; removed
  * from default because it increases BEAST susceptibility. */
 #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)
+if (!option_spec)
   {
   *results = result;
   return TRUE;
   {
   *results = result;
   return TRUE;
@@ -2325,6 +3341,7 @@ for (s=option_spec; *s != '\0'; /**/)
   keep_c = *end;
   *end = '\0';
   item_parsed = tls_openssl_one_option_parse(s, &item);
   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);
   if (!item_parsed)
     {
     DEBUG(D_tls) debug_printf("openssl option setting unrecognised: \"%s\"\n", s);
@@ -2336,7 +3353,6 @@ for (s=option_spec; *s != '\0'; /**/)
     result |= item;
   else
     result &= ~item;
     result |= item;
   else
     result &= ~item;
-  *end = keep_c;
   s = end;
   }
 
   s = end;
   }
 
@@ -2344,6 +3360,7 @@ for (s=option_spec; *s != '\0'; /**/)
 return TRUE;
 }
 
 return TRUE;
 }
 
+#endif /*!MACRO_PREDEF*/
 /* vi: aw ai sw=2
 */
 /* End of tls-openssl.c */
 /* vi: aw ai sw=2
 */
 /* End of tls-openssl.c */
index f2ab567..f79bc31 100644 (file)
--- a/src/tls.c
+++ b/src/tls.c
@@ -2,13 +2,13 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2012 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* 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
 /* 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.
 
 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.
 
@@ -19,6 +19,15 @@ functions from the OpenSSL or GNU TLS libraries. */
 #include "exim.h"
 #include "transports/smtp.h"
 
 #include "exim.h"
 #include "transports/smtp.h"
 
+#if defined(MACRO_PREDEF) && defined(SUPPORT_TLS)
+# ifndef USE_GNUTLS
+#  include "macro_predef.h"
+#  include "tls-openssl.c"
+# endif
+#endif
+
+#ifndef MACRO_PREDEF
+
 /* 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
 /* 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
@@ -41,8 +50,8 @@ static const int ssl_xfer_buffer_size = 4096;
 static uschar *ssl_xfer_buffer = NULL;
 static int ssl_xfer_buffer_lwm = 0;
 static int ssl_xfer_buffer_hwm = 0;
 static uschar *ssl_xfer_buffer = NULL;
 static int ssl_xfer_buffer_lwm = 0;
 static int ssl_xfer_buffer_hwm = 0;
-static int ssl_xfer_eof = 0;
-static int ssl_xfer_error = 0;
+static int ssl_xfer_eof = FALSE;
+static BOOL ssl_xfer_error = FALSE;
 #endif
 
 uschar *tls_channelbinding_b64 = NULL;
 #endif
 
 uschar *tls_channelbinding_b64 = NULL;
@@ -64,39 +73,63 @@ Returns:    TRUE if OK; result may still be NULL after forced failure
 */
 
 static BOOL
 */
 
 static BOOL
-expand_check(const uschar *s, const uschar *name, uschar **result)
+expand_check(const uschar *s, const uschar *name, uschar **result, uschar ** errstr)
 {
 {
-if (s == NULL) *result = NULL; else
+if (!s)
+  *result = NULL;
+else if (  !(*result = expand_string(US s)) /* need to clean up const more */
+       && !f.expand_string_forcedfail
+       )
   {
   {
-  *result = expand_string(US s); /* need to clean up const some more */
-  if (*result == NULL && !expand_string_forcedfail)
-    {
-    log_write(0, LOG_MAIN|LOG_PANIC, "expansion of %s failed: %s", name,
-      expand_string_message);
-    return FALSE;
-    }
+  *errstr = US"Internal error";
+  log_write(0, LOG_MAIN|LOG_PANIC, "expansion of %s failed: %s", name,
+    expand_string_message);
+  return FALSE;
   }
 return TRUE;
 }
 
 
   }
 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
 /*************************************************
 *        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
 
 #else
-#include "tls-openssl.c"
-#include "tlscert-openssl.c"
+# include "tls-openssl.c"
+# include "tlscert-openssl.c"
 #endif
 
 
 #endif
 
 
@@ -138,7 +171,7 @@ Returns:       non-zero if the eof flag is set
 int
 tls_feof(void)
 {
 int
 tls_feof(void)
 {
-return ssl_xfer_eof;
+return (int)ssl_xfer_eof;
 }
 
 
 }
 
 
@@ -160,7 +193,7 @@ Returns:       non-zero if the error flag is set
 int
 tls_ferror(void)
 {
 int
 tls_ferror(void)
 {
-return ssl_xfer_error;
+return (int)ssl_xfer_error;
 }
 
 
 }
 
 
@@ -224,7 +257,7 @@ NOTE: We modify the supplied dn string during operation.
 
 Arguments:
        dn      Distinguished Name string
 
 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,
                field selector match, comma-separated
 Return:
        allocated string with list of matching fields,
@@ -232,32 +265,33 @@ Return:
 */
 
 uschar *
 */
 
 uschar *
-tls_field_from_dn(uschar * dn, uschar * mod)
+tls_field_from_dn(uschar * dn, const uschar * mod)
 {
 int insep = ',';
 uschar outsep = '\n';
 uschar * ele;
 uschar * match = NULL;
 int len;
 {
 int insep = ',';
 uschar outsep = '\n';
 uschar * ele;
 uschar * match = NULL;
 int len;
-uschar * list = NULL;
+gstring * list = NULL;
 
 while ((ele = string_nextinlist(&mod, &insep, NULL, 0)))
   if (ele[0] != '>')
     match = ele;       /* field tag to match */
   else if (ele[1])
 
 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 = ',';
 
 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);
     list = string_append_listele(list, outsep, ele+len+1);
-return list;
+return string_from_gstring(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.
 /* 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 +324,7 @@ Returns:
 */
 
 BOOL
 */
 
 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;
 {
 uschar * altnames = tls_cert_subject_altname(cert, US"dns");
 uschar * subjdn;
@@ -303,7 +337,7 @@ if ((altnames = tls_cert_subject_altname(cert, US"dns")))
   int alt_sep = '\n';
   while ((cmpname = string_nextinlist(&namelist, &cmp_sep, NULL, 0)))
     {
   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;
     while ((certname = string_nextinlist(&an, &alt_sep, NULL, 0)))
       if (is_name_match(cmpname, certname))
        return TRUE;
@@ -317,7 +351,7 @@ else if ((subjdn = tls_cert_subject(cert, NULL)))
   dn_to_list(subjdn);
   while ((cmpname = string_nextinlist(&namelist, &cmp_sep, NULL, 0)))
     {
   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'
     while ((certname = string_nextinlist(&sn, &sn_sep, NULL, 0)))
       if (  *certname++ == 'C'
         && *certname++ == 'N'
@@ -329,8 +363,8 @@ else if ((subjdn = tls_cert_subject(cert, NULL)))
   }
 return FALSE;
 }
   }
 return FALSE;
 }
-# endif        /*EXPERIMENTAL_CERTNAMES*/
 #endif /*SUPPORT_TLS*/
 #endif /*SUPPORT_TLS*/
+#endif /*!MACRO_PREDEF*/
 
 /* vi: aw ai sw=2
 */
 
 /* vi: aw ai sw=2
 */
index 3261c4e..9fe8c49 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) Jeremy Harris 2014 */
+/* Copyright (c) Jeremy Harris 2014 - 2018 */
 
 /* This file provides TLS/SSL support for Exim using the GnuTLS library,
 one of the available supported implementations.  This file is #included into
 
 /* 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;
 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)))
 
 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;
 {
 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;
 
 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);
 gnutls_x509_crt_init(&crt);
 
 datum.data = string_unprinting(US buf);
@@ -73,10 +77,15 @@ return fail;
 }
 
 void
 }
 
 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;
 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);
 
 
 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 (f.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;
 }
 
 return len > 0 ? cp : NULL;
 }
 
@@ -128,12 +142,12 @@ uschar * cp = NULL;
 int ret;
 size_t siz = 0;
 
 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);
     != 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;
   return g_err("gi1", __FUNCTION__, ret);
 
 return mod ? tls_field_from_dn(cp, mod) : cp;
@@ -168,8 +182,8 @@ if ((ret = gnutls_x509_crt_get_serial((gnutls_x509_crt_t)cert,
     bin, &sz)))
   return g_err("gs0", __FUNCTION__, ret);
 
     bin, &sz)))
   return g_err("gs0", __FUNCTION__, ret);
 
-for(dp = txt, sp = bin; sz; dp += 2, sp++, sz--)
-  sprintf(dp, "%.2x", *sp);
+for(dp = txt, sp = bin; sz; sz--)
+  dp += sprintf(CS dp, "%.2x", *sp++);
 for(sp = txt; sp[0]=='0' && sp[1]; ) sp++;     /* leading zeroes */
 return string_copy(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 *
 tls_cert_signature(void * cert, uschar * mod)
 {
-uschar * cp1;
+uschar * cp1 = NULL;
 uschar * cp2;
 uschar * cp3;
 size_t len = 0;
 int ret;
 
 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);
     != 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);
 
   return g_err("gs1", __FUNCTION__, ret);
 
-for(cp3 = cp2 = cp1+len; cp1 < cp2; cp3 += 3, cp1++)
-  sprintf(cp3, "%.2x ", *cp1);
+for(cp3 = cp2 = cp1+len; cp1 < cp2; cp1++)
+  cp3 += sprintf(CS cp3, "%.2x ", *cp1);
 cp3[-1]= '\0';
 
 return cp2;
 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);
 {
 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 *
 }
 
 uschar *
@@ -213,12 +227,12 @@ uschar * cp = NULL;
 int ret;
 size_t siz = 0;
 
 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);
     != 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;
   return g_err("gs1", __FUNCTION__, ret);
 
 return mod ? tls_field_from_dn(cp, mod) : cp;
@@ -241,22 +255,22 @@ unsigned int crit;
 int ret;
 
 ret = gnutls_x509_crt_get_extension_by_oid ((gnutls_x509_crt_t)cert,
 int ret;
 
 ret = gnutls_x509_crt_get_extension_by_oid ((gnutls_x509_crt_t)cert,
-  oid, idx, cp1, &siz, &crit);
+  CS 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,
 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);
+  CS oid, idx, CS cp1, &siz, &crit);
 if (ret < 0)
   return g_err("ge1", __FUNCTION__, ret);
 
 /* binary data, DER encoded */
 
 /* just dump for now */
 if (ret < 0)
   return g_err("ge1", __FUNCTION__, ret);
 
 /* binary data, DER encoded */
 
 /* just dump for now */
-for(cp3 = cp2 = cp1+siz; cp1 < cp2; cp3 += 3, cp1++)
-  sprintf(cp3, "%.2x ", *cp1);
+for(cp3 = cp2 = cp1+siz; cp1 < cp2; cp1++)
+  cp3 += sprintf(CS cp3, "%.2x ", *cp1);
 cp3[-1]= '\0';
 
 return cp2;
 cp3[-1]= '\0';
 
 return cp2;
@@ -265,7 +279,7 @@ return cp2;
 uschar *
 tls_cert_subject_altname(void * cert, uschar * mod)
 {
 uschar *
 tls_cert_subject_altname(void * cert, uschar * mod)
 {
-uschar * list = NULL;
+gstring * list = NULL;
 int index;
 size_t siz;
 int ret;
 int index;
 size_t siz;
 int ret;
@@ -293,7 +307,7 @@ for(index = 0;; index++)
       (gnutls_x509_crt_t)cert, index, NULL, &siz, NULL))
     {
     case GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE:
       (gnutls_x509_crt_t)cert, index, NULL, &siz, NULL))
     {
     case GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE:
-      return list;     /* no more elements; normal exit */
+      return string_from_gstring(list);        /* no more elements; normal exit */
 
     case GNUTLS_E_SHORT_MEMORY_BUFFER:
       break;
 
     case GNUTLS_E_SHORT_MEMORY_BUFFER:
       break;
@@ -314,11 +328,11 @@ for(index = 0;; index++)
   switch (ret)
     {
     case GNUTLS_SAN_DNSNAME:    tag = US"DNS";  break;
   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 */
     }
     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*/
           match == -1 ? string_sprintf("%s=%s", tag, ele) : ele);
   }
 /*NOTREACHED*/
@@ -332,7 +346,7 @@ gnutls_datum_t uri;
 int ret;
 uschar sep = '\n';
 int index;
 int ret;
 uschar sep = '\n';
 int index;
-uschar * list = NULL;
+gstring * list = NULL;
 
 if (mod)
   if (*mod == '>' && *++mod) sep = *mod++;
 
 if (mod)
   if (*mod == '>' && *++mod) sep = *mod++;
@@ -343,18 +357,17 @@ for(index = 0;; index++)
          index, GNUTLS_IA_OCSP_URI, &uri, NULL);
 
   if (ret == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
          index, GNUTLS_IA_OCSP_URI, &uri, NULL);
 
   if (ret == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
-    return list;
+    return string_from_gstring(list);
   if (ret < 0)
     return g_err("gai", __FUNCTION__, ret);
 
   if (ret < 0)
     return g_err("gai", __FUNCTION__, ret);
 
-  list = string_append_listele(list, sep,
-           string_copyn(uri.data, uri.size));
+  list = string_append_listele_n(list, sep, uri.data, uri.size);
   }
 /*NOTREACHED*/
 
 #else
 
   }
 /*NOTREACHED*/
 
 #else
 
-expand_string_message = 
+expand_string_message =
   string_sprintf("%s: OCSP support with GnuTLS requires version 3.0.0\n",
     __FUNCTION__);
 return NULL;
   string_sprintf("%s: OCSP support with GnuTLS requires version 3.0.0\n",
     __FUNCTION__);
 return NULL;
@@ -369,7 +382,7 @@ int ret;
 size_t siz;
 uschar sep = '\n';
 int index;
 size_t siz;
 uschar sep = '\n';
 int index;
-uschar * list = NULL;
+gstring * list = NULL;
 uschar * ele;
 
 if (mod)
 uschar * ele;
 
 if (mod)
@@ -382,20 +395,19 @@ for(index = 0;; index++)
     (gnutls_x509_crt_t)cert, index, NULL, &siz, NULL, NULL))
     {
     case GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE:
     (gnutls_x509_crt_t)cert, index, NULL, &siz, NULL, NULL))
     {
     case GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE:
-      return list;
+      return string_from_gstring(list);
     case GNUTLS_E_SHORT_MEMORY_BUFFER:
       break;
     default:
       return g_err("gc0", __FUNCTION__, ret);
     }
 
     case GNUTLS_E_SHORT_MEMORY_BUFFER:
       break;
     default:
       return g_err("gc0", __FUNCTION__, ret);
     }
 
-  ele = store_get(siz+1);
+  ele = store_get(siz);
   if ((ret = gnutls_x509_crt_get_crl_dist_points(
       (gnutls_x509_crt_t)cert, index, ele, &siz, NULL, NULL)) < 0)
     return g_err("gc1", __FUNCTION__, ret);
 
   if ((ret = gnutls_x509_crt_get_crl_dist_points(
       (gnutls_x509_crt_t)cert, index, ele, &siz, NULL, NULL)) < 0)
     return g_err("gc1", __FUNCTION__, ret);
 
-  ele[siz] = '\0';
-  list = string_append_listele(list, sep, ele);
+  list = string_append_listele_n(list, sep, ele, siz);
   }
 /*NOTREACHED*/
 }
   }
 /*NOTREACHED*/
 }
@@ -404,6 +416,28 @@ for(index = 0;; index++)
 /*****************************************************
 *  Certificate operator routines
 *****************************************************/
 /*****************************************************
 *  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)
 {
 static uschar *
 fingerprint(gnutls_x509_crt_t cert, gnutls_digest_algorithm_t algo)
 {
@@ -421,8 +455,8 @@ cp = store_get(siz*3+1);
 if ((ret = gnutls_x509_crt_get_fingerprint(cert, algo, cp, &siz)) < 0)
   return g_err("gf1", __FUNCTION__, ret);
 
 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);
+for (cp3 = cp2 = cp+siz; cp < cp2; cp++)
+  cp3 += sprintf(CS cp3, "%02X", *cp);
 return cp2;
 }
 
 return cp2;
 }
 
index a57980d..7e0128e 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) Jeremy Harris 2014 */
+/* Copyright (c) Jeremy Harris 2014 - 2018 */
 
 /* 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.
 
 /* 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,13 @@ library. It is #included into the tls.c file when that library is used.
 #include <openssl/rand.h>
 #include <openssl/x509v3.h>
 
 #include <openssl/rand.h>
 #include <openssl/x509v3.h>
 
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+# define EXIM_HAVE_ASN1_MACROS
+#endif
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
+# define ASN1_STRING_get0_data ASN1_STRING_data
+#endif
 
 /*****************************************************
 *  Export/import a certificate, binary/printable
 
 /*****************************************************
 *  Export/import a certificate, binary/printable
@@ -55,9 +62,11 @@ tls_import_cert(const uschar * buf, void ** cert)
 void * reset_point = store_get(0);
 const uschar * cp = string_unprinting(US buf);
 BIO * bp;
 void * reset_point = store_get(0);
 const uschar * cp = string_unprinting(US buf);
 BIO * bp;
-X509 * x;
+X509 * x = *(X509 **)cert;
 int fail = 0;
 
 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)))
   {
 bp = BIO_new_mem_buf(US cp, -1);
 if (!(x = PEM_read_bio_X509(bp, NULL, 0, NULL)))
   {
@@ -73,9 +82,14 @@ return fail;
 }
 
 void
 }
 
 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 +117,65 @@ return cp;
 }
 
 static uschar *
 }
 
 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;
 
 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;
+
+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(TIME_T_FMT, t);
+
+    else
+      {
+      if (!f.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);
+      }
+    }
 
 
-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_tz)
+    restore_tz(tz);
+  }
+BIO_free(bp);
+return s;
 }
 
 static uschar *
 }
 
 static uschar *
@@ -199,9 +248,9 @@ BIO * bp = BIO_new(BIO_s_mem());
 if (!bp) return badalloc();
 
 if (X509_print_ex(bp, (X509 *)cert, 0,
 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)
   {
   /* X509_FLAG_NO_SIGDUMP is the missing one */
   X509_FLAG_NO_AUX) == 1)
   {
@@ -225,10 +274,10 @@ BIO * bp = BIO_new(BIO_s_mem());
 if (!bp) return badalloc();
 
 if (X509_print_ex(bp, (X509 *)cert, 0,
 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_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);
   X509_FLAG_NO_SIGDUMP | X509_FLAG_NO_AUX) == 1)
   {
   long len = BIO_get_mem_data(bp, &cp);
@@ -254,7 +303,7 @@ return mod ? tls_field_from_dn(cp, mod) : cp;
 uschar *
 tls_cert_version(void * cert, uschar * mod)
 {
 uschar *
 tls_cert_version(void * cert, uschar * mod)
 {
-return string_sprintf("%d", X509_get_version((X509 *)cert));
+return string_sprintf("%ld", X509_get_version((X509 *)cert));
 }
 
 uschar *
 }
 
 uschar *
@@ -272,17 +321,20 @@ uschar * cp3;
 
 if (!bp) return badalloc();
 
 
 if (!bp) return badalloc();
 
+#ifdef EXIM_HAVE_ASN1_MACROS
+ASN1_STRING_print(bp, adata);
+#else
 M_ASN1_OCTET_STRING_print(bp, adata);
 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);
 
 while(len)
   {
 /* just dump for now */
 len = BIO_get_mem_data(bp, &cp1);
 cp3 = cp2 = store_get(len*3+1);
 
 while(len)
   {
-  sprintf(CS cp2, "%.2x ", *cp1++);
-  cp2 += 3;
+  cp2 += sprintf(CS cp2, "%.2x ", *cp1++);
   len--;
   }
 cp2[-1] = '\0';
   len--;
   }
 cp2[-1] = '\0';
@@ -293,10 +345,10 @@ return cp3;
 uschar *
 tls_cert_subject_altname(void * cert, uschar * mod)
 {
 uschar *
 tls_cert_subject_altname(void * cert, uschar * mod)
 {
-uschar * list = NULL;
+gstring * list = NULL;
 STACK_OF(GENERAL_NAME) * san = (STACK_OF(GENERAL_NAME) *)
   X509_get_ext_d2i((X509 *)cert, NID_subject_alt_name, NULL, 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;
 uschar * tag = US"";
 uschar * ele;
 int match = -1;
@@ -304,16 +356,15 @@ int len;
 
 if (!san) return NULL;
 
 
 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)
   }
 
 while (sk_GENERAL_NAME_num(san) > 0)
@@ -325,17 +376,17 @@ while (sk_GENERAL_NAME_num(san) > 0)
     {
     case GEN_DNS:
       tag = US"DNS";
     {
     case GEN_DNS:
       tag = US"DNS";
-      ele = ASN1_STRING_data(namePart->d.dNSName);
+      ele = US ASN1_STRING_get0_data(namePart->d.dNSName);
       len = ASN1_STRING_length(namePart->d.dNSName);
       break;
     case GEN_URI:
       tag = US"URI";
       len = ASN1_STRING_length(namePart->d.dNSName);
       break;
     case GEN_URI:
       tag = US"URI";
-      ele = ASN1_STRING_data(namePart->d.uniformResourceIdentifier);
+      ele = US ASN1_STRING_get0_data(namePart->d.uniformResourceIdentifier);
       len = ASN1_STRING_length(namePart->d.uniformResourceIdentifier);
       break;
     case GEN_EMAIL:
       tag = US"MAIL";
       len = ASN1_STRING_length(namePart->d.uniformResourceIdentifier);
       break;
     case GEN_EMAIL:
       tag = US"MAIL";
-      ele = ASN1_STRING_data(namePart->d.rfc822Name);
+      ele = US ASN1_STRING_get0_data(namePart->d.rfc822Name);
       len = ASN1_STRING_length(namePart->d.rfc822Name);
       break;
     default:
       len = ASN1_STRING_length(namePart->d.rfc822Name);
       break;
     default:
@@ -344,13 +395,13 @@ while (sk_GENERAL_NAME_num(san) > 0)
   if (ele[len])        /* not nul-terminated */
     ele = string_copyn(ele, len);
 
   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);
   }
 
 sk_GENERAL_NAME_free(san);
          match == -1 ? string_sprintf("%s=%s", tag, ele) : ele);
   }
 
 sk_GENERAL_NAME_free(san);
-return list;
+return string_from_gstring(list);
 }
 
 uschar *
 }
 
 uschar *
@@ -361,7 +412,7 @@ STACK_OF(ACCESS_DESCRIPTION) * ads = (STACK_OF(ACCESS_DESCRIPTION) *)
 int adsnum = sk_ACCESS_DESCRIPTION_num(ads);
 int i;
 uschar sep = '\n';
 int adsnum = sk_ACCESS_DESCRIPTION_num(ads);
 int i;
 uschar sep = '\n';
-uschar * list = NULL;
+gstring * list = NULL;
 
 if (mod)
   if (*mod == '>' && *++mod) sep = *mod++;
 
 if (mod)
   if (*mod == '>' && *++mod) sep = *mod++;
@@ -371,10 +422,12 @@ for (i = 0; i < adsnum; i++)
   ACCESS_DESCRIPTION * ad = sk_ACCESS_DESCRIPTION_value(ads, i);
 
   if (ad && OBJ_obj2nid(ad->method) == NID_ad_OCSP)
   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));
+    list = string_append_listele_n(list, sep,
+      US ASN1_STRING_get0_data(ad->location->d.ia5),
+      ASN1_STRING_length(ad->location->d.ia5));
   }
   }
-return list;
+sk_ACCESS_DESCRIPTION_free(ads);
+return string_from_gstring(list);
 }
 
 uschar *
 }
 
 uschar *
@@ -387,7 +440,7 @@ DIST_POINT * dp;
 int dpsnum = sk_DIST_POINT_num(dps);
 int i;
 uschar sep = '\n';
 int dpsnum = sk_DIST_POINT_num(dps);
 int i;
 uschar sep = '\n';
-uschar * list = NULL;
+gstring * list = NULL;
 
 if (mod)
   if (*mod == '>' && *++mod) sep = *mod++;
 
 if (mod)
   if (*mod == '>' && *++mod) sep = *mod++;
@@ -404,10 +457,12 @@ if (dps) for (i = 0; i < dpsnum; i++)
       if (  (np = sk_GENERAL_NAME_value(names, j))
         && np->type == GEN_URI
         )
       if (  (np = sk_GENERAL_NAME_value(names, j))
         && np->type == GEN_URI
         )
-       list = string_append_listele(list, sep,
-               ASN1_STRING_data(np->d.uniformResourceIdentifier));
+       list = string_append_listele_n(list, sep,
+         US ASN1_STRING_get0_data(np->d.uniformResourceIdentifier),
+         ASN1_STRING_length(np->d.uniformResourceIdentifier));
     }
     }
-return list;
+sk_DIST_POINT_free(dps);
+return string_from_gstring(list);
 }
 
 
 }
 
 
@@ -415,6 +470,26 @@ return list;
 /*****************************************************
 *  Certificate operator routines
 *****************************************************/
 /*****************************************************
 *  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)
 {
 static uschar *
 fingerprint(X509 * cert, const EVP_MD * fdig)
 {
@@ -433,19 +508,19 @@ for (j = 0; j < (int)n; j++) sprintf(CS cp+2*j, "%02X", md[j]);
 return(cp);
 }
 
 return(cp);
 }
 
-uschar * 
+uschar *
 tls_cert_fprt_md5(void * cert)
 {
 return fingerprint((X509 *)cert, EVP_md5());
 }
 
 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());
 }
 
 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());
 tls_cert_fprt_sha256(void * cert)
 {
 return fingerprint((X509 *)cert, EVP_sha256());
dissimilarity index 62%
index 0297e37..9088fc6 100644 (file)
--- a/src/tod.c
+++ b/src/tod.c
-/*************************************************
-*     Exim - an Internet mail transport agent    *
-*************************************************/
-
-/* Copyright (c) University of Cambridge 1995 - 2009 */
-/* See the file NOTICE for conditions of use and distribution. */
-
-/* A function for returning the time of day in various formats */
-
-
-#include "exim.h"
-
-/* #define TESTING_LOG_DATESTAMP */
-
-
-static uschar timebuf[sizeof("www, dd-mmm-yyyy hh:mm:ss +zzzz")];
-
-
-/*************************************************
-*                Return timestamp                *
-*************************************************/
-
-/* The log timestamp format is dd-mmm-yy so as to be non-confusing on both
-sides of the Atlantic. We calculate an explicit numerical offset from GMT for
-the full datestamp and BSD inbox datestamp. Note that on some systems
-localtime() and gmtime() re-use the same store, so we must save the local time
-values before calling gmtime(). If timestamps_utc is set, don't use
-localtime(); all times are then in UTC (with offset +0000).
-
-There are also some contortions to get the day of the month without
-a leading zero for the full stamp, since Ustrftime() doesn't provide this
-option.
-
-Argument:  type of timestamp required:
-             tod_bsdin                  BSD inbox format
-             tod_epoch                  Unix epoch format
-             tod_epochl                 Unix epoch/usec format
-             tod_full                   full date and time
-             tod_log                    log file data line format,
-                                          with zone if log_timezone is TRUE
-             tod_log_bare               always without zone
-             tod_log_datestamp_daily    for log file names when datestamped daily
-             tod_log_datestamp_monthly  for log file names when datestamped monthly
-             tod_log_zone               always with zone
-             tod_mbx                    MBX inbox format
-             tod_zone                   just the timezone offset
-             tod_zulu                   time in 8601 zulu format
-
-Returns:   pointer to fixed buffer containing the timestamp
-*/
-
-uschar *
-tod_stamp(int type)
-{
-time_t now;
-struct tm *t;
-
-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 );
-  return timebuf;
-  }
-
-now = time(NULL);
-
-/* Vary log type according to timezone requirement */
-
-if (type == tod_log) type = log_timezone? tod_log_zone : tod_log_bare;
-
-/* Styles that don't need local time */
-
-else if (type == tod_epoch)
-  {
-  (void) sprintf(CS timebuf, TIME_T_FMT, now);  /* Unix epoch format */
-  return timebuf;      /* NB the above will be wrong if time_t is FP */
-  }
-
-else if (type == tod_zulu)
-  {
-  t = gmtime(&now);
-  (void) sprintf(CS timebuf, "%04d%02d%02d%02d%02d%02dZ",
-    1900 + t->tm_year, 1 + t->tm_mon, t->tm_mday, t->tm_hour, t->tm_min,
-    t->tm_sec);
-  return timebuf;
-  }
-
-/* Convert to local time or UTC */
-
-t = timestamps_utc? gmtime(&now) : localtime(&now);
-
-switch(type)
-  {
-  case tod_log_bare:          /* Format used in logging without timezone */
-  (void) sprintf(CS timebuf, "%04d-%02d-%02d %02d:%02d:%02d",
-    1900 + t->tm_year, 1 + t->tm_mon, t->tm_mday,
-    t->tm_hour, t->tm_min, t->tm_sec);
-  break;
-
-  /* Format used as suffix of log file name when 'log_datestamp' is active. For
-  testing purposes, it changes the file every second. */
-
-  #ifdef TESTING_LOG_DATESTAMP
-  case tod_log_datestamp_daily:
-  case tod_log_datestamp_monthly:
-  (void) sprintf(CS timebuf, "%04d%02d%02d%02d%02d",
-    1900 + t->tm_year, 1 + t->tm_mon, t->tm_mday, t->tm_hour, t->tm_min);
-  break;
-
-  #else
-  case tod_log_datestamp_daily:
-  (void) sprintf(CS timebuf, "%04d%02d%02d",
-    1900 + t->tm_year, 1 + t->tm_mon, t->tm_mday);
-  break;
-
-  case tod_log_datestamp_monthly:
-  (void) sprintf(CS timebuf, "%04d%02d",
-    1900 + t->tm_year, 1 + t->tm_mon);
-  break;
-  #endif
-
-  /* Format used in BSD inbox separator lines. Sort-of documented in RFC 976
-  ("UUCP Mail Interchange Format Standard") but only by example, not by
-  explicit definition. The examples show no timezone offsets, and some MUAs
-  appear to be sensitive to this, so Exim has been changed to remove the
-  timezone offsets that originally appeared. */
-
-  case tod_bsdin:
-    {
-    int len = Ustrftime(timebuf, sizeof(timebuf), "%a %b %d %H:%M:%S", t);
-    Ustrftime(timebuf + len, sizeof(timebuf) - len, " %Y", t);
-    }
-  break;
-
-  /* Other types require the GMT offset to be calculated, or just set up in the
-  case of UTC timestamping. We need to take a copy of the local time first. */
-
-  default:
-    {
-    int diff_hour, diff_min;
-    struct tm local;
-    memcpy(&local, t, sizeof(struct tm));
-
-    if (timestamps_utc)
-      {
-      diff_hour = diff_min = 0;
-      }
-    else
-      {
-      struct tm *gmt = gmtime(&now);
-      diff_min = 60*(local.tm_hour - gmt->tm_hour) + local.tm_min - gmt->tm_min;
-      if (local.tm_year != gmt->tm_year)
-        diff_min += (local.tm_year > gmt->tm_year)? 1440 : -1440;
-      else if (local.tm_yday != gmt->tm_yday)
-        diff_min += (local.tm_yday > gmt->tm_yday)? 1440 : -1440;
-      diff_hour = diff_min/60;
-      diff_min  = abs(diff_min - diff_hour*60);
-      }
-
-    switch(type)
-      {
-      case tod_log_zone:          /* Format used in logging with timezone */
-      (void) sprintf(CS timebuf, "%04d-%02d-%02d %02d:%02d:%02d %+03d%02d",
-        1900 + local.tm_year, 1 + local.tm_mon, local.tm_mday,
-        local.tm_hour, local.tm_min, local.tm_sec,
-        diff_hour, diff_min);
-      break;
-
-      case tod_zone:              /* Just the timezone offset */
-      (void) sprintf(CS timebuf, "%+03d%02d", diff_hour, diff_min);
-      break;
-
-      /* tod_mbx: format used in MBX mailboxes - subtly different to tod_full */
-
-      #ifdef SUPPORT_MBX
-      case tod_mbx:
-        {
-        int len;
-        (void) sprintf(CS timebuf, "%02d-", local.tm_mday);
-        len = Ustrlen(timebuf);
-        len += Ustrftime(timebuf + len, sizeof(timebuf) - len, "%b-%Y %H:%M:%S",
-          &local);
-        (void) sprintf(CS timebuf + len, " %+03d%02d", diff_hour, diff_min);
-        }
-      break;
-      #endif
-
-      /* tod_full: format used in Received: headers (use as default just in case
-      called with a junk type value) */
-
-      default:
-        {
-        int len = Ustrftime(timebuf, sizeof(timebuf), "%a, ", &local);
-        (void) sprintf(CS timebuf + len, "%02d ", local.tm_mday);
-        len += Ustrlen(timebuf + len);
-        len += Ustrftime(timebuf + len, sizeof(timebuf) - len, "%b %Y %H:%M:%S",
-          &local);
-        (void) sprintf(CS timebuf + len, " %+03d%02d", diff_hour, diff_min);
-        }
-      break;
-      }
-    }
-  break;
-  }
-
-return timebuf;
-}
-
-/* End of tod.c */
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+/* A function for returning the time of day in various formats */
+
+
+#include "exim.h"
+
+/* #define TESTING_LOG_DATESTAMP */
+
+
+static uschar timebuf[sizeof("www, dd-mmm-yyyy hh:mm:ss.ddd +zzzz")];
+
+
+/*************************************************
+*                Return timestamp                *
+*************************************************/
+
+/* The log timestamp format is dd-mmm-yy so as to be non-confusing on both
+sides of the Atlantic. We calculate an explicit numerical offset from GMT for
+the full datestamp and BSD inbox datestamp. Note that on some systems
+localtime() and gmtime() re-use the same store, so we must save the local time
+values before calling gmtime(). If timestamps_utc is set, don't use
+localtime(); all times are then in UTC (with offset +0000).
+
+There are also some contortions to get the day of the month without
+a leading zero for the full stamp, since Ustrftime() doesn't provide this
+option.
+
+Argument:  type of timestamp required:
+             tod_bsdin                  BSD inbox format
+             tod_epoch                  Unix epoch format
+             tod_epochl                 Unix epoch/usec format
+             tod_full                   full date and time
+             tod_log                    log file data line format,
+                                          with zone if log_timezone is TRUE
+             tod_log_bare               always without zone
+             tod_log_datestamp_daily    for log file names when datestamped daily
+             tod_log_datestamp_monthly  for log file names when datestamped monthly
+             tod_log_zone               always with zone
+             tod_mbx                    MBX inbox format
+             tod_zone                   just the timezone offset
+             tod_zulu                   time in 8601 zulu format
+
+Returns:   pointer to fixed buffer containing the timestamp
+*/
+
+uschar *
+tod_stamp(int type)
+{
+struct timeval now;
+struct tm * t;
+
+gettimeofday(&now, NULL);
+
+/* Styles that don't need local time */
+
+switch(type)
+  {
+  case tod_epoch:
+    (void) sprintf(CS timebuf, TIME_T_FMT, now.tv_sec);  /* Unix epoch format */
+    return timebuf;    /* NB the above will be wrong if time_t is FP */
+
+  case tod_epoch_l:
+    /* Unix epoch/usec format */
+    (void) sprintf(CS timebuf, TIME_T_FMT "%06ld", now.tv_sec, (long) now.tv_usec );
+    return timebuf;
+
+  case tod_zulu:
+    t = gmtime(&now.tv_sec);
+    (void) sprintf(CS timebuf, "%04u%02u%02u%02u%02u%02uZ",
+      1900 + (uint)t->tm_year, 1 + (uint)t->tm_mon, (uint)t->tm_mday, (uint)t->tm_hour, (uint)t->tm_min,
+      (uint)t->tm_sec);
+    return timebuf;
+  }
+
+/* Vary log type according to timezone requirement */
+
+if (type == tod_log) type = log_timezone ? tod_log_zone : tod_log_bare;
+
+/* Convert to local time or UTC */
+
+t = f.timestamps_utc ? gmtime(&now.tv_sec) : localtime(&now.tv_sec);
+
+switch(type)
+  {
+  case tod_log_bare:          /* Format used in logging without timezone */
+#ifndef COMPILE_UTILITY
+    if (LOGGING(millisec))
+      sprintf(CS timebuf, "%04u-%02u-%02u %02u:%02u:%02u.%03u",
+       1900 + (uint)t->tm_year, 1 + (uint)t->tm_mon, (uint)t->tm_mday,
+       (uint)t->tm_hour, (uint)t->tm_min, (uint)t->tm_sec,
+       (uint)(now.tv_usec/1000));
+    else
+#endif
+      sprintf(CS timebuf, "%04u-%02u-%02u %02u:%02u:%02u",
+       1900 + (uint)t->tm_year, 1 + (uint)t->tm_mon, (uint)t->tm_mday,
+       (uint)t->tm_hour, (uint)t->tm_min, (uint)t->tm_sec);
+
+    break;
+
+    /* Format used as suffix of log file name when 'log_datestamp' is active. For
+    testing purposes, it changes the file every second. */
+
+#ifdef TESTING_LOG_DATESTAMP
+  case tod_log_datestamp_daily:
+  case tod_log_datestamp_monthly:
+    sprintf(CS timebuf, "%04u%02u%02u%02u%02u",
+      1900 + (uint)t->tm_year, 1 + (uint)t->tm_mon, (uint)t->tm_mday,
+      (uint)t->tm_hour, (uint)t->tm_min);
+    break;
+
+#else
+  case tod_log_datestamp_daily:
+    sprintf(CS timebuf, "%04u%02u%02u",
+      1900 + (uint)t->tm_year, 1 + (uint)t->tm_mon, (uint)t->tm_mday);
+    break;
+
+  case tod_log_datestamp_monthly:
+#ifndef COMPILE_UTILITY
+    sprintf(CS timebuf, "%04u%02u",
+      1900 + (uint)t->tm_year, 1 + (uint)t->tm_mon);
+#endif
+    break;
+#endif
+
+    /* Format used in BSD inbox separator lines. Sort-of documented in RFC 976
+    ("UUCP Mail Interchange Format Standard") but only by example, not by
+    explicit definition. The examples show no timezone offsets, and some MUAs
+    appear to be sensitive to this, so Exim has been changed to remove the
+    timezone offsets that originally appeared. */
+
+  case tod_bsdin:
+      {
+      int len = Ustrftime(timebuf, sizeof(timebuf), "%a %b %d %H:%M:%S", t);
+      Ustrftime(timebuf + len, sizeof(timebuf) - len, " %Y", t);
+      }
+    break;
+
+    /* Other types require the GMT offset to be calculated, or just set up in the
+    case of UTC timestamping. We need to take a copy of the local time first. */
+
+  default:
+      {
+      int diff_hour, diff_min;
+      struct tm local;
+      memcpy(&local, t, sizeof(struct tm));
+
+      if (f.timestamps_utc)
+       diff_hour = diff_min = 0;
+      else
+       {
+       struct tm * gmt = gmtime(&now.tv_sec);
+
+       diff_min = 60*(local.tm_hour - gmt->tm_hour) + local.tm_min - gmt->tm_min;
+       if (local.tm_year != gmt->tm_year)
+         diff_min += (local.tm_year > gmt->tm_year)? 1440 : -1440;
+       else if (local.tm_yday != gmt->tm_yday)
+         diff_min += (local.tm_yday > gmt->tm_yday)? 1440 : -1440;
+       diff_hour = diff_min/60;
+       diff_min  = abs(diff_min - diff_hour*60);
+       }
+
+      switch(type)
+       {
+       case tod_log_zone:          /* Format used in logging with timezone */
+#ifndef COMPILE_UTILITY
+         if (LOGGING(millisec))
+           (void) sprintf(CS timebuf,
+             "%04u-%02u-%02u %02u:%02u:%02u.%03u %+03d%02d",
+             1900 + (uint)local.tm_year, 1 + (uint)local.tm_mon, (uint)local.tm_mday,
+             (uint)local.tm_hour, (uint)local.tm_min, (uint)local.tm_sec, (uint)(now.tv_usec/1000),
+             diff_hour, diff_min);
+         else
+#endif
+           (void) sprintf(CS timebuf,
+             "%04u-%02u-%02u %02u:%02u:%02u %+03d%02d",
+             1900 + (uint)local.tm_year, 1 + (uint)local.tm_mon, (uint)local.tm_mday,
+             (uint)local.tm_hour, (uint)local.tm_min, (uint)local.tm_sec,
+             diff_hour, diff_min);
+         break;
+
+       case tod_zone:              /* Just the timezone offset */
+         (void) sprintf(CS timebuf, "%+03d%02d", diff_hour, diff_min);
+         break;
+
+       /* tod_mbx: format used in MBX mailboxes - subtly different to tod_full */
+
+         #ifdef SUPPORT_MBX
+       case tod_mbx:
+           {
+           int len;
+           (void) sprintf(CS timebuf, "%02u-", (uint)local.tm_mday);
+           len = Ustrlen(timebuf);
+           len += Ustrftime(timebuf + len, sizeof(timebuf) - len, "%b-%Y %H:%M:%S",
+             &local);
+           (void) sprintf(CS timebuf + len, " %+03d%02d", diff_hour, diff_min);
+           }
+         break;
+         #endif
+
+       /* tod_full: format used in Received: headers (use as default just in case
+       called with a junk type value) */
+
+       default:
+           {
+           int len = Ustrftime(timebuf, sizeof(timebuf), "%a, ", &local);
+           (void) sprintf(CS timebuf + len, "%02u ", (uint)local.tm_mday);
+           len += Ustrlen(timebuf + len);
+           len += Ustrftime(timebuf + len, sizeof(timebuf) - len, "%b %Y %H:%M:%S",
+             &local);
+           (void) sprintf(CS timebuf + len, " %+03d%02d", diff_hour, diff_min);
+           }
+         break;
+       }
+      }
+    break;
+  }
+
+return timebuf;
+}
+
+/* End of tod.c */
index 92a3b9d..db00d87 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
 
 # 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
 # Philip Hazel, May 1997
 #############################################################################
 
 # Philip Hazel, May 1997
 #############################################################################
 
+use warnings;
+BEGIN { pop @INC if $INC[-1] eq '.' };
+use File::Basename;
+
+if ($ARGV[0] eq '--version') {
+    print basename($0) . ": $0\n",
+        "build: EXIM_RELEASE_VERSIONEXIM_VARIANT_VERSION\n",
+        "perl(runtime): $]\n";
+        exit 0;
+}
 
 # If the filter is called with any arguments, insert them into the message
 # as X-Arg headers, just to verify what they are.
 
 # 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..8ccdd03 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* General functions concerned with transportation, and generic options for all
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* General functions concerned with transportation, and generic options for all
@@ -11,35 +11,13 @@ transports. */
 
 #include "exim.h"
 
 
 #include "exim.h"
 
-#ifdef HAVE_LINUX_SENDFILE
-#include <sys/sendfile.h>
-#endif
-
-/* Structure for keeping list of addresses that have been added to
-Envelope-To:, in order to avoid duplication. */
-
-struct aci {
-  struct aci *next;
-  address_item *ptr;
-  };
-
-
-/* Static data for write_chunk() */
-
-static uschar *chunk_ptr;           /* chunk pointer */
-static uschar *nl_check;            /* string to look for at line start */
-static int     nl_check_length;     /* length of same */
-static uschar *nl_escape;           /* string to insert */
-static int     nl_escape_length;    /* length of same */
-static int     nl_partial_match;    /* length matched at chunk end */
-
-
 /* Generic options for transports, all of which live inside transport_instance
 data blocks and which therefore have the opt_public flag set. Note that there
 are other options living inside this structure which can be set only from
 certain transports. */
 
 optionlist optionlist_transports[] = {
 /* Generic options for transports, all of which live inside transport_instance
 data blocks and which therefore have the opt_public flag set. Note that there
 are other options living inside this structure which can be set only from
 certain transports. */
 
 optionlist optionlist_transports[] = {
+  /*   name            type                                    value */
   { "*expand_group",    opt_stringptr|opt_hidden|opt_public,
                  (void *)offsetof(transport_instance, expand_gid) },
   { "*expand_user",     opt_stringptr|opt_hidden|opt_public,
   { "*expand_group",    opt_stringptr|opt_hidden|opt_public,
                  (void *)offsetof(transport_instance, expand_gid) },
   { "*expand_user",     opt_stringptr|opt_hidden|opt_public,
@@ -66,6 +44,10 @@ optionlist optionlist_transports[] = {
                  (void *)offsetof(transport_instance, driver_name) },
   { "envelope_to_add",   opt_bool|opt_public,
                  (void *)(offsetof(transport_instance, envelope_to_add)) },
                  (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,
   { "group",             opt_expand_gid|opt_public,
                  (void *)offsetof(transport_instance, gid) },
   { "headers_add",      opt_stringptr|opt_public|opt_rep_str,
@@ -80,6 +62,8 @@ optionlist optionlist_transports[] = {
                  (void *)offsetof(transport_instance, home_dir) },
   { "initgroups",       opt_bool|opt_public,
                  (void *)offsetof(transport_instance, initgroups) },
                  (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,
   { "message_size_limit", opt_stringptr|opt_public,
                  (void *)offsetof(transport_instance, message_size_limit) },
   { "rcpt_include_affixes", opt_bool|opt_public,
@@ -94,10 +78,6 @@ optionlist optionlist_transports[] = {
                  (void *)offsetof(transport_instance, shadow_condition) },
   { "shadow_transport", opt_stringptr|opt_public,
                  (void *)offsetof(transport_instance, shadow) },
                  (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,
   { "transport_filter", opt_stringptr|opt_public,
                  (void *)offsetof(transport_instance, filter_command) },
   { "transport_filter_timeout", opt_time|opt_public,
@@ -106,8 +86,47 @@ optionlist optionlist_transports[] = {
                  (void *)offsetof(transport_instance, uid) }
 };
 
                  (void *)offsetof(transport_instance, uid) }
 };
 
-int optionlist_transports_size =
-  sizeof(optionlist_transports)/sizeof(optionlist);
+int optionlist_transports_size = nelem(optionlist_transports);
+
+#ifdef MACRO_PREDEF
+
+# include "macro_predef.h"
+
+void
+options_transports(void)
+{
+struct transport_info * ti;
+uschar buf[64];
+
+options_from_list(optionlist_transports, nelem(optionlist_transports), US"TRANSPORTS", NULL);
+
+for (ti = transports_available; ti->driver_name[0]; ti++)
+  {
+  spf(buf, sizeof(buf), US"_DRIVER_TRANSPORT_%T", ti->driver_name);
+  builtin_macro_create(buf);
+  options_from_list(ti->options, (unsigned)*ti->options_count, US"TRANSPORT", ti->driver_name);
+  }
+}
+
+#else  /*!MACRO_PREDEF*/
+
+/* Structure for keeping list of addresses that have been added to
+Envelope-To:, in order to avoid duplication. */
+
+struct aci {
+  struct aci *next;
+  address_item *ptr;
+  };
+
+
+/* Static data for write_chunk() */
+
+static uschar *chunk_ptr;           /* chunk pointer */
+static uschar *nl_check;            /* string to look for at line start */
+static int     nl_check_length;     /* length of same */
+static uschar *nl_escape;           /* string to insert */
+static int     nl_escape_length;    /* length of same */
+static int     nl_partial_match;    /* length matched at chunk end */
 
 
 /*************************************************
 
 
 /*************************************************
@@ -137,14 +156,11 @@ readconf_driver_init(US"transport",
 /* Now scan the configured transports and check inconsistencies. A shadow
 transport is permitted only for local transports. */
 
 /* 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,
 
   if (t->body_only && t->headers_only)
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
@@ -192,19 +208,21 @@ evermore, so stick a maximum repetition count on the loop to act as a
 longstop.
 
 Arguments:
 longstop.
 
 Arguments:
-  fd        file descriptor to write to
+  tctx      transport context: file descriptor or string to write to
   block     block of bytes to write
   len       number of bytes to write
   block     block of bytes to write
   len       number of bytes to write
+  more     further data expected soon
 
 Returns:    TRUE on success, FALSE on failure (with errno preserved);
               transport_count is incremented by the number of bytes written
 */
 
 
 Returns:    TRUE on success, FALSE on failure (with errno preserved);
               transport_count is incremented by the number of bytes written
 */
 
-BOOL
-transport_write_block(int fd, uschar *block, int len)
+static BOOL
+transport_write_block_fd(transport_ctx * tctx, uschar *block, int len, BOOL more)
 {
 int i, rc, save_errno;
 int local_timeout = transport_write_timeout;
 {
 int i, rc, save_errno;
 int local_timeout = transport_write_timeout;
+int fd = tctx->u.fd;
 
 /* This loop is for handling incomplete writes and other retries. In most
 normal cases, it is only ever executed once. */
 
 /* This loop is for handling incomplete writes and other retries. In most
 normal cases, it is only ever executed once. */
@@ -212,8 +230,8 @@ normal cases, it is only ever executed once. */
 for (i = 0; i < 100; i++)
   {
   DEBUG(D_transport)
 for (i = 0; i < 100; i++)
   {
   DEBUG(D_transport)
-    debug_printf("writing data block fd=%d size=%d timeout=%d\n",
-      fd, len, local_timeout);
+    debug_printf("writing data block fd=%d size=%d timeout=%d%s\n",
+      fd, len, local_timeout, more ? " (more expected)" : "");
 
   /* This code makes use of alarm() in order to implement the timeout. This
   isn't a very tidy way of doing things. Using non-blocking I/O with select()
 
   /* This code makes use of alarm() in order to implement the timeout. This
   isn't a very tidy way of doing things. Using non-blocking I/O with select()
@@ -222,10 +240,15 @@ for (i = 0; i < 100; i++)
 
   if (transport_write_timeout <= 0)   /* No timeout wanted */
     {
 
   if (transport_write_timeout <= 0)   /* No timeout wanted */
     {
-    #ifdef SUPPORT_TLS
-    if (tls_out.active == fd) rc = tls_write(FALSE, block, len); else
-    #endif
-    rc = write(fd, block, len);
+    rc =
+#ifdef SUPPORT_TLS
+       tls_out.active.sock == fd ? tls_write(tls_out.active.tls_ctx, block, len, more) :
+#endif
+#ifdef MSG_MORE
+       more && !(tctx->options & topt_not_socket)
+         ? send(fd, block, len, MSG_MORE) :
+#endif
+       write(fd, block, len);
     save_errno = errno;
     }
 
     save_errno = errno;
     }
 
@@ -233,13 +256,20 @@ for (i = 0; i < 100; i++)
 
   else
     {
 
   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);
+    ALARM(local_timeout);
+
+    rc =
+#ifdef SUPPORT_TLS
+       tls_out.active.sock == fd ? tls_write(tls_out.active.tls_ctx, block, len, more) :
+#endif
+#ifdef MSG_MORE
+       more && !(tctx->options & topt_not_socket)
+         ? send(fd, block, len, MSG_MORE) :
+#endif
+       write(fd, block, len);
+
     save_errno = errno;
     save_errno = errno;
-    local_timeout = alarm(0);
+    local_timeout = ALARM_CLR(0);
     if (sigalrm_seen)
       {
       errno = ETIMEDOUT;
     if (sigalrm_seen)
       {
       errno = ETIMEDOUT;
@@ -309,6 +339,22 @@ return FALSE;
 }
 
 
 }
 
 
+BOOL
+transport_write_block(transport_ctx * tctx, uschar *block, int len, BOOL more)
+{
+if (!(tctx->options & topt_output_string))
+  return transport_write_block_fd(tctx, block, len, more);
+
+/* Write to expanding-string.  NOTE: not NUL-terminated */
+
+if (!tctx->u.msg)
+  tctx->u.msg = string_get(1024);
+
+tctx->u.msg = string_catn(tctx->u.msg, block, len);
+return TRUE;
+}
+
+
 
 
 /*************************************************
 
 
 /*************************************************
@@ -328,17 +374,31 @@ Returns:      the yield of transport_write_block()
 BOOL
 transport_write_string(int fd, const char *format, ...)
 {
 BOOL
 transport_write_string(int fd, const char *format, ...)
 {
+transport_ctx tctx = {{0}};
+gstring gs = { .size = big_buffer_size, .ptr = 0, .s = big_buffer };
 va_list ap;
 va_list ap;
+
 va_start(ap, format);
 va_start(ap, format);
-if (!string_vformat(big_buffer, big_buffer_size, format, ap))
+if (!string_vformat(&gs, FALSE, format, ap))
   log_write(0, LOG_MAIN|LOG_PANIC_DIE, "overlong formatted string in transport");
 va_end(ap);
   log_write(0, LOG_MAIN|LOG_PANIC_DIE, "overlong formatted string in transport");
 va_end(ap);
-return transport_write_block(fd, big_buffer, Ustrlen(big_buffer));
+tctx.u.fd = fd;
+return transport_write_block(&tctx, gs.s, gs.ptr, FALSE);
 }
 
 
 
 
 }
 
 
 
 
+void
+transport_write_reset(int options)
+{
+if (!(options & topt_continuation)) chunk_ptr = deliver_out_buffer;
+nl_partial_match = -1;
+nl_check_length = nl_escape_length = 0;
+}
+
+
+
 /*************************************************
 *              Write character chunk             *
 *************************************************/
 /*************************************************
 *              Write character chunk             *
 *************************************************/
@@ -352,22 +412,22 @@ Static data is used to handle the case when the last character of the previous
 chunk was NL, or matched part of the data that has to be escaped.
 
 Arguments:
 chunk was NL, or matched part of the data that has to be escaped.
 
 Arguments:
-  fd         file descript to write to
+  tctx       transport context - processing to be done during output,
+               and file descriptor to write to
   chunk      pointer to data to write
   len        length of data to write
   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
 
 In addition, the static nl_xxx variables must be set as required.
 
 Returns:     TRUE on success, FALSE on failure (with errno preserved)
 */
 
 
 In addition, the static nl_xxx variables must be set as required.
 
 Returns:     TRUE on success, FALSE on failure (with errno preserved)
 */
 
-static BOOL
-write_chunk(int fd, uschar *chunk, int len, BOOL use_crlf)
+BOOL
+write_chunk(transport_ctx * tctx, uschar *chunk, int len)
 {
 uschar *start = chunk;
 uschar *end = chunk + 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
 int mlen = DELIVER_OUT_BUFFER_SIZE - nl_escape_length - 2;
 
 /* The assumption is made that the check string will never stretch over move
@@ -406,27 +466,49 @@ possible. */
 
 for (ptr = start; ptr < end; ptr++)
   {
 
 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. */
 
 
   /* 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->options & topt_use_bdat  &&  tctx->chunk_cb)
+      {
+      if (  tctx->chunk_cb(tctx, (unsigned)len, 0) != OK
+        || !transport_write_block(tctx, deliver_out_buffer, len, FALSE)
+        || tctx->chunk_cb(tctx, 0, tc_reap_prev) != OK
+        )
+       return FALSE;
+      }
+    else
+      if (!transport_write_block(tctx, deliver_out_buffer, len, FALSE))
+       return FALSE;
     chunk_ptr = deliver_out_buffer;
     }
 
     chunk_ptr = deliver_out_buffer;
     }
 
+  /* Remove CR before NL if required */
+
+  if (  *ptr == '\r' && ptr[1] == '\n'
+     && !(tctx->options & topt_use_crlf)
+     && f.spool_file_wireformat
+     )
+    ptr++;
+
   if ((ch = *ptr) == '\n')
     {
     int left = end - ptr - 1;  /* count of chars left after NL */
 
     /* Insert CR before NL if required */
 
   if ((ch = *ptr) == '\n')
     {
     int left = end - ptr - 1;  /* count of chars left after NL */
 
     /* Insert CR before NL if required */
 
-    if (use_crlf) *chunk_ptr++ = '\r';
+    if (tctx->options & topt_use_crlf && !f.spool_file_wireformat)
+      *chunk_ptr++ = '\r';
     *chunk_ptr++ = '\n';
     transport_newlines++;
 
     *chunk_ptr++ = '\n';
     transport_newlines++;
 
@@ -510,7 +592,7 @@ at = Ustrrchr(addr->address, '@');
 plen = (addr->prefix == NULL)? 0 : Ustrlen(addr->prefix);
 slen = Ustrlen(addr->suffix);
 
 plen = (addr->prefix == NULL)? 0 : Ustrlen(addr->prefix);
 slen = Ustrlen(addr->suffix);
 
-return string_sprintf("%.*s@%s", (at - addr->address - plen - slen),
+return string_sprintf("%.*s@%s", (int)(at - addr->address - plen - slen),
    addr->address + plen, at + 1);
 }
 
    addr->address + plen, at + 1);
 }
 
@@ -542,15 +624,15 @@ Arguments:
   pplist    address of anchor of the list of addresses not to output
   pdlist    address of anchor of the list of processed addresses
   first     TRUE if this is the first address; set it FALSE afterwards
   pplist    address of anchor of the list of addresses not to output
   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
+             and the file descriptor to write to
 
 Returns:    FALSE if writing failed
 */
 
 static BOOL
 write_env_to(address_item *p, struct aci **pplist, struct aci **pdlist,
 
 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, transport_ctx * tctx)
 {
 address_item *pp;
 struct aci *ppp;
 {
 address_item *pp;
 struct aci *ppp;
@@ -558,8 +640,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. */
 
 /* 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;
 
 ppp = store_get(sizeof(struct aci));
 ppp->next = *pdlist;
@@ -571,19 +652,17 @@ ppp->ptr = p;
 for (pp = p;; pp = pp->parent)
   {
   address_item *dup;
 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, tctx))
+       return FALSE;
+  if (!pp->parent) break;
   }
 
 /* Check to see if we have already output the progenitor. */
 
   }
 
 /* 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. */
 
 
 /* Remember what we have output, and output it. */
 
@@ -592,15 +671,15 @@ ppp->next = *pplist;
 *pplist = ppp;
 ppp->ptr = pp;
 
 *pplist = ppp;
 ppp->ptr = pp;
 
-if (!(*first) && !write_chunk(fd, US",\n ", 3, use_crlf)) return FALSE;
+if (!*first && !write_chunk(tctx, US",\n ", 3)) return FALSE;
 *first = FALSE;
 *first = FALSE;
-return write_chunk(fd, pp->address, Ustrlen(pp->address), use_crlf);
+return write_chunk(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
 
 Globals:
   header_list
@@ -608,61 +687,58 @@ Globals:
 Arguments:
   addr                  (chain of) addresses (for extra headers), or NULL;
                           only the first address is used
 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
 
 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(transport_ctx * tctx,
+  BOOL (*sendfn)(transport_ctx * tctx, uschar * s, int len))
 {
 header_line *h;
 {
 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 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;
   {
   int i;
-  uschar *list = remove_headers;
-
   BOOL include_header = TRUE;
 
   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;
     {
     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;
 
        if (i == 0)
        {
        int len;
 
        if (i == 0)
-         if (!(s = expand_string(s)) && !expand_string_forcedfail)
+         if (!(s = expand_string(s)) && !f.expand_string_forcedfail)
            {
            errno = ERRNO_CHHEADER_FAIL;
            return FALSE;
            }
            {
            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 (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
     }
 
   /* If this header is to be output, try to rewrite it if there are rewriting
@@ -670,14 +746,15 @@ for (h = header_list; h != NULL; h = h->next) if (h->type != htype_old)
 
   if (include_header)
     {
 
   if (include_header)
     {
-    if (rewrite_rules)
+    if (tblock && tblock->rewrite_rules)
       {
       void *reset_point = store_get(0);
       header_line *hh;
 
       {
       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(tctx, hh->text, hh->slen)) return FALSE;
        store_reset(reset_point);
        continue;     /* With the next header line */
        }
        store_reset(reset_point);
        continue;     /* With the next header line */
        }
@@ -685,15 +762,13 @@ for (h = header_list; h != NULL; h = h->next) if (h->type != htype_old)
 
     /* Either no rewriting rules, or it didn't get rewritten */
 
 
     /* Either no rewriting rules, or it didn't get rewritten */
 
-    if (!sendfn(fd, h->text, h->slen, use_crlf)) return FALSE;
+    if (!sendfn(tctx, h->text, h->slen)) return FALSE;
     }
 
   /* Header removed */
 
   else
     }
 
   /* Header removed */
 
   else
-    {
     DEBUG(D_transport) debug_printf("removed header line:\n%s---\n", h->text);
     DEBUG(D_transport) debug_printf("removed header line:\n%s---\n", h->text);
-    }
   }
 
 /* Add on any address-specific headers. If there are multiple addresses,
   }
 
 /* Add on any address-specific headers. If there are multiple addresses,
@@ -710,23 +785,21 @@ Headers added to an address by a router are guaranteed to end with a newline.
 if (addr)
   {
   int i;
 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++)
   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)
        {
       {
       hnext = h->next;
       h->next = hprev;
       hprev = h;
       if (i == 1)
        {
-       if (!sendfn(fd, h->text, h->slen, use_crlf)) return FALSE;
+       if (!sendfn(tctx, h->text, h->slen)) return FALSE;
        DEBUG(D_transport)
          debug_printf("added header line(s):\n%s---\n", h->text);
        }
       }
        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
   }
 
 /* If a string containing additional headers exists it is a newline-sep
@@ -736,24 +809,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. */
 
 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;
 
   {
   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)
        {
       {
       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(tctx, s, len)) return FALSE;
+       if (s[len-1] != '\n' && !sendfn(tctx, US"\n", 1))
          return FALSE;
        DEBUG(D_transport)
          {
          return FALSE;
        DEBUG(D_transport)
          {
@@ -763,11 +831,13 @@ if (add_headers)
          }
        }
       }
          }
        }
       }
+    else if (!f.expand_string_forcedfail)
+      { errno = ERRNO_CHHEADER_FAIL; return FALSE; }
   }
 
 /* Separate headers from body with a blank line */
 
   }
 
 /* Separate headers from body with a blank line */
 
-return sendfn(fd, US"\n", 1, use_crlf);
+return sendfn(tctx, US"\n", 1);
 }
 
 
 }
 
 
@@ -800,30 +870,34 @@ can include timeouts for certain transports, which are requested by setting
 transport_write_timeout non-zero.
 
 Arguments:
 transport_write_timeout non-zero.
 
 Arguments:
-  addr                  (chain of) addresses (for extra headers), or NULL;
+  tctx
+    (fd, msg)          Either and fd, to write the message to,
+                       or a string: if null write message to allocated space
+                       otherwire take content as headers.
+    addr                (chain of) addresses (for extra headers), or NULL;
                           only the first address is used
                           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
+    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
 
 Returns:                TRUE on success; FALSE (with errno) on failure.
                         In addition, the global variable transport_count
@@ -831,54 +905,53 @@ Returns:                TRUE on success; FALSE (with errno) on failure.
 */
 
 static BOOL
 */
 
 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(transport_ctx * tctx, int size_limit)
 {
 {
-int written = 0;
-int len;
-BOOL use_crlf  = (options & topt_use_crlf)  != 0;
+int len, size = 0;
 
 /* Initialize pointer in output buffer. */
 
 
 /* Initialize pointer in output buffer. */
 
-chunk_ptr = deliver_out_buffer;
+transport_write_reset(tctx->options);
 
 /* Set up the data for start-of-line data checking and escaping */
 
 
 /* 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_check_length = Ustrlen(nl_check);
-  nl_escape = escape_string;
+  nl_escape = tctx->escape_string;
   nl_escape_length = Ustrlen(nl_escape);
   }
   nl_escape_length = Ustrlen(nl_escape);
   }
-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. */
 
 
 /* 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
 
 /* Write the headers if required, including any that have to be added. If there
-are header rewriting rules, apply them. */
+are header rewriting rules, apply them.  The datasource is not the -D spoolfile
+so temporarily hide the global that adjusts for its format. */
 
 
-if ((options & topt_no_headers) == 0)
+if (!(tctx->options & topt_no_headers))
   {
   {
+  BOOL save_wireformat = f.spool_file_wireformat;
+  f.spool_file_wireformat = FALSE;
+
   /* Add return-path: if requested. */
 
   /* Add return-path: if requested. */
 
-  if ((options & topt_add_return_path) != 0)
+  if (tctx->options & topt_add_return_path)
     {
     uschar buffer[ADDRESS_MAXLENGTH + 20];
     {
     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);
       return_path);
-    if (!write_chunk(fd, buffer, Ustrlen(buffer), use_crlf)) return FALSE;
+    if (!write_chunk(tctx, buffer, n)) goto bad;
     }
 
   /* Add envelope-to: if requested */
 
     }
 
   /* 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;
     {
     BOOL first = TRUE;
     address_item *p;
@@ -886,293 +959,197 @@ if ((options & topt_no_headers) == 0)
     struct aci *dlist = NULL;
     void *reset_point = store_get(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(tctx, US"Envelope-to: ", 13)) goto bad;
 
     /* 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
 
     /* 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, tctx)) goto bad;
 
     /* Add a final newline and reset the store used for tracking duplicates */
 
 
     /* 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(tctx, US"\n", 1)) goto bad;
     store_reset(reset_point);
     }
 
   /* Add delivery-date: if requested. */
 
     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;
+    uschar * s = tod_stamp(tod_full);
+
+    if (  !write_chunk(tctx, US"Delivery-date: ", 15)
+       || !write_chunk(tctx, s, Ustrlen(s))
+       || !write_chunk(tctx, US"\n", 1)) goto bad;
     }
 
   /* 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
     }
 
   /* 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. */
   addr is not NULL. */
-  if (!transport_headers_send(addr, fd, add_headers, remove_headers, &write_chunk,
-       use_crlf, rewrite_rules, rewrite_existflags))
-    return FALSE;
-  }
-
-/* If the body is required, ensure that the data for check strings (formerly
-the "from hack") is enabled by negating the length if necessary. (It will be
-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)
-  {
-  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 (!transport_headers_send(tctx, &write_chunk))
     {
     {
-    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;
-        }
-      }
+bad:
+    f.spool_file_wireformat = save_wireformat;
+    return FALSE;
     }
 
     }
 
-  /* A read error on the body will have left len == -1 and errno set. */
-
-  if (len != 0) return FALSE;
+  f.spool_file_wireformat = save_wireformat;
   }
 
   }
 
-/* Finished with the check string */
-
-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))
-  return FALSE;
-
-/* Write out any remaining data in the buffer before returning. */
-
-return (len = chunk_ptr - deliver_out_buffer) <= 0 ||
-  transport_write_block(fd, deliver_out_buffer, len);
-}
-
-
-#ifndef DISABLE_DKIM
+/* 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;
 
 
-/***************************************************************************************************
-*    External interface to write the message, while signing it with DKIM and/or Domainkeys         *
-***************************************************************************************************/
+  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  &&  !f.spool_file_wireformat)
+      size += body_linecount;  /* account for CRLF-expansion */
+
+    /* With topt_use_bdat we never do dot-stuffing; no need to
+    account for any expansion due to that. */
+    }
 
 
-/* This function is a wrapper around transport_write_message().
-   It is only called from the smtp transport if DKIM or Domainkeys support
-   is compiled in.  The function sets up a replacement fd into a -K file,
-   then calls the normal function. This way, the exact bits that exim would
-   have put "on the wire" will end up in the file (except for TLS
-   encapsulation, which is the very very last thing). When we are done
-   signing the file, send the signed message down the original fd (or TLS fd).
+  /* 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. */
 
 
-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
+  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(tctx, hsize, 0) != OK
+       || !transport_write_block(tctx, deliver_out_buffer, hsize, FALSE)
+       || tctx->chunk_cb(tctx, 0, tc_reap_prev) != OK
+       )
+      return FALSE;
+    chunk_ptr = deliver_out_buffer;
+    size -= hsize;
+    }
 
 
-Returns:       TRUE on success; FALSE (with errno) for any failure
-*/
+  /* Emit a LAST datachunk command, and unmark the context for further
+  BDAT commands. */
 
 
-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
-  )
-{
-int dkim_fd;
-int save_errno = 0;
-BOOL rc;
-uschar dkim_spool_name[256];
-char sbuf[2048];
-int sread = 0;
-int wwritten = 0;
-uschar *dkim_signature = NULL;
-off_t size = 0;
-
-/* If we can't sign, just call the original function. */
-
-if (!(dkim_private_key && dkim_domain && dkim_selector))
-  return transport_write_message(addr, fd, options,
-           size_limit, add_headers, remove_headers,
-           check_string, escape_string, rewrite_rules,
-           rewrite_existflags);
-
-(void)string_format(dkim_spool_name, 256, "%s/input/%s/%s-%d-K",
-       spool_directory, message_subdir, message_id, (int)getpid());
-
-if ((dkim_fd = Uopen(dkim_spool_name, O_RDWR|O_CREAT|O_TRUNC, SPOOL_MODE)) < 0)
-  {
-  /* Can't create spool file. Ugh. */
-  rc = FALSE;
-  save_errno = errno;
-  goto CLEANUP;
+  if (tctx->chunk_cb(tctx, size, tc_chunk_last) != OK)
+    return FALSE;
+  tctx->options &= ~topt_use_bdat;
   }
 
   }
 
-/* Call original function to write the -K file */
-
-rc = transport_write_message(addr, dkim_fd, options,
-  size_limit, add_headers, remove_headers,
-  check_string, escape_string, rewrite_rules,
-  rewrite_existflags);
+/* If the body is required, ensure that the data for check strings (formerly
+the "from hack") is enabled by negating the length if necessary. (It will be
+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. */
 
 
-/* Save error state. We must clean up before returning. */
-if (!rc)
+/* If we have a wireformat -D file (CRNL lines, non-dotstuffed, no ending dot)
+and we want to send a body without dotstuffing or ending-dot, in-clear,
+then we can just dump it using sendfile.
+This should get used for CHUNKING output and also for writing the -K file for
+dkim signing,  when we had CHUNKING input.  */
+
+#ifdef OS_SENDFILE
+if (  f.spool_file_wireformat
+   && !(tctx->options & (topt_no_body | topt_end_dot))
+   && !nl_check_length
+   && tls_out.active.sock != tctx->u.fd
+   )
   {
   {
-  save_errno = errno;
-  goto CLEANUP;
-  }
+  ssize_t copied = 0;
+  off_t offset = SPOOL_DATA_START_OFFSET;
 
 
-if (dkim_private_key && dkim_domain && dkim_selector)
-  {
-  /* Rewind file and feed it to the goats^W DKIM lib */
-  lseek(dkim_fd, 0, SEEK_SET);
-  dkim_signature = dkim_exim_sign(dkim_fd,
-                                 dkim_private_key,
-                                 dkim_domain,
-                                 dkim_selector,
-                                 dkim_canon,
-                                 dkim_sign_headers);
-  if (!dkim_signature)
-    {
-    if (dkim_strict)
-      {
-      uschar *dkim_strict_result = expand_string(dkim_strict);
-      if (dkim_strict_result)
-       if ( (strcmpic(dkim_strict,US"1") == 0) ||
-            (strcmpic(dkim_strict,US"true") == 0) ) 
-         {
-         /* Set errno to something halfway meaningful */
-         save_errno = EACCES;
-         log_write(0, LOG_MAIN, "DKIM: message could not be signed,"
-           " and dkim_strict is set. Deferring message delivery.");
-         rc = FALSE;
-         goto CLEANUP;
-         }
-      }
-    }
-  else
+  /* Write out any header data in the buffer */
+
+  if ((len = chunk_ptr - deliver_out_buffer) > 0)
     {
     {
-    int siglen = Ustrlen(dkim_signature);
-    while(siglen > 0)
-      {
-#ifdef SUPPORT_TLS
-      wwritten = tls_out.active == fd
-       ? tls_write(FALSE, dkim_signature, siglen)
-       : write(fd, dkim_signature, siglen);
-#else
-      wwritten = write(fd, dkim_signature, siglen);
-#endif
-      if (wwritten == -1)
-        {
-       /* error, bail out */
-       save_errno = errno;
-       rc = FALSE;
-       goto CLEANUP;
-       }
-      siglen -= wwritten;
-      dkim_signature += wwritten;
-      }
+    if (!transport_write_block(tctx, deliver_out_buffer, len, TRUE))
+      return FALSE;
+    size -= len;
     }
     }
-  }
 
 
-/* Fetch file size */
-size = lseek(dkim_fd, 0, SEEK_END);
+  DEBUG(D_transport) debug_printf("using sendfile for body\n");
 
 
-/* Rewind file */
-lseek(dkim_fd, 0, SEEK_SET);
-
-#ifdef HAVE_LINUX_SENDFILE
-/* We can use sendfile() to shove the file contents
-   to the socket. However only if we don't use TLS,
-   as then there's another layer of indirection
-   before the data finally hits the socket. */
-if (tls_out.active != fd)
-  {
-  ssize_t copied = 0;
-  off_t offset = 0;
-  while(copied >= 0 && offset < size)
-    copied = sendfile(fd, dkim_fd, &offset, size - offset);
-  if (copied < 0)
+  while(size > 0)
     {
     {
-    save_errno = errno;
-    rc = FALSE;
+    if ((copied = os_sendfile(tctx->u.fd, deliver_datafile, &offset, size)) <= 0) break;
+    size -= copied;
     }
     }
-  goto CLEANUP;
+  return copied >= 0;
   }
   }
+#else
+DEBUG(D_transport) debug_printf("cannot use sendfile for body: no support\n");
 #endif
 
 #endif
 
-/* Send file down the original fd */
-while((sread = read(dkim_fd, sbuf, 2048)) > 0)
+DEBUG(D_transport)
+  if (!(tctx->options & topt_no_body))
+    debug_printf("cannot use sendfile for body: %s\n",
+      !f.spool_file_wireformat ? "spoolfile not wireformat"
+      : tctx->options & topt_end_dot ? "terminating dot wanted"
+      : nl_check_length ? "dot- or From-stuffing wanted"
+      : "TLS output wanted");
+
+if (!(tctx->options & topt_no_body))
   {
   {
-  char *p = sbuf;
-  /* write the chunk */
+  int size = size_limit;
 
 
-  while (sread)
+  nl_check_length = abs(nl_check_length);
+  nl_partial_match = 0;
+  if (lseek(deliver_datafile, SPOOL_DATA_START_OFFSET, SEEK_SET) < 0)
+    return FALSE;
+  while (  (len = MAX(DELIVER_IN_BUFFER_SIZE, size)) > 0
+       && (len = read(deliver_datafile, deliver_in_buffer, len)) > 0)
     {
     {
-#ifdef SUPPORT_TLS
-    wwritten = tls_out.active == fd
-      ? tls_write(FALSE, US p, sread)
-      : write(fd, p, sread);
-#else
-    wwritten = write(fd, p, sread);
-#endif
-    if (wwritten == -1)
-      {
-      /* error, bail out */
-      save_errno = errno;
-      rc = FALSE;
-      goto CLEANUP;
-      }
-    p += wwritten;
-    sread -= wwritten;
+    if (!write_chunk(tctx, deliver_in_buffer, len))
+      return FALSE;
+    size -= len;
     }
     }
-  }
 
 
-if (sread == -1)
-  {
-  save_errno = errno;
-  rc = FALSE;
-  goto CLEANUP;
+  /* A read error on the body will have left len == -1 and errno set. */
+
+  if (len != 0) return FALSE;
   }
 
   }
 
-CLEANUP:
-/* unlink -K file */
-(void)close(dkim_fd);
-Uunlink(dkim_spool_name);
-errno = save_errno;
-return rc;
+/* Finished with the check string, and spool-format consideration */
+
+nl_check_length = nl_escape_length = 0;
+f.spool_file_wireformat = FALSE;
+
+/* If requested, add a terminating "." line (SMTP output). */
+
+if (tctx->options & topt_end_dot && !write_chunk(tctx, US".\n", 2))
+  return FALSE;
+
+/* Write out any remaining data in the buffer before returning. */
+
+return (len = chunk_ptr - deliver_out_buffer) <= 0 ||
+  transport_write_block(tctx, deliver_out_buffer, len, FALSE);
 }
 
 }
 
-#endif
 
 
 
 
 
 
@@ -1184,7 +1161,8 @@ return rc;
 the real work, passing over all the arguments from this function. Otherwise,
 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
 the real work, passing over all the arguments from this function. Otherwise,
 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.
+down the fd in the transport context. At the end, tidy up the pipes and the
+processes.
 
 Arguments:     as for internal_transport_write_message() above
 
 
 Arguments:     as for internal_transport_write_message() above
 
@@ -1193,39 +1171,36 @@ Returns:       TRUE on success; FALSE (with errno) for any failure
 */
 
 BOOL
 */
 
 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(transport_ctx * tctx, int size_limit)
 {
 {
-BOOL use_crlf;
 BOOL last_filter_was_NL = TRUE;
 BOOL last_filter_was_NL = TRUE;
+BOOL save_spool_file_wireformat = f.spool_file_wireformat;
 int rc, len, yield, fd_read, fd_write, save_errno;
 int rc, len, yield, fd_read, fd_write, save_errno;
-int pfd[2];
+int pfd[2] = {-1, -1};
 pid_t filter_pid, write_pid;
 
 pid_t filter_pid, write_pid;
 
-transport_filter_timed_out = FALSE;
+f.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 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(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. */
 
 
 /* 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;
 
 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_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;
   nl_escape_length = Ustrlen(nl_escape);
   }
 else nl_check_length = nl_escape_length = 0;
@@ -1242,14 +1217,17 @@ save_errno = 0;
 yield = FALSE;
 write_pid = (pid_t)(-1);
 
 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(tctx->u.fd, F_GETFD);
+  (void)fcntl(tctx->u.fd, F_SETFD, bits | FD_CLOEXEC);
+  filter_pid = child_open(USS transport_filter_argv, NULL, 077,
+   &fd_write, &fd_read, FALSE);
+  (void)fcntl(tctx->u.fd, F_SETFD, bits & ~FD_CLOEXEC);
+  }
 if (filter_pid < 0) goto TIDY_UP;      /* errno set */
 
 DEBUG(D_transport)
 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
     (int)filter_pid, fd_write, fd_read);
 
 /* Fork subprocess to write the message to the filter, and return the result
@@ -1263,16 +1241,21 @@ if ((write_pid = fork()) == 0)
   (void)close(fd_read);
   (void)close(pfd[pipe_read]);
   nl_check_length = nl_escape_length = 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->u.fd = fd_write;
+  tctx->check_string = tctx->escape_string = NULL;
+  tctx->options &= ~(topt_use_crlf | topt_end_dot | topt_use_bdat);
+
+  rc = internal_transport_write_message(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)
   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)
+     || write(pfd[pipe_write], (void *)&tctx->addr->delivery_usec, sizeof(int))
         != sizeof(int)
      )
     rc = FALSE;        /* compiler quietening */
         != sizeof(int)
      )
     rc = FALSE;        /* compiler quietening */
@@ -1296,7 +1279,7 @@ if (write_pid < 0)
 
 /* When testing, let the subprocess get going */
 
 
 /* When testing, let the subprocess get going */
 
-if (running_in_test_harness) millisleep(250);
+if (f.running_in_test_harness) millisleep(250);
 
 DEBUG(D_transport)
   debug_printf("process %d writing to transport filter\n", (int)write_pid);
 
 DEBUG(D_transport)
   debug_printf("process %d writing to transport filter\n", (int)write_pid);
@@ -1310,20 +1293,22 @@ DEBUG(D_transport) debug_printf("copying from the filter\n");
 
 /* Copy the output of the filter, remembering if the last character was NL. If
 no data is returned, that counts as "ended with NL" (default setting of the
 
 /* Copy the output of the filter, remembering if the last character was NL. If
 no data is returned, that counts as "ended with NL" (default setting of the
-variable is TRUE). */
+variable is TRUE).  The output should always be unix-format as we converted
+any wireformat source on writing input to the filter. */
 
 
+f.spool_file_wireformat = FALSE;
 chunk_ptr = deliver_out_buffer;
 
 for (;;)
   {
   sigalrm_seen = FALSE;
 chunk_ptr = deliver_out_buffer;
 
 for (;;)
   {
   sigalrm_seen = FALSE;
-  alarm(transport_filter_timeout);
+  ALARM(transport_filter_timeout);
   len = read(fd_read, deliver_in_buffer, DELIVER_IN_BUFFER_SIZE);
   len = read(fd_read, deliver_in_buffer, DELIVER_IN_BUFFER_SIZE);
-  alarm(0);
+  ALARM_CLR(0);
   if (sigalrm_seen)
     {
     errno = ETIMEDOUT;
   if (sigalrm_seen)
     {
     errno = ETIMEDOUT;
-    transport_filter_timed_out = TRUE;
+    f.transport_filter_timed_out = TRUE;
     goto TIDY_UP;
     }
 
     goto TIDY_UP;
     }
 
@@ -1332,7 +1317,7 @@ for (;;)
 
   if (len > 0)
     {
 
   if (len > 0)
     {
-    if (!write_chunk(fd, deliver_in_buffer, len, use_crlf)) goto TIDY_UP;
+    if (!write_chunk(tctx, deliver_in_buffer, len)) goto TIDY_UP;
     last_filter_was_NL = (deliver_in_buffer[len-1] == '\n');
     }
 
     last_filter_was_NL = (deliver_in_buffer[len-1] == '\n');
     }
 
@@ -1351,6 +1336,7 @@ there has been an error, kill the processes before waiting for them, just to be
 sure. Also apply a paranoia timeout. */
 
 TIDY_UP:
 sure. Also apply a paranoia timeout. */
 
 TIDY_UP:
+f.spool_file_wireformat = save_spool_file_wireformat;
 save_errno = errno;
 
 (void)close(fd_read);
 save_errno = errno;
 
 (void)close(fd_read);
@@ -1369,7 +1355,7 @@ if (filter_pid > 0 && (rc = child_close(filter_pid, 30)) != 0 && yield)
   {
   yield = FALSE;
   save_errno = ERRNO_FILTER_FAIL;
   {
   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);
   }
 
   DEBUG(D_transport) debug_printf("filter process returned %d\n", rc);
   }
 
@@ -1382,15 +1368,22 @@ if (write_pid > 0)
   {
   rc = child_close(write_pid, 30);
   if (yield)
   {
   rc = child_close(write_pid, 30);
   if (yield)
-    {
     if (rc == 0)
       {
       BOOL ok;
     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));
+        dummy = read(pfd[pipe_read], (void *)&tctx->addr->delivery_usec, sizeof(int));
+       dummy = dummy;          /* compiler quietening */
         yield = FALSE;
         }
       }
         yield = FALSE;
         }
       }
@@ -1398,10 +1391,9 @@ if (write_pid > 0)
       {
       yield = FALSE;
       save_errno = ERRNO_FILTER_FAIL;
       {
       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);
       }
       DEBUG(D_transport) debug_printf("writing process returned %d\n", rc);
       }
-    }
   }
 (void)close(pfd[pipe_read]);
 
   }
 (void)close(pfd[pipe_read]);
 
@@ -1412,28 +1404,28 @@ filter was not NL, insert a NL to make the SMTP protocol work. */
 if (yield)
   {
   nl_check_length = nl_escape_length = 0;
 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)))
-    {
+  f.spool_file_wireformat = FALSE;
+  if (  tctx->options & topt_end_dot
+     && ( last_filter_was_NL
+        ? !write_chunk(tctx, US".\n", 2)
+       : !write_chunk(tctx, US"\n.\n", 3)
+     )  )
     yield = FALSE;
     yield = FALSE;
-    }
 
   /* Write out any remaining data in the buffer. */
 
   else
 
   /* 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(tctx, deliver_out_buffer, len, FALSE);
   }
   }
-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(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;
   }
 
 return yield;
@@ -1481,8 +1473,7 @@ Returns:    nothing
 void
 transport_update_waiting(host_item *hostlist, uschar *tpname)
 {
 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;
 host_item *host;
 open_db dbblock;
 open_db *dbm_file;
@@ -1491,19 +1482,20 @@ DEBUG(D_transport) debug_printf("updating wait-%s database\n", tpname);
 
 /* Open the database for this transport */
 
 
 /* Open the database for this transport */
 
-sprintf(CS buffer, "wait-%.200s", tpname);
-dbm_file = dbfn_open(buffer, O_RDWR, &dbblock, TRUE);
-if (dbm_file == NULL) return;
+if (!(dbm_file = dbfn_open(string_sprintf("wait-%.200s", tpname),
+                     O_RDWR, &dbblock, TRUE)))
+  return;
 
 /* Scan the list of hosts for which this message is waiting, and ensure
 that the message id is in each host record. */
 
 
 /* Scan the list of hosts for which this message is waiting, and ensure
 that the message id is in each host record. */
 
-for (host = hostlist; host!= NULL; host = host->next)
+for (host = hostlist; host; host = host->next)
   {
   BOOL already = FALSE;
   dbdata_wait *host_record;
   uschar *s;
   int i, host_length;
   {
   BOOL already = FALSE;
   dbdata_wait *host_record;
   uschar *s;
   int i, host_length;
+  uschar buffer[256];
 
   /* Skip if this is the same host as we just processed; otherwise remember
   the name for next time. */
 
   /* Skip if this is the same host as we just processed; otherwise remember
   the name for next time. */
@@ -1513,8 +1505,7 @@ for (host = hostlist; host!= NULL; host = host->next)
 
   /* Look up the host record; if there isn't one, make an empty one. */
 
 
   /* Look up the host record; if there isn't one, make an empty one. */
 
-  host_record = dbfn_read(dbm_file, host->name);
-  if (host_record == NULL)
+  if (!(host_record = dbfn_read(dbm_file, host->name)))
     {
     host_record = store_get(sizeof(dbdata_wait) + MESSAGE_ID_LENGTH);
     host_record->count = host_record->sequence = 0;
     {
     host_record = store_get(sizeof(dbdata_wait) + MESSAGE_ID_LENGTH);
     host_record->count = host_record->sequence = 0;
@@ -1528,10 +1519,8 @@ for (host = hostlist; host!= NULL; host = host->next)
 
   for (s = host_record->text; s < host_record->text + host_length;
        s += MESSAGE_ID_LENGTH)
 
   for (s = host_record->text; s < host_record->text + host_length;
        s += MESSAGE_ID_LENGTH)
-    {
     if (Ustrncmp(s, message_id, MESSAGE_ID_LENGTH) == 0)
       { already = TRUE; break; }
     if (Ustrncmp(s, message_id, MESSAGE_ID_LENGTH) == 0)
       { already = TRUE; break; }
-    }
 
   /* If we haven't found this message in the main record, search any
   continuation records that exist. */
 
   /* If we haven't found this message in the main record, search any
   continuation records that exist. */
@@ -1540,15 +1529,12 @@ for (host = hostlist; host!= NULL; host = host->next)
     {
     dbdata_wait *cont;
     sprintf(CS buffer, "%.200s:%d", host->name, i);
     {
     dbdata_wait *cont;
     sprintf(CS buffer, "%.200s:%d", host->name, i);
-    cont = dbfn_read(dbm_file, buffer);
-    if (cont != NULL)
+    if ((cont = dbfn_read(dbm_file, buffer)))
       {
       int clen = cont->count * MESSAGE_ID_LENGTH;
       for (s = cont->text; s < cont->text + clen; s += MESSAGE_ID_LENGTH)
       {
       int clen = cont->count * MESSAGE_ID_LENGTH;
       for (s = cont->text; s < cont->text + clen; s += MESSAGE_ID_LENGTH)
-        {
         if (Ustrncmp(s, message_id, MESSAGE_ID_LENGTH) == 0)
           { already = TRUE; break; }
         if (Ustrncmp(s, message_id, MESSAGE_ID_LENGTH) == 0)
           { already = TRUE; break; }
-        }
       }
     }
 
       }
     }
 
@@ -1623,19 +1609,30 @@ 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
                        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
 */
 
 
 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
 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;
 {
 dbdata_wait *host_record;
-int host_length, path_len;
+int host_length;
 open_db dbblock;
 open_db *dbm_file;
 open_db dbblock;
 open_db *dbm_file;
-uschar buffer[256];
+
+int         i;
+struct stat statbuf;
 
 *more = FALSE;
 
 
 *more = FALSE;
 
@@ -1659,14 +1656,13 @@ if (local_message_max > 0 && continue_sequence >= local_message_max)
 
 /* Open the waiting information database. */
 
 
 /* Open the waiting information database. */
 
-sprintf(CS buffer, "wait-%.200s", transport_name);
-dbm_file = dbfn_open(buffer, O_RDWR, &dbblock, TRUE);
-if (dbm_file == NULL) return FALSE;
+if (!(dbm_file = dbfn_open(string_sprintf("wait-%.200s", transport_name),
+                         O_RDWR, &dbblock, TRUE)))
+  return FALSE;
 
 /* See if there is a record for this host; if not, there's nothing to do. */
 
 
 /* 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);
   {
   dbfn_close(dbm_file);
   DEBUG(D_transport) debug_printf("no messages waiting for %s\n", hostname);
@@ -1689,58 +1685,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.
 */
 
 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);
       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 */
 
 
-    /* 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. */
+  for (i = 0; i < msgq_count; ++i)
+    if (Ustrcmp(msgq[i].message_id, message_id) == 0)
+      {
+      msgq[i].bKeep = FALSE;
+      break;
+      }
 
 
-    if (Ustrcmp(new_message_id, message_id) != 0 &&
-        Ustat(buffer, &statbuf) == 0)
+  /* now find the next acceptable message_id */
+
+  for (i = msgq_count - 1; i >= 0; --i) if (msgq[i].bKeep)
+    {
+    uschar subdir[2];
+
+    subdir[0] = split_spool_directory ? msgq[i].message_id[5] : 0;
+    subdir[1] = 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;
       }
     }
 
       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;
+      }
+    }
+
+  /* Check for a continuation record. */
 
   while (host_length <= 0)
     {
     int i;
 
   while (host_length <= 0)
     {
     int i;
-    dbdata_wait *newr = NULL;
+    dbdata_wait * newr = NULL;
+    uschar buffer[256];
 
     /* Search for a continuation */
 
 
     /* 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);
       {
       sprintf(CS buffer, "%.200s:%d", hostname, i);
       newr = dbfn_read(dbm_file, buffer);
@@ -1748,7 +1792,7 @@ for (;;)
 
     /* If no continuation, delete the current and break the loop */
 
 
     /* If no continuation, delete the current and break the loop */
 
-    if (newr == NULL)
+    if (!newr)
       {
       dbfn_delete(dbm_file, hostname);
       break;
       {
       dbfn_delete(dbm_file, hostname);
       break;
@@ -1759,11 +1803,15 @@ for (;;)
     dbfn_delete(dbm_file, buffer);
     host_record = newr;
     host_length = host_record->count * MESSAGE_ID_LENGTH;
     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
 
   /* 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 +1823,20 @@ for (;;)
     DEBUG(D_transport) debug_printf("waiting messages already delivered\n");
     return FALSE;
     }
     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
 
 /* 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 +1846,7 @@ record if required, close the database, and return TRUE. */
 if (host_length > 0)
   {
   host_record->count = host_length/MESSAGE_ID_LENGTH;
 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;
   }
   dbfn_write(dbm_file, hostname, host_record, (int)sizeof(dbdata_wait) + host_length);
   *more = TRUE;
   }
@@ -1793,12 +1855,74 @@ dbfn_close(dbm_file);
 return TRUE;
 }
 
 return TRUE;
 }
 
-
-
 /*************************************************
 *    Deliver waiting message down same socket    *
 *************************************************/
 
 /*************************************************
 *    Deliver waiting message down same socket    *
 *************************************************/
 
+/* Just the regain-root-privilege exec portion */
+void
+transport_do_pass_socket(const uschar *transport_name, const uschar *hostname,
+  const uschar *hostaddress, uschar *id, int socket_fd)
+{
+int i = 20;
+const uschar **argv;
+
+/* Set up the calling arguments; use the standard function for the basics,
+but we have a number of extras that may be added. */
+
+argv = CUSS child_exec_exim(CEE_RETURN_ARGV, TRUE, &i, FALSE, 0);
+
+if (f.smtp_authenticated)                      argv[i++] = US"-MCA";
+if (smtp_peer_options & OPTION_CHUNKING)       argv[i++] = US"-MCK";
+if (smtp_peer_options & OPTION_DSN)            argv[i++] = US"-MCD";
+if (smtp_peer_options & OPTION_PIPE)           argv[i++] = US"-MCP";
+if (smtp_peer_options & OPTION_SIZE)           argv[i++] = US"-MCS";
+#ifdef SUPPORT_TLS
+if (smtp_peer_options & OPTION_TLS)
+  if (tls_out.active.sock >= 0 || continue_proxy_cipher)
+    {
+    argv[i++] = US"-MCt";
+    argv[i++] = sending_ip_address;
+    argv[i++] = string_sprintf("%d", sending_port);
+    argv[i++] = tls_out.active.sock >= 0 ? tls_out.cipher : continue_proxy_cipher;
+    }
+  else
+    argv[i++] = US"-MCT";
+#endif
+
+if (queue_run_pid != (pid_t)0)
+  {
+  argv[i++] = US"-MCQ";
+  argv[i++] = string_sprintf("%d", queue_run_pid);
+  argv[i++] = string_sprintf("%d", queue_run_pipe);
+  }
+
+argv[i++] = US"-MC";
+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;
+
+/* Arrange for the channel to be on stdin. */
+
+if (socket_fd != 0)
+  {
+  (void)dup2(socket_fd, 0);
+  (void)close(socket_fd);
+  }
+
+DEBUG(D_exec) debug_print_argv(argv);
+exim_nullstd();                          /* Ensure std{out,err} exist */
+execv(CS argv[0], (char *const *)argv);
+
+DEBUG(D_any) debug_printf("execv failed: %s\n", strerror(errno));
+_exit(errno);         /* Note: must be _exit(), NOT exit() */
+}
+
+
+
 /* Fork a new exim process to deliver the message, and do a re-exec, both to
 get a clean delivery process, and to regain root privilege in cases where it
 has been given away.
 /* Fork a new exim process to deliver the message, and do a re-exec, both to
 get a clean delivery process, and to regain root privilege in cases where it
 has been given away.
@@ -1814,8 +1938,8 @@ Returns:          FALSE if fork fails; TRUE otherwise
 */
 
 BOOL
 */
 
 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;
 {
 pid_t pid;
 int status;
@@ -1824,65 +1948,20 @@ DEBUG(D_transport) debug_printf("transport_pass_socket entered\n");
 
 if ((pid = fork()) == 0)
   {
 
 if ((pid = fork()) == 0)
   {
-  int i = 16;
-  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,
   write the log, etc., so that the output is always in the same order for
   automatic comparison. */
 
   /* 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,
   write the log, etc., so that the output is always in the same order for
   automatic comparison. */
 
-  if ((pid = fork()) != 0) _exit(EXIT_SUCCESS);
-  if (running_in_test_harness) sleep(1);
-
-  /* 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
-
-  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 (queue_run_pid != (pid_t)0)
-    {
-    argv[i++] = US"-MCQ";
-    argv[i++] = string_sprintf("%d", queue_run_pid);
-    argv[i++] = string_sprintf("%d", queue_run_pipe);
-    }
-
-  argv[i++] = US"-MC";
-  argv[i++] = transport_name;
-  argv[i++] = hostname;
-  argv[i++] = hostaddress;
-  argv[i++] = string_sprintf("%d", continue_sequence + 1);
-  argv[i++] = id;
-  argv[i++] = NULL;
-
-  /* Arrange for the channel to be on stdin. */
-
-  if (socket_fd != 0)
+  if ((pid = fork()) != 0)
     {
     {
-    (void)dup2(socket_fd, 0);
-    (void)close(socket_fd);
+    DEBUG(D_transport) debug_printf("transport_pass_socket succeeded (final-pid %d)\n", pid);
+    _exit(EXIT_SUCCESS);
     }
     }
+  if (f.running_in_test_harness) sleep(1);
 
 
-  DEBUG(D_exec) debug_print_argv(argv);
-  exim_nullstd();                          /* Ensure std{out,err} exist */
-  execv(CS argv[0], (char *const *)argv);
-
-  DEBUG(D_any) debug_printf("execv failed: %s\n", strerror(errno));
-  _exit(errno);         /* Note: must be _exit(), NOT exit() */
+  transport_do_pass_socket(transport_name, hostname, hostaddress,
+    id, socket_fd);
   }
 
 /* If the process creation succeeded, wait for the first-level child, which
   }
 
 /* If the process creation succeeded, wait for the first-level child, which
@@ -1893,7 +1972,7 @@ if (pid > 0)
   {
   int rc;
   while ((rc = wait(&status)) != pid && (rc >= 0 || errno != ECHILD));
   {
   int rc;
   while ((rc = wait(&status)) != pid && (rc >= 0 || errno != ECHILD));
-  DEBUG(D_transport) debug_printf("transport_pass_socket succeeded\n");
+  DEBUG(D_transport) debug_printf("transport_pass_socket succeeded (inter-pid %d)\n", pid);
   return TRUE;
   }
 else
   return TRUE;
   }
 else
@@ -1918,7 +1997,7 @@ case, no addresses are passed.
 
 Arguments:
   argvptr            pointer to anchor for argv vector
 
 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
   expand_arguments   true if expansion is to occur
   expand_failed      error value to set if expansion fails; not relevant if
                      addr == NULL
@@ -1932,11 +2011,12 @@ Returns:             TRUE if all went well; otherwise an error will be
 */
 
 BOOL
 */
 
 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;
 {
 address_item *ad;
-uschar **argv;
+const uschar **argv;
 uschar *s, *ss;
 int address_count = 0;
 int argcount = 0;
 uschar *s, *ss;
 int address_count = 0;
 int argcount = 0;
@@ -1970,11 +2050,11 @@ while (*s != 0 && argcount < max_args)
     if (*s != 0) s++;
     *ss++ = 0;
     }
     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++;
   }
 
   while (isspace(*s)) s++;
   }
 
-argv[argcount] = (uschar *)0;
+argv[argcount] = US 0;
 
 /* If *s != 0 we have run out of argument slots. */
 
 
 /* If *s != 0 we have run out of argument slots. */
 
@@ -2010,7 +2090,7 @@ $recipients. */
 DEBUG(D_transport)
   {
   debug_printf("direct command:\n");
 DEBUG(D_transport)
   {
   debug_printf("direct command:\n");
-  for (i = 0; argv[i] != (uschar *)0; i++)
+  for (i = 0; argv[i] != US 0; i++)
     debug_printf("  argv[%d] = %s\n", i, string_printing(argv[i]));
   }
 
     debug_printf("  argv[%d] = %s\n", i, string_printing(argv[i]));
   }
 
@@ -2020,7 +2100,7 @@ if (expand_arguments)
     addr->parent != NULL &&
     Ustrcmp(addr->parent->address, "system-filter") == 0;
 
     addr->parent != NULL &&
     Ustrcmp(addr->parent->address, "system-filter") == 0;
 
-  for (i = 0; argv[i] != (uschar *)0; i++)
+  for (i = 0; argv[i] != US 0; i++)
     {
 
     /* Handle special fudge for passing an address list */
     {
 
     /* Handle special fudge for passing an address list */
@@ -2099,11 +2179,12 @@ if (expand_arguments)
           if (*s != 0) s++;
           *ss++ = 0;
           }
           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 */
         }
 
         while (isspace(*s)) s++; /* strip space after arg */
         }
 
-      address_pipe_argv[address_pipe_argcount] = (uschar *)0;
+      address_pipe_argv[address_pipe_argcount] = US 0;
 
       /* If *s != 0 we have run out of argument slots. */
       if (*s != 0)
 
       /* If *s != 0 we have run out of argument slots. */
       if (*s != 0)
@@ -2139,7 +2220,7 @@ if (expand_arguments)
        */
       if (address_pipe_argcount > 1)
         memmove(
        */
       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,
           argv + i + address_pipe_argcount,
           /* current position + 1 (for the (uschar *)0 at the end) */
           argv + i + 1,
@@ -2151,7 +2232,7 @@ if (expand_arguments)
        * [argv 0][argv 1][argv 2=pipeargv[0]][argv 3=pipeargv[1]][old argv 3][0]
        */
       for (address_pipe_i = 0;
        * [argv 0][argv 1][argv 2=pipeargv[0]][argv 3=pipeargv[1]][old argv 3][0]
        */
       for (address_pipe_i = 0;
-           address_pipe_argv[address_pipe_i] != (uschar *)0;
+           address_pipe_argv[address_pipe_i] != US 0;
            address_pipe_i++)
         {
         argv[i++] = address_pipe_argv[address_pipe_i];
            address_pipe_i++)
         {
         argv[i++] = address_pipe_argv[address_pipe_i];
@@ -2167,10 +2248,10 @@ if (expand_arguments)
 
     else
       {
 
     else
       {
-      uschar *expanded_arg;
-      enable_dollar_recipients = allow_dollar_recipients;
-      expanded_arg = expand_string(argv[i]);
-      enable_dollar_recipients = FALSE;
+      const uschar *expanded_arg;
+      f.enable_dollar_recipients = allow_dollar_recipients;
+      expanded_arg = expand_cstring(argv[i]);
+      f.enable_dollar_recipients = FALSE;
 
       if (expanded_arg == NULL)
         {
 
       if (expanded_arg == NULL)
         {
@@ -2192,7 +2273,7 @@ if (expand_arguments)
   DEBUG(D_transport)
     {
     debug_printf("direct command after expansion:\n");
   DEBUG(D_transport)
     {
     debug_printf("direct command after expansion:\n");
-    for (i = 0; argv[i] != (uschar *)0; i++)
+    for (i = 0; argv[i] != US 0; i++)
       debug_printf("  argv[%d] = %s\n", i, string_printing(argv[i]));
     }
   }
       debug_printf("  argv[%d] = %s\n", i, string_printing(argv[i]));
     }
   }
@@ -2200,6 +2281,7 @@ if (expand_arguments)
 return TRUE;
 }
 
 return TRUE;
 }
 
+#endif /*!MACRO_PREDEF*/
 /* vi: aw ai sw=2
 */
 /* End of transport.c */
 /* vi: aw ai sw=2
 */
 /* End of transport.c */
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.
 
 # 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
 
 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
 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.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
 
 
 tf_maildir.o:    $(HDRS) tf_maildir.c tf_maildir.h appendfile.h
 
index f56862b..522115d 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
 /* See the file NOTICE for conditions of use and distribution. */
 
 
 #endif
 
 
 #endif
 
 
-/* Encodings for mailbox formats, and their names. MBX format is actually
-supported only if SUPPORT_MBX is set. */
-
-enum { mbf_unix, mbf_mbx, mbf_smail, mbf_maildir, mbf_mailstore };
-
-static const char *mailbox_formats[] = {
-  "unix", "mbx", "smail", "maildir", "mailstore" };
-
-
-/* Check warn threshold only if quota size set or not a percentage threshold
-   percentage check should only be done if quota > 0 */
-
-#define THRESHOLD_CHECK  (ob->quota_warn_threshold_value > 0 && \
-  (!ob->quota_warn_threshold_is_percent || ob->quota_value > 0))
-
-
 /* 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
 /* 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
@@ -170,6 +154,16 @@ address can appear in the tables drtables.c. */
 int appendfile_transport_options_count =
   sizeof(appendfile_transport_options)/sizeof(optionlist);
 
 int appendfile_transport_options_count =
   sizeof(appendfile_transport_options)/sizeof(optionlist);
 
+
+#ifdef MACRO_PREDEF
+
+/* Dummy values */
+appendfile_transport_options_block appendfile_transport_option_defaults = {0};
+void appendfile_transport_init(transport_instance *tblock) {}
+BOOL appendfile_transport_entry(transport_instance *tblock, address_item *addr) {return FALSE;}
+
+#else  /*!MACRO_PREDEF*/
+
 /* Default private options block for the appendfile transport. */
 
 appendfile_transport_options_block appendfile_transport_option_defaults = {
 /* Default private options block for the appendfile transport. */
 
 appendfile_transport_options_block appendfile_transport_option_defaults = {
@@ -186,7 +180,7 @@ appendfile_transport_options_block appendfile_transport_option_defaults = {
   NULL,           /* quota_warn_threshold */
   NULL,           /* mailbox_size_string */
   NULL,           /* mailbox_filecount_string */
   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 */
   US"^(?:cur|new|\\..*)$",  /* maildir_dir_regex */
   NULL,           /* maildir_tag */
   NULL,           /* maildirfolder_create_regex */
@@ -234,10 +228,28 @@ appendfile_transport_options_block appendfile_transport_option_defaults = {
   FALSE,          /* mailstore_format */
   FALSE,          /* mbx_format */
   FALSE,          /* quota_warn_threshold_is_percent */
   FALSE,          /* mailstore_format */
   FALSE,          /* mbx_format */
   FALSE,          /* quota_warn_threshold_is_percent */
-  TRUE            /* quota_is_inclusive */
+  TRUE,           /* quota_is_inclusive */
+  FALSE,          /* quota_no_check */
+  FALSE           /* quota_filecount_no_check */
 };
 
 
 };
 
 
+/* Encodings for mailbox formats, and their names. MBX format is actually
+supported only if SUPPORT_MBX is set. */
+
+enum { mbf_unix, mbf_mbx, mbf_smail, mbf_maildir, mbf_mailstore };
+
+static const char *mailbox_formats[] = {
+  "unix", "mbx", "smail", "maildir", "mailstore" };
+
+
+/* Check warn threshold only if quota size set or not a percentage threshold
+   percentage check should only be done if quota > 0 */
+
+#define THRESHOLD_CHECK  (ob->quota_warn_threshold_value > 0 && \
+  (!ob->quota_warn_threshold_is_percent || ob->quota_value > 0))
+
+
 
 /*************************************************
 *              Setup entry point                 *
 
 /*************************************************
 *              Setup entry point                 *
@@ -276,7 +288,7 @@ uid = uid;
 gid = gid;
 
 if (ob->expand_maildir_use_size_file)
 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,
                US"`maildir_use_size_file` in transport", tblock->name);
 
 /* Loop for quota, quota_filecount, quota_warn_threshold, mailbox_size,
@@ -285,18 +297,20 @@ mailbox_filecount */
 for (i = 0; i < 5; i++)
   {
   double d;
 for (i = 0; i < 5; i++)
   {
   double d;
+  int no_check = 0;
   uschar *which = NULL;
 
   uschar *which = NULL;
 
-  if (q == NULL) d = default_value; else
+  if (q == NULL) d = default_value;
+  else
     {
     uschar *rest;
     uschar *s = expand_string(q);
 
     {
     uschar *rest;
     uschar *s = expand_string(q);
 
-    if (s == NULL)
+    if (!s)
       {
       *errmsg = string_sprintf("Expansion of \"%s\" in %s transport failed: "
         "%s", q, tblock->name, expand_string_message);
       {
       *errmsg = string_sprintf("Expansion of \"%s\" in %s transport failed: "
         "%s", q, tblock->name, expand_string_message);
-      return search_find_defer? DEFER : FAIL;
+      return f.search_find_defer ? DEFER : FAIL;
       }
 
     d = Ustrtod(s, &rest);
       }
 
     d = Ustrtod(s, &rest);
@@ -310,7 +324,8 @@ for (i = 0; i < 5; i++)
     else if (tolower(*rest) == 'g') { d *= 1024.0*1024.0*1024.0; rest++; }
     else if (*rest == '%' && i == 2)
       {
     else if (tolower(*rest) == 'g') { d *= 1024.0*1024.0*1024.0; rest++; }
     else if (*rest == '%' && i == 2)
       {
-      if (ob->quota_value <= 0 && !ob->maildir_use_size_file) d = 0;
+      if (ob->quota_value <= 0 && !ob->maildir_use_size_file)
+       d = 0;
       else if ((int)d < 0 || (int)d > 100)
         {
         *errmsg = string_sprintf("Invalid quota_warn_threshold percentage (%d)"
       else if ((int)d < 0 || (int)d > 100)
         {
         *errmsg = string_sprintf("Invalid quota_warn_threshold percentage (%d)"
@@ -321,6 +336,15 @@ for (i = 0; i < 5; i++)
       rest++;
       }
 
       rest++;
       }
 
+
+    /* For quota and quota_filecount there may be options
+    appended. Currently only "no_check", so we can be lazy parsing it */
+    if (i < 2 && Ustrstr(rest, "/no_check") == rest)
+      {
+       no_check = 1;
+       rest += sizeof("/no_check") - 1;
+      }
+
     while (isspace(*rest)) rest++;
 
     if (*rest != 0)
     while (isspace(*rest)) rest++;
 
     if (*rest != 0)
@@ -336,39 +360,44 @@ for (i = 0; i < 5; i++)
   switch (i)
     {
     case 0:
   switch (i)
     {
     case 0:
-    if (d >= 2.0*1024.0*1024.0*1024.0 && sizeof(off_t) <= 4) which = US"quota";
-    ob->quota_value = (off_t)d;
-    q = ob->quota_filecount;
-    break;
+      if (d >= 2.0*1024.0*1024.0*1024.0 && sizeof(off_t) <= 4)
+       which = US"quota";
+      ob->quota_value = (off_t)d;
+      ob->quota_no_check = no_check;
+      q = ob->quota_filecount;
+      break;
 
     case 1:
 
     case 1:
-    if (d >= 2.0*1024.0*1024.0*1024.0) which = US"quota_filecount";
-    ob->quota_filecount_value = (int)d;
-    q = ob->quota_warn_threshold;
-    break;
+      if (d >= 2.0*1024.0*1024.0*1024.0)
+       which = US"quota_filecount";
+      ob->quota_filecount_value = (int)d;
+      ob->quota_filecount_no_check = no_check;
+      q = ob->quota_warn_threshold;
+      break;
 
     case 2:
     if (d >= 2.0*1024.0*1024.0*1024.0 && sizeof(off_t) <= 4)
 
     case 2:
     if (d >= 2.0*1024.0*1024.0*1024.0 && sizeof(off_t) <= 4)
-      which = US"quota_warn_threshold";
-    ob->quota_warn_threshold_value = (off_t)d;
-    q = ob->mailbox_size_string;
-    default_value = -1.0;
-    break;
+       which = US"quota_warn_threshold";
+      ob->quota_warn_threshold_value = (off_t)d;
+      q = ob->mailbox_size_string;
+      default_value = -1.0;
+      break;
 
     case 3:
 
     case 3:
-    if (d >= 2.0*1024.0*1024.0*1024.0 && sizeof(off_t) <= 4)
-      which = US"mailbox_size";;
-    ob->mailbox_size_value = (off_t)d;
-    q = ob->mailbox_filecount_string;
-    break;
+      if (d >= 2.0*1024.0*1024.0*1024.0 && sizeof(off_t) <= 4)
+       which = US"mailbox_size";;
+      ob->mailbox_size_value = (off_t)d;
+      q = ob->mailbox_filecount_string;
+      break;
 
     case 4:
 
     case 4:
-    if (d >= 2.0*1024.0*1024.0*1024.0) which = US"mailbox_filecount";
-    ob->mailbox_filecount_value = (int)d;
-    break;
+      if (d >= 2.0*1024.0*1024.0*1024.0)
+       which = US"mailbox_filecount";
+      ob->mailbox_filecount_value = (int)d;
+      break;
     }
 
     }
 
-  if (which != NULL)
+  if (which)
     {
     *errmsg = string_sprintf("%s value %.10g is too large (overflow) in "
       "%s transport", which, d, tblock->name);
     {
     *errmsg = string_sprintf("%s value %.10g is too large (overflow) in "
       "%s transport", which, d, tblock->name);
@@ -550,12 +579,12 @@ else if (ob->dirname == NULL && !ob->maildir_format && !ob->mailstore_format)
 driver options. Only one of body_only and headers_only can be set. */
 
 ob->options |=
 driver options. Only one of body_only and headers_only can be set. */
 
 ob->options |=
-  (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) |
-  ((ob->use_crlf || ob->mbx_format)? topt_use_crlf : 0);
+  (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) |
+  ((ob->use_crlf || ob->mbx_format) ? topt_use_crlf : 0);
 }
 
 
 }
 
 
@@ -619,19 +648,18 @@ if (host_find_byname(&host, NULL, 0, NULL, FALSE) == HOST_FIND_FAILED)
 host.address = US"127.0.0.1";
 
 
 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 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);
 
 
   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. */
 
 
   /* 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, NULL);
   rc = send(sock, buffer, Ustrlen(buffer) + 1, 0);
   (void)close(sock);
 
   rc = send(sock, buffer, Ustrlen(buffer) + 1, 0);
   (void)close(sock);
 
@@ -664,7 +692,7 @@ Returns:       pointer to the required transport, or NULL
 transport_instance *
 check_file_format(int cfd, transport_instance *tblock, address_item *addr)
 {
 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));
   ((appendfile_transport_options_block *)(tblock->options_block))->file_format;
 uschar data[256];
 int len = read(cfd, data, sizeof(data));
@@ -679,15 +707,16 @@ if (len == 0) return tblock;
 
 /* Search the formats for a match */
 
 
 /* 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);
   {
   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;
     {
     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)
       if (Ustrcmp(tp, tt->name) == 0)
         {
         DEBUG(D_transport)
@@ -855,10 +884,10 @@ if (dofcntl)
   {
   if (fcntltime > 0)
     {
   {
   if (fcntltime > 0)
     {
-    alarm(fcntltime);
+    ALARM(fcntltime);
     yield = fcntl(fd, F_SETLKW, &lock_data);
     save_errno = errno;
     yield = fcntl(fd, F_SETLKW, &lock_data);
     save_errno = errno;
-    alarm(0);
+    ALARM_CLR(0);
     errno = save_errno;
     }
   else yield = fcntl(fd, F_SETLK, &lock_data);
     errno = save_errno;
     }
   else yield = fcntl(fd, F_SETLK, &lock_data);
@@ -867,13 +896,13 @@ if (dofcntl)
 #ifndef NO_FLOCK
 if (doflock && (yield >= 0))
   {
 #ifndef NO_FLOCK
 if (doflock && (yield >= 0))
   {
-  int flocktype = (fcntltype == F_WRLCK)? LOCK_EX : LOCK_SH;
+  int flocktype = (fcntltype == F_WRLCK) ? LOCK_EX : LOCK_SH;
   if (flocktime > 0)
     {
   if (flocktime > 0)
     {
-    alarm(flocktime);
+    ALARM(flocktime);
     yield = flock(fd, flocktype);
     save_errno = errno;
     yield = flock(fd, flocktype);
     save_errno = errno;
-    alarm(0);
+    ALARM_CLR(0);
     errno = save_errno;
     }
   else yield = flock(fd, flocktype | LOCK_NB);
     errno = save_errno;
     }
   else yield = flock(fd, flocktype | LOCK_NB);
@@ -916,6 +945,7 @@ copy_mbx_message(int to_fd, int from_fd, off_t saved_size)
 int used;
 off_t size;
 struct stat statbuf;
 int used;
 off_t size;
 struct stat statbuf;
+transport_ctx tctx = { .u={.fd = to_fd}, .options = topt_not_socket };
 
 /* If the current mailbox size is zero, write a header block */
 
 
 /* If the current mailbox size is zero, write a header block */
 
@@ -928,7 +958,7 @@ if (saved_size == 0)
     (long int)time(NULL));
   for (i = 0; i < MBX_NUSERFLAGS; i++)
     sprintf (CS(s += Ustrlen(s)), "\015\012");
     (long int)time(NULL));
   for (i = 0; i < MBX_NUSERFLAGS; i++)
     sprintf (CS(s += Ustrlen(s)), "\015\012");
-  if (!transport_write_block (to_fd, deliver_out_buffer, MBX_HDRSIZE))
+  if (!transport_write_block (&tctx, deliver_out_buffer, MBX_HDRSIZE, FALSE))
     return DEFER;
   }
 
     return DEFER;
   }
 
@@ -946,7 +976,7 @@ used = Ustrlen(deliver_out_buffer);
 
 /* Rewind the temporary file, and copy it over in chunks. */
 
 
 /* Rewind the temporary file, and copy it over in chunks. */
 
-lseek(from_fd, 0 , SEEK_SET);
+if (lseek(from_fd, 0 , SEEK_SET) < 0) return DEFER;
 
 while (size > 0)
   {
 
 while (size > 0)
   {
@@ -957,7 +987,7 @@ while (size > 0)
     if (len == 0) errno = ERRNO_MBXLENGTH;
     return DEFER;
     }
     if (len == 0) errno = ERRNO_MBXLENGTH;
     return DEFER;
     }
-  if (!transport_write_block(to_fd, deliver_out_buffer, used + len))
+  if (!transport_write_block(&tctx, deliver_out_buffer, used + len, FALSE))
     return DEFER;
   size -= len;
   used = 0;
     return DEFER;
   size -= len;
   used = 0;
@@ -1146,7 +1176,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
 
      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
 
      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
@@ -1243,7 +1273,7 @@ BOOL wait_for_tick = FALSE;
 uid_t uid = geteuid();     /* See note above */
 gid_t gid = getegid();
 int mbformat;
 uid_t uid = geteuid();     /* See note above */
 gid_t gid = getegid();
 int mbformat;
-int mode = (addr->mode > 0)? addr->mode : ob->mode;
+int mode = (addr->mode > 0) ? addr->mode : ob->mode;
 off_t saved_size = -1;
 off_t mailbox_size = ob->mailbox_size_value;
 int mailbox_filecount = ob->mailbox_filecount_value;
 off_t saved_size = -1;
 off_t mailbox_size = ob->mailbox_size_value;
 int mailbox_filecount = ob->mailbox_filecount_value;
@@ -1321,7 +1351,7 @@ if ((ob->maildir_format || ob->mailstore_format) && !isdirectory)
   addr->transport_return = PANIC;
   addr->message = string_sprintf("mail%s_format requires \"directory\" "
     "to be specified for the %s transport",
   addr->transport_return = PANIC;
   addr->message = string_sprintf("mail%s_format requires \"directory\" "
     "to be specified for the %s transport",
-    ob->maildir_format? "dir" : "store", tblock->name);
+    ob->maildir_format ? "dir" : "store", tblock->name);
   return FALSE;
   }
 
   return FALSE;
   }
 
@@ -1361,10 +1391,10 @@ if (isdirectory)
   {
   mbformat =
   #ifdef SUPPORT_MAILDIR
   {
   mbformat =
   #ifdef SUPPORT_MAILDIR
-    (ob->maildir_format)? mbf_maildir :
+    (ob->maildir_format) ? mbf_maildir :
   #endif
   #ifdef SUPPORT_MAILSTORE
   #endif
   #ifdef SUPPORT_MAILSTORE
-    (ob->mailstore_format)? mbf_mailstore :
+    (ob->mailstore_format) ? mbf_mailstore :
   #endif
     mbf_smail;
   }
   #endif
     mbf_smail;
   }
@@ -1372,7 +1402,7 @@ else
   {
   mbformat =
   #ifdef SUPPORT_MBX
   {
   mbformat =
   #ifdef SUPPORT_MBX
-    (ob->mbx_format)? mbf_mbx :
+    (ob->mbx_format) ? mbf_mbx :
   #endif
     mbf_unix;
   }
   #endif
     mbf_unix;
   }
@@ -1380,29 +1410,32 @@ else
 DEBUG(D_transport)
   {
   debug_printf("appendfile: mode=%o notify_comsat=%d quota=" OFF_T_FMT
 DEBUG(D_transport)
   {
   debug_printf("appendfile: mode=%o notify_comsat=%d quota=" OFF_T_FMT
+    "%s%s"
     " warning=" OFF_T_FMT "%s\n"
     "  %s=%s format=%s\n  message_prefix=%s\n  message_suffix=%s\n  "
     "maildir_use_size_file=%s\n",
     mode, ob->notify_comsat, ob->quota_value,
     " warning=" OFF_T_FMT "%s\n"
     "  %s=%s format=%s\n  message_prefix=%s\n  message_suffix=%s\n  "
     "maildir_use_size_file=%s\n",
     mode, ob->notify_comsat, ob->quota_value,
+    ob->quota_no_check ? " (no_check)" : "",
+    ob->quota_filecount_no_check ? " (no_check_filecount)" : "",
     ob->quota_warn_threshold_value,
     ob->quota_warn_threshold_value,
-    ob->quota_warn_threshold_is_percent? "%" : "",
-    isdirectory? "directory" : "file",
+    ob->quota_warn_threshold_is_percent ? "%" : "",
+    isdirectory ? "directory" : "file",
     path, mailbox_formats[mbformat],
     path, mailbox_formats[mbformat],
-    (ob->message_prefix == NULL)? US"null" : string_printing(ob->message_prefix),
-    (ob->message_suffix == NULL)? US"null" : string_printing(ob->message_suffix),
-    (ob->maildir_use_size_file)? "yes" : "no");
+    (ob->message_prefix == NULL) ? US"null" : string_printing(ob->message_prefix),
+    (ob->message_suffix == NULL) ? US"null" : string_printing(ob->message_suffix),
+    (ob->maildir_use_size_file) ? "yes" : "no");
 
   if (!isdirectory) debug_printf("  locking by %s%s%s%s%s\n",
 
   if (!isdirectory) debug_printf("  locking by %s%s%s%s%s\n",
-    ob->use_lockfile? "lockfile " : "",
-    ob->use_mbx_lock? "mbx locking (" : "",
-    ob->use_fcntl? "fcntl " : "",
-    ob->use_flock? "flock" : "",
-    ob->use_mbx_lock? ")" : "");
+    ob->use_lockfile ? "lockfile " : "",
+    ob->use_mbx_lock ? "mbx locking (" : "",
+    ob->use_fcntl ? "fcntl " : "",
+    ob->use_flock ? "flock" : "",
+    ob->use_mbx_lock ? ")" : "");
   }
 
 /* If the -N option is set, can't do any more. */
 
   }
 
 /* If the -N option is set, can't do any more. */
 
-if (dont_deliver)
+if (f.dont_deliver)
   {
   DEBUG(D_transport)
     debug_printf("*** delivery by %s transport bypassed by -N option\n",
   {
   DEBUG(D_transport)
     debug_printf("*** delivery by %s transport bypassed by -N option\n",
@@ -1709,7 +1742,7 @@ if (!isdirectory)
     int sleep_before_retry = TRUE;
     file_opened = FALSE;
 
     int sleep_before_retry = TRUE;
     file_opened = FALSE;
 
-    if((use_lstat? Ulstat(filename, &statbuf) : Ustat(filename, &statbuf)) != 0)
+    if((use_lstat ? Ulstat(filename, &statbuf) : Ustat(filename, &statbuf)) != 0)
       {
       /* Let's hope that failure to stat (other than non-existence) is a
       rare event. */
       {
       /* Let's hope that failure to stat (other than non-existence) is a
       rare event. */
@@ -1756,7 +1789,7 @@ if (!isdirectory)
       get a shared lock. */
 
       fd = Uopen(filename, O_RDWR | O_APPEND | O_CREAT |
       get a shared lock. */
 
       fd = Uopen(filename, O_RDWR | O_APPEND | O_CREAT |
-        (use_lstat? O_EXCL : 0), mode);
+        (use_lstat ? O_EXCL : 0), mode);
       if (fd < 0)
         {
         if (errno == EEXIST) continue;
       if (fd < 0)
         {
         if (errno == EEXIST) continue;
@@ -1805,7 +1838,7 @@ if (!isdirectory)
         addr->basic_errno = ERRNO_BADUGID;
         addr->message = string_sprintf("mailbox %s%s has wrong uid "
           "(%ld != %ld)", filename,
         addr->basic_errno = ERRNO_BADUGID;
         addr->message = string_sprintf("mailbox %s%s has wrong uid "
           "(%ld != %ld)", filename,
-          islink? " (symlink)" : "",
+          islink ? " (symlink)" : "",
           (long int)(statbuf.st_uid), (long int)uid);
         goto RETURN;
         }
           (long int)(statbuf.st_uid), (long int)uid);
         goto RETURN;
         }
@@ -1816,7 +1849,7 @@ if (!isdirectory)
         {
         addr->basic_errno = ERRNO_BADUGID;
         addr->message = string_sprintf("mailbox %s%s has wrong gid (%d != %d)",
         {
         addr->basic_errno = ERRNO_BADUGID;
         addr->message = string_sprintf("mailbox %s%s has wrong gid (%d != %d)",
-          filename, islink? " (symlink)" : "", statbuf.st_gid, gid);
+          filename, islink ? " (symlink)" : "", statbuf.st_gid, gid);
         goto RETURN;
         }
 
         goto RETURN;
         }
 
@@ -1827,7 +1860,7 @@ if (!isdirectory)
         {
         addr->basic_errno = ERRNO_NOTREGULAR;
         addr->message = string_sprintf("mailbox %s%s has too many links (%d)",
         {
         addr->basic_errno = ERRNO_NOTREGULAR;
         addr->message = string_sprintf("mailbox %s%s has too many links (%d)",
-          filename, islink? " (symlink)" : "", statbuf.st_nlink);
+          filename, islink ? " (symlink)" : "", statbuf.st_nlink);
         goto RETURN;
 
         }
         goto RETURN;
 
         }
@@ -1853,7 +1886,7 @@ if (!isdirectory)
         {
         addr->basic_errno = ERRNO_NOTREGULAR;
         addr->message = string_sprintf("mailbox %s is not a regular file%s",
         {
         addr->basic_errno = ERRNO_NOTREGULAR;
         addr->message = string_sprintf("mailbox %s is not a regular file%s",
-          filename, ob->allow_fifo? " or named pipe" : "");
+          filename, ob->allow_fifo ? " or named pipe" : "");
         goto RETURN;
         }
 
         goto RETURN;
         }
 
@@ -1902,7 +1935,7 @@ if (!isdirectory)
       a FIFO is opened WRONLY + NDELAY so that it fails if there is no process
       reading the pipe. */
 
       a FIFO is opened WRONLY + NDELAY so that it fails if there is no process
       reading the pipe. */
 
-      fd = Uopen(filename, isfifo? (O_WRONLY|O_NDELAY) : (O_RDWR|O_APPEND),
+      fd = Uopen(filename, isfifo ? (O_WRONLY|O_NDELAY) : (O_RDWR|O_APPEND),
         mode);
       if (fd < 0)
         {
         mode);
       if (fd < 0)
         {
@@ -1949,7 +1982,7 @@ if (!isdirectory)
         {
         addr->basic_errno = ERRNO_INODECHANGED;
         addr->message = string_sprintf("opened mailbox %s inode number changed "
         {
         addr->basic_errno = ERRNO_INODECHANGED;
         addr->message = string_sprintf("opened mailbox %s inode number changed "
-          "from %d to %ld", filename, inode, statbuf.st_ino);
+          "from " INO_T_FMT " to " INO_T_FMT, filename, inode, statbuf.st_ino);
         addr->special_action = SPECIAL_FREEZE;
         goto RETURN;
         }
         addr->special_action = SPECIAL_FREEZE;
         goto RETURN;
         }
@@ -1963,7 +1996,7 @@ if (!isdirectory)
         addr->basic_errno = ERRNO_NOTREGULAR;
         addr->message =
           string_sprintf("opened mailbox %s is no longer a %s", filename,
         addr->basic_errno = ERRNO_NOTREGULAR;
         addr->message =
           string_sprintf("opened mailbox %s is no longer a %s", filename,
-            isfifo? "named pipe" : "regular file");
+            isfifo ? "named pipe" : "regular file");
         addr->special_action = SPECIAL_FREEZE;
         goto RETURN;
         }
         addr->special_action = SPECIAL_FREEZE;
         goto RETURN;
         }
@@ -2401,7 +2434,7 @@ else
         {
         uschar *s = path + check_path_len;
         while (*s == '/') s++;
         {
         uschar *s = path + check_path_len;
         while (*s == '/') s++;
-        s = (*s == 0)? US "new" : string_sprintf("%s/new", s);
+        s = (*s == 0) ? US "new" : string_sprintf("%s/new", s);
         if (pcre_exec(dir_regex, NULL, CS s, Ustrlen(s), 0, 0, NULL, 0) < 0)
           {
           disable_quota = TRUE;
         if (pcre_exec(dir_regex, NULL, CS s, Ustrlen(s), 0, 0, NULL, 0) < 0)
           {
           disable_quota = TRUE;
@@ -2518,7 +2551,7 @@ else
     $message_size is accurately known. */
 
     if (nametag != NULL && expand_string(nametag) == NULL &&
     $message_size is accurately known. */
 
     if (nametag != NULL && expand_string(nametag) == NULL &&
-        !expand_string_forcedfail)
+        !f.expand_string_forcedfail)
       {
       addr->transport_return = PANIC;
       addr->message = string_sprintf("Expansion of \"%s\" (maildir_tag "
       {
       addr->transport_return = PANIC;
       addr->message = string_sprintf("Expansion of \"%s\" (maildir_tag "
@@ -2540,8 +2573,8 @@ else
       uschar *basename;
 
       (void)gettimeofday(&msg_tv, NULL);
       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" PID_T_FMT ".%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);
 
       filename = dataname = string_sprintf("tmp/%s", basename);
       newname = string_sprintf("new/%s", basename);
@@ -2561,7 +2594,7 @@ else
       if (i >= ob->maildir_retries)
         {
         addr->message = string_sprintf ("failed to open %s (%d tr%s)",
       if (i >= ob->maildir_retries)
         {
         addr->message = string_sprintf ("failed to open %s (%d tr%s)",
-          filename, i, (i == 1)? "y" : "ies");
+          filename, i, (i == 1) ? "y" : "ies");
         addr->basic_errno = errno;
         if (errno == errno_quota || errno == ENOSPC)
           addr->user_message = US"mailbox is full";
         addr->basic_errno = errno;
         if (errno == errno_quota || errno == ENOSPC)
           addr->user_message = US"mailbox is full";
@@ -2655,7 +2688,7 @@ else
       uschar *s = expand_string(ob->mailstore_prefix);
       if (s == NULL)
         {
       uschar *s = expand_string(ob->mailstore_prefix);
       if (s == NULL)
         {
-        if (!expand_string_forcedfail)
+        if (!f.expand_string_forcedfail)
           {
           addr->transport_return = PANIC;
           addr->message = string_sprintf("Expansion of \"%s\" (mailstore "
           {
           addr->transport_return = PANIC;
           addr->message = string_sprintf("Expansion of \"%s\" (mailstore "
@@ -2684,7 +2717,7 @@ else
       uschar *s = expand_string(ob->mailstore_suffix);
       if (s == NULL)
         {
       uschar *s = expand_string(ob->mailstore_suffix);
       if (s == NULL)
         {
-        if (!expand_string_forcedfail)
+        if (!f.expand_string_forcedfail)
           {
           addr->transport_return = PANIC;
           addr->message = string_sprintf("Expansion of \"%s\" (mailstore "
           {
           addr->transport_return = PANIC;
           addr->message = string_sprintf("Expansion of \"%s\" (mailstore "
@@ -2770,25 +2803,37 @@ if (!disable_quota && ob->quota_value > 0)
     debug_printf("Exim quota = " OFF_T_FMT " old size = " OFF_T_FMT
       " this message = %d (%sincluded)\n",
       ob->quota_value, mailbox_size, message_size,
     debug_printf("Exim quota = " OFF_T_FMT " old size = " OFF_T_FMT
       " this message = %d (%sincluded)\n",
       ob->quota_value, mailbox_size, message_size,
-      ob->quota_is_inclusive? "" : "not ");
+      ob->quota_is_inclusive ? "" : "not ");
     debug_printf("  file count quota = %d count = %d\n",
       ob->quota_filecount_value, mailbox_filecount);
     }
     debug_printf("  file count quota = %d count = %d\n",
       ob->quota_filecount_value, mailbox_filecount);
     }
-  if (mailbox_size + (ob->quota_is_inclusive? message_size:0) > ob->quota_value)
-    {
-    DEBUG(D_transport) debug_printf("mailbox quota exceeded\n");
-    yield = DEFER;
-    errno = ERRNO_EXIMQUOTA;
-    }
-  else if (ob->quota_filecount_value > 0 &&
-           mailbox_filecount + (ob->quota_is_inclusive ? 1:0) >
-             ob->quota_filecount_value)
+
+  if (mailbox_size + (ob->quota_is_inclusive ? message_size:0) > ob->quota_value)
     {
     {
-    DEBUG(D_transport) debug_printf("mailbox file count quota exceeded\n");
-    yield = DEFER;
-    errno = ERRNO_EXIMQUOTA;
-    filecount_msg = US" filecount";
+
+      if (!ob->quota_no_check)
+        {
+        DEBUG(D_transport) debug_printf("mailbox quota exceeded\n");
+        yield = DEFER;
+        errno = ERRNO_EXIMQUOTA;
+        }
+      else DEBUG(D_transport) debug_printf("mailbox quota exceeded but ignored\n");
+
     }
     }
+
+  if (ob->quota_filecount_value > 0
+           && mailbox_filecount + (ob->quota_is_inclusive ? 1:0) >
+              ob->quota_filecount_value)
+    if(!ob->quota_filecount_no_check)
+      {
+      DEBUG(D_transport) debug_printf("mailbox file count quota exceeded\n");
+      yield = DEFER;
+      errno = ERRNO_EXIMQUOTA;
+      filecount_msg = US" filecount";
+      }
+    else DEBUG(D_transport) if (ob->quota_filecount_no_check)
+      debug_printf("mailbox file count quota exceeded but ignored\n");
+
   }
 
 /* If we are writing in MBX format, what we actually do is to write the message
   }
 
 /* If we are writing in MBX format, what we actually do is to write the message
@@ -2855,7 +2900,7 @@ if (yield == OK && ob->use_bsmtp)
     transport_newlines++;
     for (a = addr; a != NULL; a = a->next)
       {
     transport_newlines++;
     for (a = addr; a != NULL; a = a->next)
       {
-      address_item *b = testflag(a, af_pfr)? a->parent: a;
+      address_item *b = testflag(a, af_pfr) ? a->parent: a;
       if (!transport_write_string(fd, "RCPT TO:<%s>%s\n",
         transport_rcpt_address(b, tblock->rcpt_include_affixes), cr))
           { yield = DEFER; break; }
       if (!transport_write_string(fd, "RCPT TO:<%s>%s\n",
         transport_rcpt_address(b, tblock->rcpt_include_affixes), cr))
           { yield = DEFER; break; }
@@ -2873,9 +2918,15 @@ at initialization time. */
 
 if (yield == OK)
   {
 
 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 = {
+    .u = {.fd=fd},
+    .tblock = tblock,
+    .addr = addr,
+    .check_string = ob->check_string,
+    .escape_string = ob->escape_string,
+    .options =  ob->options | topt_not_socket
+  };
+  if (!transport_write_message(&tctx, 0))
     yield = DEFER;
   }
 
     yield = DEFER;
   }
 
@@ -3029,7 +3080,7 @@ if (yield != OK)
         }
       else   /* Want a repeatable time when in test harness */
         {
         }
       else   /* Want a repeatable time when in test harness */
         {
-        addr->more_errno = running_in_test_harness? 10 :
+        addr->more_errno = f.running_in_test_harness ? 10 :
           (int)time(NULL) - statbuf.st_mtime;
         }
       DEBUG(D_transport)
           (int)time(NULL) - statbuf.st_mtime;
         }
       DEBUG(D_transport)
@@ -3054,8 +3105,8 @@ if (yield != OK)
     addr->user_message = US"mailbox is full";
     DEBUG(D_transport) debug_printf("System quota exceeded for %s%s%s\n",
       dataname,
     addr->user_message = US"mailbox is full";
     DEBUG(D_transport) debug_printf("System quota exceeded for %s%s%s\n",
       dataname,
-      isdirectory? US"" : US": time since file read = ",
-      isdirectory? US"" : readconf_printtime(addr->more_errno));
+      isdirectory ? US"" : US": time since file read = ",
+      isdirectory ? US"" : readconf_printtime(addr->more_errno));
     }
 
   /* Handle Exim's own quota-imposition */
     }
 
   /* Handle Exim's own quota-imposition */
@@ -3068,8 +3119,8 @@ if (yield != OK)
     addr->user_message = US"mailbox is full";
     DEBUG(D_transport) debug_printf("Exim%s quota exceeded for %s%s%s\n",
       filecount_msg, dataname,
     addr->user_message = US"mailbox is full";
     DEBUG(D_transport) debug_printf("Exim%s quota exceeded for %s%s%s\n",
       filecount_msg, dataname,
-      isdirectory? US"" : US": time since file read = ",
-      isdirectory? US"" : readconf_printtime(addr->more_errno));
+      isdirectory ? US"" : US": time since file read = ",
+      isdirectory ? US"" : readconf_printtime(addr->more_errno));
     }
 
   /* Handle a process failure while writing via a filter; the return
     }
 
   /* Handle a process failure while writing via a filter; the return
@@ -3080,7 +3131,7 @@ if (yield != OK)
     yield = PANIC;
     addr->message = string_sprintf("transport filter process failed (%d) "
       "while writing to %s%s", addr->more_errno, dataname,
     yield = PANIC;
     addr->message = string_sprintf("transport filter process failed (%d) "
       "while writing to %s%s", addr->more_errno, dataname,
-      (addr->more_errno == EX_EXECFAILED)? ": unable to execute command" : "");
+      (addr->more_errno == EX_EXECFAILED) ? ": unable to execute command" : "");
     }
 
   /* Handle failure to expand header changes */
     }
 
   /* Handle failure to expand header changes */
@@ -3123,7 +3174,7 @@ if (yield != OK)
   fcntl() call (BSDI & FreeBSD do not). */
 
   if (!isdirectory && ftruncate(fd, saved_size))
   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
   }
 
 /* Handle successful writing - we want the modification time to be now for
@@ -3154,7 +3205,7 @@ else
       {
       addr->basic_errno = errno;
       addr->message = string_sprintf("close() error for %s",
       {
       addr->basic_errno = errno;
       addr->message = string_sprintf("close() error for %s",
-        (ob->mailstore_format)? dataname : filename);
+        (ob->mailstore_format) ? dataname : filename);
       yield = DEFER;
       }
 
       yield = DEFER;
       }
 
@@ -3368,4 +3419,5 @@ put in the first address of a batch. */
 return FALSE;
 }
 
 return FALSE;
 }
 
+#endif /*!MACRO_PREDEF*/
 /* End of transport/appendfile.c */
 /* End of transport/appendfile.c */
index 52dc3ba..4b14db1 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Private structure for the private options. */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Private structure for the private options. */
@@ -70,6 +70,8 @@ typedef struct {
   BOOL  mbx_format;
   BOOL  quota_warn_threshold_is_percent;
   BOOL  quota_is_inclusive;
   BOOL  mbx_format;
   BOOL  quota_warn_threshold_is_percent;
   BOOL  quota_is_inclusive;
+  BOOL  quota_no_check;
+  BOOL  quota_filecount_no_check;
 } appendfile_transport_options_block;
 
 /* Restricted creation options */
 } appendfile_transport_options_block;
 
 /* Restricted creation options */
index 4e391b8..bc38168 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -62,6 +62,17 @@ address can appear in the tables drtables.c. */
 int autoreply_transport_options_count =
   sizeof(autoreply_transport_options)/sizeof(optionlist);
 
 int autoreply_transport_options_count =
   sizeof(autoreply_transport_options)/sizeof(optionlist);
 
+
+#ifdef MACRO_PREDEF
+
+/* Dummy values */
+autoreply_transport_options_block autoreply_transport_option_defaults = {0};
+void autoreply_transport_init(transport_instance *tblock) {}
+BOOL autoreply_transport_entry(transport_instance *tblock, address_item *addr) {return FALSE;}
+
+#else   /*!MACRO_PREDEF*/
+
+
 /* Default private options block for the autoreply transport. */
 
 autoreply_transport_options_block autoreply_transport_option_defaults = {
 /* Default private options block for the autoreply transport. */
 
 autoreply_transport_options_block autoreply_transport_option_defaults = {
@@ -157,12 +168,13 @@ if (ss == NULL)
 if (type != cke_text) for (t = ss; *t != 0; t++)
   {
   int c = *t;
 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;
   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 "
   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;
   }
 
   return NULL;
   }
 
@@ -187,7 +199,7 @@ Returns:      nothing
 */
 
 static void
 */
 
 static void
-check_never_mail(uschar **listptr, uschar *never_mail)
+check_never_mail(uschar **listptr, const uschar *never_mail)
 {
 uschar *s = *listptr;
 
 {
 uschar *s = *listptr;
 
@@ -266,7 +278,6 @@ autoreply_transport_entry(
 {
 int fd, pid, rc;
 int cache_fd = -1;
 {
 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;
 int cache_size = 0;
 int add_size = 0;
 EXIM_DB *dbm_file = NULL;
@@ -279,7 +290,7 @@ uschar *message_id = NULL;
 header_line *h;
 time_t now = time(NULL);
 time_t once_repeat_sec = 0;
 header_line *h;
 time_t now = time(NULL);
 time_t once_repeat_sec = 0;
-FILE *f;
+FILE *fp;
 FILE *ff = NULL;
 
 autoreply_transport_options_block *ob =
 FILE *ff = NULL;
 
 autoreply_transport_options_block *ob =
@@ -335,33 +346,22 @@ else
   file_expand = ob->file_expand;
   return_message = ob->return_message;
 
   file_expand = ob->file_expand;
   return_message = ob->return_message;
 
-  if ((from  != NULL &&
-        (from = checkexpand(from, addr, tblock->name, cke_hdr)) == NULL) ||
-      (reply_to    != NULL &&
-        (reply_to = checkexpand(reply_to, addr, tblock->name, cke_hdr)) == NULL) ||
-      (to    != NULL &&
-        (to = checkexpand(to, addr, tblock->name, cke_hdr)) == NULL) ||
-      (cc    != NULL &&
-        (cc = checkexpand(cc, addr, tblock->name, cke_hdr)) == NULL) ||
-      (bcc   != NULL &&
-        (bcc = checkexpand(bcc, addr, tblock->name, cke_hdr)) == NULL) ||
-      (subject   != NULL &&
-        (subject = checkexpand(subject, addr, tblock->name, cke_hdr)) == NULL) ||
-      (headers != NULL &&
-        (headers = checkexpand(headers, addr, tblock->name, cke_text)) == NULL) ||
-      (text  != NULL &&
-        (text = checkexpand(text, addr, tblock->name, cke_text)) == NULL) ||
-      (file  != NULL &&
-        (file = checkexpand(file, addr, tblock->name, cke_file)) == NULL) ||
-      (logfile != NULL &&
-        (logfile = checkexpand(logfile, addr, tblock->name, cke_file)) == NULL) ||
-      (oncelog != NULL &&
-        (oncelog = checkexpand(oncelog, addr, tblock->name, cke_file)) == NULL) ||
-      (oncerepeat != NULL &&
-        (oncerepeat = checkexpand(oncerepeat, addr, tblock->name, cke_file)) == NULL))
+  if (  from && !(from = checkexpand(from, addr, tblock->name, cke_hdr))
+     || reply_to && !(reply_to = checkexpand(reply_to, addr, tblock->name, cke_hdr))
+     || to && !(to = checkexpand(to, addr, tblock->name, cke_hdr))
+     || cc && !(cc = checkexpand(cc, addr, tblock->name, cke_hdr))
+     || bcc && !(bcc = checkexpand(bcc, addr, tblock->name, cke_hdr))
+     || subject && !(subject = checkexpand(subject, addr, tblock->name, cke_hdr))
+     || headers && !(headers = checkexpand(headers, addr, tblock->name, cke_text))
+     || text && !(text = checkexpand(text, addr, tblock->name, cke_text))
+     || file && !(file = checkexpand(file, addr, tblock->name, cke_file))
+     || logfile && !(logfile = checkexpand(logfile, addr, tblock->name, cke_file))
+     || oncelog && !(oncelog = checkexpand(oncelog, addr, tblock->name, cke_file))
+     || oncerepeat && !(oncerepeat = checkexpand(oncerepeat, addr, tblock->name, cke_file))
+     )
     return FALSE;
 
     return FALSE;
 
-  if (oncerepeat != NULL)
+  if (oncerepeat)
     {
     once_repeat_sec = readconf_readtime(oncerepeat, 0, FALSE);
     if (once_repeat_sec < 0)
     {
     once_repeat_sec = readconf_readtime(oncerepeat, 0, FALSE);
     if (once_repeat_sec < 0)
@@ -377,11 +377,11 @@ else
 /* If the never_mail option is set, we have to scan all the recipients and
 remove those that match. */
 
 /* If the never_mail option is set, we have to scan all the recipients and
 remove those that match. */
 
-if (ob->never_mail != NULL)
+if (ob->never_mail)
   {
   {
-  uschar *never_mail = expand_string(ob->never_mail);
+  const uschar *never_mail = expand_string(ob->never_mail);
 
 
-  if (never_mail == NULL)
+  if (!never_mail)
     {
     addr->transport_return = FAIL;
     addr->message = string_sprintf("Failed to expand \"%s\" for "
     {
     addr->transport_return = FAIL;
     addr->message = string_sprintf("Failed to expand \"%s\" for "
@@ -389,11 +389,11 @@ if (ob->never_mail != NULL)
     return FALSE;
     }
 
     return FALSE;
     }
 
-  if (to != NULL) check_never_mail(&to, never_mail);
-  if (cc != NULL) check_never_mail(&cc, never_mail);
-  if (bcc != NULL) check_never_mail(&bcc, never_mail);
+  if (to) check_never_mail(&to, never_mail);
+  if (cc) check_never_mail(&cc, never_mail);
+  if (bcc) check_never_mail(&bcc, never_mail);
 
 
-  if (to == NULL && cc == NULL && bcc == NULL)
+  if (!to && !cc && !bcc)
     {
     DEBUG(D_transport)
       debug_printf("*** all recipients removed by never_mail\n");
     {
     DEBUG(D_transport)
       debug_printf("*** all recipients removed by never_mail\n");
@@ -403,7 +403,7 @@ if (ob->never_mail != NULL)
 
 /* If the -N option is set, can't do any more. */
 
 
 /* If the -N option is set, can't do any more. */
 
-if (dont_deliver)
+if (f.dont_deliver)
   {
   DEBUG(D_transport)
     debug_printf("*** delivery by %s transport bypassed by -N option\n",
   {
   DEBUG(D_transport)
     debug_printf("*** delivery by %s transport bypassed by -N option\n",
@@ -419,7 +419,7 @@ recipient, the effect might not be quite as envisaged. If once_file_size is
 set, instead of a dbm file, we use a regular file containing a circular buffer
 recipient cache. */
 
 set, instead of a dbm file, we use a regular file containing a circular buffer
 recipient cache. */
 
-if (oncelog != NULL && *oncelog != 0 && to != NULL)
+if (oncelog && *oncelog != 0 && to)
   {
   time_t then = 0;
 
   {
   time_t then = 0;
 
@@ -427,7 +427,7 @@ if (oncelog != NULL && *oncelog != 0 && to != NULL)
 
   if (ob->once_file_size > 0)
     {
 
   if (ob->once_file_size > 0)
     {
-    uschar *p;
+    uschar * p, * nextp;
     struct stat statbuf;
     cache_fd = Uopen(oncelog, O_CREAT|O_RDWR, ob->mode);
 
     struct stat statbuf;
     cache_fd = Uopen(oncelog, O_CREAT|O_RDWR, ob->mode);
 
@@ -464,18 +464,16 @@ if (oncelog != NULL && *oncelog != 0 && to != NULL)
     zero. If we find a match, put the time into "then", and the place where it
     was found into "cache_time". Otherwise, "then" is left at zero. */
 
     zero. If we find a match, put the time into "then", and the place where it
     was found into "cache_time". Otherwise, "then" is left at zero. */
 
-    p = cache_buff;
-    while (p < cache_buff + cache_size)
+    for (p = cache_buff; p < cache_buff + cache_size; p = nextp)
       {
       uschar *s = p + sizeof(time_t);
       {
       uschar *s = p + sizeof(time_t);
-      uschar *nextp = s + Ustrlen(s) + 1;
+      nextp = s + Ustrlen(s) + 1;
       if (Ustrcmp(to, s) == 0)
         {
         memcpy(&then, p, sizeof(time_t));
         cache_time = p;
         break;
         }
       if (Ustrcmp(to, s) == 0)
         {
         memcpy(&then, p, sizeof(time_t));
         cache_time = p;
         break;
         }
-      p = nextp;
       }
     }
 
       }
     }
 
@@ -484,8 +482,12 @@ if (oncelog != NULL && *oncelog != 0 && to != NULL)
   else
     {
     EXIM_DATUM key_datum, result_datum;
   else
     {
     EXIM_DATUM key_datum, result_datum;
-    EXIM_DBOPEN(oncelog, O_RDWR|O_CREAT, ob->mode, &dbm_file);
-    if (dbm_file == NULL)
+    uschar * dirname = string_copy(oncelog);
+    uschar * s;
+
+    if ((s = Ustrrchr(dirname, '/'))) *s = '\0';
+    EXIM_DBOPEN(oncelog, dirname, O_RDWR|O_CREAT, ob->mode, &dbm_file);
+    if (!dbm_file)
       {
       addr->transport_return = DEFER;
       addr->message = string_sprintf("Failed to open %s file %s when sending "
       {
       addr->transport_return = DEFER;
       addr->message = string_sprintf("Failed to open %s file %s when sending "
@@ -509,10 +511,9 @@ if (oncelog != NULL && *oncelog != 0 && to != NULL)
       can be abolished. */
 
       if (EXIM_DATUM_SIZE(result_datum) == sizeof(time_t))
       can be abolished. */
 
       if (EXIM_DATUM_SIZE(result_datum) == sizeof(time_t))
-        {
         memcpy(&then, EXIM_DATUM_DATA(result_datum), sizeof(time_t));
         memcpy(&then, EXIM_DATUM_DATA(result_datum), sizeof(time_t));
-        }
-      else then = now;
+      else
+        then = now;
       }
     }
 
       }
     }
 
@@ -521,9 +522,10 @@ if (oncelog != NULL && *oncelog != 0 && to != NULL)
 
   if (then != 0 && (once_repeat_sec <= 0 || now - then < once_repeat_sec))
     {
 
   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" : "");
     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;
     if (log_fd >= 0)
       {
       uschar *ptr = log_buffer;
@@ -543,10 +545,10 @@ if (oncelog != NULL && *oncelog != 0 && to != NULL)
 
 /* We are going to send a message. Ensure any requested file is available. */
 
 
 /* We are going to send a message. Ensure any requested file is available. */
 
-if (file != NULL)
+if (file)
   {
   ff = Ufopen(file, "rb");
   {
   ff = Ufopen(file, "rb");
-  if (ff == NULL && !ob->file_optional)
+  if (!ff && !ob->file_optional)
     {
     addr->transport_return = DEFER;
     addr->message = string_sprintf("Failed to open file %s when sending "
     {
     addr->transport_return = DEFER;
     addr->message = string_sprintf("Failed to open file %s when sending "
@@ -567,6 +569,7 @@ if (pid < 0)
   addr->message = string_sprintf("Failed to create child process to send "
     "message from %s transport: %s", tblock->name, strerror(errno));
   DEBUG(D_transport) debug_printf("%s\n", addr->message);
   addr->message = string_sprintf("Failed to create child process to send "
     "message from %s transport: %s", tblock->name, strerror(errno));
   DEBUG(D_transport) debug_printf("%s\n", addr->message);
+  if (dbm_file) EXIM_DBCLOSE(dbm_file);
   return FALSE;
   }
 
   return FALSE;
   }
 
@@ -574,37 +577,37 @@ if (pid < 0)
 as the -t option is used. The "headers" stuff *must* be last in case there
 are newlines in it which might, if placed earlier, screw up other headers. */
 
 as the -t option is used. The "headers" stuff *must* be last in case there
 are newlines in it which might, if placed earlier, screw up other headers. */
 
-f = fdopen(fd, "wb");
+fp = fdopen(fd, "wb");
 
 
-if (from != NULL) fprintf(f, "From: %s\n", from);
-if (reply_to != NULL) fprintf(f, "Reply-To: %s\n", reply_to);
-if (to != NULL) fprintf(f, "To: %s\n", to);
-if (cc != NULL) fprintf(f, "Cc: %s\n", cc);
-if (bcc != NULL) fprintf(f, "Bcc: %s\n", bcc);
-if (subject != NULL) fprintf(f, "Subject: %s\n", subject);
+if (from) fprintf(fp, "From: %s\n", from);
+if (reply_to) fprintf(fp, "Reply-To: %s\n", reply_to);
+if (to) fprintf(fp, "To: %s\n", to);
+if (cc) fprintf(fp, "Cc: %s\n", cc);
+if (bcc) fprintf(fp, "Bcc: %s\n", bcc);
+if (subject) fprintf(fp, "Subject: %s\n", subject);
 
 /* Generate In-Reply-To from the message_id header; there should
 always be one, but code defensively. */
 
 
 /* Generate In-Reply-To from the message_id header; there should
 always be one, but code defensively. */
 
-for (h = header_list; h != NULL; h = h->next)
+for (h = header_list; h; h = h->next)
   if (h->type == htype_id) break;
 
   if (h->type == htype_id) break;
 
-if (h != NULL)
+if (h)
   {
   message_id = Ustrchr(h->text, ':') + 1;
   while (isspace(*message_id)) message_id++;
   {
   message_id = Ustrchr(h->text, ':') + 1;
   while (isspace(*message_id)) message_id++;
-  fprintf(f, "In-Reply-To: %s", message_id);
+  fprintf(fp, "In-Reply-To: %s", message_id);
   }
 
 /* Generate a References header if there is at least one of Message-ID:,
 References:, or In-Reply-To: (see RFC 2822). */
 
   }
 
 /* Generate a References header if there is at least one of Message-ID:,
 References:, or In-Reply-To: (see RFC 2822). */
 
-for (h = header_list; h != NULL; h = h->next)
+for (h = header_list; h; h = h->next)
   if (h->type != htype_old && strncmpic(US"References:", h->text, 11) == 0)
     break;
 
   if (h->type != htype_old && strncmpic(US"References:", h->text, 11) == 0)
     break;
 
-if (h == NULL)
-  for (h = header_list; h != NULL; h = h->next)
+if (!h)
+  for (h = header_list; h; h = h->next)
     if (h->type != htype_old && strncmpic(US"In-Reply-To:", h->text, 12) == 0)
       break;
 
     if (h->type != htype_old && strncmpic(US"In-Reply-To:", h->text, 12) == 0)
       break;
 
@@ -613,10 +616,10 @@ limit, some systems do not like headers growing beyond recognition.
 Keep the first message ID for the thread root and the last few for
 the position inside the thread, up to a maximum of 12 altogether. */
 
 Keep the first message ID for the thread root and the last few for
 the position inside the thread, up to a maximum of 12 altogether. */
 
-if (h != NULL || message_id != NULL)
+if (h || message_id)
   {
   {
-  fprintf(f, "References:");
-  if (h != NULL)
+  fprintf(fp, "References:");
+  if (h)
     {
     uschar *s, *id, *error;
     uschar *referenced_ids[12];
     {
     uschar *s, *id, *error;
     uschar *referenced_ids[12];
@@ -624,10 +627,10 @@ if (h != NULL || message_id != NULL)
     int i;
 
     s = Ustrchr(h->text, ':') + 1;
     int i;
 
     s = Ustrchr(h->text, ':') + 1;
-    parse_allow_group = FALSE;
+    f.parse_allow_group = FALSE;
     while (*s != 0 && (s = parse_message_id(s, &id, &error)) != NULL)
       {
     while (*s != 0 && (s = parse_message_id(s, &id, &error)) != NULL)
       {
-      if (reference_count == sizeof(referenced_ids)/sizeof(uschar *))
+      if (reference_count == nelem(referenced_ids))
         {
         memmove(referenced_ids + 1, referenced_ids + 2,
            sizeof(referenced_ids) - 2*sizeof(uschar *));
         {
         memmove(referenced_ids + 1, referenced_ids + 2,
            sizeof(referenced_ids) - 2*sizeof(uschar *));
@@ -635,31 +638,31 @@ if (h != NULL || message_id != NULL)
         }
       else referenced_ids[reference_count++] = id;
       }
         }
       else referenced_ids[reference_count++] = id;
       }
-    for (i = 0; i < reference_count; ++i) fprintf(f, " %s", referenced_ids[i]);
+    for (i = 0; i < reference_count; ++i) fprintf(fp, " %s", referenced_ids[i]);
     }
 
   /* The message id will have a newline on the end of it. */
 
     }
 
   /* The message id will have a newline on the end of it. */
 
-  if (message_id != NULL) fprintf(f, " %s", message_id);
-    else fprintf(f, "\n");
+  if (message_id) fprintf(fp, " %s", message_id);
+  else fprintf(fp, "\n");
   }
 
 /* Add an Auto-Submitted: header */
 
   }
 
 /* Add an Auto-Submitted: header */
 
-fprintf(f, "Auto-Submitted: auto-replied\n");
+fprintf(fp, "Auto-Submitted: auto-replied\n");
 
 /* Add any specially requested headers */
 
 
 /* Add any specially requested headers */
 
-if (headers != NULL) fprintf(f, "%s\n", headers);
-fprintf(f, "\n");
+if (headers) fprintf(fp, "%s\n", headers);
+fprintf(fp, "\n");
 
 
-if (text != NULL)
+if (text)
   {
   {
-  fprintf(f, "%s", CS text);
-  if (text[Ustrlen(text)-1] != '\n') fprintf(f, "\n");
+  fprintf(fp, "%s", CS text);
+  if (text[Ustrlen(text)-1] != '\n') fprintf(fp, "\n");
   }
 
   }
 
-if (ff != NULL)
+if (ff)
   {
   while (Ufgets(big_buffer, big_buffer_size, ff) != NULL)
     {
   {
   while (Ufgets(big_buffer, big_buffer_size, ff) != NULL)
     {
@@ -668,14 +671,15 @@ if (ff != NULL)
       uschar *s = expand_string(big_buffer);
       DEBUG(D_transport)
         {
       uschar *s = expand_string(big_buffer);
       DEBUG(D_transport)
         {
-        if (s == NULL)
+        if (!s)
           debug_printf("error while expanding line from file:\n  %s\n  %s\n",
             big_buffer, expand_string_message);
         }
           debug_printf("error while expanding line from file:\n  %s\n  %s\n",
             big_buffer, expand_string_message);
         }
-      fprintf(f, "%s", (s == NULL)? CS big_buffer : CS s);
+      fprintf(fp, "%s", s ? CS s : CS big_buffer);
       }
       }
-    else fprintf(f, "%s", CS big_buffer);
+    else fprintf(fp, "%s", CS big_buffer);
     }
     }
+  (void) fclose(ff);
   }
 
 /* Copy the original message if required, observing the return size
   }
 
 /* Copy the original message if required, observing the return size
@@ -689,6 +693,19 @@ 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";
     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 = {
+    .u = {.fd = fileno(fp)},
+    .tblock = tblock,
+    .addr = addr,
+    .check_string = NULL,
+    .escape_string =  NULL,
+    .options = (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)
+       | topt_not_socket
+  };
 
   if (bounce_return_size_limit > 0 && !tblock->headers_only)
     {
 
   if (bounce_return_size_limit > 0 && !tblock->headers_only)
     {
@@ -697,30 +714,23 @@ if (return_message)
       DELIVER_IN_BUFFER_SIZE;
     if (fstat(deliver_datafile, &statbuf) == 0 && statbuf.st_size > max)
       {
       DELIVER_IN_BUFFER_SIZE;
     if (fstat(deliver_datafile, &statbuf) == 0 && statbuf.st_size > max)
       {
-      fprintf(f, "\n%s"
+      fprintf(fp, "\n%s"
 "------ The body of the message is " OFF_T_FMT " characters long; only the first\n"
 "------ %d or so are included here.\n\n", rubric, statbuf.st_size,
         (max/1000)*1000);
       }
 "------ The body of the message is " OFF_T_FMT " characters long; only the first\n"
 "------ %d or so are included here.\n\n", rubric, statbuf.st_size,
         (max/1000)*1000);
       }
-    else fprintf(f, "\n%s\n", rubric);
+    else fprintf(fp, "\n%s\n", rubric);
     }
     }
-  else fprintf(f, "\n%s\n", rubric);
+  else fprintf(fp, "\n%s\n", rubric);
 
 
-  fflush(f);
+  fflush(fp);
   transport_count = 0;
   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(&tctx, bounce_return_size_limit);
   }
 
 /* End the message and wait for the child process to end; no timeout. */
 
   }
 
 /* End the message and wait for the child process to end; no timeout. */
 
-(void)fclose(f);
+(void)fclose(fp);
 rc = child_close(pid, 0);
 
 /* Update the "sent to" log whatever the yield. This errs on the side of
 rc = child_close(pid, 0);
 
 /* Update the "sent to" log whatever the yield. This errs on the side of
@@ -739,30 +749,32 @@ if (cache_fd >= 0)
   {
   uschar *from = cache_buff;
   int size = cache_size;
   {
   uschar *from = cache_buff;
   int size = cache_size;
-  (void)lseek(cache_fd, 0, SEEK_SET);
 
 
-  if (cache_time == NULL)
+  if (lseek(cache_fd, 0, SEEK_SET) == 0)
     {
     {
-    cache_time = from + size;
-    memcpy(cache_time + sizeof(time_t), to, add_size - sizeof(time_t));
-    size += add_size;
-
-    if (cache_size > 0 && size > ob->once_file_size)
+    if (!cache_time)
       {
       {
-      from += sizeof(time_t) + Ustrlen(from + sizeof(time_t)) + 1;
-      size -= (from - cache_buff);
+      cache_time = from + size;
+      memcpy(cache_time + sizeof(time_t), to, add_size - sizeof(time_t));
+      size += add_size;
+
+      if (cache_size > 0 && size > ob->once_file_size)
+       {
+       from += sizeof(time_t) + Ustrlen(from + sizeof(time_t)) + 1;
+       size -= (from - cache_buff);
+       }
       }
       }
-    }
 
 
-  memcpy(cache_time, &now, sizeof(time_t));
-  if(write(cache_fd, from, size) != size)
-    DEBUG(D_transport) debug_printf("Problem writing cache file %s for %s "
-      "transport\n", oncelog, tblock->name);
+    memcpy(cache_time, &now, sizeof(time_t));
+    if(write(cache_fd, from, size) != size)
+      DEBUG(D_transport) debug_printf("Problem writing cache file %s for %s "
+       "transport\n", oncelog, tblock->name);
+    }
   }
 
 /* Update DBM file */
 
   }
 
 /* Update DBM file */
 
-else if (dbm_file != NULL)
+else if (dbm_file)
   {
   EXIM_DATUM key_datum, value_datum;
   EXIM_DATUM_INIT(key_datum);          /* Some DBM libraries need to have */
   {
   EXIM_DATUM key_datum, value_datum;
   EXIM_DATUM_INIT(key_datum);          /* Some DBM libraries need to have */
@@ -784,7 +796,6 @@ try will skip, of course. However, if there were no recipients in the
 message, we do not fail. */
 
 if (rc != 0)
 message, we do not fail. */
 
 if (rc != 0)
-  {
   if (rc == EXIT_NORECIPIENTS)
     {
     DEBUG(D_any) debug_printf("%s transport: message contained no recipients\n",
   if (rc == EXIT_NORECIPIENTS)
     {
     DEBUG(D_any) debug_printf("%s transport: message contained no recipients\n",
@@ -797,7 +808,6 @@ if (rc != 0)
       "transport (%d)", tblock->name, rc);
     goto END_OFF;
     }
       "transport (%d)", tblock->name, rc);
     goto END_OFF;
     }
-  }
 
 /* Log the sending of the message if successful and required. If the file
 fails to open, it's hard to know what to do. We cannot write to the Exim
 
 /* Log the sending of the message if successful and required. If the file
 fails to open, it's hard to know what to do. We cannot write to the Exim
@@ -808,7 +818,7 @@ file opened for appending, in order to avoid interleaving of output from
 different processes. The log_buffer can be used exactly as for main log
 writing. */
 
 different processes. The log_buffer can be used exactly as for main log
 writing. */
 
-if (logfile != NULL)
+if (logfile)
   {
   int log_fd = Uopen(logfile, O_WRONLY|O_APPEND|O_CREAT, ob->mode);
   if (log_fd >= 0)
   {
   int log_fd = Uopen(logfile, O_WRONLY|O_APPEND|O_CREAT, ob->mode);
   if (log_fd >= 0)
@@ -817,37 +827,37 @@ if (logfile != NULL)
     DEBUG(D_transport) debug_printf("logging message details\n");
     sprintf(CS ptr, "%s\n", tod_stamp(tod_log));
     while(*ptr) ptr++;
     DEBUG(D_transport) debug_printf("logging message details\n");
     sprintf(CS ptr, "%s\n", tod_stamp(tod_log));
     while(*ptr) ptr++;
-    if (from != NULL)
+    if (from)
       {
       (void)string_format(ptr, LOG_BUFFER_SIZE - (ptr-log_buffer),
         "  From: %s\n", from);
       while(*ptr) ptr++;
       }
       {
       (void)string_format(ptr, LOG_BUFFER_SIZE - (ptr-log_buffer),
         "  From: %s\n", from);
       while(*ptr) ptr++;
       }
-    if (to != NULL)
+    if (to)
       {
       (void)string_format(ptr, LOG_BUFFER_SIZE - (ptr-log_buffer),
         "  To: %s\n", to);
       while(*ptr) ptr++;
       }
       {
       (void)string_format(ptr, LOG_BUFFER_SIZE - (ptr-log_buffer),
         "  To: %s\n", to);
       while(*ptr) ptr++;
       }
-    if (cc != NULL)
+    if (cc)
       {
       (void)string_format(ptr, LOG_BUFFER_SIZE - (ptr-log_buffer),
         "  Cc: %s\n", cc);
       while(*ptr) ptr++;
       }
       {
       (void)string_format(ptr, LOG_BUFFER_SIZE - (ptr-log_buffer),
         "  Cc: %s\n", cc);
       while(*ptr) ptr++;
       }
-    if (bcc != NULL)
+    if (bcc)
       {
       (void)string_format(ptr, LOG_BUFFER_SIZE - (ptr-log_buffer),
         "  Bcc: %s\n", bcc);
       while(*ptr) ptr++;
       }
       {
       (void)string_format(ptr, LOG_BUFFER_SIZE - (ptr-log_buffer),
         "  Bcc: %s\n", bcc);
       while(*ptr) ptr++;
       }
-    if (subject != NULL)
+    if (subject)
       {
       (void)string_format(ptr, LOG_BUFFER_SIZE - (ptr-log_buffer),
         "  Subject: %s\n", subject);
       while(*ptr) ptr++;
       }
       {
       (void)string_format(ptr, LOG_BUFFER_SIZE - (ptr-log_buffer),
         "  Subject: %s\n", subject);
       while(*ptr) ptr++;
       }
-    if (headers != NULL)
+    if (headers)
       {
       (void)string_format(ptr, LOG_BUFFER_SIZE - (ptr-log_buffer),
         "  %s\n", headers);
       {
       (void)string_format(ptr, LOG_BUFFER_SIZE - (ptr-log_buffer),
         "  %s\n", headers);
@@ -863,7 +873,7 @@ if (logfile != NULL)
   }
 
 END_OFF:
   }
 
 END_OFF:
-if (dbm_file != NULL) EXIM_DBCLOSE(dbm_file);
+if (dbm_file) EXIM_DBCLOSE(dbm_file);
 if (cache_fd > 0) (void)close(cache_fd);
 
 DEBUG(D_transport) debug_printf("%s transport succeeded\n", tblock->name);
 if (cache_fd > 0) (void)close(cache_fd);
 
 DEBUG(D_transport) debug_printf("%s transport succeeded\n", tblock->name);
@@ -871,4 +881,5 @@ DEBUG(D_transport) debug_printf("%s transport succeeded\n", tblock->name);
 return FALSE;
 }
 
 return FALSE;
 }
 
+#endif /*!MACRO_PREDEF*/
 /* End of transport/autoreply.c */
 /* End of transport/autoreply.c */
index 84bbeb9..240d78b 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -40,6 +40,17 @@ address can appear in the tables drtables.c. */
 int lmtp_transport_options_count =
   sizeof(lmtp_transport_options)/sizeof(optionlist);
 
 int lmtp_transport_options_count =
   sizeof(lmtp_transport_options)/sizeof(optionlist);
 
+
+#ifdef MACRO_PREDEF
+
+/* Dummy values */
+lmtp_transport_options_block lmtp_transport_option_defaults = {0};
+void lmtp_transport_init(transport_instance *tblock) {}
+BOOL lmtp_transport_entry(transport_instance *tblock, address_item *addr) {return FALSE;}
+
+#else   /*!MACRO_PREDEF*/
+
+
 /* Default private options block for the lmtp transport. */
 
 lmtp_transport_options_block lmtp_transport_option_defaults = {
 /* Default private options block for the lmtp transport. */
 
 lmtp_transport_options_block lmtp_transport_option_defaults = {
@@ -106,7 +117,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
   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
 */
 
 Returns:       TRUE if a "QUIT" command should be sent, else FALSE
 */
@@ -171,7 +182,7 @@ if (*errno_value == ERRNO_WRITEINCOMPLETE)
 
 if (buffer[0] != 0)
   {
 
 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;
   *message = string_sprintf("LMTP error after %s: %s", big_buffer, s);
   *yield = buffer[0];
   return TRUE;
@@ -212,19 +223,21 @@ Returns:     TRUE if successful, FALSE if not, with errno set
 static BOOL
 lmtp_write_command(int fd, const char *format, ...)
 {
 static BOOL
 lmtp_write_command(int fd, const char *format, ...)
 {
-int count, rc;
+gstring gs = { .size = big_buffer_size, .ptr = 0, .s = big_buffer };
+int rc;
 va_list ap;
 va_list ap;
+
 va_start(ap, format);
 va_start(ap, format);
-if (!string_vformat(big_buffer, big_buffer_size, CS format, ap))
+if (!string_vformat(&gs, FALSE, CS format, ap))
   {
   {
+  va_end(ap);
   errno = ERRNO_SMTPFORMAT;
   return FALSE;
   }
 va_end(ap);
   errno = ERRNO_SMTPFORMAT;
   return FALSE;
   }
 va_end(ap);
-count = Ustrlen(big_buffer);
-DEBUG(D_transport|D_v) debug_printf("  LMTP>> %s", big_buffer);
-rc = write(fd, big_buffer, count);
-big_buffer[count-2] = 0;     /* remove \r\n for debug and error message */
+DEBUG(D_transport|D_v) debug_printf("  LMTP>> %s", string_from_gstring(&gs));
+rc = write(fd, gs.s, gs.ptr);
+gs.ptr -= 2; string_from_gstring(&gs); /* remove \r\n for debug and error message */
 if (rc > 0) return TRUE;
 DEBUG(D_transport) debug_printf("write failed: %s\n", strerror(errno));
 return FALSE;
 if (rc > 0) return TRUE;
 DEBUG(D_transport) debug_printf("write failed: %s\n", strerror(errno));
 return FALSE;
@@ -287,10 +300,10 @@ for (;;)
 
     *readptr = 0;           /* In case nothing gets read */
     sigalrm_seen = FALSE;
 
     *readptr = 0;           /* In case nothing gets read */
     sigalrm_seen = FALSE;
-    alarm(timeout);
+    ALARM(timeout);
     rc = Ufgets(readptr, size-1, f);
     save_errno = errno;
     rc = Ufgets(readptr, size-1, f);
     save_errno = errno;
-    alarm(0);
+    ALARM_CLR(0);
     errno = save_errno;
 
     if (rc != NULL) break;  /* A line has been read */
     errno = save_errno;
 
     if (rc != NULL) break;  /* A line has been read */
@@ -460,7 +473,7 @@ BOOL yield = FALSE;
 address_item *addr;
 uschar *igquotstr = US"";
 uschar *sockname = NULL;
 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);
 uschar buffer[256];
 
 DEBUG(D_transport) debug_printf("%s transport entered\n", tblock->name);
@@ -469,13 +482,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. */
 
 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;
   {
   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 (f.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. */
   }
 
 /* When a socket is specified, expand the string and create a socket. */
@@ -498,38 +527,11 @@ else
         ob->skt, tblock->name, strerror(errno));
     return FALSE;
     }
         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 (f.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)
   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 +543,7 @@ else
     }
   }
 
     }
   }
 
+
 /* Make the output we are going to read into a file. */
 
 out = fdopen(fd_out, "rb");
 /* Make the output we are going to read into a file. */
 
 out = fdopen(fd_out, "rb");
@@ -618,6 +621,13 @@ for (addr = addrlist; addr != NULL; addr = addr->next)
 if (send_data)
   {
   BOOL ok;
 if (send_data)
   {
   BOOL ok;
+  transport_ctx tctx = {
+    {fd_in},
+    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))
 
   if (!lmtp_write_command(fd_in, "DATA\r\n")) goto WRITE_FAILED;
   if (!lmtp_read_response(out, buffer, sizeof(buffer), '3', timeout))
@@ -637,9 +647,7 @@ if (send_data)
     debug_printf("  LMTP>> writing message and terminating \".\"\n");
 
   transport_count = 0;
     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(&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. */
 
   /* 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 +672,11 @@ if (send_data)
     if (lmtp_read_response(out, buffer, sizeof(buffer), '2', timeout))
       {
       addr->transport_return = OK;
     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)? US string_copy(s) : US s;
         }
       }
     /* If the response has failed badly, use it for all the remaining pending
         }
       }
     /* If the response has failed badly, use it for all the remaining pending
@@ -784,6 +793,15 @@ DEBUG(D_transport)
   debug_printf("%s transport yields %d\n", tblock->name, yield);
 
 return yield;
   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;
 }
 
 }
 
+#endif /*!MACRO_PREDEF*/
 /* End of transport/lmtp.c */
 /* End of transport/lmtp.c */
index 3366a6d..b94c223 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -95,6 +95,17 @@ address can appear in the tables drtables.c. */
 int pipe_transport_options_count =
   sizeof(pipe_transport_options)/sizeof(optionlist);
 
 int pipe_transport_options_count =
   sizeof(pipe_transport_options)/sizeof(optionlist);
 
+
+#ifdef MACRO_PREDEF
+
+/* Dummy values */
+pipe_transport_options_block pipe_transport_option_defaults = {0};
+void pipe_transport_init(transport_instance *tblock) {}
+BOOL pipe_transport_entry(transport_instance *tblock, address_item *addr) {return FALSE;}
+
+#else   /*!MACRO_PREDEF*/
+
+
 /* Default private options block for the pipe transport. */
 
 pipe_transport_options_block pipe_transport_option_defaults = {
 /* Default private options block for the pipe transport. */
 
 pipe_transport_options_block pipe_transport_option_defaults = {
@@ -326,22 +337,20 @@ Returns:             TRUE if all went well; otherwise an error will be
 */
 
 static BOOL
 */
 
 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;
   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). */
 
 
 /* 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,
 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. */
   return FALSE;
 
 /* Point to the set-up arguments. */
@@ -350,14 +359,13 @@ argv = *argvptr;
 
 /* If allow_commands is set, see if the command is in the permitted list. */
 
 
 /* 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;
   {
   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\" "
     {
     addr->transport_return = DEFER;
     addr->message = string_sprintf("failed to expand string \"%s\" "
@@ -365,10 +373,8 @@ if (ob->allow_commands != NULL)
     return FALSE;
     }
 
     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 (Ustrcmp(p, argv[0]) == 0) { permitted = TRUE; break; }
-    }
   }
 
 /* If permitted is TRUE it means the command was found in the allowed list, and
   }
 
 /* If permitted is TRUE it means the command was found in the allowed list, and
@@ -391,7 +397,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 "
     {
     addr->transport_return = FAIL;
     addr->message = string_sprintf("\"%s\" command not permitted by %s "
@@ -407,10 +413,9 @@ if (argv[0][0] != '/')
   {
   int sep = 0;
   uschar *p;
   {
   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]);
     {
     struct stat statbuf;
     sprintf(CS big_buffer, "%.256s/%.256s", p, argv[0]);
@@ -420,7 +425,7 @@ if (argv[0][0] != '/')
       break;
       }
     }
       break;
       }
     }
-  if (p == NULL)
+  if (!p)
     {
     addr->transport_return = FAIL;
     addr->message = string_sprintf("\"%s\" command not found for %s transport",
     {
     addr->transport_return = FAIL;
     addr->message = string_sprintf("\"%s\" command not found for %s transport",
@@ -453,10 +458,10 @@ Returns:             TRUE if all went well; otherwise an error will be
 */
 
 static BOOL
 */
 
 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 *));
 
 
 *argvptr = argv = store_get((4)*sizeof(uschar *));
 
@@ -466,13 +471,18 @@ argv[1] = US"-c";
 /* We have to take special action to handle the special "variable" called
 $pipe_addresses, which is not recognized by the normal expansion function. */
 
 /* We have to take special action to handle the special "variable" called
 $pipe_addresses, which is not recognized by the normal expansion function. */
 
-DEBUG(D_transport)
-  debug_printf("shell pipe command before expansion:\n  %s\n", cmd);
-
 if (expand_arguments)
   {
 if (expand_arguments)
   {
-  uschar *s = cmd;
-  uschar *p = Ustrstr(cmd, "pipe_addresses");
+  uschar * p = Ustrstr(cmd, "pipe_addresses");
+  gstring * g = NULL;
+
+  DEBUG(D_transport)
+    debug_printf("shell pipe command before expansion:\n  %s\n", cmd);
+
+  /* Allow $recipients in the expansion iff it comes from a system filter */
+
+  f.enable_dollar_recipients = addr && addr->parent &&
+    Ustrcmp(addr->parent->address, "system-filter") == 0;
 
   if (p != NULL && (
          (p > cmd && p[-1] == '$') ||
 
   if (p != NULL && (
          (p > cmd && p[-1] == '$') ||
@@ -480,36 +490,30 @@ if (expand_arguments)
     {
     address_item *ad;
     uschar *q = p + 14;
     {
     address_item *ad;
     uschar *q = p + 14;
-    int size = Ustrlen(cmd) + 64;
-    int offset;
 
     if (p[-1] == '{') { q++; p--; }
 
 
     if (p[-1] == '{') { q++; p--; }
 
-    s = store_get(size);
-    offset = p - cmd - 1;
-    Ustrncpy(s, cmd, offset);
+    g = string_get(Ustrlen(cmd) + 64);
+    g = string_catn(g, cmd, p - cmd - 1);
 
 
-    for (ad = addr; ad != NULL; ad = ad->next)
+    for (ad = addr; ad; 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) g = string_catn(g, US" ", 1);
+      g = string_cat(g, ad->address);
       }
 
       }
 
-    string_cat(s, &size, &offset, q, Ustrlen(q));
-    s[offset] = 0;
+    g = string_cat(g, q);
+    argv[2] = (cmd = string_from_gstring(g)) ? expand_string(cmd) : NULL;
     }
     }
+  else
+    argv[2] = expand_string(cmd);
 
 
-  /* Allow $recipients in the expansion iff it comes from a system filter */
-
-  enable_dollar_recipients = addr != NULL &&
-    addr->parent != NULL &&
-    Ustrcmp(addr->parent->address, "system-filter") == 0;
-  argv[2] = expand_string(s);
-  enable_dollar_recipients = FALSE;
+  f.enable_dollar_recipients = FALSE;
 
 
-  if (argv[2] == NULL)
+  if (!argv[2])
     {
     {
-    addr->transport_return = search_find_defer? DEFER : expand_fail;
+    addr->transport_return = f.search_find_defer ? DEFER : expand_fail;
     addr->message = string_sprintf("Expansion of command \"%s\" "
       "in %s transport failed: %s",
       cmd, tname, expand_string_message);
     addr->message = string_sprintf("Expansion of command \"%s\" "
       "in %s transport failed: %s",
       cmd, tname, expand_string_message);
@@ -519,9 +523,14 @@ if (expand_arguments)
   DEBUG(D_transport)
     debug_printf("shell pipe command after expansion:\n  %s\n", argv[2]);
   }
   DEBUG(D_transport)
     debug_printf("shell pipe command after expansion:\n  %s\n", argv[2]);
   }
-else argv[2] = cmd;
+else
+  {
+  DEBUG(D_transport)
+    debug_printf("shell pipe command (no expansion):\n  %s\n", cmd);
+  argv[2] = cmd;
+  }
 
 
-argv[3] = (uschar *)0;
+argv[3] = US 0;
 return TRUE;
 }
 
 return TRUE;
 }
 
@@ -551,11 +560,18 @@ pipe_transport_options_block *ob =
 int timeout = ob->timeout;
 BOOL written_ok = FALSE;
 BOOL expand_arguments;
 int timeout = ob->timeout;
 BOOL written_ok = FALSE;
 BOOL expand_arguments;
-uschar **argv;
+const uschar **argv;
 uschar *envp[50];
 uschar *envp[50];
-uschar *envlist = ob->environment;
+const uschar *envlist = ob->environment;
 uschar *cmd, *ss;
 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 = tblock,
+  .addr = addr,
+  .check_string = ob->check_string,
+  .escape_string = ob->escape_string,
+  ob->options | topt_not_socket /* set at initialization time */
+};
 
 DEBUG(D_transport) debug_printf("%s transport entered\n", tblock->name);
 
 
 DEBUG(D_transport) debug_printf("%s transport entered\n", tblock->name);
 
@@ -574,7 +590,7 @@ if (testflag(addr, af_pfr) && addr->local_part[0] == '|')
   {
   if (ob->force_command)
     {
   {
   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;
     setflag(addr, af_force_command);
     cmd = ob->cmd;
     expand_arguments = TRUE;
@@ -612,7 +628,7 @@ if (cmd == NULL || *cmd == '\0')
 and numerical the variables in existence. These are passed in
 addr->pipe_expandn for use here. */
 
 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;
   {
   uschar **ss = addr->pipe_expandn;
   expand_nmax = -1;
@@ -652,7 +668,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("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);
 envp[envcount++] = string_sprintf("RECIPIENT=%#s%#s%#s@%#s",
   deliver_localpart_prefix, deliver_localpart, deliver_localpart_suffix,
   deliver_domain);
@@ -663,15 +679,15 @@ envp[envcount++] = US"SHELL=/bin/sh";
 if (addr->host_list != NULL)
   envp[envcount++] = string_sprintf("HOST=%s", addr->host_list->name);
 
 if (addr->host_list != NULL)
   envp[envcount++] = string_sprintf("HOST=%s", addr->host_list->name);
 
-if (timestamps_utc) envp[envcount++] = US"TZ=UTC";
+if (f.timestamps_utc) envp[envcount++] = US"TZ=UTC";
 else if (timezone_string != NULL && timezone_string[0] != 0)
   envp[envcount++] = string_sprintf("TZ=%s", timezone_string);
 
 /* Add any requested items */
 
 else if (timezone_string != NULL && timezone_string[0] != 0)
   envp[envcount++] = string_sprintf("TZ=%s", timezone_string);
 
 /* Add any requested items */
 
-if (envlist != NULL)
+if (envlist)
   {
   {
-  envlist = expand_string(envlist);
+  envlist = expand_cstring(envlist);
   if (envlist == NULL)
     {
     addr->transport_return = DEFER;
   if (envlist == NULL)
     {
     addr->transport_return = DEFER;
@@ -682,10 +698,9 @@ if (envlist != NULL)
     }
   }
 
     }
   }
 
-while ((ss = string_nextinlist(&envlist, &envsep, big_buffer, big_buffer_size))
-       != NULL)
+while ((ss = string_nextinlist(&envlist, &envsep, big_buffer, big_buffer_size)))
    {
    {
-   if (envcount > sizeof(envp)/sizeof(uschar *) - 2)
+   if (envcount > nelem(envp) - 2)
      {
      addr->transport_return = DEFER;
      addr->message = string_sprintf("too many environment settings for "
      {
      addr->transport_return = DEFER;
      addr->message = string_sprintf("too many environment settings for "
@@ -699,7 +714,7 @@ envp[envcount] = NULL;
 
 /* If the -N option is set, can't do any more. */
 
 
 /* If the -N option is set, can't do any more. */
 
-if (dont_deliver)
+if (f.dont_deliver)
   {
   DEBUG(D_transport)
     debug_printf("*** delivery by %s transport bypassed by -N option",
   {
   DEBUG(D_transport)
     debug_printf("*** delivery by %s transport bypassed by -N option",
@@ -729,7 +744,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. */
 
 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(
   {
   addr->transport_return = DEFER;
   addr->message = string_sprintf(
@@ -737,6 +752,7 @@ if ((pid = child_open(argv, envp, ob->umask, &fd_in, &fd_out, TRUE)) < 0)
       strerror(errno));
   return FALSE;
   }
       strerror(errno));
   return FALSE;
   }
+tctx.u.fd = fd_in;
 
 /* Now fork a process to handle the output that comes down the pipe. */
 
 
 /* Now fork a process to handle the output that comes down the pipe. */
 
@@ -798,7 +814,7 @@ bit here to let the sub-process get going, but it may still not complete. So we
 ignore all writing errors. (When in the test harness, we do do a short sleep so
 any debugging output is likely to be in the same order.) */
 
 ignore all writing errors. (When in the test harness, we do do a short sleep so
 any debugging output is likely to be in the same order.) */
 
-if (running_in_test_harness) millisleep(500);
+if (f.running_in_test_harness) millisleep(500);
 
 DEBUG(D_transport) debug_printf("Writing message to pipe\n");
 
 
 DEBUG(D_transport) debug_printf("Writing message to pipe\n");
 
@@ -821,13 +837,13 @@ if (ob->message_prefix != NULL)
   uschar *prefix = expand_string(ob->message_prefix);
   if (prefix == NULL)
     {
   uschar *prefix = expand_string(ob->message_prefix);
   if (prefix == NULL)
     {
-    addr->transport_return = search_find_defer? DEFER : PANIC;
+    addr->transport_return = f.search_find_defer? DEFER : PANIC;
     addr->message = string_sprintf("Expansion of \"%s\" (prefix for %s "
       "transport) failed: %s", ob->message_prefix, tblock->name,
       expand_string_message);
     return FALSE;
     }
     addr->message = string_sprintf("Expansion of \"%s\" (prefix for %s "
       "transport) failed: %s", ob->message_prefix, tblock->name,
       expand_string_message);
     return FALSE;
     }
-  if (!transport_write_block(fd_in, prefix, Ustrlen(prefix)))
+  if (!transport_write_block(&tctx, prefix, Ustrlen(prefix), FALSE))
     goto END_WRITE;
   }
 
     goto END_WRITE;
   }
 
@@ -843,39 +859,35 @@ if (ob->use_bsmtp)
   if (!transport_write_string(fd_in, "MAIL FROM:<%s>%s", return_path, eol))
     goto END_WRITE;
 
   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,
         "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;
   }
 
 
   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(&tctx, 0))
     goto END_WRITE;
 
 /* Now any configured suffix */
 
     goto END_WRITE;
 
 /* Now any configured suffix */
 
-if (ob->message_suffix != NULL)
+if (ob->message_suffix)
   {
   uschar *suffix = expand_string(ob->message_suffix);
   {
   uschar *suffix = expand_string(ob->message_suffix);
-  if (suffix == NULL)
+  if (!suffix)
     {
     {
-    addr->transport_return = search_find_defer? DEFER : PANIC;
+    addr->transport_return = f.search_find_defer? DEFER : PANIC;
     addr->message = string_sprintf("Expansion of \"%s\" (suffix for %s "
       "transport) failed: %s", ob->message_suffix, tblock->name,
       expand_string_message);
     return FALSE;
     }
     addr->message = string_sprintf("Expansion of \"%s\" (suffix for %s "
       "transport) failed: %s", ob->message_suffix, tblock->name,
       expand_string_message);
     return FALSE;
     }
-  if (!transport_write_block(fd_in, suffix, Ustrlen(suffix)))
+  if (!transport_write_block(&tctx, suffix, Ustrlen(suffix), FALSE))
     goto END_WRITE;
   }
 
     goto END_WRITE;
   }
 
@@ -908,7 +920,7 @@ if (!written_ok)
   if (errno == ETIMEDOUT)
     {
     addr->message = string_sprintf("%stimeout while writing to pipe",
   if (errno == ETIMEDOUT)
     {
     addr->message = string_sprintf("%stimeout while writing to pipe",
-      transport_filter_timed_out? "transport filter " : "");
+      f.transport_filter_timed_out ? "transport filter " : "");
     addr->transport_return = ob->timeout_defer? DEFER : FAIL;
     timeout = 1;
     }
     addr->transport_return = ob->timeout_defer? DEFER : FAIL;
     timeout = 1;
     }
@@ -974,7 +986,7 @@ if ((rc = child_close(pid, timeout)) != 0)
   This prevents the transport_filter timeout message from getting overwritten
   by the exit error which is not the cause of the problem. */
 
   This prevents the transport_filter timeout message from getting overwritten
   by the exit error which is not the cause of the problem. */
 
-  else if (transport_filter_timed_out)
+  else if (f.transport_filter_timed_out)
     {
     killpg(pid, SIGKILL);
     kill(outpid, SIGKILL);
     {
     killpg(pid, SIGKILL);
     kill(outpid, SIGKILL);
@@ -1026,7 +1038,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
   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
 
 
       #define EX_UNAVAILABLE  69
 
@@ -1062,9 +1074,10 @@ if ((rc = child_close(pid, timeout)) != 0)
     else if (!ob->ignore_status)
       {
       uschar *ss;
     else if (!ob->ignore_status)
       {
       uschar *ss;
-      int size, ptr, i;
+      gstring * g;
+      int 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. */
 
       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 +1086,13 @@ if ((rc = child_close(pid, timeout)) != 0)
 
       else
         {
 
       else
         {
-        uschar *s = ob->temp_errors;
+        const uschar *s = ob->temp_errors;
         uschar *p;
         uschar *p;
-        uschar buffer[64];
         int sep = 0;
 
         addr->transport_return = FAIL;
         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; }
           if (rc == Uatoi(p)) { addr->transport_return = DEFER; break; }
-          }
         }
 
       /* Ensure the message contains the expanded command and arguments. This
         }
 
       /* Ensure the message contains the expanded command and arguments. This
@@ -1090,9 +1100,7 @@ if ((rc = child_close(pid, timeout)) != 0)
 
       addr->message = string_sprintf("Child process of %s transport returned "
         "%d", tblock->name, rc);
 
       addr->message = string_sprintf("Child process of %s transport returned "
         "%d", tblock->name, rc);
-
-      ptr = Ustrlen(addr->message);
-      size = ptr + 1;
+      g = string_cat(NULL, addr->message);
 
       /* If the return code is > 128, it often means that a shell command
       was terminated by a signal. */
 
       /* If the return code is > 128, it often means that a shell command
       was terminated by a signal. */
@@ -1104,38 +1112,34 @@ if ((rc = child_close(pid, timeout)) != 0)
 
       if (*ss != 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));
+        g = string_catn(g, US" ", 1);
+        g = string_cat (g, ss);
         }
 
       /* Now add the command and arguments */
 
         }
 
       /* Now add the command and arguments */
 
-      addr->message = string_cat(addr->message, &size, &ptr,
-        US" from command:", 14);
+      g = string_catn(g, US" from command:", 14);
 
       for (i = 0; i < sizeof(argv)/sizeof(int *) && argv[i] != NULL; i++)
         {
         BOOL quote = FALSE;
 
       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);
+        g = string_catn(g, US" ", 1);
         if (Ustrpbrk(argv[i], " \t") != NULL)
           {
           quote = TRUE;
         if (Ustrpbrk(argv[i], " \t") != NULL)
           {
           quote = TRUE;
-          addr->message = string_cat(addr->message, &size, &ptr, US"\"", 1);
+          g = string_catn(g, US"\"", 1);
           }
           }
-        addr->message = string_cat(addr->message, &size, &ptr, argv[i],
-          Ustrlen(argv[i]));
+        g = string_cat(g, argv[i]);
         if (quote)
         if (quote)
-          addr->message = string_cat(addr->message, &size, &ptr, US"\"", 1);
+          g = string_catn(g, US"\"", 1);
         }
 
       /* Add previous filter timeout message, if present. */
 
         }
 
       /* Add previous filter timeout message, if present. */
 
-      if (*tmsg != 0)
-        addr->message = string_cat(addr->message, &size, &ptr, tmsg,
-          Ustrlen(tmsg));
+      if (*tmsg)
+        g = string_cat(g, tmsg);
 
 
-      addr->message[ptr] = 0;  /* Ensure concatenated string terminated */
+      addr->message = string_from_gstring(g);
       }
     }
   }
       }
     }
   }
@@ -1158,4 +1162,5 @@ if (addr->transport_return != OK)
 return FALSE;
 }
 
 return FALSE;
 }
 
+#endif /*!MACRO_PREDEF*/
 /* End of transport/pipe.c */
 /* End of transport/pipe.c */
diff --git a/src/transports/queuefile.c b/src/transports/queuefile.c
new file mode 100644 (file)
index 0000000..cde6e53
--- /dev/null
@@ -0,0 +1,279 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) Andrew Colin Kissa <andrew@topdog.za.net> 2016 */
+/* Copyright (c) University of Cambridge 2016 */
+/* Copyright (c) The Exim Maintainers 1995 - 2018 */
+/* 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);
+
+
+#ifdef MACRO_PREDEF
+
+/* Dummy values */
+queuefile_transport_options_block queuefile_transport_option_defaults = {0};
+void queuefile_transport_init(transport_instance *tblock) {}
+BOOL queuefile_transport_entry(transport_instance *tblock, address_item *addr) {return FALSE;}
+
+#else   /*!MACRO_PREDEF*/
+
+
+
+/* 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
+  dstpath      destination directory name
+  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,
+  const uschar * dstpath, 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 * s, * op;
+
+dstpath = string_sprintf("%s/%s-%s", dstpath, message_id, suffix);
+
+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, * dstdir;
+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 (!(dstdir = expand_string(ob->dirname)))
+  {
+  addr->message = string_sprintf("%s transport: failed to expand dirname option",
+    tblock->name);
+  addr->transport_return = DEFER;
+  return FALSE;
+  }
+if (*dstdir != '/')
+  {
+  addr->transport_return = PANIC;
+  addr->message = string_sprintf("%s transport directory: "
+    "%s is not absolute", tblock->name, dstdir);
+  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 = dstdir,
+       (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 = dstdir,    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 (f.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, dstdir, 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, dstdir, 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", dstdir, 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;
+}
+
+#endif /*!MACRO_PREDEF*/
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..a351da8 100644 (file)
@@ -2,16 +2,12 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "../exim.h"
 #include "smtp.h"
 
 /* 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
 
 /* 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,10 +15,19 @@ 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[] = {
 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,
       (void *)offsetof(smtp_transport_options_block, allow_localhost) },
   { "address_retry_include_sender", opt_bool,
       (void *)offsetof(smtp_transport_options_block, address_retry_include_sender) },
   { "allow_localhost",      opt_bool,
       (void *)offsetof(smtp_transport_options_block, allow_localhost) },
+#ifdef EXPERIMENTAL_ARC
+  { "arc_sign", opt_stringptr,
+      (void *)offsetof(smtp_transport_options_block, arc_sign) },
+#endif
   { "authenticated_sender", opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, authenticated_sender) },
   { "authenticated_sender_force", opt_bool,
   { "authenticated_sender", opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, authenticated_sender) },
   { "authenticated_sender_force", opt_bool,
@@ -33,32 +38,42 @@ optionlist smtp_transport_options[] = {
       (void *)offsetof(smtp_transport_options_block, connect_timeout) },
   { "connection_max_messages", opt_int | opt_public,
       (void *)offsetof(transport_instance, connection_max_messages) },
       (void *)offsetof(smtp_transport_options_block, connect_timeout) },
   { "connection_max_messages", opt_int | opt_public,
       (void *)offsetof(transport_instance, connection_max_messages) },
+# ifdef SUPPORT_DANE
+  { "dane_require_tls_ciphers", opt_stringptr,
+      (void *)offsetof(smtp_transport_options_block, dane_require_tls_ciphers) },
+# endif
   { "data_timeout",         opt_time,
       (void *)offsetof(smtp_transport_options_block, data_timeout) },
   { "delay_after_cutoff", opt_bool,
       (void *)offsetof(smtp_transport_options_block, delay_after_cutoff) },
 #ifndef DISABLE_DKIM
   { "dkim_canon", opt_stringptr,
   { "data_timeout",         opt_time,
       (void *)offsetof(smtp_transport_options_block, data_timeout) },
   { "delay_after_cutoff", opt_bool,
       (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,
   { "dkim_domain", opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, dkim_domain) },
+      (void *)offsetof(smtp_transport_options_block, dkim.dkim_domain) },
+  { "dkim_hash", opt_stringptr,
+      (void *)offsetof(smtp_transport_options_block, dkim.dkim_hash) },
+  { "dkim_identity", opt_stringptr,
+      (void *)offsetof(smtp_transport_options_block, dkim.dkim_identity) },
   { "dkim_private_key", opt_stringptr,
   { "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,
   { "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,
   { "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,
   { "dkim_strict", opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, dkim_strict) },
+      (void *)offsetof(smtp_transport_options_block, dkim.dkim_strict) },
+  { "dkim_timestamps", opt_stringptr,
+      (void *)offsetof(smtp_transport_options_block, dkim.dkim_timestamps) },
 #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,
 #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,
   { "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,
   { "dscp",                 opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, dscp) },
   { "fallback_hosts",       opt_stringptr,
@@ -67,17 +82,6 @@ optionlist smtp_transport_options[] = {
       (void *)offsetof(smtp_transport_options_block, final_timeout) },
   { "gethostbyname",        opt_bool,
       (void *)offsetof(smtp_transport_options_block, gethostbyname) },
       (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,
   { "helo_data",            opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, helo_data) },
   { "hosts",                opt_stringptr,
@@ -97,9 +101,15 @@ optionlist smtp_transport_options[] = {
 #ifdef SUPPORT_TLS
   { "hosts_nopass_tls",     opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, hosts_nopass_tls) },
 #ifdef SUPPORT_TLS
   { "hosts_nopass_tls",     opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, hosts_nopass_tls) },
+  { "hosts_noproxy_tls",    opt_stringptr,
+      (void *)offsetof(smtp_transport_options_block, hosts_noproxy_tls) },
 #endif
   { "hosts_override",       opt_bool,
       (void *)offsetof(smtp_transport_options_block, hosts_override) },
 #endif
   { "hosts_override",       opt_bool,
       (void *)offsetof(smtp_transport_options_block, hosts_override) },
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+  { "hosts_pipe_connect",   opt_stringptr,
+      (void *)offsetof(smtp_transport_options_block, hosts_pipe_connect) },
+#endif
   { "hosts_randomize",      opt_bool,
       (void *)offsetof(smtp_transport_options_block, hosts_randomize) },
 #if defined(SUPPORT_TLS) && !defined(DISABLE_OCSP)
   { "hosts_randomize",      opt_bool,
       (void *)offsetof(smtp_transport_options_block, hosts_randomize) },
 #if defined(SUPPORT_TLS) && !defined(DISABLE_OCSP)
@@ -109,6 +119,10 @@ optionlist smtp_transport_options[] = {
   { "hosts_require_auth",   opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, hosts_require_auth) },
 #ifdef SUPPORT_TLS
   { "hosts_require_auth",   opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, hosts_require_auth) },
 #ifdef SUPPORT_TLS
+# ifdef SUPPORT_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) },
 # ifndef DISABLE_OCSP
   { "hosts_require_ocsp",   opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, hosts_require_ocsp) },
@@ -118,6 +132,14 @@ optionlist smtp_transport_options[] = {
 #endif
   { "hosts_try_auth",       opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, hosts_try_auth) },
 #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(SUPPORT_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) },
 #ifndef DISABLE_PRDR
   { "hosts_try_prdr",       opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, hosts_try_prdr) },
@@ -134,20 +156,24 @@ 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) },
       (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) },
       (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, 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) }
+      (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
 #ifdef SUPPORT_TLS
,{ "tls_certificate",      opt_stringptr,
 { "tls_certificate",      opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, tls_certificate) },
   { "tls_crl",              opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, tls_crl) },
       (void *)offsetof(smtp_transport_options_block, tls_certificate) },
   { "tls_crl",              opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, tls_crl) },
@@ -163,125 +189,161 @@ 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) },
       (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)},
   { "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,
   { "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) }
+      (void *)offsetof(smtp_transport_options_block, tls_verify_hosts) },
 #endif
 #endif
-#ifdef EXPERIMENTAL_TPDA
,{ "tpda_host_defer_action", opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, tpda_host_defer_action) },
+#ifdef SUPPORT_I18N
 { "utf8_downconvert",            opt_stringptr,
+      (void *)offsetof(smtp_transport_options_block, utf8_downconvert) },
 #endif
 };
 
 /* Size of the options list. An extern variable has to be used so that its
 address can appear in the tables drtables.c. */
 
 #endif
 };
 
 /* Size of the options list. An extern variable has to be used so that its
 address can appear in the tables drtables.c. */
 
-int smtp_transport_options_count =
-  sizeof(smtp_transport_options)/sizeof(optionlist);
+int smtp_transport_options_count = nelem(smtp_transport_options);
+
+
+#ifdef MACRO_PREDEF
+
+/* Dummy values */
+smtp_transport_options_block smtp_transport_option_defaults = {0};
+void smtp_transport_init(transport_instance *tblock) {}
+BOOL smtp_transport_entry(transport_instance *tblock, address_item *addr) {return FALSE;}
+void smtp_transport_closedown(transport_instance *tblock) {}
+
+#else   /*!MACRO_PREDEF*/
+
 
 /* Default private options block for the smtp transport. */
 
 smtp_transport_options_block smtp_transport_option_defaults = {
 
 /* Default private options block for the smtp transport. */
 
 smtp_transport_options_block smtp_transport_option_defaults = {
-  NULL,                /* hosts */
-  NULL,                /* fallback_hosts */
-  NULL,                /* hostlist */
-  NULL,                /* fallback_hostlist */
-  NULL,                /* authenticated_sender */
-  US"$primary_hostname", /* helo_data */
-  NULL,                /* interface */
-  NULL,                /* port */
-  US"smtp",            /* protocol */
-  NULL,                /* DSCP */
-  NULL,                /* serialize_hosts */
-  NULL,                /* hosts_try_auth */
-  NULL,                /* hosts_require_auth */
+  .hosts =                     NULL,
+  .fallback_hosts =            NULL,
+  .hostlist =                  NULL,
+  .fallback_hostlist =         NULL,
+  .helo_data =                 US"$primary_hostname",
+  .interface =                 NULL,
+  .port =                      NULL,
+  .protocol =                  US"smtp",
+  .dscp =                      NULL,
+  .serialize_hosts =           NULL,
+  .hosts_try_auth =            NULL,
+  .hosts_require_auth =                NULL,
+  .hosts_try_chunking =                US"*",
+#ifdef SUPPORT_DANE
+  .hosts_try_dane =            NULL,
+  .hosts_require_dane =                NULL,
+  .dane_require_tls_ciphers =  NULL,
+#endif
+  .hosts_try_fastopen =                NULL,
 #ifndef DISABLE_PRDR
 #ifndef DISABLE_PRDR
-  NULL,                /* hosts_try_prdr */
+  .hosts_try_prdr =            US"*",
 #endif
 #ifndef DISABLE_OCSP
 #endif
 #ifndef DISABLE_OCSP
-  US"*",               /* hosts_request_ocsp */
-  NULL,                /* hosts_require_ocsp */
-#endif
-  NULL,                /* hosts_require_tls */
-  NULL,                /* hosts_avoid_tls */
-  US"*",               /* hosts_verify_avoid_tls */
-  NULL,                /* hosts_avoid_pipelining */
-  NULL,                /* hosts_avoid_esmtp */
-  NULL,                /* hosts_nopass_tls */
-  5*60,                /* command_timeout */
-  5*60,                /* connect_timeout; shorter system default overrides */
-  5*60,                /* data timeout */
-  10*60,               /* final timeout */
-  1024,                /* size_addition */
-  5,                   /* hosts_max_try */
-  50,                  /* hosts_max_try_hardlimit */
-  TRUE,                /* address_retry_include_sender */
-  FALSE,               /* allow_localhost */
-  FALSE,               /* authenticated_sender_force */
-  FALSE,               /* gethostbyname */
-  TRUE,                /* dns_qualify_single */
-  FALSE,               /* dns_search_parents */
-  NULL,                /* dnssec_request_domains */
-  NULL,                /* dnssec_require_domains */
-  TRUE,                /* delay_after_cutoff */
-  FALSE,               /* hosts_override */
-  FALSE,               /* hosts_randomize */
-  TRUE,                /* keepalive */
-  FALSE,               /* lmtp_ignore_quota */
-  TRUE                 /* retry_include_ip_address */
+  .hosts_request_ocsp =                US"*",               /* hosts_request_ocsp (except under DANE; tls_client_start()) */
+  .hosts_require_ocsp =                NULL,
+#endif
+  .hosts_require_tls =         NULL,
+  .hosts_avoid_tls =           NULL,
+  .hosts_verify_avoid_tls =    NULL,
+  .hosts_avoid_pipelining =    NULL,
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+  .hosts_pipe_connect =                NULL,
+#endif
+  .hosts_avoid_esmtp =         NULL,
 #ifdef SUPPORT_TLS
 #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 */
-  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
+  .hosts_nopass_tls =          NULL,
+  .hosts_noproxy_tls =         US"*",
 #endif
 #endif
-#ifndef DISABLE_DKIM
- ,NULL,                /* dkim_canon */
-  NULL,                /* dkim_domain */
-  NULL,                /* dkim_private_key */
-  NULL,                /* dkim_selector */
-  NULL,                /* dkim_sign_headers */
-  NULL                 /* dkim_strict */
+  .command_timeout =           5*60,
+  .connect_timeout =           5*60,
+  .data_timeout =              5*60,
+  .final_timeout =             10*60,
+  .size_addition =             1024,
+  .hosts_max_try =             5,
+  .hosts_max_try_hardlimit =   50,
+  .address_retry_include_sender = TRUE,
+  .allow_localhost =           FALSE,
+  .authenticated_sender_force =        FALSE,
+  .gethostbyname =             FALSE,
+  .dns_qualify_single =                TRUE,
+  .dns_search_parents =                FALSE,
+  .dnssec = { .request=NULL, .require=NULL },
+  .delay_after_cutoff =                TRUE,
+  .hosts_override =            FALSE,
+  .hosts_randomize =           FALSE,
+  .keepalive =                 TRUE,
+  .lmtp_ignore_quota =         FALSE,
+  .expand_retry_include_ip_address =   NULL,
+  .retry_include_ip_address =  TRUE,
+#ifdef SUPPORT_SOCKS
+  .socks_proxy =               NULL,
+#endif
+#ifdef SUPPORT_TLS
+  .tls_certificate =           NULL,
+  .tls_crl =                   NULL,
+  .tls_privatekey =            NULL,
+  .tls_require_ciphers =       NULL,
+  .tls_sni =                   NULL,
+  .tls_verify_certificates =   US"system",
+  .tls_dh_min_bits =           EXIM_CLIENT_DH_DEFAULT_MIN_BITS,
+  .tls_tempfail_tryclear =     TRUE,
+  .tls_verify_hosts =          NULL,
+  .tls_try_verify_hosts =      US"*",
+  .tls_verify_cert_hostnames = US"*",
+#endif
+#ifdef SUPPORT_I18N
+  .utf8_downconvert =          NULL,
 #endif
 #endif
-#ifdef EXPERIMENTAL_TPDA
- ,NULL                 /* tpda_host_defer_action */
+#ifndef DISABLE_DKIM
+ .dkim =
+   {.dkim_domain =             NULL,
+    .dkim_identity =           NULL,
+    .dkim_private_key =                NULL,
+    .dkim_selector =           NULL,
+    .dkim_canon =              NULL,
+    .dkim_sign_headers =       NULL,
+    .dkim_strict =             NULL,
+    .dkim_hash =               US"sha256",
+    .dkim_timestamps =         NULL,
+    .dot_stuffed =             FALSE,
+    .force_bodyhash =          FALSE,
+# ifdef EXPERIMENTAL_ARC
+    .arc_signspec =            NULL,
+# endif
+    },
+# ifdef EXPERIMENTAL_ARC
+  .arc_sign =                  NULL,
+# endif
 #endif
 };
 
 #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 };
 
 /* 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 */
 
 
 
 
 /* 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 */
+
+/*XXX move to smtp_context */
+static BOOL    pipelining_active;      /* current transaction is in pipe mode */
+
+
+static unsigned ehlo_response(uschar * buf, unsigned checks);
 
 
 /*************************************************
 
 
 /*************************************************
@@ -310,8 +372,7 @@ static int
 smtp_transport_setup(transport_instance *tblock, address_item *addrlist,
   transport_feedback *tf, uid_t uid, gid_t gid, uschar **errmsg)
 {
 smtp_transport_setup(transport_instance *tblock, address_item *addrlist,
   transport_feedback *tf, uid_t uid, gid_t gid, uschar **errmsg)
 {
-smtp_transport_options_block *ob =
-  (smtp_transport_options_block *)(tblock->options_block);
+smtp_transport_options_block *ob = SOB tblock->options_block;
 
 errmsg = errmsg;    /* Keep picky compilers happy */
 uid = uid;
 
 errmsg = errmsg;    /* Keep picky compilers happy */
 uid = uid;
@@ -319,7 +380,7 @@ gid = gid;
 
 /* Pass back options if required. This interface is getting very messy. */
 
 
 /* Pass back options if required. This interface is getting very messy. */
 
-if (tf != NULL)
+if (tf)
   {
   tf->interface = ob->interface;
   tf->port = ob->port;
   {
   tf->interface = ob->interface;
   tf->port = ob->port;
@@ -338,11 +399,8 @@ host lists, provided that the local host wasn't present in the original host
 list. */
 
 if (!testflag(addrlist, af_local_host_removed))
 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;
 }
 
 return OK;
 }
@@ -364,8 +422,7 @@ Returns:    nothing
 void
 smtp_transport_init(transport_instance *tblock)
 {
 void
 smtp_transport_init(transport_instance *tblock)
 {
-smtp_transport_options_block *ob =
-  (smtp_transport_options_block *)(tblock->options_block);
+smtp_transport_options_block *ob = SOB tblock->options_block;
 
 /* Retry_use_local_part defaults FALSE if unset */
 
 
 /* Retry_use_local_part defaults FALSE if unset */
 
@@ -374,9 +431,11 @@ if (tblock->retry_use_local_part == TRUE_UNSET)
 
 /* Set the default port according to the protocol */
 
 
 /* Set the default port according to the protocol */
 
-if (ob->port == NULL)
-  ob->port = (strcmpic(ob->protocol, US"lmtp") == 0)? US"lmtp" :
-    (strcmpic(ob->protocol, US"smtps") == 0)? US"smtps" : US"smtp";
+if (!ob->port)
+  ob->port = strcmpic(ob->protocol, US"lmtp") == 0
+  ? US"lmtp"
+  : strcmpic(ob->protocol, US"smtps") == 0
+  ? US"smtps" : US"smtp";
 
 /* Set up the setup entry point, to be called before subprocesses for this
 transport. */
 
 /* Set up the setup entry point, to be called before subprocesses for this
 transport. */
@@ -400,15 +459,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);
 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 +478,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
   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
 
 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 +491,11 @@ Returns:       nothing
 
 static void
 set_errno(address_item *addrlist, int errno_value, uschar *msg, int rc,
 
 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;
 {
 address_item *addr;
 int orvalue = 0;
@@ -447,20 +504,43 @@ if (errno_value == ERRNO_CONNECTTIMEOUT)
   errno_value = ETIMEDOUT;
   orvalue = RTEF_CTOUT;
   }
   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 +559,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
   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
 */
 
   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 *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 */
 
 
 *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)
-  {
-  *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)
+switch(*errno_value)
   {
   {
-  *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)
   {
   }
 
 /* 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;
   *pass_message = TRUE;
   *yield = buffer[0];
   return TRUE;
@@ -577,10 +640,11 @@ assume the connection is now dead. */
 if (*errno_value == 0 || *errno_value == ECONNRESET)
   {
   *errno_value = ERRNO_SMTPCLOSED;
 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;
 }
 
 return FALSE;
 }
@@ -594,40 +658,44 @@ return FALSE;
 /* This writes to the main log and to the message log.
 
 Arguments:
 /* This writes to the main log and to the message log.
 
 Arguments:
-  addr     the address item containing error information
   host     the current host
   host     the current host
+  detail  the current message (addr_item->message)
+  basic_errno the errno (addr_item->basic_errno)
 
 Returns:   nothing
 */
 
 static void
 
 Returns:   nothing
 */
 
 static void
-write_logs(address_item *addr, host_item *host)
+write_logs(const host_item *host, const uschar *suffix, int basic_errno)
 {
 {
-if (addr->message != NULL)
+gstring * message = LOGGING(outgoing_port)
+  ? string_fmt_append(NULL, "H=%s [%s]:%d", host->name, host->address,
+                   host->port == PORT_NONE ? 25 : host->port)
+  : string_fmt_append(NULL, "H=%s [%s]", host->name, host->address);
+
+if (suffix)
   {
   {
-  uschar *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);
-  deliver_msglog("%s %s\n", tod_stamp(tod_log), message);
+  message = string_fmt_append(message, ": %s", suffix);
+  if (basic_errno > 0)
+    message = string_fmt_append(message, ": %s", strerror(basic_errno));
   }
 else
   }
 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));
-  }
+  message = string_fmt_append(message, " %s", exim_errstr(basic_errno));
+
+log_write(0, LOG_MAIN, "%s", string_from_gstring(message));
+deliver_msglog("%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                            *
 *************************************************/
 /*************************************************
 *   Post-defer action                            *
 *************************************************/
@@ -636,7 +704,6 @@ else
    It might, for example, be used to write to the database log.
 
 Arguments:
    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
 
   addr                  the address item containing error information
   host                  the current host
 
@@ -644,40 +711,309 @@ Returns:   nothing
 */
 
 static void
 */
 
 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)
 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;
 
 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
 
 router_name = transport_name = NULL;
 }
 #endif
 
+/*************************************************
+*           Reap SMTP specific responses         *
+*************************************************/
+static int
+smtp_discard_responses(smtp_context * sx, smtp_transport_options_block * ob,
+  int count)
+{
+uschar flushbuffer[4096];
+
+while (count-- > 0)
+  {
+  if (!smtp_read_response(sx, flushbuffer, sizeof(flushbuffer),
+            '2', ob->command_timeout)
+      && (errno != 0 || flushbuffer[0] == 0))
+    break;
+  }
+return count;
+}
+
+
+/* Return boolean success */
+
+static BOOL
+smtp_reap_banner(smtp_context * sx)
+{
+BOOL good_response = smtp_read_response(sx, sx->buffer, sizeof(sx->buffer),
+  '2', (SOB sx->conn_args.ob)->command_timeout);
+#ifdef EXPERIMENTAL_DSN_INFO
+sx->smtp_greeting = string_copy(sx->buffer);
+#endif
+return good_response;
+}
+
+static BOOL
+smtp_reap_ehlo(smtp_context * sx)
+{
+if (!smtp_read_response(sx, sx->buffer, sizeof(sx->buffer), '2',
+       (SOB sx->conn_args.ob)->command_timeout))
+  {
+  if (errno != 0 || sx->buffer[0] == 0 || sx->lmtp)
+    {
+#ifdef EXPERIMENTAL_DSN_INFO
+    sx->helo_response = string_copy(sx->buffer);
+#endif
+    return FALSE;
+    }
+  sx->esmtp = FALSE;
+  }
+#ifdef EXPERIMENTAL_DSN_INFO
+sx->helo_response = string_copy(sx->buffer);
+#endif
+return TRUE;
+}
+
+
+
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+static uschar *
+ehlo_cache_key(const smtp_context * sx)
+{
+host_item * host = sx->conn_args.host;
+return Ustrchr(host->address, ':')
+  ? string_sprintf("[%s]:%d.EHLO", host->address,
+    host->port == PORT_NONE ? sx->port : host->port)
+  : string_sprintf("%s:%d.EHLO", host->address,
+    host->port == PORT_NONE ? sx->port : host->port);
+}
+
+static void
+write_ehlo_cache_entry(const smtp_context * sx)
+{
+open_db dbblock, * dbm_file;
+
+if ((dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE)))
+  {
+  uschar * ehlo_resp_key = ehlo_cache_key(sx);
+  dbdata_ehlo_resp er = { .data = sx->ehlo_resp };
+
+  HDEBUG(D_transport) debug_printf("writing clr %04x/%04x cry %04x/%04x\n",
+    sx->ehlo_resp.cleartext_features, sx->ehlo_resp.cleartext_auths,
+    sx->ehlo_resp.crypted_features, sx->ehlo_resp.crypted_auths);
+
+  dbfn_write(dbm_file, ehlo_resp_key, &er, (int)sizeof(er));
+  dbfn_close(dbm_file);
+  }
+}
+
+static void
+invalidate_ehlo_cache_entry(smtp_context * sx)
+{
+open_db dbblock, * dbm_file;
+
+if (  sx->early_pipe_active
+   && (dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE)))
+  {
+  uschar * ehlo_resp_key = ehlo_cache_key(sx);
+  dbfn_delete(dbm_file, ehlo_resp_key);
+  dbfn_close(dbm_file);
+  }
+}
+
+static BOOL
+read_ehlo_cache_entry(smtp_context * sx)
+{
+open_db dbblock;
+open_db * dbm_file;
+
+if (!(dbm_file = dbfn_open(US"misc", O_RDONLY, &dbblock, FALSE)))
+  { DEBUG(D_transport) debug_printf("ehlo-cache: no misc DB\n"); }
+else
+  {
+  uschar * ehlo_resp_key = ehlo_cache_key(sx);
+  dbdata_ehlo_resp * er;
+
+  if (!(er = dbfn_read(dbm_file, ehlo_resp_key)))
+    { DEBUG(D_transport) debug_printf("no ehlo-resp record\n"); }
+  else if (time(NULL) - er->time_stamp > retry_data_expire)
+    {
+    DEBUG(D_transport) debug_printf("ehlo-resp record too old\n");
+    dbfn_close(dbm_file);
+    if ((dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE)))
+      dbfn_delete(dbm_file, ehlo_resp_key);
+    }
+  else
+    {
+    sx->ehlo_resp = er->data;
+    dbfn_close(dbm_file);
+    DEBUG(D_transport) debug_printf(
+       "EHLO response bits from cache: cleartext 0x%04x crypted 0x%04x\n",
+       er->data.cleartext_features, er->data.crypted_features);
+    return TRUE;
+    }
+  dbfn_close(dbm_file);
+  }
+return FALSE;
+}
+
+
+
+/* Return an auths bitmap for the set of AUTH methods offered by the server
+which match our authenticators. */
+
+static unsigned short
+study_ehlo_auths(smtp_context * sx)
+{
+uschar * names;
+auth_instance * au;
+uschar authnum;
+unsigned short authbits = 0;
+
+if (!sx->esmtp) return 0;
+if (!regex_AUTH) regex_AUTH = regex_must_compile(AUTHS_REGEX, FALSE, TRUE);
+if (!regex_match_and_setup(regex_AUTH, sx->buffer, 0, -1)) return 0;
+expand_nmax = -1;                                              /* reset */
+names = string_copyn(expand_nstring[1], expand_nlength[1]);
+
+for (au = auths, authnum = 0; au; au = au->next, authnum++) if (au->client)
+  {
+  const uschar * list = names;
+  int sep = ' ';
+  uschar name[32];
+
+  while (string_nextinlist(&list, &sep, name, sizeof(name)))
+    if (strcmpic(au->public_name, name) == 0)
+      { authbits |= BIT(authnum); break; }
+  }
+
+DEBUG(D_transport)
+  debug_printf("server offers %s AUTH, methods '%s', bitmap 0x%04x\n",
+    tls_out.active.sock >= 0 ? "crypted" : "plaintext", names, authbits);
+
+if (tls_out.active.sock >= 0)
+  sx->ehlo_resp.crypted_auths = authbits;
+else
+  sx->ehlo_resp.cleartext_auths = authbits;
+return authbits;
+}
+
+
+
+
+/* Wait for and check responses for early-pipelining.
+
+Called from the lower-level smtp_read_response() function
+used for general code that assume synchronisation, if context
+flags indicate outstanding early-pipelining commands.  Also
+called fom sync_responses() which handles pipelined commands.
+
+Arguments:
+ sx    smtp connection context
+ countp        number of outstanding responses, adjusted on return
+
+Return:
+ OK    all well
+ FAIL  SMTP error in response
+*/
+int
+smtp_reap_early_pipe(smtp_context * sx, int * countp)
+{
+BOOL pending_BANNER = sx->pending_BANNER;
+BOOL pending_EHLO = sx->pending_EHLO;
+
+sx->pending_BANNER = FALSE;    /* clear early to avoid recursion */
+sx->pending_EHLO = FALSE;
+
+if (pending_BANNER)
+  {
+  DEBUG(D_transport) debug_printf("%s expect banner\n", __FUNCTION__);
+  (*countp)--;
+  if (!smtp_reap_banner(sx))
+    {
+    DEBUG(D_transport) debug_printf("bad banner\n");
+    goto fail;
+    }
+  }
+
+if (pending_EHLO)
+  {
+  unsigned peer_offered;
+  unsigned short authbits = 0, * ap;
+
+  DEBUG(D_transport) debug_printf("%s expect ehlo\n", __FUNCTION__);
+  (*countp)--;
+  if (!smtp_reap_ehlo(sx))
+    {
+    DEBUG(D_transport) debug_printf("bad response for EHLO\n");
+    goto fail;
+    }
+
+  /* Compare the actual EHLO response to the cached value we assumed;
+  on difference, dump or rewrite the cache and arrange for a retry. */
+
+  ap = tls_out.active.sock < 0
+      ? &sx->ehlo_resp.cleartext_auths : &sx->ehlo_resp.crypted_auths;
+
+  peer_offered = ehlo_response(sx->buffer,
+         (tls_out.active.sock < 0 ?  OPTION_TLS : OPTION_REQUIRETLS)
+       | OPTION_CHUNKING | OPTION_PRDR | OPTION_DSN | OPTION_PIPE | OPTION_SIZE
+       | OPTION_UTF8 | OPTION_EARLY_PIPE
+       );
+  if (  peer_offered != sx->peer_offered
+     || (authbits = study_ehlo_auths(sx)) != *ap)
+    {
+    HDEBUG(D_transport)
+      debug_printf("EHLO %s extensions changed, 0x%04x/0x%04x -> 0x%04x/0x%04x\n",
+                   tls_out.active.sock < 0 ? "cleartext" : "crypted",
+                   sx->peer_offered, *ap, peer_offered, authbits);
+    *(tls_out.active.sock < 0
+      ? &sx->ehlo_resp.cleartext_features : &sx->ehlo_resp.crypted_features) = peer_offered;
+    *ap = authbits;
+    if (peer_offered & OPTION_EARLY_PIPE)
+      write_ehlo_cache_entry(sx);
+    else
+      invalidate_ehlo_cache_entry(sx);
+
+    return OK;         /* just carry on */
+    }
+  }
+return OK;
+
+fail:
+  invalidate_ehlo_cache_entry(sx);
+  (void) smtp_discard_responses(sx, sx->conn_args.ob, *countp);
+  return FAIL;
+}
+#endif
 
 
 /*************************************************
 
 
 /*************************************************
@@ -706,21 +1042,11 @@ subsequent general error, it will get reset accordingly. If not, it will get
 converted to OK at the end.
 
 Arguments:
 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
   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
   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
 
 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
@@ -729,46 +1055,53 @@ Returns:      3 if at least one address had 2xx and one had 5xx
              -1 timeout while reading RCPT response
              -2 I/O or other non-response error for RCPT
              -3 DATA or MAIL failed - errno and buffer set
              -1 timeout while reading RCPT response
              -2 I/O or other non-response error for RCPT
              -3 DATA or MAIL failed - errno and buffer set
+            -4 banner or EHLO failed (early-pipelining)
 */
 
 static int
 */
 
 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 = sx->conn_args.ob;
 int yield = 0;
 
 int yield = 0;
 
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+if (smtp_reap_early_pipe(sx, &count) != OK)
+  return -4;
+#endif
+
 /* 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. */
 
 /* 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)
   {
   {
+  DEBUG(D_transport) debug_printf("%s expect mail\n", __FUNCTION__);
   count--;
   count--;
-  if (!smtp_read_response(inblock, buffer, buffsize, '2', timeout))
+  if (!smtp_read_response(sx, 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! */
     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;
       int save_errno = 0;
-      if (buffer[0] == '4')
+      if (sx->buffer[0] == '4')
         {
         save_errno = ERRNO_MAIL4XX;
         {
         save_errno = ERRNO_MAIL4XX;
-        addr->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
-        }
-      while (count-- > 0)
-        {
-        if (!smtp_read_response(inblock, flushbuffer, sizeof(flushbuffer),
-                   '2', timeout)
-            && (errno != 0 || flushbuffer[0] == 0))
-          break;
+        addr->more_errno |= ((sx->buffer[1] - '0')*10 + sx->buffer[2] - '0') << 8;
         }
         }
+      count = smtp_discard_responses(sx, ob, count);
       errno = save_errno;
       }
       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->conn_args.host;
+      addr = addr->next;
+      }
     return -3;
     }
   }
     return -3;
     }
   }
@@ -781,11 +1114,16 @@ with an address by scanning for the next address whose status is PENDING_DEFER.
 
 while (count-- > 0)
   {
 
 while (count-- > 0)
   {
-  while (addr->transport_return != PENDING_DEFER) addr = addr->next;
+  while (addr->transport_return != PENDING_DEFER)
+    if (!(addr = addr->next))
+      return -2;
 
   /* The address was accepted */
 
   /* The address was accepted */
+  addr->host_used = sx->conn_args.host;
 
 
-  if (smtp_read_response(inblock, buffer, buffsize, '2', timeout))
+  DEBUG(D_transport) debug_printf("%s expect rcpt\n", __FUNCTION__);
+  if (smtp_read_response(sx, sx->buffer, sizeof(sx->buffer),
+                         '2', ob->command_timeout))
     {
     yield |= 1;
     addr->transport_return = PENDING_OK;
     {
     yield |= 1;
     addr->transport_return = PENDING_OK;
@@ -807,11 +1145,9 @@ while (count-- > 0)
 
   else if (errno == ETIMEDOUT)
     {
 
   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->conn_args.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;
     retry_add_item(addr, addr->address_retry_key, 0);
     update_waiting = FALSE;
     return -1;
@@ -822,10 +1158,10 @@ while (count-- > 0)
   big_buffer for which we are checking the response, so the error message
   makes sense. */
 
   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>",
     {
     string_format(big_buffer, big_buffer_size, "RCPT TO:<%s>",
-      transport_rcpt_address(addr, include_affixes));
+      transport_rcpt_address(addr, sx->conn_args.tblock->rcpt_include_affixes));
     return -2;
     }
 
     return -2;
     }
 
@@ -835,14 +1171,15 @@ while (count-- > 0)
     {
     addr->message =
       string_sprintf("SMTP error from remote mail server after RCPT TO:<%s>: "
     {
     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->conn_args.tblock->rcpt_include_affixes),
+       string_printing(sx->buffer));
     setflag(addr, af_pass_message);
     setflag(addr, af_pass_message);
-    deliver_msglog("%s %s\n", tod_stamp(tod_log), addr->message);
+    if (!sx->verify)
+      msglog_line(sx->conn_args.host, addr->message);
 
     /* The response was 5xx */
 
 
     /* The response was 5xx */
 
-    if (buffer[0] == '5')
+    if (sx->buffer[0] == '5')
       {
       addr->transport_return = FAIL;
       yield |= 2;
       {
       addr->transport_return = FAIL;
       yield |= 2;
@@ -854,28 +1191,47 @@ while (count-- > 0)
       {
       addr->transport_return = DEFER;
       addr->basic_errno = ERRNO_RCPT4XX;
       {
       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. */
+
+       if (sx->conn_args.host->next)
+         if (LOGGING(outgoing_port))
+           log_write(0, LOG_MAIN, "H=%s [%s]:%d %s", sx->conn_args.host->name,
+             sx->conn_args.host->address,
+             sx->port == PORT_NONE ? 25 : sx->port, addr->message);
+         else
+           log_write(0, LOG_MAIN, "H=%s [%s]: %s", sx->conn_args.host->name,
+             sx->conn_args.host->address, addr->message);
+
+#ifndef DISABLE_EVENT
+       else
+         msg_event_raise(US"msg:rcpt:defer", addr);
+#endif
 
 
-      /* Do not put this message on the list of those waiting for specific
-      hosts, as otherwise it is likely to be tried too often. */
+       /* Do not put this message on the list of those waiting for specific
+       hosts, as otherwise it is likely to be tried too often. */
 
 
-      update_waiting = FALSE;
+       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. */
+       /* 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. */
 
 
-      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);
+       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 */
       }
     }
   }       /* Loop for next RCPT response */
@@ -883,29 +1239,33 @@ while (count-- > 0)
 /* Update where to start at for the next block of responses, unless we
 have already handled all the addresses. */
 
 /* 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. */
 
 
 /* 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))
+if (pending_DATA != 0)
   {
   {
-  int code;
-  uschar *msg;
-  BOOL pass_message;
-  if (pending_DATA > 0 || (yield & 1) != 0)
+  DEBUG(D_transport) debug_printf("%s expect data\n", __FUNCTION__);
+  if (!smtp_read_response(sx, sx->buffer, sizeof(sx->buffer),
+                       '3', ob->command_timeout))
     {
     {
-    if (errno == 0 && buffer[0] == '4')
+    int code;
+    uschar *msg;
+    BOOL pass_message;
+    if (pending_DATA > 0 || (yield & 1) != 0)
       {
       {
-      errno = ERRNO_DATA4XX;
-      addrlist->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
+      if (errno == 0 && sx->buffer[0] == '4')
+       {
+       errno = ERRNO_DATA4XX;
+       sx->first_addr->more_errno |= ((sx->buffer[1] - '0')*10 + sx->buffer[2] - '0') << 8;
+       }
+      return -3;
       }
       }
-    return -3;
+    (void)check_response(sx->conn_args.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);
     }
     }
-  (void)check_response(host, &errno, 0, 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);
   }
 
 /* All responses read and handled; MAIL (if present) received 2xx and DATA (if
   }
 
 /* All responses read and handled; MAIL (if present) received 2xx and DATA (if
@@ -917,170 +1277,242 @@ return yield;
 
 
 
 
 
 
-/* Do the client side of smtp-level authentication */
-/*
-Arguments:
-  buffer       EHLO response from server (gets overwritten)
-  addrlist      chain of potential addresses to deliver
-  host          host to deliver to
-  ob           transport options
-  ibp, obp     comms channel control blocks
 
 
-Returns:
-  OK                   Success, or failed (but not required): global "smtp_authenticated" set
-  DEFER                        Failed authentication (and was required)
-  ERROR                        Internal problem
 
 
-  FAIL_SEND            Failed communications - transmit
-  FAIL                 - response
-*/
+/* Try an authenticator's client entry */
 
 
-int
-smtp_auth(uschar *buffer, unsigned bufsize, address_item *addrlist, host_item *host,
-    smtp_transport_options_block *ob, BOOL is_esmtp,
-    smtp_inblock *ibp, smtp_outblock *obp)
+static int
+try_authenticator(smtp_context * sx, auth_instance * au)
 {
 {
-  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_transport_options_block * ob = sx->conn_args.ob;  /* transport options */
+host_item * host = sx->conn_args.host;                 /* host to deliver to */
+int rc;
 
 
-  if (is_esmtp && !regex_AUTH) regex_AUTH =
-      regex_must_compile(US"\\n250[\\s\\-]AUTH\\s+([\\-\\w\\s]+)(?:\\n|$)",
-            FALSE, TRUE);
+sx->outblock.authenticating = TRUE;
+rc = (au->info->clientcode)(au, sx, ob->command_timeout,
+                           sx->buffer, sizeof(sx->buffer));
+sx->outblock.authenticating = FALSE;
+DEBUG(D_transport) debug_printf("%s authenticator yielded %d\n", au->name, rc);
 
 
-  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 */
+/* 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. */
 
 
-    /* Must not do this check until after we have saved the result of the
-    regex match above. */
+switch(rc)
+  {
+  case OK:
+    f.smtp_authenticated = TRUE;   /* stops the outer loop */
+    client_authenticator = au->name;
+    if (au->set_client_id)
+      client_authenticated_id = expand_string(au->set_client_id);
+    break;
 
 
-    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";
+  /* Failure after writing a command */
 
 
-      DEBUG(D_transport) debug_printf("scanning authentication mechanisms\n");
+  case FAIL_SEND:
+    return FAIL_SEND;
 
 
-      /* 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.
-      */
+  /* Failure after reading a response */
 
 
-      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;
-          }
+  case FAIL:
+    if (errno != 0 || sx->buffer[0] != '5') return FAIL;
+    log_write(0, LOG_MAIN, "%s authenticator failed H=%s [%s] %s",
+      au->name, host->name, host->address, sx->buffer);
+    break;
 
 
-        /* Loop to scan supported server mechanisms */
+  /* 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 (*sx->buffer != 0)
+      log_write(0, LOG_MAIN, "%s authenticator cancelled "
+       "authentication H=%s [%s] %s", au->name, host->name,
+       host->address, sx->buffer);
+    break;
 
 
-        while (*p != 0)
-          {
-          int rc;
-          int len = Ustrlen(au->public_name);
-          while (isspace(*p)) p++;
+  /* Internal problem, message in buffer. */
 
 
-          if (strncmpic(au->public_name, p, len) != 0 ||
-              (p[len] != 0 && !isspace(p[len])))
-            {
-            while (*p != 0 && !isspace(*p)) p++;
-            continue;
-            }
+  case ERROR:
+    set_errno_nohost(sx->addrlist, ERRNO_AUTHPROB, string_copy(sx->buffer),
+             DEFER, FALSE);
+    return ERROR;
+  }
+return OK;
+}
 
 
-          /* 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);
 
 
-          /* 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;
-            }
+/* Do the client side of smtp-level authentication.
 
 
-          break;  /* If not authenticated, try next authenticator */
-          }       /* Loop for scanning supported server mechanisms */
-        }         /* Loop for further authenticators */
-      }
-    }
+Arguments:
+  sx           smtp connection context
 
 
-  /* If we haven't authenticated, but are required to, give up. */
+sx->buffer should have the EHLO response from server (gets overwritten)
 
 
-  if (require_auth == OK && !smtp_authenticated)
-    {
-    set_errno(addrlist, ERRNO_AUTHFAIL,
-      string_sprintf("authentication required but %s", fail_reason), DEFER,
-      FALSE);
-    return DEFER;
-    }
+Returns:
+  OK                   Success, or failed (but not required): global "smtp_authenticated" set
+  DEFER                        Failed authentication (and was required)
+  ERROR                        Internal problem
 
 
-  return OK;
+  FAIL_SEND            Failed communications - transmit
+  FAIL                 - response
+*/
+
+static int
+smtp_auth(smtp_context * sx)
+{
+host_item * host = sx->conn_args.host;                 /* host to deliver to */
+smtp_transport_options_block * ob = sx->conn_args.ob;  /* transport options */
+int require_auth = verify_check_given_host(CUSS &ob->hosts_require_auth, host);
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+unsigned short authbits = tls_out.active.sock >= 0
+      ? sx->ehlo_resp.crypted_auths : sx->ehlo_resp.cleartext_auths;
+#endif
+uschar * fail_reason = US"server did not advertise AUTH support";
+
+f.smtp_authenticated = FALSE;
+client_authenticator = client_authenticated_id = client_authenticated_sender = NULL;
+
+if (!regex_AUTH)
+  regex_AUTH = regex_must_compile(AUTHS_REGEX, FALSE, TRUE);
+
+/* Is the server offering AUTH? */
+
+if (  sx->esmtp
+   &&
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+      sx->early_pipe_active ? authbits
+      :
+#endif
+       regex_match_and_setup(regex_AUTH, sx->buffer, 0, -1)
+   )
+  {
+  uschar * names = NULL;
+  expand_nmax = -1;                          /* reset */
+
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+  if (!sx->early_pipe_active)
+#endif
+    names = string_copyn(expand_nstring[1], expand_nlength[1]);
+
+  /* Must not do this check until after we have saved the result of the
+  regex match above as the check could be another RE. */
+
+  if (  require_auth == OK
+     || verify_check_given_host(CUSS &ob->hosts_try_auth, host) == OK)
+    {
+    auth_instance * au;
+
+    DEBUG(D_transport) debug_printf("scanning authentication mechanisms\n");
+    fail_reason = US"no common mechanisms were found";
+
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+    if (sx->early_pipe_active)
+      {
+      /* Scan our authenticators (which support use by a client and were offered
+      by the server (checked at cache-write time)), not suppressed by
+      client_condition.  If one is found, attempt to authenticate by calling its
+      client function.  We are limited to supporting up to 16 authenticator
+      public-names by the number of bits in a short. */
+
+      uschar bitnum;
+      int rc;
+
+      for (bitnum = 0, au = auths;
+          !f.smtp_authenticated && au && bitnum < 16;
+          bitnum++, au = au->next) if (authbits & BIT(bitnum))
+       {
+       if (  au->client_condition
+          && !expand_check_condition(au->client_condition, au->name,
+                   US"client authenticator"))
+         {
+         DEBUG(D_transport) debug_printf("skipping %s authenticator: %s\n",
+           au->name, "client_condition is false");
+         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. */
+
+       fail_reason = US"authentication attempt(s) failed";
+
+       if ((rc = try_authenticator(sx, au)) != OK)
+         return rc;
+       }
+      }
+    else
+#endif
+
+    /* 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; !f.smtp_authenticated && au; au = au->next)
+      {
+      uschar *p = names;
+
+      if (  !au->client
+         || (   au->client_condition
+           &&  !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 */
+
+      while (*p)
+       {
+       int len = Ustrlen(au->public_name);
+       int rc;
+
+       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;
+         }
+
+       /* 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";
+
+       if ((rc = try_authenticator(sx, au)) != OK)
+         return rc;
+
+       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 (require_auth == OK && !f.smtp_authenticated)
+  {
+  set_errno_nohost(sx->addrlist, ERRNO_AUTHFAIL,
+    string_sprintf("authentication required but %s", fail_reason), DEFER,
+    FALSE);
+  return DEFER;
+  }
+
+return OK;
 }
 
 
 }
 
 
@@ -1091,7 +1523,7 @@ Arguments
   addrlist      chain of potential addresses to deliver
   ob           transport options
 
   addrlist      chain of potential addresses to deliver
   ob           transport options
 
-Globals                smtp_authenticated
+Globals                f.smtp_authenticated
                client_authenticated_sender
 Return True on error, otherwise buffer has (possibly empty) terminated string
 */
                client_authenticated_sender
 Return True on error, otherwise buffer has (possibly empty) terminated string
 */
@@ -1103,7 +1535,7 @@ smtp_mail_auth_str(uschar *buffer, unsigned bufsize, address_item *addrlist,
 uschar *local_authenticated_sender = authenticated_sender;
 
 #ifdef notdef
 uschar *local_authenticated_sender = authenticated_sender;
 
 #ifdef notdef
-  debug_printf("smtp_mail_auth_str: as<%s> os<%s> SA<%s>\n", authenticated_sender, ob->authenticated_sender, smtp_authenticated?"Y":"N");
+  debug_printf("smtp_mail_auth_str: as<%s> os<%s> SA<%s>\n", authenticated_sender, ob->authenticated_sender, f.smtp_authenticated?"Y":"N");
 #endif
 
 if (ob->authenticated_sender != NULL)
 #endif
 
 if (ob->authenticated_sender != NULL)
@@ -1111,11 +1543,11 @@ if (ob->authenticated_sender != NULL)
   uschar *new = expand_string(ob->authenticated_sender);
   if (new == NULL)
     {
   uschar *new = expand_string(ob->authenticated_sender);
   if (new == NULL)
     {
-    if (!expand_string_forcedfail)
+    if (!f.expand_string_forcedfail)
       {
       uschar *message = string_sprintf("failed to expand "
         "authenticated_sender: %s", expand_string_message);
       {
       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;
       }
     }
       return TRUE;
       }
     }
@@ -1124,7 +1556,7 @@ if (ob->authenticated_sender != NULL)
 
 /* Add the authenticated sender address if present */
 
 
 /* Add the authenticated sender address if present */
 
-if ((smtp_authenticated || ob->authenticated_sender_force) &&
+if ((f.smtp_authenticated || ob->authenticated_sender_force) &&
     local_authenticated_sender != NULL)
   {
   string_format(buffer, bufsize, " AUTH=%s",
     local_authenticated_sender != NULL)
   {
   string_format(buffer, bufsize, " AUTH=%s",
@@ -1140,116 +1572,421 @@ return FALSE;
 
 
 
 
 
 
-/*************************************************
-*       Deliver address list to given host       *
-*************************************************/
+#ifdef SUPPORT_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;
+int rc;
+BOOL sec;
 
 
-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.
+rc = dns_lookup(dnsa, buffer, T_TLSA, &fullname);
+sec = dns_is_secure(dnsa);
+DEBUG(D_transport)
+  debug_printf("TLSA lookup ret %d %sDNSSEC\n", rc, sec ? "" : "not ");
+
+switch (rc)
+  {
+  case DNS_AGAIN:
+    return DEFER; /* just defer this TLS'd conn */
+
+  case DNS_SUCCEED:
+    if (sec)
+      {
+      DEBUG(D_transport)
+       {
+       dns_scan dnss;
+       dns_record * rr;
+       for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
+            rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
+         if (rr->type == T_TLSA && rr->size > 3)
+           {
+           uint16_t payload_length = rr->size - 3;
+           uschar s[MAX_TLSA_EXPANDED_SIZE], * sp = s, * p = US rr->data;
+
+           sp += sprintf(CS sp, "%d ", *p++); /* usage */
+           sp += sprintf(CS sp, "%d ", *p++); /* selector */
+           sp += sprintf(CS sp, "%d ", *p++); /* matchtype */
+           while (payload_length-- > 0 && sp-s < (MAX_TLSA_EXPANDED_SIZE - 4))
+             sp += sprintf(CS sp, "%02x", *p++);
+
+           debug_printf(" %s\n", s);
+           }
+       }
+      return OK;
+      }
+    log_write(0, LOG_MAIN,
+      "DANE error: TLSA lookup for %s not DNSSEC", host->name);
+    /*FALLTRHOUGH*/
+
+  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;
+
+  default:
+  case DNS_FAIL:
+    return dane_required ? FAIL : DEFER;
+  }
+}
+#endif
+
+
+
+typedef struct smtp_compare_s
+{
+    uschar                          *current_sender_address;
+    struct transport_instance       *tblock;
+} smtp_compare_t;
+
+
+/* Create a unique string that identifies this message, it is based on
+sender_address, helo_data and tls_certificate if enabled.
+*/
+
+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 = SOB tblock->options_block;
+
+sender_address = sender;
+
+addr1 = deliver_make_addr (sender, TRUE);
+deliver_set_expansions(addr1);
+
+if (ob->interface)
+  if1 = expand_string(ob->interface);
+
+if (ob->helo_data)
+  helo1 = expand_string(ob->helo_data);
+
+#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
+
+deliver_set_expansions(NULL);
+sender_address = save_sender_address;
+
+return local_identity;
+}
+
+
+
+/* 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.  */
+
+static BOOL
+smtp_are_same_identities(uschar * message_id, smtp_compare_t * s_compare)
+{
+uschar * message_local_identity,
+       * current_local_identity,
+       * new_sender_address;
+
+current_local_identity =
+  smtp_local_identity(s_compare->current_sender_address, s_compare->tblock);
+
+if (!(new_sender_address = deliver_get_sender_address(message_id)))
+    return 0;
+
+message_local_identity =
+  smtp_local_identity(new_sender_address, s_compare->tblock);
+
+return Ustrcmp(current_local_identity, message_local_identity) == 0;
+}
+
+
+
+static unsigned
+ehlo_response(uschar * buf, unsigned checks)
+{
+size_t bsize = Ustrlen(buf);
+
+/* debug_printf("%s: check for 0x%04x\n", __FUNCTION__, checks); */
+
+#ifdef SUPPORT_TLS
+# ifdef EXPERIMENTAL_REQUIRETLS
+if (  checks & OPTION_REQUIRETLS
+   && pcre_exec(regex_REQUIRETLS, NULL, CS buf,bsize, 0, PCRE_EOPT, NULL,0) < 0)
+# endif
+  checks &= ~OPTION_REQUIRETLS;
+
+if (  checks & OPTION_TLS
+   && pcre_exec(regex_STARTTLS, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
+#endif
+  checks &= ~OPTION_TLS;
+
+if (  checks & OPTION_IGNQ
+   && pcre_exec(regex_IGNOREQUOTA, NULL, CS buf, bsize, 0,
+               PCRE_EOPT, NULL, 0) < 0)
+  checks &= ~OPTION_IGNQ;
+
+if (  checks & OPTION_CHUNKING
+   && pcre_exec(regex_CHUNKING, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
+  checks &= ~OPTION_CHUNKING;
+
+#ifndef DISABLE_PRDR
+if (  checks & OPTION_PRDR
+   && pcre_exec(regex_PRDR, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
+#endif
+  checks &= ~OPTION_PRDR;
 
 
+#ifdef SUPPORT_I18N
+if (  checks & OPTION_UTF8
+   && pcre_exec(regex_UTF8, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
+#endif
+  checks &= ~OPTION_UTF8;
+
+if (  checks & OPTION_DSN
+   && pcre_exec(regex_DSN, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
+  checks &= ~OPTION_DSN;
+
+if (  checks & OPTION_PIPE
+   && pcre_exec(regex_PIPELINING, NULL, CS buf, bsize, 0,
+               PCRE_EOPT, NULL, 0) < 0)
+  checks &= ~OPTION_PIPE;
+
+if (  checks & OPTION_SIZE
+   && pcre_exec(regex_SIZE, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
+  checks &= ~OPTION_SIZE;
+
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+if (  checks & OPTION_EARLY_PIPE
+   && pcre_exec(regex_EARLY_PIPE, NULL, CS buf, bsize, 0,
+               PCRE_EOPT, NULL, 0) < 0)
+#endif
+  checks &= ~OPTION_EARLY_PIPE;
+
+/* debug_printf("%s: found     0x%04x\n", __FUNCTION__, checks); */
+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, and always reap
+the response from a previous BDAT command.
+
+Args:
+ tctx          transport context
+ chunk_size    value for SMTP BDAT command
+ flags
+   tc_chunk_last       add LAST option to SMTP BDAT command
+   tc_reap_prev                reap response to previous SMTP commands
+
+Returns:       OK or ERROR
+*/
+
+static int
+smtp_chunk_cmd_callback(transport_ctx * tctx, unsigned chunk_size,
+  unsigned flags)
+{
+smtp_transport_options_block * ob = SOB tctx->tblock->options_block;
+smtp_context * sx = tctx->smtp_context;
+int cmd_count = 0;
+int prev_cmd_count;
+
+/* Write SMTP chunk header command.  If not reaping responses, note that
+there may be more writes (like, the chunk data) done soon. */
+
+if (chunk_size > 0)
+  {
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+  BOOL new_conn = !!(sx->outblock.conn_args);
+#endif
+  if((cmd_count = smtp_write_command(sx,
+             flags & tc_reap_prev ? SCMD_FLUSH : SCMD_MORE,
+             "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 */
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+  /* That command write could have been the one that made the connection.
+  Copy the fd from the client conn ctx (smtp transport specific) to the
+  generic transport ctx. */
+
+  if (new_conn)
+    tctx->u.fd = sx->outblock.cctx->sock;
+#endif
+  }
+
+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 */
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+    case -4:                           /* non-2xx for pipelined banner or EHLO */
+#endif
+    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, 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:
 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.
+  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;
   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;
-                          the result for each address is in its data block.
+                          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.
                   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
+                  ERROR - helo_data or add_headers or authenticated_sender is
+                         specified for this transport, and the string failed
+                         to expand
 */
 */
-
-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)
+int
+smtp_setup_conn(smtp_context * sx, BOOL suppress_tls)
 {
 {
-address_item *addr;
-address_item *sync_addr;
-address_item *first_addr = addrlist;
+#if defined(SUPPORT_TLS) && defined(SUPPORT_DANE)
+dns_answer tlsa_dnsa;
+#endif
+smtp_transport_options_block * ob = sx->conn_args.tblock->options_block;
+BOOL pass_message = FALSE;
+uschar * message = NULL;
 int yield = OK;
 int yield = OK;
-int address_count;
-int save_errno;
 int rc;
 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;
+
+sx->conn_args.ob = ob;
+
+sx->lmtp = strcmpic(ob->protocol, US"lmtp") == 0;
+sx->smtps = strcmpic(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
 #endif
-#ifdef EXPERIMENTAL_DSN
-BOOL dsn_all_lasthop = TRUE;
+sx->dsn_all_lasthop = TRUE;
+#if defined(SUPPORT_TLS) && defined(SUPPORT_DANE)
+sx->dane = FALSE;
+sx->dane_required =
+  verify_check_given_host(CUSS &ob->hosts_require_dane, sx->conn_args.host) == OK;
+#endif
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+sx->early_pipe_active = sx->early_pipe_ok = FALSE;
+sx->ehlo_resp.cleartext_features = sx->ehlo_resp.crypted_features = 0;
+sx->pending_BANNER = sx->pending_EHLO = FALSE;
 #endif
 #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 */
+if ((sx->max_rcpt = sx->conn_args.tblock->max_addresses) == 0) sx->max_rcpt = 999999;
+sx->peer_offered = 0;
+sx->avoid_option = 0;
+sx->igquotstr = US"";
+if (!sx->helo_data) sx->helo_data = ob->helo_data;
+#ifdef EXPERIMENTAL_DSN_INFO
+sx->smtp_greeting = NULL;
+sx->helo_response = NULL;
+#endif
 
 
-*message_defer = FALSE;
 smtp_command = US"initial connection";
 smtp_command = US"initial connection";
-if (max_rcpt == 0) max_rcpt = 999999;
+sx->buffer[0] = '\0';
 
 /* Set up the buffer for reading SMTP response packets. */
 
 
 /* Set up the buffer for reading SMTP response packets. */
 
-inblock.buffer = inbuffer;
-inblock.buffersize = sizeof(inbuffer);
-inblock.ptr = inbuffer;
-inblock.ptrend = inbuffer;
+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 */
 
 
 /* Set up the buffer for holding SMTP commands while pipelining */
 
-outblock.buffer = outbuffer;
-outblock.buffersize = sizeof(outbuffer);
-outblock.ptr = outbuffer;
-outblock.cmd_count = 0;
-outblock.authenticating = FALSE;
+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;
+sx->outblock.conn_args = NULL;
 
 /* Reset the parameters of a TLS session. */
 
 
 /* Reset the parameters of a TLS session. */
 
@@ -1265,14 +2002,16 @@ 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
 
 /* 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. */
+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
 
 tls_modify_variables(&tls_out);
 
 #ifndef SUPPORT_TLS
-if (smtps)
+if (sx->smtps)
   {
   {
-  set_errno(addrlist, 0, US"TLS support not available", DEFER, FALSE);
+  set_errno_nohost(sx->addrlist, ERRNO_TLSFAILURE, US"TLS support not available",
+           DEFER, FALSE);
   return ERROR;
   }
 #endif
   return ERROR;
   }
 #endif
@@ -1281,49 +2020,183 @@ if (smtps)
 the initial interaction and HELO/EHLO/LHLO. Connect timeout errors are handled
 specially so they can be identified for retries. */
 
 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 (!continue_hostname)
   {
   {
-  inblock.sock = outblock.sock =
-    smtp_connect(host, host_af, port, interface, ob->connect_timeout,
-      ob->keepalive, ob->dscp);   /* This puts port into host->port */
+  if (sx->verify)
+    HDEBUG(D_verify) debug_printf("interface=%s port=%d\n", sx->conn_args.interface, sx->port);
 
 
-  if (inblock.sock < 0)
+  /* Get the actual port the connection will use, into sx->conn_args.host */
+
+  smtp_port_for_connect(sx->conn_args.host, sx->port);
+
+#if defined(SUPPORT_TLS) && defined(SUPPORT_DANE)
+    /* Do TLSA lookup for DANE */
+    {
+    tls_out.dane_verified = FALSE;
+    tls_out.tlsa_usage = 0;
+
+    if (sx->conn_args.host->dnssec == DS_YES)
+      {
+      if(  sx->dane_required
+       || verify_check_given_host(CUSS &ob->hosts_try_dane, sx->conn_args.host) == OK
+       )
+       switch (rc = tlsa_lookup(sx->conn_args.host, &tlsa_dnsa, sx->dane_required))
+         {
+         case OK:              sx->dane = TRUE;
+                               ob->tls_tempfail_tryclear = FALSE;
+                               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);
+# ifndef DISABLE_EVENT
+                               (void) event_raise(sx->conn_args.tblock->event_action,
+                                 US"dane:fail", sx->dane_required
+                                   ?  US"dane-required" : US"dnssec-invalid");
+# endif
+                               return rc;
+         }
+      }
+    else if (sx->dane_required)
+      {
+      set_errno_nohost(sx->addrlist, ERRNO_DNSDEFER,
+       string_sprintf("DANE error: %s lookup not DNSSEC", sx->conn_args.host->name),
+       FAIL, FALSE);
+# ifndef DISABLE_EVENT
+      (void) event_raise(sx->conn_args.tblock->event_action,
+       US"dane:fail", US"dane-required");
+# endif
+      return FAIL;
+      }
+    }
+#endif /*DANE*/
+
+  /* Make the TCP connection */
+
+  sx->cctx.tls_ctx = NULL;
+  sx->inblock.cctx = sx->outblock.cctx = &sx->cctx;
+  sx->avoid_option = sx->peer_offered = smtp_peer_options = 0;
+
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+  if (verify_check_given_host(CUSS &ob->hosts_pipe_connect, sx->conn_args.host) == OK)
     {
     {
-    set_errno(addrlist, (errno == ETIMEDOUT)? ERRNO_CONNECTTIMEOUT : errno,
-      NULL, DEFER, FALSE);
-    return DEFER;
+    sx->early_pipe_ok = TRUE;
+    if (  read_ehlo_cache_entry(sx)
+       && sx->ehlo_resp.cleartext_features & OPTION_EARLY_PIPE)
+      {
+      DEBUG(D_transport) debug_printf("Using cached cleartext PIPE_CONNECT\n");
+      sx->early_pipe_active = TRUE;
+      sx->peer_offered = sx->ehlo_resp.cleartext_features;
+      }
     }
 
     }
 
+  if (sx->early_pipe_active)
+    sx->outblock.conn_args = &sx->conn_args;
+  else
+#endif
+    {
+    if ((sx->cctx.sock = smtp_connect(&sx->conn_args, NULL)) < 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;
+      }
+    }
   /* 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. */
   /* 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);
+/*XXX early-pipe: they still will not be. Is there any way to find out what they
+will be?  Somehow I doubt it. */
+
+  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. */
 
 
   /* 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 (!smtps)
+  if (!sx->smtps)
     {
     {
-    if (!smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
-      ob->command_timeout)) goto RESPONSE_FAILED;
-
-    /* Now check if the helo_data expansion went well, and sign off cleanly if
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+    if (sx->early_pipe_active)
+      {
+      sx->pending_BANNER = TRUE;       /* sync_responses() must eventually handle */
+      sx->outblock.cmd_count = 1;
+      }
+    else
+#endif
+      {
+#ifdef TCP_QUICKACK
+      (void) setsockopt(sx->cctx.sock, IPPROTO_TCP, TCP_QUICKACK, US &off,
+                       sizeof(off));
+#endif
+      if (!smtp_reap_banner(sx))
+       goto RESPONSE_FAILED;
+      }
+
+#ifndef DISABLE_EVENT
+      {
+      uschar * s;
+      lookup_dnssec_authenticated = sx->conn_args.host->dnssec==DS_YES ? US"yes"
+       : sx->conn_args.host->dnssec==DS_NO ? US"no" : NULL;
+      s = event_raise(sx->conn_args.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. */
 
     it didn't. */
 
-    if (helo_data == NULL)
+    if (!sx->helo_data)
       {
       {
-      uschar *message = string_sprintf("failed to expand helo_data: %s",
+      message = string_sprintf("failed to expand helo_data: %s",
         expand_string_message);
         expand_string_message);
-      set_errno(addrlist, 0, message, DEFER, FALSE);
+      set_errno_nohost(sx->addrlist, ERRNO_EXPANDFAIL, message, DEFER, FALSE);
       yield = DEFER;
       goto SEND_QUIT;
       }
     }
 
 /** Debugging without sending a message
       yield = DEFER;
       goto SEND_QUIT;
       }
     }
 
 /** Debugging without sending a message
-addrlist->transport_return = DEFER;
+sx->addrlist->transport_return = DEFER;
 goto SEND_QUIT;
 **/
 
 goto SEND_QUIT;
 **/
 
@@ -1357,88 +2230,189 @@ 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. */
 
   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(CUSS &ob->hosts_avoid_esmtp, sx->conn_args.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. */
 
   /* 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 |= OPTION_TLS;
     suppress_tls = FALSE;
     ob->tls_tempfail_tryclear = FALSE;
     smtp_command = US"SSL-on-connect";
     goto TLS_NEGOTIATE;
     }
     suppress_tls = FALSE;
     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,
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+         sx->early_pipe_active ? SCMD_BUFFER :
+#endif
+           SCMD_FLUSH,
+         "%s %s\r\n", sx->lmtp ? "LHLO" : "EHLO", sx->helo_data) < 0)
       goto SEND_FAILED;
       goto SEND_FAILED;
-    if (!smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
-           ob->command_timeout))
+    sx->esmtp_sent = TRUE;
+
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+    if (sx->early_pipe_active)
       {
       {
-      if (errno != 0 || buffer[0] == 0 || lmtp) goto RESPONSE_FAILED;
-      esmtp = FALSE;
+      sx->pending_EHLO = TRUE;
+
+      /* If we have too many authenticators to handle and might need to AUTH
+      for this transport, pipeline no further as we will need the
+      list of auth methods offered.  Reap the banner and EHLO. */
+
+      if (  (ob->hosts_require_auth || ob->hosts_try_auth)
+        && f.smtp_in_early_pipe_no_auth)
+       {
+       DEBUG(D_transport) debug_printf("may need to auth, so pipeline no further\n");
+       if (smtp_write_command(sx, SCMD_FLUSH, NULL) < 0)
+         goto SEND_FAILED;
+       if (sync_responses(sx, 2, 0) != 0)
+         {
+         HDEBUG(D_transport)
+           debug_printf("failed reaping pipelined cmd responses\n");
+         goto RESPONSE_FAILED;
+         }
+       sx->early_pipe_active = FALSE;
+       }
       }
       }
+    else
+#endif
+      if (!smtp_reap_ehlo(sx))
+       goto RESPONSE_FAILED;
     }
   else
     }
   else
-    {
     DEBUG(D_transport)
       debug_printf("not sending EHLO (host matches hosts_avoid_esmtp)\n");
     DEBUG(D_transport)
       debug_printf("not sending EHLO (host matches hosts_avoid_esmtp)\n");
-    }
 
 
-  if (!esmtp)
-    {
-    if (smtp_write_command(&outblock, FALSE, "HELO %s\r\n", helo_data) < 0)
-      goto SEND_FAILED;
-    if (!smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
-      ob->command_timeout)) goto RESPONSE_FAILED;
-    }
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+  if (!sx->early_pipe_active)
+#endif
+    if (!sx->esmtp)
+      {
+      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, SCMD_FLUSH, "HELO %s\r\n", sx->helo_data) < 0)
+       goto SEND_FAILED;
+      good_response = smtp_read_response(sx, rsp, n, '2', 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 */
 
 
-  /* Set IGNOREQUOTA if the response to LHLO specifies support and the
-  lmtp_ignore_quota option was set. */
+       if (rsp != sx->buffer && rsp[0] == 0 && (errno == 0 || errno == ECONNRESET))
+         {
+         errno = ERRNO_SMTPCLOSED;
+         goto EHLOHELO_FAILED;
+         }
+       memmove(sx->buffer, rsp, Ustrlen(rsp));
+       goto RESPONSE_FAILED;
+       }
+      }
 
 
-  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)
+    {
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+    if (!sx->early_pipe_active)
+#endif
+      {
+      sx->peer_offered = ehlo_response(sx->buffer,
+       OPTION_TLS      /* others checked later */
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+       | (sx->early_pipe_ok
+         ?   OPTION_IGNQ
+           | OPTION_CHUNKING | OPTION_PRDR | OPTION_DSN | OPTION_PIPE | OPTION_SIZE
+#ifdef SUPPORT_I18N
+           | OPTION_UTF8
+#endif
+           | OPTION_EARLY_PIPE
+         : 0
+         )
+#endif
+       );
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+      if (sx->early_pipe_ok)
+       {
+       sx->ehlo_resp.cleartext_features = sx->peer_offered;
+
+       if (  (sx->peer_offered & (OPTION_PIPE | OPTION_EARLY_PIPE))
+          == (OPTION_PIPE | OPTION_EARLY_PIPE))
+         {
+         DEBUG(D_transport) debug_printf("PIPE_CONNECT usable in future for this IP\n");
+         sx->ehlo_resp.cleartext_auths = study_ehlo_auths(sx);
+         write_ehlo_cache_entry(sx);
+         }
+       }
+#endif
+      }
 
   /* Set tls_offered if the response to EHLO specifies support for STARTTLS. */
 
 
   /* 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 & OPTION_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
+/* For continuing deliveries down the same channel, having re-exec'd  the socket
+is the standard input; for a socket held open from verify it is recorded
+in the cutthrough context block.  Either way 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_peer_options will have been
 set from the command line if they were set in the process that passed the
 connection on. */
 
 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
   {
 else
   {
-  inblock.sock = outblock.sock = fileno(stdin);
+  if (cutthrough.cctx.sock >= 0 && cutthrough.callout_hold_only)
+    {
+    sx->cctx = cutthrough.cctx;
+    sx->conn_args.host->port = sx->port = cutthrough.host.port;
+    }
+  else
+    {
+    sx->cctx.sock = 0;                         /* stdin */
+    sx->cctx.tls_ctx = NULL;
+    smtp_port_for_connect(sx->conn_args.host, sx->port);       /* Record the port that was used */
+    }
+  sx->inblock.cctx = sx->outblock.cctx = &sx->cctx;
   smtp_command = big_buffer;
   smtp_command = big_buffer;
-  host->port = port;    /* Record the port that was used */
+  sx->helo_data = NULL;                /* ensure we re-expand ob->helo_data */
+
+  /* For a continued connection with TLS being proxied for us, or a
+  held-open verify connection with TLS, nothing more to do. */
+
+  if (  continue_proxy_cipher
+     || (cutthrough.cctx.sock >= 0 && cutthrough.callout_hold_only
+         && cutthrough.is_tls)
+     )
+    {
+    sx->peer_offered = smtp_peer_options;
+    sx->pipelining_used = pipelining_active = !!(smtp_peer_options & OPTION_PIPE);
+    HDEBUG(D_transport) debug_printf("continued connection, %s TLS\n",
+      continue_proxy_cipher ? "proxied" : "verify conn with");
+    return OK;
+    }
+  HDEBUG(D_transport) debug_printf("continued connection, no TLS\n");
   }
 
 /* If TLS is available on this connection, whether continued or not, attempt to
   }
 
 /* If TLS is available on this connection, whether continued or not, attempt to
@@ -1450,14 +2424,30 @@ the client not be required to use TLS. If the response is bad, copy the buffer
 for error analysis. */
 
 #ifdef SUPPORT_TLS
 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 & OPTION_TLS
+   && !suppress_tls
+   && verify_check_given_host(CUSS &ob->hosts_avoid_tls, sx->conn_args.host) != OK
+   && (  !sx->verify
+      || verify_check_given_host(CUSS &ob->hosts_verify_avoid_tls, sx->conn_args.host) != OK
+   )  )
   {
   uschar buffer2[4096];
   {
   uschar buffer2[4096];
-  if (smtp_write_command(&outblock, FALSE, "STARTTLS\r\n") < 0)
+
+  if (smtp_write_command(sx, SCMD_FLUSH, "STARTTLS\r\n") < 0)
     goto SEND_FAILED;
 
     goto SEND_FAILED;
 
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+  /* If doing early-pipelining reap the banner and EHLO-response but leave
+  the response for the STARTTLS we just sent alone. */
+
+  if (sx->early_pipe_active && sync_responses(sx, 2, 0) != 0)
+    {
+    HDEBUG(D_transport)
+      debug_printf("failed reaping pipelined cmd responses\n");
+    goto RESPONSE_FAILED;
+    }
+#endif
+
   /* 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
   /* 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
@@ -1465,13 +2455,15 @@ 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). */
 
   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, buffer2, sizeof(buffer2), '2', 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' && !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;
       }
     }
       goto RESPONSE_FAILED;
       }
     }
@@ -1481,24 +2473,45 @@ if (tls_offered && !suppress_tls &&
   else
   TLS_NEGOTIATE:
     {
   else
   TLS_NEGOTIATE:
     {
-    int rc = tls_client_start(inblock.sock, host, addrlist, ob);
-
-    /* TLS negotiation failed; give an error. From outside, this function may
-    be called again to try in clear on a new connection, if the options permit
-    it for this host. */
+    address_item * addr;
+    uschar * errstr;
+    sx->cctx.tls_ctx = tls_client_start(sx->cctx.sock, sx->conn_args.host,
+                           sx->addrlist, sx->conn_args.tblock,
+# ifdef SUPPORT_DANE
+                            sx->dane ? &tlsa_dnsa : NULL,
+# endif
+                            &tls_out, &errstr);
 
 
-    if (rc != OK)
+    if (!sx->cctx.tls_ctx)
       {
       {
-      save_errno = ERRNO_TLSFAILURE;
-      message = US"failure while setting up TLS session";
-      send_quit = FALSE;
+      /* TLS negotiation failed; give an error. From outside, this function may
+      be called again to try in clear on a new connection, if the options permit
+      it for this host. */
+      DEBUG(D_tls) debug_printf("TLS session fail: %s\n", errstr);
+
+# ifdef SUPPORT_DANE
+      if (sx->dane)
+        {
+       log_write(0, LOG_MAIN,
+         "DANE attempt failed; TLS connection to %s [%s]: %s",
+         sx->conn_args.host->name, sx->conn_args.host->address, errstr);
+#  ifndef DISABLE_EVENT
+       (void) event_raise(sx->conn_args.tblock->event_action,
+         US"dane:fail", US"validation-failure");       /* could do with better detail */
+#  endif
+       }
+# endif
+
+      errno = ERRNO_TLSFAILURE;
+      message = string_sprintf("TLS session: %s", errstr);
+      sx->send_quit = FALSE;
       goto TLS_FAILED;
       }
 
     /* TLS session is set up */
 
       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;
       if (addr->transport_return == PENDING_DEFER)
         {
         addr->cipher = tls_out.cipher;
@@ -1507,7 +2520,6 @@ if (tls_offered && !suppress_tls &&
         addr->peerdn = tls_out.peerdn;
        addr->ocsp = tls_out.ocsp;
         }
         addr->peerdn = tls_out.peerdn;
        addr->ocsp = tls_out.ocsp;
         }
-      }
     }
   }
 
     }
   }
 
@@ -1521,60 +2533,110 @@ another process, and so we won't have expanded helo_data above. We have to
 expand it here. $sending_ip_address and $sending_port are set up right at the
 start of the Exim process (in exim.c). */
 
 expand it here. $sending_ip_address and $sending_port are set up right at the
 start of the Exim process (in exim.c). */
 
-if (tls_out.active >= 0)
+if (tls_out.active.sock >= 0)
   {
   {
-  char *greeting_cmd;
-  if (helo_data == NULL)
+  uschar * greeting_cmd;
+
+  if (!sx->helo_data && !(sx->helo_data = expand_string(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)
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+  /* For SMTPS there is no cleartext early-pipe; use the crypted permission bit.
+  We're unlikely to get the group sent and delivered before the server sends its
+  banner, but it's still worth sending as a group.
+  For STARTTLS allow for cleartext early-pipe but no crypted early-pipe, but not
+  the reverse.  */
+
+  if (sx->smtps ? sx->early_pipe_ok : sx->early_pipe_active)
     {
     {
-    if (!smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
-      ob->command_timeout)) goto RESPONSE_FAILED;
+    sx->peer_offered = sx->ehlo_resp.crypted_features;
+    if ((sx->early_pipe_active =
+        !!(sx->ehlo_resp.crypted_features & OPTION_EARLY_PIPE)))
+      DEBUG(D_transport) debug_printf("Using cached crypted PIPE_CONNECT\n");
     }
     }
+#endif
+
+  /* For SMTPS we need to wait for the initial OK response. */
+  if (sx->smtps)
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+    if (sx->early_pipe_active)
+      {
+      sx->pending_BANNER = TRUE;
+      sx->outblock.cmd_count = 1;
+      }
+    else
+#endif
+      if (!smtp_reap_banner(sx))
+       goto RESPONSE_FAILED;
 
 
-  if (esmtp)
-    greeting_cmd = "EHLO";
+  if (sx->lmtp)
+    greeting_cmd = US"LHLO";
+  else if (sx->esmtp)
+    greeting_cmd = US"EHLO";
   else
     {
   else
     {
-    greeting_cmd = "HELO";
+    greeting_cmd = US"HELO";
     DEBUG(D_transport)
       debug_printf("not sending EHLO (host matches hosts_avoid_esmtp)\n");
     }
 
     DEBUG(D_transport)
       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,
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+       sx->early_pipe_active ? SCMD_BUFFER :
+#endif
+         SCMD_FLUSH,
+       "%s %s\r\n", greeting_cmd, sx->helo_data) < 0)
     goto SEND_FAILED;
     goto SEND_FAILED;
-  if (!smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
-       ob->command_timeout))
-    goto RESPONSE_FAILED;
+
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+  if (sx->early_pipe_active)
+    sx->pending_EHLO = TRUE;
+  else
+#endif
+    {
+    if (!smtp_reap_ehlo(sx))
+      goto RESPONSE_FAILED;
+    smtp_peer_options = 0;
+    }
   }
 
 /* If the host is required to use a secure channel, ensure that we
 have one. */
 
   }
 
 /* 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 SUPPORT_DANE
+       || sx->dane
+# endif
+# ifdef EXPERIMENTAL_REQUIRETLS
+       || tls_requiretls & REQUIRETLS_MSG
+# endif
+       || verify_check_given_host(CUSS &ob->hosts_require_tls, sx->conn_args.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 =
+# ifdef EXPERIMENTAL_REQUIRETLS
+      tls_requiretls & REQUIRETLS_MSG ? ERRNO_REQUIRETLS :
+# endif
+      ERRNO_TLSREQUIRED;
+  message = string_sprintf("a TLS session is required, but %s",
+    smtp_peer_options & OPTION_TLS
+    ? "an attempt to start TLS failed" : "the server did not offer TLS support");
+# if defined(SUPPORT_DANE) && !defined(DISABLE_EVENT)
+  if (sx->dane)
+    (void) event_raise(sx->conn_args.tblock->event_action, US"dane:fail",
+      smtp_peer_options & OPTION_TLS
+      ? US"validation-failure"         /* could do with better detail */
+      : US"starttls-not-supported");
+# endif
   goto TLS_FAILED;
   }
   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
 
 /* 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 +2644,415 @@ continued session down a previously-used socket, we haven't just done EHLO, so
 we skip this. */
 
 if (continue_hostname == NULL
 we skip this. */
 
 if (continue_hostname == NULL
-    #ifdef SUPPORT_TLS
-    || tls_out.active >= 0
-    #endif
+#ifdef SUPPORT_TLS
+    || tls_out.active.sock >= 0
+#endif
     )
   {
     )
   {
-  /* Set for IGNOREQUOTA if the response to LHLO specifies support and the
-  lmtp_ignore_quota option was set. */
+  if (sx->esmtp || sx->lmtp)
+    {
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+  if (!sx->early_pipe_active)
+#endif
+    {
+    sx->peer_offered = ehlo_response(sx->buffer,
+       0 /* no TLS */
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+       | (sx->lmtp && ob->lmtp_ignore_quota ? OPTION_IGNQ : 0)
+       | OPTION_DSN | OPTION_PIPE | OPTION_SIZE
+       | OPTION_CHUNKING | OPTION_PRDR | OPTION_UTF8 | OPTION_REQUIRETLS
+       | (tls_out.active.sock >= 0 ? OPTION_EARLY_PIPE : 0) /* not for lmtp */
+
+#else
+
+       | (sx->lmtp && ob->lmtp_ignore_quota ? OPTION_IGNQ : 0)
+       | OPTION_CHUNKING
+       | OPTION_PRDR
+# ifdef SUPPORT_I18N
+       | (sx->addrlist->prop.utf8_msg ? OPTION_UTF8 : 0)
+         /*XXX if we hand peercaps on to continued-conn processes,
+               must not depend on this addr */
+# endif
+       | OPTION_DSN
+       | OPTION_PIPE
+       | (ob->size_addition >= 0 ? OPTION_SIZE : 0)
+# if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+       | (tls_requiretls & REQUIRETLS_MSG ? OPTION_REQUIRETLS : 0)
+# endif
+#endif
+      );
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+    if (tls_out.active.sock >= 0)
+      sx->ehlo_resp.crypted_features = sx->peer_offered;
+#endif
+    }
+
+    /* Set for IGNOREQUOTA if the response to LHLO specifies support and the
+    lmtp_ignore_quota option was set. */
 
 
-  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"";
+    sx->igquotstr = sx->peer_offered & OPTION_IGNQ ? US" IGNOREQUOTA" : US"";
 
 
-  /* If the response to EHLO specified support for the SIZE parameter, note
-  this, provided size_addition is non-negative. */
+    /* If the response to EHLO specified support for the SIZE parameter, note
+    this, provided size_addition is non-negative. */
 
 
-  smtp_use_size = esmtp && ob->size_addition >= 0 &&
-    pcre_exec(regex_SIZE, NULL, CS buffer, Ustrlen(CS buffer), 0,
-      PCRE_EOPT, NULL, 0) >= 0;
+    smtp_peer_options |= sx->peer_offered & OPTION_SIZE;
 
 
-  /* 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. */
+    /* 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_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 & OPTION_PIPE
+       && verify_check_given_host(CUSS &ob->hosts_avoid_pipelining, sx->conn_args.host) != OK)
+      smtp_peer_options |= OPTION_PIPE;
 
 
-  DEBUG(D_transport) debug_printf("%susing PIPELINING\n",
-    smtp_use_pipelining? "" : "not ");
+    DEBUG(D_transport) debug_printf("%susing PIPELINING\n",
+      smtp_peer_options & OPTION_PIPE ? "" : "not ");
+
+    if (  sx->peer_offered & OPTION_CHUNKING
+       && verify_check_given_host(CUSS &ob->hosts_try_chunking, sx->conn_args.host) != OK)
+      sx->peer_offered &= ~OPTION_CHUNKING;
+
+    if (sx->peer_offered & OPTION_CHUNKING)
+      DEBUG(D_transport) debug_printf("CHUNKING usable\n");
 
 #ifndef DISABLE_PRDR
 
 #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 & OPTION_PRDR
+       && verify_check_given_host(CUSS &ob->hosts_try_prdr, sx->conn_args.host) != OK)
+      sx->peer_offered &= ~OPTION_PRDR;
+
+    if (sx->peer_offered & OPTION_PRDR)
+      DEBUG(D_transport) debug_printf("PRDR usable\n");
+#endif
+
+    /* Note if the server supports DSN */
+    smtp_peer_options |= sx->peer_offered & OPTION_DSN;
+    DEBUG(D_transport) debug_printf("%susing DSN\n",
+                       sx->peer_offered & OPTION_DSN ? "" : "not ");
 
 
-  if (prdr_offered)
-    {DEBUG(D_transport) debug_printf("PRDR usable\n");}
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+    if (sx->peer_offered & OPTION_REQUIRETLS)
+      {
+      smtp_peer_options |= OPTION_REQUIRETLS;
+      DEBUG(D_transport) debug_printf(
+       tls_requiretls & REQUIRETLS_MSG
+       ? "using REQUIRETLS\n" : "REQUIRETLS offered\n");
+      }
 #endif
 
 #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);
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+    if (  sx->early_pipe_ok
+       && !sx->early_pipe_active
+       && tls_out.active.sock >= 0
+       && smtp_peer_options & OPTION_PIPE
+       && ( sx->ehlo_resp.cleartext_features | sx->ehlo_resp.crypted_features)
+         & OPTION_EARLY_PIPE)
+      {
+      DEBUG(D_transport) debug_printf("PIPE_CONNECT usable in future for this IP\n");
+      sx->ehlo_resp.crypted_auths = study_ehlo_auths(sx);
+      write_ehlo_cache_entry(sx);
+      }
 #endif
 
 #endif
 
-  /* Note if the response to EHLO specifies support for the AUTH extension.
-  If it has, check that this host is one we want to authenticate to, and do
-  the business. The host name and address must be available when the
-  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))
+      {
+      default:         goto SEND_QUIT;
+      case OK:         break;
+      case FAIL_SEND:  goto SEND_FAILED;
+      case FAIL:       goto RESPONSE_FAILED;
+      }
     }
   }
     }
   }
+sx->pipelining_used = pipelining_active = !!(smtp_peer_options & OPTION_PIPE);
 
 /* The setting up of the SMTP call is now complete. Any subsequent errors are
 message-specific. */
 
 
 /* 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. */
-
-if (tblock->filter_command != NULL)
+#ifdef SUPPORT_I18N
+if (sx->addrlist->prop.utf8_msg)
   {
   {
-  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;
+  uschar * s;
 
 
-  /* On failure, copy the error to all addresses, abandon the SMTP call, and
-  yield ERROR. */
+  /* If the transport sets a downconversion mode it overrides any set by ACL
+  for the message. */
 
 
-  if (!rc)
+  if ((s = ob->utf8_downconvert))
     {
     {
-    set_errno(addrlist->next, addrlist->basic_errno, addrlist->message, DEFER,
-      FALSE);
-    yield = ERROR;
-    goto SEND_QUIT;
+    if (!(s = expand_string(s)))
+      {
+      message = string_sprintf("failed to expand utf8_downconvert: %s",
+        expand_string_message);
+      set_errno_nohost(sx->addrlist, ERRNO_EXPANDFAIL, message, DEFER, FALSE);
+      yield = DEFER;
+      goto SEND_QUIT;
+      }
+    switch (*s)
+      {
+      case '1':        sx->addrlist->prop.utf8_downcvt = TRUE;
+               sx->addrlist->prop.utf8_downcvt_maybe = FALSE;
+               break;
+      case '0':        sx->addrlist->prop.utf8_downcvt = FALSE;
+               sx->addrlist->prop.utf8_downcvt_maybe = FALSE;
+               break;
+      case '-':        if (s[1] == '1')
+                 {
+                 sx->addrlist->prop.utf8_downcvt = FALSE;
+                 sx->addrlist->prop.utf8_downcvt_maybe = TRUE;
+                 }
+               break;
+      }
     }
     }
+
+  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 this is an international message we need the host to speak SMTPUTF8 */
+if (sx->utf8_needed && !(sx->peer_offered & OPTION_UTF8))
+  {
+  errno = ERRNO_UTF8_FWD;
+  goto RESPONSE_FAILED;
+  }
+#endif /*SUPPORT_I18N*/
 
 
-/* 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. */
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+  /*XXX should tls_requiretls actually be per-addr? */
 
 
-SEND_MESSAGE:
-sync_addr = first_addr;
-address_count = 0;
-ok = FALSE;
-send_rset = TRUE;
-completed_address = FALSE;
+if (  tls_requiretls & REQUIRETLS_MSG
+   && !(sx->peer_offered & OPTION_REQUIRETLS)
+   )
+  {
+  sx->setting_up = TRUE;
+  errno = ERRNO_REQUIRETLS;
+  message = US"REQUIRETLS support is required from the server"
+    " but it was not offered";
+  DEBUG(D_transport) debug_printf("%s\n", message);
+  goto TLS_FAILED;
+  }
+#endif
+
+return OK;
+
+
+  {
+  int code;
+
+  RESPONSE_FAILED:
+    message = NULL;
+    sx->send_quit = check_response(sx->conn_args.host, &errno, sx->addrlist->more_errno,
+      sx->buffer, &code, &message, &pass_message);
+    yield = DEFER;
+    goto FAILED;
+
+  SEND_FAILED:
+    code = '4';
+    message = US string_sprintf("send() to %s [%s] failed: %s",
+      sx->conn_args.host->name, sx->conn_args.host->address, strerror(errno));
+    sx->send_quit = FALSE;
+    yield = DEFER;
+    goto FAILED;
+
+  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;
+    yield = DEFER;
+    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. */
+
+#ifdef SUPPORT_TLS
+  TLS_FAILED:
+# ifdef EXPERIMENTAL_REQUIRETLS
+    if (errno == ERRNO_REQUIRETLS)
+      code = '5', yield = FAIL;
+      /*XXX DSN will be labelled 500; prefer 530 5.7.4 */
+    else
+# endif
+      code = '4', yield = DEFER;
+    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, and there are no more hosts to try,
+  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.
+
+  XXX This peeking for another host feels like a layering violation. We want
+  to note the host as unusable, but down here we shouldn't know if this was
+  the last host to try for the addr(list).  Perhaps the upper layer should be
+  the one to do set_errno() ?  The problem is that currently the addr is where
+  errno etc. are stashed, but until we run out of hosts to try the errors are
+  host-specific.  Maybe we should enhance the host_item definition? */
+
+FAILED:
+  sx->ok = FALSE;                /* For when reached by GOTO */
+  set_errno(sx->addrlist, errno, message,
+           sx->conn_args.host->next
+           ? DEFER
+           : code == '5'
+#ifdef SUPPORT_I18N
+                       || errno == ERRNO_UTF8_FWD
+#endif
+           ? FAIL : DEFER,
+           pass_message, sx->conn_args.host
+#ifdef EXPERIMENTAL_DSN_INFO
+           , sx->smtp_greeting, sx->helo_response
+#endif
+           );
+  }
+
+
+SEND_QUIT:
+
+if (sx->send_quit)
+  (void)smtp_write_command(sx, SCMD_FLUSH, "QUIT\r\n");
+
+#ifdef SUPPORT_TLS
+if (sx->cctx.tls_ctx)
+  {
+  tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT);
+  sx->cctx.tls_ctx = NULL;
+  }
+#endif
+
+/* 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,
+*/
+
+HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP(close)>>\n");
+if (sx->send_quit)
+  {
+  shutdown(sx->cctx.sock, SHUT_WR);
+  if (fcntl(sx->cctx.sock, F_SETFL, O_NONBLOCK) == 0)
+    for (rc = 16; read(sx->cctx.sock, sx->inbuffer, sizeof(sx->inbuffer)) > 0 && rc > 0;)
+      rc--;                            /* drain socket */
+  sx->send_quit = FALSE;
+  }
+(void)close(sx->cctx.sock);
+sx->cctx.sock = -1;
+
+#ifndef DISABLE_EVENT
+(void) event_raise(sx->conn_args.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;
 
 
-/* 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
+/* If we know the receiving MTA supports the SIZE qualification, and we know it,
+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.) */
 
 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 (  message_size > 0
+   && sx->peer_offered & OPTION_SIZE && !(sx->avoid_option & OPTION_SIZE))
   {
   {
-  sprintf(CS p, " SIZE=%d", message_size+message_linecount+ob->size_addition);
+/*XXX problem here under spool_files_wireformat?
+Or just forget about lines?  Or inflate by a fixed proportion? */
+
+  sprintf(CS p, " SIZE=%d", message_size+message_linecount+(SOB sx->conn_args.ob)->size_addition);
   while (*p) p++;
   }
 
 #ifndef DISABLE_PRDR
   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 Responses, and we have more than one recipient,
+request that */
+
+sx->prdr_active = FALSE;
+if (sx->peer_offered & OPTION_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 */
     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;
          sprintf(CS p, " PRDR"); p += 5;
-         goto prdr_is_active;
+         break;
          }
       break;
       }
          }
       break;
       }
-  }
-prdr_is_active:
 #endif
 
 #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 & OPTION_UTF8
+   && addrlist->prop.utf8_msg
+   && !addrlist->prop.utf8_downcvt
+   )
+  Ustrcpy(p, " SMTPUTF8"), p += 9;
+#endif
 
 
-if ((smtp_use_dsn) && (dsn_all_lasthop == FALSE))
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+if (tls_requiretls & REQUIRETLS_MSG)
+  Ustrcpy(p, " REQUIRETLS") , p += 11;
+#endif
+
+/* 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 & OPTION_DSN && !sx->dsn_all_lasthop)
+  {
+  if (dsn_ret == dsn_ret_hdrs)
+    { Ustrcpy(p, " RET=HDRS"); p += 9; }
   else if (dsn_ret == dsn_ret_full)
   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++;
     }
   }
     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
 
 /* 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 +3061,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. */
 
 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->conn_args.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 & OPTION_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
 
 /* 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,152 +3134,436 @@ 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. */
 
 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 & OPTION_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, pipelining_active ? SCMD_BUFFER : SCMD_FLUSH,
+         "MAIL FROM:<%s>%s\r\n", s, sx->buffer);
+  }
 
 
-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 */
 
 switch(rc)
   {
   case -1:                /* Transmission error */
 mail_command = string_copy(big_buffer);  /* Save for later error message */
 
 switch(rc)
   {
   case -1:                /* Transmission error */
-  goto SEND_FAILED;
+    return -5;
 
 
-  case +1:                /* Block was sent */
-  if (!smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
-       ob->command_timeout))
-    {
-    if (errno == 0 && buffer[0] == '4')
+  case +1:                /* Cmd was sent */
+    if (!smtp_read_response(sx, sx->buffer, sizeof(sx->buffer), '2',
+       (SOB sx->conn_args.ob)->command_timeout))
       {
       {
-      errno = ERRNO_MAIL4XX;
-      addrlist->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
+      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;
       }
       }
-    goto RESPONSE_FAILED;
-    }
-  pending_MAIL = FALSE;
-  break;
+    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.
   }
 
 /* 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
+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
 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. */
+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 = first_addr;
-     address_count < max_rcpt && addr != NULL;
-     addr = addr->next)
+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;
   {
   int count;
   BOOL no_flush;
+  uschar * rcpt_addr;
 
 
-  #ifdef EXPERIMENTAL_DSN
-  if(smtp_use_dsn)
-    addr->dsn_aware = dsn_support_yes;
-  else
-    addr->dsn_aware = dsn_support_no;
-  #endif
-
-  if (addr->transport_return != PENDING_DEFER) continue;
+  addr->dsn_aware = sx->peer_offered & OPTION_DSN
+    ? dsn_support_yes : dsn_support_no;
 
   address_count++;
 
   address_count++;
-  no_flush = smtp_use_pipelining && (!mua_wrapper || addr->next != NULL);
+  no_flush = pipelining_active && !sx->verify
+         && (!mua_wrapper || addr->next && address_count < sx->max_rcpt);
+
+  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->conn_args.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, no_flush ? SCMD_BUFFER : SCMD_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 */
+
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+      case -4: return -1;                      /* non-2xx for pipelined banner or EHLO */
+#endif
+      }
+    sx->pending_MAIL = FALSE;            /* Dealt with MAIL */
+    }
+  }      /* Loop for next address */
+
+sx->next_addr = addr;
+return 0;
+}
+
 
 
-  #ifdef EXPERIMENTAL_DSN
-  /* Add any DSN flags to the rcpt command and add to the sent string */
+#ifdef SUPPORT_TLS
+/*****************************************************
+* Proxy TLS connection for another transport process *
+******************************************************/
+/*
+Close the unused end of the pipe, fork once more, then use the given buffer
+as a staging area, and select on both the given fd and the TLS'd client-fd for
+data to read (per the coding in ip_recv() and fd_ready() this is legitimate).
+Do blocking full-size writes, and reads under a timeout.  Once both input
+channels are closed, exit the process.
+
+Arguments:
+  ct_ctx       tls context
+  buf          space to use for buffering
+  bufsiz       size of buffer
+  pfd          pipe filedescriptor array; [0] is comms to proxied process
+  timeout      per-read timeout, seconds
+*/
+
+void
+smtp_proxy_tls(void * ct_ctx, uschar * buf, size_t bsize, int * pfd,
+  int timeout)
+{
+fd_set rfds, efds;
+int max_fd = MAX(pfd[0], tls_out.active.sock) + 1;
+int rc, i, fd_bits, nbytes;
+
+close(pfd[1]);
+if ((rc = fork()))
+  {
+  DEBUG(D_transport) debug_printf("proxy-proc final-pid %d\n", rc);
+  _exit(rc < 0 ? EXIT_FAILURE : EXIT_SUCCESS);
+  }
+
+if (f.running_in_test_harness) millisleep(100); /* let parent debug out */
+set_process_info("proxying TLS connection for continued transport");
+FD_ZERO(&rfds);
+FD_SET(tls_out.active.sock, &rfds);
+FD_SET(pfd[0], &rfds);
+
+for (fd_bits = 3; fd_bits; )
+  {
+  time_t time_left = timeout;
+  time_t time_start = time(NULL);
+
+  /* wait for data */
+  efds = rfds;
+  do
+    {
+    struct timeval tv = { time_left, 0 };
+
+    rc = select(max_fd,
+      (SELECT_ARG2_TYPE *)&rfds, NULL, (SELECT_ARG2_TYPE *)&efds, &tv);
+
+    if (rc < 0 && errno == EINTR)
+      if ((time_left -= time(NULL) - time_start) > 0) continue;
+
+    if (rc <= 0)
+      {
+      DEBUG(D_transport) if (rc == 0) debug_printf("%s: timed out\n", __FUNCTION__);
+      goto done;
+      }
+
+    if (FD_ISSET(tls_out.active.sock, &efds) || FD_ISSET(pfd[0], &efds))
+      {
+      DEBUG(D_transport) debug_printf("select: exceptional cond on %s fd\n",
+       FD_ISSET(pfd[0], &efds) ? "proxy" : "tls");
+      goto done;
+      }
+    }
+  while (rc < 0 || !(FD_ISSET(tls_out.active.sock, &rfds) || FD_ISSET(pfd[0], &rfds)));
+
+  /* handle inbound data */
+  if (FD_ISSET(tls_out.active.sock, &rfds))
+    if ((rc = tls_read(ct_ctx, buf, bsize)) <= 0)
+      {
+      fd_bits &= ~1;
+      FD_CLR(tls_out.active.sock, &rfds);
+      shutdown(pfd[0], SHUT_WR);
+      timeout = 5;
+      }
+    else
+      {
+      for (nbytes = 0; rc - nbytes > 0; nbytes += i)
+       if ((i = write(pfd[0], buf + nbytes, rc - nbytes)) < 0) goto done;
+      }
+  else if (fd_bits & 1)
+    FD_SET(tls_out.active.sock, &rfds);
+
+  /* handle outbound data */
+  if (FD_ISSET(pfd[0], &rfds))
+    if ((rc = read(pfd[0], buf, bsize)) <= 0)
+      {
+      fd_bits = 0;
+      tls_close(ct_ctx, TLS_SHUTDOWN_NOWAIT);
+      ct_ctx = NULL;
+      }
+    else
+      {
+      for (nbytes = 0; rc - nbytes > 0; nbytes += i)
+       if ((i = tls_write(ct_ctx, buf + nbytes, rc - nbytes, FALSE)) < 0)
+         goto done;
+      }
+  else if (fd_bits & 2)
+    FD_SET(pfd[0], &rfds);
+  }
+
+done:
+  if (f.running_in_test_harness) millisleep(100);      /* let logging complete */
+  exim_exit(0, US"TLS proxy");
+}
+#endif
+
+
+/*************************************************
+*       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
+  defport         default TCP/IP port to use if host does not specify, 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
+
+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
+*/
+
+static int
+smtp_deliver(address_item *addrlist, host_item *host, int host_af, int defport,
+  uschar *interface, transport_instance *tblock,
+  BOOL *message_defer, BOOL suppress_tls)
+{
+address_item *addr;
+smtp_transport_options_block * ob = SOB tblock->options_block;
+int yield = OK;
+int save_errno;
+int rc;
+struct timeval start_delivery_time;
+
+BOOL pass_message = FALSE;
+uschar *message = NULL;
+uschar new_message_id[MESSAGE_ID_LENGTH + 1];
+
+smtp_context sx;
+
+gettimeofday(&start_delivery_time, NULL);
+suppress_tls = suppress_tls;  /* stop compiler warning when no TLS support */
+*message_defer = FALSE;
+
+sx.addrlist = addrlist;
+sx.conn_args.host = host;
+sx.conn_args.host_af = host_af,
+sx.port = defport;
+sx.conn_args.interface = interface;
+sx.helo_data = NULL;
+sx.conn_args.tblock = tblock;
+sx.verify = FALSE;
+sx.sync_addr = sx.first_addr = addrlist;
+
+/* Get the channel set up ready for a message (MAIL FROM being the next
+SMTP command to send */
+
+if ((rc = smtp_setup_conn(&sx, suppress_tls)) != OK)
+  return rc;
+
+/* 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. */
+
+if (tblock->filter_command)
+  {
+  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 (!transport_set_up_command(&transport_filter_argv,
+       tblock->filter_command, TRUE, DEFER, addrlist,
+       string_sprintf("%.50s transport", tblock->name), NULL))
     {
     {
-    if ((addr->dsn_flags & rf_dsnflags) != 0)
-      {
-      int i;
-      BOOL first = TRUE;
-      strcpy(p, " NOTIFY=");
-      while (*p) p++;
-      for (i = 0; i < 4; i++)
-        {
-        if ((addr->dsn_flags & rf_list[i]) != 0)
-          {
-          if (!first) *p++ = ',';
-          first = FALSE;
-          strcpy(p, rf_names[i]);
-          while (*p) p++;
-          }
-        }
-      }
-
-    if (addr->dsn_orcpt != NULL) {
-      string_format(p, sizeof(buffer) - (p-buffer), " ORCPT=%s",
-        addr->dsn_orcpt);
-      while (*p) p++;
-      }
+    set_errno_nohost(addrlist->next, addrlist->basic_errno, addrlist->message, DEFER,
+      FALSE);
+    yield = ERROR;
+    goto SEND_QUIT;
     }
     }
-  #endif
 
 
+  if (  transport_filter_argv
+     && *transport_filter_argv
+     && **transport_filter_argv
+     && sx.peer_offered & OPTION_CHUNKING
+     )
+    {
+    sx.peer_offered &= ~OPTION_CHUNKING;
+    DEBUG(D_transport) debug_printf("CHUNKING not usable due to transport filter\n");
+    }
+  }
 
 
-  /* 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.sync_addr = sx.first_addr;
+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;
+/* If we are a continued-connection-after-verify the MAIL and RCPT
+commands were already sent; do not re-send but do mark the addrs as
+having been accepted up to RCPT stage.  A traditional cont-conn
+always has a sequence number greater than one. */
 
 
-      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 */
+if (continue_hostname && continue_sequence == 1)
+  {
+  address_item * addr;
 
 
-/* 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
-RCPT. */
+  sx.peer_offered = smtp_peer_options;
+  sx.pending_MAIL = FALSE;
+  sx.ok = TRUE;
+  sx.next_addr = NULL;
 
 
-if (mua_wrapper)
+  for (addr = addrlist; addr; addr = addr->next)
+    addr->transport_return = PENDING_OK;
+  }
+else
   {
   {
-  address_item *badaddr;
-  for (badaddr = first_addr; badaddr != NULL; badaddr = badaddr->next)
+  /* Initiate a message transfer. */
+
+  switch(smtp_write_mail_and_rcpt_cmds(&sx, &yield))
     {
     {
-    if (badaddr->transport_return != PENDING_OK) break;
+    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 (badaddr != NULL)
+
+  /* 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
+  RCPT. */
+
+  if (mua_wrapper)
     {
     {
-    set_errno(addrlist, 0, badaddr->message, FAIL,
-      testflag(badaddr, af_pass_message));
-    ok = FALSE;
+    address_item * a;
+    unsigned cnt;
+
+    for (a = sx.first_addr, cnt = 0; a && cnt < sx.max_rcpt; a = a->next, cnt++)
+      if (a->transport_return != PENDING_OK)
+       {
+       /*XXX could we find a better errno than 0 here? */
+       set_errno_nohost(addrlist, 0, a->message, FAIL,
+         testflag(a, af_pass_message));
+       sx.ok = FALSE;
+       break;
+       }
     }
   }
 
     }
   }
 
@@ -1930,33 +3571,38 @@ if (mua_wrapper)
 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
 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 & OPTION_CHUNKING)
+   && (sx.ok || (pipelining_active && !mua_wrapper)))
   {
   {
-  int count = smtp_write_command(&outblock, FALSE, "DATA\r\n");
+  int count = smtp_write_command(&sx, SCMD_FLUSH, "DATA\r\n");
+
   if (count < 0) goto SEND_FAILED;
   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;
 
     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 */
     case 0: break;                       /* No 2xx or 5xx, but no probs */
 
     case -1: goto END_OFF;               /* Timeout on RCPT */
+
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+    case -4:  HDEBUG(D_transport)
+               debug_printf("failed reaping pipelined cmd responses\n");
+#endif
     default: goto RESPONSE_FAILED;       /* I/O error, or any MAIL/DATA error */
     }
     default: goto RESPONSE_FAILED;       /* I/O error, or any MAIL/DATA error */
     }
+  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
 /* 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 +3610,94 @@ 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.) */
 
 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 & OPTION_CHUNKING) && !sx.ok)
+  {
+  /* Save the first address of the next batch. */
+  sx.first_addr = sx.next_addr;
+
+  sx.ok = TRUE;
+  }
+else
   {
   {
+  transport_ctx tctx = {
+    .u = {.fd = sx.cctx.sock}, /*XXX will this need TLS info? */
+    .tblock =  tblock,
+    .addr =    addrlist,
+    .check_string = US".",
+    .escape_string = US"..",   /* Escaping strings */
+    .options =
+      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 & OPTION_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;
   smtp_command = US"sending data block";   /* For error messages */
   DEBUG(D_transport|D_v)
   sigalrm_seen = FALSE;
   transport_write_timeout = 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 & OPTION_CHUNKING)
+      debug_printf("         will write message using CHUNKING\n");
+    else
+      debug_printf("  SMTP>> writing message and terminating \".\"\n");
   transport_count = 0;
   transport_count = 0;
+
 #ifndef DISABLE_DKIM
 #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
-    );
+  dkim_exim_sign_init();
+# ifdef EXPERIMENTAL_ARC
+    {
+    uschar * s = ob->arc_sign;
+    if (s)
+      {
+      if (!(ob->dkim.arc_signspec = s = expand_string(s)))
+       {
+       if (!f.expand_string_forcedfail)
+         {
+         message = US"failed to expand arc_sign";
+         sx.ok = FALSE;
+         goto SEND_FAILED;
+         }
+       }
+      else if (*s)
+       {
+       /* Ask dkim code to hash the body for ARC */
+       (void) arc_ams_setup_sign_bodyhash();
+       ob->dkim.force_bodyhash = TRUE;
+       }
+      }
+    }
+# endif
+  sx.ok = dkim_transport_write_message(&tctx, &ob->dkim, CUSS &message);
 #else
 #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(&tctx, 0);
 #endif
 
   /* transport_write_message() uses write() because it is called from other
 #endif
 
   /* transport_write_message() uses write() because it is called from other
@@ -2009,13 +3708,12 @@ if (!ok) ok = TRUE; else
   transport_write_timeout = 0;   /* for subsequent transports */
 
   /* Failure can either be some kind of I/O disaster (including timeout),
   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 */
-    goto RESPONSE_FAILED;
-    }
+  if (!sx.ok)
+    if (message) goto SEND_FAILED;
+    else         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
 
   /* 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 +3723,47 @@ if (!ok) ok = TRUE; else
 
   smtp_command = US"end of data";
 
 
   smtp_command = US"end of data";
 
+  if (sx.peer_offered & OPTION_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 */
+
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+      case -4:  HDEBUG(D_transport)
+                 debug_printf("failed reaping pipelined cmd responses\n");
+#endif
+      default: goto RESPONSE_FAILED;       /* I/O error, or any MAIL/DATA error */
+      }
+    }
+
 #ifndef DISABLE_PRDR
 #ifndef DISABLE_PRDR
-  /* For PRDR we optionally get a partial-responses warning
-   * followed by the individual responses, before going on with
-   * the overall response.  If we don't get the warning then deal
-   * with per non-PRDR. */
-  if(prdr_active)
+  /* 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(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, sx.buffer, sizeof(sx.buffer), '3', 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
     }
   else
 #endif
@@ -2051,14 +3771,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. */
 
   /* 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',
+    sx.ok = smtp_read_response(&sx, sx.buffer, sizeof(sx.buffer), '2',
       ob->final_timeout);
       ob->final_timeout);
-    if (!ok && errno == 0 && buffer[0] == '4')
+    if (!sx.ok && errno == 0 && sx.buffer[0] == '4')
       {
       errno = ERRNO_DATA4XX;
       {
       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 +3794,35 @@ if (!ok) ok = TRUE; else
   software before the spool gets updated. Also record the final SMTP
   confirmation if needed (for SMTP only). */
 
   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 flag = '=';
-    int delivery_time = (int)(time(NULL) - start_delivery_time);
+    struct timeval delivery_time;
     int len;
     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. */
+    uschar * conf = NULL;
 
 
-    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;
+    timesince(&delivery_time, &start_delivery_time);
+    sx.send_rset = FALSE;
+    pipelining_active = FALSE;
 
     /* Set up confirmation if needed - applies only to SMTP */
 
     if (
 
     /* 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)? US string_copy(s) : US s;
       }
 
     /* Process all transported addresses - for LMTP or PRDR, read a status for
     each one. */
 
       }
 
     /* 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;
 
       {
       if (addr->transport_return != PENDING_OK) continue;
 
@@ -2121,42 +3832,43 @@ if (!ok) ok = TRUE; else
       it doesn't get tried again too soon. */
 
 #ifndef DISABLE_PRDR
       it doesn't get tried again too soon. */
 
 #ifndef DISABLE_PRDR
-      if (lmtp || prdr_active)
+      if (sx.lmtp || sx.prdr_active)
 #else
 #else
-      if (lmtp)
+      if (sx.lmtp)
 #endif
         {
 #endif
         {
-        if (!smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
+        if (!smtp_read_response(&sx, sx.buffer, sizeof(sx.buffer), '2',
             ob->final_timeout))
           {
             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
           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
 #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 */
           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->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
             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;
           }
 #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;
           }
         }
 
           }
         }
 
@@ -2164,17 +3876,30 @@ if (!ok) ok = TRUE; else
       actual host that was used. */
 
       addr->transport_return = OK;
       actual host that was used. */
 
       addr->transport_return = OK;
-      addr->more_errno = delivery_time;
-      addr->host_used = thost;
+      addr->more_errno = delivery_time.tv_sec;
+      addr->delivery_usec = delivery_time.tv_usec;
+      addr->host_used = host;
       addr->special_action = flag;
       addr->message = conf;
       addr->special_action = flag;
       addr->message = conf;
+
+      if (tcp_out_fastopen)
+       {
+       setflag(addr, af_tcp_fastopen_conn);
+       if (tcp_out_fastopen >= TFO_USED_NODATA) setflag(addr, af_tcp_fastopen);
+       if (tcp_out_fastopen >= TFO_USED_DATA) setflag(addr, af_tcp_fastopen_data);
+       }
+      if (sx.pipelining_used) setflag(addr, af_pipelining);
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+      if (sx.early_pipe_active) setflag(addr, af_early_pipe);
+#endif
 #ifndef DISABLE_PRDR
 #ifndef DISABLE_PRDR
-      if (prdr_active) addr->flags |= af_prdr_used;
+      if (sx.prdr_active) setflag(addr, af_prdr_used);
 #endif
 #endif
+      if (sx.peer_offered & OPTION_CHUNKING) setflag(addr, af_chunking_used);
       flag = '-';
 
 #ifndef DISABLE_PRDR
       flag = '-';
 
 #ifndef DISABLE_PRDR
-      if (!prdr_active)
+      if (!sx.prdr_active)
 #endif
         {
         /* Update the journal. For homonymic addresses, use the base address plus
 #endif
         {
         /* Update the journal. For homonymic addresses, use the base address plus
@@ -2183,55 +3908,65 @@ if (!ok) ok = TRUE; else
         write error, as it may prove possible to update the spool file later. */
 
         if (testflag(addr, af_homonym))
         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
         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("S: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 "
           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
         }
       }
 
 #ifndef DISABLE_PRDR
-      if (prdr_active)
+      if (sx.prdr_active)
         {
         {
+       const uschar * overall_message;
+
        /* PRDR - get the final, overall response.  For any non-success
        upgrade all the address statuses. */
        /* PRDR - get the final, overall response.  For any non-success
        upgrade all the address statuses. */
-        ok = smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
+
+        sx.ok = smtp_read_response(&sx, sx.buffer, sizeof(sx.buffer), '2',
           ob->final_timeout);
           ob->final_timeout);
-        if (!ok)
+        if (!sx.ok)
          {
          {
-         if(errno == 0 && buffer[0] == '4')
+         if(errno == 0 && sx.buffer[0] == '4')
             {
             errno = ERRNO_DATA4XX;
             {
             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;
          }
 
               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)
+       /* Append the overall response to the individual PRDR response for logging
+       and update the journal, or setup retry. */
+
+       overall_message = string_printing(sx.buffer);
+        for (addr = addrlist; addr != sx.first_addr; addr = addr->next)
          if (addr->transport_return == OK)
          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);
+           addr->message = string_sprintf("%s\\n%s", addr->message, overall_message);
 
 
-          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);
+        for (addr = addrlist; addr != sx.first_addr; addr = addr->next)
+         if (addr->transport_return == OK)
+           {
+           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
 
        }
 #endif
 
@@ -2252,57 +3987,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. */
 
 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:
 
   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:
 
   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 = string_sprintf("send() to %s [%s] failed: %s",
+      host->name, host->address, message ? message : US strerror(save_errno));
+    sx.send_quit = FALSE;
+    goto FAILED;
+    }
 
   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
 
   /* 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 +4024,26 @@ if (!ok)
   cases are treated in the same way as a 4xx response. This next bit of code
   does the classification. */
 
   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:
     switch(save_errno)
       {
       case 0:
       case ERRNO_MAIL4XX:
       case ERRNO_DATA4XX:
-      message_error = TRUE;
-      break;
+       message_error = TRUE;
+       break;
 
       case ETIMEDOUT:
 
       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:
 
       case ERRNO_SMTPCLOSED:
-      message_error = Ustrncmp(smtp_command,"end ",4) == 0;
-      break;
+       message_error = Ustrncmp(smtp_command,"end ",4) == 0;
+       break;
 
       default:
 
       default:
-      message_error = FALSE;
-      break;
+       message_error = FALSE;
+       break;
       }
 
     /* Handle the cases that are treated as message errors. These are:
       }
 
     /* Handle the cases that are treated as message errors. These are:
@@ -2341,6 +4051,7 @@ if (!ok)
       (a) negative response or timeout after MAIL
       (b) negative response after DATA
       (c) negative response or timeout or dropped connection after "."
       (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
 
     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 +4065,20 @@ if (!ok)
     if (message_error)
       {
       if (mua_wrapper) code = '5';  /* Force hard failure in wrapper mode */
     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 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 (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);
+
+        write_logs(host, message, sx.first_addr ? sx.first_addr->basic_errno : 0);
+
         *message_defer = TRUE;
         }
       }
         *message_defer = TRUE;
         }
       }
@@ -2378,11 +4091,28 @@ if (!ok)
 
     else
       {
 
     else
       {
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+      /* If we were early-pipelinng and the actual EHLO response did not match
+      the cached value we assumed, we could have detected it and passed a
+      custom errno through to here.  It would be nice to RSET and retry right
+      away, but to reliably do that we eould need an extra synch point before
+      we committed to data and that would discard half the gained roundrips.
+      Or we could summarily drop the TCP connection. but that is also ugly.
+      Instead, we ignore the possibility (having freshened the cache) and rely
+      on the server telling us with a nonmessage error if we have tried to
+      do something it no longer supports. */
+#endif
+      set_rc = DEFER;
       yield = (save_errno == ERRNO_CHHEADER_FAIL ||
       yield = (save_errno == ERRNO_CHHEADER_FAIL ||
-               save_errno == ERRNO_FILTER_FAIL)? ERROR : DEFER;
-      set_errno(addrlist, save_errno, message, DEFER, pass_message);
+               save_errno == ERRNO_FILTER_FAIL) ? ERROR : DEFER;
       }
     }
       }
     }
+
+  set_errno(addrlist, save_errno, set_message, set_rc, pass_message, host
+#ifdef EXPERIMENTAL_DSN_INFO
+           , sx.smtp_greeting, sx.helo_response
+#endif
+           );
   }
 
 
   }
 
 
@@ -2394,7 +4124,7 @@ connection if there are several waiting, provided we haven't already sent so
 many as to hit the configured limit. The function transport_check_waiting looks
 for a waiting message and returns its id. Then transport_pass_socket tries to
 set up a continued delivery by passing the socket on to another process. The
 many as to hit the configured limit. The function transport_check_waiting looks
 for a waiting message and returns its id. Then transport_pass_socket tries to
 set up a continued delivery by passing the socket on to another process. The
-variable send_rset is FALSE if a message has just been successfully transfered.
+variable send_rset is FALSE if a message has just been successfully transferred.
 
 If we are already sending down a continued channel, there may be further
 addresses not yet delivered that are aimed at the same host, but which have not
 
 If we are already sending down a continued channel, there may be further
 addresses not yet delivered that are aimed at the same host, but which have not
@@ -2416,88 +4146,173 @@ hosts_nopass_tls. */
 
 DEBUG(D_transport)
   debug_printf("ok=%d send_quit=%d send_rset=%d continue_more=%d "
 
 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, f.continue_more, yield, sx.first_addr ? "not " : "");
 
 
-if (completed_address && ok && send_quit)
+if (sx.completed_addr && sx.ok && sx.send_quit)
   {
   BOOL more;
   {
   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
+     || f.continue_more
+     || (
+#ifdef SUPPORT_TLS
+          (  tls_out.active.sock < 0  &&  !continue_proxy_cipher
+           || verify_check_given_host(CUSS &ob->hosts_nopass_tls, host) != OK
+          )
         &&
         &&
+#endif
            transport_check_waiting(tblock->name, host->name,
            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;
 
     {
     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, SCMD_FLUSH, "RSET\r\n") >= 0))
         {
         msg = US string_sprintf("send() to %s [%s] failed: %s", host->name,
         {
         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, sx.buffer, sizeof(sx.buffer),
+                 '2', ob->command_timeout)))
         {
         int code;
         {
         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);
           &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 */
 
 
     /* Either RSET was not needed, or it succeeded */
 
-    if (ok)
+    if (sx.ok)
       {
       {
-      if (first_addr != NULL)            /* More addresses still to be sent */
+#ifdef SUPPORT_TLS
+      int pfd[2];
+#endif
+      int socket_fd = sx.cctx.sock;
+
+
+      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;
         }
         {                                /*   in this run of the transport */
         continue_sequence++;             /* Causes * in logging */
         goto SEND_MESSAGE;
         }
-      if (continue_more) return yield;   /* More addresses for another run */
 
 
-      /* Pass the socket to a new Exim process. Before doing so, we must shut
-      down TLS. Not all MTAs allow for the continuation of the SMTP session
-      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. */
+      /* Unless caller said it already has more messages listed for this host,
+      pass the connection on to a new Exim process (below, the call to
+      transport_pass_socket).  If the caller has more ready, just return with
+      the connection still open. */
 
 
-      #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);
-        }
-      #endif
+#ifdef SUPPORT_TLS
+      if (tls_out.active.sock >= 0)
+       if (  f.continue_more
+          || verify_check_given_host(CUSS &ob->hosts_noproxy_tls, host) == OK)
+         {
+         /* Before passing the socket on, or returning to caller with it still
+         open, we must shut down TLS.  Not all MTAs allow for the continuation
+         of the SMTP session 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. */
+
+         tls_close(sx.cctx.tls_ctx, TLS_SHUTDOWN_WAIT);
+         sx.cctx.tls_ctx = NULL;
+         smtp_peer_options = smtp_peer_options_wrap;
+         sx.ok = !sx.smtps
+           && smtp_write_command(&sx, SCMD_FLUSH, "EHLO %s\r\n", sx.helo_data)
+               >= 0
+           && smtp_read_response(&sx, sx.buffer, sizeof(sx.buffer),
+                                     '2', ob->command_timeout);
+
+         if (sx.ok && f.continue_more)
+           return yield;               /* More addresses for another run */
+         }
+       else
+         {
+         /* Set up a pipe for proxying TLS for the new transport process */
+
+         smtp_peer_options |= OPTION_TLS;
+         if (sx.ok = (socketpair(AF_UNIX, SOCK_STREAM, 0, pfd) == 0))
+           socket_fd = pfd[1];
+         else
+           set_errno(sx.first_addr, errno, US"internal allocation problem",
+                   DEFER, FALSE, host
+# ifdef EXPERIMENTAL_DSN_INFO
+                   , sx.smtp_greeting, sx.helo_response
+# endif
+                   );
+         }
+      else
+#endif
+       if (f.continue_more)
+         return yield;                 /* More addresses for another run */
 
 
-      /* 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. */
 
       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, socket_fd))
+       {
+        sx.send_quit = FALSE;
+
+       /* We have passed the client socket to a fresh transport process.
+       If TLS is still active, we need to proxy it for the transport we
+       just passed the baton to.  Fork a child to to do it, and return to
+       get logging done asap.  Which way to place the work makes assumptions
+       about post-fork prioritisation which may not hold on all platforms. */
+#ifdef SUPPORT_TLS
+       if (tls_out.active.sock >= 0)
+         {
+         int pid = fork();
+         if (pid == 0)         /* child; fork again to disconnect totally */
+           {
+           if (f.running_in_test_harness) millisleep(100); /* let parent debug out */
+           /* does not return */
+           smtp_proxy_tls(sx.cctx.tls_ctx, sx.buffer, sizeof(sx.buffer), pfd,
+                           ob->command_timeout);
+           }
+
+         if (pid > 0)          /* parent */
+           {
+           DEBUG(D_transport) debug_printf("proxy-proc inter-pid %d\n", pid);
+           close(pfd[0]);
+           /* tidy the inter-proc to disconn the proxy proc */
+           waitpid(pid, NULL, 0);
+           tls_close(sx.cctx.tls_ctx, TLS_NO_SHUTDOWN);
+           sx.cctx.tls_ctx = NULL;
+           (void)close(sx.cctx.sock);
+           sx.cctx.sock = -1;
+           continue_transport = NULL;
+           continue_hostname = NULL;
+           return yield;
+           }
+         log_write(0, LOG_PANIC_DIE, "fork failed");
+         }
+#endif
+       }
       }
 
     /* If RSET failed and there are addresses left, they get deferred. */
       }
 
     /* 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,12 +4335,13 @@ 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:
 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, SCMD_FLUSH, "QUIT\r\n");
 
 END_OFF:
 
 #ifdef SUPPORT_TLS
 
 END_OFF:
 
 #ifdef SUPPORT_TLS
-tls_close(FALSE, TRUE);
+tls_close(sx.cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT);
+sx.cctx.tls_ctx = NULL;
 #endif
 
 /* Close the socket, and return the appropriate value, first setting
 #endif
 
 /* Close the socket, and return the appropriate value, first setting
@@ -2538,7 +4354,21 @@ 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. */
 
 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.cctx.sock, SHUT_WR);
+  millisleep(f.running_in_test_harness ? 200 : 20);
+  if (fcntl(sx.cctx.sock, F_SETFL, O_NONBLOCK) == 0)
+    for (rc = 16; read(sx.cctx.sock, sx.inbuffer, sizeof(sx.inbuffer)) > 0 && rc > 0;)
+      rc--;                            /* drain socket */
+  }
+(void)close(sx.cctx.sock);
+
+#ifndef DISABLE_EVENT
+(void) event_raise(tblock->event_action, US"tcp:close", NULL);
+#endif
+
 continue_transport = NULL;
 continue_hostname = NULL;
 return yield;
 continue_transport = NULL;
 continue_hostname = NULL;
 return yield;
@@ -2567,31 +4397,33 @@ Returns:    nothing
 void
 smtp_transport_closedown(transport_instance *tblock)
 {
 void
 smtp_transport_closedown(transport_instance *tblock)
 {
-smtp_transport_options_block *ob =
-  (smtp_transport_options_block *)(tblock->options_block);
-smtp_inblock inblock;
-smtp_outblock outblock;
+smtp_transport_options_block * ob = SOB tblock->options_block;
+client_conn_ctx cctx;
+smtp_context sx;
 uschar buffer[256];
 uschar inbuffer[4096];
 uschar outbuffer[16];
 
 uschar buffer[256];
 uschar inbuffer[4096];
 uschar outbuffer[16];
 
-inblock.sock = fileno(stdin);
-inblock.buffer = inbuffer;
-inblock.buffersize = sizeof(inbuffer);
-inblock.ptr = inbuffer;
-inblock.ptrend = inbuffer;
-
-outblock.sock = inblock.sock;
-outblock.buffersize = sizeof(outbuffer);
-outblock.buffer = outbuffer;
-outblock.ptr = outbuffer;
-outblock.cmd_count = 0;
-outblock.authenticating = FALSE;
-
-(void)smtp_write_command(&outblock, FALSE, "QUIT\r\n");
-(void)smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
-  ob->command_timeout);
-(void)close(inblock.sock);
+/*XXX really we need an active-smtp-client ctx, rather than assuming stdout */
+cctx.sock = fileno(stdin);
+cctx.tls_ctx = cctx.sock == tls_out.active.sock ? tls_out.active.tls_ctx : NULL;
+
+sx.inblock.cctx = &cctx;
+sx.inblock.buffer = inbuffer;
+sx.inblock.buffersize = sizeof(inbuffer);
+sx.inblock.ptr = inbuffer;
+sx.inblock.ptrend = inbuffer;
+
+sx.outblock.cctx = &cctx;
+sx.outblock.buffersize = sizeof(outbuffer);
+sx.outblock.buffer = outbuffer;
+sx.outblock.ptr = outbuffer;
+sx.outblock.cmd_count = 0;
+sx.outblock.authenticating = FALSE;
+
+(void)smtp_write_command(&sx, SCMD_FLUSH, "QUIT\r\n");
+(void)smtp_read_response(&sx, buffer, sizeof(buffer), '2', ob->command_timeout);
+(void)close(cctx.sock);
 }
 
 
 }
 
 
@@ -2619,22 +4451,26 @@ prepare_addresses(address_item *addrlist, host_item *host)
 {
 address_item *first_addr = NULL;
 address_item *addr;
 {
 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;
 }
 
 return first_addr;
 }
 
@@ -2656,7 +4492,7 @@ smtp_transport_entry(
   address_item *addrlist)          /* addresses we are working on */
 {
 int cutoff_retry;
   address_item *addrlist)          /* addresses we are working on */
 {
 int cutoff_retry;
-int port;
+int defport;
 int hosts_defer = 0;
 int hosts_fail  = 0;
 int hosts_looked_up = 0;
 int hosts_defer = 0;
 int hosts_fail  = 0;
 int hosts_looked_up = 0;
@@ -2666,22 +4502,28 @@ int hosts_total = 0;
 int total_hosts_tried = 0;
 address_item *addr;
 BOOL expired = TRUE;
 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);
 uschar *expanded_hosts = NULL;
 uschar *pistring;
 uschar *tid = string_sprintf("%s transport", tblock->name);
-smtp_transport_options_block *ob =
-  (smtp_transport_options_block *)(tblock->options_block);
+smtp_transport_options_block *ob = SOB tblock->options_block;
 host_item *hostlist = addrlist->host_list;
 host_item *hostlist = addrlist->host_list;
-host_item *host = NULL;
+host_item *host;
 
 DEBUG(D_transport)
   {
   debug_printf("%s transport entered\n", tblock->name);
 
 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);
     debug_printf("  %s\n", addr->address);
-  if (continuing) debug_printf("already connected to %s [%s]\n",
-      continue_hostname, continue_host_address);
+  if (hostlist)
+    {
+    debug_printf("hostlist:\n");
+    for (host = hostlist; host; host = host->next)
+      debug_printf("  '%s' IP %s port %d\n", host->name, host->address, host->port);
+    }
+  if (continue_hostname)
+    debug_printf("already connected to %s [%s] (on fd %d)\n",
+      continue_hostname, continue_host_address,
+      cutthrough.cctx.sock >= 0 ? cutthrough.cctx.sock : 0);
   }
 
 /* Set the flag requesting that these hosts be added to the waiting
   }
 
 /* Set the flag requesting that these hosts be added to the waiting
@@ -2696,9 +4538,15 @@ 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. */
 
 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 defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+if (tls_requiretls & REQUIRETLS_MSG)
+  ob->tls_tempfail_tryclear = FALSE;   /*XXX surely we should have a local for this
+                                       rather than modifying the transport? */
+#endif
+
+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);
     {
     addrlist->message = string_sprintf("%s transport called with no hosts set",
       tblock->name);
@@ -2717,18 +4565,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. */
 
   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)
       {
     {
     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->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 = f.search_find_defer ? DEFER : PANIC;
         return FALSE;     /* Only top address has status */
         }
       DEBUG(D_transport) debug_printf("expanded list of hosts \"%s\" to "
         return FALSE;     /* Only top address has status */
         }
       DEBUG(D_transport) debug_printf("expanded list of hosts \"%s\" to "
@@ -2741,7 +4588,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. */
     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);
       {
       addrlist->message =
         string_sprintf("%s transport has empty hosts setting", tblock->name);
@@ -2752,13 +4599,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 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. */
 
     }
 
   /* 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
   }
 
 /* The host list was supplied with the address. If hosts_randomize is set, we
@@ -2766,17 +4614,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). */
 
 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;
   {
   host_item *newlist = NULL;
-  while (hostlist != NULL)
+  while (hostlist)
     {
     host_item *h = hostlist;
     hostlist = hostlist->next;
 
     h->sort_key = random_number(100);
 
     {
     host_item *h = hostlist;
     hostlist = hostlist->next;
 
     h->sort_key = random_number(100);
 
-    if (newlist == NULL)
+    if (!newlist)
       {
       h->next = NULL;
       newlist = h;
       {
       h->next = NULL;
       newlist = h;
@@ -2789,7 +4637,7 @@ else if (ob->hosts_randomize && hostlist->mx == MX_NONE && !continuing)
     else
       {
       host_item *hh = newlist;
     else
       {
       host_item *hh = newlist;
-      while (hh->next != NULL)
+      while (hh->next)
         {
         if (h->sort_key < hh->next->sort_key) break;
         hh = hh->next;
         {
         if (h->sort_key < hh->next->sort_key) break;
         hh = hh->next;
@@ -2802,11 +4650,9 @@ else if (ob->hosts_randomize && hostlist->mx == MX_NONE && !continuing)
   hostlist = addrlist->host_list = newlist;
   }
 
   hostlist = addrlist->host_list = newlist;
   }
 
-
 /* Sort out the default port.  */
 
 /* Sort out the default port.  */
 
-if (!smtp_get_port(ob->port, addrlist, &port, tid)) return FALSE;
-
+if (!smtp_get_port(ob->port, addrlist, &defport, tid)) return FALSE;
 
 /* For each host-plus-IP-address on the list:
 
 
 /* For each host-plus-IP-address on the list:
 
@@ -2853,26 +4699,26 @@ 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. */
 
 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;
      cutoff_retry++)
   {
   host_item *nexthost = NULL;
   int unexpired_hosts_tried = 0;
+  BOOL continue_host_tried = FALSE;
 
 
+retry_non_continued:
   for (host = hostlist;
   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;
        host = nexthost)
     {
     int rc;
     int host_af;
     uschar *rs;
-    BOOL serialized = FALSE;
     BOOL host_is_expired = FALSE;
     BOOL message_defer = 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;
     BOOL some_deferred = FALSE;
     address_item *first_addr = NULL;
     uschar *interface = NULL;
@@ -2900,11 +4746,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. */
 
     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;
       {
       int new_port, flags;
       host_item *hh;
-      uschar *canonical_name;
 
       if (host->status >= hstatus_unusable)
         {
 
       if (host->status >= hstatus_unusable)
         {
@@ -2927,16 +4772,16 @@ for (cutoff_retry = 0; expired &&
       /* Find by name if so configured, or if it's an IP address. We don't
       just copy the IP address, because we need the test-for-local to happen. */
 
       /* Find by name if so configured, or if it's an IP address. We don't
       just copy the IP address, because we need the test-for-local to happen. */
 
-      flags = HOST_FIND_BY_A;
+      flags = HOST_FIND_BY_A | HOST_FIND_BY_AAAA;
       if (ob->dns_qualify_single) flags |= HOST_FIND_QUALIFY_SINGLE;
       if (ob->dns_search_parents) flags |= HOST_FIND_SEARCH_PARENTS;
 
       if (ob->gethostbyname || string_is_ip_address(host->name, NULL) != 0)
       if (ob->dns_qualify_single) flags |= HOST_FIND_QUALIFY_SINGLE;
       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,
       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. */
 
       /* Update the host (and any additional blocks, resulting from
       multihoming) with a host-specific port, if any. */
@@ -2951,7 +4796,7 @@ for (cutoff_retry = 0; expired &&
       commonly points to a configuration error, but the best action is still
       to carry on for the next host. */
 
       commonly points to a configuration error, but the best action is still
       to carry on for the next host. */
 
-      if (rc == HOST_FIND_AGAIN || rc == HOST_FIND_FAILED)
+      if (rc == HOST_FIND_AGAIN || rc == HOST_FIND_SECURITY || rc == HOST_FIND_FAILED)
         {
         retry_add_item(addrlist, string_sprintf("R:%s", host->name), 0);
         expired = FALSE;
         {
         retry_add_item(addrlist, string_sprintf("R:%s", host->name), 0);
         expired = FALSE;
@@ -2960,12 +4805,15 @@ for (cutoff_retry = 0; expired &&
           "HOST_FIND_AGAIN" : "HOST_FIND_FAILED", host->name);
         host->status = hstatus_unusable;
 
           "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;
           {
           if (addr->transport_return != DEFER) continue;
           addr->basic_errno = ERRNO_UNKNOWNHOST;
-          addr->message =
-            string_sprintf("failed to lookup IP address for %s", host->name);
+          addr->message = string_sprintf(
+           rc == HOST_FIND_SECURITY
+             ? "lookup of IP address for %s was insecure"
+             : "failed to lookup IP address for %s",
+           host->name);
           }
         continue;
         }
           }
         continue;
         }
@@ -2976,7 +4824,7 @@ for (cutoff_retry = 0; expired &&
 
       if (rc == HOST_FOUND_LOCAL && !ob->allow_localhost)
         {
 
       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 "
           {
           addr->basic_errno = 0;
           addr->message = string_sprintf("%s transport found host %s to be "
@@ -2992,12 +4840,16 @@ for (cutoff_retry = 0; expired &&
     result of the lookup. Set expired FALSE, to save the outer loop executing
     twice. */
 
     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))
-      {
-      expired = FALSE;
-      continue;      /* With next host */
-      }
+    if (continue_hostname)
+      if (  Ustrcmp(continue_hostname, host->name) != 0
+         || Ustrcmp(continue_host_address, host->address) != 0
+        )
+       {
+       expired = FALSE;
+       continue;      /* With next host */
+       }
+      else
+       continue_host_tried = TRUE;
 
     /* Reset the default next host in case a multihomed host whose addresses
     are not looked up till just above added to the host list. */
 
     /* Reset the default next host in case a multihomed host whose addresses
     are not looked up till just above added to the host list. */
@@ -3011,16 +4863,15 @@ for (cutoff_retry = 0; expired &&
     were not in it. We don't want to hold up all SMTP deliveries! Except when
     doing a two-stage queue run, don't do this if forcing. */
 
     were not in it. We don't want to hold up all SMTP deliveries! Except when
     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,
+    if ((!f.deliver_force || f.queue_2stage) && (f.queue_smtp ||
+        match_isinlist(addrlist->domain,
+         (const uschar **)&queue_smtp_domains, 0,
           &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL) == OK))
       {
       expired = FALSE;
           &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 */
       }
 
       continue;      /* With next host */
       }
 
@@ -3043,20 +4894,23 @@ for (cutoff_retry = 0; expired &&
     the standard SMTP port. A host may have its own port setting that overrides
     the default. */
 
     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
+      ? defport : host->port);
     if (Ustrcmp(pistring, ":25") == 0) pistring = US"";
 
     /* Select IPv4 or IPv6, and choose an outgoing interface. If the interface
     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
 
     /* 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,20 +4918,26 @@ for (cutoff_retry = 0; expired &&
 
     if (cutoff_retry == 0)
       {
 
     if (cutoff_retry == 0)
       {
+      BOOL incl_ip;
       /* Ensure the status of the address is set by checking retry data if
       /* 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. */
 
       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,
       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,
-        (host->status == hstatus_usable)? "usable" :
-        (host->status == hstatus_unusable)? "unusable" :
-        (host->status == hstatus_unusable_expired)? "unusable (expired)" : "?");
+      DEBUG(D_transport) debug_printf("%s [%s]%s retry-status = %s\n", host->name,
+        host->address ? host->address : US"", pistring,
+        host->status == hstatus_usable ? "usable"
+        : host->status == hstatus_unusable ? "unusable"
+        : host->status == hstatus_unusable_expired ? "unusable (expired)" : "?");
 
       /* Skip this address if not usable at this time, noting if it wasn't
       actually expired, both locally and in the address. */
 
       /* Skip this address if not usable at this time, noting if it wasn't
       actually expired, both locally and in the address. */
@@ -3085,24 +4945,25 @@ for (cutoff_retry = 0; expired &&
       switch (host->status)
         {
         case hstatus_unusable:
       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:
 
         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_insecure:
+           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 +4972,11 @@ for (cutoff_retry = 0; expired &&
 
     else
       {
 
     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.tv_sec)
         continue;
         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;
       }
           host->name, host->address, pistring);
       host_is_expired = TRUE;
       }
@@ -3133,12 +4993,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. */
 
     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(CUSS &ob->serialize_hosts, host) == OK)
       {
       serialize_key = string_sprintf("host-serialize-%s", host->name);
       {
       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 "
         {
         DEBUG(D_transport)
           debug_printf("skipping host %s because another Exim process "
@@ -3146,7 +5005,6 @@ for (cutoff_retry = 0; expired &&
         hosts_serial++;
         continue;
         }
         hosts_serial++;
         continue;
         }
-      serialized = TRUE;
       }
 
     /* OK, we have an IP address that is not waiting for its retry time to
       }
 
     /* OK, we have an IP address that is not waiting for its retry time to
@@ -3160,20 +5018,20 @@ 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,
 
     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)? "" : ", ...");
+    set_process_info("delivering %s to %s [%s]%s (%s%s)",
+      message_id, host->name, host->address, pistring, addrlist->address,
+      addrlist->next ? ", ..." : "");
 
     /* This is not for real; don't do the delivery. If there are
     any remaining hosts, list them. */
 
 
     /* This is not for real; don't do the delivery. If there are
     any remaining hosts, list them. */
 
-    if (dont_deliver)
+    if (f.dont_deliver)
       {
       host_item *host2;
       {
       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 = '*';
         {
         addr->host_used = host;
         addr->special_action = '*';
@@ -3183,9 +5041,9 @@ for (cutoff_retry = 0; expired &&
         {
         debug_printf("*** delivery by %s transport bypassed by -N option\n"
                      "*** host and remaining hosts:\n", tblock->name);
         {
         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,
           debug_printf("    %s [%s]\n", host2->name,
-            (host2->address == NULL)? US"unset" : host2->address);
+            host2->address ? host2->address : US"unset");
         }
       rc = OK;
       }
         }
       rc = OK;
       }
@@ -3205,27 +5063,40 @@ for (cutoff_retry = 0; expired &&
 
     else
       {
 
     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");
       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++;
         }
 
       /* 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, defport, interface, tblock,
+        &message_defer, FALSE);
 
       /* Yield is one of:
          OK     => connection made, each address contains its result;
 
       /* Yield is one of:
          OK     => connection made, each address contains its result;
@@ -3242,14 +5113,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. */
 
       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)
-        write_logs(first_addr, host);
+      if (rc == DEFER && first_addr->basic_errno != ERRNO_AUTHFAIL
+                     && first_addr->basic_errno != ERRNO_TLSFAILURE)
+        write_logs(host, first_addr->message, first_addr->basic_errno);
 
 
-      #ifdef EXPERIMENTAL_TPDA
+#ifndef DISABLE_EVENT
       if (rc == DEFER)
       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
 
       /* 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 +5131,43 @@ for (cutoff_retry = 0; expired &&
       session, so the in-clear transmission after those errors, if permitted,
       happens inside smtp_deliver().] */
 
       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(CUSS &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);
+        log_write(0, LOG_MAIN,
+         "%s: delivering unencrypted to H=%s [%s] (not in hosts_require_tls)",
+         first_addr->message, host->name, host->address);
         first_addr = prepare_addresses(addrlist, host);
         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, defport, interface, tblock,
+          &message_defer, TRUE);
         if (rc == DEFER && first_addr->basic_errno != ERRNO_AUTHFAIL)
         if (rc == DEFER && first_addr->basic_errno != ERRNO_AUTHFAIL)
-          write_logs(first_addr, host);
-        #ifdef EXPERIMENTAL_TPDA
+          write_logs(host, first_addr->message, first_addr->basic_errno);
+# ifndef DISABLE_EVENT
         if (rc == DEFER)
         if (rc == DEFER)
-          tpda_deferred(ob, first_addr, host);
-        #endif
+          deferred_event_raise(first_addr, host);
+endif
         }
         }
-      #endif
+#endif /*SUPPORT_TLS*/
       }
 
     /* Delivery attempt finished */
 
       }
 
     /* 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);
+    set_process_info("delivering %s: just tried %s [%s]%s for %s%s: result %s",
+      message_id, host->name, host->address, pistring, addrlist->address,
+      addrlist->next ? " (& others)" : "", rs);
 
     /* Release serialization if set up */
 
 
     /* 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
 
     /* 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 +5177,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.) */
 
     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
         }
 
       /* If a delivery of another message over an existing SMTP connection
@@ -3322,7 +5203,7 @@ for (cutoff_retry = 0; expired &&
       host is genuinely down, another non-continued message delivery will
       notice it soon enough. */
 
       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
         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 +5211,8 @@ for (cutoff_retry = 0; expired &&
 
       if (rc == DEFER)
         {
 
       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;
         }
       }
         host->why = hwhy_deferred;
         }
       }
@@ -3344,15 +5225,21 @@ for (cutoff_retry = 0; expired &&
     reasonable. Also, stop the message from being remembered as waiting
     for specific hosts. */
 
     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);
         }
       retry_add_item(addrlist, retry_message_key,
         rf_message | rf_host | delete_flag);
@@ -3364,16 +5251,12 @@ for (cutoff_retry = 0; expired &&
     case, see if any of them are deferred. */
 
     if (rc == OK)
     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 (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
 
     /* 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 +5273,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. */
 
     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
 
     /* 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,18 +5283,18 @@ 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. */
 
     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);
 
       {
       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;
         {
         retry_rule *last_rule;
         for (last_rule = retry->rules;
-             last_rule->next != NULL;
+             last_rule->next;
              last_rule = last_rule->next);
              last_rule = last_rule->next);
-        timedout = time(NULL) - received_time > last_rule->timeout;
+        timedout = time(NULL) - received_time.tv_sec > last_rule->timeout;
         }
       else timedout = TRUE;    /* No rule => timed out */
 
         }
       else timedout = TRUE;    /* No rule => timed out */
 
@@ -3422,8 +5305,52 @@ for (cutoff_retry = 0; expired &&
           "hosts_max_try (message older than host's retry time)\n");
         }
       }
           "hosts_max_try (message older than host's retry time)\n");
         }
       }
+
+    DEBUG(D_transport)
+      {
+      if (unexpired_hosts_tried >= ob->hosts_max_try)
+       debug_printf("reached transport hosts_max_try limit %d\n",
+         ob->hosts_max_try);
+      if (total_hosts_tried >= ob->hosts_max_try_hardlimit)
+       debug_printf("reached transport hosts_max_try_hardlimit limit %d\n",
+         ob->hosts_max_try_hardlimit);
+      }
+
+    if (f.running_in_test_harness) millisleep(500); /* let server debug out */
     }   /* End of loop for trying multiple hosts. */
 
     }   /* End of loop for trying multiple hosts. */
 
+  /* If we failed to find a matching host in the list, for an already-open
+  connection, just close it and start over with the list.  This can happen
+  for routing that changes from run to run, or big multi-IP sites with
+  round-robin DNS. */
+
+  if (continue_hostname && !continue_host_tried)
+    {
+    int fd = cutthrough.cctx.sock >= 0 ? cutthrough.cctx.sock : 0;
+
+    DEBUG(D_transport) debug_printf("no hosts match already-open connection\n");
+#ifdef SUPPORT_TLS
+    /* A TLS conn could be open for a cutthrough, but not for a plain continued-
+    transport */
+/*XXX doublecheck that! */
+
+    if (cutthrough.cctx.sock >= 0 && cutthrough.is_tls)
+      {
+      (void) tls_write(cutthrough.cctx.tls_ctx, US"QUIT\r\n", 6, FALSE);
+      tls_close(cutthrough.cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT);
+      cutthrough.cctx.tls_ctx = NULL;
+      cutthrough.is_tls = FALSE;
+      }
+    else
+#else
+      (void) write(fd, US"QUIT\r\n", 6);
+#endif
+    (void) close(fd);
+    cutthrough.cctx.sock = -1;
+    continue_hostname = NULL;
+    goto retry_non_continued;
+    }
+
   /* This is the end of the loop that repeats iff expired is TRUE and
   ob->delay_after_cutoff is FALSE. The second time round we will
   try those hosts that haven't been tried since the message arrived. */
   /* This is the end of the loop that repeats iff expired is TRUE and
   ob->delay_after_cutoff is FALSE. The second time round we will
   try those hosts that haven't been tried since the message arrived. */
@@ -3443,7 +5370,7 @@ specific failures. Force the delivery status for all addresses to FAIL. */
 
 if (mua_wrapper)
   {
 
 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;
   }
     addr->transport_return = FAIL;
   goto END_TRANSPORT;
   }
@@ -3460,7 +5387,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. */
 
 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
   {
   /* 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 +5396,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. */
 
   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)
     if (total_hosts_tried >= ob->hosts_max_try_hardlimit)
       {
       DEBUG(D_transport)
@@ -3483,53 +5409,57 @@ 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);
       }
         debug_printf("hosts_max_try limit caused some hosts to be skipped\n");
       setflag(addr, af_retry_skipped);
       }
-    }
 
 
-  if (queue_smtp)    /* no deliveries attempted */
+  if (f.queue_smtp)    /* no deliveries attempted */
     {
     addr->transport_return = DEFER;
     addr->basic_errno = 0;
     addr->message = US"SMTP delivery explicitly queued";
     }
 
     {
     addr->transport_return = DEFER;
     addr->basic_errno = 0;
     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;
     {
     addr->basic_errno = ERRNO_HRETRY;
-    if (continue_hostname != NULL)
-      {
+    if (continue_hostname)
       addr->message = US"no host found for existing SMTP connection";
       addr->message = US"no host found for existing SMTP connection";
-      }
     else if (expired)
       {
       setflag(addr, af_pass_message);   /* This is not a security risk */
     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 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
       {
         addr->transport_return = FAIL;
       }
     else
       {
+      const char * s;
       if (hosts_retry == hosts_total)
       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)
       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)
       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)
       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)
       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"");
       }
     }
   }
       }
     }
   }
@@ -3551,6 +5481,7 @@ DEBUG(D_transport) debug_printf("Leaving %s transport\n", tblock->name);
 return TRUE;   /* Each address has its status */
 }
 
 return TRUE;   /* Each address has its status */
 }
 
+#endif /*!MACRO_PREDEF*/
 /* vi: aw ai sw=2
 */
 /* End of transport/smtp.c */
 /* vi: aw ai sw=2
 */
 /* End of transport/smtp.c */
index dd41e1f..57f87a2 100644 (file)
@@ -2,9 +2,16 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* 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 {
 /* Private structure for the private options and other private data. */
 
 typedef struct {
@@ -21,6 +28,13 @@ typedef struct {
   uschar *serialize_hosts;
   uschar *hosts_try_auth;
   uschar *hosts_require_auth;
   uschar *serialize_hosts;
   uschar *hosts_try_auth;
   uschar *hosts_require_auth;
+  uschar *hosts_try_chunking;
+#ifdef SUPPORT_DANE
+  uschar *hosts_try_dane;
+  uschar *hosts_require_dane;
+  uschar *dane_require_tls_ciphers;
+#endif
+  uschar *hosts_try_fastopen;
 #ifndef DISABLE_PRDR
   uschar *hosts_try_prdr;
 #endif
 #ifndef DISABLE_PRDR
   uschar *hosts_try_prdr;
 #endif
@@ -32,8 +46,14 @@ typedef struct {
   uschar *hosts_avoid_tls;
   uschar *hosts_verify_avoid_tls;
   uschar *hosts_avoid_pipelining;
   uschar *hosts_avoid_tls;
   uschar *hosts_verify_avoid_tls;
   uschar *hosts_avoid_pipelining;
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+  uschar *hosts_pipe_connect;
+#endif
   uschar *hosts_avoid_esmtp;
   uschar *hosts_avoid_esmtp;
+#ifdef SUPPORT_TLS
   uschar *hosts_nopass_tls;
   uschar *hosts_nopass_tls;
+  uschar *hosts_noproxy_tls;
+#endif
   int     command_timeout;
   int     connect_timeout;
   int     data_timeout;
   int     command_timeout;
   int     connect_timeout;
   int     data_timeout;
@@ -47,45 +67,118 @@ typedef struct {
   BOOL    gethostbyname;
   BOOL    dns_qualify_single;
   BOOL    dns_search_parents;
   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;
   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;
   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;
 #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;
   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;
   uschar *tls_verify_cert_hostnames;
-# endif
+#endif
+#ifdef SUPPORT_I18N
+  uschar *utf8_downconvert;
 #endif
 #ifndef DISABLE_DKIM
 #endif
 #ifndef DISABLE_DKIM
-  uschar *dkim_domain;
-  uschar *dkim_private_key;
-  uschar *dkim_selector;
-  uschar *dkim_canon;
-  uschar *dkim_sign_headers;
-  uschar *dkim_strict;
+  struct ob_dkim dkim;
 #endif
 #endif
-#ifdef EXPERIMENTAL_TPDA
-  uschar *tpda_host_defer_action;
+#ifdef EXPERIMENTAL_ARC
+  uschar *arc_sign;
 #endif
 } smtp_transport_options_block;
 
 #endif
 } smtp_transport_options_block;
 
+#define SOB (smtp_transport_options_block *)
+
+
+/* smtp connect context */
+typedef struct {
+  uschar *             from_addr;
+  address_item *       addrlist;
+
+  smtp_connect_args    conn_args;
+  int                  port;
+
+  BOOL verify:1;
+  BOOL lmtp:1;
+  BOOL smtps:1;
+  BOOL ok:1;
+  BOOL setting_up:1;
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+  BOOL early_pipe_ok:1;
+  BOOL early_pipe_active:1;
+#endif
+  BOOL esmtp:1;
+  BOOL esmtp_sent:1;
+  BOOL pipelining_used: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(SUPPORT_DANE)
+  BOOL dane:1;
+  BOOL dane_required:1;
+#endif
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+  BOOL pending_BANNER:1;
+  BOOL pending_EHLO: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;
+
+  unsigned     peer_offered;
+  unsigned     avoid_option;
+  uschar *     igquotstr;
+  uschar *     helo_data;
+#ifdef EXPERIMENTAL_DSN_INFO
+  uschar *     smtp_greeting;
+  uschar *     helo_response;
+#endif
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+  ehlo_resp_precis     ehlo_resp;
+#endif
+
+  address_item *       first_addr;
+  address_item *       next_addr;
+  address_item *       sync_addr;
+
+  client_conn_ctx      cctx;
+  smtp_inblock         inblock;
+  smtp_outblock                outblock;
+  uschar       buffer[DELIVER_BUFFER_SIZE];
+  uschar       inbuffer[4096];
+  uschar       outbuffer[4096];
+} smtp_context;
+
+extern int smtp_setup_conn(smtp_context *, BOOL);
+extern int smtp_write_mail_and_rcpt_cmds(smtp_context *, int *);
+extern int smtp_reap_early_pipe(smtp_context *, int *);
+
+
 /* Data for reading the private options. */
 
 extern optionlist smtp_transport_options[];
 /* Data for reading the private options. */
 
 extern optionlist smtp_transport_options[];
@@ -103,10 +196,12 @@ extern void smtp_transport_closedown(transport_instance *);
 
 
 
 
 
 
-extern int     smtp_auth(uschar *, unsigned, address_item *, host_item *,
-                smtp_transport_options_block *, BOOL,
-                smtp_inblock *, smtp_outblock *);
 extern BOOL    smtp_mail_auth_str(uschar *, unsigned,
                 address_item *, smtp_transport_options_block *);
 
 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 */
 /* 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..7d3a462
--- /dev/null
@@ -0,0 +1,421 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) Jeremy Harris 2015 - 2018 */
+/* 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)
+{
+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(CCS opt + 5);
+else if (Ustrncmp(opt, "tmo=", 4) == 0)
+  sob->timeout = atoi(CCS opt + 4);
+else if (Ustrncmp(opt, "pri=", 4) == 0)
+  sob->priority = atoi(CCS opt + 4);
+else if (Ustrncmp(opt, "weight=", 7) == 0)
+  sob->weight = atoi(CCS 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)
+      return FAIL;
+#ifdef TCP_QUICKACK
+    (void) setsockopt(fd, IPPROTO_TCP, TCP_QUICKACK, US &off, sizeof(off));
+#endif
+    if (!fd_ready(fd, tmo-time(NULL)) || read(fd, s, 2) != 2)
+      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;
+blob early_data;
+
+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);
+  }
+
+/* Set up the socks protocol method-selection message,
+for sending on connection */
+
+state = US"method select";
+buf[0] = 5; buf[1] = 1; buf[2] = sob->auth_type;
+early_data.data = buf;
+early_data.len = 3;
+
+/* 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 = proxy.name = sob->proxy_host;
+  proxy_af = Ustrchr(sob->proxy_host, ':') ? AF_INET6 : AF_INET;
+
+  /*XXX we trust that the method-select command is idempotent */
+  if ((fd = smtp_sock_connect(&proxy, proxy_af, sob->port,
+             interface, tb, sob->timeout, &early_data)) >= 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 */
+
+HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SOCKS>> 05 01 %02x\n", sob->auth_type);
+
+/* expect method response */
+
+#ifdef TCP_QUICKACK
+(void) setsockopt(fd, IPPROTO_TCP, TCP_QUICKACK, US &off, sizeof(off));
+#endif
+
+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..4caf0cd 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2012 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions in support of the use of maildirsize files for handling quotas in
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions in support of the use of maildirsize files for handling quotas in
@@ -211,10 +211,12 @@ int len;
 uschar buffer[256];
 sprintf(CS buffer, "%d 1\n", size);
 len = Ustrlen(buffer);
 uschar buffer[256];
 sprintf(CS buffer, "%d 1\n", size);
 len = Ustrlen(buffer);
-(void)lseek(fd, 0, SEEK_END);
-len = write(fd, buffer, len);
-DEBUG(D_transport)
-  debug_printf("added '%.*s' to maildirsize file\n", len-1, buffer);
+if (lseek(fd, 0, SEEK_END) >= 0)
+  {
+  len = write(fd, buffer, len);
+  DEBUG(D_transport)
+    debug_printf("added '%.*s' to maildirsize file\n", len-1, buffer);
+  }
 }
 
 
 }
 
 
@@ -356,7 +358,7 @@ Or, at least, it is supposed to!
 
 Arguments:
   path             the path to the maildir directory; this is already backed-up
 
 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
   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 +556,8 @@ else
     FALSE);
 
   (void)gettimeofday(&tv, NULL);
     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)
 
   fd = Uopen(tempname, O_RDWR|O_CREAT|O_EXCL, ob->mode ? ob->mode : 0600);
   if (fd >= 0)
index 2a9d90a..3b6c360 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     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
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for maintaining binary balanced trees and some associated
@@ -328,13 +328,13 @@ Returns:    pointer to node, or NULL if not found
 */
 
 tree_node *
 */
 
 tree_node *
-tree_search(tree_node *p, uschar *name)
+tree_search(tree_node *p, const uschar *name)
 {
 {
-while (p != NULL)
+while (p)
   {
   int c = Ustrcmp(name, p->name);
   if (c == 0) return p;
   {
   int c = Ustrcmp(name, p->name);
   if (c == 0) return p;
-  p = (c < 0)? p->left : p->right;
+  p = c < 0 ? p->left : p->right;
   }
 return NULL;
 }
   }
 return NULL;
 }
@@ -355,10 +355,10 @@ Arguments:
 void
 tree_walk(tree_node *p, void (*f)(uschar*, uschar*, void*), void *ctx)
 {
 void
 tree_walk(tree_node *p, void (*f)(uschar*, uschar*, void*), void *ctx)
 {
-if (p == NULL) return;
+if (!p) return;
 f(p->name, p->data.ptr, ctx);
 f(p->name, p->data.ptr, ctx);
-if (p->left != NULL) tree_walk(p->left, f, ctx);
-if (p->right != NULL) tree_walk(p->right, f, ctx);
+tree_walk(p->left, f, ctx);
+tree_walk(p->right, f, ctx);
 }
 
 
 }
 
 
diff --git a/src/utf8.c b/src/utf8.c
new file mode 100644 (file)
index 0000000..16a5303
--- /dev/null
@@ -0,0 +1,273 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) Jeremy Harris 2015 - 2018 */
+/* 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((const uint8_t *) 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 = '.';
+gstring * g = 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
+    g = string_append_listele(g, '.', label);
+return string_from_gstring(g);
+
+#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.
 
    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.
 
       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
       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.
 
    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.
 
 */
 
 
 /* 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
    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.
    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
       _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
     ||  (defined(PLAT_x86_win32) && defined(__GNUC__))
 
 typedef
-   struct { 
+   struct {
       unsigned int nraddr; /* where's the code? */
    }
    OrigFn;
       unsigned int nraddr; /* where's the code? */
    }
    OrigFn;
@@ -262,7 +262,7 @@ typedef
 #if defined(PLAT_x86_win32) && !defined(__GNUC__)
 
 typedef
 #if defined(PLAT_x86_win32) && !defined(__GNUC__)
 
 typedef
-   struct { 
+   struct {
       unsigned int nraddr; /* where's the code? */
    }
    OrigFn;
       unsigned int nraddr; /* where's the code? */
    }
    OrigFn;
@@ -317,7 +317,7 @@ typedef
 #if defined(PLAT_amd64_linux)  ||  defined(PLAT_amd64_darwin)
 
 typedef
 #if defined(PLAT_amd64_linux)  ||  defined(PLAT_amd64_darwin)
 
 typedef
-   struct { 
+   struct {
       unsigned long long int nraddr; /* where's the code? */
    }
    OrigFn;
       unsigned long long int nraddr; /* where's the code? */
    }
    OrigFn;
@@ -371,7 +371,7 @@ typedef
 #if defined(PLAT_ppc32_linux)
 
 typedef
 #if defined(PLAT_ppc32_linux)
 
 typedef
-   struct { 
+   struct {
       unsigned int nraddr; /* where's the code? */
    }
    OrigFn;
       unsigned int nraddr; /* where's the code? */
    }
    OrigFn;
@@ -431,7 +431,7 @@ typedef
 #if defined(PLAT_ppc64_linux)
 
 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? */
    }
       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
 #if defined(PLAT_arm_linux)
 
 typedef
-   struct { 
+   struct {
       unsigned int nraddr; /* where's the code? */
    }
    OrigFn;
       unsigned int nraddr; /* where's the code? */
    }
    OrigFn;
@@ -556,7 +556,7 @@ typedef
 #if defined(PLAT_ppc32_aix5)
 
 typedef
 #if defined(PLAT_ppc32_aix5)
 
 typedef
-   struct { 
+   struct {
       unsigned int nraddr; /* where's the code? */
       unsigned int r2;  /* what tocptr do we need? */
    }
       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
 #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? */
    }
       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
 /* 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.
    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"
 
    "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)                                   \
    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))
 
 #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. */
    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,
    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);
                               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,
    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);
                               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
 
 
 /* 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:
 
    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.
    - 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
    '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 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;
    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.
 
    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)    \
    Ignored if addr == 0.
 */
 #define VALGRIND_MALLOCLIKE_BLOCK(addr, sizeB, rzB, is_zeroed)    \
index b1b9f29..236a87c 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions concerned with verifying things. The original code for callout
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions concerned with verifying things. The original code for callout
@@ -14,14 +14,14 @@ 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 */
 
 #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;
+static smtp_context ctctx;
 uschar ctbuffer[8192];
 
 
 /* Structure for caching DNSBL lookups */
 
 typedef struct dnsbl_cache_block {
 uschar ctbuffer[8192];
 
 
 /* Structure for caching DNSBL lookups */
 
 typedef struct dnsbl_cache_block {
+  time_t expiry;
   dns_address *rhs;
   uschar *text;
   int rc;
   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
 
 #define MT_NOT 1
 #define MT_ALL 2
 
+static uschar cutthrough_response(client_conn_ctx *, char, uschar **, int);
+
 
 
 /*************************************************
 
 
 /*************************************************
@@ -58,7 +60,7 @@ Returns:            the cache record if a non-expired one exists, else NULL
 */
 
 static dbdata_callout_cache *
 */
 
 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;
   int positive_expire, int negative_expire)
 {
 BOOL negative;
@@ -66,11 +68,9 @@ int length, expire;
 time_t now;
 dbdata_callout_cache *cache_record;
 
 time_t now;
 dbdata_callout_cache *cache_record;
 
-cache_record = dbfn_read_with_length(dbm_file, key, &length);
-
-if (cache_record == NULL)
+if (!(cache_record = dbfn_read_with_length(dbm_file, key, &length)))
   {
   {
-  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;
   }
 
   return NULL;
   }
 
@@ -84,7 +84,7 @@ now = time(NULL);
 
 if (now - cache_record->time_stamp > expire)
   {
 
 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;
   }
 
   return NULL;
   }
 
@@ -111,134 +111,53 @@ if (type[0] == 'd' && cache_record->result != ccache_reject)
     cache_record->random_result = ccache_unknown;
   }
 
     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;
 }
 
 
 
 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;
 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. */
 
 
 /* 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");
   }
   {
   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");
   }
   {
   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 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
     {
     /* 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 +167,20 @@ if (dbm_file != NULL)
     not to disturb the cached domain value if this whole verification succeeds
     (we don't want it turning into "accept"). */
 
     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)
       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).";
       setflag(addr, af_verify_nsfail);
       addr->user_message = US"(result of an earlier callout reused).";
-      yield = FAIL;
+      *yield = FAIL;
       *failure_ptr = US"mail";
       *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
       }
 
     /* If a previous check on a "random" local part was accepted, we assume
@@ -271,26 +190,29 @@ 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. */
 
     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:
       {
       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");
+       *failure_ptr = US"random";
+       dbfn_close(dbm_file);
+       return TRUE;     /* Default yield is OK */
 
       case ccache_reject:
 
       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:
 
       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,
       }
 
     /* If a postmaster check is requested, but there was a previous failure,
@@ -298,27 +220,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. */
 
     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)
       {
       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)
       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
 
       /* 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 +250,10 @@ if (dbm_file != NULL)
       */
 
       HDEBUG(D_verify) debug_printf("callout cache: domain accepts RCPT "
       */
 
       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,35 +262,306 @@ if (dbm_file != NULL)
   sender address if we are doing a recipient callout with a non-empty sender).
   */
 
   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. */
 
     }
 
   /* Close the cache database while we actually do the callout for real. */
 
-  END_CACHE:
   dbfn_close(dbm_file);
   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, ':') ? AF_INET6 : AF_INET;
+
+      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);
+
+      smtp_port_for_connect(host, port);
+
+      if (  (  interface == cutthrough.interface
+           || (  interface
+              && cutthrough.interface
+              && Ustrcmp(interface, cutthrough.interface) == 0
+           )  )
+        && host->port == cutthrough.host.port
+        )
+       {
+       uschar * resp = NULL;
+
+       /* Match!  Send the RCPT TO, set done from the response */
+       done =
+            smtp_write_command(&ctctx, SCMD_FLUSH, "RCPT TO:<%.1000s>\r\n",
+             transport_rcpt_address(addr,
+                addr->transport->rcpt_include_affixes)) >= 0
+         && cutthrough_response(&cutthrough.cctx, '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(TRUE, US"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(TRUE, US"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
+                     vopt_callout_hold         => lazy close connection
+  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))
+  {
+  cancel_cutthrough_connection(TRUE, US"cache-hit");
+  goto END_CALLOUT;
   }
 
 if (!addr->transport)
   }
 
 if (!addr->transport)
@@ -380,20 +575,18 @@ else
   {
   smtp_transport_options_block *ob =
     (smtp_transport_options_block *)addr->transport->options_block;
   {
   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,
 
   /* 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);
       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. */
 
   /* 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 +601,36 @@ else
   and cause the client to time out. So in this case we forgo the PIPELINING
   optimization. */
 
   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 && !f.disable_callout_flush) mac_smtp_fflush();
 
 
-  /* Now make connections to the hosts and do real callouts. The list of hosts
-  is passed in as an argument. */
+  clearflag(addr, af_verify_pmfail);  /* postmaster callout flag */
+  clearflag(addr, af_verify_nsfail);  /* null sender callout flag */
 
 
-  for (host = host_list; host != NULL && !done; host = host->next)
+/* 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.cctx.sock >= 0
+     && (options & (vopt_callout_recipsender | vopt_callout_recippmaster))
+       == vopt_callout_recipsender
+     && !random_local_part
+     && !pm_mailfrom
+     )
+    done = cutthrough_multi(addr, host_list, tf, &yield);
+
+  /* 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 && !done; host = host->next)
     {
     {
-    smtp_inblock inblock;
-    smtp_outblock outblock;
     int host_af;
     int port = 25;
     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 *interface = NULL;  /* Outgoing interface to use; NULL => any */
-    uschar inbuffer[4096];
-    uschar outbuffer[1024];
-    uschar responsebuffer[4096];
-
-    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. */
+    smtp_context sx;
 
 
-    if (host->address == NULL)
+    if (!host->address)
       {
       DEBUG(D_verify) debug_printf("no IP address for host name %s: skipping\n",
         host->name);
       {
       DEBUG(D_verify) debug_printf("no IP address for host name %s: skipping\n",
         host->name);
@@ -452,7 +647,7 @@ else
 
     /* Set IPv4 or IPv6 */
 
 
     /* 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).
 
     /* 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 +657,304 @@ else
 
     deliver_host = host->name;
     deliver_host_address = host->address;
 
     deliver_host = host->name;
     deliver_host_address = host->address;
+    deliver_host_port = host->port;
     deliver_domain = addr->domain;
     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"))
-      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 */
-
-    outblock.buffer = outbuffer;
-    outblock.buffersize = sizeof(outbuffer);
-    outblock.ptr = outbuffer;
-    outblock.cmd_count = 0;
-    outblock.authenticating = FALSE;
-
-    /* Reset the parameters of a TLS session */
-    tls_out.cipher = tls_out.peerdn = NULL;
-
-    /* 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. */
-
-    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)
-      {
-      addr->message = string_sprintf("could not connect to %s [%s]: %s",
-          host->name, host->address, strerror(errno));
-      deliver_host = deliver_host_address = NULL;
-      deliver_domain = save_deliver_domain;
-      continue;
-      }
-
-    /* Expand the helo_data string to find the host name to use. */
-
-    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;
-      }
-
-    /* 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");
-
-    /* Unless ssl-on-connect, wait for the initial greeting */
-    smtps_redo_greeting:
-
-    #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;
-
-    /* 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");
-
-    tls_redo_helo:
-
-    #ifdef SUPPORT_TLS
-    if (smtps  &&  tls_out.active < 0) /* ssl-on-connect, first pass */
-      {
-      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;
-       }
-      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 /*SUPPORT_TLS*/
-
-    done = TRUE; /* so far so good; have response to HELO */
-
-    /*XXX the EHLO response would be analyzed here for IGNOREQUOTA, SIZE, PIPELINING */
-
-    /* For now, transport_filter by cutthrough-delivery is not supported */
-    /* Need proper integration with the proper transport mechanism. */
-    if (cutthrough_delivery)
-      {
-      if (addr->transport->filter_command)
-        {
-        cutthrough_delivery= FALSE;
-        HDEBUG(D_acl|D_v) debug_printf("Cutthrough cancelled by presence of transport filter\n");
-        }
-      #ifndef DISABLE_DKIM
-      if (ob->dkim_domain)
-        {
-        cutthrough_delivery= FALSE;
-        HDEBUG(D_acl|D_v) debug_printf("Cutthrough cancelled by presence of DKIM signing\n");
-        }
-      #endif
-      }
-
-    SEND_FAILED:
-    RESPONSE_FAILED:
-    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. */
-
-    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;
-        }
-      }
-
-    /* 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),
+    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);
 
 
-    /* Send the MAIL command */
-        (smtp_write_command(&outblock, FALSE, "MAIL FROM:<%s>%s\r\n",
-          from_address, responsebuffer) >= 0)
-      )  &&
+    sx.addrlist = addr;
+    sx.conn_args.host = host;
+    sx.conn_args.host_af = host_af,
+    sx.port = port;
+    sx.conn_args.interface = interface;
+    sx.helo_data = tf->helo_data;
+    sx.conn_args.tblock = addr->transport;
+    sx.verify = TRUE;
 
 
-      smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer),
-        '2', callout);
+tls_retry_connection:
+    /* Set the address state so that errors are recorded in it */
 
 
-    deliver_host = deliver_host_address = NULL;
-    deliver_domain = save_deliver_domain;
+    addr->transport_return = PENDING_DEFER;
+    ob->connect_timeout = callout_connect;
+    ob->command_timeout = callout;
 
 
-    /* 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. */
+    /* 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 */
 
 
-    if (!done)
+    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(CUSS &ob->hosts_require_tls, host) != OK
+       )
       {
       {
-      *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;
-        }
+      log_write(0, LOG_MAIN,
+       "%s: callout unencrypted to %s [%s] (not in hosts_require_tls)",
+       addr->message, host->name, host->address);
+      addr->transport_return = PENDING_DEFER;
+      yield = smtp_setup_conn(&sx, TRUE);
       }
       }
-
-    /* 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
+#endif
+    if (yield != OK)
       {
       {
-      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 */
+      errno = addr->basic_errno;
+      transport_name = NULL;
+      deliver_host = deliver_host_address = NULL;
+      deliver_domain = save_deliver_domain;
 
 
-        new_domain_record.random_stamp = time(NULL);
+      /* Failure to accept HELO is cached; this blocks the whole domain for all
+      senders. I/O errors and defer responses are not cached. */
 
 
-        /* If accepted, we aren't going to do any further tests below. */
+      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;
+      }
 
 
-        if (random_ok)
-          {
-          new_domain_record.random_result = ccache_accept;
-          }
+    /* If we needed to authenticate, smtp_setup_conn() did that.  Copy
+    the AUTH info for logging */
 
 
-        /* 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. */
+    addr->authenticator = client_authenticator;
+    addr->auth_id = client_authenticated_id;
 
 
-        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 */
+    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;
 
 
-      /* 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. */
+    new_domain_record.result = old_domain_cache_result == ccache_reject_mfnull
+      ? ccache_reject_mfnull : ccache_accept;
 
 
-      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 the random local part check first. Temporarily replace the recipient
+    with the "random" value */
 
 
-        /* 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 (random_local_part)
+      {
+      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! */
+       }
+#endif
 
 
-        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");
+      /* This would be ok for 1st rcpt of a cutthrough (the case handled here;
+      subsequents are done in cutthrough_multi()), but no way to
+      handle a subsequent because of the RSET vaporising the MAIL FROM.
+      So refuse to support any.  Most cutthrough use will not involve
+      random_local_part, so no loss. */
+      cancel_cutthrough_connection(TRUE, US"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.
+
+      Avoid using a SIZE option on the MAIL for all random-rcpt checks.
+      */
 
 
-          done =
-            smtp_write_command(&outblock, FALSE, "RSET\r\n") >= 0 &&
-            smtp_read_response(&inblock, responsebuffer,
-              sizeof(responsebuffer), '2', callout) &&
+      sx.avoid_option = OPTION_SIZE;
 
 
-            smtp_write_command(&outblock, FALSE,
-              "MAIL FROM:<%s>\r\n", pm_mailfrom) >= 0 &&
-            smtp_read_response(&inblock, responsebuffer,
-              sizeof(responsebuffer), '2', callout) &&
+      /* Remember when we last did a random test */
+      new_domain_record.random_stamp = time(NULL);
 
 
-            /* First try using the current domain */
+      if (smtp_write_mail_and_rcpt_cmds(&sx, &yield) == 0)
+       switch(addr->transport_return)
+         {
+         case PENDING_OK:      /* random was accepted, unfortunately */
+           new_domain_record.random_result = ccache_accept;
+           yield = OK;         /* Only usable verify result we can return */
+           done = TRUE;
+           *failure_ptr = US"random";
+           goto no_conn;
+         case FAIL:            /* rejected: the preferred result */
+           new_domain_record.random_result = ccache_reject;
+           sx.avoid_option = 0;
+
+           /* 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, SCMD_FLUSH, "RSET\r\n") >= 0 &&
+             smtp_read_response(&sx, 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(sx.cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT);
+#endif
+           HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP(close)>>\n");
+           (void)close(sx.cctx.sock);
+           sx.cctx.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;
+         case DEFER:           /* 4xx response to random */
+           break;              /* Just to be clear. ccache_unknown, !done. */
+         }
 
 
-            ((
-            smtp_write_command(&outblock, FALSE,
-              "RCPT TO:<postmaster@%.1000s>\r\n", addr->domain) >= 0 &&
-            smtp_read_response(&inblock, responsebuffer,
-              sizeof(responsebuffer), '2', callout)
-            )
+      /* 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;
 
 
-            ||
+    /* Main verify.  For rcpt-verify use SIZE if we know it and we're not cacheing;
+    for sndr-verify never use it. */
 
 
-            /* If that doesn't work, and a full check is requested,
-            try without the domain. */
+    if (done)
+      {
+      if (!(options & vopt_is_recipient  &&  options & vopt_callout_no_cache))
+       sx.avoid_option = OPTION_SIZE;
+
+      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 */
+       }
+      }
 
 
-            (
-            (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)
-            ));
+    addr->auth_sndr = client_authenticated_sender;
 
 
-          /* Sort out the cache record */
+    deliver_host = deliver_host_address = NULL;
+    deliver_domain = save_deliver_domain;
 
 
-          new_domain_record.postmaster_stamp = time(NULL);
+    /* 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)
-            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 */
+    if (done && pm_mailfrom)
+      {
+      /* 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(TRUE, US"postmaster verify");
+      HDEBUG(D_acl|D_v) debug_printf_indent("Cutthrough cancelled by presence of postmaster verify\n");
+
+      done = smtp_write_command(&sx, SCMD_FLUSH, "RSET\r\n") >= 0
+          && smtp_read_response(&sx, 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;
+       sx.avoid_option = OPTION_SIZE;
+
+       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, SCMD_FLUSH,
+                           "RCPT TO:<postmaster>\r\n") >= 0
+             && smtp_read_response(&sx, 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
     /* 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 +965,219 @@ else
     don't give the IP address because this may be an internal host whose identity
     is not to be widely broadcast. */
 
     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));
+      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
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+      case ERRNO_REQUIRETLS:
+        addr->user_message = US"530 5.7.4 REQUIRETLS support required";
+       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 rationalise.  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;
+      }
 
 
-        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);
+    /* End the SMTP conversation and close the connection. */
 
 
-        /* Hard rejection ends the process */
+    /* 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.  Ditto for a lazy-close
+    verify. */
 
 
-        if (responsebuffer[0] == '5')   /* Address rejected */
-          {
-          yield = FAIL;
-          done = TRUE;
-          }
+    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.dkim_domain)
+        {
+        cutthrough.delivery= FALSE;
+        HDEBUG(D_acl|D_v) debug_printf("Cutthrough cancelled by presence of DKIM signing\n");
+        }
+#endif
+#ifdef EXPERIMENTAL_ARC
+      if (ob->arc_sign)
+        {
+        cutthrough.delivery= FALSE;
+        HDEBUG(D_acl|D_v) debug_printf("Cutthrough cancelled by presence of ARC signing\n");
         }
         }
+#endif
       }
 
       }
 
-    /* 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
-    here is where we want to leave the conn open */
-    if (  cutthrough_delivery
+    if (  (cutthrough.delivery || options & vopt_callout_hold)
+       && rcpt_count == 1
        && done
        && yield == OK
        && 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
        && !random_local_part
        && !pm_mailfrom
-       && cutthrough_fd < 0
+       && cutthrough.cctx.sock < 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;
-      if (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;
+      address_item * parent, * caddr;
+
+      HDEBUG(D_acl|D_v) debug_printf_indent("holding verify callout open for %s\n",
+       cutthrough.delivery
+       ? "cutthrough delivery" : "potential further verifies and delivery");
+
+      cutthrough.callout_hold_only = !cutthrough.delivery;
+      cutthrough.is_tls =      tls_out.active.sock >= 0;
+      /* We assume no buffer in use in the outblock */
+      cutthrough.cctx =                sx.cctx;
+      cutthrough.nrcpt =       1;
+      cutthrough.transport =   addr->transport->name;
+      cutthrough.interface =   interface;
+      cutthrough.snd_port =    sending_port;
+      cutthrough.peer_options =        smtp_peer_options;
+      cutthrough.host =                *host;
+       {
+       int oldpool = store_pool;
+       store_pool = POOL_PERM;
+       cutthrough.snd_ip = string_copy(sending_ip_address);
+       cutthrough.host.name = string_copy(host->name);
+       cutthrough.host.address = string_copy(host->address);
+       store_pool = oldpool;
+       }
+
+      /* Save the address_item and parent chain for later logging */
+      cutthrough.addr =                *addr;
+      cutthrough.addr.next =   NULL;
+      cutthrough.addr.host_used = &cutthrough.host;
+      for (caddr = &cutthrough.addr, parent = addr->parent;
+          parent;
+          caddr = caddr->parent, parent = parent->parent)
+        *(caddr->parent = store_get(sizeof(address_item))) = *parent;
+
+      ctctx.outblock.buffer = ctbuffer;
+      ctctx.outblock.buffersize = sizeof(ctbuffer);
+      ctctx.outblock.ptr = ctbuffer;
+      /* ctctx.outblock.cmd_count = 0; ctctx.outblock.authenticating = FALSE; */
+      ctctx.outblock.cctx = &cutthrough.cctx;
       }
     else
       {
       }
     else
       {
-      /* Ensure no cutthrough on multiple address verifies */
+      /* Ensure no cutthrough on multiple verifies that were incompatible */
       if (options & vopt_callout_recipsender)
       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(TRUE, US"not usable for cutthrough");
+      if (sx.send_quit)
+       {
+       (void) smtp_write_command(&sx, SCMD_FLUSH, "QUIT\r\n");
+
+       /* Wait a short time for response, and discard it */
+       smtp_read_response(&sx, sx.buffer, sizeof(sx.buffer), '2', 1);
+       }
 
 
-      #ifdef SUPPORT_TLS
-      tls_close(FALSE, TRUE);
-      #endif
-      (void)close(inblock.sock);
+      if (sx.cctx.sock >= 0)
+       {
+#ifdef SUPPORT_TLS
+       if (sx.cctx.tls_ctx)
+         {
+         tls_close(sx.cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT);
+         sx.cctx.tls_ctx = NULL;
+         }
+#endif
+       HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP(close)>>\n");
+       (void)close(sx.cctx.sock);
+       sx.cctx.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.
     }    /* 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);
-    }
-  }
-
-/* If a definite result was obtained for the callout, cache it unless caching
-is disabled. */
+However, there may be domain-specific information to cache in both cases. */
 
 
-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. */
 
 
 /* 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;
 
   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,
       "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 */
 
 
   /* Force a specific error code */
 
@@ -1087,7 +1187,7 @@ else   /* !done */
 /* Come here from within the cache-reading code on fast-track exit. */
 
 END_CALLOUT:
 /* 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;
 }
 
 return yield;
 }
 
@@ -1096,23 +1196,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.
 */
 /* Called after recipient-acl to get a cutthrough connection open when
    one was requested and a recipient-verify wasn't subsequently done.
 */
-void
-open_cutthrough_connection( address_item * addr )
+int
+open_cutthrough_connection(address_item * addr)
 {
 address_item addr2;
 {
 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;
 
 /* 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);
        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,22 +1225,24 @@ return;
 static BOOL
 cutthrough_send(int n)
 {
 static BOOL
 cutthrough_send(int n)
 {
-if(cutthrough_fd < 0)
+if(cutthrough.cctx.sock < 0)
   return TRUE;
 
 if(
 #ifdef SUPPORT_TLS
   return TRUE;
 
 if(
 #ifdef SUPPORT_TLS
-   (tls_out.active == cutthrough_fd) ? tls_write(FALSE, ctblock.buffer, n) :
+   cutthrough.is_tls
+   ? tls_write(cutthrough.cctx.tls_ctx, ctctx.outblock.buffer, n, FALSE)
+   :
 #endif
 #endif
-   send(cutthrough_fd, ctblock.buffer, n, 0) > 0
+     send(cutthrough.cctx.sock, ctctx.outblock.buffer, n, 0) > 0
   )
 {
   transport_count += n;
   )
 {
   transport_count += n;
-  ctblock.ptr= ctblock.buffer;
+  ctctx.outblock.ptr= ctctx.outblock.buffer;
   return TRUE;
 }
 
   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;
 }
 
 return FALSE;
 }
 
@@ -1147,30 +1253,37 @@ _cutthrough_puts(uschar * cp, int n)
 {
 while(n--)
  {
 {
 while(n--)
  {
- if(ctblock.ptr >= ctblock.buffer+ctblock.buffersize)
-   if(!cutthrough_send(ctblock.buffersize))
+ if(ctctx.outblock.ptr >= ctctx.outblock.buffer+ctctx.outblock.buffersize)
+   if(!cutthrough_send(ctctx.outblock.buffersize))
      return FALSE;
 
      return FALSE;
 
- *ctblock.ptr++ = *cp++;
+ *ctctx.outblock.ptr++ = *cp++;
  }
 return TRUE;
 }
 
 /* Buffered output of counted data block.   Return boolean success */
  }
 return TRUE;
 }
 
 /* Buffered output of counted data block.   Return boolean success */
-BOOL
+static BOOL
 cutthrough_puts(uschar * cp, int n)
 {
 cutthrough_puts(uschar * cp, int n)
 {
-if (cutthrough_fd < 0)       return TRUE;
-if (_cutthrough_puts(cp, n)) return TRUE;
-cancel_cutthrough_connection("transmit failed");
+if (cutthrough.cctx.sock < 0) return TRUE;
+if (_cutthrough_puts(cp, n))  return TRUE;
+cancel_cutthrough_connection(TRUE, US"transmit failed");
 return FALSE;
 }
 
 return FALSE;
 }
 
+void
+cutthrough_data_puts(uschar * cp, int n)
+{
+if (cutthrough.delivery) (void) cutthrough_puts(cp, n);
+return;
+}
+
 
 static BOOL
 
 static BOOL
-_cutthrough_flush_send( void )
+_cutthrough_flush_send(void)
 {
 {
-int n= ctblock.ptr-ctblock.buffer;
+int n = ctctx.outblock.ptr - ctctx.outblock.buffer;
 
 if(n>0)
   if(!cutthrough_send(n))
 
 if(n>0)
   if(!cutthrough_send(n))
@@ -1181,42 +1294,48 @@ return TRUE;
 
 /* Send out any bufferred output.  Return boolean success. */
 BOOL
 
 /* Send out any bufferred output.  Return boolean success. */
 BOOL
-cutthrough_flush_send( void )
+cutthrough_flush_send(void)
 {
 if (_cutthrough_flush_send()) return TRUE;
 {
 if (_cutthrough_flush_send()) return TRUE;
-cancel_cutthrough_connection("transmit failed");
+cancel_cutthrough_connection(TRUE, US"transmit failed");
 return FALSE;
 }
 
 
 return FALSE;
 }
 
 
-BOOL
-cutthrough_put_nl( void )
+static BOOL
+cutthrough_put_nl(void)
 {
 return cutthrough_puts(US"\r\n", 2);
 }
 
 
 {
 return cutthrough_puts(US"\r\n", 2);
 }
 
 
+void
+cutthrough_data_put_nl(void)
+{
+cutthrough_data_puts(US"\r\n", 2);
+}
+
+
 /* Get and check response from cutthrough target */
 static uschar
 /* Get and check response from cutthrough target */
 static uschar
-cutthrough_response(char expect, uschar ** copy)
+cutthrough_response(client_conn_ctx * cctx, char expect, uschar ** copy, int timeout)
 {
 {
-smtp_inblock inblock;
+smtp_context sx = {0};
 uschar inbuffer[4096];
 uschar responsebuffer[4096];
 
 uschar inbuffer[4096];
 uschar responsebuffer[4096];
 
-inblock.buffer = inbuffer;
-inblock.buffersize = sizeof(inbuffer);
-inblock.ptr = inbuffer;
-inblock.ptrend = inbuffer;
-inblock.sock = cutthrough_fd;
-/* this relies on (inblock.sock == tls_out.active) */
-if(!smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), expect, CUTTHROUGH_DATA_TIMEOUT))
-  cancel_cutthrough_connection("target timeout on read");
+sx.inblock.buffer = inbuffer;
+sx.inblock.buffersize = sizeof(inbuffer);
+sx.inblock.ptr = inbuffer;
+sx.inblock.ptrend = inbuffer;
+sx.inblock.cctx = cctx;
+if(!smtp_read_response(&sx, responsebuffer, sizeof(responsebuffer), expect, timeout))
+  cancel_cutthrough_connection(TRUE, US"target timeout on read");
 
 
-if(copy != NULL)
+if(copy)
   {
   uschar * cp;
   {
   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';
   /* Trim the trailing end of line */
   cp += Ustrlen(responsebuffer);
   if(cp > *copy  &&  cp[-1] == '\n') *--cp = '\0';
@@ -1229,23 +1348,23 @@ return responsebuffer[0];
 
 /* Negotiate dataphase with the cutthrough target, returning success boolean */
 BOOL
 
 /* Negotiate dataphase with the cutthrough target, returning success boolean */
 BOOL
-cutthrough_predata( void )
+cutthrough_predata(void)
 {
 {
-if(cutthrough_fd < 0)
+if(cutthrough.cctx.sock < 0 || cutthrough.callout_hold_only)
   return FALSE;
 
   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. */
 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(&cutthrough.cctx, '3', NULL, CUTTHROUGH_DATA_TIMEOUT) == '3';
 }
 
 
 }
 
 
-/* fd and use_crlf args only to match write_chunk() */
+/* tctx arg only to match write_chunk() */
 static BOOL
 static BOOL
-cutthrough_write_chunk(int fd, uschar * s, int len, BOOL use_crlf)
+cutthrough_write_chunk(transport_ctx * tctx, uschar * s, int len)
 {
 uschar * s2;
 while(s && (s2 = Ustrchr(s, '\n')))
 {
 uschar * s2;
 while(s && (s2 = Ustrchr(s, '\n')))
@@ -1262,57 +1381,87 @@ return TRUE;
 /* Expands newlines to wire format (CR,NL).           */
 /* Also sends header-terminating blank line.          */
 BOOL
 /* 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.cctx.sock < 0 || cutthrough.callout_hold_only)
   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)
 */
   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.u.fd = cutthrough.cctx.sock;
+tctx.tblock = cutthrough.addr.transport;
+tctx.addr = &cutthrough.addr;
+tctx.check_string = US".";
+tctx.escape_string = US"..";
+/*XXX check under spool_files_wireformat.  Might be irrelevant */
+tctx.options = topt_use_crlf;
+
+if (!transport_headers_send(&tctx, &cutthrough_write_chunk))
   return FALSE;
 
   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
 return TRUE;
 }
 
 
 static void
-close_cutthrough_connection( const char * why )
+close_cutthrough_connection(const uschar * why)
 {
 {
-if(cutthrough_fd >= 0)
+int fd = cutthrough.cctx.sock;
+if(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.
   */
   {
   /* 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");
+  client_conn_ctx tmp_ctx = cutthrough.cctx;
+  ctctx.outblock.ptr = ctbuffer;
+  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();
   _cutthrough_puts(US"QUIT\r\n", 6);   /* avoid recursion */
   _cutthrough_flush_send();
-  /* No wait for response */
-
-  #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);
+  cutthrough.cctx.sock = -1;           /* avoid recursion via read timeout */
+  cutthrough.nrcpt = 0;                        /* permit re-cutthrough on subsequent message */
+
+  /* Wait a short time for response, and discard it */
+  cutthrough_response(&tmp_ctx, '2', NULL, 1);
+
+#ifdef SUPPORT_TLS
+  if (cutthrough.is_tls)
+    {
+    tls_close(cutthrough.cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT);
+    cutthrough.cctx.tls_ctx = NULL;
+    cutthrough.is_tls = FALSE;
+    }
+#endif
+  HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP(close)>>\n");
+  (void)close(fd);
+  HDEBUG(D_acl) debug_printf_indent("----------- cutthrough shutdown (%s) ------------\n", why);
   }
   }
-ctblock.ptr = ctbuffer;
+ctctx.outblock.ptr = ctbuffer;
+}
+
+void
+cancel_cutthrough_connection(BOOL close_noncutthrough_verifies, const uschar * why)
+{
+if (cutthrough.delivery || close_noncutthrough_verifies)
+  close_cutthrough_connection(why);
+cutthrough.delivery = cutthrough.callout_hold_only = FALSE;
 }
 
 }
 
+
 void
 void
-cancel_cutthrough_connection( const char * why )
+release_cutthrough_connection(const uschar * why)
 {
 {
-close_cutthrough_connection(why);
-cutthrough_delivery= FALSE;
+if (cutthrough.cctx.sock < 0) return;
+HDEBUG(D_acl) debug_printf_indent("release cutthrough conn: %s\n", why);
+cutthrough.cctx.sock = -1;
+cutthrough.cctx.tls_ctx = NULL;
+cutthrough.delivery = cutthrough.callout_hold_only = FALSE;
 }
 
 
 }
 
 
@@ -1324,33 +1473,46 @@ cutthrough_delivery= FALSE;
    Return smtp response-class digit.
 */
 uschar *
    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 */
 
 /* 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(&cutthrough.cctx, '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(US"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 +1543,7 @@ if (addr != vaddr)
   vaddr->user_message = addr->user_message;
   vaddr->basic_errno = addr->basic_errno;
   vaddr->more_errno = addr->more_errno;
   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;
   copyflag(vaddr, addr, af_pass_message);
   }
 return yield;
@@ -1418,7 +1580,7 @@ va_list ap;
 
 va_start(ap, format);
 if (smtp_out && (f == smtp_out))
 
 va_start(ap, format);
 if (smtp_out && (f == smtp_out))
-  smtp_vprintf(format, ap);
+  smtp_vprintf(format, FALSE, ap);
 else
   vfprintf(f, format, ap);
 va_end(ap);
 else
   vfprintf(f, format, ap);
 va_end(ap);
@@ -1477,27 +1639,26 @@ Returns:           OK      address verified
 */
 
 int
 */
 
 int
-verify_address(address_item *vaddr, FILE *f, int options, int callout,
-  int callout_overall, int callout_connect, uschar *se_mailfrom,
+verify_address(address_item * vaddr, FILE * fp, int options, int callout,
+  int callout_overall, int callout_connect, uschar * se_mailfrom,
   uschar *pm_mailfrom, BOOL *routed)
 {
 BOOL allok = TRUE;
   uschar *pm_mailfrom, BOOL *routed)
 {
 BOOL allok = TRUE;
-BOOL full_info = (f == NULL)? FALSE : (debug_selector != 0);
-BOOL is_recipient = (options & vopt_is_recipient) != 0;
+BOOL full_info = fp ? debug_selector != 0 : FALSE;
 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 :
 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;
+   f.address_test_mode? v_none :
+          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;
 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;
 uschar *ko_prefix, *cr;
 uschar *address = vaddr->address;
 uschar *save_sender;
@@ -1522,54 +1683,54 @@ else ko_prefix = cr = US"";
 
 if (parse_find_at(address) == NULL)
   {
 
 if (parse_find_at(address) == NULL)
   {
-  if ((options & vopt_qualify) == 0)
+  if (!(options & vopt_qualify))
     {
     {
-    if (f != NULL)
-      respond_printf(f, "%sA domain is required for \"%s\"%s\n",
+    if (fp)
+      respond_printf(fp, "%sA domain is required for \"%s\"%s\n",
         ko_prefix, address, cr);
     *failure_ptr = US"qualify";
     return FAIL;
     }
         ko_prefix, address, cr);
     *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)
   {
   debug_printf(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
   }
 
 DEBUG(D_verify)
   {
   debug_printf(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
-  debug_printf("%s %s\n", address_test_mode? "Testing" : "Verifying", address);
+  debug_printf("%s %s\n", f.address_test_mode? "Testing" : "Verifying", address);
   }
 
 /* Rewrite and report on it. Clear the domain and local part caches - these
 may have been set by domains and local part tests during an ACL. */
 
   }
 
 /* Rewrite and report on it. Clear the domain and local part caches - these
 may have been set by domains and local part tests during an ACL. */
 
-if (global_rewrite_rules != NULL)
+if (global_rewrite_rules)
   {
   uschar *old = address;
   {
   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)
     {
     for (i = 0; i < (MAX_NAMED_LIST * 2)/32; i++) vaddr->localpart_cache[i] = 0;
     for (i = 0; i < (MAX_NAMED_LIST * 2)/32; i++) vaddr->domain_cache[i] = 0;
     global_rewrite_rules, rewrite_existflags);
   if (address != old)
     {
     for (i = 0; i < (MAX_NAMED_LIST * 2)/32; i++) vaddr->localpart_cache[i] = 0;
     for (i = 0; i < (MAX_NAMED_LIST * 2)/32; i++) vaddr->domain_cache[i] = 0;
-    if (f != NULL && !expn) fprintf(f, "Address rewritten as: %s\n", address);
+    if (fp && !expn) fprintf(fp, "Address rewritten as: %s\n", address);
     }
   }
 
 /* If this is the real sender address, we must update sender_address at
 this point, because it may be referred to in the routers. */
 
     }
   }
 
 /* If this is the real sender address, we must update sender_address at
 this point, because it may be referred to in the routers. */
 
-if ((options & (vopt_fake_sender|vopt_is_recipient)) == 0)
+if (!(options & (vopt_fake_sender|vopt_is_recipient)))
   sender_address = address;
 
 /* If the address was rewritten to <> no verification can be done, and we have
 to return OK. This rewriting is permitted only for sender addresses; for other
 addresses, such rewriting fails. */
 
   sender_address = address;
 
 /* If the address was rewritten to <> no verification can be done, and we have
 to return OK. This rewriting is permitted only for sender addresses; for other
 addresses, such rewriting fails. */
 
-if (address[0] == 0) return OK;
+if (!address[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
 
 /* 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);
 
 
 tls_modify_variables(&tls_out);
 
@@ -1578,6 +1739,10 @@ while verifying a sender address (a nice bit of self-reference there). */
 
 save_sender = sender_address;
 
 
 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. */
 
 /* 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 +1758,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. */
 
 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;
   {
   int rc;
   address_item *addr = addr_new;
@@ -1613,47 +1778,47 @@ while (addr_new != NULL)
   if (testflag(addr, af_pfr))
     {
     allok = FALSE;
   if (testflag(addr, af_pfr))
     {
     allok = FALSE;
-    if (f != NULL)
+    if (fp)
       {
       BOOL allow;
 
       if (addr->address[0] == '>')
         {
         allow = testflag(addr, af_allow_reply);
       {
       BOOL allow;
 
       if (addr->address[0] == '>')
         {
         allow = testflag(addr, af_allow_reply);
-        fprintf(f, "%s -> mail %s", addr->parent->address, addr->address + 1);
+        fprintf(fp, "%s -> mail %s", addr->parent->address, addr->address + 1);
         }
       else
         {
         }
       else
         {
-        allow = (addr->address[0] == '|')?
-          testflag(addr, af_allow_pipe) : testflag(addr, af_allow_file);
-        fprintf(f, "%s -> %s", addr->parent->address, addr->address);
+        allow = addr->address[0] == '|'
+          testflag(addr, af_allow_pipe) : testflag(addr, af_allow_file);
+        fprintf(fp, "%s -> %s", addr->parent->address, addr->address);
         }
 
       if (addr->basic_errno == ERRNO_BADTRANSPORT)
         }
 
       if (addr->basic_errno == ERRNO_BADTRANSPORT)
-        fprintf(f, "\n*** Error in setting up pipe, file, or autoreply:\n"
+        fprintf(fp, "\n*** Error in setting up pipe, file, or autoreply:\n"
           "%s\n", addr->message);
       else if (allow)
           "%s\n", addr->message);
       else if (allow)
-        fprintf(f, "\n  transport = %s\n", addr->transport->name);
+        fprintf(fp, "\n  transport = %s\n", addr->transport->name);
       else
       else
-        fprintf(f, " *** forbidden ***\n");
+        fprintf(fp, " *** forbidden ***\n");
       }
     continue;
     }
 
   /* Just in case some router parameter refers to it. */
 
       }
     continue;
     }
 
   /* 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. */
 
 
   /* 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 ((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 */
     rc = route_address(addr, &addr_local, &addr_remote, &addr_new,
       &addr_succeed, verify_type);
     sender_address = save_sender;     /* Put back the real sender */
@@ -1667,43 +1832,44 @@ while (addr_new != NULL)
 
   if (rc == OK)
     {
 
   if (rc == OK)
     {
-    if (routed != NULL) *routed = TRUE;
+    if (routed) *routed = TRUE;
     if (callout > 0)
       {
     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. */
 
       transport_feedback tf = {
 
       /* Make up some data for use in the case where there is no remote
       transport. */
 
       transport_feedback tf = {
-        NULL,                       /* interface (=> any) */
-        US"smtp",                   /* port */
-        US"smtp",                   /* protocol */
-        NULL,                       /* hosts */
-        US"$smtp_active_hostname",  /* helo_data */
-        FALSE,                      /* hosts_override */
-        FALSE,                      /* hosts_randomize */
-        FALSE,                      /* gethostbyname */
-        TRUE,                       /* qualify_single */
-        FALSE                       /* search_parents */
+        .interface =           NULL,                       /* interface (=> any) */
+        .port =                        US"smtp",
+        .protocol =            US"smtp",
+        .hosts =               NULL,
+        .helo_data =           US"$smtp_active_hostname",
+        .hosts_override =      FALSE,
+        .hosts_randomize =     FALSE,
+        .gethostbyname =       FALSE,
+        .qualify_single =      TRUE,
+        .search_parents =      FALSE
         };
 
       /* If verification yielded a remote transport, we want to use that
       transport's options, so as to mimic what would happen if we were really
       sending a message to this address. */
 
         };
 
       /* If verification yielded a remote transport, we want to use that
       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 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 *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 */
           uschar *save_deliver_localpart = deliver_localpart;
 
           host_list = NULL;    /* Ignore the router's hosts */
@@ -1714,16 +1880,15 @@ while (addr_new != NULL)
           deliver_domain = save_deliver_domain;
           deliver_localpart = save_deliver_localpart;
 
           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,
             {
             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;
             }
           else
             {
             int flags;
-            uschar *canonical_name;
             host_item *host, *nexthost;
             host_build_hostlist(&host_list, s, tf.hosts_randomize);
 
             host_item *host, *nexthost;
             host_build_hostlist(&host_list, s, tf.hosts_randomize);
 
@@ -1733,30 +1898,28 @@ while (addr_new != NULL)
             additional host items being inserted into the chain. Hence we must
             save the next host first. */
 
             additional host items being inserted into the chain. Hence we must
             save the next host first. */
 
-            flags = HOST_FIND_BY_A;
+            flags = HOST_FIND_BY_A | HOST_FIND_BY_AAAA;
             if (tf.qualify_single) flags |= HOST_FIND_QUALIFY_SINGLE;
             if (tf.search_parents) flags |= HOST_FIND_SEARCH_PARENTS;
 
             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)
               {
               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
                {
               else
                {
-               uschar * d_request = NULL, * d_require = NULL;
-               if (Ustrcmp(addr->transport->driver_name, "smtp") == 0)
+               const dnssec_domains * dsp = NULL;
+               if (Ustrcmp(tp->driver_name, "smtp") == 0)
                  {
                  smtp_transport_options_block * ob =
                  {
                  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;
+                 dsp = &ob->dnssec;
                  }
 
                  }
 
-                (void)host_find_bydns(host, NULL, flags, NULL, NULL, NULL,
-                 d_request, d_require, &canonical_name, NULL);
+                (void) host_find_bydns(host, NULL, flags, NULL, NULL, NULL,
+                 dsp, NULL, NULL);
                }
               }
             }
                }
               }
             }
@@ -1766,10 +1929,10 @@ 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. */
 
       /* 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");
         {
         HDEBUG(D_verify) debug_printf("Attempting full verification using callout\n");
-        if (host_checking && !host_checking_callout)
+        if (host_checking && !f.host_checking_callout)
           {
           HDEBUG(D_verify)
             debug_printf("... callout omitted by default when host testing\n"
           {
           HDEBUG(D_verify)
             debug_printf("... callout omitted by default when host testing\n"
@@ -1782,12 +1945,15 @@ while (addr_new != NULL)
 #endif
           rc = do_callout(addr, host_list, &tf, callout, callout_overall,
             callout_connect, options, se_mailfrom, pm_mailfrom);
 #endif
           rc = do_callout(addr, host_list, &tf, callout, callout_overall,
             callout_connect, options, se_mailfrom, pm_mailfrom);
+#ifdef SUPPORT_TLS
+         deliver_set_expansions(NULL);
+#endif
           }
         }
       else
         {
         HDEBUG(D_verify) debug_printf("Cannot do callout: neither router nor "
           }
         }
       else
         {
         HDEBUG(D_verify) debug_printf("Cannot do callout: neither router nor "
-          "transport provided a host list\n");
+          "transport provided a host list, or transport is not smtp\n");
         }
       }
     }
         }
       }
     }
@@ -1807,38 +1973,38 @@ while (addr_new != NULL)
   if (rc == FAIL)
     {
     allok = FALSE;
   if (rc == FAIL)
     {
     allok = FALSE;
-    if (f != NULL)
+    if (fp)
       {
       address_item *p = addr->parent;
 
       {
       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");
-      if (!expn && admin_user)
+      respond_printf(fp, "%s%s %s", ko_prefix,
+        full_info ? addr->address : address,
+        f.address_test_mode ? "is undeliverable" : "failed to verify");
+      if (!expn && f.admin_user)
         {
         if (addr->basic_errno > 0)
         {
         if (addr->basic_errno > 0)
-          respond_printf(f, ": %s", strerror(addr->basic_errno));
-        if (addr->message != NULL)
-          respond_printf(f, ": %s", addr->message);
+          respond_printf(fp, ": %s", strerror(addr->basic_errno));
+        if (addr->message)
+          respond_printf(fp, ": %s", addr->message);
         }
 
       /* Show parents iff doing full info */
 
         }
 
       /* 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);
+        respond_printf(fp, "%s\n    <-- %s", cr, p->address);
         p = p->parent;
         }
         p = p->parent;
         }
-      respond_printf(f, "%s\n", cr);
+      respond_printf(fp, "%s\n", cr);
       }
       }
-    cancel_cutthrough_connection("routing hard fail");
+    cancel_cutthrough_connection(TRUE, US"routing hard fail");
 
     if (!full_info)
 
     if (!full_info)
-    {
+      {
       yield = copy_error(vaddr, addr, FAIL);
       goto out;
       yield = copy_error(vaddr, addr, FAIL);
       goto out;
-    }
-    else yield = FAIL;
+      }
+    yield = FAIL;
     }
 
   /* Soft failure */
     }
 
   /* Soft failure */
@@ -1846,38 +2012,38 @@ while (addr_new != NULL)
   else if (rc == DEFER)
     {
     allok = FALSE;
   else if (rc == DEFER)
     {
     allok = FALSE;
-    if (f != NULL)
+    if (fp)
       {
       address_item *p = addr->parent;
       {
       address_item *p = addr->parent;
-      respond_printf(f, "%s%s cannot be resolved at this time", ko_prefix,
+      respond_printf(fp, "%s%s cannot be resolved at this time", ko_prefix,
         full_info? addr->address : address);
         full_info? addr->address : address);
-      if (!expn && admin_user)
+      if (!expn && f.admin_user)
         {
         if (addr->basic_errno > 0)
         {
         if (addr->basic_errno > 0)
-          respond_printf(f, ": %s", strerror(addr->basic_errno));
-        if (addr->message != NULL)
-          respond_printf(f, ": %s", addr->message);
+          respond_printf(fp, ": %s", strerror(addr->basic_errno));
+        if (addr->message)
+          respond_printf(fp, ": %s", addr->message);
         else if (addr->basic_errno <= 0)
         else if (addr->basic_errno <= 0)
-          respond_printf(f, ": unknown error");
+          respond_printf(fp, ": unknown error");
         }
 
       /* Show parents iff doing full info */
 
         }
 
       /* 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);
+        respond_printf(fp, "%s\n    <-- %s", cr, p->address);
         p = p->parent;
         }
         p = p->parent;
         }
-      respond_printf(f, "%s\n", cr);
+      respond_printf(fp, "%s\n", cr);
       }
       }
-    cancel_cutthrough_connection("routing soft fail");
+    cancel_cutthrough_connection(TRUE, US"routing soft fail");
 
     if (!full_info)
       {
       yield = copy_error(vaddr, addr, DEFER);
       goto out;
       }
 
     if (!full_info)
       {
       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
     }
 
   /* If we are handling EXPN, we do not want to continue to route beyond
@@ -1886,20 +2052,20 @@ while (addr_new != NULL)
   else if (expn)
     {
     uschar *ok_prefix = US"250-";
   else if (expn)
     {
     uschar *ok_prefix = US"250-";
-    if (addr_new == NULL)
-      {
-      if (addr_local == NULL && addr_remote == NULL)
-        respond_printf(f, "250 mail to <%s> is discarded\r\n", address);
+
+    if (!addr_new)
+      if (!addr_local && !addr_remote)
+        respond_printf(fp, "250 mail to <%s> is discarded\r\n", address);
       else
       else
-        respond_printf(f, "250 <%s>\r\n", address);
-      }
-    else while (addr_new != NULL)
+        respond_printf(fp, "250 <%s>\r\n", address);
+
+    else do
       {
       address_item *addr2 = addr_new;
       addr_new = addr2->next;
       {
       address_item *addr2 = addr_new;
       addr_new = addr2->next;
-      if (addr_new == NULL) ok_prefix = US"250 ";
-      respond_printf(f, "%s<%s>\r\n", ok_prefix, addr2->address);
-      }
+      if (!addr_new) ok_prefix = US"250 ";
+      respond_printf(fp, "%s<%s>\r\n", ok_prefix, addr2->address);
+      } while (addr_new);
     yield = OK;
     goto out;
     }
     yield = OK;
     goto out;
     }
@@ -1921,21 +2087,30 @@ while (addr_new != NULL)
     just a single new address as a special case, and continues on to verify the
     generated address. */
 
     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 (fp) fprintf(fp, "%s %s\n",
+        address, f.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 */
 
 
       /* 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(TRUE, US"multiple addresses from routing");
+
       yield = OK;
       goto out;
       }
       yield = OK;
       goto out;
       }
@@ -1943,7 +2118,7 @@ while (addr_new != NULL)
   }     /* Loop for generated addresses */
 
 /* Display the full results of the successful routing, including any generated
   }     /* Loop for generated addresses */
 
 /* Display the full results of the successful routing, including any generated
-addresses. Control gets here only when full_info is set, which requires f not
+addresses. Control gets here only when full_info is set, which requires fp not
 to be NULL, and this occurs only when a top-level verify is called with the
 debugging switch on.
 
 to be NULL, and this occurs only when a top-level verify is called with the
 debugging switch on.
 
@@ -1951,24 +2126,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. */
 
 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);
+  fprintf(fp, "mail to %s is discarded\n", address);
   goto out;
   }
 
 for (addr_list = addr_local, i = 0; i < 2; addr_list = addr_remote, i++)
   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;
     {
     address_item *addr = addr_list;
     address_item *p = addr->parent;
+    transport_instance * tp = addr->transport;
+
     addr_list = addr->next;
 
     addr_list = addr->next;
 
-    fprintf(f, "%s", CS addr->address);
+    fprintf(fp, "%s", CS addr->address);
 #ifdef EXPERIMENTAL_SRS
 #ifdef EXPERIMENTAL_SRS
-    if(addr->p.srs_sender)
-      fprintf(f, "    [srs = %s]", addr->p.srs_sender);
+    if(addr->prop.srs_sender)
+      fprintf(fp, "    [srs = %s]", addr->prop.srs_sender);
 #endif
 
     /* If the address is a duplicate, show something about it. */
 #endif
 
     /* If the address is a duplicate, show something about it. */
@@ -1976,72 +2152,62 @@ for (addr_list = addr_local, i = 0; i < 2; addr_list = addr_remote, i++)
     if (!testflag(addr, af_pfr))
       {
       tree_node *tnode;
     if (!testflag(addr, af_pfr))
       {
       tree_node *tnode;
-      if ((tnode = tree_search(tree_duplicates, addr->unique)) != NULL)
-        fprintf(f, "   [duplicate, would not be delivered]");
+      if ((tnode = tree_search(tree_duplicates, addr->unique)))
+        fprintf(fp, "   [duplicate, would not be delivered]");
       else tree_add_duplicate(addr->unique, addr);
       }
 
     /* Now show its parents */
 
       else tree_add_duplicate(addr->unique, addr);
       }
 
     /* Now show its parents */
 
-    while (p != NULL)
-      {
-      fprintf(f, "\n    <-- %s", p->address);
-      p = p->parent;
-      }
-    fprintf(f, "\n  ");
+    for (p = addr->parent; p; p = p->parent)
+      fprintf(fp, "\n    <-- %s", p->address);
+    fprintf(fp, "\n  ");
 
     /* Show router, and transport */
 
 
     /* 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(fp, "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. */
 
 
     /* 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;
       {
       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;
         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;
         }
         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);
-        if (h->port != PORT_NONE) fprintf(f, " port=%d", h->port);
-        if (h->status == hstatus_unusable) fprintf(f, " ** unusable **");
-        fprintf(f, "\n");
+      for (h = addr->host_list; h; h = h->next)
+       {
+       fprintf(fp, "  host %-*s ", maxlen, h->name);
+
+       if (h->address)
+         fprintf(fp, "[%s%-*c", h->address, maxaddlen+1 - Ustrlen(h->address), ']');
+       else if (tp->info->local)
+         fprintf(fp, " %-*s ", maxaddlen, "");  /* Omit [unknown] for local */
+       else
+         fprintf(fp, "[%s%-*c", "unknown", maxaddlen+1 - 7, ']');
+
+        if (h->mx >= 0) fprintf(fp, " MX=%d", h->mx);
+        if (h->port != PORT_NONE) fprintf(fp, " port=%d", h->port);
+        if (f.running_in_test_harness  &&  h->dnssec == DS_YES) fputs(" AD", fp);
+        if (h->status == hstatus_unusable) fputs(" ** unusable **", fp);
+       fputc('\n', fp);
         }
       }
     }
         }
       }
     }
-  }
 
 /* Yield will be DEFER or FAIL if any one address has, only for full_info (which is
 the -bv or -bt case). */
 
 out:
 
 /* 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;
 tls_modify_variables(&tls_in);
 
 return yield;
@@ -2055,7 +2221,7 @@ return yield;
 *************************************************/
 
 /* This function checks those header lines that contain addresses, and verifies
 *************************************************/
 
 /* This function checks those header lines that contain addresses, and verifies
-that all the addresses therein are syntactially correct.
+that all the addresses therein are 5322-syntactially correct.
 
 Arguments:
   msgptr     where to put an error message
 
 Arguments:
   msgptr     where to put an error message
@@ -2071,7 +2237,7 @@ header_line *h;
 uschar *colon, *s;
 int yield = OK;
 
 uschar *colon, *s;
 int yield = OK;
 
-for (h = header_list; h != NULL && yield == OK; h = h->next)
+for (h = header_list; h && yield == OK; h = h->next)
   {
   if (h->type != htype_from &&
       h->type != htype_reply_to &&
   {
   if (h->type != htype_from &&
       h->type != htype_reply_to &&
@@ -2088,9 +2254,9 @@ for (h = header_list; h != NULL && yield == OK; h = h->next)
   /* Loop for multiple addresses in the header, enabling group syntax. Note
   that we have to reset this after the header has been scanned. */
 
   /* Loop for multiple addresses in the header, enabling group syntax. Note
   that we have to reset this after the header has been scanned. */
 
-  parse_allow_group = TRUE;
+  f.parse_allow_group = TRUE;
 
 
-  while (*s != 0)
+  while (*s)
     {
     uschar *ss = parse_find_address_end(s, FALSE);
     uschar *recipient, *errmess;
     {
     uschar *ss = parse_find_address_end(s, FALSE);
     uschar *recipient, *errmess;
@@ -2107,15 +2273,15 @@ for (h = header_list; h != NULL && yield == OK; h = h->next)
     /* Permit an unqualified address only if the message is local, or if the
     sending host is configured to be permitted to send them. */
 
     /* Permit an unqualified address only if the message is local, or if the
     sending host is configured to be permitted to send them. */
 
-    if (recipient != NULL && domain == 0)
+    if (recipient && !domain)
       {
       if (h->type == htype_from || h->type == htype_sender)
         {
       {
       if (h->type == htype_from || h->type == htype_sender)
         {
-        if (!allow_unqualified_sender) recipient = NULL;
+        if (!f.allow_unqualified_sender) recipient = NULL;
         }
       else
         {
         }
       else
         {
-        if (!allow_unqualified_recipient) recipient = NULL;
+        if (!f.allow_unqualified_recipient) recipient = NULL;
         }
       if (recipient == NULL) errmess = US"unqualified address not permitted";
       }
         }
       if (recipient == NULL) errmess = US"unqualified address not permitted";
       }
@@ -2123,7 +2289,7 @@ for (h = header_list; h != NULL && yield == OK; h = h->next)
     /* It's an error if no address could be extracted, except for the special
     case of an empty address. */
 
     /* It's an error if no address could be extracted, except for the special
     case of an empty address. */
 
-    if (recipient == NULL && Ustrcmp(errmess, "empty address") != 0)
+    if (!recipient && Ustrcmp(errmess, "empty address") != 0)
       {
       uschar *verb = US"is";
       uschar *t = ss;
       {
       uschar *verb = US"is";
       uschar *t = ss;
@@ -2150,9 +2316,10 @@ for (h = header_list; h != NULL && yield == OK; h = h->next)
         verb = US"begins";
         }
 
         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",
         string_sprintf("%s: failing address in \"%.*s:\" header %s: %.*s",
-          errmess, tt - h->text, h->text, verb, len, s));
+          errmess, (int)(tt - h->text), h->text, verb, len, s));
 
       yield = FAIL;
       break;          /* Out of address loop */
 
       yield = FAIL;
       break;          /* Out of address loop */
@@ -2160,12 +2327,12 @@ for (h = header_list; h != NULL && yield == OK; h = h->next)
 
     /* Advance to the next address */
 
 
     /* Advance to the next address */
 
-    s = ss + (terminator? 1:0);
+    s = ss + (terminator ? 1 : 0);
     while (isspace(*s)) s++;
     }   /* Next address */
 
     while (isspace(*s)) s++;
     }   /* Next address */
 
-  parse_allow_group = FALSE;
-  parse_found_group = FALSE;
+  f.parse_allow_group = FALSE;
+  f.parse_found_group = FALSE;
   }     /* Next header unless yield has been set FALSE */
 
 return yield;
   }     /* Next header unless yield has been set FALSE */
 
 return yield;
@@ -2176,7 +2343,7 @@ return yield;
 *      Check header names for 8-bit characters   *
 *************************************************/
 
 *      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:
 RFC 5322, 2.2. and RFC 6532, 3.
 
 Arguments:
@@ -2192,18 +2359,16 @@ verify_check_header_names_ascii(uschar **msgptr)
 header_line *h;
 uschar *colon, *s;
 
 header_line *h;
 uschar *colon, *s;
 
-for (h = header_list; h != NULL; h = h->next)
+for (h = header_list; h; h = h->next)
   {
   {
-   colon = Ustrchr(h->text, ':');
-   for(s = h->text; s < colon; s++)
-     {
-        if ((*s < 33) || (*s > 126))
-        {
-                *msgptr = string_sprintf("Invalid character in header \"%.*s\" found",
-                                         colon - h->text, h->text);
-                return FAIL;
-        }
-     }
+  colon = Ustrchr(h->text, ':');
+  for(s = h->text; s < colon; s++)
+    if ((*s < 33) || (*s > 126))
+      {
+      *msgptr = string_sprintf("Invalid character in header \"%.*s\" found",
+                            colon - h->text, h->text);
+      return FAIL;
+      }
   }
 return OK;
 }
   }
 return OK;
 }
@@ -2249,7 +2414,7 @@ for (i = 0; i < recipients_count; i++)
     /* Loop for multiple addresses in the header, enabling group syntax. Note
     that we have to reset this after the header has been scanned. */
 
     /* Loop for multiple addresses in the header, enabling group syntax. Note
     that we have to reset this after the header has been scanned. */
 
-    parse_allow_group = TRUE;
+    f.parse_allow_group = TRUE;
 
     while (*s != 0)
       {
 
     while (*s != 0)
       {
@@ -2284,8 +2449,8 @@ for (i = 0; i < recipients_count; i++)
       while (isspace(*s)) s++;
       }   /* Next address */
 
       while (isspace(*s)) s++;
       }   /* Next address */
 
-    parse_allow_group = FALSE;
-    parse_found_group = FALSE;
+    f.parse_allow_group = FALSE;
+    f.parse_found_group = FALSE;
     }     /* Next header (if found is false) */
 
   if (!found) return FAIL;
     }     /* Next header (if found is false) */
 
   if (!found) return FAIL;
@@ -2386,7 +2551,7 @@ for (i = 0; i < 3 && !done; i++)
     /* Scan the addresses in the header, enabling group syntax. Note that we
     have to reset this after the header has been scanned. */
 
     /* Scan the addresses in the header, enabling group syntax. Note that we
     have to reset this after the header has been scanned. */
 
-    parse_allow_group = TRUE;
+    f.parse_allow_group = TRUE;
 
     while (*s != 0)
       {
 
     while (*s != 0)
       {
@@ -2455,7 +2620,7 @@ for (i = 0; i < 3 && !done; i++)
           while (ss > s && isspace(ss[-1])) ss--;
           *log_msgptr = string_sprintf("syntax error in '%.*s' header when "
             "scanning for sender: %s in \"%.*s\"",
           while (ss > s && isspace(ss[-1])) ss--;
           *log_msgptr = string_sprintf("syntax error in '%.*s' header when "
             "scanning for sender: %s in \"%.*s\"",
-            endname - h->text, h->text, *log_msgptr, ss - s, s);
+            (int)(endname - h->text), h->text, *log_msgptr, (int)(ss - s), s);
           yield = FAIL;
           done = TRUE;
           break;
           yield = FAIL;
           done = TRUE;
           break;
@@ -2483,11 +2648,9 @@ for (i = 0; i < 3 && !done; i++)
         {
         *verrno = vaddr->basic_errno;
         if (smtp_return_error_details)
         {
         *verrno = vaddr->basic_errno;
         if (smtp_return_error_details)
-          {
           *user_msgptr = string_sprintf("Rejected after DATA: "
             "could not verify \"%.*s\" header address\n%s: %s",
           *user_msgptr = string_sprintf("Rejected after DATA: "
             "could not verify \"%.*s\" header address\n%s: %s",
-            endname - h->text, h->text, vaddr->address, vaddr->message);
-          }
+            (int)(endname - h->text), h->text, vaddr->address, vaddr->message);
         }
 
       /* Success or defer */
         }
 
       /* Success or defer */
@@ -2506,8 +2669,8 @@ for (i = 0; i < 3 && !done; i++)
       s = ss;
       }     /* Next address */
 
       s = ss;
       }     /* Next address */
 
-    parse_allow_group = FALSE;
-    parse_found_group = FALSE;
+    f.parse_allow_group = FALSE;
+    f.parse_found_group = FALSE;
     }       /* Next header, unless done */
   }         /* Next header type unless done */
 
     }       /* Next header, unless done */
   }         /* Next header type unless done */
 
@@ -2545,9 +2708,11 @@ Side effect: any received ident value is put in sender_ident (NULL otherwise)
 void
 verify_get_ident(int port)
 {
 void
 verify_get_ident(int port)
 {
-int sock, host_af, qlen;
+client_conn_ctx ident_conn_ctx = {0};
+int host_af, qlen;
 int received_sender_port, received_interface_port, n;
 uschar *p;
 int received_sender_port, received_interface_port, n;
 uschar *p;
+blob early_data;
 uschar buffer[2048];
 
 /* Default is no ident. Check whether we want to do an ident check for this
 uschar buffer[2048];
 
 /* Default is no ident. Check whether we want to do an ident check for this
@@ -2563,40 +2728,33 @@ 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. */
 
 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 ((ident_conn_ctx.sock = ip_socket(SOCK_STREAM, host_af)) < 0) return;
 
 
-if (ip_bind(sock, host_af, interface_address, 0) < 0)
+if (ip_bind(ident_conn_ctx.sock, host_af, interface_address, 0) < 0)
   {
   DEBUG(D_ident) debug_printf("bind socket for ident failed: %s\n",
     strerror(errno));
   goto END_OFF;
   }
 
   {
   DEBUG(D_ident) debug_printf("bind socket for ident failed: %s\n",
     strerror(errno));
   goto END_OFF;
   }
 
-if (ip_connect(sock, host_af, sender_host_address, port, rfc1413_query_timeout)
-     < 0)
+/* Construct and send the query. */
+
+qlen = snprintf(CS buffer, sizeof(buffer), "%d , %d\r\n",
+  sender_host_port, interface_port);
+early_data.data = buffer;
+early_data.len = qlen;
+
+/*XXX we trust that the query is idempotent */
+if (ip_connect(ident_conn_ctx.sock, host_af, sender_host_address, port,
+               rfc1413_query_timeout, &early_data) < 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);
     log_write(0, LOG_MAIN, "ident connection to %s timed out",
       sender_host_address);
-    }
   else
   else
-    {
     DEBUG(D_ident) debug_printf("ident connection to %s failed: %s\n",
       sender_host_address, strerror(errno));
     DEBUG(D_ident) debug_printf("ident connection to %s failed: %s\n",
       sender_host_address, strerror(errno));
-    }
-  goto END_OFF;
-  }
-
-/* Construct and send the query. */
-
-sprintf(CS buffer, "%d , %d\r\n", sender_host_port, interface_port);
-qlen = Ustrlen(buffer);
-if (send(sock, buffer, qlen, 0) < 0)
-  {
-  DEBUG(D_ident) debug_printf("ident send failed: %s\n", strerror(errno));
   goto END_OFF;
   }
 
   goto END_OFF;
   }
 
@@ -2612,7 +2770,7 @@ for (;;)
   int size = sizeof(buffer) - (p - buffer);
 
   if (size <= 0) goto END_OFF;   /* Buffer filled without seeing \n. */
   int size = sizeof(buffer) - (p - buffer);
 
   if (size <= 0) goto END_OFF;   /* Buffer filled without seeing \n. */
-  count = ip_recv(sock, p, size, rfc1413_query_timeout);
+  count = ip_recv(&ident_conn_ctx, p, size, rfc1413_query_timeout);
   if (count <= 0) goto END_OFF;  /* Read error or EOF */
 
   /* Scan what we just read, to see if we have reached the terminating \r\n. Be
   if (count <= 0) goto END_OFF;  /* Read error or EOF */
 
   /* Scan what we just read, to see if we have reached the terminating \r\n. Be
@@ -2671,13 +2829,13 @@ 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
 /* 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:
 DEBUG(D_ident) debug_printf("sender_ident = %s\n", sender_ident);
 
 END_OFF:
-(void)close(sock);
+(void)close(ident_conn_ctx.sock);
 return;
 }
 
 return;
 }
 
@@ -2718,7 +2876,7 @@ Returns:         OK      matched
 */
 
 int
 */
 
 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;
 {
 check_host_block *cb = (check_host_block *)arg;
 int mlen = -1;
@@ -2726,7 +2884,7 @@ int maskoffset;
 BOOL iplookup = FALSE;
 BOOL isquery = FALSE;
 BOOL isiponly = cb->host_name != NULL && cb->host_name[0] == 0;
 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;
 
 uschar *semicolon;
 uschar **aliases;
 
@@ -2762,7 +2920,7 @@ if (*ss == '@')
   }
 
 /* If the pattern is an IP address, optionally followed by a bitmask count, do
   }
 
 /* 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);
 
 if (string_is_ip_address(ss, &maskoffset) != 0)
   return (host_is_in_net(cb->host_address, ss, maskoffset)? OK : FAIL);
@@ -2867,12 +3025,12 @@ if (iplookup)
   /* Now do the actual lookup; note that there is no search_close() because
   of the caching arrangements. */
 
   /* 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);
   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;
+  if (valueptr) *valueptr = result;
+  return result ? OK : f.search_find_defer ? DEFER: FAIL;
   }
 
 /* The pattern is not an IP address or network reference of any kind. That is,
   }
 
 /* The pattern is not an IP address or network reference of any kind. That is,
@@ -2907,6 +3065,10 @@ if (*t == 0)
   h.address = NULL;
   h.mx = MX_NONE;
 
   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)
     {
   rc = host_find_byname(&h, NULL, HOST_FIND_QUALIFY_SINGLE, NULL, FALSE);
   if (rc == HOST_FOUND || rc == HOST_FOUND_LOCAL)
     {
@@ -2939,7 +3101,7 @@ on spec. */
 
 if ((semicolon = Ustrchr(ss, ';')) != NULL)
   {
 
 if ((semicolon = Ustrchr(ss, ';')) != NULL)
   {
-  uschar *affix;
+  const uschar *affix;
   int partial, affixlen, starflags, id;
 
   *semicolon = 0;
   int partial, affixlen, starflags, id;
 
   *semicolon = 0;
@@ -2968,7 +3130,7 @@ if (isquery)
 /* Not a query-style lookup; must ensure the host name is present, and then we
 do a check on the name and all its aliases. */
 
 /* Not a query-style lookup; must ensure the host name is present, and then we
 do a check on the name and all its aliases. */
 
-if (sender_host_name == NULL)
+if (!sender_host_name)
   {
   HDEBUG(D_host_lookup)
     debug_printf("sender host name required, to match against %s\n", ss);
   {
   HDEBUG(D_host_lookup)
     debug_printf("sender host name required, to match against %s\n", ss);
@@ -2983,8 +3145,7 @@ if (sender_host_name == NULL)
 
 /* Match on the sender host name, using the general matching function */
 
 
 /* Match on the sender host name, using the general matching function */
 
-switch(match_check_string(sender_host_name, ss, -1, TRUE, TRUE, TRUE,
-       valueptr))
+switch(match_check_string(sender_host_name, ss, -1, TRUE, TRUE, TRUE, valueptr))
   {
   case OK:    return OK;
   case DEFER: return DEFER;
   {
   case OK:    return OK;
   case DEFER: return DEFER;
@@ -2993,14 +3154,12 @@ switch(match_check_string(sender_host_name, ss, -1, TRUE, TRUE, TRUE,
 /* If there are aliases, try matching on them. */
 
 aliases = sender_host_aliases;
 /* If there are aliases, try matching on them. */
 
 aliases = sender_host_aliases;
-while (*aliases != NULL)
-  {
+while (*aliases)
   switch(match_check_string(*aliases++, ss, -1, TRUE, TRUE, TRUE, valueptr))
     {
     case OK:    return OK;
     case DEFER: return DEFER;
     }
   switch(match_check_string(*aliases++, ss, -1, TRUE, TRUE, TRUE, valueptr))
     {
     case OK:    return OK;
     case DEFER: return DEFER;
     }
-  }
 return FAIL;
 }
 
 return FAIL;
 }
 
@@ -3040,24 +3199,22 @@ 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
 "+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;
 {
 int rc;
 unsigned int *local_cache_bits = cache_bits;
-uschar *save_host_address = deliver_host_address;
-check_host_block cb;
-cb.host_name = host_name;
-cb.host_address = host_address;
+const uschar *save_host_address = deliver_host_address;
+check_host_block cb = { .host_name = host_name, .host_address = host_address };
 
 
-if (valueptr != NULL) *valueptr = NULL;
+if (valueptr) *valueptr = NULL;
 
 /* If the host address starts off ::ffff: it is an IPv6 address in
 IPv4-compatible mode. Find the IPv4 part for checking against IPv4
 addresses. */
 
 
 /* If the host address starts off ::ffff: it is an IPv6 address in
 IPv4-compatible mode. Find the IPv4 part for checking against IPv4
 addresses. */
 
-cb.host_ipv4 = (Ustrncmp(host_address, "::ffff:", 7) == 0)?
-  host_address + 7 : host_address;
+cb.host_ipv4 = Ustrncmp(host_address, "::ffff:", 7) == 0
+  host_address + 7 : host_address;
 
 /* During the running of the check, put the IP address into $host_address. In
 the case of calls from the smtp transport, it will already be there. However,
 
 /* During the running of the check, put the IP address into $host_address. In
 the case of calls from the smtp transport, it will already be there. However,
@@ -3084,6 +3241,15 @@ return rc;
 
 
 
 
 
 
+/*************************************************
+*      Check the given host item matches a list  *
+*************************************************/
+int
+verify_check_given_host(const uschar **listptr, const host_item *host)
+{
+return verify_check_this_host(listptr, NULL, host->name, host->address, NULL);
+}
+
 /*************************************************
 *      Check the remote host matches a list      *
 *************************************************/
 /*************************************************
 *      Check the remote host matches a list      *
 *************************************************/
@@ -3103,7 +3269,7 @@ Returns:               the yield of verify_check_this_host(),
 int
 verify_check_host(uschar **listptr)
 {
 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);
 }
 
   (sender_host_address == NULL)? US"" : sender_host_address, NULL);
 }
 
@@ -3232,23 +3398,39 @@ if (!string_format(query, sizeof(query), "%s.%s", prepend, domain))
 
 /* Look for this query in the cache. */
 
 
 /* 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 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;
 
   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);
 
   HDEBUG(D_dnsbl) debug_printf("new DNS lookup for %s\n", query);
   cb->rc = dns_basic_lookup(&dnsa, query, T_A);
@@ -3258,52 +3440,46 @@ if (t == NULL)
 
   /* If the lookup succeeded, cache the RHS address. The code allows for
   more than one address - this was for complete generality and the possible
 
   /* If the lookup succeeded, cache the RHS address. The code allows for
   more than one address - this was for complete generality and the possible
-  use of A6 records. However, A6 records have been reduced to experimental
-  status (August 2001) and may die out. So they may never get used at all,
-  let alone in dnsbl records. However, leave the code here, just in case.
+  use of A6 records. However, A6 records are no longer supported. Leave the code
+  here, just in case.
 
   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
 
   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);
 
   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))
          rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
-      {
       if (rr->type == T_A)
         {
         dns_address *da = dns_address_from_rr(&dnsa, rr);
       if (rr->type == T_A)
         {
         dns_address *da = dns_address_from_rr(&dnsa, rr);
-        if (da != NULL)
+        if (da)
           {
           *addrp = da;
           {
           *addrp = da;
-          while (da->next != NULL) da = da->next;
-          addrp = &(da->next);
+          while (da->next) da = da->next;
+          addrp = &da->next;
+         if (ttl > rr->ttl) ttl = rr->ttl;
           }
         }
           }
         }
-      }
 
     /* If we didn't find any A records, change the return code. This can
     happen when there is a CNAME record but there are no A records for what
     it points to. */
 
 
     /* If we didn't find any A records, change the return code. This can
     happen when there is a CNAME record but there are no A records for what
     it points to. */
 
-    if (cb->rhs == NULL) cb->rc = DNS_NODATA;
+    if (!cb->rhs) cb->rc = DNS_NODATA;
     }
 
     }
 
+  cb->expiry = time(NULL)+ttl;
   store_pool = old_pool;
   }
 
   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
 /* 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
@@ -3319,7 +3495,7 @@ if (cb->rc == DNS_SUCCEED)
   records. For A6 records (currently not expected to be used) there may be
   multiple addresses from a single record. */
 
   records. For A6 records (currently not expected to be used) there may be
   multiple addresses from a single record. */
 
-  for (da = cb->rhs->next; da != NULL; da = da->next)
+  for (da = cb->rhs->next; da; da = da->next)
     addlist = string_sprintf("%s, %s", addlist, da->address);
 
   HDEBUG(D_dnsbl) debug_printf("DNS lookup for %s succeeded (yielding %s)\n",
     addlist = string_sprintf("%s, %s", addlist, da->address);
 
   HDEBUG(D_dnsbl) debug_printf("DNS lookup for %s succeeded (yielding %s)\n",
@@ -3328,24 +3504,23 @@ if (cb->rc == DNS_SUCCEED)
   /* Address list check; this can be either for equality, or via a bitmask.
   In the latter case, all the bits must match. */
 
   /* Address list check; this can be either for equality, or via a bitmask.
   In the latter case, all the bits must match. */
 
-  if (iplist != NULL)
+  if (iplist)
     {
     {
-    for (da = cb->rhs; da != NULL; da = da->next)
+    for (da = cb->rhs; da; da = da->next)
       {
       int ipsep = ',';
       uschar ip[46];
       {
       int ipsep = ',';
       uschar ip[46];
-      uschar *ptr = iplist;
+      const uschar *ptr = iplist;
       uschar *res;
 
       /* Handle exact matching */
 
       if (!bitmask)
       uschar *res;
 
       /* Handle exact matching */
 
       if (!bitmask)
-        {
-        while ((res = string_nextinlist(&ptr, &ipsep, ip, sizeof(ip))) != NULL)
-          {
-          if (Ustrcmp(CS da->address, ip) == 0) break;
-          }
-        }
+       {
+        while ((res = string_nextinlist(&ptr, &ipsep, ip, sizeof(ip))))
+          if (Ustrcmp(CS da->address, ip) == 0)
+           break;
+       }
 
       /* Handle bitmask matching */
 
 
       /* Handle bitmask matching */
 
@@ -3365,7 +3540,7 @@ if (cb->rc == DNS_SUCCEED)
 
         /* Scan the returned addresses, skipping any that are IPv6 */
 
 
         /* Scan the returned addresses, skipping any that are IPv6 */
 
-        while ((res = string_nextinlist(&ptr, &ipsep, ip, sizeof(ip))) != NULL)
+        while ((res = string_nextinlist(&ptr, &ipsep, ip, sizeof(ip))))
           {
           if (host_aton(ip, address) != 1) continue;
           if ((address[0] & mask) == address[0]) break;
           {
           if (host_aton(ip, address) != 1) continue;
           if ((address[0] & mask) == address[0]) break;
@@ -3398,17 +3573,13 @@ if (cb->rc == DNS_SUCCEED)
         switch(match_type)
           {
           case 0:
         switch(match_type)
           {
           case 0:
-          res = US"was no match";
-          break;
+           res = US"was no match"; break;
           case MT_NOT:
           case MT_NOT:
-          res = US"was an exclude match";
-          break;
+           res = US"was an exclude match"; break;
           case MT_ALL:
           case MT_ALL:
-          res = US"was an IP address that did not match";
-          break;
+           res = US"was an IP address that did not match"; break;
           case MT_NOT|MT_ALL:
           case MT_NOT|MT_ALL:
-          res = US"were no IP addresses that did not match";
-          break;
+           res = US"were no IP addresses that did not match"; break;
           }
         debug_printf("=> but we are not accepting this block class because\n");
         debug_printf("=> there %s for %s%c%s\n",
           }
         debug_printf("=> but we are not accepting this block class because\n");
         debug_printf("=> there %s for %s%c%s\n",
@@ -3440,15 +3611,15 @@ if (cb->rc == DNS_SUCCEED)
       {
       dns_record *rr;
       for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
       {
       dns_record *rr;
       for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
-           rr != NULL;
+           rr;
            rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
         if (rr->type == T_TXT) break;
            rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
         if (rr->type == T_TXT) break;
-      if (rr != NULL)
+      if (rr)
         {
         int len = (rr->data)[0];
         if (len > 511) len = 127;
         store_pool = POOL_PERM;
         {
         int len = (rr->data)[0];
         if (len > 511) len = 127;
         store_pool = POOL_PERM;
-        cb->text = string_sprintf("%.*s", len, (const uschar *)(rr->data+1));
+        cb->text = string_sprintf("%.*s", len, CUS (rr->data+1));
         store_pool = old_pool;
         }
       }
         store_pool = old_pool;
         }
       }
@@ -3527,7 +3698,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:
 Note: a domain for testing RFCI is example.tld.dsn.rfc-ignorant.org
 
 Arguments:
+  where        the acl type
   listptr      the domain/address/data list
   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
 
 Returns:    OK      successful lookup (i.e. the address is on the list), or
                       lookup deferred after +include_unknown
@@ -3537,11 +3710,11 @@ Returns:    OK      successful lookup (i.e. the address is on the list), or
 */
 
 int
 */
 
 int
-verify_check_dnsbl(uschar **listptr)
+verify_check_dnsbl(int where, const uschar ** listptr, uschar ** log_msgptr)
 {
 int sep = 0;
 int defer_return = FAIL;
 {
 int sep = 0;
 int defer_return = FAIL;
-uschar *list = *listptr;
+const uschar *list = *listptr;
 uschar *domain;
 uschar *s;
 uschar buffer[1024];
 uschar *domain;
 uschar *s;
 uschar buffer[1024];
@@ -3584,21 +3757,19 @@ while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL
 
   /* See if there's explicit data to be looked up */
 
 
   /* 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. */
 
 
   /* 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, '&');
     }
 
     {
     bitmask = TRUE;
     iplist = Ustrchr(domain, '&');
     }
 
-  if (iplist != NULL)                          /* Found either = or & */
+  if (iplist)                                 /* Found either = or & */
     {
     if (iplist > domain && iplist[-1] == '!')  /* Handle preceding ! */
       {
     {
     if (iplist > domain && iplist[-1] == '!')  /* Handle preceding ! */
       {
@@ -3617,6 +3788,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. */
   /* 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 +3834,13 @@ while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL
 
   if (key == 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,
     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 +3866,7 @@ while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL
     uschar keybuffer[256];
     uschar keyrevadd[128];
 
     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;
             sizeof(keybuffer))) != NULL)
       {
       uschar *prepend = keydomain;
index 04a2d07..91d8124 100644 (file)
@@ -3,6 +3,7 @@
 *************************************************/
 
 /* Copyright (c) University of Cambridge 1995 - 2009 */
 *************************************************/
 
 /* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) The Exim Maintainers 2010 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Function for setting up the version string. */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Function for setting up the version string. */
@@ -40,6 +41,16 @@ version_cnumber_format = US"%d\0<<eximcnumber>>";
 sprintf(CS version_cnumber, CS version_cnumber_format, cnumber);
 version_string = US EXIM_VERSION_STR "\0<<eximversion>>";
 
 sprintf(CS version_cnumber, CS version_cnumber_format, cnumber);
 version_string = US EXIM_VERSION_STR "\0<<eximversion>>";
 
+#ifdef EXIM_BUILD_DATE_OVERRIDE
+/* Reproducible build support; build tooling should have given us something looking like
+ * "25-Feb-2017 20:15:40" in EXIM_BUILD_DATE_OVERRIDE based on $SOURCE_DATE_EPOCH in environ
+ * per <https://reproducible-builds.org/specs/source-date-epoch/>
+ */
+version_date = date_buffer;
+version_date[0] = 0;
+Ustrncat(version_date, EXIM_BUILD_DATE_OVERRIDE, sizeof(date_buffer));
+
+#else
 Ustrcpy(today, __DATE__);
 if (today[4] == ' ') today[4] = '0';
 today[3] = today[6] = '-';
 Ustrcpy(today, __DATE__);
 if (today[4] == ' ') today[4] = '0';
 today[3] = today[6] = '-';
@@ -51,6 +62,7 @@ Ustrncat(version_date, today, 4);
 Ustrncat(version_date, today+7, 4);
 Ustrcat(version_date, " ");
 Ustrcat(version_date, __TIME__);
 Ustrncat(version_date, today+7, 4);
 Ustrcat(version_date, " ");
 Ustrcat(version_date, __TIME__);
+#endif
 }
 
 /* End of version.c */
 }
 
 /* End of version.c */
diff --git a/src/version.h b/src/version.h
new file mode 100644 (file)
index 0000000..f428693
--- /dev/null
@@ -0,0 +1,7 @@
+/* automatically generated file - see ../scripts/reversion */
+#define EXIM_RELEASE_VERSION "4.92"
+#ifdef EXIM_VARIANT_VERSION
+#define EXIM_VERSION_STR EXIM_RELEASE_VERSION "-" EXIM_VARIANT_VERSION
+#else
+#define EXIM_VERSION_STR EXIM_RELEASE_VERSION
+#endif
index 1de6875..e94bec6 100644 (file)
@@ -1,4 +1,3 @@
-# initial version automatically generated from release-process/scripts/mk_exim_release.pl
-EXIM_RELEASE_VERSION=4.84
-EXIM_VARIANT_VERSION=_2
-EXIM_COMPILE_NUMBER=0
+# automatically generated file - see ../scripts/reversion
+EXIM_RELEASE_VERSION="4.92"
+EXIM_COMPILE_NUMBER="1"
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.
 
 #       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;
 
 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.
  */
  * 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)
   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;
         rc, p - spaceless);
 
   return b;
@@ -134,7 +134,7 @@ emit_c_format_dh(FILE *stream, DH *dh)
       break;
     }
     *nl = '\0';
       break;
     }
     *nl = '\0';
-    fprintf(stream, "\"%s\\n\"\n", p);
+    fprintf(stream, "\"%s\\n\"%s\n", p, (nl == end - 1 ? ";" : ""));
     p = nl + 1;
   }
 }
     p = nl + 1;
   }
 }
@@ -143,9 +143,11 @@ emit_c_format_dh(FILE *stream, DH *dh)
 void __attribute__((__noreturn__))
 usage(FILE *stream, int exitcode)
 {
 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"
 "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"
 "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"
 "\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[])
 {
 int
 main(int argc, char *argv[])
 {
-  BIGNUM *p, *g;
+  BIGNUM *p, *g, *q;
   DH *dh;
   int ch;
   bool perform_dh_check = false;
   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 show_numbers = false;
   bool show_pem = true;
   bool show_text = false;
+  bool given_q = false;
 
   while ((ch = getopt(argc, argv, "CPcsth")) != -1) {
     switch (ch) {
 
   while ((ch = getopt(argc, argv, "CPcsth")) != -1) {
     switch (ch) {
@@ -201,25 +204,49 @@ main(int argc, char *argv[])
   argc -= optind;
   argv += optind;
 
   argc -= optind;
   argv += optind;
 
-  if (argc != 3) {
+  if ((argc < 3) || (argc > 4)) {
     fprintf(stderr, "argc: %d\n", argc);
     usage(stderr, 1);
   }
 
     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]);
   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 (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();
     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;
   dh->p = p;
   dh->g = g;
+  if (given_q) {
+    dh->q = q;
+  }
+#endif
 
   if (perform_dh_check)
     our_dh_check(dh);
 
   if (perform_dh_check)
     our_dh_check(dh);
@@ -234,6 +261,6 @@ main(int argc, char *argv[])
       PEM_write_DHparams(stdout, dh);
   }
 
       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;
 }
   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'
 # 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;
 
 #
 # 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);
 
 $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;
 # 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;
 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;
 #
 use strict;
 use warnings;
+BEGIN { pop @INC if $INC[-1] eq '.' };
 use IO::Select;
 use IO::Socket;
 use Getopt::Long;
 use IO::Select;
 use IO::Socket;
 use Getopt::Long;
index d7fd43a..e212fa2 100644 (file)
@@ -2,6 +2,8 @@
 
 use strict;
 
 
 use strict;
 
+BEGIN { pop @INC if $INC[-1] eq '.' };
+
 sub usage () {
   print <<END;
 usage: ratelimit.pl [options] <period> <regex> <logfile>
 sub usage () {
   print <<END;
 usage: ratelimit.pl [options] <period> <regex> <logfile>
diff --git a/util/renew-opendmarc-tlds.sh b/util/renew-opendmarc-tlds.sh
new file mode 100755 (executable)
index 0000000..9967018
--- /dev/null
@@ -0,0 +1,128 @@
+#!/bin/sh -eu
+#
+# Short version of this script:
+#   curl -f -o /var/cache/exim/opendmarc.tlds https://publicsuffix.org/list/public_suffix_list.dat
+# but run as Exim runtime user, writing to a place it can write to, and with
+# sanity checks and atomic replacement.
+#
+# For now, we deliberately leave the invalid file around for analysis
+# with .<pid> suffix.
+#
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~8< cut here >8~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+#
+# Create a cron-job as the Exim run-time user to invoke this daily, with a
+# single parameter, 'cron'.  Eg:
+#
+#    3 4 * * *   /usr/local/sbin/renew-opendmarc-tlds.sh cron
+#
+# That will, at 3 minutes past the 4th hour (in whatever timezone cron is
+# running it) invoke this script with 'cron'; we will then sleep between 10 and
+# 50 seconds, before continuing.
+#
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~8< cut here >8~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+#
+# This should be "pretty portable"; the only things it depends upon are:
+#  * a POSIX shell which additionally implements 'local' (dash works)
+#  * the 'curl' command; change the fetch_candidate() function to replace that
+#  * the 'stat' command, to get the size of a file; else Perl
+#    + change size_of() if need be; it's defined per-OS
+#  * the 'hexdump' command and /dev/urandom existing
+#    + used when invoked with 'cron', to avoid retrieving on a minute boundary
+#      and contending with many other automated systems.
+#    + with bash/zsh, can replace with: $(( 10 + ( RANDOM % 40 ) ))
+#    + on Debian/Ubuntu systems, hexdump is in the 'bsdmainutils' package.
+
+# Consider putting an email address inside the parentheses, something like
+# noc@example.org or other reachable address, so that if something goes wrong
+# and the server operators need to step in, they can see from logs who to
+# contact instead of just blocking your IP:
+readonly CurlUserAgent='renew-opendmarc-tlds/0.1 (distributed with Exim)'
+
+# change this to your Exim run-time user (exim -n -bP exim_user) :
+readonly RuntimeUser='_exim'
+
+# Do not make this a directory which untrusted users can write to:
+readonly StateDir='/var/cache/exim'
+
+readonly URL='https://publicsuffix.org/list/public_suffix_list.dat'
+
+readonly TargetShortFile='opendmarc.tlds'
+
+# When replacing, new file must be at least this percentage the size of
+# the old one or it's an error:
+readonly MinNewSizeRation=90
+
+# Each of these regexps must be matched by the file, or it's an error:
+readonly MustExistRegexps='
+  ^ac\.uk$
+  ^org$
+  ^tech$
+  '
+
+# =======================8< end of configuration >8=======================
+
+set -eu
+
+readonly FullTargetPath="${StateDir}/${TargetShortFile}"
+readonly WorkingFile="${FullTargetPath}.$$"
+
+progname="$(basename "$0")"
+note() { printf >&2 '%s: %s\n' "$progname" "$*"; }
+die() { note "$@"; exit 1; }
+
+# guard against stomping on file-permissions
+[ ".$(id -un)" = ".${RuntimeUser:?}" ] || \
+  die "must be invoked as ${RuntimeUser}"
+
+fetch_candidate() {
+       curl --user-agent "$CurlUserAgent" -fSs -o "${WorkingFile}" "${URL}"
+}
+
+case $(uname -s) in
+*BSD|Darwin)
+       size_of() { stat -f %z "$1"; }
+       ;;
+Linux)
+       size_of() { stat -c %s "$1"; }
+       ;;
+*)
+       # why do we live in a world where Perl is the safe portable solution
+       # to getting the size of a file?
+       size_of() { perl -le 'print((stat($ARGV[0]))[7])' -- "$1"; }
+       ;;
+esac
+
+sanity_check_candidate() {
+       local new_size prev_size re
+       new_size="$(size_of "$WorkingFile")"
+
+       for re in $MustExistRegexps; do
+               grep -qs "$re" -- "$WorkingFile" || \
+                 die "regexp $re not found in $WorkingFile"
+       done
+
+       if ! prev_size="$(size_of "$FullTargetPath")"; then
+               note "missing previous file, can't size-compare: $FullTargetPath"
+               # We're sane by definition, probably initial fetch, and the
+               # stat failure and this note will be printed.  That's fine; if
+               # a cron invocation is missing the file then something has gone
+               # badly wrong.
+               return 0
+       fi
+       local ratio
+       ratio=$(expr $new_size \* 100 / $prev_size)
+       if [ $ratio -lt $MinNewSizeRation ]; then
+               die "New $TargetShortFile candidate only ${ratio}% size of old; $new_size vs $prev_size"
+       fi
+}
+
+if [ "${1:-.}" = "cron" ]; then
+       shift
+       # Don't pull on-the-minute, wait for off-cycle-peak
+       sleep $(( ($(dd if=/dev/urandom bs=1 count=1 2>/dev/null | hexdump -e '1/1 "%u"') % 40) + 10))
+fi
+
+umask 022
+fetch_candidate
+sanity_check_candidate
+mv -- "$WorkingFile" "$FullTargetPath"