# Review base: exim-4.99.4
# Target branch: master
#
# Commits only in master (present in the resulting patch):
# commit 7562c428879f14ae984a6a1ba540e6dd249eac0f
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Mon Jun 22 14:59:13 2026 +0100
# 
#     AUTH: harden External authenticator against failing option expansions
# 
# commit b327cc6d854600671cce05bc69cdceecd7e7c723
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Mon Jun 22 14:47:50 2026 +0100
# 
#     PROXY: harden agains early-close from client proxy
# 
# commit fdf81beee5be35706c26dcd375fafed3e82b8e14
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Fri Jun 19 08:05:08 2026 +0100
# 
#     Perl: fix DNS NOMATCH handling
# 
# commit a582cffb75fb9a7097e8c079782dff34ee198920
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Thu Jun 18 11:57:41 2026 +0100
# 
#     typo
# 
# commit cc438308f85b1d457e636eb880e47c1d41760c38
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Thu Jun 18 11:56:39 2026 +0100
# 
#     Debug: add config location to startup line
# 
# commit 101db173a191c34e7a3d744a25c21d391bdfc5ce
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sat Jun 13 20:33:24 2026 +0100
# 
#     DMARC: fix handling of a null return from regdom lookup
# 
# commit c10b65cf8cd0231115d88ba6ec8344063762dcf8
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Thu Jun 11 21:32:52 2026 +0100
# 
#     Build: fix non-Linux build env
#     
#     Broken-by: 184f2012f3eb
# 
# commit dfb2eeae3e043112071a32c6a77e8e250b7cf2d5
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Thu Jun 11 19:00:55 2026 +0100
# 
#     Debug: fix display of top-bit-set chars
# 
# commit a693a05cf8ae07a1e5cc610735298416b57f345f
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Thu Jun 11 12:04:47 2026 +0100
# 
#     Debug: more info on address-parsing expansions
# 
# commit f5fb7f5ad0313ca0158f8c28f8b22b744c4d9038
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sun Jun 7 20:04:18 2026 +0100
# 
#     constify
# 
# commit 9293462a54ec43570087ed16685b788ace89a2fa
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sat Jun 6 14:45:14 2026 +0100
# 
#     constify
# 
# commit 184f2012f3eb474d55e0e67fafbc7261725dafa2
# Merge: f285ac8e5 6c566ce28
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sun May 31 18:10:01 2026 +0100
# 
#     Merge branch 'receive_processing_stack'
# 
# commit f285ac8e5b3506e61216ad82291260fdeeb4073f
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sun May 31 14:44:23 2026 +0100
# 
#     TLS: handle rxd TLS close mid-message
# 
# commit 15abfdba2db678d74e46c861f85ca0eac994701e
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sat May 30 18:24:56 2026 +0100
# 
#     typo
#     
#     Broken-by: f0e4329d42a9
# 
# commit f0e4329d42a9ee246625ba62957edd1cd4acc726
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sun May 17 22:40:03 2026 +0100
# 
#     smtp input processing: ensure double-ungetc safe
# 
# commit 81a7c635fff9fccaae9779e17e03b9223879db58
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Fri May 29 16:46:42 2026 +0100
# 
#     Testsuite: output changes resulting
#     
#     Broken-by: 040c1ce6889f
# 
# commit de1f0c28941137c21b838f816be2ba8454ab58fa
# Merge: f72c69f0e 25d2d8eb9
# Author: Heiko Schlittermann (HS12-RIPE) <hs@schlittermann.de>
# Date:   Fri May 29 16:51:43 2026 +0200
# 
#     Merge branch 'exim-4.99+fixes'
#     
#     * exim-4.99+fixes:
#       doc: record CVE-2026-48840 for the PROXYv2 fix (EXIM-Security-2026-05-19.1)
#       Security: fix PROXYv2 uninitialised-stack disclosure (EXIM-Security-2026-05-16.1)
#       test: regression for EXIM-Security-2026-05-19.1
#       test: fix aux-fixed permissions in make
#       test: skip empty script dirs for ranges
# 
# commit f72c69f0e025b9531d783ef034a1f8986e871fb9
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Fri May 29 14:45:19 2026 +0100
# 
#     DKIM: harden the authresults expansion against malicious senders
# 
# commit e06343106eade2b5f772b5d99cdfa16fa64e602d
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Thu May 28 16:05:12 2026 +0100
# 
#     debug: clarify interproc trigger
# 
# commit a513441cbc793192dd12294af4e089fcfaa3ab96
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Wed May 20 14:15:30 2026 +0100
# 
#     testsuite: wait for rxd tls shutdown before completing stoptls action
# 
# commit d513bde1de025530e7574a038c840c7481e3ebab
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sun May 17 16:50:25 2026 +0100
# 
#     Expansions: fix $host_lookup_failed, $host_lookup_deferred to match docs
# 
# commit 1da9c193f2851ca19edf72ac626d133521586491
# Author: Heiko Schlittermann (HS12-RIPE) <hs@schlittermann.de>
# Date:   Sun May 17 09:23:25 2026 +0200
# 
#     Docs: correct NFS file locking advice for appendfile (closes #3147)
#     
#     The use_lockfile description claimed fcntl() locking risks file corruption
#     on NFS while a lock file prevents it. As pointed out in the exim-users
#     thread [1] and Debian #960358 [2], that is unfounded: if fcntl() over NFS
#     works, it is sufficient; if it does not, a lock file does not rescue the
#     situation either (client-side caching can still let stale writes reach
#     the server). Rewrite the paragraph and soften the matching "always use
#     lock files" advice in the file option description.
#     
#     [1] https://lists.exim.org/lurker/message/20200513.125602.7be7983c.en.html
#     [2] https://bugs.debian.org/960358
# 
# commit 2f9fc93c0a70e65a1e52789c883bbd06877a0c1b
# Author: robertsilen <robertsilen@noreply.localhost>
# Date:   Tue May 19 03:44:54 2026 +0100
# 
#     Docs: mention MariaDB in MySQL lookup sections
#     
#     Note MariaDB alongside MySQL in the mysql lookup subsection, SQL
#     lookups introduction, and special MySQL features section.
# 
# commit fcef59366c2001f16b9b25284931248944cddff4
# Merge: 2045ec000 48175da29
# Author: Heiko Schlittermann (HS12-RIPE) <hs@schlittermann.de>
# Date:   Fri May 15 12:18:23 2026 +0200
# 
#     Merge branch 'exim-4.99+fixes'
#     
#     Resolved conflicts in src/src/tls-gnu.c (debug category renames vs.
#     security fix). Deduplicated and reordered ChangeLog entries.
# 
# commit 2045ec00087a44a1c012b31107258d1ed80eedfb
# Author: Heiko Schlittermann (HS12-RIPE) <hs@schlittermann.de>
# Date:   Sat May 9 23:58:14 2026 +0200
# 
#     release-process: mk_exim_release: add doc build failure hint
#     
#             - Output a hint if the documentation build fails, suggesting the use of
#               `DOCBOOK2TEXI="docbook2x-texi --encoding=UTF-8"` to work around
#               issues with entity errors during XML processing.
# 
# commit ea6ea15428fda61a4d8776c5e1201372de0d9cc1
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Wed May 6 12:52:36 2026 +0100
# 
#     Docs: tidying
# 
# commit a05bf660f41fd1bf3ca633048c09d500e992be4e
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Mon May 4 12:49:12 2026 +0100
# 
#     regex: more debug for compile stage
# 
# commit 120a174a1ef25d6e28a0b49b90a3ed3f4922d68d
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sun May 3 19:10:44 2026 +0100
# 
#     string formatting: fix %q precision
# 
# commit 822fb99c083e60da915aa2ba4a50149b21d94076
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Thu Apr 30 17:38:28 2026 +0100
# 
#     Expansions: harden acl expansions against multiline or comment text
# 
# commit b3161d2c3e054ebd166ba266afd19aa666e76bac
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Thu Apr 30 17:36:19 2026 +0100
# 
#     Expansions: harden reverse_ip operator against an ipv6, built without support
# 
# commit 69a0432567d8b2b5595af51894249bba50595a17
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Thu Apr 30 17:31:18 2026 +0100
# 
#     Expansions: harden against long name or header-name
# 
# commit 87acc784cb0f1c5c620d4b15a3b9ad9042022ef5
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Thu Apr 30 13:42:32 2026 +0100
# 
#         Dsearch: fix restrictions on key=path
# 
# commit 10d7ec7c3ce6cd62bcbba73ee25f9abb101f75fa
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Tue Apr 21 13:22:38 2026 +0100
# 
#     Content scan: quote local_parts passed to rspamd if needed
# 
# commit 50bdafc98063e2a1ffd3089143a12bd98e537b4f
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Tue Apr 21 11:07:11 2026 +0100
# 
#     Transports: truncate oversize message return from local transports
# 
# commit ddd5fb778a78d41e2210150c4d2411bf14eb926b
# Author: Bernard Quatermass <toolsmith@quatermass.co.uk>
# Date:   Wed Mar 25 14:55:38 2026 +0000
# 
#     Content Scan: make clam socket parsing more resilient
# 
# commit 723bb0b77d196978487dbf1d0a7b1c605e6291b0
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Tue Mar 24 17:47:50 2026 +0000
# 
#     MIME: limit nesting depth of multiparts
# 
# commit 15366806f0c0758fda8a7efc7b47da080330d2bb
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Tue Mar 24 15:43:31 2026 +0000
# 
#     String formatting: harden FP handling for oversize values
# 
# commit e6d29d93447eedbb6cc4471bc3c43c085288aeff
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Tue Mar 24 12:06:22 2026 +0000
# 
#     GnuTLS: harden ALPN handling
# 
# commit 865547c311e42ce7e444f4189dfb2ab4c285fea5
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Tue Mar 24 11:16:41 2026 +0000
# 
#     DKIM: check for bogus dns record size
# 
# commit dce08f67d8505c16986b3f3dac55930bf92384f4
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Tue Mar 24 11:00:47 2026 +0000
# 
#     SPF (libspf2): limit copy size for A/AAAA records
# 
# commit beb0d4fe2c58864b5d979709072a68e6f7d55d06
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Tue Mar 24 10:04:42 2026 +0000
# 
#     SMTP transport: avoid overflow on 2GB+ message
# 
# commit 7fcacbc7cd40624474069b2e3b939c16409a9396
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Tue Mar 24 10:03:25 2026 +0000
# 
#     Expansions: limit field number for "extract"
# 
# commit e47223d397421112c000053608900e1f06f308b0
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Tue Mar 24 10:02:03 2026 +0000
# 
#     DMARC: use perm pool for results cache
# 
# commit d0aea5300f2e9b6b5e852c7ee608ecafa9f2eb08
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Mon Mar 23 18:43:41 2026 +0000
# 
#     Fix string_printing() defined-length case
# 
# commit 7707c11583e43dce37e670b427d4178b5e29392a
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Mon Mar 23 17:45:44 2026 +0000
# 
#     Expansions: check for zero arg to nhash
# 
# commit 9efcb34170a079e6c35cf1d099771bc55b85e252
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Mon Mar 23 17:33:28 2026 +0000
# 
#     Expansions: check for negative arg to hash
# 
# commit bbcddc0ded9b4da9bfc011165a492ed3189effd9
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Mon Mar 23 16:48:17 2026 +0000
# 
#     Expansions: refuse to eval a tainted string
# 
# commit 79e2319aefcc7b1cc1fb1b9a03d0d58cb644eb59
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Mon Mar 23 14:35:57 2026 +0000
# 
#     ARC: clear the verify context at RSET time
# 
# commit 68b963b9f75ca27b38e1c0f8c87037990199f505
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Tue Mar 10 21:29:52 2026 +0000
# 
#     SPA authenticator: harden buffer usage
#     
#     CVE-2026-40687
# 
# commit f2570bde16fb4d4a1242ff363a4c4eecf6372efc
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Mon Mar 23 15:10:28 2026 +0000
# 
#     Expansions: harden for malformed UTF-8
#     
#     CVE2026-40686
# 
# commit 9fdc057e71b87c87a0d3d2288b2810a0efaaba57
# Author: Bernard Quatermass <toolsmith@quatermass.co.uk>
# Date:   Mon Mar 23 16:43:51 2026 +0000
# 
#     when dewrap, only skip \ if associated char
#     
#     CVE2026-40685
# 
# commit 628bbaca7672748d941a12e7cd5f0122a4e18c81
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Tue Apr 28 14:47:32 2026 +0100
# 
#     Support musl libc dn_expand oddity
#     
#     CVE-2026-40684
# 
# commit 371e5210218746e876fd71c888fdb666c85ceb56
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sun Apr 19 15:14:14 2026 +0100
# 
#     GnuTLS: fix hostname verify of server cert for empty Subject.  Bug 3215
# 
# commit ebc783075de6eb6d3cc6ffe57335a1ec936481f2
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sun Apr 19 11:34:48 2026 +0100
# 
#     Debug: add channel "start"
# 
# commit 73190d4ef041f9e13e73cd5d9b7999a212928a89
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Wed Apr 15 09:53:51 2026 +0100
# 
#     Docs: tweak description of list syntax
# 
# commit da96127de22cde0e60da77a92eb1859fa506b115
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sun Apr 12 13:23:27 2026 +0100
# 
#     Docs: minor clarification
# 
# commit a8c046b7000dd0654ac93571166f706d8a1df186
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Wed Apr 15 09:42:48 2026 +0100
# 
#     debug_store: fix bad-ptr on freezing messsage
# 
# commit e8a71fcd7027ff383b06bfe715836c32b18eaea2
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sat Apr 11 14:17:01 2026 +0100
# 
#     Docs: note domain-list use of primary_hostname.  Bug 3214
# 
# commit 2989eba6bf044cb66cc494ed706238c2499cfe35
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Mon Apr 6 10:47:04 2026 +0100
# 
#     Testsuite: munge dmarc/arc
# 
# commit 320482241e766498f8afd1c0b19f209820cd1831
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sun Apr 5 21:55:13 2026 +0100
# 
#     Debug: use standard indent mech for exim-filter
# 
# commit fd8d862e9249274a28e7bc21bcab5141199618f9
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sun Apr 5 21:36:18 2026 +0100
# 
#     Exim filter: move testprint to debug stream, when debug enabled.  Bug 424
# 
# commit 284cae4e9b5308477438ac26bd5c783d10c879c1
# Author: Bernard Quatermass <bernardq@exim.org>
# Date:   Sun Apr 5 14:12:15 2026 +0100
# 
#     Removal of obsolete AV scanners for #3210 (#3212)
#     
#     Removal of obsolete AV scanners for #3210
#     
#     Reviewed-on: https://code.exim.org/exim/exim/pulls/3212
#     Reviewed-by: Jeremy Harris <jgh146exb@wizmail.org>
# 
# commit 6f5edf501bafdca1f234af6103c1173cc43805da
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sat Apr 4 22:27:58 2026 +0100
# 
#     Sieve filters: add more debug output for script errors.  Bug 2083
# 
# commit b1e08b4dba5cc484b366e40315b61d82f74ba227
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sat Apr 4 19:48:32 2026 +0100
# 
#     Docs: clarify local_from_check option.  Bug 1343
# 
# commit 8a4fad6c09d8e4dd29187c0d69b335145b3535ab
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Fri Apr 3 11:16:29 2026 +0100
# 
#     ARC: fix crash on bad pubkey contruction
# 
# commit c74edb1fa98418191e2b9541d515d4a262b6a378
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Thu Apr 2 20:45:13 2026 +0100
# 
#     Compiler quietening
#     
#     Broken-by: dc76912f892e
# 
# commit dc76912f892e9ed25e3eafe036529107537652a1
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Thu Apr 2 20:38:30 2026 +0100
# 
#     DKIM: harden tag parsing.  Bug 3056
# 
# commit 434f854796819554e55e1f18fa403ca0a4d8c5f8
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Thu Apr 2 12:59:04 2026 +0100
# 
#     Docs: more on EXPERIMENTAL_XCLIENT
# 
# commit 9f6219b392555f829170ad005f5a753ec39d1c14
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Thu Apr 2 12:58:20 2026 +0100
# 
#     Harden spoolfile reading
# 
# commit c8d81e125e58515384bed22d3a924801645ce4a5
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Thu Apr 2 11:06:41 2026 +0100
# 
#     Drop ancient spoolfile format support
# 
# commit de3d186fd49e0ff10386b8e7bf3d03c888e4f76b
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Mon Mar 23 10:41:01 2026 +0000
# 
#     Fix logged errno for journal file perms fail.  Bug 3209
# 
# commit aefe2ae93663cfcb3e0e356c79a592c2cfd3a33a
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Fri Mar 20 11:42:27 2026 +0000
# 
#     Debug: results of expansion conditionals
# 
# commit 8f7d8b4e9af6f89684e547df14bcf0edd4248d1b
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Fri Mar 20 10:25:58 2026 +0000
# 
#     tidying
# 
# commit 8dc59b5636252a110644c9a759e987e861e37824
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Tue Mar 17 21:06:06 2026 +0000
# 
#     Content Scan: use allocated report buffers
# 
# commit 90e7f8cfe01235c80cd9951a462c8452b5e878db
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Tue Mar 17 20:20:20 2026 +0000
# 
#     Content Scan: fix  $spam_score_int for negative values.  Bug 2987
# 
# commit 4f09add48b47e098b11e3dc5ee55afac6c9260d7
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sat Mar 14 16:32:46 2026 +0000
# 
#     ACL seen: fix refresh timestamp
# 
# commit 5830c8ed96c44fea5dc8ba0a1bcf05bc779356d3
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sat Mar 7 18:32:40 2026 +0000
# 
#     Queue runs: newest-first option.  Bug 127
# 
# commit 8f0d8de19c538395da75f338ab26db9a3b761034
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Fri Mar 6 21:06:05 2026 +0000
# 
#     Nongreedy affix wildcards for local_parts.  Bug 126
# 
# commit af8bea97c267514d085af5b845f0ad7803ac4a67
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Fri Mar 6 11:55:14 2026 +0000
# 
#     tidying
# 
# commit 9d2a8b572f81baea8be95d826ee71c87e01d1f05
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Thu Mar 5 15:21:23 2026 +0000
# 
#     check malloc return values
# 
# commit cefac7fd7405546c1ad28d752338684779f5aaa4
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Wed Mar 4 13:58:54 2026 +0000
# 
#     Testsuite: missing output file
#     
#     Broken-by: 221f20507eff
# 
# commit 221f20507effe34c8cafa105b8a3986fc56691f5
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Wed Mar 4 13:02:03 2026 +0000
# 
#     DSN: commandline options.  Bug 2560
# 
# commit 313cb880f46d5adbe6a39430439d12b6510573ac
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Tue Mar 3 17:11:23 2026 +0000
# 
#     tidying
# 
# commit f90ab861cf2faa7fb8efa62636cebd80b71ed383
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sat Feb 28 19:43:09 2026 +0000
# 
#     DKIM: Add List-Unsubscribe-Post: to signing list sample macros.  Bug 3153
# 
# commit 5dbf408be98caa1c7af21854288ba33c49ef0766
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sat Feb 28 16:14:56 2026 +0000
# 
#     Event: proc:deliver
# 
# commit f7a396e8ba595384f616543a6ac1c57998d6a9ee
# Author: Ryo Onodera <ryo@tetera.org>
# Date:   Wed Feb 25 15:19:24 2026 +0000
# 
#     Docs: typo
# 
# commit d29a01a56df80a8a94faf2b218d82e971c2dcc74
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Wed Feb 25 15:09:52 2026 +0000
# 
#     Testsuite: munge retry db dump
# 
# commit 695adf7af4c7ce611371076aee62ccfdfc730974
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Wed Feb 25 10:51:22 2026 +0000
# 
#     Testsuite: munge for pipe-open eror
# 
# commit a245b7d8b659e6b6be19bd8f25895cd5d2d33bfe
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Wed Feb 25 09:00:38 2026 +0000
# 
#     Log: avoid numeric error codes in log lines
# 
# commit e742c93d787657340ebb994c45fc8a05c0027d89
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Tue Feb 24 19:42:05 2026 +0000
# 
#     Force a 4xx reposponse to end-of-data when close fails.  Bug 3204
# 
# commit 6bb19a2ef5233b3129b71b6a062b6df80fa8a480
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Mon Feb 23 17:07:41 2026 +0000
# 
#     Logging: remove mainlog-only channel control
# 
# commit 369500e6c650f925a1057265744459cea45ccc16
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sun Feb 22 09:51:51 2026 +0000
# 
#     tidying
# 
# commit ef14c52eb0694572cbbebc4e7e33a90e7814f36c
# Author: Bernard Quatermass <bernardq@exim.org>
# Date:   Sun Feb 22 14:03:47 2026 +0000
# 
#     Remove AVE/KAV/MKS content scanning
# 
# commit 9ebb81bbf93d099233e9d89ebbf184094a230020
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sat Feb 21 14:40:26 2026 +0000
# 
#     Debug/logging: use explicit channel numbers
# 
# commit 60f069faa364a7259c2e564090a201ef7868a814
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Fri Feb 20 20:39:03 2026 +0000
# 
#     markup syntax
#     
#     Broken-by: 434eed8bce1e
# 
# commit 38d5bdbaf26a17849ba2c58bd3e230415b8851fe
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Fri Feb 20 19:41:09 2026 +0000
# 
#     Logging: handle noncontiguous channel list
# 
# commit 434eed8bce1e73e3175ef83a1feb6f07ec59c7bb
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Fri Feb 20 16:31:06 2026 +0000
# 
#     Debug: rework for small-wordsize platforms
# 
# commit 287ed9fb10be0324c3cfcc5760cc05916c1ad072
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Thu Feb 19 13:44:18 2026 +0000
# 
#     Debug: note empty list elements
# 
# commit e386f312398285a94848457ca86f3de0f4e9d0ae
# Author: Bernard Quatermass <toolsmith@quatermass.co.uk>
# Date:   Wed Feb 18 16:20:21 2026 +0000
# 
#     Update project URLs
# 
# commit d04dacc83e57a342b2b6dea7ec434a4c53e116aa
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Wed Feb 18 13:28:16 2026 +0000
# 
#     tidying
# 
# commit aede923bf9a62aa6ce308b03e85c29a5deb7c622
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Wed Feb 18 14:05:31 2026 +0000
# 
#     constifying
# 
# commit ca01bed5ec82761a1b22ca529def1ec47257a72f
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Tue Feb 17 22:24:21 2026 +0000
# 
#     bounce_charset option.  Bug 2281
# 
# commit b329cb2c4c2c3fe9300eca7fd04abdadd41f4d54
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Fri Feb 13 16:42:45 2026 +0000
# 
#     exiwhat: compress daemon listener addrs
# 
# commit a1c6e418abb14297980e705a508b5f32f67ca63a
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Fri Feb 13 11:55:51 2026 +0000
# 
#     Add daemon job counters to exiwhat.  Bug 341
# 
# commit 3982394cf435a6e0321ce81cd26269892ee64a7b
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Thu Feb 12 17:25:04 2026 +0000
# 
#     Fix logged autoreply size.  Bug 3197
# 
# commit 46e4e20c3d5ffb0680f36b6cddf5998f80a70ef4
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Thu Feb 12 15:16:09 2026 +0000
# 
#     Build: more detail on consistency-check fail
# 
# commit f9f0152615c628934e95c7ce6d4a2e74286a5e7e
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Fri Feb 6 00:30:05 2026 +0000
# 
#     tidying
# 
# commit f667087092f23247d9b9196d07e2470b5df0be77
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Wed Feb 11 14:38:41 2026 +0000
# 
#     tidying
# 
# commit 48de6fe98d29505ebef85eb8e0be93e5b13b7427
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Tue Feb 10 22:54:24 2026 +0000
# 
#     Fix delay of chained message after bounce
#     
#     Broken-by: be5901583f97
# 
# commit c4025d649a74007d7141d89253018e3f51a4a456
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Wed Feb 11 11:50:05 2026 +0000
# 
#     tidying: pid_t
# 
# commit 0e3c999792b354e7b3e7c8679e58bcca7a47c837
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Tue Feb 10 12:55:31 2026 +0000
# 
#     Debug: DSN detail
# 
# commit c767650d991e8186826a9f255852c01e8a9b9500
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Mon Feb 9 10:16:07 2026 +0000
# 
#     Debug: more uid detail
# 
# commit 1e62b4faea65113cc0a49f32ed5674ea07bfae1a
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Mon Feb 9 01:20:44 2026 +0000
# 
#     Testsuite: output changed resulting
#     
#     Broken-by: 69f2b85553ac
# 
# commit 69f2b85553ac6e24c72ee231cb9ff79b67ce520f
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sun Feb 8 17:12:54 2026 +0000
# 
#     Debug: more uid info
# 
# commit a9be8b81637d12d66601be77447f0e89b8e615d2
# Author: Bernard Quatermass <toolsmith@quatermass.co.uk>
# Date:   Sun Feb 8 15:11:08 2026 +0000
# 
#     Move to forgejo templates for issues and PRs (#3196)
#     
#     Added basic bug and feature templates for issues.
#     Added first cut of PR template.
#     Removed old github templates.
#     
#     Reviewed-on: https://code.exim.org/exim/exim/pulls/3196
#     Co-authored-by: Bernard Quatermass <toolsmith@quatermass.co.uk>
#     Co-committed-by: Bernard Quatermass <toolsmith@quatermass.co.uk>
# 
# commit 86b804ab3f2ed0acfb4789b5cc171336c1ca0bdd
# Author: Josh Soref <2119212+jsoref@users.noreply.github.com>
# Date:   Thu Feb 5 11:58:28 2026 -0500
# 
#     Docs: Bugs moved to forgejo
#     
#     Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
# 
# commit 2e50598cfe4ba43717d10680c7bb7162501c95b3
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Thu Feb 5 11:04:17 2026 +0000
# 
#     Logging: DSN
# 
# commit 84f957f04761159d5047c376638c89dc94207ee9
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Thu Feb 5 11:03:03 2026 +0000
# 
#     Build: warn on small wordsize
# 
# commit b9f061a1204bc288344fc63698c4faa949404d53
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Thu Feb 5 09:32:32 2026 +0000
# 
#     Docs: new url for bug-tracker
# 
# commit be9dbc15f1d9fd6a50e56cdc1e8dca7c0fbb858f
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sat Jan 31 14:54:12 2026 +0000
# 
#     Build: handle int-size pid_t
# 
# commit 5c9ac3a56f580ab575ddbac9b7d5688ee2ef7d31
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Tue Jan 27 14:28:15 2026 +0000
# 
#     Docs: add notes on client cert verification
# 
# commit 02b8b183a16a5bc7e10eb45cfb454002f63400c7
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Tue Jan 27 00:20:57 2026 +0000
# 
#     Build: Gnu/HURD types
#     
#     Broken-by: c33264393a1c
# 
# commit 697303f0af9f46062d3ebc3b3c336903ef8447cb
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Thu Jan 15 12:17:45 2026 +0000
# 
#     tidying
# 
# commit b7dda913d7f81dece907c536587b54a16b253e4a
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sun Jan 25 23:20:23 2026 +0000
# 
#     DANE: separately alloc the dns answer buffer for TLSA
# 
# commit 8fdbbec22de1bdb8f7cc60c13798050edcafe696
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Mon Jan 26 10:52:12 2026 +0000
# 
#         Taint: track the dns answer buffers as being tainted
# 
# commit 57ef5eda41a8c3b3c037d1075f5f6506cd82daed
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Mon Jan 26 10:51:11 2026 +0000
# 
#         Debug: wordsize mods follow-up
#     
#         Broken-by: c33264393a1c
# 
# commit c1bdd01d925954872fa7a7ffea10d3d86965a6a0
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sun Jan 25 14:27:55 2026 +0000
# 
#     tidying
# 
# commit c58cf949706491e185303796a0193e74188ae035
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Fri Jan 23 18:25:03 2026 +0000
# 
#     Fix header collapse in auto-generated bounce message.  Bug 3192
# 
# commit d8be063c6a5eeb86cb5c3628ac595e66ed5392b6
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Fri Jan 23 17:13:22 2026 +0000
# 
#     Docs: add crossref
# 
# commit c4b6cd19106c7866a0446815a54e8551141512ed
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Fri Jan 23 14:38:26 2026 +0000
# 
#     Debug: wordsize mods follow-up
#     
#     Broken-by: c33264393a1c
# 
# commit d61563f31ad33c93eae8e1c448475783c437f6e8
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Thu Jan 22 23:13:10 2026 +0000
# 
#     Tidying: pid string formatting
# 
# commit c33264393a1c70c92a1fccdfa0640988da26351e
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Thu Jan 22 19:30:43 2026 +0000
# 
#     Debug: use defined-width type for bitmap
# 
# commit 3ec1c65df8dd1cedb3f77666107a860000dd280b
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Thu Jan 22 18:21:00 2026 +0000
# 
#     tidying
# 
# commit 39d48fd851886268ae28c2dc13bdf68cd227966d
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Thu Jan 22 11:59:26 2026 +0000
# 
#     Testsuite: diagnostics
# 
# commit 2d4c73da7de2c0bfc47d5321f158f6620c008b7a
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Wed Jan 21 13:21:27 2026 +0000
# 
#     More coverage in "-bV" datatype size diagnostics
# 
# commit c052ac7dc3c71e083317d8bcd234fa32a0f964ab
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Tue Jan 20 20:20:27 2026 +0000
# 
#     Debug: add "regex" channel
# 
# commit 6f268ee4c14cdde79b29809ef25179fb6b60689b
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Tue Jan 20 18:48:15 2026 +0000
# 
#     Debug: ensure extanded channels propagated
#     
#     Broken-by: bdb65dd6fd1a
# 
# commit bdb65dd6fd1af3f7d186adc05bfe27f11b1403d4
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Mon Jan 19 14:52:58 2026 +0000
# 
#     Debug: add "macro" channel
# 
# commit 7d5f543c395691cdffdba8013ee4684834f6cb01
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Mon Jan 19 00:57:15 2026 +0000
# 
#     DMARC (native): ignore tags failing verification
# 
# commit f92a63ebf6bcf991a09a0429808c61e2407bb10f
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Fri Jan 16 20:01:33 2026 +0000
# 
#     DNS: fix memory leak
# 
# commit fbbcf70e20371573a096856b5342fc3a661d4488
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Fri Jan 16 20:17:50 2026 +0000
# 
#     Testsuite: fix DMARC testcase artifact
#     
#     Broken-by: 6096b50a869d
# 
# commit 7a7e1513914f2ded45e5e51426d77ec78b4f6d54
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Tue Jan 13 21:20:07 2026 +0000
# 
#     tidying
# 
# commit 4dd582c432c7f8817d1484b91bb5607851d85ebb
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Tue Jan 13 14:57:07 2026 +0000
# 
#     Tidying: move ETRN handling out-of-line
# 
# commit 40d39e3522a6f66e21591341c33b33c05864287d
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Fri Jan 9 17:01:11 2026 +0000
# 
#     tidying
# 
# commit 63483b7c18cadbf73ca669945e9775330cc7857a
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Fri Jan 2 01:16:04 2026 +0000
# 
#     Testsuite: TFO results per Linux
# 
# commit fd0f59e7ba8bf0942fd09945cbd5b7611fd5732d
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Tue Dec 30 15:49:06 2025 +0000
# 
#     Docs: tweak decription of def: expansion condition
# 
# commit 13835a3c1e057efad7da269c0f93bf2eac850205
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Mon Dec 29 00:14:20 2025 +0000
# 
#     LDAP: support module load triggered by variable access
# 
# commit df74f182f53ce00d9f6db5c18a51d6fa242b64ce
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sat Dec 27 16:00:42 2025 +0000
# 
#     Postgres version variances
# 
# commit af14bc8cf9b19e8a53131c209d6cff2b82d6b476
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sat Dec 27 15:47:45 2025 +0000
# 
#     string-handling: insert space for empty item when building stringlist
# 
# commit d06324cdd0ee9fc3180810bc5e818da715f0be23
# Author: Bernard Quatermass <toolsmith@quatermass.co.uk>
# Date:   Fri Dec 26 15:31:18 2025 +0000
# 
#     Readme.pod removed in favour of up-to-date README.md
# 
# commit 84030909c23fbdd6aabcd38b6853d6097f33982d
# Author: Bernard Quatermass <toolsmith@quatermass.co.uk>
# Date:   Fri Dec 26 15:29:40 2025 +0000
# 
#     last push to github
# 
# commit e38b380adcf53f4f9771d0e1444afb1f4b79a801
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Fri Dec 26 14:25:51 2025 +0000
# 
#     func prototypes
# 
# commit 71f1ce17a7ceda7ec778b84188d74419ab082e1e
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Fri Dec 26 14:10:30 2025 +0000
# 
#     constify
# 
# commit a87bd5b303e01724fa4cec576a5d824186a8074c
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Thu Dec 25 09:16:09 2025 +0000
# 
#     daemon_modules_preload option
# 
# commit 928e43f00d3b88e2684fdd530545932895ef684b
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Wed Dec 24 14:51:36 2025 +0000
# 
#     Fix non-LOOKUP_MODULE_DIR build
#     
#     Broken-by: b3881e820e87
# 
# commit d6c8c56fd08000ea9da90d4cf506870a6a7e205b
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Wed Dec 24 14:18:00 2025 +0000
# 
#     Docs: build note
# 
# commit b3881e820e87ccaabfb2c4a08d3cd07974c59b75
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Wed Dec 24 13:48:55 2025 +0000
# 
#     Move lookup options & variables from global to module
# 
# commit 711a7387e0200c5dda3f9d0a744dae18afb80351
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Tue Dec 23 13:07:35 2025 +0000
# 
#     Keep handle open for module dir
# 
# commit bfd2759002fd2b5b376a1544e8fc59f313dc4e9c
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Mon Dec 22 10:10:01 2025 +0000
# 
#     Disable -bI:sieve for sieve-less builds.  Bug 3184
# 
# commit 6d75a89d2f506f88cfddf092440af5b2d54f07df
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sun Dec 21 18:01:26 2025 +0000
# 
#     Invalidate more variables at smtp-reset
# 
# commit 91586f969ddddc4ff4339167c05141141a4b6272
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sun Dec 21 15:54:59 2025 +0000
# 
#     Cmdline option for list of dynamic-load modules
# 
# commit 8783c47fcae5a69aea91cb37da694070185569e5
# Author: Andreas Metzler <ametzler@bebt.de>
# Date:   Sun Dec 21 12:42:13 2025 +0000
# 
#     typo, c'n'p errors
# 
# commit cb28da1d541b29ce86ea862f00f6c278117cbcb9
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Fri Dec 19 20:27:36 2025 +0000
# 
#     Tidying
# 
# commit c98d480fd5f18febf9c8cdb3ff730429fb4bc133
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sat Dec 20 11:24:15 2025 +0000
# 
#     Testsuite: more munge tweaks
# 
# commit afd61b0c0936c7d74829a0f790488600afc3b518
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Fri Dec 19 20:26:00 2025 +0000
# 
#     Testsuite: munge for build variants
# 
# commit 9abb9e4e0ef53f6ac015cf6b7de07d345da00a35
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Fri Dec 19 12:54:22 2025 +0000
# 
#     Testsuite: file-dependencies for testcase groups
# 
# commit e46d2bb2bd33044b45cd9cc508a50f63d91aa0f8
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Fri Dec 19 10:29:58 2025 +0000
# 
#     Utils: eliminate use of asprintf()
# 
# commit 74f79e9cf49dfce8e68d723b9b7eb917c8e4840c
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Fri Dec 19 00:51:19 2025 +0000
# 
#     Testsuite: mysql case debugging
# 
# commit fc6c642e1bfecab156b65c992697dbf1b3002b8b
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Thu Dec 18 19:13:06 2025 +0000
# 
#     Build: Solaris
# 
# commit 7b81731f51c53643c30d330596779a1c2f7eeb73
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Thu Dec 18 16:04:24 2025 +0000
# 
#     Build: Solaris
# 
# commit 0a831ddcab09196dd183306155482fdd8d98c72d
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Thu Dec 18 13:44:35 2025 +0000
# 
#     Docs: typo
#     
#     Broken-by: e76fd60f5560
# 
# commit 0b98b3d655993f08104b9caf71be382cee57bd79
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Thu Dec 18 13:35:42 2025 +0000
# 
#     Avoid function name conflict with Solaris libgen
# 
# commit dbb8fa7d6275bb2a21d9a7803b3ab902a2e15301
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Thu Dec 18 13:30:02 2025 +0000
# 
#     Utils; fix exim_dumpdb build for non- pipe-connect
# 
# commit 7873db47ac111972cc46b42e741fd437ec7a9869
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Thu Dec 18 11:03:22 2025 +0000
# 
#     Add notes on alternates to prototype Makefile
# 
# commit de32296674ed4e57cb5a8efa3bb3f2c838d03d1a
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sun Nov 30 10:19:10 2025 +0000
# 
#     add version checking and grooming for hintsdb records
# 
# commit 73d3b73c090557b9f11cef78623cf11b81e9c7a9
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sat Nov 29 21:02:12 2025 +0000
# 
#     track tainted values through hintsdb storage
# 
# commit e76fd60f5560db45225a4e2a35c5c4a27217464b
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sat Nov 29 12:56:09 2025 +0000
# 
#     exim_fixdb for lookup-dbm files
# 
# commit a51a01811d1b0984603662609a6b7f0845b34387
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Fri Nov 28 12:45:10 2025 +0000
# 
#     exim_dumpdb for lookup-dbm files
# 
# commit 53e771b0ddaa89477793c8955ed474acf42c32b5
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Thu Dec 18 10:50:33 2025 +0000
# 
#     Docs: more notes on DBM lookups
# 
# commit 7e494d1b0fe256995d77caaf8bbb29630f519ff0
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Thu Dec 18 10:45:37 2025 +0000
# 
#     Testsuite: output change
#     
#     Broken-by: 4a1161712759
# 
# commit fda16457fbba6a11abdb09370ce21ea09f9e64f7
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sun Dec 14 15:32:48 2025 +0000
# 
#     proxy-protocol module
# 
# commit ee143587852f7a6937a6997da730b4e02e427fc4
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sun Dec 14 18:23:37 2025 +0000
# 
#     xclient module
# 
# commit 732c68f17cb249e6d40727ab0a043ded0fed2247
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sun Dec 14 12:52:18 2025 +0000
# 
#     socks module
# 
# commit 5103de3423da39e0edcbd556ab2633c8d5c09c5f
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Wed Dec 17 15:56:32 2025 +0000
# 
#     dscp module
# 
# commit d09946f296565ebdd640b07d9bee0b30283d186d
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Mon Dec 15 15:32:12 2025 +0000
# 
#     Fix DISABLE_ESMTP_LIMITS build
# 
# commit 227e467d611d9c5bcb942f2b2635e4466a8c7fd6
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Mon Dec 15 13:43:19 2025 +0000
# 
#     Fast-ramp: do not notify daemon during high load-avg
# 
# commit 4a11617127599c2a40552f5f7e9bf0e863f10e8d
# Merge: d9081efac d46a67277
# Author: Heiko Schlittermann (HS12-RIPE) <hs@schlittermann.de>
# Date:   Tue Dec 16 12:55:32 2025 +0100
# 
#     Merge branch 'exim-4.99+fixes' (EXIM-Security-2025-12-09.1, CVE-2025-67896)
#     
#     * exim-4.99+fixes:
#       doc: update about CVE-2025-67896
#       fix: release process docbook2texi
#       EXIM-Security-2025-12-09.1
#       doc: update Changelog (relates #7)
#       Testsuite: add testing for sqlite db external usage
#       return scan result into key parameter
#       move to blobs for keys as well as data. refactor sqlite3_ calls to reduce duplication
#       new: xtextencode
#       CVE-2025-30232
#       Squashed from fix/cve-2025-26794 (fixes CVE-26794)
# 
# commit d9081efaca19d0bef80c0dbc3ffa045207f3171e
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sat Dec 13 23:52:08 2025 +0000
# 
#     Re-order module init in "-bV" support
# 
# commit bc92408a988ec965777e2dcff207cb14b5cc2518
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sat Dec 13 23:14:20 2025 +0000
# 
#     Testsuite: missing output file
# 
# commit acdc767008460bf5e86a1bf39b43e8da7bd6e90d
# Author: Andreas Metzler <ametzler@bebt.de>
# Date:   Sat Dec 13 14:52:14 2025 +0000
# 
#     Build: linker invocation ordering
#     
#     Apparently some linkers are sensitive to positioning
#     of library args in the commandline
# 
# commit 6b109f69cfc8804105f373dd22936d7b9edd66f9
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sat Dec 13 11:58:46 2025 +0000
# 
#     Debug: tweaks
# 
# commit c88bb63efafd2c897d37cedaffe4e1bcbe8d749e
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Tue Dec 9 11:52:18 2025 +0000
# 
#     Docs: Fix description of DMARC-related variable availability
# 
# commit b064cc0ef055fdcb91c971234b756e494f64e347
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Tue Dec 9 10:59:13 2025 +0000
# 
#     DMARC: rework module API
# 
# commit abef0531c0676680cab9b1fbd91da4482e35d45d
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Mon Dec 8 16:02:57 2025 +0000
# 
#     DMARC: fix for empty env-from.  Bug 1994
# 
# commit c038d63e56be080c1bf8043b2fc5c3aac4b57544
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Mon Dec 8 11:37:18 2025 +0000
# 
#     DMARC: downgrade detailed result logging to debug
# 
# commit 01c6fb5f07c0d7555e45ab0409dd8e551359dc2c
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sun Dec 7 14:16:30 2025 +0000
# 
#     tidying
# 
# commit 6b77712fcc9e4daf673751f0302c1ae003ed7dce
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sun Dec 7 13:17:41 2025 +0000
# 
#     DMARC: fix retval for module init
# 
# commit f50972904e5a48a822286a2e7670bc076b7c2b97
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sun Dec 7 11:39:05 2025 +0000
# 
#     Docs: typo
# 
# commit 3a8b7ebc46f38769692a73ee8c06150c21beeb35
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Fri Dec 5 16:40:36 2025 +0000
# 
#     testsuite: munging
#     
#     Broken-by: 266b6e714cf4
# 
# commit 9b1e9cf2d17390c02a2107180852f051099f4fee
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Fri Dec 5 12:46:36 2025 +0000
# 
#     Testsuite: munge for some more build-option cases
# 
# commit 46846fc1f6254cb6a1ef14babc40fcae9f5baea1
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Fri Dec 5 12:18:39 2025 +0000
# 
#     taint: better tracking for dns lookup results
# 
# commit c5007722d5f5e86953912ea75b13575577e4f98a
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Thu Dec 4 22:22:17 2025 +0000
# 
#     Perl: tweak dns lookup fail cases
# 
# commit 953aa1b28d32412b22aee0b1aa44a3e67f95c9da
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Thu Dec 4 22:21:15 2025 +0000
# 
#     Testsuite: output changes resulting
#     
#     Broken-by: c5d279450750
# 
# commit c5d27945075095ee56c92eee36a81aa239a53494
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Wed Dec 3 15:06:10 2025 +0000
# 
#     DMARC: logging
# 
# commit 266b6e714cf4a95d412a81197516e6dcb1454214
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Wed Dec 3 14:10:50 2025 +0000
# 
#     Debug: redefine the early-debug macro to be also active after main-debug init
# 
# commit b8d16e2a4e8e2016e8cd03402705a6f47f1e2b5b
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Wed Dec 3 12:16:59 2025 +0000
# 
#     SPF: support * in ACL condition
# 
# commit 9536defc5cb25beeb00491f6b00c454aadfdb7a5
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Tue Dec 2 22:59:04 2025 +0000
# 
#     SPF: logging
# 
# commit 29b229ef956c08381bb5514a4da3fecf89148441
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Tue Dec 2 11:19:49 2025 +0000
# 
#     Build: GCC on Solaris
# 
# commit 5f52bd307cccd1a840ab4e0b761add7b3d1946cd
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Mon Dec 1 22:43:53 2025 +0000
# 
#     Build: Solaris compilers
# 
# commit 1b88acb34288b0858d0f26c1ab2ebc752d046595
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Mon Dec 1 18:17:46 2025 +0000
# 
#     Fix nonstandard DNS server port number use from perl.  Bug 3177
#     
#     Broken-by: 040f2adb9003
# 
# commit 2cb9ab582d2e656787f82ce613eada251f30adb9
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sun Nov 30 20:27:22 2025 +0000
# 
#     testsuite: directory permissions
# 
# commit 2a87c1fb9d76cd00cc1815c9b56b8bea47e409e1
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sun Nov 30 19:41:43 2025 +0000
# 
#     perl-spf: fix undef whine
# 
# commit 6096b50a869de851b710262dcc479f61e68c1bc7
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Tue Nov 18 18:11:10 2025 +0000
# 
#     dmarc-native: fix rua/ruf verify
# 
# commit 21811ff3fd95a5325c665cb28b1d65e612a179dc
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Tue Nov 18 18:03:39 2025 +0000
# 
#     Testsuite: fix for perl-spf dynamic build
# 
# commit 34b791b30d336dd78db0ded8c9b9790fdfa8f3c9
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sun Nov 16 15:23:56 2025 +0000
# 
#     fix build with perl-spf
# 
# commit e4e66eda1683cfd3a6658b39c21be91a7ca99df0
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sat Nov 29 14:01:04 2025 +0000
# 
#     local_scan: bump API version, for smtp_fflush()
#     
#     Broken-by: 9682a4923d1d
# 
# commit 89c878362b94d35fa662e02a8834238b699762d7
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sat Nov 29 12:51:38 2025 +0000
# 
#     extend string-formatting facility
# 
# commit 2acf3494b804d16eed8b6b11408f246beb337e35
# Author: Heiko Schlittermann (HS12-RIPE) <hs@schlittermann.de>
# Date:   Sat Nov 29 12:26:33 2025 +0100
# 
#     testing: add note about home dir requirement for testing user
# 
# commit 63927ea2f58c1870baf0113272c5d92423225dfe
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Thu Nov 27 11:56:36 2025 +0000
# 
#     Testsuite: output changes resulting
# 
# commit 535d186b02f437431fc1ef1ced21c7c47242c9d0
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Thu Nov 27 11:46:07 2025 +0000
# 
#     Testsuite: add testing for sqlite db external usage
# 
# commit c9ab62b819f2b62f51034d9210703b1167ed1d53
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Wed Nov 26 09:58:04 2025 +0000
# 
#     Docs: fix wellknown_advertise_hosts description
#     
#     Broken-since: e0b3815dc39
# 
# commit 5921ece54a48e3d773293b280722fb0e34a66539
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Mon Nov 24 12:27:09 2025 +0000
# 
#     Testsuite: add testcases for dbmjz/dbmnz under sqlite
# 
# commit 66a452cb6128ddc0805eac37230afd77d6fb168c
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sun Nov 23 14:18:54 2025 +0000
# 
#     Local-scan: fix API
#     
#     Broken-by: 7b4e2a15a529
# 
# commit 9ecbb33982299a8d83a72d4dcd951949e5cfb81e
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sun Nov 23 10:35:12 2025 +0000
# 
#     Local-scan: bump API minor version
#     
#     Things like constification and ifdeffed-feature removal
#     have, strictly, affected the API definition
# 
# commit 1fd121860e713f4d2783be54939b92a297a82dd4
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Fri Nov 21 14:37:12 2025 +0000
# 
#     fix non-DANE build
#     
#     Broken-by: d2353fcd8f60
# 
# commit d2353fcd8f602a1f4fdeb31ae006e6b7bc46b349
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Fri Nov 21 14:17:39 2025 +0000
# 
#     fix TLS held-open verify to delivery
# 
# commit 71c0636d35ad0907f395ac84a68179824bc19a10
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Fri Nov 21 11:56:32 2025 +0000
# 
#     Testsuite: fix callout lazy-close testcase
# 
# commit 3c24de41ada52816d2890033b99eda0543a06b06
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Fri Nov 21 11:54:08 2025 +0000
# 
#     debug: indents
# 
# commit 7358e018d0a5cd62248b2d06ca440c93d5e20c63
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Fri Nov 21 10:21:46 2025 +0000
# 
#     Docs: add note on RFC line length limits
# 
# commit 68841125d8ba6168ae38de296273bdcd4df592c8
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sun Nov 16 15:16:53 2025 +0000
# 
#         tidying
# 
# commit 11f6b4c7d34582a3b046345dd67d576b3fef920f
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Fri Nov 14 14:17:14 2025 +0000
# 
#     DMARC: fix history-file ARC reporting, under dynamic build
# 
# commit 03c4dd24276bfb43f5f803fa7c8cc83076f1c485
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Fri Nov 14 12:11:10 2025 +0000
# 
#     LDAP: fix version report under dynamic build
# 
# commit 726e6f798bc2a18b019a23ba48ee6ddbf68ebf7f
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Fri Nov 14 11:58:10 2025 +0000
# 
#     Build: fix spf dynamic-module build
#     
#     Broken-by: 998636a4f849
# 
# commit 44ccabdc9737c14d6845fdeee89083490b8cc61b
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Thu Nov 13 11:36:48 2025 +0000
# 
#     Testsuite: munging
# 
# commit 86161af9d94e2beeb7ad630e80247ef791c22313
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Wed Nov 12 21:40:04 2025 +0000
# 
#     Experimental native DMARC
#     
#     Bug 3140
# 
# commit ed97fea3e920289ac87f1736146c68f4ce3050a8
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Wed Nov 12 14:37:42 2025 +0000
# 
#     Fix non-openldap2 build
#     
#     Broken-by: 998636a4f849
# 
# commit 998636a4f8493855a351bb1080fc7687269cb9f9
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Wed Nov 12 11:20:42 2025 +0000
# 
#     library versions
# 
# commit 1cb819c5032dc7797999c60c023f65c842dcf5d4
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sun Nov 9 12:15:07 2025 +0000
# 
#     Build: quietening
# 
# commit 3bac7a1bac4fbccea548b3f8ed9a788b8b41d943
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Mon Nov 10 10:09:49 2025 +0000
# 
#     Fix CYRUS_SASLAUTHD_SOCKET build.  Bug 3176
#     
#     Broken-by: 0cf5f1656eff
# 
# commit 0cf5f1656eff31bcc1131ca047030f4866b4224b
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Fri Nov 7 17:50:28 2025 +0000
# 
#     Retire "pwcheck" expansion condition
# 
# commit eaf7eae8474ded19ec64022f71d1b9e610013d5c
# Author: Samuel Thibault <samuel.thibault@ens-lyon.org>
# Date:   Fri Nov 7 09:37:04 2025 +0000
# 
#     Fix file open modes for Gnu/Hurd.  Bug 3175
# 
# commit d3dd48e449dcd329126c4365c8f92335c8afa350
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Thu Nov 6 10:55:05 2025 +0000
# 
#     fix radius expansion condition
# 
# commit ea41f8373bd699697585193862afab334a62ec1a
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Wed Nov 5 10:17:04 2025 +0000
# 
#     Build: more files for cscope
# 
# commit 140c289d1170334e29ee3fd4e2c385cdb7bd837c
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Tue Nov 4 18:44:53 2025 +0000
# 
#     Fix remote-delivery DNS defers.  Bug 3172
#     
#     Broken-by: 6748707c6446
# 
# commit e5c1a2ba01a0bd00615ec1bfd05b47c6127bec38
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Tue Nov 4 14:52:05 2025 +0000
# 
#     Fix local delivery defers
#     
#     Broken-by: 3cee6033bae8
# 
# commit 826a1788778a6655a25b7d157ff4000531b9a215
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Tue Nov 4 12:46:33 2025 +0000
# 
#         Unbreak Solaris build
#     
#         Broken-by: dea05068da8c
# 
# commit acfcde05f2800db3bf24ba521c62599d95713a08
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Tue Nov 4 11:48:08 2025 +0000
# 
#     Unbreak Solaris build
#     
#     Broken-by: dea05068da8c
# 
# commit 2486cac31dc0ce68d3705695c6fe61d68db51d31
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Tue Nov 4 11:12:11 2025 +0000
# 
#     Unbreak Solaris build
#     
#     Broken-by: dea05068da8c
# 
# commit e5dc9209cde969a66ac528c2d5fc5a244c5f5999
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Tue Nov 4 09:58:43 2025 +0000
# 
#     compiler quietening
# 
# commit dea05068da8cbd8cb9c9707aa8c3e432bd967e55
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Mon Nov 3 16:40:26 2025 +0000
# 
#     fix constification
#     
#     Broken-by: 96f8f12e212b
# 
# commit dd081f9869df90ab7f7d7911c1bdd3ef976f1439
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Wed Oct 29 12:13:09 2025 +0000
# 
#     constify
# 
# commit c7b6065cb9b945155491477297662bae458919d4
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sun Nov 2 21:06:43 2025 +0000
# 
#     RFC 2047: fix encode operation.  Bug 3168
# 
# commit b93ee3883ef8a11c440c5519812f3cb6c074a02f
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sun Nov 2 19:58:29 2025 +0000
# 
#     Build: quieten sqlite-hints -Wunused-function build
# 
# commit e35ada6dc21d111ac30c30e8b9d792fbca55c9b4
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sun Nov 2 14:34:25 2025 +0000
# 
#     TLS: log key-exchange group
# 
# commit 195bf3719bb6d673f6730b221cfcd0dfec0281b4
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sun Nov 2 14:31:22 2025 +0000
# 
#         Revert "Retire identd support"
#     
#         This reverts commit 106c3eb8ee31297588d5ec0555195be966dfd7b2.
# 
# commit d021d9bddfbe66cfec7027999aa3ee501198e20a
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Sun Nov 2 10:12:55 2025 +0000
# 
#     tidying
# 
# commit baad2e56cdcffa83e2e5d138537dcef858bdf5b6
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Fri Oct 31 12:59:39 2025 +0000
# 
#     Fix duplicate address processing vs. continued-transport
#     
#     Broken-by: 79344067b96a
# 
# commit 50a6abf200c5116e9b86f12afbcc973ccd021261
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Fri Oct 31 12:55:40 2025 +0000
# 
#     Testsuite: expand store_free() checking
# 
# commit 3858878623272c18ad8b4d3f856c3a3dbe22577c
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Thu Oct 30 09:42:01 2025 +0000
# 
#     Fix taint status for dbm lookups.  Bug 3169
#     
#     Broken-by: c66a6edf7ba8
# 
# commit 054562b47653f23c3460ef9e21123683f33431b4
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Tue Oct 28 17:05:13 2025 +0000
# 
#     Docs: note C11 compiler required for build
# 
# commit 52171c081d7d8f15665211aa4e731eafea279361
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Tue Oct 28 14:25:50 2025 +0000
# 
#     Testsuite: drop identd
#     
#     Broken-by: 106c3eb8ee31
# 
# commit eb39a357cb57fdf83b89c9039dd6e7a7983fd5df
# Merge: d997c8cd3 dddc92898
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Tue Oct 28 13:26:04 2025 +0000
# 
#     Merge branch '4.next'
# 
# commit d997c8cd39b2f4e23741c02476b5b5e7cbd56df1
# Author: Jeremy Harris <jgh146exb@wizmail.org>
# Date:   Tue Oct 28 12:40:40 2025 +0000
# 
#     Docs: strip changebars

diff --git a/src/Makefile b/src/Makefile
index 9c08915ef..eac96323e 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -29,6 +29,9 @@ RM_COMMAND=/bin/rm
 
 buildname=$${build:-`$(SHELL) scripts/os-type`-`$(SHELL) scripts/arch-type`}$${EXIM_BUILD_SUFFIX:+.$$EXIM_BUILD_SUFFIX}
 
+# Quieten gnumake wrt. sub-make directories
+GNUMAKEFLAGS=--no-print-directory
+
 # 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.
 # If Local/Makefile-<buildname> exists, it is read too.
@@ -63,12 +66,14 @@ build-directory:
 	  (mkdir $$builddir; cd $$builddir; $(SHELL) ../scripts/MakeLinks)";
 
 checks:
-	$(SHELL) scripts/source_checks
+	@$(SHELL) scripts/source_checks
 
 # The "configure" target ensures that the build directory exists, then arranges
 # to build the main makefile from inside the build directory, by calling the
 # Configure-Makefile script. This does its own dependency checking because of
 # the optional files.
+# We always run the script (as there is no actual"configure" file)
+# but it quietly dooes nothing if nothing is needed.
 
 configure: checks build-directory \
 		scripts/lookups-Makefile scripts/drivers-Makefile
@@ -119,12 +124,17 @@ distclean: clean_doc
 cscope.files: FRC
 	echo "-q" > $@
 	echo "-p3" >> $@
-	-bd=build-$(buildname); [ -d $$bd ] && echo -e "$$bd/config.h\n$$bd/Makefile" >> $@
+	-bd=build-$(buildname); [ -d $$bd ] && echo -e "$$bd/config.h\n$$bd/version.h\n$$bd/Makefile" >> $@
 	find src Local OS exim_monitor -name "*.[cshyl]" -print \
+		    -o -name "*.src" -print \
 		    -o -name "os.[ch]*" -print \
 		    -o -name "*akefile*" -print \
 		    -o -name config.h.defaults -print \
 		    -o -name EDITME -print >> $@
+	echo scripts/* >> $@
+
+cscope:	cscope.files
+	cscope -b -p 3
 
 FRC:
 
diff --git a/src/OS/Makefile-Base b/src/OS/Makefile-Base
index 5a15b0a6b..945c7c38d 100644
--- a/src/OS/Makefile-Base
+++ b/src/OS/Makefile-Base
@@ -36,8 +36,9 @@ FE       = $(FULLECHO)
 # up-to-date. Then the os-specific source files and the C configuration file
 # are set up, and finally it goes to the main Exim target.
 
-all:       utils exim dynmodules
-config:    $(EDITME) checklocalmake Makefile os.c config.h version.h version.sh macro.c
+CONFIG_DEPS = Makefile os.c config.h version.h version.sh macro.c
+
+all:       $(EDITME) checklocalmake $(CONFIG_DEPS) utils exim dynmodules
 
 exim_openssl exim_gnutls: clean exim
 	cp exim $@
@@ -236,16 +237,15 @@ macro_predef: $(OBJ_MACRO)
 	$(FE)$(LNCC) -o $@ $(LFLAGS) $(OBJ_MACRO)
 
 macro.c: macro_predef
-	./macro_predef > macro.c
+	./macro_predef > tmpfile
+	mv tmpfile $@
 
 # 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 utils \
-	buildauths buildlookups buildrouters \
-        buildtransports buildmisc dynmodules checklocalmake clean
+.PHONY: all config utils dynmodules clean
 
 
 utils: $(EXIM_MONITOR) exicyclog exinext exiwhat \
@@ -261,12 +261,12 @@ buildconfig: buildconfig.c
 	$(FE)$(CC) $(CFLAGS) $(INCLUDE) -o buildconfig buildconfig.c $(LIBS)
 
 
-util_deps: config ../src/utils/msgid.frag
+UTIL_DEPS = $(CONFIG_DEPS) ../src/utils/msgid.frag
 
 # Target for the exicyclog utility script
-exicyclog: util_deps ../src/utils/exicyclog.src
-	@rm -f exicyclog
-	@. ./version.sh && sed \
+exicyclog: $(UTIL_DEPS) ../src/utils/exicyclog.src
+	$(FE)rm -f exicyclog
+	$(FE). ./version.sh && sed \
 	  -e "s?PROCESSED_FLAG?This file has been so processed.?"\
 	  -e "/^# /p" \
 	  -e "/^# /d" \
@@ -292,15 +292,15 @@ exicyclog: util_deps ../src/utils/exicyclog.src
 	  -e "s?^?# Insert ?" \
 	  -e "r ../src/utils/msgid.frag" \
 	  ../src/utils/exicyclog.src > exicyclog-t
-	@mv exicyclog-t exicyclog
-	@chmod a+x exicyclog
-	@./exicyclog -v 2>&1 >/dev/null
-	@echo ">>> exicyclog script built"
+	$(FE)mv exicyclog-t exicyclog
+	$(FE)chmod a+x exicyclog
+	$(FE)./exicyclog -v 2>&1 >/dev/null
+#	$(FE)echo ">>> exicyclog script built"
 
 # Target for the exinext utility script
-exinext: util_deps ../src/utils/exinext.src
-	@rm -f exinext
-	@. ./version.sh && sed \
+exinext: $(UTIL_DEPS) ../src/utils/exinext.src
+	$(FE)rm -f exinext
+	$(FE). ./version.sh && sed \
 	  -e "s?PROCESSED_FLAG?This file has been so processed.?"\
 	  -e "/^# /p" \
 	  -e "/^# /d" \
@@ -316,15 +316,15 @@ exinext: util_deps ../src/utils/exinext.src
 	  -e "s?^?# Insert ?" \
 	  -e "r ../src/utils/msgid.frag" \
 	  ../src/utils/exinext.src > exinext-t
-	@mv exinext-t exinext
-	@chmod a+x exinext
-	@./exinext -v 2>&1 >/dev/null
-	@echo ">>> exinext script built"
+	$(FE)mv exinext-t exinext
+	$(FE)chmod a+x exinext
+	$(FE)./exinext -v 2>&1 >/dev/null
+#	$(FE)echo ">>> exinext script built"
 
 # Target for the exiwhat utility script
-exiwhat: config ../src/utils/exiwhat.src
-	@rm -f exiwhat
-	@. ./version.sh && sed \
+exiwhat: $(CONFIG_DEPS) ../src/utils/exiwhat.src
+	$(FE)rm -f exiwhat
+	$(FE). ./version.sh && sed \
 	  -e "s?PROCESSED_FLAG?This file has been so processed.?"\
 	  -e "/^# /p" \
 	  -e "/^# /d" \
@@ -341,15 +341,15 @@ exiwhat: config ../src/utils/exiwhat.src
 	  -e "s?EXIM_VARIANT_VERSION?$${EXIM_VARIANT_VERSION}?" \
 	  -e "s?RM_COMMAND?$(RM_COMMAND)?" \
 	  ../src/utils/exiwhat.src > exiwhat-t
-	@mv exiwhat-t exiwhat
-	@chmod a+x exiwhat
-	@./exiwhat -v 2>&1 >/dev/null
-	@echo ">>> exiwhat script built"
+	$(FE)mv exiwhat-t exiwhat
+	$(FE)chmod a+x exiwhat
+	$(FE)./exiwhat -v 2>&1 >/dev/null
+#	$(FE)echo ">>> exiwhat script built"
 
 # Target for the exim_checkaccess utility script
-exim_checkaccess: config ../src/utils/exim_checkaccess.src
-	@rm -f exim_checkaccess
-	@. ./version.sh && sed \
+exim_checkaccess: $(CONFIG_DEPS) ../src/utils/exim_checkaccess.src
+	$(FE)rm -f exim_checkaccess
+	$(FE). ./version.sh && sed \
 	  -e "s?PROCESSED_FLAG?This file has been so processed.?"\
 	  -e "/^# /p" \
 	  -e "/^# /d" \
@@ -360,17 +360,17 @@ exim_checkaccess: config ../src/utils/exim_checkaccess.src
 	  -e "s?EXIM_RELEASE_VERSION?$${EXIM_RELEASE_VERSION}?" \
 	  -e "s?EXIM_VARIANT_VERSION?$${EXIM_VARIANT_VERSION}?" \
 	  ../src/utils/exim_checkaccess.src > exim_checkaccess-t
-	@mv exim_checkaccess-t exim_checkaccess
-	@chmod a+x exim_checkaccess
-	# @./exim_checkaccess -v 2>&1 >/dev/null
-	@echo ">>> exim_checkaccess script built"; echo ""
+	$(FE)mv exim_checkaccess-t exim_checkaccess
+	$(FE)chmod a+x exim_checkaccess
+#	@./exim_checkaccess -v 2>&1 >/dev/null
+#	$(FE)echo ">>> exim_checkaccess script built"; echo ""
 
 # Target for the Exim monitor start-up script
-eximon: config ../src/utils/eximon.src ../OS/eximon.conf-Default \
+eximon: $(CONFIG_DEPS) ../src/utils/eximon.src ../OS/eximon.conf-Default \
           ../Local/eximon.conf
-	@rm -f eximon
+	$(FE)rm -f eximon
 	$(SHELL) $(SCRIPTS)/Configure-eximon
-	@. ./version.sh && sed \
+	$(FE). ./version.sh && sed \
 	  -e "s?PROCESSED_FLAG?This file has been so processed.?"\
 	  -e "/^# /p" \
 	  -e "/^# /d" \
@@ -383,15 +383,15 @@ eximon: config ../src/utils/eximon.src ../OS/eximon.conf-Default \
 	  -e "s?EXIM_RELEASE_VERSION?$${EXIM_RELEASE_VERSION}?" \
 	  -e "s?EXIM_VARIANT_VERSION?$${EXIM_VARIANT_VERSION}?" \
 	  ../src/utils/eximon.src >> eximon
-	@./eximon -v 2>&1 >/dev/null
-	@echo ">>> eximon script built"; echo ""
+	$(FE)./eximon -v 2>&1 >/dev/null
+#	$(FE)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: util_deps ../src/utils/exigrep.src
-	@rm -f exigrep
-	@. ./version.sh && sed \
+exigrep: $(UTIL_DEPS) ../src/utils/exigrep.src
+	$(FE)rm -f exigrep
+	$(FE). ./version.sh && sed \
 	  -e "s?PROCESSED_FLAG?This file has been so processed.?"\
 	  -e "/^# /p" \
 	  -e "/^# /d" \
@@ -407,14 +407,14 @@ exigrep: util_deps ../src/utils/exigrep.src
 	  -e "s?^?# Insert ?" \
 	  -e "r ../src/utils/msgid.frag" \
 	  ../src/utils/exigrep.src > exigrep-t
-	@mv exigrep-t exigrep
-	@chmod a+x exigrep
-	@./exigrep --version 2>&1 >/dev/null
-	@echo ">>> exigrep script built"
-
-exim_msgdate: util_deps ../src/utils/exim_msgdate.src
-	@rm -f exim_msgdate
-	@. ./version.sh && sed \
+	$(FE)mv exigrep-t exigrep
+	$(FE)chmod a+x exigrep
+	$(FE)./exigrep --version 2>&1 >/dev/null
+#	$(FE)echo ">>> exigrep script built"
+
+exim_msgdate: $(UTIL_DEPS) ../src/utils/exim_msgdate.src
+	$(FE)rm -f exim_msgdate
+	$(FE). ./version.sh && sed \
 	  -e "s?PROCESSED_FLAG?This file has been so processed.?"\
 	  -e "/^[ \t]*# /p" \
 	  -e "/^[ \t]*# /d" \
@@ -431,26 +431,26 @@ exim_msgdate: util_deps ../src/utils/exim_msgdate.src
 	  -e "s?^?# Insert ?" \
 	  -e "r ../src/utils/msgid.frag" \
 	  ../src/utils/exim_msgdate.src > exim_msgdate-t
-	@mv exim_msgdate-t exim_msgdate
-	@chmod a+x exim_msgdate
-	@./exim_msgdate -v 2>&1 >/dev/null
-	@echo ">>> exim_msgdate script built"
-
-eximstats: config ../src/utils/eximstats.src
-	@rm -f eximstats
-	@. ./version.sh && sed \
+	$(FE)mv exim_msgdate-t exim_msgdate
+	$(FE)chmod a+x exim_msgdate
+	$(FE)./exim_msgdate -v 2>&1 >/dev/null
+#	$(FE)echo ">>> exim_msgdate script built"
+
+eximstats: $(CONFIG_DEPS) ../src/utils/eximstats.src
+	$(FE)rm -f eximstats
+	$(FE). ./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/utils/eximstats.src > eximstats-t
-	@mv eximstats-t eximstats
-	@chmod a+x eximstats
-	@./eximstats -v 2>&1 >/dev/null
-	@echo ">>> eximstats script built"
-
-exiqgrep: util_deps ../src/utils/exiqgrep.src
-	@rm -f exiqgrep
-	@. ./version.sh && sed \
+	$(FE)mv eximstats-t eximstats
+	$(FE)chmod a+x eximstats
+	$(FE)./eximstats -v 2>&1 >/dev/null
+#	$(FE)echo ">>> eximstats script built"
+
+exiqgrep: $(UTIL_DEPS) ../src/utils/exiqgrep.src
+	$(FE)rm -f exiqgrep
+	$(FE). ./version.sh && sed \
 	  -e "s?PROCESSED_FLAG?This file has been so processed.?"\
 	  -e "/^# /p" \
 	  -e "/^# /d" \
@@ -465,14 +465,14 @@ exiqgrep: util_deps ../src/utils/exiqgrep.src
 	  -e "s?^?# Insert ?" \
 	  -e "r ../src/utils/msgid.frag" \
 	  ../src/utils/exiqgrep.src > exiqgrep-t
-	@mv exiqgrep-t exiqgrep
-	@chmod a+x exiqgrep
-	@./exiqgrep -v 2>&1 >/dev/null
-	@echo ">>> exiqgrep script built"
-
-exiqsumm: util_deps ../src/utils/exiqsumm.src
-	@rm -f exiqsumm
-	@. ./version.sh && sed \
+	$(FE)mv exiqgrep-t exiqgrep
+	$(FE)chmod a+x exiqgrep
+	$(FE)./exiqgrep -v 2>&1 >/dev/null
+#	$(FE)echo ">>> exiqgrep script built"
+
+exiqsumm: $(UTIL_DEPS) ../src/utils/exiqsumm.src
+	$(FE)rm -f exiqsumm
+	$(FE). ./version.sh && sed \
 	  -e "/^MSGID_RE/b mi" \
 	  -e "s?PERL_COMMAND?$(PERL_COMMAND)?" \
 	  -e "s?EXIM_RELEASE_VERSION?$${EXIM_RELEASE_VERSION}?" \
@@ -483,28 +483,28 @@ exiqsumm: util_deps ../src/utils/exiqsumm.src
 	  -e "s?^?# Insert ?" \
 	  -e "r ../src/utils/msgid.frag" \
 	  ../src/utils/exiqsumm.src > exiqsumm-t
-	@mv exiqsumm-t exiqsumm
-	@chmod a+x exiqsumm
-	@./exiqsumm -v 2>&1 >/dev/null
-	@echo ">>> exiqsumm script built"
-
-exipick: config ../src/utils/exipick.src
-	@rm -f exipick
-	@. ./version.sh && sed \
+	$(FE)mv exiqsumm-t exiqsumm
+	$(FE)chmod a+x exiqsumm
+	$(FE)./exiqsumm -v 2>&1 >/dev/null
+#	$(FE)echo ">>> exiqsumm script built"
+
+exipick: $(CONFIG_DEPS) ../src/utils/exipick.src
+	$(FE)rm -f exipick
+	$(FE). ./version.sh && sed \
 	  -e "s?PERL_COMMAND?$(PERL_COMMAND)?" \
 	  -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/utils/exipick.src > exipick-t
-	@mv exipick-t exipick
-	@chmod a+x exipick
-	@./exipick -v 2>&1 >/dev/null
-	@echo ">>> exipick script built"
-
-exim_id_update: util_deps ../src/utils/exim_id_update.src
-	@rm -f exim_id_update
-	@. ./version.sh && sed \
+	$(FE)mv exipick-t exipick
+	$(FE)chmod a+x exipick
+	$(FE)./exipick -v 2>&1 >/dev/null
+#	$(FE)echo ">>> exipick script built"
+
+exim_id_update: $(UTIL_DEPS) ../src/utils/exim_id_update.src
+	$(FE)rm -f exim_id_update
+	$(FE). ./version.sh && sed \
 	  -e "/^MSGID_RE/b mi" \
 	  -e "s?PERL_COMMAND?$(PERL_COMMAND)?" \
 	  -e "s?SPOOL_DIRECTORY?$(SPOOL_DIRECTORY)?" \
@@ -517,21 +517,21 @@ exim_id_update: util_deps ../src/utils/exim_id_update.src
 	  -e "s?^?# Insert ?" \
 	  -e "r ../src/utils/msgid.frag" \
 	  ../src/utils/exim_id_update.src > exim_id_update-t
-	@mv exim_id_update-t exim_id_update
-	@chmod a+x exim_id_update
-	@./exim_id_update -v 2>&1 >/dev/null
-	@echo ">>> exim_id_update script built"
-
-transport-filter.pl: config ../src/transport-filter.src
-	@rm -f transport-filter.pl
-	@. ./version.sh && sed \
+	$(FE)mv exim_id_update-t exim_id_update
+	$(FE)chmod a+x exim_id_update
+	$(FE)./exim_id_update -v 2>&1 >/dev/null
+#	$(FE)echo ">>> exim_id_update script built"
+
+transport-filter.pl: $(CONFIG_DEPS) ../src/transport-filter.src
+	$(FE)rm -f transport-filter.pl
+	$(FE). ./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"
+	$(FE)mv transport-filter.pl-t transport-filter.pl
+	$(FE)chmod a+x transport-filter.pl
+#	$(FE)echo ">>> transport-filter.pl script built"
 
 
 # These are objects of optional features. They are always compiled, but
@@ -539,12 +539,10 @@ transport-filter.pl: config ../src/transport-filter.src
 # are thrown away by the linker.
 
 OBJ_WITH_CONTENT_SCAN = malware.o mime.o regex.o spam.o spool_mbox.o
-OBJ_EXPERIMENTAL =	bmi_spam.o \
-			dane.o \
+OBJ_EXPERIMENTAL =	dane.o \
 			dcc.o \
 			imap_utf7.o \
-			utf8.o \
-			xclient.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.
@@ -553,14 +551,14 @@ OBJ_LOOKUPS = lf_quote.o lf_check_file.o lf_sqlperform.o
 OBJ_ROUTERS = rf_change_domain.o rf_expand_data.o rf_get_errors_address.o \
 	rf_get_munge_headers.o rf_get_transport.o rf_get_ugid.o \
 	rf_lookup_hostlist.o rf_queue_add.o rf_self_action.o rf_set_ugid.o
-OBJ_AUTHS = call_pwcheck.o check_serv_cond.o \
+OBJ_AUTHS = call_saslauthd.o check_serv_cond.o \
 	get_data.o get_no64_data.o pwcheck.o
 
 OBJ_EXIM = acl.o atrn.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 \
         filtertest.o globals.o dnsbl.o hash.o \
         header.o host.o host_address.o ip.o log.o lss.o match.o md5.o moan.o \
-        os.o parse.o priv.o proxy.o queue.o \
+        os.o parse.o priv.o queue.o \
         rda.o readconf.o receive.o retry.o rewrite.o rfc2047.o regex_cache.o \
         route.o search.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 \
@@ -569,9 +567,8 @@ OBJ_EXIM = acl.o atrn.o base64.o child.o crypt16.o daemon.o dbfn.o debug.o \
         local_scan.o $(OBJ_WITH_CONTENT_SCAN) \
         $(OBJ_EXPERIMENTAL)
 
-exim:   buildlookups buildauths \
-        buildrouters buildtransports buildmisc \
-        $(OBJ_EXIM) version.o
+exim:   buildlookups buildauths buildrouters buildtransports buildmisc \
+        	$(OBJ_EXIM) version.o
 	@echo "$(LNCC) -o exim"
 	$(FE)$(PURIFY) $(LNCC) -o exim $(LFLAGS) $(OBJ_EXIM) version.o \
 	  routers/routers.a transports/transports.a lookups/lookups.a \
@@ -584,13 +581,13 @@ exim:   buildlookups buildauths \
 	  $(STRIP_COMMAND) exim; \
 	fi
 	$(EXIM_CHMOD)
-	@echo " "
-	@echo ">>> exim binary built"
-	@echo " "
+#	@echo " "
+#	@echo ">>> exim binary built"
+#	@echo " "
 
 # The utility for dumping the contents of an exim database
 
-OBJ_DUMPDB = exim_dumpdb.o util-os.o util-store.o util-xtextencode.o
+OBJ_DUMPDB = exim_dumpdb.o util-os.o util-store.o util-string.o
 
 exim_dumpdb: $(OBJ_DUMPDB)
 	@echo "$(LNCC) -o exim_dumpdb"
@@ -600,12 +597,12 @@ exim_dumpdb: $(OBJ_DUMPDB)
 	  echo $(STRIP_COMMAND) exim_dumpdb; \
 	  $(STRIP_COMMAND) exim_dumpdb; \
 	fi
-	@echo ">>> exim_dumpdb utility built"
-	@echo " "
+#	@echo ">>> exim_dumpdb utility built"
+#	@echo " "
 
 # The utility for interrogating/fixing the contents of an exim database
 
-OBJ_FIXDB = exim_fixdb.o util-os.o util-store.o util-md5.o util-xtextencode.o
+OBJ_FIXDB = exim_fixdb.o util-os.o util-store.o util-string.o util-md5.o
 
 exim_fixdb:  $(OBJ_FIXDB)
 	@echo "$(LNCC) -o exim_fixdb"
@@ -615,12 +612,12 @@ exim_fixdb:  $(OBJ_FIXDB)
 	  echo $(STRIP_COMMAND) exim_fixdb; \
 	  $(STRIP_COMMAND) exim_fixdb; \
 	fi
-	@echo ">>> exim_fixdb utility built"
-	@echo " "
+#	@echo ">>> exim_fixdb utility built"
+#	@echo " "
 
 # The utility for tidying the contents of an exim database
 
-OBJ_TIDYDB = exim_tidydb.o util-os.o util-store.o util-xtextencode.o
+OBJ_TIDYDB = exim_tidydb.o util-os.o util-store.o util-string.o
 
 exim_tidydb: $(OBJ_TIDYDB)
 	@echo "$(LNCC) -o exim_tidydb"
@@ -631,12 +628,12 @@ exim_tidydb: $(OBJ_TIDYDB)
 	  echo $(STRIP_COMMAND) exim_tidydb; \
 	  $(STRIP_COMMAND) exim_tidydb; \
 	fi
-	@echo ">>> exim_tidydb utility built"
-	@echo " "
+#	@echo ">>> exim_tidydb utility built"
+#	@echo " "
 
 # The utility for building dbm files
 
-OBJ_DBMBUILD = exim_dbmbuild.o util-xtextencode.o
+OBJ_DBMBUILD = exim_dbmbuild.o
 
 exim_dbmbuild: $(OBJ_DBMBUILD)
 	@echo "$(LNCC) -o exim_dbmbuild"
@@ -647,8 +644,8 @@ exim_dbmbuild: $(OBJ_DBMBUILD)
 	  echo $(STRIP_COMMAND) exim_dbmbuild; \
 	  $(STRIP_COMMAND) exim_dbmbuild; \
 	fi
-	@echo ">>> exim_dbmbuild utility built"
-	@echo " "
+#	@echo ">>> exim_dbmbuild utility built"
+#	@echo " "
 
 # The utility for locking a mailbox while messing around with it
 
@@ -662,8 +659,8 @@ exim_lock: exim_lock.c os.h
 	  echo $(STRIP_COMMAND) exim_lock; \
 	  $(STRIP_COMMAND) exim_lock; \
 	fi
-	@echo ">>> exim_lock utility built"
-	@echo " "
+#	@echo ">>> exim_lock utility built"
+#	@echo " "
 
 # The X-based Exim monitor program's binary part. There's a macro for cutting
 # out the modified TextPop module, because some antique link editors cannot
@@ -686,7 +683,6 @@ OBJ_MONBIN = util-host_address.o \
 	     util-string.o \
 	     util-tod.o \
 	     util-tree.o \
-	     util-xtextencode.o \
 	     $(MONBIN)
 
 eximon.bin: $(EXIMON_EDITME) eximon $(OBJ_MONBIN) ../exim_monitor/em_version.c \
@@ -702,8 +698,8 @@ eximon.bin: $(EXIMON_EDITME) eximon $(OBJ_MONBIN) ../exim_monitor/em_version.c \
 	  echo $(STRIP_COMMAND) eximon.bin; \
 	  $(STRIP_COMMAND) eximon.bin; \
 	fi
-	@echo ">>> exim monitor binary built"
-	@echo " "
+#	@echo ">>> exim monitor binary built"
+#	@echo " "
 
 
 # Compile step for most of the exim modules. HDRS is a list of headers
@@ -847,10 +843,6 @@ util-tree.o:   $(HDRS) tree.c
 	@echo "$(CC) -DCOMPILE_UTILITY tree.c"
 	$(FE)$(CC) -c $(CFLAGS) $(INCLUDE) -DCOMPILE_UTILITY -o util-tree.o tree.c
 
-util-xtextencode.o:   $(HDRS) xtextencode.c
-	@echo "$(CC) -DCOMPILE_UTILITY xtextencode.c"
-	$(FE)$(CC) -c $(CFLAGS) $(INCLUDE) -DCOMPILE_UTILITY -o util-xtextencode.o xtextencode.c
-
 util-os.o:       $(HDRS) os.c
 	@echo "$(CC) -DCOMPILE_UTILITY os.c"
 	$(FE)$(CC) -c $(CFLAGS) $(INCLUDE) \
@@ -862,7 +854,7 @@ util-os.o:       $(HDRS) os.c
 # The local scan module depends only on its own special header, and is compiled
 # from a source whose location is set by configuration.
 
-local_scan.o:    config local_scan.h ../$(LOCAL_SCAN_SOURCE)
+local_scan.o:    $(CONFIG_DEPS) local_scan.h ../$(LOCAL_SCAN_SOURCE)
 	@echo "$(CC) local_scan.c"
 	$(FE)$(CC) -DLOCAL_SCAN -c $(CFLAGS) -I. $(INCLUDE) -o local_scan.o ../$(LOCAL_SCAN_SOURCE)
 
@@ -899,7 +891,6 @@ moan.o:          $(HDRS) moan.c
 os.o:            $(HDRS) $(OS_C_INCLUDES) os.c
 parse.o:         $(HDRS) parse.c
 priv.o:          $(HDRS) priv.c
-proxy.o:         $(HDRS) proxy.c
 queue.o:         $(HDRS) queue.c
 rda.o:           $(HDRS) rda.c
 readconf.o:      $(HDRS) readconf.c
@@ -938,12 +929,10 @@ spool_mbox.o:    $(HDRS) spool_mbox.c
 
 # Dependencies for EXPERIMENTAL_* modules
 
-bmi_spam.o:	$(HDRS) bmi_spam.c
 dane.o:		$(HDRS) dane.c dane-openssl.c
 dcc.o:		$(HDRS) dcc.h dcc.c
 imap_utf7.o:	$(HDRS) imap_utf7.c
 utf8.o:		$(HDRS) utf8.c
-xclient.o:	$(HDRS) xclient.c
 
 # The module containing tables of available lookups, routers, auths, and
 # transports must be rebuilt if any of them are. However, because the makefiles
@@ -1005,7 +994,7 @@ rf_set_ugid.o:           routers/rf_set_ugid.c
 auth-spa.o:         auths/auth-spa.c
 	@echo "$(CC) $<"
 	$(FE)$(CC) -c $(CFLAGS) -I. $(INCLUDE) $<
-call_pwcheck.o:     auths/call_pwcheck.c auths/pwcheck.h
+call_saslauthd.o:  auths/call_saslauthd.c auths/pwcheck.h
 	@echo "$(CC) $<"
 	$(FE)$(CC) -c $(CFLAGS) -I. $(INCLUDE) $<
 check_serv_cond.o:  auths/check_serv_cond.c
@@ -1049,11 +1038,10 @@ $(MONBIN): $(HDRS)
 
 # Copies of modules built as dynamic-load libraries
 
-dynmodules: buildlookups buildrouters buildtransports buildauths \
-		buildmisc
-	rm -fr dynmodules
-	mkdir dynmodules
-	for d in lookup router transport auth miscmod; do \
+dynmodules: buildlookups buildrouters buildtransports buildauths buildmisc
+	$(FE)rm -fr dynmodules
+	$(FE)mkdir dynmodules
+	$(FE)for d in lookup router transport auth miscmod; do \
 	  for f in $${d}s/*.so; do \
 	    [ -e $$f ] && ln $$f dynmodules/`basename $$f .so`_$$d.so; \
 	  done; \
@@ -1062,42 +1050,42 @@ dynmodules: buildlookups buildrouters buildtransports buildauths \
 
 # The lookups library.
 
-buildlookups: config
-	 @cd lookups && $(MAKE) SHELL=$(SHELL) AR="$(AR)" $(MFLAGS) CC="$(CC)" CFLAGS="$(CFLAGS)" \
+buildlookups: $(CONFIG_DEPS)
+	@cd lookups && $(MAKE) SHELL=$(SHELL) AR="$(AR)" $(MFLAGS) CC="$(CC)" CFLAGS="$(CFLAGS)" \
 	   CFLAGS_DYNAMIC="$(CFLAGS_DYNAMIC)" HDRS="../version.h $(PHDRS)" \
 	   FE="$(FE)" RANLIB="$(RANLIB)" RM_COMMAND="$(RM_COMMAND)" \
 	   INCLUDE="$(INCLUDE) $(IPV6_INCLUDE) $(TLS_INCLUDE) $(LOOKUP_INCLUDE)"
-	 @echo " "
+#	@echo " "
 
 # The routers library.
 
-buildrouters: config
-	 @cd routers && $(MAKE) SHELL=$(SHELL) AR="$(AR)" $(MFLAGS) CC="$(CC)" CFLAGS="$(CFLAGS)" \
+buildrouters: $(CONFIG_DEPS)
+	@cd routers && $(MAKE) SHELL=$(SHELL) AR="$(AR)" $(MFLAGS) CC="$(CC)" CFLAGS="$(CFLAGS)" \
 	   CFLAGS_DYNAMIC="$(CFLAGS_DYNAMIC)" \
 	   FE="$(FE)" RANLIB="$(RANLIB)" RM_COMMAND="$(RM_COMMAND)" HDRS="$(PHDRS)" \
 	   INCLUDE="$(INCLUDE) $(IPV6_INCLUDE) $(TLS_INCLUDE)"
-	 @echo " "
+#	@echo " "
 
 # The transports library.
 
-buildtransports: config
-	 @cd transports && $(MAKE) SHELL=$(SHELL) AR="$(AR)" $(MFLAGS) CC="$(CC)" CFLAGS="$(CFLAGS)" \
+buildtransports: $(CONFIG_DEPS)
+	@cd transports && $(MAKE) SHELL=$(SHELL) AR="$(AR)" $(MFLAGS) CC="$(CC)" CFLAGS="$(CFLAGS)" \
 	   CFLAGS_DYNAMIC="$(CFLAGS_DYNAMIC)" \
 	   FE="$(FE)" RANLIB="$(RANLIB)" RM_COMMAND="$(RM_COMMAND)" HDRS="$(PHDRS)" \
 	   INCLUDE="$(INCLUDE) $(IPV6_INCLUDE) $(TLS_INCLUDE)"
-	 @echo " "
+#	@echo " "
 
-# The library of authorization modules
+# The library of authentication modules
 
-buildauths: config
-	 @cd auths && $(MAKE) SHELL=$(SHELL) AR="$(AR)" $(MFLAGS) CC="$(CC)" CFLAGS="$(CFLAGS)" \
+buildauths: $(CONFIG_DEPS)
+	@cd auths && $(MAKE) SHELL=$(SHELL) AR="$(AR)" $(MFLAGS) CC="$(CC)" CFLAGS="$(CFLAGS)" \
 	   CFLAGS_DYNAMIC="$(CFLAGS_DYNAMIC)" \
 	   FE="$(FE)" RANLIB="$(RANLIB)" RM_COMMAND="$(RM_COMMAND)" HDRS="$(PHDRS)" \
 	   INCLUDE="$(INCLUDE) $(IPV6_INCLUDE) $(TLS_INCLUDE)"
-	 @echo " "
+#	@echo " "
 
-buildmisc: config
-	 @cd miscmods && $(MAKE) SHELL=$(SHELL) AR="$(AR)" $(MFLAGS) \
+buildmisc: $(CONFIG_DEPS)
+	@cd miscmods && $(MAKE) SHELL=$(SHELL) AR="$(AR)" $(MFLAGS) \
 	   CC="$(CC)" CFLAGS="$(CFLAGS)" \
 	     CFLAGS_DYNAMIC="$(CFLAGS_DYNAMIC)" \
 	     LDFLAGS_PARTIAL="$(LDFLAGS_PARTIAL)" HDRS="../version.h $(PHDRS)" \
@@ -1105,7 +1093,7 @@ buildmisc: config
 	     PERL_CC="$(PERL_CC)" PERL_CCOPTS="$(PERL_CCOPTS)" \
 	     PERL_CFLAGS="$(PERL_CFLAGS)" PERL_LFLAGS="$(PERL_LFLAGS)" \
 	     INCLUDE="$(INCLUDE) $(IPV6_INCLUDE)" TLS_INCLUDE="$(TLS_INCLUDE)"
-	 @echo " "
+#	@echo " "
 
 # The "clean", "install", and "makefile" targets just pass themselves back to
 # the main Exim makefile. These targets will be obeyed only if "make" is obeyed
diff --git a/src/OS/os.c-Linux b/src/OS/os.c-Linux
index 34dd7c190..ad1cb855b 100644
--- a/src/OS/os.c-Linux
+++ b/src/OS/os.c-Linux
@@ -139,8 +139,8 @@ while (fscanf(f, "%4s%4s%4s%4s%4s%4s%4s%4s %02x %02x %02x %02x %20s\n",
     last = next;
     }
 
-  DEBUG(D_interface)
-    debug_printf("Actual local interface address is %s (%s)\n", last->address,
+  DEBUG(interface)
+    debug_printf_indent("Actual local interface address is %s (%s)\n", last->address,
       devname);
   }
 fclose(f);
diff --git a/src/OS/os.h-FreeBSD b/src/OS/os.h-FreeBSD
index 57ea82265..bb596ff0a 100644
--- a/src/OS/os.h-FreeBSD
+++ b/src/OS/os.h-FreeBSD
@@ -59,11 +59,6 @@ performance on outgoing mail a bit. */
 #define OS_SENDFILE
 extern ssize_t os_sendfile(int, int, off_t *, size_t);
 
-#ifdef PID_T_FMT
-# undef PID_T_FMT
-#endif
-#define PID_T_FMT "%d"
-
 
 /*******************/
 
diff --git a/src/OS/os.h-GNU b/src/OS/os.h-GNU
index 2a335bc27..448548840 100644
--- a/src/OS/os.h-GNU
+++ b/src/OS/os.h-GNU
@@ -1,5 +1,5 @@
 /* Exim: OS-specific C header file for GNU/Hurd */
-/* Copyright (c) The Exim Maintainers 2020 - 2025 */
+/* Copyright (c) The Exim Maintainers 2020 - 2026 */
 /* SPDX-License-Identifier: GPL-2.0-or-later */
 
 #include <features.h>
diff --git a/src/OS/os.h-OpenBSD b/src/OS/os.h-OpenBSD
index 19db7189a..e2ef6b597 100644
--- a/src/OS/os.h-OpenBSD
+++ b/src/OS/os.h-OpenBSD
@@ -42,11 +42,6 @@ typedef struct __res_state *res_state;
 #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
diff --git a/src/OS/os.h-SunOS5 b/src/OS/os.h-SunOS5
index f7ad50421..389b3403b 100644
--- a/src/OS/os.h-SunOS5
+++ b/src/OS/os.h-SunOS5
@@ -2,6 +2,13 @@
 /* Copyright (c) The Exim Maintainers 2021 - 2025 */
 /* SPDX-License-Identifier: GPL-2.0-or-later */
 
+/* The Sun Studio compilers define these, but GCC does not.
+Adjust if needed. */
+#if !defined(__SunOS_5_10) && !defined(__SunOS_5_11)
+# define __SunOS_5_11
+#endif
+
+
 #define CRYPT_H
 #define HAVE_MMAP
 #define HAVE_SYS_STATVFS_H
@@ -35,6 +42,11 @@ it seems. */
 
 /* default is non-const */
 #define ICONV_ARG2_TYPE const char **
+/* A GCC user reported that the following was needed.  Possibly using
+Glibc iconv?
+  #define ICONV_ARG2_TYPE char ** restrict
+A way of spotting the difference would be useful.
+*/
 
 #if _POSIX_C_SOURCE + 0 < 200112L
 # define MISSING_UNSETENV_3
diff --git a/src/OS/unsupported/os.c-IRIX b/src/OS/unsupported/os.c-IRIX
index 6f043d044..f9f75a425 100644
--- a/src/OS/unsupported/os.c-IRIX
+++ b/src/OS/unsupported/os.c-IRIX
@@ -106,7 +106,7 @@ for (nextaddr = buf; nextaddr < lim; nextaddr += ifm->ifm_msglen)
         last = next;
         }
 
-      DEBUG(D_interface) debug_printf("Actual local interface address is %s\n",
+      DEBUG(interface) debug_printf_indent("Actual local interface address is %s\n",
         last->address);
       }
     }
diff --git a/src/OS/unsupported/os.c-IRIX6 b/src/OS/unsupported/os.c-IRIX6
index 6f043d044..f9f75a425 100644
--- a/src/OS/unsupported/os.c-IRIX6
+++ b/src/OS/unsupported/os.c-IRIX6
@@ -106,7 +106,7 @@ for (nextaddr = buf; nextaddr < lim; nextaddr += ifm->ifm_msglen)
         last = next;
         }
 
-      DEBUG(D_interface) debug_printf("Actual local interface address is %s\n",
+      DEBUG(interface) debug_printf_indent("Actual local interface address is %s\n",
         last->address);
       }
     }
diff --git a/src/OS/unsupported/os.c-IRIX632 b/src/OS/unsupported/os.c-IRIX632
index 6f043d044..f9f75a425 100644
--- a/src/OS/unsupported/os.c-IRIX632
+++ b/src/OS/unsupported/os.c-IRIX632
@@ -106,7 +106,7 @@ for (nextaddr = buf; nextaddr < lim; nextaddr += ifm->ifm_msglen)
         last = next;
         }
 
-      DEBUG(D_interface) debug_printf("Actual local interface address is %s\n",
+      DEBUG(interface) debug_printf_indent("Actual local interface address is %s\n",
         last->address);
       }
     }
diff --git a/src/OS/unsupported/os.c-IRIX65 b/src/OS/unsupported/os.c-IRIX65
index 6f043d044..f9f75a425 100644
--- a/src/OS/unsupported/os.c-IRIX65
+++ b/src/OS/unsupported/os.c-IRIX65
@@ -106,7 +106,7 @@ for (nextaddr = buf; nextaddr < lim; nextaddr += ifm->ifm_msglen)
         last = next;
         }
 
-      DEBUG(D_interface) debug_printf("Actual local interface address is %s\n",
+      DEBUG(interface) debug_printf_indent("Actual local interface address is %s\n",
         last->address);
       }
     }
diff --git a/src/OS/unsupported/os.c-cygwin b/src/OS/unsupported/os.c-cygwin
index 56085b857..92cff1eec 100644
--- a/src/OS/unsupported/os.c-cygwin
+++ b/src/OS/unsupported/os.c-cygwin
@@ -55,7 +55,7 @@ int cygwin_setuid(uid_t uid )
   if (fakesetugid == 0) { 
     res = setuid(uid);
     if (cygwin_debug)
-      fprintf(stderr, "setuid %u %u %d pid: %d\n",
+      fprintf(stderr, "setuid %u %u %d pid: " PID_T_FMT "\n",
               uid, getuid(),res, getpid());
   }
   return res;
@@ -68,7 +68,7 @@ int cygwin_setgid(gid_t gid )
   if (fakesetugid == 0) { 
     res = setgid(gid);
     if (cygwin_debug)
-      fprintf(stderr, "setgid %u %u %d pid: %d\n",
+      fprintf(stderr, "setgid %u %u %d pid: " PID_T_FMT "\n",
               gid, getgid(), res, getpid());
   }
   return res;
@@ -357,7 +357,7 @@ static BOOL LoadNtdll()
                                                      "RtlNtStatusToDosError")))
     return TRUE;
 
-  DEBUG(D_load)
+  DEBUG(load)
     debug_printf("perf: load: %u (Windows)\n", GetLastError());
   return FALSE;
 }
@@ -381,18 +381,18 @@ static BOOL ReadStat(unsigned long long int *Time100nsPtr,
   if ((ret = NtQuerySystemInformation(SystemBasicInformation,
                                       (PVOID) &sbi, sizeof sbi, NULL))
       != STATUS_SUCCESS) {
-    DEBUG(D_load)
+    DEBUG(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(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(load)
       debug_printf("Perf: NtQuerySystemInformation: %u (Windows)\n",
                    RtlNtStatusToDosError(ret));
   }
@@ -472,12 +472,12 @@ int os_getloadavg()
     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(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(load)
       debug_printf("Perf: MapViewOfFile: addr %p\n", (void *) cygwin_load.perf);
     if (new && cygwin_load.perf)
       InitLoadAvg(cygwin_load.perf);
@@ -493,7 +493,7 @@ int os_getloadavg()
      results in an immediate delivery .*/
 
   if (InterlockedCompareExchange(&cygwin_load.perf->Lock, 1, 0)) {
-    DEBUG(D_load)
+    DEBUG(load)
       debug_printf("Perf: Lock busy\n");
     return 0;
   }
@@ -512,7 +512,7 @@ int os_getloadavg()
       cygwin_load.perf->IdleCount = IdleCount;
       cygwin_load.perf->LastCounter = CurrCounter;
       cygwin_load.perf->LastLoad = value;
-      DEBUG(D_load)
+      DEBUG(load)
         debug_printf("Perf: New load average %d\n", value);
       }
       else { /* Something bad happened.
@@ -523,7 +523,7 @@ int os_getloadavg()
     }
   }
   else
-  DEBUG(D_load)
+  DEBUG(load)
       debug_printf("Perf: Old load average %d\n", cygwin_load.perf->LastLoad);
   cygwin_load.perf->Lock = 0;
   return cygwin_load.perf->LastLoad;
diff --git a/src/exim_monitor/em_TextPop.c b/src/exim_monitor/em_TextPop.c
index 83f4cf514..c58d8bf71 100644
--- a/src/exim_monitor/em_TextPop.c
+++ b/src/exim_monitor/em_TextPop.c
@@ -136,11 +136,8 @@ static char search_text_trans[] =
 
 /* ARGSUSED */
 void
-_XawTextDoSearchAction(w, event, params, num_params)
-Widget w;
-XEvent *event;
-String * params;
-Cardinal * num_params;
+_XawTextDoSearchAction(Widget w, XEvent * event, String * params,
+  Cardinal * num_params)
 {
   TextWidget tw = (TextWidget) XtParent(XtParent(XtParent(w)));
   Boolean popdown = FALSE;
@@ -162,11 +159,8 @@ Cardinal * num_params;
 
 /* ARGSUSED */
 void
-_XawTextPopdownSearchAction(w, event, params, num_params)
-Widget w;
-XEvent *event;
-String * params;
-Cardinal * num_params;
+_XawTextPopdownSearchAction(Widget w, XEvent * event, String * params,
+  Cardinal * num_params)
 {
   TextWidget tw = (TextWidget) XtParent(XtParent(XtParent(w)));
 
@@ -183,10 +177,7 @@ Cardinal * num_params;
 
 /* ARGSUSED */
 static void
-PopdownSearch(w, closure, call_data)
-Widget w;
-XtPointer closure;
-XtPointer call_data;
+PopdownSearch(Widget w, XtPointer closure, XtPointer call_data)
 {
   struct SearchAndReplace * search = (struct SearchAndReplace *) closure;
 
@@ -204,10 +195,7 @@ XtPointer call_data;
 
 /* ARGSUSED */
 static void
-SearchButton(w, closure, call_data)
-Widget w;
-XtPointer closure;
-XtPointer call_data;
+SearchButton(Widget w, XtPointer closure, XtPointer call_data)
 {
   (void) DoSearch( (struct SearchAndReplace *) closure );
 }
@@ -235,11 +223,7 @@ XtPointer call_data;
 #define SEARCH_HEADER ("Text Widget - Search():")
 
 void
-_XawTextSearch(w, event, params, num_params)
-Widget w;
-XEvent *event;
-String * params;
-Cardinal * num_params;
+_XawTextSearch(Widget w, XEvent *event, String * params, Cardinal * num_params)
 {
   TextWidget ctx = (TextWidget)w;
   XawTextScanDirection dir;
@@ -337,9 +321,7 @@ replace_active = replace_active; /* PH - shuts compilers up */
  */
 
 static void
-AddSearchChildren(form, ptr, tw)
-Widget form, tw;
-char * ptr;
+AddSearchChildren(Widget form, char * ptr, Widget tw)
 {
   Arg args[10];
   Cardinal num_args;
@@ -455,8 +437,7 @@ char * ptr;
 
 /* ARGSUSED */
 static Boolean
-DoSearch(search)
-struct SearchAndReplace * search;
+DoSearch(struct SearchAndReplace * search)
 {
   char msg[BUFSIZ];
   Widget tw = XtParent(search->search_popup);
@@ -530,10 +511,7 @@ msg2 = msg2; /* PH - shuts compilers up */
  */
 
 static void
-SetResource(w, res_name, value)
-Widget w;
-char * res_name;
-XtArgVal value;
+SetResource(Widget w, char * res_name, XtArgVal value)
 {
   Arg args[1];
 
@@ -548,8 +526,7 @@ XtArgVal value;
  */
 
 static String
-GetString(text)
-Widget text;
+GetString(Widget text)
 {
   String string;
   Arg args[1];
@@ -570,9 +547,7 @@ Widget text;
  */
 
 static void
-CenterWidgetOnPoint(w, event)
-Widget w;
-XEvent *event;
+CenterWidgetOnPoint(Widget w, XEvent *event)
 {
   Arg args[3];
   Cardinal num_args;
@@ -668,8 +643,7 @@ CreateDialog(Widget parent, String ptr, String name,
   */
 
 static Widget
-GetShell(w)
-Widget w;
+GetShell(Widget w)
 {
     while ((w != NULL) && !XtIsShell(w))
 	w = XtParent(w);
@@ -677,14 +651,7 @@ Widget w;
     return (w);
 }
 
-/* Add proper prototype to keep IRIX 6 compiler happy. PH */
-
-static Boolean InParams(String, String *, Cardinal);
-
-static Boolean InParams(str, p, n)
-    String str;
-    String *p;
-    Cardinal n;
+static Boolean InParams(String str, String *p, Cardinal n)
 {
     int i;
     for (i=0; i < n; p++, i++)
@@ -694,11 +661,11 @@ static Boolean InParams(str, p, n)
 
 static char *WM_DELETE_WINDOW = "WM_DELETE_WINDOW";
 
-static void WMProtocols(w, event, params, num_params)
-    Widget w;		/* popup shell */
-    XEvent *event;
-    String *params;
-    Cardinal *num_params;
+static void WMProtocols(
+    Widget w,		/* popup shell */
+    XEvent *event,
+    String *params,
+    Cardinal *num_params)
 {
     Atom wm_delete_window;
     Atom wm_protocols;
@@ -730,8 +697,8 @@ static void WMProtocols(w, event, params, num_params)
     }
 }
 
-static void SetWMProtocolTranslations(w)
-    Widget	w;	/* realized popup shell */
+static void SetWMProtocolTranslations(
+    Widget	w)	/* realized popup shell */
 {
     int i;
     XtAppContext app_context;
diff --git a/src/exim_monitor/em_globals.c b/src/exim_monitor/em_globals.c
index 491a9dda6..45dc6f298 100644
--- a/src/exim_monitor/em_globals.c
+++ b/src/exim_monitor/em_globals.c
@@ -44,11 +44,6 @@ uschar  actioned_message[24];
 uschar *action_required;
 uschar *alternate_config = NULL;
 
-#ifdef EXPERIMENTAL_BRIGHTMAIL
-int     bmi_run                = 0;
-uschar *bmi_verdicts           = NULL;
-#endif
-
 int     body_max = 20000;
 
 uschar *exim_path              = US BIN_DIRECTORY "/exim"
@@ -136,11 +131,6 @@ time_t  deliver_frozen_at      = 0;
 BOOL    deliver_manual_thaw    = FALSE;
 
 #ifndef DISABLE_DKIM
-uschar *dkim_cur_signer          = NULL;
-uschar *dkim_signers             = NULL;
-uschar *dkim_signing_domain      = NULL;
-uschar *dkim_signing_selector    = NULL;
-uschar *dkim_verify_signers      = US"$dkim_signers";
 unsigned dkim_collect_input      = 0;
 BOOL    dkim_disable_verify      = FALSE;
 #endif
diff --git a/src/exim_monitor/em_main.c b/src/exim_monitor/em_main.c
index 143de870d..bf03f0a75 100644
--- a/src/exim_monitor/em_main.c
+++ b/src/exim_monitor/em_main.c
@@ -159,7 +159,6 @@ from modules such as store.c when things go drastically wrong (e.g. malloc()
 failing). In normal use they won't get obeyed.
 
 Arguments:
-  selector  not relevant when running a utility
   flags     not relevant when running a utility
   format    a printf() format
   ...       arguments for format
@@ -168,7 +167,7 @@ Returns:    nothing
 */
 
 void
-log_write(unsigned int selector, int flags, const char *format, ...)
+log_write(int flags, const char *format, ...)
 {
 va_list ap;
 va_start(ap, format);
@@ -179,7 +178,7 @@ va_end(ap);
 
 
 void
-log_write_die(unsigned int selector, int flags, const char *format, ...)
+log_write_die(int flags, const char *format, ...)
 {
 va_list ap;
 va_start(ap, format);
diff --git a/src/exim_monitor/em_version.c b/src/exim_monitor/em_version.c
index 15b16c10f..5b4fa1b92 100644
--- a/src/exim_monitor/em_version.c
+++ b/src/exim_monitor/em_version.c
@@ -9,12 +9,13 @@
 
 #define EM_VERSION_C
 
+#include <string.h>
+#include <stdint.h>
+#include <stdlib.h>
 #include "mytypes.h"
 #include "store.h"
 #include "path_max.h"
 #include "macros.h"
-#include <string.h>
-#include <stdlib.h>
 
 #include "version.h"
 
@@ -30,14 +31,15 @@ uschar today[20];
 #endif
 
 version_string = US"2.06";
+version_date = US store_malloc(32);
+version_date[0] = 0;
 
 #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;
+/* 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/> */
+
 Ustrncat(version_date, EXIM_BUILD_DATE_OVERRIDE, 31);
 
 #else
@@ -45,8 +47,6 @@ Ustrcpy(today, US __DATE__);
 if (today[4] == ' ') i = 1;
 today[3] = today[6] = '-';
 
-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);
diff --git a/src/scripts/Configure-Makefile b/src/scripts/Configure-Makefile
index 73737274e..84a8abbb2 100755
--- a/src/scripts/Configure-Makefile
+++ b/src/scripts/Configure-Makefile
@@ -38,15 +38,20 @@ rebuild=yes
 
 if [ -f Makefile ] ; then
   rebuild=no
-  if ../scripts/newer $editme Makefile || \
-     ../scripts/newer $editme-$ostype Makefile || \
-     ../scripts/newer $editme-$archtype Makefile || \
-     ../scripts/newer $editme-$ostype-$archtype Makefile || \
-     ../scripts/newer ../scripts/Configure-Makefile Makefile || \
-     ../scripts/newer ../scripts/lookups-Makefile Makefile || \
-     ../scripts/newer ../scripts/drivers-Makefile Makefile || \
-     ../scripts/newer ../OS/Makefile-Base Makefile || \
-     ../scripts/newer ../OS/Makefile-Default Makefile
+  if ../scripts/newer $editme Makefile \
+     || ../scripts/newer $editme-$ostype Makefile \
+     || ../scripts/newer $editme-$archtype Makefile \
+     || ../scripts/newer $editme-$ostype-$archtype Makefile \
+     || ../scripts/newer ../scripts/Configure-Makefile Makefile \
+     || ../scripts/newer ../scripts/lookups-Makefile Makefile \
+     || ../scripts/newer ../scripts/drivers-Makefile Makefile \
+     || ../scripts/newer ../OS/Makefile-Base Makefile \
+     || ../scripts/newer ../OS/Makefile-Default Makefile \
+     || ../scripts/newer ../src/auths/Makefile Makefile \
+     || ../scripts/newer ../src/lookups/Makefile Makefile \
+     || ../scripts/newer ../src/miscmods/Makefile Makefile \
+     || ../scripts/newer ../src/routers/Makefile Makefile \
+     || ../scripts/newer ../src/transports/Makefile Makefile
   then
     rebuild=yes
   fi
@@ -78,8 +83,8 @@ fi
 # If Makefile is up-to-date, no need to rebuild it.
 
 if [ $rebuild = no ] ; then
-  echo "\`Makefile' is up to date."
-  echo " "
+#  echo "\`Makefile' is up to date."
+#  echo " "
   exit
 fi
 
@@ -308,7 +313,13 @@ fi
 cp ../src/lookups/Makefile $look_mf_pre
 EGREP="$egrep" ../scripts/lookups-Makefile
 
-# make the Makefiles for routers, transports, auths and miscmods
+# Make the Makefiles for routers, transports, auths and miscmods
+#
+# Columns in the here-file:
+#  1		"class"		the subdir name to work in
+#  2		"classdef"	is a prefix on the driver name, used for pulling
+#  				construction data from Local/Makefile
+#  (remainder)	"names"		a list of driver names, in caps
 # An _ prefix on a name means the control in Local/Makefile is DISABLED_<name>
 #
 while read class classdef names
@@ -321,7 +332,7 @@ done <<-END
  routers    ROUTER	ACCEPT DNSLOOKUP IPLITERAL IPLOOKUP MANUALROUTE QUERYPROGRAM REDIRECT
  transports TRANSPORT	APPENDFILE AUTOREPLY LMTP PIPE QUEUEFILE SMTP
  auths	    AUTH	CRAM_MD5 CYRUS_SASL DOVECOT EXTERNAL GSASL HEIMDAL_GSSAPI PLAINTEXT SPA TLS
- miscmods   SUPPORT	ARC _DKIM DMARC _EXIM_FILTER PAM PERL RADIUS _SIEVE_FILTER SPF SPF_PERL
+ miscmods   SUPPORT	ARC _DKIM DMARC DMARC_NATIVE DSCP _EXIM_FILTER PAM PERL PROXY RADIUS _SIEVE_FILTER SOCKS SPF SPF_PERL XCLIENT
 END
 
 # See if there is a definition of EXIM_PERL in what we have built so far.
diff --git a/src/scripts/MakeLinks b/src/scripts/MakeLinks
index d35962f98..5219d3dbc 100755
--- a/src/scripts/MakeLinks
+++ b/src/scripts/MakeLinks
@@ -32,9 +32,9 @@ d="lookups"
 mkdir $d
 cd $d
 # Makefile is generated
-for f in README cdb.c dbmdb.c dnsdb.c dsearch.c ibase.c json.c ldap.c \
+for f in README cdb.c dbmdb.c dnsdb.c dsearch.c json.c ldap.c \
   lmdb.c lsearch.c mysql.c nis.c nisplus.c nmh.c oracle.c passwd.c \
-  pgsql.c readsock.c redis.c spf.c sqlite.c testdb.c whoson.c \
+  pgsql.c psl.c readsock.c redis.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/$d/$f $f
@@ -66,7 +66,7 @@ cd $d
 # Makefile is generated
 for f in README 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
+  smtp.h smtp.c tf_maildir.c tf_maildir.h
 do
   ln -s ../../src/$d/$f $f
 done
@@ -78,7 +78,7 @@ d="auths"
 mkdir $d
 cd $d
 # Makefile is generated
-for f in README call_pwcheck.c \
+for f in README call_saslauthd.c \
   check_serv_cond.c cyrus_sasl.c cyrus_sasl.h gsasl.c \
   gsasl.h get_data.c get_no64_data.c heimdal_gssapi.c heimdal_gssapi.h \
   cram_md5.c cram_md5.h plaintext.c plaintext.h \
@@ -90,7 +90,7 @@ done
 cd ..
 
 # miscellaneous modules
-# Note that the file in the miscmods/pdkim/ source subdir get linked to the 
+# Note that the files in the miscmods/pdkim/ source subdir get linked to the 
 # destination miscmods/ dir
 d="miscmods"
 mkdir $d
@@ -101,13 +101,17 @@ for f in dummy.c \
 	dkim.c dkim_transport.c dkim.h dkim_api.h \
 	pdkim/crypt_ver.h pdkim/pdkim.c pdkim/pdkim.h \
 	pdkim/pdkim_hash.h pdkim/signing.c pdkim/signing.h \
-	dmarc.c dmarc.h dmarc_api.h \
+	dmarc.c dmarc_common.c dmarc.h dmarc_api.h dmarc_native.c \
+	dscp.c dscp_api.h \
 	exim_filter.c exim_filter_api.h \
 	pam.c pam_api.h \
 	perl.c perl_api.h \
+	proxy.c proxy_api.h \
 	radius.c radius_api.h \
 	sieve_filter.c sieve_filter_api.h \
-	spf.c spf_perl.c spf.h spf_api.h
+	socks.c socks_api.h \
+	spf.c spf_perl.c spf.h spf_api.h \
+	xclient.c xclient_api.h
 do
   ln -s ../../src/$d/$f `basename $f`
 done
@@ -136,7 +140,7 @@ for f in blob.h dbfunctions.h exim.h functions.h globals.h \
   debug.c deliver.c directory.c dns.c dnsbl.c drtables.c dummies.c enq.c \
   exim.c exim_dbmbuild.c exim_dbutil.c exim_lock.c expand.c filtertest.c \
   globals.c hash.c header.c host.c host_address.c ip.c log.c lss.c match.c \
-  md5.c moan.c parse.c priv.c proxy.c queue.c rda.c readconf.c receive.c \
+  md5.c moan.c parse.c priv.c queue.c rda.c readconf.c receive.c \
   retry.c rewrite.c regex_cache.c rfc2047.c route.c search.c setenv.c \
   environment.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-cipher-stdname.c \
@@ -155,8 +159,8 @@ do
 done
 
 # EXPERIMENTAL_*
-for f in  bmi_spam.c bmi_spam.h dcc.c dcc.h dane.c dane-openssl.c \
-  danessl.h imap_utf7.c utf8.c xclient.c
+for f in  dcc.c dcc.h dane.c dane-openssl.c \
+  danessl.h imap_utf7.c utf8.c
 do
   ln -s ../src/$f $f
 done
diff --git a/src/scripts/lookups-Makefile b/src/scripts/lookups-Makefile
index 0617e31cd..7aa186e2e 100755
--- a/src/scripts/lookups-Makefile
+++ b/src/scripts/lookups-Makefile
@@ -101,7 +101,8 @@ want_dynamic() {
 
 want_at_all() {
   local want_name="$1"
-  local re="(LOOKUP|EXPERIMENTAL)_${want_name}[ $tab]*=[ $tab]*."
+  # SUPPORT is here purely for the spf lookup
+  local re="(LOOKUP|EXPERIMENTAL|SUPPORT)_${want_name}[ $tab]*=[ $tab]*."
   env | ${egrep} -q "^$re"
   if [ $? -eq 0 ]; then return 0; fi
   ${egrep} -q "^[ $tab]*$re" "$defs_source"
@@ -156,23 +157,18 @@ exec > "$target"
 sed -n "1,/$tag_marker/p" < "$input"
 
 for name_mod in \
-    CDB DBM:dbmdb DNSDB DSEARCH IBASE JSON LMDB LDAP LSEARCH MYSQL NIS NISPLUS \
-    NMH ORACLE PASSWD PGSQL REDIS SQLITE TESTDB WHOSON
+    CDB DBM:dbmdb DNSDB DSEARCH JSON LMDB LDAP LSEARCH MYSQL NIS NISPLUS \
+    NMH ORACLE PASSWD PGSQL PSL REDIS SQLITE SPF SPF_PERL:spf TESTDB WHOSON
 do
   emit_module_rule $name_mod
 done
 
-# Because the variable is SUPPORT_SPF and not LOOKUP_SPF we
-# always include spf.o and compile a dummy if SUPPORT_SPF is not
-# defined.
-
-OBJ="${OBJ} spf.o"
-
 # readsock is always wanted as it implements the ${readsock } expansion
-OBJ="${OBJ} readsock.o"
+OBJ_ALWAYS="${OBJ_ALWAYS} readsock.o"
 
 echo "MODS = $MODS"
 echo "OBJ = $OBJ"
+echo "OBJ_ALWAYS = $OBJ_ALWAYS"
 
 sed -n "/$tag_marker/,\$p" < "$input"
 
diff --git a/src/scripts/reversion b/src/scripts/reversion
index 6fd0782a4..00b4058d3 100755
--- a/src/scripts/reversion
+++ b/src/scripts/reversion
@@ -122,9 +122,9 @@ then
   test -n "$EXIM_VARIANT_VERSION" && \
   echo '#define EXIM_VARIANT_VERSION "'"$EXIM_VARIANT_VERSION"'"'
   echo '#ifdef EXIM_VARIANT_VERSION'
-  echo '#define EXIM_VERSION_STR EXIM_RELEASE_VERSION "-" 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 '# 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}"'"'
diff --git a/src/scripts/source_checks b/src/scripts/source_checks
index 8620b27f5..0d52da6a2 100644
--- a/src/scripts/source_checks
+++ b/src/scripts/source_checks
@@ -13,13 +13,11 @@ do
   | awk '/{ (US)?"/ {print $2}' \
   | awk -F\" '{print $2}' \
   | LC_ALL=C sort -c \
-  || exit 1
+  || { echo "Table $table in $file is not alphabetically ordered" >&2 ; exit 1; }
 done <<-END
 	readconf.c	optionlist_config
 	globals.c	optionlist_auths
-	globals.c	debug_options
 	globals.c	header_names
-	globals.c	log_options
 	expand.c	item_table
 	std-crypto.c	dh_constants
 	transport.c	optionlist_transports
@@ -34,6 +32,21 @@ done <<-END
 	acl.c		controls_list
 END
 
+# Tables with one-arg macros as items
+# (ignoring #ifdef lines)
+while read file table
+do
+  : $file $table
+  < $file \
+  perl -e '$/= undef; while (<>) { print $1 if /(?<='$table'\[\])\s*=\s*{\s?(([^}]*)+)}/m }' \
+  | awk '!/^\s*#/ {split($0,a,"[()]");print a[2]}' \
+  | LC_ALL=C sort -c \
+  || { echo "Table $table in $file is not alphabetically ordered" >&2 ; exit 1; }
+done <<-END
+	globals.c	debug_channels
+	globals.c	log_channels
+END
+
 # Tables with just string items
 while read file table
 do
@@ -42,8 +55,7 @@ do
   perl -e '$/= undef; while (<>) { print $1 if /(?<='$table'\[\])\s*=\s*{\s?(([^}]*)+)}/m }' \
   | awk -F\" '/"/ {print $2}' \
   | LC_ALL=C sort -c \
-  || exit 1
-
+  || { echo "Table $table in $file is not alphabetically ordered" >&2 ; exit 1; }
 done <<-END
 	expand.c	item_table
 	expand.c	op_table_underscore
diff --git a/src/src/EDITME b/src/src/EDITME
index 3a9b167de..67c9e74e9 100644
--- a/src/src/EDITME
+++ b/src/src/EDITME
@@ -320,8 +320,8 @@ SPOOL_DIRECTORY=/var/spool/exim
 # is historic).
 # You need to add -export-dynamic -rdynamic to EXTRALIBS. You may also need to
 # add -ldl to EXTRALIBS so that dlopen() is available to Exim. You need to
-# define CFLAGS_DYNAIC and LOOKUP_MODULE_DIR below so the builds are done right,
-# and so the exim binary actually loads dynamic lookup modules.
+# define CFLAGS_DYNAMIC and LOOKUP_MODULE_DIR below so the builds are done
+# right, and so the exim binary actually loads dynamic lookup modules.
 
 ROUTER_ACCEPT=yes
 ROUTER_DNSLOOKUP=yes
@@ -350,8 +350,8 @@ ROUTER_REDIRECT=yes
 # is historic).
 # You need to add -export-dynamic -rdynamic to EXTRALIBS. You may also need to
 # add -ldl to EXTRALIBS so that dlopen() is available to Exim. You need to
-# define CFLAGS_DYNAIC and LOOKUP_MODULE_DIR below so the builds are done right,
-# and so the exim binary actually loads dynamic lookup modules.
+# define CFLAGS_DYNAMIC and LOOKUP_MODULE_DIR below so the builds are done
+# right, and so the exim binary actually loads dynamic lookup modules.
 # The smtp transport cannot be built as a module.
 
 TRANSPORT_APPENDFILE=yes
@@ -406,11 +406,13 @@ TRANSPORT_SMTP=yes
 # for the specialist case of using the DNS as a general database facility (not
 # common).
 # If set to "2" instead of "yes" then the corresponding lookup will be
-# built as a module and must be installed into LOOKUP_MODULE_DIR. You need to
+# built as a module and left in the "dynlibs" directory under the build
+# directory. They must be installed into LOOKUP_MODULE_DIR. You need to
 # add -export-dynamic -rdynamic to EXTRALIBS. You may also need to add -ldl to
 # EXTRALIBS so that dlopen() is available to Exim. You need to define
 # LOOKUP_MODULE_DIR above so the exim binary actually loads dynamic lookup
 # modules.
+#
 # Also, instead of adding all the libraries/includes to LOOKUP_INCLUDE and
 # LOOKUP_LIBS, add them to the respective LOOKUP_*_INCLUDE and LOOKUP_*_LIBS
 # (where * is the name as given here in this list). That ensures that only
@@ -436,7 +438,6 @@ LOOKUP_DNSDB=yes
 
 # LOOKUP_CDB=yes
 # LOOKUP_DSEARCH=yes
-# LOOKUP_IBASE=yes
 # LOOKUP_JSON=yes
 # LOOKUP_LDAP=yes
 # LOOKUP_LMDB=yes
@@ -448,6 +449,7 @@ LOOKUP_DNSDB=yes
 # LOOKUP_ORACLE=yes
 # LOOKUP_PASSWD=yes
 # LOOKUP_PGSQL=yes
+# LOOKUP_PSL=yes
 # LOOKUP_REDIS=yes
 # LOOKUP_SQLITE=yes
 # LOOKUP_SQLITE_PC=sqlite3
@@ -461,9 +463,6 @@ LOOKUP_DNSDB=yes
 # LOOKUP_NWILDLSEARCH=yes
 
 
-# For IBASE you may need:
-#LIBS += -lfbclient
-
 #------------------------------------------------------------------------------
 # If you have set LOOKUP_LDAP, you should set LDAP_LIB_TYPE to indicate
 # which LDAP library you have. Unfortunately, though most of their functions
@@ -512,7 +511,7 @@ SUPPORT_DANE=yes
 # the command for linking Exim itself, not on any auxiliary programs. You
 # don't need to set LOOKUP_INCLUDE if the relevant directories are already
 # specified in INCLUDE. The settings below are just examples; -lpq is for
-# PostgreSQL, -lgds is for Interbase, -lsqlite3 is for SQLite, -lhiredis
+# PostgreSQL, -lsqlite3 is for SQLite, -lhiredis
 # is for Redis, -ljansson for JSON.
 #
 # You do not need to use this for any lookup information added via pkg-config.
@@ -526,15 +525,16 @@ SUPPORT_DANE=yes
 
 # LOOKUP_INCLUDE=-I /usr/local/ldap/include -I /usr/local/mysql/include -I /usr/local/pgsql/include
 # LOOKUP_INCLUDE +=-I /usr/local/include
-# LOOKUP_LIBS=-L/usr/local/lib -lldap -llber -lmysqlclient -lpq -lgds -lsqlite3 -llmdb
+# LOOKUP_LIBS=-L/usr/local/lib -lldap -llber -lmysqlclient -lpq -lsqlite3 -llmdb
 
 # LOOKUP_LIBS=-L/usr/local/lib -lldap -llber
 # Some platforms may need this for LOOKUP_NIS:
 #LOOKUP_LIBS += -lnsl
+
+# These lookup types need appropriate libraries
 #LOOKUP_LIBS += -ljansson
 #LOOKUP_LIBS += -lhiredis
 
-#------------------------------------------------------------------------------
 # If you included LOOKUP_LMDB above you will need the library. Depending
 # on where installed you may also need an include directory
 #
@@ -542,6 +542,7 @@ SUPPORT_DANE=yes
 # LOOKUP_LIBS += -llmdb
 # For dynamic-modules builds, use instead LOOKUP_LMDB_INCLUDE & LOOKUP_LMDB_LIBS
 
+# LOOKUP_PSL will require that SUPPORT_I18N is defined (but needs no libraries)
 
 #------------------------------------------------------------------------------
 # Compiling the Exim monitor: If you want to compile the Exim monitor, a
@@ -565,7 +566,6 @@ SUPPORT_DANE=yes
 
 # SUPPORT_EXIM_FILTER=2
 
-#------------------------------------------------------------------------------
 # Compiling with support for Sieve filters is the default. To disable this
 # uncomment the line below.
 
@@ -576,6 +576,10 @@ SUPPORT_DANE=yes
 
 # SUPPORT_SIEVE_FILTER=2
 
+#------------------------------------------------------------------------------
+# Uncomment the line to include DSCP support.  Set to 2 for a module build.
+# SUPPORT_DSCP
+
 #------------------------------------------------------------------------------
 # Compiling Exim with content scanning support: If you want to compile Exim
 # with support for message body content scanning, set WITH_CONTENT_SCAN to
@@ -588,23 +592,12 @@ SUPPORT_DANE=yes
 # 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.
 
-# 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 built with TLS, Exim includes code to support DKIM (DomainKeys Identified
 # Mail, RFC4871) signing and verification.  Verification of signatures is
@@ -650,8 +643,8 @@ DISABLE_MAL_MKS=yes
 # Uncomment the following to remove the fast-ramp two-phase-queue-run support
 # DISABLE_QUEUE_RAMP=yes
 
-# Uncomment the following lines to add SRS (Sender Rewriting Scheme) support
-# using only native facilities.
+# Uncomment the following lines to add SRS (Sender Rewriting Scheme) support.
+# This needs no library.
 # SUPPORT_SRS=yes
 
 # Uncomment the following to remove support for the ESMTP extension "WELLKNOWN"
@@ -689,20 +682,14 @@ DISABLE_MAL_MKS=yes
 # 1.3.2-3 works.  It seems that the OpenDMARC project broke their API.
 # Use this option if you need to build with an old library (1.3.x)
 # DMARC_API=100300
+#
+# As an alternative, see EXPERIMENTAL_DMARC_NATIVE
+# in doc-txt/experimental-spec.txt
 
 # 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
-# LDFLAGS lines.
-
-# EXPERIMENTAL_BRIGHTMAIL=yes
-# CFLAGS  += -I/opt/brightmail/bsdk-6.0/include
-# LDFLAGS += -lxml2_single -lbmiclient_single -L/opt/brightmail/bsdk-6.0/lib
-
 # Uncomment the following to include extra information in fail DSN message (bounces)
 # EXPERIMENTAL_DSN_INFO=yes
 
@@ -712,7 +699,8 @@ DISABLE_MAL_MKS=yes
 # Uncomment the following line to add SRV smtps support
 # EXPERIMENTAL_SRV_SMTPS=yes
 #
-# Uncomment the following line to add XCLIENT support
+# Uncomment the following line to add XCLIENT support.
+# Set to 2 for a dynamic-load module.
 # EXPERIMENTAL_XCLIENT=yes
 
 ###############################################################################
@@ -752,6 +740,8 @@ DISABLE_MAL_MKS=yes
 # sqlite
 # USE_SQLITE = yes
 # DBMLIB = -lsqlite3
+#  If using Gnu Make this is usable:
+# DBMLIB = $(shell pkg-config --libs sqlite3)
 
 
 #------------------------------------------------------------------------------
@@ -875,7 +865,7 @@ FIXED_NEVER_USERS=root
 # right and so the exim binary actually loads dynamic lookup modules.
 #
 # Libraries being built as modules should be added to respective
-# LOOKUP_*_INCLUDE and LOOKUP_*_LIBS rather than the the ones for the
+# AUTH_*_INCLUDE and AUTH_*_LIBS rather than the the ones for the
 # core exim build.  This gets them linked with the module instead.
 # The heimdal does build but we have no test coverage so it is not know to work.
 
@@ -1136,12 +1126,12 @@ ZCAT_COMMAND=/usr/bin/zcat
 # Proxying.
 #
 # If you may want to use outbound (client-side) proxying, using Socks5,
-# uncomment the line below.
+# uncomment the line below.  To build as a dynamic module set it to 2.
 
 # SUPPORT_SOCKS=yes
 
 # If you may want to use inbound (server-side) proxying, using Proxy Protocol,
-# uncomment the line below.
+# uncomment the line below.  To build as a dynamic module set it to 2.
 
 # SUPPORT_PROXY=yes
 
@@ -1179,7 +1169,10 @@ ZCAT_COMMAND=/usr/bin/zcat
 # SUPPORT_SPF=yes
 # CFLAGS  += -I/usr/local/include
 # LDFLAGS += -lspf2
+# SUPPORT_SPF_LIBS= -lspf2
 
+# As an alternative, see EXPERIMENTAL_SPF_PERL
+# in doc-txt/experimental-spec.txt
 
 #------------------------------------------------------------------------------
 # Support for authentication via Radius is also available. The Exim support,
@@ -1215,23 +1208,6 @@ ZCAT_COMMAND=/usr/bin/zcat
 # using the original API.
 
 
-#------------------------------------------------------------------------------
-# Support for authentication via the Cyrus SASL pwcheck daemon is available.
-# Note, however, that pwcheck is now deprecated in favour of saslauthd (see
-# next item). The Exim support for pwcheck, which is intented for use in
-# conjunction with the SMTP AUTH facilities, is included only when requested by
-# setting the following parameter to the location of the pwcheck daemon's
-# socket.
-#
-# 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
-# pwcheck, you should arrange for it to be started by root at boot time.
-
-# CYRUS_PWCHECK_SOCKET=/var/pwcheck/pwcheck
-
-
 #------------------------------------------------------------------------------
 # Support for authentication via the Cyrus SASL saslauthd daemon is available.
 # The Exim support, which is intended for use in conjunction with the SMTP AUTH
diff --git a/src/src/acl.c b/src/src/acl.c
index 80a17c937..044ebeaed 100644
--- a/src/src/acl.c
+++ b/src/src/acl.c
@@ -63,9 +63,6 @@ enum { ACLC_ACL,
        ACLC_ADD_HEADER,
        ACLC_ATRN_DOMAINS,
        ACLC_AUTHENTICATED,
-#ifdef EXPERIMENTAL_BRIGHTMAIL
-       ACLC_BMI_OPTIN,
-#endif
        ACLC_CONDITION,
        ACLC_CONTINUE,
        ACLC_CONTROL,
@@ -80,7 +77,7 @@ enum { ACLC_ACL,
        ACLC_DKIM_SIGNER,
        ACLC_DKIM_STATUS,
 #endif
-#ifdef SUPPORT_DMARC
+#ifdef EXIM_HAVE_DMARC
        ACLC_DMARC_STATUS,
 #endif
        ACLC_DNSLISTS,
@@ -168,19 +165,6 @@ static condition_def conditions[] = {
 				    ACL_BIT_NOTSMTP_START |
 				    ACL_BIT_CONNECT | ACL_BIT_HELO),
   },
-#ifdef EXPERIMENTAL_BRIGHTMAIL
-  [ACLC_BMI_OPTIN] =		{ US"bmi_optin",	ACD_EXP | ACD_MOD,
-				  FORBIDDEN(ACL_BIT_AUTH |
-				    ACL_BIT_CONNECT | ACL_BIT_HELO |
-				    ACL_BIT_DATA | ACL_BIT_MIME |
-				    ACL_BIT_PRDR |
-				    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
   [ACLC_CONDITION] =		{ US"condition",	ACD_EXP,
 				  FORBIDDEN(0) },
   [ACLC_CONTINUE] =		{ US"continue",		ACD_EXP | ACD_MOD,
@@ -222,9 +206,9 @@ static condition_def conditions[] = {
       ),
   },
 #endif
-#ifdef SUPPORT_DMARC
+#ifdef EXIM_HAVE_DMARC
   [ACLC_DMARC_STATUS] =		{ US"dmarc_status",
-# if SUPPORT_DMARC==2
+# if EXIM_HAVE_DMARC==2
 				  ACD_LOAD |
 # endif
 				  ACD_EXP,
@@ -406,7 +390,7 @@ static int spf_condx[] = { ACLC_SPF, ACLC_SPF_GUESS, -1 };
 #  if SUPPORT_DKIM==2
 static int dkim_condx[] = { ACLC_DKIM_SIGNER, ACLC_DKIM_STATUS, -1 };
 #  endif
-#  if SUPPORT_DMARC==2
+#  if EXIM_HAVE_DMARC==2
 static int dmarc_condx[] = { ACLC_DMARC_STATUS, -1 };
 #  endif
 
@@ -420,7 +404,7 @@ static condition_module condition_modules[] = {
 #  if SUPPORT_DKIM==2
   {.mod_name = US"dkim", .conditions = dkim_condx},
 #  endif
-#  if SUPPORT_DMARC==2
+#  if EXIM_HAVE_DMARC==2
   {.mod_name = US"dmarc", .conditions = dmarc_condx},
 #  endif
 };
@@ -433,9 +417,6 @@ static condition_module condition_modules[] = {
 
 enum {
   CONTROL_AUTH_UNADVERTISED,
-#ifdef EXPERIMENTAL_BRIGHTMAIL
-  CONTROL_BMI_RUN,
-#endif
   CONTROL_CASEFUL_LOCAL_PART,
   CONTROL_CASELOWER_LOCAL_PART,
   CONTROL_CUTTHROUGH_DELIVERY,
@@ -443,7 +424,7 @@ enum {
 #ifndef DISABLE_DKIM
   CONTROL_DKIM_VERIFY,
 #endif
-#ifdef SUPPORT_DMARC
+#ifdef EXIM_HAVE_DMARC
   CONTROL_DMARC_VERIFY,
   CONTROL_DMARC_FORENSIC,
 #endif
@@ -493,10 +474,6 @@ static control_def controls_list[] = {
 				  (unsigned)
 				  ~(ACL_BIT_CONNECT | ACL_BIT_HELO)
   },
-#ifdef EXPERIMENTAL_BRIGHTMAIL
-[CONTROL_BMI_RUN] =
-  { US"bmi_run",                 FALSE,		0 },
-#endif
 [CONTROL_CASEFUL_LOCAL_PART] =
   { US"caseful_local_part",      FALSE, (unsigned) ~ACL_BIT_RCPT },
 [CONTROL_CASELOWER_LOCAL_PART] =
@@ -515,7 +492,7 @@ static control_def controls_list[] = {
   },
 #endif
 
-#ifdef SUPPORT_DMARC
+#ifdef EXIM_HAVE_DMARC
 [CONTROL_DMARC_VERIFY] =
   { US"dmarc_disable_verify",    FALSE,
 	  ACL_BIT_DATA | ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START
@@ -895,11 +872,8 @@ Returns:      pointer to ACL, or NULL
 acl_block *
 acl_read(uschar *(*func)(void), uschar **error)
 {
-acl_block *yield = NULL;
-acl_block **lastp = &yield;
-acl_block *this = NULL;
-acl_condition_block *cond;
-acl_condition_block **condp = NULL;
+acl_block * yield = NULL, ** lastp = &yield, * this = NULL;
+acl_condition_block * cond, ** condp = NULL;
 const uschar * s;
 
 *error = NULL;
@@ -1275,7 +1249,7 @@ if (log_message && log_message != user_message)
   if (!logged)
     {
     int length = Ustrlen(text) + 1;
-    log_write(0, LOG_MAIN, "%s", text);
+    log_write(LOG_MAIN, "%s", text);
     logged = store_malloc(sizeof(string_item) + length);
     logged->text = US logged + sizeof(string_item);
     memcpy(logged->text, text, length);
@@ -1293,7 +1267,7 @@ Log an error. */
 
 if (where > ACL_WHERE_NOTSMTP)
   {
-  log_write(0, LOG_MAIN|LOG_PANIC, "ACL \"warn\" with \"message\" setting "
+  log_write(LOG_MAIN|LOG_PANIC, "ACL \"warn\" with \"message\" setting "
     "found in a non-message (%s) ACL: cannot specify header lines here: "
     "message ignored", acl_wherenames[where]);
   return;
@@ -1344,7 +1318,7 @@ if (host_lookup_failed)
 
 /* Need to do a lookup */
 
-HDEBUG(D_acl)
+HDEBUG(acl)
   debug_printf_indent("looking up host name to force name/address consistency check\n");
 
 if ((rc = host_name_lookup()) != OK)
@@ -1415,7 +1389,7 @@ for (dns_record * rr = dns_next_rr(dnsa, dnss, reset);
     {
     /* If the client IP address matches the target IP address, it's good! */
 
-    DEBUG(D_acl) debug_printf_indent("CSA target address is %s\n", da->address);
+    DEBUG(acl) debug_printf_indent("CSA target address is %s\n", da->address);
 
     if (strcmpic(sender_host_address, da->address) == 0) return CSA_OK;
     }
@@ -1458,7 +1432,7 @@ tree_node *t;
 const uschar *found;
 int priority, weight, port;
 dns_answer * dnsa;
-dns_scan dnss;
+dns_scan dnss = {0};
 dns_record *rr;
 int rc, type, yield;
 #define TARGET_SIZE 256
@@ -1548,7 +1522,7 @@ for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS);
   GETSHORT(weight, p);
   GETSHORT(port, p);
 
-  DEBUG(D_acl)
+  DEBUG(acl)
     debug_printf_indent("CSA priority=%d weight=%d port=%d\n", priority, weight, port);
 
   /* Check the CSA version number */
@@ -1588,7 +1562,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, TARGET_SIZE);
 
-  DEBUG(D_acl) debug_printf_indent("CSA target is %s\n", target);
+  DEBUG(acl) debug_printf_indent("CSA target is %s\n", target);
 
   break;
   }
@@ -1607,7 +1581,7 @@ be authorized. (This is an odd configuration because weight=2 target=. is
 equivalent to weight=1, but we check for it in order to keep load off the
 root name servers.) Note that dn_expand() turns "." into "". */
 
-if (Ustrcmp(target, "") == 0)
+if (!*target)
   {
   yield = CSA_FAIL_NOADDR;
   goto out;
@@ -1879,7 +1853,7 @@ switch(vp->value)
     *log_msgptr = *user_msgptr = string_sprintf("client SMTP authorization %s",
                                               csa_reason_string[rc]);
     csa_status = csa_status_string[rc];
-    DEBUG(D_acl) debug_printf_indent("CSA result %s\n", csa_status);
+    DEBUG(acl) debug_printf_indent("CSA result %s\n", csa_status);
     rc = csa_return_code[rc];
     goto OUT;
 
@@ -2207,7 +2181,7 @@ else if (verify_sender_address)
       rc = sender_vaddr->special_action;
       *basic_errno = sender_vaddr->basic_errno;
       }
-    HDEBUG(D_acl) debug_printf_indent("using cached sender verify result\n");
+    HDEBUG(acl) debug_printf_indent("using cached sender verify result\n");
     }
 
   /* Do a new verification, and cache the result. The cache is used to avoid
@@ -2254,12 +2228,12 @@ else if (verify_sender_address)
       rc = verify_address(sender_vaddr, -1, verify_options, callout,
         callout_overall, callout_connect, se_mailfrom, pm_mailfrom, &routed);
 
-      HDEBUG(D_acl) debug_printf_indent("----------- end verify ------------\n");
+      HDEBUG(acl) debug_printf_indent("----------- end verify ------------\n");
 
       if (rc != OK)
         *basic_errno = sender_vaddr->basic_errno;
       else
-	DEBUG(D_acl)
+	DEBUG(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);
@@ -2269,7 +2243,7 @@ else if (verify_sender_address)
       }
     else
       {
-      DEBUG(D_acl) debug_printf_indent(" null sender\n");
+      DEBUG(acl) debug_printf_indent(" null sender\n");
       rc = OK;
       }
 
@@ -2308,7 +2282,7 @@ else
   addr2 = *addr;
   rc = verify_address(&addr2, -1, verify_options|vopt_is_recipient, callout,
     callout_overall, callout_connect, se_mailfrom, pm_mailfrom, NULL);
-  HDEBUG(D_acl) debug_printf_indent("----------- end verify ------------\n");
+  HDEBUG(acl) debug_printf_indent("----------- end verify ------------\n");
 
   *basic_errno = addr2.basic_errno;
   *log_msgptr = addr2.message;
@@ -2328,7 +2302,7 @@ if (  rc == DEFER
       || callout_defer_ok && *basic_errno == ERRNO_CALLOUTDEFER
    )  )
   {
-  HDEBUG(D_acl) debug_printf_indent("verify defer overridden by %s\n",
+  HDEBUG(acl) debug_printf_indent("verify defer overridden by %s\n",
     defer_ok? "defer_ok" : "callout_defer_ok");
   rc = OK;
   }
@@ -2475,22 +2449,18 @@ Returns:       OK        - Sender's rate is above limit
 */
 
 static int
-acl_ratelimit(const uschar *arg, int where, uschar **log_msgptr)
+acl_ratelimit(const uschar * arg, int where, uschar ** log_msgptr)
 {
 double limit, period, count;
-uschar *ss;
-uschar *key = NULL;
-uschar *unique = NULL;
-int sep = '/';
+uschar * ss, * key = NULL, * unique = NULL;
+int sep = '/', mode = RATE_PER_WHAT, old_pool, rc;
 BOOL leaky = FALSE, strict = FALSE, readonly = FALSE;
 BOOL noupdate = FALSE, badacl = FALSE;
-int mode = RATE_PER_WHAT;
-int old_pool, rc;
 tree_node ** anchor = NULL, * t;
-open_db dbblock, *dbm;
+open_db dbblock, * dbm;
 int dbdb_size;
-dbdata_ratelimit *dbd;
-dbdata_ratelimit_unique *dbdb;
+dbdata_ratelimit * dbd;
+dbdata_ratelimit_unique * dbdb;
 struct timeval tv;
 
 /* Parse the first two options and record their values in expansion
@@ -2627,7 +2597,7 @@ key = string_sprintf("%s/%s/%s%s",
   unique == NULL ? "" : "unique/",
   key);
 
-HDEBUG(D_acl)
+HDEBUG(acl)
   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.
@@ -2657,7 +2627,7 @@ else switch(mode)
     anchor = &ratelimiters_cmd;
     break;
   default:
-    log_write_die(0, LOG_MAIN,
+    log_write_die(LOG_MAIN,
       "internal ACL error: unknown ratelimit mode %d", mode);
     /*NOTREACHED*/
     break;
@@ -2670,7 +2640,7 @@ if ((t = tree_search(*anchor, key)))
   rc = (dbd->rate < limit)? FAIL : OK;
   store_pool = old_pool;
   sender_rate = string_sprintf("%.1f", dbd->rate);
-  HDEBUG(D_acl)
+  HDEBUG(acl)
     debug_printf_indent("ratelimit found pre-computed rate %s\n", sender_rate);
   return rc;
   }
@@ -2682,7 +2652,7 @@ if (!(dbm = dbfn_open(US"ratelimit", O_RDWR|O_CREAT, &dbblock, TRUE, TRUE)))
   {
   store_pool = old_pool;
   sender_rate = NULL;
-  HDEBUG(D_acl) debug_printf_indent("ratelimit database not available\n");
+  HDEBUG(acl) debug_printf_indent("ratelimit database not available\n");
   *log_msgptr = US"ratelimit database not available";
   return DEFER;
   }
@@ -2694,7 +2664,7 @@ gettimeofday(&tv, NULL);
 if (dbdb)
   {
   /* Locate the basic ratelimit block inside the DB data. */
-  HDEBUG(D_acl) debug_printf_indent("ratelimit found key in database\n");
+  HDEBUG(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
@@ -2704,7 +2674,7 @@ if (dbdb)
 
   if(unique && tv.tv_sec > dbdb->bloom_epoch + period)
     {
-    HDEBUG(D_acl) debug_printf_indent("ratelimit discarding old Bloom filter\n");
+    HDEBUG(acl) debug_printf_indent("ratelimit discarding old Bloom filter\n");
     dbdb = NULL;
     }
 
@@ -2712,7 +2682,7 @@ if (dbdb)
 
   if(unique && dbdb_size < sizeof(*dbdb))
     {
-    HDEBUG(D_acl) debug_printf_indent("ratelimit discarding undersize Bloom filter\n");
+    HDEBUG(acl) debug_printf_indent("ratelimit discarding undersize Bloom filter\n");
     dbdb = NULL;
     }
   }
@@ -2725,14 +2695,14 @@ if (!dbdb)
   if (!unique)
     {
     /* No Bloom filter. This basic ratelimit block is initialized below. */
-    HDEBUG(D_acl) debug_printf_indent("ratelimit creating new rate data block\n");
+    HDEBUG(acl) debug_printf_indent("ratelimit creating new rate data block\n");
     dbdb_size = sizeof(*dbd);
     dbdb = store_get(dbdb_size, GET_UNTAINTED);
     }
   else
     {
     int extra;
-    HDEBUG(D_acl) debug_printf_indent("ratelimit creating new Bloom filter\n");
+    HDEBUG(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;
@@ -2816,7 +2786,7 @@ if (unique && !readonly)
   /* Scan the bits corresponding to this event. A zero bit means we have
   not seen it before. Ensure all bits are set to record this event. */
 
-  HDEBUG(D_acl) debug_printf_indent("ratelimit checking uniqueness of %s\n", unique);
+  HDEBUG(acl) debug_printf_indent("ratelimit checking uniqueness of %s\n", unique);
 
   seen = TRUE;
   for (n = 0; n < 8; n++, hash += hinc)
@@ -2834,11 +2804,11 @@ if (unique && !readonly)
 
   if (seen)
     {
-    HDEBUG(D_acl) debug_printf_indent("ratelimit event found in Bloom filter\n");
+    HDEBUG(acl) debug_printf_indent("ratelimit event found in Bloom filter\n");
     count = 0.0;
     }
   else
-    HDEBUG(D_acl) debug_printf_indent("ratelimit event added to Bloom filter\n");
+    HDEBUG(acl) debug_printf_indent("ratelimit event added to Bloom filter\n");
   }
 
 /* If there was no previous ratelimit data block for this key, initialize
@@ -2847,9 +2817,9 @@ is what would be computed by the code below for an infinite interval. */
 
 if (!dbd)
   {
-  HDEBUG(D_acl) debug_printf_indent("ratelimit initializing new key's rate data\n");
+  HDEBUG(acl) debug_printf_indent("ratelimit initializing new key's rate data\n");
   dbd = &dbdb->dbd;
-  dbd->time_stamp = tv.tv_sec;
+  dbd->gen.time_stamp = tv.tv_sec;
   dbd->time_usec = tv.tv_usec;
   dbd->rate = count;
   }
@@ -2899,7 +2869,7 @@ else
 
   double this_time = (double)tv.tv_sec
                    + (double)tv.tv_usec / 1000000.0;
-  double prev_time = (double)dbd->time_stamp
+  double prev_time = (double)dbd->gen.time_stamp
                    + (double)dbd->time_usec / 1000000.0;
 
   /* We must avoid division by zero, and deal gracefully with the clock going
@@ -2916,7 +2886,7 @@ else
   using the smoothing factor a. In order to measure sized events, multiply the
   instantaneous rate by the count of bytes or recipients etc. */
 
-  dbd->time_stamp = tv.tv_sec;
+  dbd->gen.time_stamp = tv.tv_sec;
   dbd->time_usec = tv.tv_usec;
   dbd->rate = (1 - a) * count / i_over_p + a * dbd->rate;
 
@@ -2948,11 +2918,11 @@ neither leaky nor strict are set, so we do not do any updates. */
 if ((rc == FAIL && leaky) || strict)
   {
   dbfn_write(dbm, key, dbdb, dbdb_size);
-  HDEBUG(D_acl) debug_printf_indent("ratelimit db updated\n");
+  HDEBUG(acl) debug_printf_indent("ratelimit db updated\n");
   }
 else
   {
-  HDEBUG(D_acl) debug_printf_indent("ratelimit db not updated: %s\n",
+  HDEBUG(acl) debug_printf_indent("ratelimit db not updated: %s\n",
     readonly? "readonly mode" : "over the limit, but leaky");
   }
 
@@ -2972,7 +2942,7 @@ order to ensure that it is done using the correct storage pool. */
 store_pool = old_pool;
 sender_rate = string_sprintf("%.1f", dbd->rate);
 
-HDEBUG(D_acl)
+HDEBUG(acl)
   debug_printf_indent("ratelimit computed rate %s\n", sender_rate);
 
 return rc;
@@ -3062,7 +3032,7 @@ while ((ele = string_nextinlist(&list, &slash, NULL, 0)))
 
 if (!(dbm = dbfn_open(US"seen", O_RDWR|O_CREAT, &dbblock, TRUE, TRUE)))
   {
-  HDEBUG(D_acl) debug_printf_indent("database for 'seen' not available\n");
+  HDEBUG(acl) debug_printf_indent("database for 'seen' not available\n");
   *log_msgptr = US"database for 'seen' not available";
   return DEFER;
   }
@@ -3071,36 +3041,34 @@ dbd = dbfn_read_with_length(dbm, key, NULL);
 now = time(NULL);
 if (dbd)		/* an existing record */
   {
-  time_t diff = now - dbd->time_stamp;	/* time since the record was written */
+  time_t diff = now - dbd->gen.time_stamp;  /* time since record was written */
 
   if (before ? diff >= interval : diff < interval)
     yield = OK;
 
   if (mode == SEEN_READONLY)
-    { HDEBUG(D_acl) debug_printf_indent("seen db not written (readonly)\n"); }
+    { HDEBUG(acl) debug_printf_indent("seen db not written (readonly)\n"); }
   else if (mode == SEEN_WRITE || !before)
     {
-    dbd->time_stamp = now;
-    dbfn_write(dbm, key, dbd, sizeof(*dbd));
-    HDEBUG(D_acl) debug_printf_indent("seen db written (update)\n");
+    dbfn_write_ts(dbm, key, dbd, sizeof(*dbd), now);
+    HDEBUG(acl) debug_printf_indent("seen db written (update)\n");
     }
   else if (diff >= refresh)
     {
-    dbd->time_stamp = now - interval;
-    dbfn_write(dbm, key, dbd, sizeof(*dbd));
-    HDEBUG(D_acl) debug_printf_indent("seen db written (refresh)\n");
+    dbfn_write_ts(dbm, key, dbd, sizeof(*dbd), now - interval);
+    HDEBUG(acl) debug_printf_indent("seen db written (refresh)\n");
     }
   }
 else
   {			/* No record found, yield always FAIL */
   if (mode != SEEN_READONLY)
     {
-    dbdata_seen d = {.time_stamp = now};
-    dbfn_write(dbm, key, &d, sizeof(*dbd));
-    HDEBUG(D_acl) debug_printf_indent("seen db written (create)\n");
+    dbdata_seen d = {0};
+    dbfn_write_ts(dbm, key, &d, sizeof(d), now);
+    HDEBUG(acl) debug_printf_indent("seen db written (create)\n");
     }
   else
-    HDEBUG(D_acl) debug_printf_indent("seen db not written (readonly)\n");
+    HDEBUG(acl) debug_printf_indent("seen db not written (readonly)\n");
   }
 
 dbfn_close(dbm);
@@ -3184,7 +3152,7 @@ if (r == HOST_FIND_FAILED || r == HOST_FIND_AGAIN)
   return DEFER;
   }
 
-HDEBUG(D_acl)
+HDEBUG(acl)
   debug_printf_indent("udpsend [%s]:%d %s\n", h->address, portnum, arg);
 
 /*XXX this could better use sendto */
@@ -3207,7 +3175,7 @@ if (r < len)
   return DEFER;
   }
 
-HDEBUG(D_acl)
+HDEBUG(acl)
   debug_printf_indent("udpsend %d bytes\n", r);
 
 return OK;
@@ -3342,11 +3310,11 @@ for (; cb; cb = cb->next)
     case of rejection. They are expanded later. */
 
     case ACLC_MESSAGE:
-      HDEBUG(D_acl) debug_printf_indent("  message: %s\n", cb->arg);
+      HDEBUG(acl) debug_printf_indent("  message: %s\n", cb->arg);
       user_message = cb->arg;	continue;
 
     case ACLC_LOG_MESSAGE:
-      HDEBUG(D_acl) debug_printf_indent("l_message: %s\n", cb->arg);
+      HDEBUG(acl) debug_printf_indent("l_message: %s\n", cb->arg);
       log_message = cb->arg;	continue;
 
     /* The endpass "condition" just sets a flag to show it occurred. This is
@@ -3373,7 +3341,7 @@ for (; cb; cb = cb->next)
 
   /* Show condition, and expanded condition if it's different */
 
-  HDEBUG(D_acl)
+  HDEBUG(acl)
     {
     int lhswidth = 0;
     debug_printf_indent("check %s%s %n",
@@ -3440,7 +3408,7 @@ for (; cb; cb = cb->next)
     case ACLC_ATRN_DOMAINS:
       if (is_tainted(arg))
 	{
-	log_write(0, LOG_MAIN|LOG_PANIC,
+	log_write(LOG_MAIN|LOG_PANIC,
 		  "attempt to used tainted value '%s' for atrn_domains%#s",
 		    arg,
 		    config_lineno
@@ -3460,17 +3428,6 @@ for (; cb; cb = cb->next)
 	      &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL) : FAIL;
       break;
 
-    #ifdef EXPERIMENTAL_BRIGHTMAIL
-    case ACLC_BMI_OPTIN:
-      {
-      int old_pool = store_pool;
-      store_pool = POOL_PERM;
-      bmi_current_optin = string_copy(arg);
-      store_pool = old_pool;
-      }
-    break;
-    #endif
-
     case ACLC_CONDITION:
     /* The true/false parsing here should be kept in sync with that used in
     expand.c when dealing with ECOND_BOOL so that we don't have too many
@@ -3512,16 +3469,10 @@ for (; cb; cb = cb->next)
 	  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:
 	  f.dkim_disable_verify = TRUE;
-# ifdef SUPPORT_DMARC
+# ifdef EXIM_HAVE_DMARC
 	  /* Since DKIM was blocked, skip DMARC too */
 	  f.dmarc_disable_verify = TRUE;
 	  f.dmarc_enable_forensic = FALSE;
@@ -3529,7 +3480,7 @@ for (; cb; cb = cb->next)
 	break;
 #endif
 
-#ifdef SUPPORT_DMARC
+#ifdef EXIM_HAVE_DMARC
 	case CONTROL_DMARC_VERIFY:
 	  f.dmarc_disable_verify = TRUE;
 	  break;
@@ -3538,44 +3489,18 @@ for (; cb; cb = cb->next)
 	  f.dmarc_enable_forensic = TRUE;
 	  break;
 #endif
-
+#ifdef SUPPORT_DSCP
 	case CONTROL_DSCP:
-	  if (*p == '/')
-	    {
-	    int af, socklevel, 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. */
-	    if (smtp_in_fd < 0) return ERROR;
-	    if ((af = ip_get_address_family(smtp_in_fd)) < 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, &socklevel, &optname, &value))
-	      if (setsockopt(smtp_in_fd, socklevel, 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 %q\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;
-	    }
+	  {
+	  misc_module_info * mi = misc_mod_find(US"dscp", &log_message);
+	  typedef uschar * (*fn_t)(const uschar *, const uschar *);
+	  if (!mi)
+	    rc = DEFER;
+	  else if ((*log_msgptr = ((fn_t *) mi->functions)[DSCP_ACL] (arg, p)))
+	    rc = ERROR;
 	  break;
+	  }
+#endif
 
 	case CONTROL_ERROR:
 	  return ERROR;
@@ -3786,7 +3711,7 @@ for (; cb; cb = cb->next)
 	    ignored = US"repeated";
 	  else if (cutthrough.callout_hold_only)
 	    {
-	    DEBUG(D_acl)
+	    DEBUG(acl)
 	      debug_printf_indent(" cutthrough request upgrades callout hold\n");
 	    cutthrough.callout_hold_only = FALSE;
 	    cutthrough.delivery = TRUE;	/* control accepted */
@@ -3815,7 +3740,7 @@ for (; cb; cb = cb->next)
 	      }
 	    }
 
-	  DEBUG(D_acl) if (ignored)
+	  DEBUG(acl) if (ignored)
 	    debug_printf(" cutthrough request ignored on %s item\n", ignored);
 	  }
 	break;
@@ -3903,11 +3828,11 @@ for (; cb; cb = cb->next)
         }
       else
         {
-        HDEBUG(D_acl) debug_printf_indent("delay modifier requests %d-second delay\n",
+        HDEBUG(acl) debug_printf_indent("delay modifier requests %d-second delay\n",
           delay);
         if (host_checking)
           {
-          HDEBUG(D_acl)
+          HDEBUG(acl)
             debug_printf_indent("delay skipped in -bh checking mode\n");
           }
 
@@ -3936,7 +3861,7 @@ for (; cb; cb = cb->next)
 	      n = 1;
 	      }
 	    if (poll(&p, n, delay*1000) > 0)
-	      HDEBUG(D_acl) debug_printf_indent("delay cancelled by peer close\n");
+	      HDEBUG(acl) debug_printf_indent("delay cancelled by peer close\n");
 	    }
 #else
 	  /* Lacking POLLRDHUP it appears to be impossible to detect that a
@@ -3974,32 +3899,25 @@ for (; cb; cb = cb->next)
       }
 #endif
 
-#ifdef SUPPORT_DMARC
+#ifdef EXIM_HAVE_DMARC
     case ACLC_DMARC_STATUS:
       /* See comment on ACLC_SPF wrt. coding issues */
       {
       misc_module_info * mi = misc_mod_find(US"dmarc", &log_message);
-      typedef uschar * (*efn_t)(int);
-      const uschar * expanded_query;
+      typedef int (*pfn_t)(void);
+      typedef int (*mfn_t)(const uschar * const *);
 
       if (!mi)
-	{ rc = DEFER; break; }			/* shouldn't happen */
-
-      if (!f.dmarc_has_been_checked)
+	rc = DEFER;				/* shouldn't happen */
+      else
 	{
-	typedef int (*pfn_t)(void);
-	(void) (((pfn_t *) mi->functions)[DMARC_PROCESS]) ();
-	f.dmarc_has_been_checked = TRUE;
+	if (!f.dmarc_has_been_checked)		/* only once per message */
+	  {
+	  (void) (((pfn_t *) mi->functions)[DMARC_PROCESS]) ();
+	  f.dmarc_has_been_checked = TRUE;
+	  }
+	rc = (((mfn_t *) mi->functions)[DMARC_RESULT_INLIST]) (&arg);
 	}
-
-      /* used long way of dmarc_exim_expand_query() in case we need more
-      view into the process in the future. */
-
-      /*XXX is this call used with any other arg? */
-      expanded_query = (((efn_t *) mi->functions)[DMARC_EXPAND_QUERY])
-						      (DMARC_VERIFY_STATUS);
-      rc = match_isinlist(expanded_query,
-			  &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL);
       }
       break;
 #endif
@@ -4070,7 +3988,7 @@ for (; cb; cb = cb->next)
         else
           {
           logbits |= LOG_MAIN|LOG_REJECT;
-          log_write(0, LOG_MAIN|LOG_PANIC, "unknown log name %q in "
+          log_write(LOG_MAIN|LOG_PANIC, "unknown log name %q in "
             "\"log_reject_target\" in %s ACL", ss, acl_wherenames[where]);
           }
         }
@@ -4106,7 +4024,7 @@ for (; cb; cb = cb->next)
       Uskip_whitespace(&s);
 
       if (logbits == 0) logbits = LOG_MAIN;
-      log_write(0, logbits, "%s", string_printing(s));
+      log_write(logbits, "%s", string_printing(s));
       break;
       }
 
@@ -4168,7 +4086,7 @@ for (; cb; cb = cb->next)
 
 #ifdef WITH_CONTENT_SCAN
     case ACLC_REGEX:
-      rc = regex(&arg, textonly);
+      rc = exim_regex(&arg, textonly);
       break;
 #endif
 
@@ -4287,7 +4205,7 @@ for (; cb; cb = cb->next)
       break;
 
     default:
-      log_write_die(0, LOG_MAIN, "internal ACL error: unknown "
+      log_write_die(LOG_MAIN, "internal ACL error: unknown "
 	"condition %d", cb->type);
       break;
     }
@@ -4343,7 +4261,7 @@ if ((BIT(rc) & msgcond[verb]) != 0)
     if (!expmessage)
       {
       if (!f.expand_string_forcedfail)
-        log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand ACL message %q: %s",
+        log_write(LOG_MAIN|LOG_PANIC, "failed to expand ACL message %q: %s",
           user_message, expand_string_message);
       }
     else if (expmessage[0] != 0) *user_msgptr = expmessage;
@@ -4356,7 +4274,7 @@ if ((BIT(rc) & msgcond[verb]) != 0)
     if (!expmessage)
       {
       if (!f.expand_string_forcedfail)
-        log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand ACL message %q: %s",
+        log_write(LOG_MAIN|LOG_PANIC, "failed to expand ACL message %q: %s",
           log_message, expand_string_message);
       }
     else if (expmessage[0] != 0)
@@ -4406,8 +4324,8 @@ Returns:   a pointer to the next line
 */
 
 
-static uschar *acl_text;          /* Current pointer in the text */
-static uschar *acl_text_end;      /* Points one past the terminating '0' */
+static uschar * acl_text;          /* Current pointer in the text */
+static uschar * acl_text_end;      /* Points one past the terminating '0' */
 
 
 static uschar *
@@ -4526,13 +4444,12 @@ Returns:       OK         access is granted
 */
 
 static int
-acl_check_internal(int where, address_item *addr, uschar *s,
-  uschar **user_msgptr, uschar **log_msgptr)
+acl_check_internal(int where, address_item * addr, uschar * s,
+  uschar ** user_msgptr, uschar ** log_msgptr)
 {
 int fd = -1;
-acl_block *acl = NULL;
-uschar *acl_name = US"inline ACL";
-uschar *ss;
+acl_block * acl = NULL;
+uschar * acl_name = US"inline ACL", * ss;
 
 /* Catch configuration loops */
 
@@ -4544,7 +4461,7 @@ if (acl_level > 20)
 
 if (!s)
   {
-  HDEBUG(D_acl) debug_printf_indent("ACL is NULL: implicit DENY\n");
+  HDEBUG(acl) debug_printf_indent("ACL is NULL: implicit DENY\n");
   return FAIL;
   }
 
@@ -4570,7 +4487,7 @@ acl_text = ss;
 
 if (is_tainted(acl_text) && !f.running_in_test_harness)
   {
-  log_write(0, LOG_MAIN|LOG_PANIC,
+  log_write(LOG_MAIN|LOG_PANIC,
     "attempt to use tainted ACL text %q", acl_text);
   /* Avoid leaking info to an attacker */
   *log_msgptr = US"internal configuration error";
@@ -4590,11 +4507,11 @@ if (Ustrchr(ss, ' ') == NULL)
     {
     if (!(acl = (acl_block *)(t->data.ptr)))
       {
-      HDEBUG(D_acl) debug_printf_indent("ACL %q is empty: implicit DENY\n", ss);
+      HDEBUG(acl) debug_printf_indent("ACL %q is empty: implicit DENY\n", ss);
       return FAIL;
       }
     acl_name = string_sprintf("ACL %s", ss);
-    HDEBUG(D_acl) debug_printf_indent("using ACL %q\n", ss);
+    HDEBUG(acl) debug_printf_indent("using ACL %q\n", ss);
     }
 
   else if (*ss == '/')
@@ -4627,7 +4544,7 @@ if (Ustrchr(ss, ' ') == NULL)
     (void)close(fd);
 
     acl_name = string_sprintf("ACL %s", ss);
-    HDEBUG(D_acl) debug_printf_indent("read ACL from file %s\n", ss);
+    HDEBUG(acl) debug_printf_indent("read ACL from file %s\n", ss);
     }
   }
 
@@ -4667,7 +4584,7 @@ while ((acl_current = acl))
   config_filename = acl->srcfile;
   config_lineno = acl->srcline;
 
-  HDEBUG(D_acl)
+  HDEBUG(acl)
     {
     debug_printf_indent("processing %s %q", acl_name, verbs[acl->verb]);
     if (config_lineno) debug_printf(" (%s %d)", config_filename, config_lineno);
@@ -4687,7 +4604,7 @@ while ((acl_current = acl))
   switch (cond)
     {
     case DEFER:
-      HDEBUG(D_acl) debug_printf_indent("%s: condition test deferred in %s\n",
+      HDEBUG(acl) debug_printf_indent("%s: condition test deferred in %s\n",
 	verbs[acl->verb], acl_name);
       if (basic_errno != ERRNO_CALLOUTDEFER)
 	{
@@ -4702,17 +4619,17 @@ while ((acl_current = acl))
 
     default:      /* Paranoia */
     case ERROR:
-      HDEBUG(D_acl) debug_printf_indent("%s: condition test error in %s\n",
+      HDEBUG(acl) debug_printf_indent("%s: condition test error in %s\n",
 	verbs[acl->verb], acl_name);
       return ERROR;
 
     case OK:
-      HDEBUG(D_acl) debug_printf_indent("%s: condition test succeeded in %s\n",
+      HDEBUG(acl) debug_printf_indent("%s: condition test succeeded in %s\n",
 	verbs[acl->verb], acl_name);
       break;
 
     case FAIL:
-      HDEBUG(D_acl) debug_printf_indent("%s: condition test failed in %s\n",
+      HDEBUG(acl) debug_printf_indent("%s: condition test failed in %s\n",
 	verbs[acl->verb], acl_name);
       break;
 
@@ -4720,12 +4637,12 @@ while ((acl_current = acl))
     DISCARD can happen only for an "accept" or "discard" verb. */
 
     case DISCARD:
-      HDEBUG(D_acl) debug_printf_indent("%s: condition test yielded \"discard\" in %s\n",
+      HDEBUG(acl) debug_printf_indent("%s: condition test yielded \"discard\" in %s\n",
 	verbs[acl->verb], acl_name);
       break;
 
     case FAIL_DROP:
-      HDEBUG(D_acl) debug_printf_indent("%s: condition test yielded \"drop\" in %s\n",
+      HDEBUG(acl) debug_printf_indent("%s: condition test yielded \"drop\" in %s\n",
 	verbs[acl->verb], acl_name);
       break;
     }
@@ -4739,12 +4656,12 @@ while ((acl_current = acl))
     case ACL_ACCEPT:
       if (cond == OK || cond == DISCARD)
 	{
-	HDEBUG(D_acl) debug_printf_indent("end of %s: ACCEPT\n", acl_name);
+	HDEBUG(acl) debug_printf_indent("end of %s: ACCEPT\n", acl_name);
 	return cond;
 	}
       if (endpass_seen)
 	{
-	HDEBUG(D_acl) debug_printf_indent("accept: endpass encountered - denying access\n");
+	HDEBUG(acl) debug_printf_indent("accept: endpass encountered - denying access\n");
 	return cond;
 	}
       break;
@@ -4752,7 +4669,7 @@ while ((acl_current = acl))
     case ACL_DEFER:
       if (cond == OK)
 	{
-	HDEBUG(D_acl) debug_printf_indent("end of %s: DEFER\n", acl_name);
+	HDEBUG(acl) debug_printf_indent("end of %s: DEFER\n", acl_name);
 	if (acl_quit_check) goto badquit;
 	f.acl_temp_details = TRUE;
 	return DEFER;
@@ -4762,7 +4679,7 @@ while ((acl_current = acl))
     case ACL_DENY:
       if (cond == OK)
 	{
-	HDEBUG(D_acl) debug_printf_indent("end of %s: DENY\n", acl_name);
+	HDEBUG(acl) debug_printf_indent("end of %s: DENY\n", acl_name);
 	if (acl_quit_check) goto badquit;
 	return FAIL;
 	}
@@ -4771,13 +4688,13 @@ while ((acl_current = acl))
     case ACL_DISCARD:
       if (cond == OK || cond == DISCARD)
 	{
-	HDEBUG(D_acl) debug_printf_indent("end of %s: DISCARD\n", acl_name);
+	HDEBUG(acl) debug_printf_indent("end of %s: DISCARD\n", acl_name);
 	if (acl_quit_check) goto badquit;
 	return DISCARD;
 	}
       if (endpass_seen)
 	{
-	HDEBUG(D_acl)
+	HDEBUG(acl)
 	  debug_printf_indent("discard: endpass encountered - denying access\n");
 	return cond;
 	}
@@ -4786,7 +4703,7 @@ while ((acl_current = acl))
     case ACL_DROP:
       if (cond == OK)
 	{
-	HDEBUG(D_acl) debug_printf_indent("end of %s: DROP\n", acl_name);
+	HDEBUG(acl) debug_printf_indent("end of %s: DROP\n", acl_name);
 	if (acl_quit_check) goto badquit;
 	return FAIL_DROP;
 	}
@@ -4795,7 +4712,7 @@ while ((acl_current = acl))
     case ACL_REQUIRE:
       if (cond != OK)
 	{
-	HDEBUG(D_acl) debug_printf_indent("end of %s: not OK\n", acl_name);
+	HDEBUG(acl) debug_printf_indent("end of %s: not OK\n", acl_name);
 	if (acl_quit_check) goto badquit;
 	return cond;
 	}
@@ -4806,13 +4723,13 @@ while ((acl_current = acl))
 	acl_warn(where, *user_msgptr, *log_msgptr);
       else if (cond == DEFER && LOGGING(acl_warn_skipped))
 	if (config_lineno > 0)
-	  log_write(0, LOG_MAIN,
+	  log_write(LOG_MAIN,
 	    "%s Warning: ACL 'warn' statement skipped (in %s at line %d of %s):"
 	    " condition test deferred%s%s",
 	    host_and_ident(TRUE), acl_name, config_lineno, config_filename,
 	    *log_msgptr ? US": " : US"", *log_msgptr ? *log_msgptr : US"");
 	else
-	  log_write(0, LOG_MAIN,
+	  log_write(LOG_MAIN,
 	    "%s Warning: ACL 'warn' statement skipped (in %s):"
 	    " condition test deferred%s%s",
 	    host_and_ident(TRUE), acl_name,
@@ -4821,7 +4738,7 @@ while ((acl_current = acl))
       break;
 
     default:
-      log_write_die(0, LOG_MAIN, "internal ACL error: unknown verb %d",
+      log_write_die(LOG_MAIN, "internal ACL error: unknown verb %d",
 	acl->verb);
       break;
     }
@@ -4833,7 +4750,7 @@ while ((acl_current = acl))
 
 /* We have reached the end of the ACL. This is an implicit DENY. */
 
-HDEBUG(D_acl) debug_printf_indent("end of %s: implicit DENY\n", acl_name);
+HDEBUG(acl) debug_printf_indent("end of %s: implicit DENY\n", acl_name);
 return FAIL;
 
 badquit:
@@ -4911,10 +4828,9 @@ return f.search_find_defer ? DEFER : ERROR;
 
 /* Alternate interface for ACL, used by expansions */
 int
-acl_eval(int where, uschar *s, uschar **user_msgptr, uschar **log_msgptr)
+acl_eval(int where, uschar * s, uschar ** user_msgptr, uschar ** log_msgptr)
 {
-address_item adb;
-address_item *addr = NULL;
+address_item adb, * addr = NULL;
 int rc;
 
 *user_msgptr = *log_msgptr = NULL;
@@ -4933,6 +4849,9 @@ if (where == ACL_WHERE_RCPT)
   addr->lc_local_part = deliver_localpart;
   }
 
+acl_text = s;
+acl_text_end = s + Ustrlen(s) + 1;
+
 acl_level++;
 rc = acl_check_internal(where, addr, s, user_msgptr, log_msgptr);
 acl_level--;
@@ -4966,8 +4885,7 @@ acl_check(int where, const uschar * recipient, uschar * s,
   uschar ** user_msgptr, uschar ** log_msgptr)
 {
 int rc;
-address_item adb;
-address_item *addr = NULL;
+address_item adb,  * addr = NULL;
 
 *user_msgptr = *log_msgptr = NULL;
 sender_verified_failed = NULL;
@@ -5038,12 +4956,12 @@ switch (where)
     else if (cutthrough.delivery)
       if (rc != OK)
 	{
-	HDEBUG(D_acl) debug_printf_indent(
+	HDEBUG(acl) debug_printf_indent(
 			"ignore cutthrough request; ACL did not accept\n");
 	}
       else if (rcpt_count <= cutthrough.nrcpt)
 	{
-	HDEBUG(D_acl) debug_printf_indent(
+	HDEBUG(acl) debug_printf_indent(
 			"ignore cutthrough request; nonfirst message\n");
 	}
       else if (  (rc = open_cutthrough_connection(addr, cutthrough.tpt_sender))
@@ -5059,7 +4977,7 @@ switch (where)
 	  }
 	else
 	  {
-	  HDEBUG(D_acl) debug_printf_indent("cutthrough defer; will spool\n");
+	  HDEBUG(acl) debug_printf_indent("cutthrough defer; will spool\n");
 	  rc = OK;
 	  }
 
@@ -5099,7 +5017,7 @@ if (rc == DISCARD)
   {
   if (where > ACL_WHERE_NOTSMTP || where == ACL_WHERE_PREDATA)
     {
-    log_write(0, LOG_MAIN|LOG_PANIC, "\"discard\" verb not allowed in %s "
+    log_write(LOG_MAIN|LOG_PANIC, "\"discard\" verb not allowed in %s "
       "ACL", acl_wherenames[where]);
     return ERROR;
     }
@@ -5110,7 +5028,7 @@ if (rc == DISCARD)
 
 if (rc == FAIL_DROP && where == ACL_WHERE_MAILAUTH)
   {
-  log_write(0, LOG_MAIN|LOG_PANIC, "\"drop\" verb not allowed in %s "
+  log_write(LOG_MAIN|LOG_PANIC, "\"drop\" verb not allowed in %s "
     "ACL", acl_wherenames[where]);
   return ERROR;
   }
diff --git a/src/src/atrn.c b/src/src/atrn.c
index ab56189f4..9d12d672d 100644
--- a/src/src/atrn.c
+++ b/src/src/atrn.c
@@ -31,7 +31,7 @@ if (acl_smtp_atrn && !atrn_mode
    && (exp_acl = expand_string(acl_smtp_atrn)) && !*exp_acl)
   exp_acl = NULL;
 if (!exp_acl || !authenticated_id || sender_address)
-  return synprot_error(L_smtp_protocol_error,
+  return synprot_error(TRUE,
     !exp_acl ? 502 : !authenticated_id ? 530 : 503,
     NULL,
     !exp_acl ?		US"ATRN command used when not advertised"
@@ -39,8 +39,9 @@ if (!exp_acl || !authenticated_id || sender_address)
     :			US"ATRN is not permitted inside a transaction"
     );
 
-log_write(L_etrn, LOG_MAIN, "ATRN '%s' received from %s",
-  smtp_cmd_argument, host_and_ident(FALSE));
+if (LOGGING(etrn))
+  log_write(LOG_MAIN, "ATRN '%s' received from %s",
+    smtp_cmd_argument, host_and_ident(FALSE));
 
 if ((rc = acl_check(ACL_WHERE_ATRN, NULL, exp_acl, user_msgp, log_msgp)) != OK)
   return smtp_handle_acl_fail(ACL_WHERE_ATRN, rc, *user_msgp, *log_msgp);
diff --git a/src/src/auths/Makefile b/src/src/auths/Makefile
index b929f6742..44ca8d8fb 100644
--- a/src/src/auths/Makefile
+++ b/src/src/auths/Makefile
@@ -17,6 +17,7 @@
 OBJ += auth-spa.o
 
 all:		auths.a $(MODS)
+		$(FE):
 
 auths.a:	$(OBJ)
 		@$(RM_COMMAND) -f auths.a
@@ -30,8 +31,8 @@ auths.a:	$(OBJ)
 
 SO_FLAGS = -DDYNLOOKUP $(CFLAGS_DYNAMIC) $(CFLAGS) $(INCLUDE) $(DLFLAGS)
 .c.so:;		@echo "$(CC) -shared $*.c"
-		$(FE)$(CC) $(SO_FLAGS) $(AUTH_$*_INCLUDE) $(AUTH_$*_LIBS) \
-			$*.c -o $@
+		$(FE)$(CC) $(SO_FLAGS) $(AUTH_$*_INCLUDE) $*.c $(AUTH_$*_LIBS) \
+			-o $@
 
 
 $(OBJ) $(MOD): $(HDRS)
diff --git a/src/src/auths/auth-spa.c b/src/src/auths/auth-spa.c
index 5b63b0893..70c20ee3d 100644
--- a/src/src/auths/auth-spa.c
+++ b/src/src/auths/auth-spa.c
@@ -1332,7 +1332,7 @@ spa_build_auth_challenge(SPAAuthRequest * request, SPAAuthChallenge * challenge)
 {
 char chalstr[8];
 int i;
-int p = (int)getpid();
+pid_t p = getpid();
 int random_seed = (int)time(NULL) ^ ((p << 16) | p);
 
 /* Ensure challenge data is cleared, in case it isn't all used. This
diff --git a/src/src/auths/call_pwcheck.c b/src/src/auths/call_saslauthd.c
similarity index 53%
rename from src/src/auths/call_pwcheck.c
rename to src/src/auths/call_saslauthd.c
index 9e0d5d41f..eb6604e2a 100644
--- a/src/src/auths/call_pwcheck.c
+++ b/src/src/auths/call_saslauthd.c
@@ -17,58 +17,7 @@ file. This is now deprecated in favour of "saslauthd". */
 
 
 /*************************************************
-*      External entry point for pwcheck          *
-*************************************************/
-
-/* This function calls the now-deprecated "pwcheck" Cyrus-SASL authentication
-daemon, passing over a colon-separated user name and password. As this is
-called from the string expander, the string will always be in dynamic store and
-can be overwritten.
-
-Arguments:
-  s        a colon-separated username:password string
-  errptr   where to point an error message
-
-Returns:   OK if authentication succeeded
-           FAIL if authentication failed
-           ERROR some other error condition
-*/
-
-int
-auth_call_pwcheck(uschar *s, uschar **errptr)
-{
-uschar * reply = NULL, * pw = Ustrrchr(s, ':');
-
-if (!pw)
-  {
-  *errptr = US"pwcheck: malformed input - missing colon";
-  return ERROR;
-  }
-
-*pw++ = 0;   /* Separate user and password */
-
-DEBUG(D_auth) debug_printf("Running pwcheck authentication for user %q\n", s);
-
-switch (pwcheck_verify_password(CS s, CS pw, CCSS &reply))
-  {
-  case PWCHECK_OK:
-  DEBUG(D_auth) debug_printf("pwcheck: success (%s)\n", reply);
-  return OK;
-
-  case PWCHECK_NO:
-  DEBUG(D_auth) debug_printf("pwcheck: access denied (%s)\n", reply);
-  return FAIL;
-
-  default:
-  DEBUG(D_auth) debug_printf("pwcheck: query failed (%s)\n", reply);
-  *errptr = reply;
-  return ERROR;
-  }
-}
-
-
-/*************************************************
-*       External entry point for pwauthd         *
+*       External entry point for saslauthd       *
 *************************************************/
 
 /* This function calls the "saslauthd" Cyrus-SASL authentication daemon,
@@ -96,22 +45,22 @@ uschar *reply = NULL;
 if (service == NULL) service = US"";
 if (realm == NULL) realm = US"";
 
-DEBUG(D_auth)
+DEBUG(auth)
   debug_printf("Running saslauthd authentication for user %q \n", username);
 
 switch (saslauthd_verify_password(username, password, service,
         realm, (const uschar **)(&reply)))
   {
   case PWCHECK_OK:
-  DEBUG(D_auth) debug_printf("saslauthd: success (%s)\n", reply);
+  DEBUG(auth) debug_printf("saslauthd: success (%s)\n", reply);
   return OK;
 
   case PWCHECK_NO:
-  DEBUG(D_auth) debug_printf("saslauthd: access denied (%s)\n", reply);
+  DEBUG(auth) debug_printf("saslauthd: access denied (%s)\n", reply);
   return FAIL;
 
   default:
-  DEBUG(D_auth) debug_printf("saslauthd: query failed (%s)\n", reply);
+  DEBUG(auth) debug_printf("saslauthd: query failed (%s)\n", reply);
   *errptr = reply;
   return ERROR;
   }
diff --git a/src/src/auths/check_serv_cond.c b/src/src/auths/check_serv_cond.c
index 739efff43..bcada237f 100644
--- a/src/src/auths/check_serv_cond.c
+++ b/src/src/auths/check_serv_cond.c
@@ -64,7 +64,7 @@ auth_check_some_cond(auth_instance * ablock,
 {
 uschar * cond;
 
-HDEBUG(D_auth)
+HDEBUG(auth)
   {
   debug_printf("%s authenticator %s:\n", ablock->drinst.name, label);
   for (int i = 0; i < AUTH_VARS; i++) if (auth_vars[i])
@@ -85,7 +85,7 @@ server_condition will be OK and otherwise will typically be FAIL. */
 if (!condition) return unset;
 cond = expand_string(condition);
 
-HDEBUG(D_auth)
+HDEBUG(auth)
   if (!cond)
     debug_printf("expansion failed: %s\n", expand_string_message);
   else
diff --git a/src/src/auths/cram_md5.c b/src/src/auths/cram_md5.c
index df861f6c4..56a40b9b8 100644
--- a/src/src/auths/cram_md5.c
+++ b/src/src/auths/cram_md5.c
@@ -173,8 +173,8 @@ int
 auth_cram_md5_server(auth_instance * ablock, uschar * data)
 {
 auth_cram_md5_options_block * ob = ablock->drinst.options_block;
-uschar * challenge = string_sprintf("<%d.%ld@%s>", getpid(),
-    (long int) time(NULL), primary_hostname);
+uschar * challenge = string_sprintf("<" PID_T_FMT ".%ld@%s>",
+    getpid(), (long int) time(NULL), primary_hostname);
 uschar * clear, * secret;
 uschar digest[16];
 int i, rc, len;
@@ -228,7 +228,7 @@ if (secret == NULL)
 
 compute_cram_md5(secret, challenge, digest);
 
-HDEBUG(D_auth)
+HDEBUG(auth)
   {
   debug_printf("CRAM-MD5: user name = %s\n", auth_vars[0]);
   debug_printf("          challenge = %s\n", challenge);
@@ -272,7 +272,6 @@ auth_cram_md5_options_block * ob = ablock->drinst.options_block;
 uschar *secret = expand_string(ob->client_secret);
 uschar *name = expand_string(ob->client_name);
 uschar *challenge, *p;
-int i;
 uschar digest[16];
 
 /* If expansion of either the secret or the user name failed, return CANCELLED
@@ -343,7 +342,7 @@ auth_info cram_md5_auth_info = {
   .options_block =	&auth_cram_md5_option_defaults,
   .options_len =	sizeof(auth_cram_md5_options_block),
   .init =		auth_cram_md5_init,
-# ifdef DYNLOOKUP
+# if AUTH_CRAM_MD5==2
   .dyn_magic =		AUTH_MAGIC,
 # endif
   },
diff --git a/src/src/auths/cyrus_sasl.c b/src/src/auths/cyrus_sasl.c
index 8d39945bc..3e950b7b7 100644
--- a/src/src/auths/cyrus_sasl.c
+++ b/src/src/auths/cyrus_sasl.c
@@ -127,14 +127,14 @@ sasl_callback_t cbs[] = {
 if (!ob->server_mech) ob->server_mech = string_copy(ablock->public_name);
 
 if (!(expanded_hostname = expand_string(ob->server_hostname)))
-  log_write_die(0, LOG_CONFIG_FOR, "%s authenticator:  "
+  log_write_die(LOG_CONFIG_FOR, "%s authenticator:  "
       "couldn't expand server_hostname [%s]: %s",
       a->name, ob->server_hostname, expand_string_message);
 
 realm_expanded = NULL;
 if (  ob->server_realm
    && !(realm_expanded = CS expand_string(ob->server_realm)))
-  log_write_die(0, LOG_CONFIG_FOR, "%s authenticator:  "
+  log_write_die(LOG_CONFIG_FOR, "%s authenticator:  "
       "couldn't expand server_realm [%s]: %s",
       a->name, ob->server_realm, expand_string_message);
 
@@ -145,22 +145,22 @@ cbs[0].proc = (int(*)(void)) &mysasl_config;
 cbs[0].context = ob->server_mech;
 
 if (sasl_server_init(cbs, "exim") != SASL_OK)
-  log_write_die(0, LOG_CONFIG_FOR, "%s authenticator:  "
+  log_write_die(LOG_CONFIG_FOR, "%s authenticator:  "
       "couldn't initialise Cyrus SASL library.", a->name);
 
 if (sasl_server_new(CS ob->server_service, CS expanded_hostname,
                    realm_expanded, NULL, NULL, NULL, 0, &conn) != SASL_OK)
-  log_write_die(0, LOG_CONFIG_FOR, "%s authenticator:  "
+  log_write_die(LOG_CONFIG_FOR, "%s authenticator:  "
       "couldn't initialise Cyrus SASL server connection.", a->name);
 
 if (sasl_listmech(conn, NULL, "", ":", "", CCSS &list, &len, NULL) != SASL_OK)
-  log_write_die(0, LOG_CONFIG_FOR, "%s authenticator:  "
+  log_write_die(LOG_CONFIG_FOR, "%s authenticator:  "
       "couldn't get Cyrus SASL mechanism list.", a->name);
 
 sep = ':';
 listptr = list;
 
-HDEBUG(D_auth)
+HDEBUG(auth)
   {
   debug_printf("Initialised Cyrus SASL service=%q fqdn=%q realm=%q\n",
       ob->server_service, expanded_hostname, realm_expanded);
@@ -180,12 +180,12 @@ while (  (buffer = string_nextinlist(&listptr, &sep, NULL, 0))
       && strcmpic(buffer,ob->server_mech) );
 
 if (!buffer)
-  log_write_die(0, LOG_CONFIG_FOR, "%s authenticator:  "
+  log_write_die(LOG_CONFIG_FOR, "%s authenticator:  "
       "Cyrus SASL doesn't know about mechanism %s.", a->name, ob->server_mech);
 
 store_reset(rs_point);
 
-HDEBUG(D_auth) debug_printf("Cyrus SASL driver %s: %s initialised\n", a->name, ablock->public_name);
+HDEBUG(auth) debug_printf("Cyrus SASL driver %s: %s initialised\n", a->name, ablock->public_name);
 
 /* make sure that if we get here then we're allowed to advertise. */
 ablock->server = TRUE;
@@ -219,7 +219,7 @@ unsigned int inlen, outlen;
 input = data;
 inlen = Ustrlen(data);
 
-HDEBUG(D_auth) debug = string_copy(data);
+HDEBUG(auth) debug = string_copy(data);
 
 hname = expand_string(ob->server_hostname);
 if (hname && ob->server_realm)
@@ -247,7 +247,7 @@ if ((rc = sasl_server_init(cbs, "exim")) != SASL_OK)
 rc = sasl_server_new(CS ob->server_service, CS hname, realm_expanded, NULL,
   NULL, NULL, 0, &conn);
 
-HDEBUG(D_auth)
+HDEBUG(auth)
   debug_printf("Initialised Cyrus SASL server connection; service=%q fqdn=%q realm=%q\n",
       ob->server_service, hname, realm_expanded);
 
@@ -262,20 +262,20 @@ if (tls_in.cipher)
   {
   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",
+    HDEBUG(auth) debug_printf("Cyrus SASL EXTERNAL SSF set %d failed: %s\n",
         tls_in.bits, sasl_errstring(rc, NULL, NULL));
     auth_defer_msg = US"couldn't set Cyrus SASL EXTERNAL SSF";
     sasl_done();
     return DEFER;
     }
   else
-    HDEBUG(D_auth) debug_printf("Cyrus SASL set EXTERNAL SSF to %d\n", tls_in.bits);
+    HDEBUG(auth) debug_printf("Cyrus SASL set EXTERNAL SSF to %d\n", tls_in.bits);
 
   /*XXX Set channel-binding here with sasl_channel_binding_t / SASL_CHANNEL_BINDING
   Unclear what the "name" element does though, ditto the "critical" flag. */
   }
 else
-  HDEBUG(D_auth) debug_printf("Cyrus SASL: no TLS, no EXTERNAL SSF set\n");
+  HDEBUG(auth) debug_printf("Cyrus SASL: no TLS, no EXTERNAL SSF set\n");
 
 /* So sasl_setprop() documents non-shorted IPv6 addresses which is incredibly
 annoying; looking at cyrus-imapd-2.3.x source, the IP address is constructed
@@ -308,7 +308,7 @@ for (int i = 0; i < 2; ++i)
 
   if ((rc = sasl_setprop(conn, propnum, address_port)) != SASL_OK)
     {
-    HDEBUG(D_auth)
+    HDEBUG(auth)
       {
       const char * s_err = sasl_errdetail(conn);
       debug_printf("Failed to set %s SASL property: [%d] %s\n",
@@ -316,7 +316,7 @@ for (int i = 0; i < 2; ++i)
       }
     break;
     }
-  HDEBUG(D_auth) debug_printf("Cyrus SASL set %s hostport to: %s\n",
+  HDEBUG(auth) debug_printf("Cyrus SASL set %s hostport to: %s\n",
       label, address_port);
   }
 
@@ -325,7 +325,7 @@ for (rc = SASL_CONTINUE; rc == SASL_CONTINUE; )
   if (firsttime)
     {
     firsttime = 0;
-    HDEBUG(D_auth) debug_printf("Calling sasl_server_start(%s,%q)\n", ob->server_mech, debug);
+    HDEBUG(auth) debug_printf("Calling sasl_server_start(%s,%q)\n", ob->server_mech, debug);
     rc = sasl_server_start(conn, CS ob->server_mech, inlen ? CS input : NULL, inlen,
            CCSS &output, &outlen);
     }
@@ -344,7 +344,7 @@ for (rc = SASL_CONTINUE; rc == SASL_CONTINUE; )
       }
     inlen = Ustrlen(input);
 
-    HDEBUG(D_auth) debug = string_copy_taint(input, GET_TAINTED);
+    HDEBUG(auth) debug = string_copy_taint(input, GET_TAINTED);
     if (inlen)
       {
       if ((clen = b64decode(input, &clear, GET_TAINTED)) < 0)
@@ -357,7 +357,7 @@ for (rc = SASL_CONTINUE; rc == SASL_CONTINUE; )
       inlen = clen;
       }
 
-    HDEBUG(D_auth) debug_printf("Calling sasl_server_step(%q)\n", debug);
+    HDEBUG(auth) debug_printf("Calling sasl_server_step(%q)\n", debug);
     rc = sasl_server_step(conn, CS input, inlen, CCSS &output, &outlen);
     }
 
@@ -375,10 +375,10 @@ for (rc = SASL_CONTINUE; rc == SASL_CONTINUE; )
 
   if ((sasl_getprop(conn, SASL_USERNAME, (const void **)&out2)) != SASL_OK)
     {
-    HDEBUG(D_auth)
+    HDEBUG(auth)
       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): "
+    log_write(LOG_REJECT, "%s authenticator (%s): "
        "Cyrus SASL username fetch problem: %s", auname, ob->server_mech,
        sasl_errstring(rc, NULL, NULL));
     sasl_dispose(&conn);
@@ -395,9 +395,9 @@ for (rc = SASL_CONTINUE; rc == SASL_CONTINUE; )
     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(auth)
 	debug_printf("Cyrus SASL permanent failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
-      log_write(0, LOG_REJECT, "%s authenticator (%s): "
+      log_write(LOG_REJECT, "%s authenticator (%s): "
 	 "Cyrus SASL permanent failure: %s", auname, ob->server_mech,
 	 sasl_errstring(rc, NULL, NULL));
       sasl_dispose(&conn);
@@ -409,7 +409,7 @@ for (rc = SASL_CONTINUE; rc == SASL_CONTINUE; )
       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(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);
@@ -418,16 +418,16 @@ for (rc = SASL_CONTINUE; rc == SASL_CONTINUE; )
       return DEFER;
 
     case SASL_OK:
-      HDEBUG(D_auth)
+      HDEBUG(auth)
 	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)
+	HDEBUG(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): "
+	log_write(LOG_REJECT, "%s authenticator (%s): "
 	    "Cyrus SASL SSF value not available: %s", auname, ob->server_mech,
 	    sasl_errstring(rc, NULL, NULL));
 	sasl_dispose(&conn);
@@ -435,13 +435,13 @@ for (rc = SASL_CONTINUE; rc == SASL_CONTINUE; )
 	return FAIL;
 	}
       negotiated_ssf = *negotiated_ssf_ptr;
-      HDEBUG(D_auth)
+      HDEBUG(auth)
 	debug_printf("Cyrus SASL %s negotiated SSF: %d\n", ob->server_mech, negotiated_ssf);
       if (negotiated_ssf > 0)
 	{
-	HDEBUG(D_auth)
+	HDEBUG(auth)
 	  debug_printf("Exim does not implement SASL wrapping (needed for SSF %d).\n", negotiated_ssf);
-	log_write(0, LOG_REJECT, "%s authenticator (%s): "
+	log_write(LOG_REJECT, "%s authenticator (%s): "
 	    "Cyrus SASL SSF %d not supported by Exim", auname, ob->server_mech, negotiated_ssf);
 	sasl_dispose(&conn);
 	sasl_done();
@@ -459,7 +459,7 @@ for (rc = SASL_CONTINUE; rc == SASL_CONTINUE; )
       /* Anything else is a temporary failure, and we'll let SASL print out
        * the error string for us
        */
-      HDEBUG(D_auth)
+      HDEBUG(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));
@@ -521,7 +521,7 @@ auth_info cyrus_sasl_auth_info = {
   .options_block =	&auth_cyrus_sasl_option_defaults,
   .options_len =	sizeof(auth_cyrus_sasl_options_block),
   .init =		auth_cyrus_sasl_init,
-# ifdef DYNLOOKUP
+# if AUTH_CYRUS_SASL==2
   .dyn_magic =		AUTH_MAGIC,
 # endif
   },
diff --git a/src/src/auths/dovecot.c b/src/src/auths/dovecot.c
index 5319878e2..9b2819293 100644
--- a/src/src/auths/dovecot.c
+++ b/src/src/auths/dovecot.c
@@ -110,7 +110,7 @@ if (!ablock->public_name)
   ablock->public_name = a->name;
 if (ob->server_socket)
   ablock->server = TRUE;
-else DEBUG(D_auth)
+else DEBUG(auth)
   debug_printf("Dovecot auth driver: no server_socket for %s\n",
 	      ablock->public_name);
 ablock->client = FALSE;
@@ -159,7 +159,7 @@ if (n <= nptrs)
   *ptrs = last_sub_start;
 else
   {
-  HDEBUG(D_auth)
+  HDEBUG(auth)
     debug_printf("dovecot: warning: too many results from tab-splitting;"
 		  " saw %d fields, room for %d\n", n, nptrs);
   n = nptrs;
@@ -251,7 +251,7 @@ dc_write(client_conn_ctx * cctx, const uschar * s)
 {
 int len = Ustrlen(s), res;
 
-HDEBUG(D_auth) debug_printf("  DOVECOT>> '%s'\n", s);
+HDEBUG(auth) debug_printf("  DOVECOT>> '%s'\n", s);
 res =
 #ifndef DISABLE_TLS
   cctx->tls_ctx ? tls_write(cctx->tls_ctx, s, len, FALSE) :
@@ -279,7 +279,7 @@ host_item host;
 client_conn_ctx cctx = {.sock = -1, .tls_ctx = NULL};
 BOOL found = FALSE, have_mech_line = FALSE;
 
-HDEBUG(D_auth) debug_printf("dovecot authentication\n");
+HDEBUG(auth) debug_printf("dovecot authentication\n");
 
 if (!data)
   {
@@ -298,7 +298,10 @@ if (ob->server_tls)
   {
   union sockaddr_46 interface_sock;
   EXIM_SOCKLEN_T size = sizeof(interface_sock);
+
+  /* Large (64k if DANE supported) */
   smtp_connect_args conn_args = { .host = &host };
+
   tls_support tls_dummy = { .sni = NULL };
   uschar * errstr;
 
@@ -333,11 +336,11 @@ for (;;)
     OUT("authentication socket protocol line too long");
 
   *p = '\0';
-  HDEBUG(D_auth) debug_printf("  DOVECOT<< '%s'\n", buffer);
+  HDEBUG(auth) debug_printf("  DOVECOT<< '%s'\n", buffer);
 
   nargs = strcut(buffer, args, nelem(args));
 
-  HDEBUG(D_auth) debug_strcut(args, nargs, nelem(args));
+  HDEBUG(auth) debug_strcut(args, nargs, nelem(args));
 
   /* 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
@@ -361,7 +364,7 @@ for (;;)
 	   VERSION_MAJOR, VERSION_MINOR);
     
     if (dc_write(&cctx, version_command) < 0)
-      HDEBUG(D_auth) debug_printf("error sending version_command: %s\n",
+      HDEBUG(auth) debug_printf("error sending version_command: %s\n",
 	strerror(errno));
     }
   else if (Ustrcmp(args[0], US"MECH") == 0)
@@ -438,14 +441,14 @@ Subsequently, the command was modified to add "secured" and "valid-client-
 cert" when relevant.
 ****************************************************************************/
 
-auth_command = string_sprintf("CPID\t%d\n"
+auth_command = string_sprintf("CPID\t" PID_T_FMT "\n"
        "AUTH\t%d\t%s\tservice=smtp\t%srip=%s\tlip=%s\tnologin\tresp=%s\n",
        getpid(), crequid,
        ablock->public_name, auth_extra_data, sender_host_address,
        interface_address, data);
 
 if (dc_write(&cctx, auth_command) < 0)
-  HDEBUG(D_auth) debug_printf("error sending auth_command: %s\n",
+  HDEBUG(auth) debug_printf("error sending auth_command: %s\n",
     strerror(errno));
 
 while (1)
@@ -460,9 +463,9 @@ while (1)
     }
 
   buffer[Ustrlen(buffer) - 1] = 0;
-  HDEBUG(D_auth) debug_printf("  DOVECOT<< '%s'\n", buffer);
+  HDEBUG(auth) debug_printf("  DOVECOT<< '%s'\n", buffer);
   nargs = strcut(buffer, args, nelem(args));
-  HDEBUG(D_auth) debug_strcut(args, nargs, nelem(args));
+  HDEBUG(auth) debug_strcut(args, nargs, nelem(args));
 
   if (Uatoi(args[1]) != crequid)
     OUT("authentication socket connection id mismatch");
@@ -546,7 +549,7 @@ if (cctx.sock >= 0)
 /* Expand server_condition as an authorization check */
 if (ret == OK) ret = auth_check_serv_cond(ablock);
 
-HDEBUG(D_auth) debug_printf("dovecot auth ret: %s\n", rc_names[ret]);
+HDEBUG(auth) debug_printf("dovecot auth ret: %s\n", rc_names[ret]);
 return ret;
 }
 
@@ -564,7 +567,7 @@ auth_info dovecot_auth_info = {
   .options_block =	&auth_dovecot_option_defaults,
   .options_len =	sizeof(auth_dovecot_options_block),
   .init =		auth_dovecot_init,
-# ifdef DYNLOOKUP
+# if AUTH_DOVECOT==2
   .dyn_magic =		AUTH_MAGIC,
 # endif
   },
diff --git a/src/src/auths/external.c b/src/src/auths/external.c
index 0cdfcdcfe..641c6fa89 100644
--- a/src/src/auths/external.c
+++ b/src/src/auths/external.c
@@ -112,12 +112,24 @@ if (expand_nmax == 0) 	/* skip if rxd data */
 if (ob->server_param2)
   {
   uschar * s = expand_string(ob->server_param2);
+  if (!s)
+    {
+    log_write(LOG_MAIN|LOG_PANIC, "failed to expand auth param2 %q: %s",
+          ob->server_param2, expand_string_message);
+    return DEFER;
+    }
   auth_vars[expand_nmax = 1] = s;
   expand_nstring[++expand_nmax] = s;
   expand_nlength[expand_nmax] = Ustrlen(s);
+
   if (ob->server_param3)
     {
-    s = expand_string(ob->server_param3);
+    if (!(s = expand_string(ob->server_param3)))
+      {
+      log_write(LOG_MAIN|LOG_PANIC, "failed to expand auth param3 %q: %s",
+	    ob->server_param3, expand_string_message);
+      return DEFER;
+      }
     auth_vars[expand_nmax] = s;
     expand_nstring[++expand_nmax] = s;
     expand_nlength[expand_nmax] = Ustrlen(s);
@@ -125,6 +137,8 @@ if (ob->server_param2)
   }
 
 return auth_check_serv_cond(ablock);
+
+expfail:
 }
 
 
@@ -172,7 +186,7 @@ auth_info external_auth_info = {
   .options_block =	&auth_external_option_defaults,
   .options_len =	sizeof(auth_external_options_block),
   .init =		auth_external_init,
-# ifdef DYNLOOKUP
+# if AUTH_EXTERNAL==2
   .dyn_magic =		AUTH_MAGIC,
 # endif
   },
diff --git a/src/src/auths/get_data.c b/src/src/auths/get_data.c
index 20f36bb82..515fcde0b 100644
--- a/src/src/auths/get_data.c
+++ b/src/src/auths/get_data.c
@@ -34,17 +34,17 @@ else
   int len;
 
   if ((len = b64decode(data, &clear, GET_TAINTED)) < 0) return BAD64;
-  DEBUG(D_auth) debug_printf("auth input decode:");
+  DEBUG(auth) debug_printf("auth input decode:");
   for (const uschar * end = clear + len;
       clear < end && expand_nmax < EXPAND_MAXN; )
     {
-    DEBUG(D_auth) debug_printf(" '%s'", clear);
+    DEBUG(auth) debug_printf(" '%s'", clear);
     if (expand_nmax < AUTH_VARS) auth_vars[expand_nmax] = clear;
     expand_nstring[++expand_nmax] = clear;
     while (*clear) clear++;
     expand_nlength[expand_nmax] = clear++ - expand_nstring[expand_nmax];
     }
-  DEBUG(D_auth) debug_printf("\n");
+  DEBUG(auth) debug_printf("\n");
   }
 return OK;
 }
@@ -86,7 +86,7 @@ while ((c = receive_getc(GETC_BUFFER_UNLIMITED)) != '\n' && c != EOF)
   }
 if (p > 0 && big_buffer[p-1] == '\r') p--;
 big_buffer[p] = 0;
-DEBUG(D_receive) debug_printf("SMTP<< %s\n", big_buffer);
+DEBUG(receive) debug_printf("SMTP<< %s\n", big_buffer);
 if (Ustrcmp(big_buffer, "*") == 0) return CANCELLED;
 *aptr = big_buffer;
 return OK;
@@ -249,7 +249,7 @@ if (clear_len < 0)
       "response %q", save_bad);
     return CANCELLED;
     }
-  DEBUG(D_auth) debug_printf("bad b64 decode for '%s';"
+  DEBUG(auth) debug_printf("bad b64 decode for '%s';"
        " ignoring due to client_ignore_invalid_base64\n", save_bad);
   clear = string_copy(US"");
   }
diff --git a/src/src/auths/gsasl.c b/src/src/auths/gsasl.c
index 9c315fc92..99d0d8890 100644
--- a/src/src/auths/gsasl.c
+++ b/src/src/auths/gsasl.c
@@ -193,7 +193,7 @@ initialise the once. */
 if (!gsasl_ctx)
   {
   if ((rc = gsasl_init(&gsasl_ctx)) != GSASL_OK)
-    log_write_die(0, LOG_CONFIG_FOR, "%s authenticator:  "
+    log_write_die(LOG_CONFIG_FOR, "%s authenticator:  "
 	      "couldn't initialise GNU SASL library: %s (%s)",
 	      a->name, gsasl_strerror_name(rc), gsasl_strerror(rc));
 
@@ -202,10 +202,10 @@ if (!gsasl_ctx)
 
 /* We don't need this except to log it for debugging. */
 
-HDEBUG(D_auth) if (!once)
+HDEBUG(auth) if (!once)
   {
   if ((rc = gsasl_server_mechlist(gsasl_ctx, &once)) != GSASL_OK)
-    log_write_die(0, LOG_CONFIG_FOR, "%s authenticator:  "
+    log_write_die(LOG_CONFIG_FOR, "%s authenticator:  "
 	      "failed to retrieve list of mechanisms: %s (%s)",
 	      a->name,  gsasl_strerror_name(rc), gsasl_strerror(rc));
 
@@ -213,7 +213,7 @@ HDEBUG(D_auth) if (!once)
   }
 
 if (!gsasl_client_support_p(gsasl_ctx, CCS ob->server_mech))
-  log_write_die(0, LOG_CONFIG_FOR, "%s authenticator:  "
+  log_write_die(LOG_CONFIG_FOR, "%s authenticator:  "
 	    "GNU SASL does not support mechanism %q",
 	    a->name, ob->server_mech);
 
@@ -233,7 +233,7 @@ else if(  ob->server_mech
   */
 
   ablock->server = FALSE;
-  HDEBUG(D_auth) debug_printf("%s authenticator:  "
+  HDEBUG(auth) debug_printf("%s authenticator:  "
 	    "Need server_condition for %s mechanism\n",
 	    a->name, ob->server_mech);
   }
@@ -245,7 +245,7 @@ if (  !ob->server_realm
    && STREQIC(ob->server_mech, US"DIGEST-MD5"))
   {
   ablock->server = FALSE;
-  HDEBUG(D_auth) debug_printf("%s authenticator:  "
+  HDEBUG(auth) debug_printf("%s authenticator:  "
 	    "Need server_realm for %s mechanism\n",
 	    a->name, ob->server_mech);
   }
@@ -266,19 +266,19 @@ struct callback_exim_state *cb_state =
 
 if (!cb_state)
   {
-  HDEBUG(D_auth) debug_printf("gsasl callback (%d) not from our server/client processing\n", prop);
+  HDEBUG(auth) debug_printf("gsasl callback (%d) not from our server/client processing\n", prop);
 #ifdef CHANNELBIND_HACK
   if (prop == GSASL_CB_TLS_UNIQUE)
     {
     uschar * s;
     if ((s = gsasl_callback_hook_get(ctx)))	/* Gross hack for early lib vers */
       {
-      HDEBUG(D_auth) debug_printf("GSASL_CB_TLS_UNIQUE from ctx hook\n");
+      HDEBUG(auth) debug_printf("GSASL_CB_TLS_UNIQUE from ctx hook\n");
       gsasl_property_set(sctx, GSASL_CB_TLS_UNIQUE, CS s);
       }
     else
       {
-      HDEBUG(D_auth) debug_printf("GSASL_CB_TLS_UNIQUE!  dummy for now\n");
+      HDEBUG(auth) debug_printf("GSASL_CB_TLS_UNIQUE!  dummy for now\n");
       gsasl_property_set(sctx, GSASL_CB_TLS_UNIQUE, "");
       }
     return GSASL_OK;
@@ -287,7 +287,7 @@ if (!cb_state)
   return GSASL_NO_CALLBACK;
   }
 
-HDEBUG(D_auth)
+HDEBUG(auth)
   debug_printf("GNU SASL Callback entered, prop=%d (loop prop=%d)\n",
       prop, callback_loop);
 
@@ -296,7 +296,7 @@ if (callback_loop > 0)
   /* Most likely is that we were asked for property FOO, and to
   expand the string we asked for property BAR to put into an auth
   variable, but property BAR is not supplied for this mechanism. */
-  HDEBUG(D_auth)
+  HDEBUG(auth)
     debug_printf("Loop, asked for property %d while handling property %d\n",
 	prop, callback_loop);
   return GSASL_NO_CALLBACK;
@@ -308,7 +308,7 @@ if (cb_state->currently == CURRENTLY_CLIENT)
 else if (cb_state->currently == CURRENTLY_SERVER)
   rc = server_callback(ctx, sctx, prop, cb_state->ablock);
 else
-  log_write_die(0, LOG_CONFIG_FOR, "%s authenticator:  "
+  log_write_die(LOG_CONFIG_FOR, "%s authenticator:  "
       "unhandled callback state, bug in Exim", cb_state->ablock->drinst.name);
   /* NOTREACHED */
 
@@ -370,7 +370,7 @@ return CUS string_sprintf("(unknown prop: %d)", (int)prop);
 static void
 preload_prop(Gsasl_session * sctx, Gsasl_property propcode, const uschar * val)
 {
-DEBUG(D_auth) debug_printf("preloading prop %s val %s\n",
+DEBUG(auth) debug_printf("preloading prop %s val %s\n",
   gsasl_prop_code_to_name(propcode), val);
 gsasl_property_set(sctx, propcode, CCS val);
 }
@@ -392,7 +392,7 @@ Gsasl_session * sctx = NULL;
 struct callback_exim_state cb_state;
 int rc, auth_result, exim_error, exim_error_override;
 
-HDEBUG(D_auth)
+HDEBUG(auth)
   debug_printf("GNU SASL: initialising session for %s, mechanism %s\n",
       auname, ob->server_mech);
 
@@ -402,7 +402,7 @@ if (tls_in.channelbinding && ob->server_channelbinding)
 # ifndef DISABLE_TLS_RESUME
   if (!tls_in.ext_master_secret && tls_in.resumption == RESUME_USED)
     {		/* per RFC 7677 section 4 */
-    HDEBUG(D_auth) debug_printf(
+    HDEBUG(auth) debug_printf(
       "channel binding not usable on resumed TLS without extended-master-secret");
     return FAIL;
     }
@@ -421,7 +421,7 @@ if ((rc = gsasl_server_start(gsasl_ctx, CCS ob->server_mech, &sctx)) != GSASL_OK
   {
   auth_defer_msg = string_sprintf("GNU SASL: session start failure: %s (%s)",
       gsasl_strerror_name(rc), gsasl_strerror(rc));
-  HDEBUG(D_auth) debug_printf("%s\n", auth_defer_msg);
+  HDEBUG(auth) debug_printf("%s\n", auth_defer_msg);
   return DEFER;
   }
 /* Hereafter: gsasl_finish(sctx) please */
@@ -446,7 +446,7 @@ preload_prop(sctx, GSASL_QOPS, US "qop-auth");
 #ifndef DISABLE_TLS
 if (tls_in.channelbinding)
   {
-  /* 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
   the same negotiated session; if someone is terminating one session and
@@ -472,8 +472,7 @@ if (tls_in.channelbinding)
   */
   if (ob->server_channelbinding)
     {
-    HDEBUG(D_auth) debug_printf("Auth %s: Enabling channel-binding\n",
-	auname);
+    HDEBUG(auth) debug_printf("Auth %s: Enabling channel-binding\n", auname);
 # ifndef CHANNELBIND_HACK
     preload_prop(sctx,
 #  ifdef EXIM_GSASL_HAVE_EXPORTER
@@ -484,12 +483,12 @@ if (tls_in.channelbinding)
 # endif
     }
   else
-    HDEBUG(D_auth)
+    HDEBUG(auth)
       debug_printf("Auth %s: Not enabling channel-binding (data available)\n",
 	  auname);
   }
 else
-  HDEBUG(D_auth)
+  HDEBUG(auth)
     debug_printf("Auth %s: no channel-binding data available\n",
 	auname);
 #endif
@@ -520,9 +519,9 @@ do {
     case GSASL_NO_PASSCODE:
     case GSASL_NO_PIN:
     case GSASL_BASE64_ERROR:
-      HDEBUG(D_auth) debug_printf("GNU SASL permanent error: %s (%s)\n",
+      HDEBUG(auth) debug_printf("GNU SASL permanent error: %s (%s)\n",
 	  gsasl_strerror_name(rc), gsasl_strerror(rc));
-      log_write(0, LOG_REJECT, "%s authenticator (%s):\n  "
+      log_write(LOG_REJECT, "%s authenticator (%s):\n  "
 	  "GNU SASL permanent failure: %s (%s)",
 	  auname, ob->server_mech,
 	  gsasl_strerror_name(rc), gsasl_strerror(rc));
@@ -533,7 +532,7 @@ do {
     default:
       auth_defer_msg = string_sprintf("GNU SASL temporary error: %s (%s)",
 	  gsasl_strerror_name(rc), gsasl_strerror(rc));
-      HDEBUG(D_auth) debug_printf("%s\n", auth_defer_msg);
+      HDEBUG(auth) debug_printf("%s\n", auth_defer_msg);
       exim_error_override = DEFER;
       goto STOP_INTERACTION;
     }
@@ -556,7 +555,7 @@ do {
 STOP_INTERACTION:
 auth_result = rc;
 
-HDEBUG(D_auth)
+HDEBUG(auth)
   {
   const uschar * s;
   if ((s = CUS gsasl_property_fast(sctx, GSASL_SCRAM_ITER)))
@@ -580,7 +579,7 @@ if (exim_error != OK)
 
 if (auth_result != GSASL_OK)
   {
-  HDEBUG(D_auth) debug_printf("authentication returned %s (%s)\n",
+  HDEBUG(auth) debug_printf("authentication returned %s (%s)\n",
       gsasl_strerror_name(auth_result), gsasl_strerror(auth_result));
   if (exim_error_override != OK)
     return exim_error_override; /* might be DEFER */
@@ -605,7 +604,7 @@ switch (exim_rc)
   case DEFER:	sasl_error_should_defer = TRUE;
 		return GSASL_AUTHENTICATION_ERROR;
   case FAIL:	return GSASL_AUTHENTICATION_ERROR;
-  default:	log_write_die(0, LOG_CONFIG_FOR, "%s authenticator:  "
+  default:	log_write_die(LOG_CONFIG_FOR, "%s authenticator:  "
 		  "Unhandled return from checking %s: %d",
 		  ablock->drinst.name, label, exim_rc);
   }
@@ -623,7 +622,7 @@ set_exim_authvar_from_prop(Gsasl_session * sctx, Gsasl_property prop)
 uschar * propval = US gsasl_property_fast(sctx, prop);
 int i = expand_nmax, j = i + 1;
 propval = propval ? string_copy(propval) : US"";
-HDEBUG(D_auth) debug_printf("auth[%d] <=  %s'%s'\n",
+HDEBUG(auth) debug_printf("auth[%d] <=  %s'%s'\n",
 			    j, gsasl_prop_code_to_name(prop), propval);
 expand_nstring[j] = propval;
 expand_nlength[j] = Ustrlen(propval);
@@ -653,17 +652,17 @@ static int
 prop_from_option(Gsasl_session * sctx, Gsasl_property prop,
   const uschar * option)
 {
-HDEBUG(D_auth) debug_printf(" %s\n", gsasl_prop_code_to_name(prop));
+HDEBUG(auth) debug_printf(" %s\n", gsasl_prop_code_to_name(prop));
 if (option)
   {
   set_exim_authvars_from_a_az_r_props(sctx);
   option = expand_string(option);
-  HDEBUG(D_auth) debug_printf("  '%s'\n", option);
+  HDEBUG(auth) debug_printf("  '%s'\n", option);
   if (*option)
     gsasl_property_set(sctx, prop, CCS option);
   return GSASL_OK;
   }
-HDEBUG(D_auth) debug_printf("  option not set\n");
+HDEBUG(auth) debug_printf("  option not set\n");
 return GSASL_NO_CALLBACK;
 }
 
@@ -676,7 +675,7 @@ char * tmps;
 uschar * s;
 int cbrc = GSASL_NO_CALLBACK;
 
-HDEBUG(D_auth) debug_printf("GNU SASL callback %s for %s/%s as server\n",
+HDEBUG(auth) debug_printf("GNU SASL callback %s for %s/%s as server\n",
       gsasl_prop_code_to_name(prop), ablock->drinst.name, ablock->public_name);
 
 for (int i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL;
@@ -697,7 +696,7 @@ switch (prop)
   case GSASL_VALIDATE_EXTERNAL:
     if (!ablock->server_condition)
       {
-      HDEBUG(D_auth) debug_printf("No server_condition supplied, to validate EXTERNAL\n");
+      HDEBUG(auth) debug_printf("No server_condition supplied, to validate EXTERNAL\n");
       cbrc = GSASL_AUTHENTICATION_ERROR;
       break;
       }
@@ -711,7 +710,7 @@ switch (prop)
   case GSASL_VALIDATE_ANONYMOUS:
     if (!ablock->server_condition)
       {
-      HDEBUG(D_auth) debug_printf("No server_condition supplied, to validate ANONYMOUS\n");
+      HDEBUG(auth) debug_printf("No server_condition supplied, to validate ANONYMOUS\n");
       cbrc = GSASL_AUTHENTICATION_ERROR;
       break;
       }
@@ -772,17 +771,17 @@ switch (prop)
 
     if (!(s = ob->server_password))
       {
-      HDEBUG(D_auth) debug_printf("option not set\n");
+      HDEBUG(auth) debug_printf("option not set\n");
       break;
       }
     if (!(tmps = CS expand_string(s)))
       {
       sasl_error_should_defer = !f.expand_string_forcedfail;
-      HDEBUG(D_auth) debug_printf("server_password expansion failed, so "
+      HDEBUG(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("  set\n");
+    HDEBUG(auth) debug_printf("  set\n");
     gsasl_property_set(sctx, GSASL_PASSWORD, tmps);
 
     /* This is inadequate; don't think Exim's store stacks are geared
@@ -794,11 +793,11 @@ switch (prop)
     break;
 
   default:
-    HDEBUG(D_auth) debug_printf(" Unrecognised callback: %d\n", prop);
+    HDEBUG(auth) debug_printf(" Unrecognised callback: %d\n", prop);
     cbrc = GSASL_NO_CALLBACK;
   }
 
-HDEBUG(D_auth) debug_printf("Returning %s (%s)\n",
+HDEBUG(auth) debug_printf("Returning %s (%s)\n",
     gsasl_strerror_name(cbrc), gsasl_strerror(cbrc));
 
 return cbrc;
@@ -823,7 +822,7 @@ if (!(s = expand_string(val)) || !(flags & PROP_OPTIONAL) && !*s)
   }
 if (*s)
   {
-  HDEBUG(D_auth) debug_printf("%s: set %s = '%s'\n", __FUNCTION__,
+  HDEBUG(auth) debug_printf("%s: set %s = '%s'\n", __FUNCTION__,
     gsasl_prop_code_to_name(prop), s);
   gsasl_property_set(sctx, prop, CS s);
   }
@@ -853,7 +852,7 @@ uschar * s;
 BOOL initial = TRUE;
 int rc, yield = FAIL;
 
-HDEBUG(D_auth)
+HDEBUG(auth)
   debug_printf("GNU SASL: initialising session for %s, mechanism %s\n",
       auname, ob->server_mech);
 
@@ -885,7 +884,7 @@ if ((rc = gsasl_client_start(gsasl_ctx, CCS ob->server_mech, &sctx)) != GSASL_OK
   {
   string_format(buffer, buffsize, "GNU SASL: session start failure: %s (%s)",
       gsasl_strerror_name(rc), gsasl_strerror(rc));
-  HDEBUG(D_auth) debug_printf("%s\n", buffer);
+  HDEBUG(auth) debug_printf("%s\n", buffer);
   return ERROR;
   }
 
@@ -908,7 +907,7 @@ if (  !set_client_prop(sctx, GSASL_PASSWORD, ob->client_password,
 if (tls_out.channelbinding)
   if (ob->client_channelbinding)
     {
-    HDEBUG(D_auth) debug_printf("Auth %s: Enabling channel-binding\n",
+    HDEBUG(auth) debug_printf("Auth %s: Enabling channel-binding\n",
 	auname);
 # ifndef CHANNELBIND_HACK
     preload_prop(sctx,
@@ -920,7 +919,7 @@ if (tls_out.channelbinding)
 # endif
     }
   else
-    HDEBUG(D_auth)
+    HDEBUG(auth)
       debug_printf("Auth %s: Not enabling channel-binding (data available)\n",
 	  auname);
 #endif
@@ -997,14 +996,14 @@ return yield;
 static int
 client_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_instance *ablock)
 {
-HDEBUG(D_auth) debug_printf("GNU SASL callback %s for %s/%s as client\n",
+HDEBUG(auth) debug_printf("GNU SASL callback %s for %s/%s as client\n",
       gsasl_prop_code_to_name(prop), ablock->drinst.name, ablock->public_name);
 switch (prop)
   {
 #ifdef EXIM_GSASL_HAVE_EXPORTER
   case GSASL_CB_TLS_EXPORTER:	/* Should never get called for this, as pre-set */
     if (!tls_out.channelbind_exporter) break;
-    HDEBUG(D_auth) debug_printf(" filling in\n");
+    HDEBUG(auth) debug_printf(" filling in\n");
     gsasl_property_set(sctx, GSASL_CB_TLS_EXPORTER, CCS tls_out.channelbinding);
     return GSASL_OK;
 #endif
@@ -1012,7 +1011,7 @@ switch (prop)
 #ifdef EXIM_GSASL_HAVE_EXPORTER
     if (tls_out.channelbind_exporter) break;
 #endif
-    HDEBUG(D_auth) debug_printf(" filling in\n");
+    HDEBUG(auth) debug_printf(" filling in\n");
     gsasl_property_set(sctx, GSASL_CB_TLS_UNIQUE, CCS tls_out.channelbinding);
     return GSASL_OK;
   case GSASL_SCRAM_SALTED_PASSWORD:
@@ -1020,7 +1019,7 @@ switch (prop)
     uschar * client_spassword =
       ((auth_gsasl_options_block *) ablock->drinst.options_block)->client_spassword;
     uschar dummy[4];
-    HDEBUG(D_auth) if (!client_spassword)
+    HDEBUG(auth) if (!client_spassword)
       debug_printf(" client_spassword option unset\n");
     if (client_spassword)
       {
@@ -1036,7 +1035,7 @@ switch (prop)
     break;
     }
   default:
-    HDEBUG(D_auth)
+    HDEBUG(auth)
       debug_printf(" not providing one\n");
     break;
   }
@@ -1073,7 +1072,7 @@ auth_info gsasl_auth_info = {
   .options_block =	&auth_gsasl_option_defaults,
   .options_len =	sizeof(auth_gsasl_options_block),
   .init =		auth_gsasl_init,
-# ifdef DYNLOOKUP
+# if AUTH_GSASL==2
   .dyn_magic =		AUTH_MAGIC,
 # endif
   },
diff --git a/src/src/auths/heimdal_gssapi.c b/src/src/auths/heimdal_gssapi.c
index 8014e61bb..a2b2cd49f 100644
--- a/src/src/auths/heimdal_gssapi.c
+++ b/src/src/auths/heimdal_gssapi.c
@@ -135,14 +135,14 @@ ablock->client = FALSE;
 
 if (!ob->server_service || !*ob->server_service)
   {
-  HDEBUG(D_auth) debug_printf("heimdal: missing server_service\n");
+  HDEBUG(auth) debug_printf("heimdal: missing server_service\n");
   return;
   }
 
 if ((krc = krb5_init_context(&context)))
   {
   int kerr = errno;
-  HDEBUG(D_auth) debug_printf("heimdal: failed to initialise krb5 context: %s\n",
+  HDEBUG(auth) debug_printf("heimdal: failed to initialise krb5 context: %s\n",
       strerror(kerr));
   return;
   }
@@ -150,24 +150,24 @@ if ((krc = krb5_init_context(&context)))
 if (ob->server_keytab)
   {
   k_keytab_typed_name = CCS string_sprintf("file:%s", expand_string(ob->server_keytab));
-  HDEBUG(D_auth) debug_printf("heimdal: using keytab %s\n", k_keytab_typed_name);
+  HDEBUG(auth) debug_printf("heimdal: using keytab %s\n", k_keytab_typed_name);
   if ((krc = krb5_kt_resolve(context, k_keytab_typed_name, &keytab)))
     {
-    HDEBUG(D_auth) exim_heimdal_error_debug("krb5_kt_resolve", context, krc);
+    HDEBUG(auth) exim_heimdal_error_debug("krb5_kt_resolve", context, krc);
     return;
     }
   }
 else
  {
-  HDEBUG(D_auth) debug_printf("heimdal: using system default keytab\n");
+  HDEBUG(auth) debug_printf("heimdal: using system default keytab\n");
   if ((krc = krb5_kt_default(context, &keytab)))
     {
-    HDEBUG(D_auth) exim_heimdal_error_debug("krb5_kt_default", context, krc);
+    HDEBUG(auth) exim_heimdal_error_debug("krb5_kt_default", context, krc);
     return;
     }
   }
 
-HDEBUG(D_auth)
+HDEBUG(auth)
   {
   /* http://www.h5l.org/manual/HEAD/krb5/krb5_keytab_intro.html */
   if ((krc = krb5_kt_start_seq_get(context, keytab, &cursor)))
@@ -193,7 +193,7 @@ HDEBUG(D_auth)
   }
 
 if ((krc = krb5_kt_close(context, keytab)))
-  HDEBUG(D_auth) exim_heimdal_error_debug("krb5_kt_close", context, krc);
+  HDEBUG(auth) exim_heimdal_error_debug("krb5_kt_close", context, krc);
 
 krb5_free_context(context);
 
@@ -249,7 +249,7 @@ uschar requested_qop;
 
 store_reset_point = store_mark();
 
-HDEBUG(D_auth)
+HDEBUG(auth)
   debug_printf("heimdal: initialising auth context for %s\n", ablock->drinst.name);
 
 /* Construct our gss_name_t gserver describing ourselves */
@@ -272,7 +272,7 @@ if (ob->server_keytab)
   if (GSS_ERROR(maj_stat))
     return exim_gssapi_error_defer(store_reset_point, maj_stat, min_stat,
 	"registering keytab %q", keytab);
-  HDEBUG(D_auth) debug_printf("heimdal: using keytab %q\n", keytab);
+  HDEBUG(auth) debug_printf("heimdal: using keytab %q\n", keytab);
   }
 
 /* Acquire our credentials */
@@ -290,7 +290,7 @@ if (GSS_ERROR(maj_stat))
 
 maj_stat = gss_release_name(&min_stat, &gserver);
 
-HDEBUG(D_auth) debug_printf("heimdal: have server credentials.\n");
+HDEBUG(auth) debug_printf("heimdal: have server credentials.\n");
 
 /* Loop talking to client */
 step = 0;
@@ -317,12 +317,12 @@ while (step < 4)
         {
 	if (handled_empty_ir)
 	  {
-	  HDEBUG(D_auth) debug_printf("gssapi: repeated empty input, grr.\n");
+	  HDEBUG(auth) debug_printf("gssapi: repeated empty input, grr.\n");
 	  error_out = BAD64;
 	  goto ERROR_OUT;
 	  }
 
-	HDEBUG(D_auth) debug_printf("gssapi: missing initial response, nudging.\n");
+	HDEBUG(auth) debug_printf("gssapi: missing initial response, nudging.\n");
 	if ((error_out = auth_get_data(&from_client, US"", 0)) != OK)
 	  goto ERROR_OUT;
 	handled_empty_ir = TRUE;
@@ -330,7 +330,7 @@ while (step < 4)
 	}
       /* We should now have the opening data from the client, base64-encoded. */
       step += 1;
-      HDEBUG(D_auth) debug_printf("heimdal: have initial client data\n");
+      HDEBUG(auth) debug_printf("heimdal: have initial client data\n");
       break;
 
     case 1:
@@ -372,10 +372,10 @@ while (step < 4)
       if (maj_stat == GSS_S_COMPLETE)
         {
 	step += 1;
-	HDEBUG(D_auth) debug_printf("heimdal: GSS complete\n");
+	HDEBUG(auth) debug_printf("heimdal: GSS complete\n");
 	}
       else
-	HDEBUG(D_auth) debug_printf("heimdal: need more data\n");
+	HDEBUG(auth) debug_printf("heimdal: need more data\n");
       break;
 
     case 2:
@@ -406,7 +406,7 @@ while (step < 4)
 	goto ERROR_OUT;
 	}
 
-      HDEBUG(D_auth) debug_printf("heimdal SASL: requesting QOP with no security layers\n");
+      HDEBUG(auth) debug_printf("heimdal SASL: requesting QOP with no security layers\n");
 
       error_out = auth_get_data(&from_client,
 	  gbufdesc_out.value, gbufdesc_out.length);
@@ -436,7 +436,7 @@ while (step < 4)
 	}
       if (gbufdesc_out.length < 4)
         {
-	HDEBUG(D_auth)
+	HDEBUG(auth)
 	  debug_printf("gssapi: final message too short; "
 	      "need flags, buf sizes and optional authzid\n");
 	error_out = FAIL;
@@ -446,7 +446,7 @@ while (step < 4)
       requested_qop = (CS gbufdesc_out.value)[0];
       if (!(requested_qop & 0x01))
         {
-	HDEBUG(D_auth)
+	HDEBUG(auth)
 	  debug_printf("gssapi: client requested security layers (%x)\n",
 	      (unsigned int) requested_qop);
 	error_out = FAIL;
@@ -496,11 +496,11 @@ while (step < 4)
 	expand_nmax = 2;
 	expand_nlength[2] = expand_nlength[1];
 	auth_vars[1] = expand_nstring[2] = string_copyn(expand_nstring[1], expand_nlength[1]);
-	HDEBUG(D_auth)
+	HDEBUG(auth)
 	  debug_printf("heimdal SASL: empty authzid, set to dup of GSSAPI display name\n");
 	}
 
-      HDEBUG(D_auth)
+      HDEBUG(auth)
 	debug_printf("heimdal SASL: happy with client request\n"
 	   "  auth1 (verified GSSAPI display-name): %q\n"
 	   "  auth2 (unverified SASL requested authzid): %q\n",
@@ -549,7 +549,7 @@ OM_uint32 msgcontext = 0;
 gss_buffer_desc status_string;
 gstring * g = NULL;
 
-HDEBUG(D_auth)
+HDEBUG(auth)
   {
   va_start(ap, format);
   g = string_vformat(NULL, SVFMT_EXTEND|SVFMT_REBUFFER, format, ap);
@@ -565,7 +565,7 @@ do {
   if (!auth_defer_msg)
     auth_defer_msg = string_copy(US status_string.value);
 
-  HDEBUG(D_auth) debug_printf("heimdal %Y: %.*s\n",
+  HDEBUG(auth) debug_printf("heimdal %Y: %.*s\n",
       g, (int)status_string.length, CS status_string.value);
   gss_release_buffer(&min_stat, &status_string);
 
@@ -591,7 +591,7 @@ auth_heimdal_gssapi_client(
   uschar *buffer,                        /* buffer for reading response */
   int buffsize)                          /* size of buffer */
 {
-HDEBUG(D_auth)
+HDEBUG(auth)
   debug_printf("Client side NOT IMPLEMENTED: you should not see this!\n");
 /* NOT IMPLEMENTED */
 return FAIL;
@@ -625,7 +625,7 @@ auth_info heimdal_gssapi_auth_info = {
   .options_block =	&auth_heimdal_gssapi_option_defaults,
   .options_len =	sizeof(auth_heimdal_gssapi_options_block),
   .init =		auth_heimdal_gssapi_init,
-# ifdef DYNLOOKUP
+# if AUTH_HEIMDAL_GSSAPI==2
   .dyn_magic =		AUTH_MAGIC,
 # endif
   },
diff --git a/src/src/auths/plaintext.c b/src/src/auths/plaintext.c
index 598311e65..772b49ff9 100644
--- a/src/src/auths/plaintext.c
+++ b/src/src/auths/plaintext.c
@@ -194,7 +194,7 @@ auth_info plaintext_auth_info = {
   .options_block =	&auth_plaintext_option_defaults,
   .options_len =	sizeof(auth_plaintext_options_block),
   .init =		auth_plaintext_init,
-# ifdef DYNLOOKUP
+# if AUTH_PLAINTEXT==2
   .dyn_magic =		AUTH_MAGIC,
 # endif
   },
@@ -204,5 +204,5 @@ auth_info plaintext_auth_info = {
 .macros_create =	NULL,
 };
 
-#endif	/*AUTH_PLAINTEST*/
+#endif	/*AUTH_PLAINTEXT*/
 /* End of plaintext.c */
diff --git a/src/src/auths/pwcheck.c b/src/src/auths/pwcheck.c
index bf305832f..8856fcce3 100644
--- a/src/src/auths/pwcheck.c
+++ b/src/src/auths/pwcheck.c
@@ -59,16 +59,16 @@
  */
 
 /* Originally this module supported only the pwcheck daemon, which is where its
-name comes from. Nowadays it supports saslauthd as well; pwcheck is in fact
-deprecated. The definitions of CYRUS_PWCHECK_SOCKET and CYRUS_SASLAUTHD_SOCKET
-determine whether the facilities are actually supported or not. */
+name comes from. Nowadays it supports saslauthd instead; pwcheck is in fact
+deprecated. The definition of CYRUS_SASLAUTHD_SOCKET
+determines whether the facilities are actually supported or not. */
 
 
 #include "../exim.h"
 #include "pwcheck.h"
 
 
-#if defined(CYRUS_PWCHECK_SOCKET) || defined(CYRUS_SASLAUTHD_SOCKET)
+#if defined(CYRUS_SASLAUTHD_SOCKET)
 
 #include <sys/uio.h>
 
@@ -80,79 +80,6 @@ static int write_string(int, const uschar *, int);
 #endif
 
 
-/* A dummy function that always fails if pwcheck support is not
-wanted. */
-
-#ifndef CYRUS_PWCHECK_SOCKET
-int pwcheck_verify_password(const char *userid,
-                            const char *passwd,
-                            const char **reply)
-{
-*reply = "pwcheck support is not included in this Exim binary";
-return PWCHECK_FAIL;
-}
-
-
-/* This is the real function */
-
-#else
-
- /* taken from cyrus-sasl file checkpw.c */
- /* pwcheck daemon-authenticated login */
- int pwcheck_verify_password(const char *userid,
-                                  const char *passwd,
-                                  const char **reply)
- {
-     int s, start, r, n;
-     struct sockaddr_un srvaddr;
-     struct iovec iov[2];
-     static char response[1024];
-
-     *reply = NULL;
-
-     s = socket(AF_UNIX, SOCK_STREAM, 0);
-     if (s == -1) { return PWCHECK_FAIL; }
-
-     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));
-     if (r == -1) {
-        DEBUG(D_auth)
-            debug_printf("Cannot connect to pwcheck daemon (at '%s')\n",CYRUS_PWCHECK_SOCKET);
-       *reply = "cannot connect to pwcheck daemon";
-       return PWCHECK_FAIL;
-     }
-
-     iov[0].iov_base = CS userid;
-     iov[0].iov_len = strlen(userid)+1;
-     iov[1].iov_base = CS passwd;
-     iov[1].iov_len = strlen(passwd)+1;
-
-     retry_writev(s, iov, 2);
-
-     start = 0;
-     while (start < sizeof(response) - 1) {
-       n = read(s, response+start, sizeof(response) - 1 - start);
-       if (n < 1) break;
-       start += n;
-     }
-
-     (void)close(s);
-
-     if (start > 1 && !strncmp(response, "OK", 2)) {
-       return PWCHECK_OK;
-     }
-
-     response[start] = '\0';
-     *reply = response;
-     return PWCHECK_NO;
- }
-
-#endif
-
-
-
  /* A dummy function that always fails if saslauthd support is not
 wanted. */
 
@@ -184,7 +111,7 @@ int saslauthd_verify_password(const uschar *userid,
     int s, r;
     struct sockaddr_un srvaddr;
 
-    DEBUG(D_auth)
+    DEBUG(auth)
        debug_printf("saslauthd userid='%s' servicename='%s'"
                     " realm='%s'\n", userid, service, realm );
 
@@ -202,7 +129,7 @@ int saslauthd_verify_password(const uschar *userid,
             sizeof(srvaddr.sun_path));
     r = connect(s, (struct sockaddr *)&srvaddr, sizeof(srvaddr));
     if (r == -1) {
-       DEBUG(D_auth)
+       DEBUG(auth)
             debug_printf("Cannot connect to saslauthd daemon (at '%s'): %s\n",
                          CYRUS_SASLAUTHD_SOCKET, strerror(errno));
        *reply = string_sprintf("cannot connect to saslauthd daemon at "
@@ -212,14 +139,14 @@ int saslauthd_verify_password(const uschar *userid,
     }
 
     if ( write_string(s, userid, Ustrlen(userid)) < 0) {
-        DEBUG(D_auth)
+        DEBUG(auth)
             debug_printf("Failed to send userid to saslauthd daemon \n");
         (void)close(s);
         return PWCHECK_FAIL;
     }
 
     if ( write_string(s, password, Ustrlen(password)) < 0) {
-        DEBUG(D_auth)
+        DEBUG(auth)
             debug_printf("Failed to send password to saslauthd daemon \n");
         (void)close(s);
         return PWCHECK_FAIL;
@@ -228,21 +155,21 @@ int saslauthd_verify_password(const uschar *userid,
     memset((void *)password, 0, Ustrlen(password));
 
     if ( write_string(s, service, Ustrlen(service)) < 0) {
-        DEBUG(D_auth)
+        DEBUG(auth)
             debug_printf("Failed to send service name to saslauthd daemon \n");
         (void)close(s);
         return PWCHECK_FAIL;
     }
 
     if ( write_string(s, realm, Ustrlen(realm)) < 0) {
-        DEBUG(D_auth)
+        DEBUG(auth)
             debug_printf("Failed to send realm to saslauthd daemon \n");
         (void)close(s);
         return PWCHECK_FAIL;
     }
 
     if ( read_string(s, &daemon_reply ) < 2) {
-        DEBUG(D_auth)
+        DEBUG(auth)
             debug_printf("Corrupted answer '%s' received. \n", daemon_reply);
         (void)close(s);
         return PWCHECK_FAIL;
@@ -250,7 +177,7 @@ int saslauthd_verify_password(const uschar *userid,
 
     (void)close(s);
 
-    DEBUG(D_auth)
+    DEBUG(auth)
         debug_printf("Answer '%s' received. \n", daemon_reply);
 
     *reply = daemon_reply;
@@ -268,7 +195,7 @@ int saslauthd_verify_password(const uschar *userid,
 
 
 /* helper functions */
-#if defined(CYRUS_PWCHECK_SOCKET) || defined(CYRUS_SASLAUTHD_SOCKET)
+#if defined(CYRUS_SASLAUTHD_SOCKET)
 
 #define MAX_REQ_LEN 1024
 
diff --git a/src/src/auths/pwcheck.h b/src/src/auths/pwcheck.h
index 4c1d71d92..0c20292ce 100644
--- a/src/src/auths/pwcheck.h
+++ b/src/src/auths/pwcheck.h
@@ -6,8 +6,8 @@
 /* See the file NOTICE for conditions of use and distribution. */
 /* SPDX-License-Identifier: GPL-2.0-or-later */
 
-/* This file provides support for authentication via the Cyrus SASL pwcheck
-daemon (whence its name) and the newer saslauthd daemon. */
+/* This file originally provided support for authentication via the Cyrus
+SASL pwcheck daemon (whence its name), but now the newer saslauthd daemon. */
 
 /* Error codes used internally within the authentication functions */
 
@@ -21,7 +21,6 @@ daemon (whence its name) and the newer saslauthd daemon. */
 
 /* Cyrus functions for doing the business. */
 
-extern int pwcheck_verify_password(const char *, const char *, const char **);
 extern int saslauthd_verify_password(const uschar *, const uschar *,
            const uschar *, const uschar *, const uschar **);
 
diff --git a/src/src/auths/spa.c b/src/src/auths/spa.c
index 8bd95813e..65bab458b 100644
--- a/src/src/auths/spa.c
+++ b/src/src/auths/spa.c
@@ -110,7 +110,7 @@ if (!ablock->public_name)
 /* Both username and password must be set for a client */
 
 if ((ob->spa_username == NULL) != (ob->spa_password == NULL))
-  log_write_die(0, LOG_CONFIG_FOR, "%s authenticator:\n  "
+  log_write_die(LOG_CONFIG_FOR, "%s authenticator:\n  "
       "one of client_username and client_password cannot be set without "
       "the other", ablock->drinst.name);
 ablock->client = ob->spa_username != NULL;
@@ -156,7 +156,7 @@ if (!*data && auth_get_no64_data(&data, US"NTLM supported") != OK)
 
 if (spa_base64_to_bits(CS &request, sizeof(request), CCS data) < 0)
   {
-  DEBUG(D_auth) debug_printf("auth_spa_server(): bad base64 data in "
+  DEBUG(auth) debug_printf("auth_spa_server(): bad base64 data in "
     "request: %s\n", data);
   return FAIL;
   }
@@ -172,7 +172,7 @@ if (auth_get_no64_data(&data, msgbuf) != OK)
 /* dump client response */
 if (spa_base64_to_bits(CS &response, sizeof(response), CCS data) < 0)
   {
-  DEBUG(D_auth) debug_printf("auth_spa_server(): bad base64 data in "
+  DEBUG(auth) debug_printf("auth_spa_server(): bad base64 data in "
     "response: %s\n", data);
   return FAIL;
   }
@@ -201,7 +201,7 @@ that causes failure if the size of msgbuf is exceeded. ****/
      || (p = (CS responseptr) + off) + len*2 >= CS (responseptr+1)
      )
     {
-    DEBUG(D_auth)
+    DEBUG(auth)
       debug_printf("auth_spa_server(): bad uUser spec in response\n");
     return FAIL;
     }
@@ -233,13 +233,13 @@ debug_print_string(ablock->server_debug_string);    /* customized debug */
 if (!(clearpass = expand_string(ob->spa_serverpassword)))
   if (f.expand_string_forcedfail)
     {
-    DEBUG(D_auth) debug_printf("auth_spa_server(): forced failure while "
+    DEBUG(auth) debug_printf("auth_spa_server(): forced failure while "
       "expanding spa_serverpassword\n");
     return FAIL;
     }
   else
     {
-    DEBUG(D_auth) debug_printf("auth_spa_server(): error while expanding "
+    DEBUG(auth) debug_printf("auth_spa_server(): error while expanding "
       "spa_serverpassword: %s\n", expand_string_message);
     return DEFER;
     }
@@ -254,7 +254,7 @@ spa_smb_nt_encrypt(clearpass, challenge.challengeData, ntRespData);
 off = IVAL(&responseptr->ntResponse.offset,0);
 if (off >= sizeof(SPAAuthResponse) - 24)
   {
-  DEBUG(D_auth)
+  DEBUG(auth)
     debug_printf("auth_spa_server(): bad ntRespData spec in response\n");
   return FAIL;
   }
@@ -389,7 +389,7 @@ auth_info spa_auth_info = {
   .options_block =	&auth_spa_option_defaults,
   .options_len =	sizeof(auth_spa_options_block),
   .init =		auth_spa_init,
-# ifdef DYNLOOKUP
+# if AUTH_SPA==2
   .dyn_magic =		AUTH_MAGIC,
 # endif
   },
diff --git a/src/src/auths/tls.c b/src/src/auths/tls.c
index b427d29aa..d44750098 100644
--- a/src/src/auths/tls.c
+++ b/src/src/auths/tls.c
@@ -108,7 +108,7 @@ auth_info tls_auth_info = {
   .options_block =	&auth_tls_option_defaults,
   .options_len =	sizeof(auth_tls_options_block),
   .init =		auth_tls_init,
-# ifdef DYNLOOKUP
+# if AUTH_TLS==2
   .dyn_magic =		AUTH_MAGIC,
 # endif
   },
diff --git a/src/src/base64.c b/src/src/base64.c
index 60f653f10..2bafa78e5 100644
--- a/src/src/base64.c
+++ b/src/src/base64.c
@@ -137,8 +137,8 @@ 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. */
+A zero byte 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 */
@@ -156,7 +156,7 @@ b64decode(const uschar * code, uschar ** ptr, const void * proto_mem)
 {
 
 int x, y;
-uschar *result;
+uschar * result;
 
  {
   int l = Ustrlen(code);
diff --git a/src/src/bmi_spam.c b/src/src/bmi_spam.c
index 03e8defa6..e69de29bb 100644
--- a/src/src/bmi_spam.c
+++ b/src/src/bmi_spam.c
@@ -1,477 +0,0 @@
-/*************************************************
-*     Exim - an Internet mail transport agent    *
-*************************************************/
-
-/* Code for calling Brightmail AntiSpam.
-   Copyright (c) Tom Kistner <tom@duncanthrax.net> 2004
-   License: GPL */
-/* Copyright (c) The Exim Maintainers 2021 - 2022 */
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-
-#include "exim.h"
-#ifdef EXPERIMENTAL_BRIGHTMAIL
-
-#include "bmi_spam.h"
-
-uschar *bmi_current_optin = NULL;
-
-uschar *bmi_process_message(header_line *header_list, int data_fd) {
-  BmiSystem *system = NULL;
-  BmiMessage *message = NULL;
-  BmiError err;
-  BmiErrorLocation err_loc;
-  BmiErrorType err_type;
-  const BmiVerdict *verdict = NULL;
-  FILE *data_file;
-  uschar data_buffer[4096];
-  uschar localhost[] = "127.0.0.1";
-  uschar *host_address;
-  uschar *verdicts = NULL;
-  int i,j;
-
-  err = bmiInitSystem(BMI_VERSION, CS bmi_config_file, &system);
-  if (bmiErrorIsFatal(err) == BMI_TRUE) {
-    err_loc = bmiErrorGetLocation(err);
-    err_type = bmiErrorGetType(err);
-    log_write(0, LOG_PANIC,
-               "bmi error [loc %d type %d]: could not initialize Brightmail system.", (int)err_loc, (int)err_type);
-    return NULL;
-  }
-
-  err = bmiInitMessage(system, &message);
-  if (bmiErrorIsFatal(err) == BMI_TRUE) {
-    err_loc = bmiErrorGetLocation(err);
-    err_type = bmiErrorGetType(err);
-    log_write(0, LOG_PANIC,
-               "bmi error [loc %d type %d]: could not initialize Brightmail message.", (int)err_loc, (int)err_type);
-    bmiFreeSystem(system);
-    return NULL;
-  }
-
-  /* Send IP address of sending host */
-  if (sender_host_address == NULL)
-    host_address = localhost;
-  else
-    host_address = sender_host_address;
-  err = bmiProcessConnection(CS host_address, message);
-  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, CS host_address);
-    bmiFreeMessage(message);
-    bmiFreeSystem(system);
-    return NULL;
-  };
-
-  /* Send envelope sender address */
-  err = bmiProcessFROM(CS sender_address, message);
-  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, CS sender_address);
-    bmiFreeMessage(message);
-    bmiFreeSystem(system);
-    return NULL;
-  };
-
-  /* Send envelope recipients */
-  for(i=0;i<recipients_count;i++) {
-    recipient_item *r = recipients_list + i;
-    BmiOptin *optin = NULL;
-
-    /* create optin object if optin string is given */
-    if ((r->bmi_optin != NULL) && (Ustrlen(r->bmi_optin) > 1)) {
-      debug_printf("passing bmiOptin string: %s\n", r->bmi_optin);
-      bmiOptinInit(&optin);
-      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, CS r->address, CS r->bmi_optin);
-        if (optin != NULL)
-          bmiOptinFree(optin);
-        optin = NULL;
-      };
-    };
-
-    err = bmiAccumulateTO(CS r->address, optin, message);
-
-    if (optin != NULL)
-      bmiOptinFree(optin);
-
-    if (bmiErrorIsFatal(err) == BMI_TRUE) {
-      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, CS r->address);
-      bmiFreeMessage(message);
-      bmiFreeSystem(system);
-      return NULL;
-    };
-  };
-  err = bmiEndTO(message);
-  if (bmiErrorIsFatal(err) == BMI_TRUE) {
-    err_loc = bmiErrorGetLocation(err);
-    err_type = bmiErrorGetType(err);
-    log_write(0, LOG_PANIC,
-               "bmi error [loc %d type %d]: bmiEndTO() failed.", (int)err_loc, (int)err_type);
-    bmiFreeMessage(message);
-    bmiFreeSystem(system);
-    return NULL;
-  };
-
-  /* Send message headers */
-  while (header_list != NULL) {
-    /* skip deleted headers */
-    if (header_list->type == '*') {
-      header_list = header_list->next;
-      continue;
-    };
-    err = bmiAccumulateHeaders(CCS header_list->text, header_list->slen, message);
-    if (bmiErrorIsFatal(err) == BMI_TRUE) {
-      err_loc = bmiErrorGetLocation(err);
-      err_type = bmiErrorGetType(err);
-      log_write(0, LOG_PANIC,
-                 "bmi error [loc %d type %d]: bmiAccumulateHeaders() failed.", (int)err_loc, (int)err_type);
-      bmiFreeMessage(message);
-      bmiFreeSystem(system);
-      return NULL;
-    };
-    header_list = header_list->next;
-  };
-  err = bmiEndHeaders(message);
-  if (bmiErrorIsFatal(err) == BMI_TRUE) {
-    err_loc = bmiErrorGetLocation(err);
-    err_type = bmiErrorGetType(err);
-    log_write(0, LOG_PANIC,
-               "bmi error [loc %d type %d]: bmiEndHeaders() failed.", (int)err_loc, (int)err_type);
-    bmiFreeMessage(message);
-    bmiFreeSystem(system);
-    return NULL;
-  };
-
-  /* Send body */
-  data_file = fdopen(data_fd,"r");
-  do {
-    j = fread(data_buffer, 1, sizeof(data_buffer), data_file);
-    if (j > 0) {
-      err = bmiAccumulateBody(CCS data_buffer, j, message);
-      if (bmiErrorIsFatal(err) == BMI_TRUE) {
-        err_loc = bmiErrorGetLocation(err);
-        err_type = bmiErrorGetType(err);
-        log_write(0, LOG_PANIC,
-                   "bmi error [loc %d type %d]: bmiAccumulateBody() failed.", (int)err_loc, (int)err_type);
-        bmiFreeMessage(message);
-        bmiFreeSystem(system);
-        return NULL;
-      };
-    };
-  } while (j > 0);
-  err = bmiEndBody(message);
-  if (bmiErrorIsFatal(err) == BMI_TRUE) {
-    err_loc = bmiErrorGetLocation(err);
-    err_type = bmiErrorGetType(err);
-    log_write(0, LOG_PANIC,
-               "bmi error [loc %d type %d]: bmiEndBody() failed.", (int)err_loc, (int)err_type);
-    bmiFreeMessage(message);
-    bmiFreeSystem(system);
-    return NULL;
-  };
-
-
-  /* End message */
-  err = bmiEndMessage(message);
-  if (bmiErrorIsFatal(err) == BMI_TRUE) {
-    err_loc = bmiErrorGetLocation(err);
-    err_type = bmiErrorGetType(err);
-    log_write(0, LOG_PANIC,
-               "bmi error [loc %d type %d]: bmiEndMessage() failed.", (int)err_loc, (int)err_type);
-    bmiFreeMessage(message);
-    bmiFreeSystem(system);
-    return NULL;
-  };
-
-  /* Get store for the verdict string.  Since we are processing message data, assume that
-  the verdict is tainted.  XXX this should use a growable-string  */
-
-  verdicts = store_get(1, GET_TAINTED);
-  *verdicts = '\0';
-
-  for ( err = bmiAccessFirstVerdict(message, &verdict);
-        verdict;
-        err = bmiAccessNextVerdict(message, verdict, &verdict) ) {
-    char *verdict_str;
-
-    err = bmiCreateStrFromVerdict(verdict,&verdict_str);
-    if (!store_extend(verdicts,
-	  Ustrlen(verdicts)+1, Ustrlen(verdicts)+1+strlen(verdict_str)+1)) {
-      /* can't allocate more store */
-      return NULL;
-    };
-    if (*verdicts != '\0')
-      Ustrcat(verdicts, US ":");
-    Ustrcat(verdicts, US verdict_str);
-    bmiFreeStr(verdict_str);
-  };
-
-  DEBUG(D_receive) debug_printf("bmi verdicts: %s\n", verdicts);
-
-  if (Ustrlen(verdicts) == 0)
-    return NULL;
-  else
-    return verdicts;
-}
-
-
-int bmi_get_delivery_status(uschar *base64_verdict) {
-  BmiError err;
-  BmiErrorLocation err_loc;
-  BmiErrorType err_type;
-  BmiVerdict *verdict = NULL;
-  int rc = 1;   /* deliver by default */
-
-  /* always deliver when there is no verdict */
-  if (base64_verdict == NULL)
-    return 1;
-
-  /* create verdict from base64 string */
-  err = bmiCreateVerdictFromStr(CS base64_verdict, &verdict);
-  if (bmiErrorIsFatal(err) == BMI_TRUE) {
-    err_loc = bmiErrorGetLocation(err);
-    err_type = bmiErrorGetType(err);
-    log_write(0, LOG_PANIC,
-               "bmi error [loc %d type %d]: bmiCreateVerdictFromStr() failed. [%s]", (int)err_loc, (int)err_type, base64_verdict);
-    return 1;
-  };
-
-  err = bmiVerdictError(verdict);
-  if (bmiErrorIsFatal(err) == BMI_TRUE) {
-    /* deliver normally due to error */
-    rc = 1;
-  }
-  else if (bmiVerdictDestinationIsDefault(verdict) == BMI_TRUE) {
-    /* deliver normally */
-    rc = 1;
-  }
-  else if (bmiVerdictAccessDestination(verdict) == NULL) {
-    /* do not deliver */
-    rc = 0;
-  }
-  else {
-    /* deliver to alternate location */
-    rc = 1;
-  };
-
-  bmiFreeVerdict(verdict);
-  return rc;
-}
-
-
-uschar *bmi_get_alt_location(uschar *base64_verdict) {
-  BmiError err;
-  BmiErrorLocation err_loc;
-  BmiErrorType err_type;
-  BmiVerdict *verdict = NULL;
-  uschar *rc = NULL;
-
-  /* always deliver when there is no verdict */
-  if (base64_verdict == NULL)
-    return NULL;
-
-  /* create verdict from base64 string */
-  err = bmiCreateVerdictFromStr(CS base64_verdict, &verdict);
-  if (bmiErrorIsFatal(err) == BMI_TRUE) {
-    err_loc = bmiErrorGetLocation(err);
-    err_type = bmiErrorGetType(err);
-    log_write(0, LOG_PANIC,
-               "bmi error [loc %d type %d]: bmiCreateVerdictFromStr() failed. [%s]", (int)err_loc, (int)err_type, base64_verdict);
-    return NULL;
-  };
-
-  err = bmiVerdictError(verdict);
-  if (bmiErrorIsFatal(err) == BMI_TRUE) {
-    /* deliver normally due to error */
-    rc = NULL;
-  }
-  else if (bmiVerdictDestinationIsDefault(verdict) == BMI_TRUE) {
-    /* deliver normally */
-    rc = NULL;
-  }
-  else if (bmiVerdictAccessDestination(verdict) == NULL) {
-    /* do not deliver */
-    rc = NULL;
-  }
-  else {
-    /* deliver to alternate location */
-    rc = store_get(strlen(bmiVerdictAccessDestination(verdict))+1, GET_TAINTED);
-    Ustrcpy(rc, bmiVerdictAccessDestination(verdict));
-    rc[strlen(bmiVerdictAccessDestination(verdict))] = '\0';
-  };
-
-  bmiFreeVerdict(verdict);
-  return rc;
-}
-
-uschar *bmi_get_base64_verdict(uschar *bmi_local_part, uschar *bmi_domain) {
-  BmiError err;
-  BmiErrorLocation err_loc;
-  BmiErrorType err_type;
-  BmiVerdict *verdict = NULL;
-  const BmiRecipient *recipient = NULL;
-  const char *verdict_str = NULL;
-  uschar *verdict_ptr;
-  uschar *verdict_buffer = NULL;
-  int sep = 0;
-
-  /* return nothing if there are no verdicts available */
-  if (bmi_verdicts == NULL)
-    return NULL;
-
-  /* allocate room for the b64 verdict string */
-  verdict_buffer = store_get(Ustrlen(bmi_verdicts)+1, GET_TAINTED);
-
-  /* loop through verdicts */
-  verdict_ptr = bmi_verdicts;
-  while ((verdict_str = CCS string_nextinlist(&verdict_ptr, &sep,
-                                          verdict_buffer,
-                                          Ustrlen(bmi_verdicts)+1)) != NULL) {
-
-    /* create verdict from base64 string */
-    err = bmiCreateVerdictFromStr(verdict_str, &verdict);
-    if (bmiErrorIsFatal(err) == BMI_TRUE) {
-      err_loc = bmiErrorGetLocation(err);
-      err_type = bmiErrorGetType(err);
-      log_write(0, LOG_PANIC,
-                 "bmi error [loc %d type %d]: bmiCreateVerdictFromStr() failed. [%s]", (int)err_loc, (int)err_type, verdict_str);
-      return NULL;
-    };
-
-    /* loop through rcpts for this verdict */
-    for ( recipient = bmiVerdictAccessFirstRecipient(verdict);
-          recipient != NULL;
-          recipient = bmiVerdictAccessNextRecipient(verdict, recipient)) {
-      uschar *rcpt_local_part;
-      uschar *rcpt_domain;
-
-      /* compare address against our subject */
-      rcpt_local_part = US bmiRecipientAccessAddress(recipient);
-      rcpt_domain = Ustrchr(rcpt_local_part,'@');
-      if (rcpt_domain == NULL) {
-        rcpt_domain = US"";
-      }
-      else {
-        *rcpt_domain = '\0';
-        rcpt_domain++;
-      };
-
-      if ( (strcmpic(rcpt_local_part, bmi_local_part) == 0) &&
-           (strcmpic(rcpt_domain, bmi_domain) == 0) ) {
-        /* found verdict */
-        bmiFreeVerdict(verdict);
-        return US verdict_str;
-      };
-    };
-
-    bmiFreeVerdict(verdict);
-  };
-
-  return NULL;
-}
-
-
-uschar *bmi_get_base64_tracker_verdict(uschar *base64_verdict) {
-  BmiError err;
-  BmiErrorLocation err_loc;
-  BmiErrorType err_type;
-  BmiVerdict *verdict = NULL;
-  uschar *rc = NULL;
-
-  /* always deliver when there is no verdict */
-  if (base64_verdict == NULL)
-    return NULL;
-
-  /* create verdict from base64 string */
-  err = bmiCreateVerdictFromStr(CS base64_verdict, &verdict);
-  if (bmiErrorIsFatal(err) == BMI_TRUE) {
-    err_loc = bmiErrorGetLocation(err);
-    err_type = bmiErrorGetType(err);
-    log_write(0, LOG_PANIC,
-               "bmi error [loc %d type %d]: bmiCreateVerdictFromStr() failed. [%s]", (int)err_loc, (int)err_type, base64_verdict);
-    return NULL;
-  };
-
-  /* create old tracker string from verdict */
-  err = bmiCreateOldStrFromVerdict(verdict, &rc);
-  if (bmiErrorIsFatal(err) == BMI_TRUE) {
-    err_loc = bmiErrorGetLocation(err);
-    err_type = bmiErrorGetType(err);
-    log_write(0, LOG_PANIC,
-               "bmi error [loc %d type %d]: bmiCreateOldStrFromVerdict() failed. [%s]", (int)err_loc, (int)err_type, base64_verdict);
-    return NULL;
-  };
-
-  bmiFreeVerdict(verdict);
-  return rc;
-}
-
-
-int bmi_check_rule(uschar *base64_verdict, uschar *option_list) {
-  BmiError err;
-  BmiErrorLocation err_loc;
-  BmiErrorType err_type;
-  BmiVerdict *verdict = NULL;
-  int rc = 0;
-  uschar *rule_num;
-  uschar *rule_ptr;
-  uschar rule_buffer[32];
-  int sep = 0;
-
-
-  /* no verdict -> no rule fired */
-  if (base64_verdict == NULL)
-    return 0;
-
-  /* create verdict from base64 string */
-  err = bmiCreateVerdictFromStr(CS base64_verdict, &verdict);
-  if (bmiErrorIsFatal(err) == BMI_TRUE) {
-    err_loc = bmiErrorGetLocation(err);
-    err_type = bmiErrorGetType(err);
-    log_write(0, LOG_PANIC,
-               "bmi error [loc %d type %d]: bmiCreateVerdictFromStr() failed. [%s]", (int)err_loc, (int)err_type, base64_verdict);
-    return 0;
-  };
-
-  err = bmiVerdictError(verdict);
-  if (bmiErrorIsFatal(err) == BMI_TRUE) {
-    /* error -> no rule fired */
-    bmiFreeVerdict(verdict);
-    return 0;
-  }
-
-  /* loop through numbers */
-  /* option_list doesn't seem to be expanded so cannot be tainted.  If it ever is we
-  will trap here */
-  rule_ptr = option_list;
-  while ((rule_num = string_nextinlist(&rule_ptr, &sep,
-                                       rule_buffer, sizeof(rule_buffer)))) {
-    int rule_int = -1;
-
-    /* try to translate to int */
-    (void)sscanf(rule_num, "%d", &rule_int);
-    if (rule_int > 0) {
-      debug_printf("checking rule #%d\n", rule_int);
-      /* check if rule fired on the message */
-      if (bmiVerdictRuleFired(verdict, rule_int) == BMI_TRUE) {
-        debug_printf("rule #%d fired\n", rule_int);
-        rc = 1;
-        break;
-      };
-    };
-  };
-
-  bmiFreeVerdict(verdict);
-  return rc;
-};
-
-#endif
diff --git a/src/src/bmi_spam.h b/src/src/bmi_spam.h
index bb1c859a9..e69de29bb 100644
--- a/src/src/bmi_spam.h
+++ b/src/src/bmi_spam.h
@@ -1,23 +0,0 @@
-/*************************************************
-*     Exim - an Internet mail transport agent    *
-*************************************************/
-
-/* Code for calling Brightmail AntiSpam.
-   Copyright (c) Tom Kistner <tom@duncanthrax.net> 2004
-   License: GPL */
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-
-#ifdef EXPERIMENTAL_BRIGHTMAIL
-
-#include <bmi_api.h>
-
-extern uschar *bmi_process_message(header_line *, int);
-extern uschar *bmi_get_base64_verdict(uschar *, uschar *);
-extern uschar *bmi_get_base64_tracker_verdict(uschar *);
-extern int bmi_get_delivery_status(uschar *);
-extern uschar *bmi_get_alt_location(uschar *);
-extern int bmi_check_rule(uschar *,uschar *);
-
-extern uschar *bmi_current_optin;
-
-#endif
diff --git a/src/src/buildconfig.c b/src/src/buildconfig.c
index b365643bb..fab0dd97a 100644
--- a/src/src/buildconfig.c
+++ b/src/src/buildconfig.c
@@ -97,7 +97,7 @@ if (!OK)
   printf("\n*** \"%s\" (%s) must contain precisely one occurrence of\n"
     "*** \"%%s\". Please review your build-time configuration.\n\n/", value,
     name);
-  exit(1);
+  exit(EXIT_FAILURE);
   }
 }
 
@@ -115,14 +115,14 @@ char buffer[1024];
 if (argc != 1)
   {
   printf("*** Buildconfig: called with incorrect arguments\n");
-  exit(1);
+  exit(EXIT_FAILURE);
   }
 
 new = fopen("config.h", "wb");
 if (new == NULL)
   {
   printf("*** Buildconfig: failed to open config.h for output\n");
-  exit(1);
+  exit(EXIT_FAILURE);
   }
 
 printf("Building configuration file config.h\n");
@@ -180,7 +180,10 @@ else
 fprintf(new, "#endif\n\n");
 
 fprintf(new, "#ifndef PID_T_FMT\n");
-fprintf(new, "# define PID_T_FMT  \"%%lu\"\n");
+if (sizeof(pid_t) == sizeof(int))
+  fprintf(new, "# define PID_T_FMT  \"%%d\"\n");
+else
+  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
@@ -213,7 +216,7 @@ if (!(base = fopen("Makefile", "rb")))
   {
   printf("*** Buildconfig: failed to open Makefile\n");
   (void)fclose(new);
-  exit(1);
+  exit(EXIT_FAILURE);
   }
 
 errno_quota[0] = 0;    /* no over-riding value set */
@@ -254,7 +257,7 @@ while (fgets(buffer, sizeof(buffer), base) != NULL)
           {
           printf("*** Only one of USE_DB, USE_GDBM, USE_SQLITE or USE_TDB should be "
             "defined in Local/Makefile\n");
-          exit(1);
+          exit(EXIT_FAILURE);
           }
         use_which_db_in_local_makefile = 1;
         }
@@ -276,7 +279,7 @@ while (fgets(buffer, sizeof(buffer), base) != NULL)
       if (*p++ != '=')
         {
         printf("*** Buildconfig: syntax error in Makefile line %d\n", linecount);
-        exit(1);
+        exit(EXIT_FAILURE);
         }
       while (isspace((unsigned char)*p)) p++;
       if (strcmp(p, "YES") == 0 || strcmp(p, "yes") == 0) *(h->flag) = 1;
@@ -299,7 +302,7 @@ while (fgets(buffer, sizeof(buffer), base) != NULL)
       if (*p++ != '=')
         {
         printf("*** Buildconfig: syntax error in Makefile line %d\n", linecount);
-        exit(1);
+        exit(EXIT_FAILURE);
         }
       while (isspace((unsigned char)*p)) p++;
       strcpy(s->data, p);
@@ -335,7 +338,7 @@ if (base == NULL)
   {
   printf("*** Buildconfig: failed to open ../src/config.h.defaults\n");
   (void)fclose(new);
-  exit(1);
+  exit(EXIT_FAILURE);
   }
 
 while (fgets(buffer, sizeof(buffer), base) != NULL)
@@ -408,7 +411,7 @@ while (fgets(buffer, sizeof(buffer), base) != NULL)
       printf("\n*** EXIM_USER has not been defined in any of the Makefiles in "
         "the\n    \"Local\" directory. Please review your build-time "
         "configuration.\n\n");
-      return 1;
+      return EXIT_FAILURE;
       }
 
     while (isspace((unsigned char)(*user))) user++;
@@ -417,7 +420,7 @@ while (fgets(buffer, sizeof(buffer), base) != NULL)
       printf("\n*** EXIM_USER is defined as an empty string in one of the "
         "files\n    in the \"Local\" directory. Please review your build-time"
         "\n    configuration.\n\n");
-      return 1;
+      return EXIT_FAILURE;
       }
 
     for (s = user; *s; s++)
@@ -426,7 +429,7 @@ while (fgets(buffer, sizeof(buffer), base) != NULL)
         printf("\n*** EXIM_USER contains the control character 0x%02X in one "
           "of the files\n    in the \"Local\" directory. Please review your "
           "build-time\n    configuration.\n\n", *s);
-        return 1;
+        return EXIT_FAILURE;
         }
 
     /* Numeric uid given */
@@ -455,7 +458,7 @@ while (fgets(buffer, sizeof(buffer), base) != NULL)
         printf("\n*** User \"%s\" (specified in one of the Makefiles) does not "
           "exist.\n    Please review your build-time configuration.\n\n",
           user);
-        return 1;
+        return EXIT_FAILURE;
         }
 
       uid = pw->pw_uid;
@@ -481,7 +484,7 @@ while (fgets(buffer, sizeof(buffer), base) != NULL)
         else printf("EXIM_USER is defined numerically, so there is no"
           "\n    default for EXIM_GROUP and you must set it explicitly.\n    P");
         printf("lease review your build-time configuration.\n\n");
-        return 1;
+        return EXIT_FAILURE;
         }
 
       for (s = group; *s; s++)
@@ -490,7 +493,7 @@ while (fgets(buffer, sizeof(buffer), base) != NULL)
           printf("\n*** EXIM_GROUP contains the control character 0x%02X in one "
             "of the files\n    in the \"Local\" directory. Please review your "
             "build-time\n    configuration.\n\n", *s);
-          return 1;
+          return EXIT_FAILURE;
           }
 
       /* Group name given. This may be by reference or to be looked up now,
@@ -517,7 +520,7 @@ while (fgets(buffer, sizeof(buffer), base) != NULL)
           printf("\n*** Group \"%s\" (specified in one of the Makefiles) does "
             "not exist.\n   Please review your build-time configuration.\n\n",
             group);
-          return 1;
+          return EXIT_FAILURE;
           }
         gid = gr->gr_gid;
         }
@@ -529,7 +532,7 @@ while (fgets(buffer, sizeof(buffer), base) != NULL)
       {
       printf("\n*** No group set for Exim. Please review your build-time "
         "configuration.\n\n");
-      return 1;
+      return EXIT_FAILURE;
       }
 
     /* security sanity checks
@@ -542,7 +545,7 @@ while (fgets(buffer, sizeof(buffer), base) != NULL)
           (strcmp(username, "toor") == 0) )))
       {
       printf("\n*** Exim's internal user must not be root.\n\n");
-      return 1;
+      return EXIT_FAILURE;
       }
 
     /* Output user and group names or uid/gid. When names are set, uid/gid
@@ -587,7 +590,7 @@ while (fgets(buffer, sizeof(buffer), base) != NULL)
         printf("\n*** %s contains the control character 0x%02X in "
           "one of the files\n    in the \"Local\" directory. Please review "
           "your build-time\n    configuration.\n\n", name, *s);
-        return 1;
+        return EXIT_FAILURE;
         }
 
     /* Numeric uid given */
@@ -617,7 +620,7 @@ while (fgets(buffer, sizeof(buffer), base) != NULL)
         printf("\n*** Group \"%s\" (specified in one of the Makefiles) does not "
           "exist.\n    Please review your build-time configuration.\n\n",
           user);
-        return 1;
+        return EXIT_FAILURE;
         }
       gid = gr->gr_gid;
       }
@@ -630,7 +633,7 @@ while (fgets(buffer, sizeof(buffer), base) != NULL)
         printf("\n*** User \"%s\" (specified in one of the Makefiles) does not "
           "exist.\n    Please review your build-time configuration.\n\n",
           user);
-        return 1;
+        return EXIT_FAILURE;
         }
       uid = pw->pw_uid;
       }
@@ -665,7 +668,11 @@ while (fgets(buffer, sizeof(buffer), base) != NULL)
       char *p = list;
       while (*p != 0) if (*p++ == ':') count++;
 
-      vector = malloc((count+1) * sizeof(uid_t));
+      if (!(vector = malloc((count+1) * sizeof(uid_t))))
+	{
+	perror("malloc");
+	exit(EXIT_FAILURE);
+	}
       vector[0] = (uid_t)count;
 
       for (int i = 1; i <= count; list++, i++)
@@ -690,7 +697,7 @@ while (fgets(buffer, sizeof(buffer), base) != NULL)
 	      " in one of the Makefiles) does not exist.\n"
 	      "   Please review your build-time configuration.\n\n",
               name);
-            return 1;
+            return EXIT_FAILURE;
             }
           vector[j++] = pw->pw_uid;
           }
@@ -747,7 +754,7 @@ while (fgets(buffer, sizeof(buffer), base) != NULL)
         {
         printf("\n*** LDAP_LIB_TYPE=%s is not a recognized LDAP library type."
           "\n*** Please review your build-time configuration.\n\n", value);
-        return 1;
+        return EXIT_FAILURE;
         }
       }
 
@@ -763,7 +770,7 @@ while (fgets(buffer, sizeof(buffer), base) != NULL)
         {
         printf("\n*** RADIUS_LIB_TYPE=%s is not a recognized RADIUS library type."
           "\n*** Please review your build-time configuration.\n\n", value);
-        return 1;
+        return EXIT_FAILURE;
         }
       }
 
@@ -829,7 +836,7 @@ while (fgets(buffer, sizeof(buffer), base) != NULL)
         else
           {
           printf("Value of %s should be -1..9\n", name);
-          return 1;
+          return EXIT_FAILURE;
           }
         }
 
@@ -851,7 +858,7 @@ while (fgets(buffer, sizeof(buffer), base) != NULL)
         else
           {
           printf("Unreasonable value (%s) of \"%s\".\n", value, name);
-          return 1;
+          return EXIT_FAILURE;
           }
         }
 
@@ -880,7 +887,7 @@ while (fgets(buffer, sizeof(buffer), base) != NULL)
         printf("\n*** %s has not been defined in any of the Makefiles in the\n"
           "    \"Local\" directory. "
           "Please review your build-time configuration.\n\n", name);
-        return 1;
+        return EXIT_FAILURE;
         }
 
       if (strcmp(name, "TIMEZONE_DEFAULT") == 0)
@@ -927,7 +934,7 @@ Some OS' have released with it broken. */
 
 fprintf(new, "\n/* End of config.h */\n");
 (void)fclose(new);
-return 0;
+return EXIT_SUCCESS;
 }
 
 /* End of buildconfig.c */
diff --git a/src/src/child.c b/src/src/child.c
index 359b791e8..b61d3e4c8 100644
--- a/src/src/child.c
+++ b/src/src/child.c
@@ -69,15 +69,14 @@ Returns:         if CEE_RETURN_ARGV is given, returns a pointer to argv;
 */
 
 uschar **
-child_exec_exim(int exec_type, BOOL kill_v, int *pcount, BOOL minimal,
+child_exec_exim(int exec_type, BOOL kill_v, int * pcount, BOOL minimal,
   int acount, ...)
 {
-int first_special = -1;
-int n = 0;
-int extra = pcount ? *pcount : 0;
+int first_special = -1, n = 0, extra = pcount ? *pcount : 0;
 uschar **argv;
 
-argv = store_get((extra + acount + MAX_CLMACROS + 24) * sizeof(char *), GET_UNTAINTED);
+argv = store_get((extra + acount + MAX_CLMACROS + 24) * sizeof(char *),
+		 GET_UNTAINTED);
 
 /* In all case, the list starts out with the path, any macros, and a changed
 config file. */
@@ -99,15 +98,11 @@ was involved, so we do pass it on. */
 
 if (!minimal)
   {
-  if (debug_selector == D_v)
-    {
-    if (!kill_v) argv[n++] = US"-v";
-    }
-  else
-    {
-    if (debug_selector != 0)
+  if (ANY_DEBUG)
+    if (DEBUG_BIT(BIT_TABLE_IDX_NONVERB))
       {
-      argv[n++] = string_sprintf("-d=0x%x", debug_selector);
+      argv[n++] = string_from_gstring(debug_selector_dump(NULL));
+
       if (debug_fd > 2)
 	{
 	int flags = fcntl(debug_fd, F_GETFD);
@@ -117,12 +112,14 @@ if (!minimal)
 	close(debug_fd);
 	}
       }
-    }
+    else
+      if (!kill_v) argv[n++] = US"-v";
+
   if (debug_pretrigger_buf)
     { argv[n++] = US"-dp"; argv[n++] = string_sprintf("0x%x", debug_pretrigger_bsize); }
   if (dtrigger_selector != 0)
     argv[n++] = string_sprintf("-dt=0x%x", dtrigger_selector);
-  DEBUG(D_any)
+  DEBUG(any)
     {
     argv[n++] = US"-MCd";
     argv[n++] = US process_purpose;
@@ -163,12 +160,11 @@ if (exec_type == CEE_RETURN_ARGV)
 failure. We know that there will always be at least one extra option in the
 call when exec() is done here, so it can be used to add to the panic data. */
 
-DEBUG(D_exec) debug_print_argv(CUSS argv);
+DEBUG(exec) debug_print_argv(CUSS argv);
 exim_nullstd();                            /* Make sure std{in,out,err} exist */
 execv(CS argv[0], (char *const *)argv);
 
-log_write(0,
-  LOG_MAIN | (exec_type == CEE_EXEC_EXIT ? LOG_PANIC : LOG_PANIC_DIE),
+log_write(LOG_MAIN | (exec_type == CEE_EXEC_EXIT ? LOG_PANIC : LOG_PANIC_DIE),
   "re-exec of exim (%s) with %s failed: %s", exim_path, argv[first_special],
   strerror(errno));
 
@@ -346,7 +342,7 @@ pid_t pid;
 
 if (is_tainted(argv[0]))
   {
-  log_write(0, LOG_MAIN | LOG_PANIC, "Attempt to exec tainted path: '%s'", argv[0]);
+  log_write(LOG_MAIN | LOG_PANIC, "Attempt to exec tainted path: '%s'", argv[0]);
   errno = EPERM;
   return (pid_t)(-1);
   }
@@ -379,14 +375,14 @@ if (pid == 0)
 
   if (newgid && setgid(*newgid) < 0)
     {
-    DEBUG(D_any) debug_printf("failed to set gid=%ld in subprocess: %s\n",
+    DEBUG(any) debug_printf("failed to set gid=%ld in subprocess: %s\n",
       (long int)(*newgid), strerror(errno));
     goto CHILD_FAILED;
     }
 
   if (newuid && setuid(*newuid) < 0)
     {
-    DEBUG(D_any) debug_printf("failed to set uid=%ld in subprocess: %s\n",
+    DEBUG(any) debug_printf("failed to set uid=%ld in subprocess: %s\n",
       (long int)(*newuid), strerror(errno));
     goto CHILD_FAILED;
     }
@@ -395,7 +391,7 @@ if (pid == 0)
 
   if (wd && Uchdir(wd) < 0)
     {
-    DEBUG(D_any) debug_printf("failed to chdir to %s: %s\n", wd,
+    DEBUG(any) debug_printf("failed to chdir to %s: %s\n", wd,
       strerror(errno));
     goto CHILD_FAILED;
     }
@@ -407,7 +403,7 @@ if (pid == 0)
 
   if (make_leader && setpgid(0,0) < 0)
     {
-    DEBUG(D_any) debug_printf("failed to set group leader in subprocess: %s\n",
+    DEBUG(any) debug_printf("failed to set group leader in subprocess: %s\n",
       strerror(errno));
     goto CHILD_FAILED;
     }
@@ -555,3 +551,5 @@ return yield;
 }
 
 /* End of child.c */
+/* vi: aw ai sw=2
+*/
diff --git a/src/src/config.h.defaults b/src/src/config.h.defaults
index ebf67ad57..cbc840eb3 100644
--- a/src/src/config.h.defaults
+++ b/src/src/config.h.defaults
@@ -40,7 +40,6 @@ Do not put spaces between # and the 'define'.
 #define CONFIGURE_FILE_USE_NODE
 #define CONFIGURE_GROUP
 #define CONFIGURE_OWNER
-#define CYRUS_PWCHECK_SOCKET
 #define CYRUS_SASLAUTHD_SOCKET
 
 #define DEFAULT_CRYPT              crypt
@@ -102,7 +101,6 @@ Do not put spaces between # and the 'define'.
 #define LOOKUP_DBM
 #define LOOKUP_DNSDB
 #define LOOKUP_DSEARCH
-#define LOOKUP_IBASE
 #define LOOKUP_JSON
 #define LOOKUP_LDAP
 #define LOOKUP_LMDB
@@ -113,6 +111,7 @@ Do not put spaces between # and the 'define'.
 #define LOOKUP_ORACLE
 #define LOOKUP_PASSWD
 #define LOOKUP_PGSQL
+#define LOOKUP_PSL
 #define LOOKUP_REDIS
 #define LOOKUP_SQLITE
 #define LOOKUP_TESTDB
@@ -156,6 +155,7 @@ Do not put spaces between # and the 'define'.
 #define SUPPORT_DMARC
 #define DMARC_API 100400
 #define DMARC_TLD_FILE "/etc/exim/opendmarc.tlds"
+#define SUPPORT_DSCP
 #define SUPPORT_I18N
 #define SUPPORT_I18N_2008
 #define SUPPORT_MAILDIR
@@ -202,23 +202,16 @@ Do not put spaces between # and the 'define'.
 #define WHITELIST_D_MACROS
 
 #define WITH_CONTENT_SCAN
-#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 */
 #define EXPERIMENTAL_ARC
-#define EXPERIMENTAL_BRIGHTMAIL
 #define EXPERIMENTAL_DCC
+#define EXPERIMENTAL_DMARC_NATIVE
 #define EXPERIMENTAL_DSN_INFO
 #define EXPERIMENTAL_NMH
 #define EXPERIMENTAL_QUEUEFILE
@@ -247,4 +240,11 @@ for EXIM_ARITH_MAX and _MIN in OS/oh.h-FOO */
 #define SC_EXIM_ARITH "%" SCNi64		/* scanf incl. 0x prefix */
 #define SC_EXIM_DEC   "%" SCNd64		/* scanf decimal */
 
+/* Word size for debug and logging channel bitmaps.
+Change as above (but expect debugging to become odd). */
+#define bitmask_word_t		uint64_t
+#define EXIM_BITMAP_WORD_BITS	64
+#define PR_EXIM_BITMASK		"%" PRIx64
+#define SC_EXIM_BITMASK		"%" SCNx64
+
 /* End of config.h.defaults */
diff --git a/src/src/daemon.c b/src/src/daemon.c
index a31ef1eb5..96421e820 100644
--- a/src/src/daemon.c
+++ b/src/src/daemon.c
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) The Exim Maintainers 2020 - 2025 */
+/* Copyright (c) The Exim Maintainers 2020 - 2026 */
 /* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 /* SPDX-License-Identifier: GPL-2.0-or-later */
@@ -43,6 +43,7 @@ static int   accept_retry_errno;
 static BOOL  accept_retry_select_failed;
 
 static int   queue_run_count = 0;	/* current runners */
+static const uschar * daemon_process_info = NULL;
 
 static unsigned queue_runner_slot_count = 0;
 static runner_slot * queue_runner_slots = NULL;
@@ -127,7 +128,7 @@ never_error(uschar *log_msg, uschar *smtp_msg, int 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(LOG_MAIN|LOG_PANIC, "%s%s", log_msg, emsg);
 if (smtp_out_fd >= 0) smtp_printf("421 %s\r\n", SP_NO_MORE, smtp_msg);
 }
 
@@ -141,7 +142,7 @@ static void
 unlink_notifier_socket(void)
 {
 #ifndef EXIM_HAVE_ABSTRACT_UNIX_SOCKETS
-DEBUG(D_any) debug_printf("unlinking notifier socket %s\n", notifier_socket_name);
+DEBUG(any) debug_printf("unlinking notifier socket %s\n", notifier_socket_name);
 Uunlink(notifier_socket_name);
 #endif
 }
@@ -188,7 +189,6 @@ pid_t pid;
 union sockaddr_46 interface_sockaddr;
 EXIM_SOCKLEN_T ifsize = sizeof(interface_sockaddr);
 int max_for_this_host = 0;
-int save_log_selector = *log_selector;
 gstring * whofrom;
 
 rmark reset_point = store_mark();
@@ -197,7 +197,7 @@ rmark reset_point = store_mark();
 the remote port. */
 
 sender_host_address = host_ntoa(-1, accepted, NULL, &sender_host_port);
-DEBUG(D_any) debug_printf("Connection request from %s port %d\n",
+DEBUG(any) debug_printf("Connection request from %s port %d\n",
   sender_host_address, sender_host_port);
 
 /* Set up the output stream, check the socket has duplicated, and set up the
@@ -219,14 +219,14 @@ if ((smtp_in_fd = dup(accept_socket)) < 0)
 if (getsockname(accept_socket, (struct sockaddr *)(&interface_sockaddr),
      &ifsize) < 0)
   {
-  log_write(0, LOG_MAIN | ((errno == ECONNRESET)? 0 : LOG_PANIC),
+  log_write(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", SP_NO_MORE);
   goto ERROR_RETURN;
   }
 
 interface_address = host_ntoa(-1, &interface_sockaddr, NULL, &interface_port);
-DEBUG(D_interface) debug_printf("interface address=%s port=%d\n",
+DEBUG(interface) debug_printf("interface address=%s port=%d\n",
   interface_address, interface_port);
 
 /* Build a string identifying the remote host and, if requested, the port and
@@ -250,13 +250,13 @@ it might take some time. */
 
 if (smtp_accept_max > 0 && smtp_accept_count >= smtp_accept_max)
   {
-  DEBUG(D_any) debug_printf("rejecting SMTP connection: count=%d max=%d\n",
+  DEBUG(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", SP_NO_MORE);
-  log_write(L_connection_reject,
-            LOG_MAIN, "Connection from %Y refused: too many connections",
-    whofrom);
+  if (LOGGING(connection_reject))
+    log_write(LOG_MAIN, "Connection from %Y refused: too many connections",
+	    whofrom);
   goto ERROR_RETURN;
   }
 
@@ -270,12 +270,12 @@ if (smtp_load_reserve >= 0)
   load_average = OS_GETLOADAVG();
   if (!smtp_reserve_hosts && load_average > smtp_load_reserve)
     {
-    DEBUG(D_any) debug_printf("rejecting SMTP connection: load average = %.2f\n",
+    DEBUG(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", SP_NO_MORE);
-    log_write(L_connection_reject,
-              LOG_MAIN, "Connection from %Y refused: load average = %.2f",
-      whofrom, (double)load_average/1000.0);
+    if (LOGGING(connection_reject))
+      log_write(LOG_MAIN, "Connection from %Y refused: load average = %.2f",
+	      whofrom, (double)load_average/1000.0);
     goto ERROR_RETURN;
     }
   }
@@ -295,7 +295,7 @@ if (smtp_accept_max_per_host)
   if (!expanded)
     {
     if (!f.expand_string_forcedfail)
-      log_write(0, LOG_MAIN|LOG_PANIC, "expansion of smtp_accept_max_per_host "
+      log_write(LOG_MAIN|LOG_PANIC, "expansion of smtp_accept_max_per_host "
         "failed for %Y: %s", whofrom, expand_string_message);
     }
   /* For speed, interpret a decimal number inline here */
@@ -305,7 +305,7 @@ if (smtp_accept_max_per_host)
     while (isdigit(*s))
       max_for_this_host = max_for_this_host * 10 + *s++ - '0';
     if (*s)
-      log_write(0, LOG_MAIN|LOG_PANIC, "expansion of smtp_accept_max_per_host "
+      log_write(LOG_MAIN|LOG_PANIC, "expansion of smtp_accept_max_per_host "
         "for %Y contains non-digit: %s", whofrom, expanded);
     }
   }
@@ -339,14 +339,14 @@ if (  smtp_slots
 
   if (host_accept_count >= max_for_this_host)
     {
-    DEBUG(D_any) debug_printf("rejecting SMTP connection: too many from this "
+    DEBUG(any) debug_printf("rejecting SMTP connection: too many from this "
       "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", SP_NO_MORE);
-    log_write(L_connection_reject,
-              LOG_MAIN, "Connection from %Y refused: too many connections "
-      "from that IP address", whofrom);
+    if (LOGGING(connection_reject))
+      log_write(LOG_MAIN, "Connection from %Y refused: too many connections "
+	      "from that IP address", whofrom);
     search_tidyup();
     goto ERROR_RETURN;
     }
@@ -363,17 +363,15 @@ pid = exim_fork(US"daemon-accept");
 
 if (pid == 0)
   {
-  int queue_only_reason = 0;
-  int old_pool = store_pool;
-  int save_debug_selector = debug_selector;
-  BOOL local_queue_only;
-  BOOL session_local_queue_only;
+  int queue_only_reason = 0, old_pool = store_pool;
+  BOOL is_any_debug = FALSE, local_queue_only, session_local_queue_only;
 #ifdef SA_NOCLDWAIT
   struct sigaction act;
 #endif
 
   smtp_accept_count++;    /* So that it includes this process */
   set_connection_id();
+  memset(sender_host_cache, 0, sizeof(sender_host_cache));
 
   /* Log the connection if requested.
   In order to minimize the cost (because this is going to happen for every
@@ -389,29 +387,26 @@ if (pid == 0)
 
   if (LOGGING(smtp_connection))
     {
-    uschar * list = hosts_connection_nolog;
-    memset(sender_host_cache, 0, sizeof(sender_host_cache));
+    const uschar * list = hosts_connection_nolog;
+
     if (list && verify_check_host(&list) == OK)
-      save_log_selector &= ~L_smtp_connection;
-    else if (LOGGING(connection_id))
-      log_write(L_smtp_connection, LOG_MAIN, "SMTP connection from %Y "
-	"Ci=%s (TCP/IP connection count = %d)",
-	whofrom, connection_id, smtp_accept_count);
-    else
-      log_write(L_smtp_connection, LOG_MAIN, "SMTP connection from %Y "
-	"(TCP/IP connection count = %d)", whofrom, smtp_accept_count);
+      logging_modify_channels(US"-smtp_connection");
+    else if (LOGGING(smtp_connection))
+      if (LOGGING(connection_id))
+	log_write(LOG_MAIN, "SMTP connection from %Y "
+	  "Ci=%s (TCP/IP connection count = %d)",
+	  whofrom, connection_id, smtp_accept_count);
+      else
+	log_write(LOG_MAIN, "SMTP connection from %Y "
+	  "(TCP/IP connection count = %d)", whofrom, smtp_accept_count);
     }
 
   /* If the listen backlog was over the monitoring level, log it. */
 
   if (smtp_listen_backlog > smtp_backlog_monitor)
-    log_write(0, LOG_MAIN, "listen backlog %d I=[%s]:%d",
+    log_write(LOG_MAIN, "listen backlog %d I=[%s]:%d",
 		smtp_listen_backlog, interface_address, interface_port);
 
-  /* May have been modified for the subprocess */
-
-  *log_selector = save_log_selector;
-
   /* Get the local interface address into permanent store */
 
   store_pool = POOL_PERM;
@@ -435,7 +430,7 @@ if (pid == 0)
       {
       if (!f.expand_string_forcedfail)
         {
-        log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand %q "
+        log_write(LOG_MAIN|LOG_PANIC, "failed to expand %q "
           "(smtp_active_hostname): %s", raw_active_hostname,
           expand_string_message);
         smtp_printf("421 Local configuration error; "
@@ -488,19 +483,27 @@ if (pid == 0)
   finding the id, but turn it on again afterwards so that information about the
   incoming connection is output. */
 
-  if (f.debug_daemon) debug_selector = 0;
+  if (f.debug_daemon)
+    {
+    is_any_debug = !!ANY_DEBUG;
+    bit_clear(debug_selector, BIT_TABLE_IDX_NONZERO);
+    }
+
   verify_get_ident(IDENT_PORT);
   host_build_sender_fullhost();
-  debug_selector = save_debug_selector;
 
-  DEBUG(D_any)
-    debug_printf("Process %d is handling incoming connection from %s\n",
-      (int)getpid(), sender_fullhost);
+  if (f.debug_daemon && is_any_debug)
+    bit_set(debug_selector, BIT_TABLE_IDX_NONZERO);
+
+  DEBUG(any)
+    debug_printf("Process " PID_T_FMT " is handling incoming connection"
+      " from %s\n", getpid(), sender_fullhost);
 
   /* Now disable debugging permanently if it's required only for the daemon
   process. */
 
-  if (f.debug_daemon) debug_selector = 0;
+  if (f.debug_daemon && is_any_debug)
+    bit_clear(debug_selector, BIT_TABLE_IDX_NONZERO);
 
   /* If there are too many child processes for immediate delivery,
   set the session_local_queue_only flag, which is initialized from the
@@ -538,8 +541,8 @@ if (pid == 0)
     message_id[0] = 0;            /* Clear out any previous message_id */
     reset_point = store_mark();   /* Save current store high water point */
 
-    DEBUG(D_any)
-      debug_printf("Process %d is ready for new message\n", (int)getpid());
+    DEBUG(any)
+      debug_printf("Process " PID_T_FMT " is ready for new message\n", getpid());
 
     /* Smtp_setup_msg() returns 0 on QUIT or if the call is from an
     unacceptable host or if an ACL "drop" command was triggered, -1 on
@@ -568,12 +571,12 @@ if (pid == 0)
 
       /*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");
+      DEBUG(receive) debug_printf("SMTP>>(close on process exit)\n");
       exim_underbar_exit(rc ? EXIT_FAILURE : EXIT_SUCCESS);
       }
 
      {
-      BOOL ok = receive_msg(FALSE);
+      BOOL ok = receive_msg(FALSE, rf_notify_unset);
       search_tidyup();                    /* Close cached databases */
       if (!ok)                            /* Connection was dropped */
         {
@@ -587,7 +590,7 @@ if (pid == 0)
 
     /* Show the recipients when debugging */
 
-    DEBUG(D_receive)
+    DEBUG(receive)
       {
       if (sender_address)
         debug_printf("Sender: %s\n", sender_address);
@@ -656,20 +659,20 @@ if (pid == 0)
     /* Log the queueing here, when it will get a message id attached, but
     not if queue_only is set (case 0). */
 
-    if (local_queue_only) switch(queue_only_reason)
+    if (LOGGING(delay_delivery) && local_queue_only) switch(queue_only_reason)
       {
-      case 1: log_write(L_delay_delivery,
-                LOG_MAIN, "no immediate delivery: too many connections "
-                "(%d, max %d)", smtp_accept_count, smtp_accept_queue);
+      case 1: log_write(LOG_MAIN,
+		"no immediate delivery: too many connections (%d, max %d)",
+		smtp_accept_count, smtp_accept_queue);
 	      break;
 
-      case 2: log_write(L_delay_delivery,
-                LOG_MAIN, "no immediate delivery: more than %d messages "
+      case 2: log_write(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",
+      case 3: log_write(LOG_MAIN,
+		"no immediate delivery: load average %.2f",
                 (double)load_average/1000.0);
 	      break;
       }
@@ -709,11 +712,8 @@ if (pid == 0)
         signal(SIGINT, SIG_DFL);
 
         if (geteuid() != root_uid && !deliver_drop_privilege)
-          {
-          signal(SIGALRM, SIG_DFL);
 	  delivery_re_exec(CEE_EXEC_PANIC);
           /* Control does not return here. */
-          }
 
         /* No need to re-exec; SIGALRM remains set to the default handler */
 
@@ -725,12 +725,12 @@ if (pid == 0)
       if (dpid > 0)
         {
 	release_cutthrough_connection(US"passed for delivery");
-        DEBUG(D_any) debug_printf("forked delivery process %d\n", (int)dpid);
+        DEBUG(any) debug_printf("forked delivery process " PID_T_FMT "\n", dpid);
         }
       else
 	{
 	cancel_cutthrough_connection(TRUE, US"delivery fork failed");
-        log_write(0, LOG_MAIN|LOG_PANIC, "daemon: delivery process fork "
+        log_write(LOG_MAIN|LOG_PANIC, "daemon: delivery process fork "
           "failed: %s", strerror(errno));
 	}
       }
@@ -754,9 +754,11 @@ else if (smtp_slots)
       if (smtp_accept_max_per_host)
         smtp_slots[i].host_address = string_copy_malloc(sender_host_address);
       smtp_accept_count++;
+      set_process_info("daemon(%s): [%d+%d] %s", version_string,
+		      smtp_accept_count, queue_run_count, daemon_process_info);
       break;
       }
-  DEBUG(D_any) debug_printf("%d SMTP accept process%s running\n",
+  DEBUG(any) debug_printf("%d SMTP accept process%s running\n",
     smtp_accept_count, smtp_accept_count == 1 ? "" : "es");
   }
 
@@ -775,7 +777,7 @@ descriptors are closed, in order to drop the connection. */
 if (smtp_out_fd >= 0)
   {
   if (close(smtp_out_fd) != 0 && errno != ECONNRESET && errno != EPIPE)
-    log_write(0, LOG_MAIN|LOG_PANIC, "daemon: close(smtp_out_fd) failed: %s",
+    log_write(LOG_MAIN|LOG_PANIC, "daemon: close(smtp_out_fd) failed: %s",
       strerror(errno));
   smtp_out_fd = -1;
   }
@@ -783,7 +785,7 @@ if (smtp_out_fd >= 0)
 if (smtp_in_fd >= 0)
   {
   if (close(smtp_in_fd) != 0 && errno != ECONNRESET && errno != EPIPE)
-    log_write(0, LOG_MAIN|LOG_PANIC, "daemon: close(smtp_in_fd) failed: %s",
+    log_write(LOG_MAIN|LOG_PANIC, "daemon: close(smtp_in_fd) failed: %s",
       strerror(errno));
   smtp_in_fd = -1;
   }
@@ -884,7 +886,7 @@ pid_t pid;
 
 while ((pid = waitpid(-1, &status, WNOHANG)) > 0)
   {
-  DEBUG(D_any)
+  DEBUG(any)
     {
     debug_printf("child %ld ended: status=0x%x\n", (long)pid, status);
 #ifdef WCOREDUMP
@@ -910,8 +912,10 @@ while ((pid = waitpid(-1, &status, WNOHANG)) > 0)
           store_free(sp->host_address);
         *sp = empty_smtp_slot;
         if (--smtp_accept_count < 0) smtp_accept_count = 0;
-        DEBUG(D_any) debug_printf("%d SMTP accept process%s now running\n",
+        DEBUG(any) debug_printf("%d SMTP accept process%s now running\n",
           smtp_accept_count, smtp_accept_count == 1 ? "" : "es");
+	set_process_info("daemon(%s): [%d+%d] %s", version_string,
+		      smtp_accept_count, queue_run_count, daemon_process_info);
         break;
         }
     if (i < smtp_accept_max) continue;  /* Found an accepting process */
@@ -929,8 +933,10 @@ while ((pid = waitpid(-1, &status, WNOHANG)) > 0)
         r->pid = 0;			/* free up the slot */
 
         if (--queue_run_count < 0) queue_run_count = 0;
-        DEBUG(D_any) debug_printf("%d queue-runner process%s now running\n",
+        DEBUG(any) debug_printf("%d queue-runner process%s now running\n",
           queue_run_count, queue_run_count == 1 ? "" : "es");
+	set_process_info("daemon(%s): [%d+%d] %s", version_string,
+		      smtp_accept_count, queue_run_count, daemon_process_info);
 
 	for (qrunner ** p = &qrunners, * q = qrunners; q; p = &q->next, q = *p)
 	  if (q->name == r->queue_name)
@@ -958,7 +964,7 @@ if (!*pid_file_path)
   pid_file_path = string_sprintf("%s/exim-daemon.pid", spool_directory);
 
 if (pid_file_path[0] != '/')
-  log_write_die(0, LOG_PANIC_DIE,
+  log_write_die(LOG_PANIC_DIE,
 		"pid file path %s must be absolute\n", pid_file_path);
 }
 
@@ -975,7 +981,7 @@ static BOOL
 operate_on_pid_file(const enum pid_op operation, const pid_t pid)
 {
 char pid_line[sizeof(int) * 3 + 2];
-const int pid_len = snprintf(pid_line, sizeof(pid_line), "%ld\n", (long)pid);
+const int pid_len = snprintf(pid_line, sizeof(pid_line), PID_T_FMT "\n", pid);
 BOOL lines_match = FALSE;
 uschar * path, * base, * dir;
 
@@ -993,7 +999,7 @@ if (pid_len < 2 || pid_len >= (int)sizeof(pid_line)) goto cleanup;
 
 path = string_copy(pid_file_path);
 if ((base = Ustrrchr(path, '/')) == NULL)	/* should not happen, but who knows */
-  log_write_die(0, LOG_MAIN, "pid file path %q does not contain a '/'", pid_file_path);
+  log_write_die(LOG_MAIN, "pid file path %q does not contain a '/'", pid_file_path);
 
 dir = base != path ? path : US"/";
 *base++ = '\0';
@@ -1010,7 +1016,7 @@ if (dir_fd < 0 || fstat(dir_fd, &sb) != 0 || !S_ISDIR(sb.st_mode)) goto cleanup;
 if (fchdir(dir_fd) != 0) goto cleanup;
 base_fd = open(CS base, O_RDONLY | base_flags);
 if (fchdir(cwd_fd) != 0)
-  log_write_die(0, LOG_MAIN, "can't return to previous working dir: %s", strerror(errno));
+  log_write_die(LOG_MAIN, "can't return to previous working dir: %s", strerror(errno));
 
 if (base_fd >= 0)
   {
@@ -1041,7 +1047,7 @@ if (operation == PID_WRITE)
       if (fchdir(dir_fd) != 0) goto cleanup;
       error = unlink(CS base);
       if (fchdir(cwd_fd) != 0)
-        log_write_die(0, LOG_MAIN, "can't return to previous working dir: %s", strerror(errno));
+        log_write_die(LOG_MAIN, "can't return to previous working dir: %s", strerror(errno));
       if (error) goto cleanup;
       (void)close(base_fd);
       base_fd = -1;
@@ -1050,11 +1056,11 @@ if (operation == PID_WRITE)
     if (fchdir(dir_fd) != 0) goto cleanup;
     base_fd = open(CS base, O_WRONLY | O_CREAT | O_EXCL | base_flags, base_mode);
     if (fchdir(cwd_fd) != 0)
-        log_write_die(0, LOG_MAIN, "can't return to previous working dir: %s", strerror(errno));
+        log_write_die(LOG_MAIN, "can't return to previous working dir: %s", strerror(errno));
     if (base_fd < 0) goto cleanup;
     if (fchmod(base_fd, base_mode) != 0) goto cleanup;
     if (write(base_fd, pid_line, pid_len) != pid_len) goto cleanup;
-    DEBUG(D_any) debug_printf("pid written to %s\n", pid_file_path);
+    DEBUG(any) debug_printf("pid written to %s\n", pid_file_path);
     }
   }
 else
@@ -1067,7 +1073,7 @@ else
     if (fchdir(dir_fd) != 0) goto cleanup;
     error = unlink(CS base);
     if (fchdir(cwd_fd) != 0)
-        log_write_die(0, LOG_MAIN, "can't return to previous working dir: %s", strerror(errno));
+        log_write_die(LOG_MAIN, "can't return to previous working dir: %s", strerror(errno));
     if (error) goto cleanup;
     }
   }
@@ -1091,7 +1097,7 @@ delete_pid_file(void)
 {
 const BOOL success = operate_on_pid_file(PID_DELETE, getppid());
 
-DEBUG(D_any)
+DEBUG(any)
   debug_printf("delete pid file %s %s: %s\n", pid_file_path,
     success ? "success" : "failure", strerror(errno));
 
@@ -1107,7 +1113,7 @@ daemon_die(void)
 {
 pid_t pid;
 
-DEBUG(D_any) debug_printf("SIGTERM/SIGINT seen\n");
+DEBUG(any) debug_printf("SIGTERM/SIGINT seen\n");
 #if !defined(DISABLE_TLS) && (defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT))
 tls_watch_invalidate();
 #endif
@@ -1148,9 +1154,10 @@ daemon_client_sockname(struct sockaddr_un * sup, uschar ** sname)
 #ifdef EXIM_HAVE_ABSTRACT_UNIX_SOCKETS
 sup->sun_path[0] = 0;  /* Abstract local socket addr - Linux-specific? */
 return offsetof(struct sockaddr_un, sun_path) + 1
-  + snprintf(sup->sun_path+1, sizeof(sup->sun_path)-1, "exim_%d", getpid());
+  + snprintf(sup->sun_path+1, sizeof(sup->sun_path)-1,
+	      "exim_" PID_T_FMT, getpid());
 #else
-*sname = string_sprintf("%s/p_%d", spool_directory, getpid());
+*sname = string_sprintf("%s/p_" PID_T_FMT, spool_directory, getpid());
 return offsetof(struct sockaddr_un, sun_path)
   + snprintf(sup->sun_path, sizeof(sup->sun_path), "%s", CS *sname);
 #endif
@@ -1184,22 +1191,22 @@ ssize_t len;
 
 if (!f.notifier_socket_en)
   {
-  DEBUG(D_any) debug_printf("-oY used so not creating notifier socket\n");
+  DEBUG(any) debug_printf("-oY used so not creating notifier socket\n");
   return;
   }
 if (override_local_interfaces && !override_pid_file_path)
   {
-  DEBUG(D_any)
+  DEBUG(any)
     debug_printf("-oX used without -oP so not creating notifier socket\n");
   return;
   }
 if (!notifier_socket || !*notifier_socket)
   {
-  DEBUG(D_any) debug_printf("no name for notifier socket\n");
+  DEBUG(any) debug_printf("no name for notifier socket\n");
   return;
   }
 
-DEBUG(D_any) debug_printf("creating notifier socket\n");
+DEBUG(any) debug_printf("creating notifier socket\n");
 
 #ifdef SOCK_CLOEXEC
 if ((fd = socket(PF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0)) < 0)
@@ -1213,9 +1220,9 @@ if ((fd = socket(PF_UNIX, SOCK_DGRAM, 0)) < 0)
 len = daemon_notifier_sockname(&sa_un);
 
 #ifdef EXIM_HAVE_ABSTRACT_UNIX_SOCKETS
-DEBUG(D_any) debug_printf(" @%s\n", sa_un.sun_path+1);
+DEBUG(any) debug_printf(" @%s\n", sa_un.sun_path+1);
 #else			/* filesystem-visible and persistent; will neeed removal */
-DEBUG(D_any) debug_printf(" %s\n", sa_un.sun_path);
+DEBUG(any) debug_printf(" %s\n", sa_un.sun_path);
 #endif
 
 if (bind(fd, (const struct sockaddr *)&sa_un, (socklen_t)len) < 0)
@@ -1238,7 +1245,7 @@ bad2:
   Uunlink(sa_un.sun_path);
 #endif
 bad:
-  log_write(0, LOG_MAIN|LOG_PANIC, "%s %s: %s",
+  log_write(LOG_MAIN|LOG_PANIC, "%s %s: %s",
     __FUNCTION__, where, strerror(errno));
   close(fd);
   return;
@@ -1277,7 +1284,7 @@ if (sz >= sizeof(buf)) return;
 #ifdef notdef
 debug_printf("addrlen %d\n", msg.msg_namelen);
 #endif
-DEBUG(D_queue_run)
+DEBUG(queue_run)
   if (msg.msg_namelen > 0)
     {
     BOOL abstract = !*sa_un.sun_path;
@@ -1313,14 +1320,14 @@ for (struct cmsghdr * cp = CMSG_FIRSTHDR(&msg);
   struct ucred * cr = (struct ucred *) CMSG_DATA(cp);
   if (cr->uid && cr->uid != exim_uid)
     {
-    DEBUG(D_queue_run) debug_printf("%s: sender creds pid %ld uid %d gid %d\n",
+    DEBUG(queue_run) debug_printf("%s: sender creds pid %ld uid %d gid %d\n",
       __FUNCTION__, (long)cr->pid, (int)cr->uid, (int)cr->gid);
     }
 # elif defined(LOCAL_CREDS)				/* BSD-ish */
   struct sockcred * cr = (struct sockcred *) CMSG_DATA(cp);
   if (cr->sc_uid && cr->sc_uid != exim_uid)
     {
-    DEBUG(D_queue_run) debug_printf("%s: sender creds pid ??? uid %d gid %d\n",
+    DEBUG(queue_run) debug_printf("%s: sender creds pid ??? uid %d gid %d\n",
       __FUNCTION__, (int)cr->sc_uid, (int)cr->sc_gid);
     }
 # endif
@@ -1334,7 +1341,7 @@ switch (buf[0])
 #ifndef DISABLE_QUEUE_RAMP
   case NOTIFY_MSG_QRUN:
     /* this should be a message_id */
-    DEBUG(D_queue_run)
+    DEBUG(queue_run)
       debug_printf("%s: qrunner trigger: %s\n", __FUNCTION__, buf+1);
 
     memcpy(queuerun_msgid, buf+1, MESSAGE_ID_LENGTH+1);
@@ -1353,12 +1360,12 @@ switch (buf[0])
     uschar qsbuf[16];
     int len = snprintf(CS qsbuf, sizeof(qsbuf), "%u", queue_count_cached());
 
-    DEBUG(D_queue_run)
+    DEBUG(queue_run)
       debug_printf("%s: queue size request: %s\n", __FUNCTION__, qsbuf);
 
     if (sendto(daemon_notifier_fd, qsbuf, len, 0,
 		(const struct sockaddr *)&sa_un, msg.msg_namelen) < 0)
-      log_write(0, LOG_MAIN|LOG_PANIC,
+      log_write(LOG_MAIN|LOG_PANIC,
 	"%s: sendto: %s\n", __FUNCTION__, strerror(errno));
     break;
     }
@@ -1379,7 +1386,7 @@ time_t resignal_interval = inetd_wait_timeout;
 
 if (last_connection_time == (time_t)0)
   {
-  DEBUG(D_any)
+  DEBUG(any)
     debug_printf("inetd wait timeout expired, but still not seen first message, ignoring\n");
   }
 else
@@ -1387,14 +1394,14 @@ else
   time_t now = time(NULL);
   if (now == (time_t)-1)
     {
-    DEBUG(D_any) debug_printf("failed to get time: %s\n", strerror(errno));
+    DEBUG(any) debug_printf("failed to get time: %s\n", strerror(errno));
     }
   else if ((now - last_connection_time) >= inetd_wait_timeout)
     {
-    DEBUG(D_any)
+    DEBUG(any)
       debug_printf("inetd wait timeout %d expired, ending daemon\n",
 	  inetd_wait_timeout);
-    log_write(0, LOG_MAIN, "exim %s daemon terminating, inetd wait timeout reached.\n",
+    log_write(LOG_MAIN, "exim %s daemon terminating, inetd wait timeout reached.\n",
 	version_string);
     daemon_die();		/* Does not return */
     }
@@ -1455,7 +1462,7 @@ Return the number of seconds until the next due runner.
 static int
 daemon_qrun(int local_queue_run_max, struct pollfd * fd_polls, int listen_socket_count)
 {
-DEBUG(D_any) debug_printf("%s received\n",
+DEBUG(any) debug_printf("%s received\n",
 #ifndef DISABLE_QUEUE_RAMP
   *queuerun_msgid ? "qrun notification" :
 #endif
@@ -1509,7 +1516,8 @@ if (is_multiple_qrun())				/* we are managing periodic runs */
 	leave the above message, because it ties up with the "child ended"
 	debugging messages. */
 
-	if (f.debug_daemon) debug_selector = 0;
+	if (f.debug_daemon) debug_modify_channel(US"=0");
+;
 
 	/* Close any open listening sockets in the child */
 
@@ -1555,7 +1563,7 @@ if (is_multiple_qrun())				/* we are managing periodic runs */
 #ifndef DISABLE_QUEUE_RAMP
 	  if (*queuerun_msgid)
 	    {
-	    log_write(0, LOG_MAIN, "notify triggered queue run");
+	    log_write(LOG_MAIN, "notify triggered queue run");
 	    extra[extracount++] = queuerun_msgid;	/* Trigger only the */
 	    extra[extracount++] = queuerun_msgid;	/* one message      */
 	    }
@@ -1590,7 +1598,7 @@ if (is_multiple_qrun())				/* we are managing periodic runs */
 #ifndef DISABLE_QUEUE_RAMP
 	if (*queuerun_msgid)
 	  {
-	  log_write(0, LOG_MAIN, "notify triggered queue run");
+	  log_write(LOG_MAIN, "notify triggered queue run");
 	  f.queue_2stage = FALSE;
 	  queue_run(q, queuerun_msgid, queuerun_msgid, FALSE);
 	  }
@@ -1602,7 +1610,7 @@ if (is_multiple_qrun())				/* we are managing periodic runs */
 
       if (pid < 0)
 	{
-	log_write(0, LOG_MAIN|LOG_PANIC, "daemon: fork of queue-runner "
+	log_write(LOG_MAIN|LOG_PANIC, "daemon: fork of queue-runner "
 	  "process failed: %s", strerror(errno));
 	log_close_all();
 	}
@@ -1617,8 +1625,10 @@ if (is_multiple_qrun())				/* we are managing periodic runs */
 	    queue_run_count++;
 	    break;
 	    }
-	DEBUG(D_any) debug_printf("%d queue-runner process%s running\n",
+	DEBUG(any) debug_printf("%d queue-runner process%s running\n",
 	  queue_run_count, queue_run_count == 1 ? "" : "es");
+	set_process_info("daemon(%s): [%d+%d] %s", version_string,
+		      smtp_accept_count, queue_run_count, daemon_process_info);
 	}
       }
     }
@@ -1670,6 +1680,21 @@ return string_from_gstring(g);
 }
 
 
+
+static void
+daemon_preload_modules(void)
+{
+#ifdef LOOKUP_MODULE_DIR
+uschar namebuf[32];
+const uschar * list = daemon_modules_load, * ele;
+int sep = 0;
+
+while (ele = string_nextinlist(&list, &sep, namebuf, sizeof(namebuf)))
+  mod_load_anyclass(ele);
+#endif
+}
+
+
 /*************************************************
 *              Exim Daemon Mainline              *
 *************************************************/
@@ -1734,7 +1759,11 @@ process_purpose = US"daemon";
 /* 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;
+DEBUG(any|v) debug_modify_channel(US"+pid");
+
+/* Get any requested dynamic-load modules loaded */
+
+daemon_preload_modules();
 
 /* Allocate enough pollstructs for inetd mode plus the ancillary sockets;
 also used when there are no listen sockets. */
@@ -1746,7 +1775,7 @@ if (f.inetd_wait_mode)
   listen_socket_count = 1;
   (void) close(3);
   if (dup2(0, 3) == -1)
-    log_write_die(0, LOG_MAIN,
+    log_write_die(LOG_MAIN,
         "failed to dup inetd socket safely away: %s", strerror(errno));
 
   fd_polls[0].fd = 3;
@@ -1756,26 +1785,24 @@ if (f.inetd_wait_mode)
   (void) close(2);
   exim_nullstd();
 
+  /* If we want debug, it must go to a file in the log directory */
+
   if (debug_file == stderr)
     {
-    /* need a call to log_write before call to open debug_file, so that
-    log.c:file_path has been initialised.  This is unfortunate. */
-    log_write(0, LOG_MAIN, "debugging Exim in inetd wait mode starting");
-
     fclose(debug_file);
     debug_file = NULL;
     exim_nullstd(); /* re-open fd2 after we just closed it again */
     debug_logging_activate(US"-wait", NULL);
     }
 
-  DEBUG(D_any) debug_printf("running in inetd wait mode\n");
+  DEBUG(any) debug_printf("running in inetd wait mode\n");
 
   /* 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)
     if (setsockopt(3, IPPROTO_TCP, TCP_NODELAY, US &on, sizeof(on)))
-      log_write_die(0, LOG_MAIN, "failed to set socket NODELAY: %s",
+      log_write_die(LOG_MAIN, "failed to set socket NODELAY: %s",
 	strerror(errno));
   }
 
@@ -1907,7 +1934,7 @@ if (f.daemon_listen && !f.inetd_wait_mode)
     if (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",
+      DEBUG(any) debug_printf("daemon_smtp_port overridden by -oX:\n  %s\n",
         daemon_smtp_port);
       }
 
@@ -1915,7 +1942,7 @@ if (f.daemon_listen && !f.inetd_wait_mode)
       {
       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",
+      DEBUG(any) debug_printf("local_interfaces overridden by -oX:\n  %s\n",
         local_interfaces);
       }
     }
@@ -1936,13 +1963,13 @@ if (f.daemon_listen && !f.inetd_wait_mode)
       uschar * end;
       default_smtp_port[pct] = Ustrtol(s, &end, 0);
       if (*end)
-        log_write_die(0, LOG_CONFIG, "invalid SMTP port: %s", s);
+        log_write_die(LOG_CONFIG, "invalid SMTP port: %s", s);
       }
     else
       {
       struct servent * smtp_service = getservbyname(CS s, "tcp");
       if (!smtp_service)
-        log_write_die(0, LOG_CONFIG, "TCP port %q not found", s);
+        log_write_die(LOG_CONFIG, "TCP port %q not found", s);
       default_smtp_port[pct] = ntohs(smtp_service->s_port);
       }
   default_smtp_port[pct] = 0;
@@ -1968,7 +1995,7 @@ if (f.daemon_listen && !f.inetd_wait_mode)
 	  {
 	  struct servent * smtp_service = getservbyname(CS s, "tcp");
 	  if (!smtp_service)
-	    log_write_die(0, LOG_CONFIG, "TCP port %q not found", s);
+	    log_write_die(LOG_CONFIG, "TCP port %q not found", s);
 	  g = string_append_listele_fmt(g, ':', FALSE, "%d",
 					      (int)ntohs(smtp_service->s_port));
 	  }
@@ -2007,7 +2034,7 @@ if (f.daemon_listen && !f.inetd_wait_mode)
     if (ipa->port > 0) continue;
 
     if (!*daemon_smtp_port)
-      log_write_die(0, LOG_MAIN, "no port specified for interface "
+      log_write_die(LOG_MAIN, "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);
@@ -2146,7 +2173,7 @@ if (f.background_daemon)
     {
     BOOL daemon_listen = f.daemon_listen;
     pid_t pid = exim_fork(US"daemon");
-    if (pid < 0) log_write_die(0, LOG_MAIN,
+    if (pid < 0) log_write_die(LOG_MAIN,
       "fork() failed when starting daemon: %s", strerror(errno));
     if (pid > 0) exim_exit(EXIT_SUCCESS); /* in parent process, just exit */
     (void)setsid();                       /* release controlling terminal */
@@ -2191,11 +2218,11 @@ if (f.daemon_listen && !f.inetd_wait_mode)
       {
       if (check_special_case(0, addresses, ipa, FALSE))
         {
-        log_write(0, LOG_MAIN, "Failed to create IPv6 socket for wildcard "
+        log_write(LOG_MAIN, "Failed to create IPv6 socket for wildcard "
           "listening (%s): will use IPv4", strerror(errno));
         goto SKIP_SOCKET;
         }
-      log_write_die(0, LOG_PANIC_DIE, "IPv%c socket creation failed: %s",
+      log_write_die(LOG_PANIC_DIE, "IPv%c socket creation failed: %s",
         af == AF_INET6 ? '6' : '4', strerror(errno));
       }
 
@@ -2206,7 +2233,7 @@ if (f.daemon_listen && !f.inetd_wait_mode)
 #ifdef IPV6_V6ONLY
     if (af == AF_INET6 && wildcard &&
         setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)) < 0)
-      log_write(0, LOG_MAIN, "Setting IPV6_V6ONLY on daemon's IPv6 wildcard "
+      log_write(LOG_MAIN, "Setting IPV6_V6ONLY on daemon's IPv6 wildcard "
         "socket failed (%s): carrying on without it", strerror(errno));
 #endif  /* IPV6_V6ONLY */
 
@@ -2215,7 +2242,7 @@ if (f.daemon_listen && !f.inetd_wait_mode)
     smtp port for listening. */
 
     if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
-      log_write_die(0, LOG_MAIN, "setting SO_REUSEADDR on socket "
+      log_write_die(LOG_MAIN, "setting SO_REUSEADDR on socket "
         "failed when starting daemon: %s", strerror(errno));
 
     /* Set TCP_NODELAY; Exim does its own buffering. There is a switch to
@@ -2242,7 +2269,7 @@ if (f.daemon_listen && !f.inetd_wait_mode)
       if (ip_bind(fd, af, ipa->address, ipa->port) >= 0) break;
       if (check_special_case(errno, addresses, ipa, TRUE))
         {
-        DEBUG(D_any) debug_printf("wildcard IPv4 bind() failed after IPv6 "
+        DEBUG(any) debug_printf("wildcard IPv4 bind() failed after IPv6 "
           "listen() success; EADDRINUSE ignored\n");
         (void)close(fd);
         goto SKIP_SOCKET;
@@ -2254,10 +2281,10 @@ if (f.daemon_listen && !f.inetd_wait_mode)
 	: US"(any IPv4)"
 	: ipa->address;
       if (daemon_startup_retries <= 0)
-        log_write_die(0, LOG_MAIN,
+        log_write_die(LOG_MAIN,
           "socket bind() to port %d for address %s failed: %s: "
           "daemon abandoned", ipa->port, addr, msg);
-      log_write(0, LOG_MAIN, "socket bind() to port %d for address %s "
+      log_write(LOG_MAIN, "socket bind() to port %d for address %s "
         "failed: %s: waiting %s before trying again (%d more %s)",
         ipa->port, addr, msg, readconf_printtime(daemon_startup_sleep),
         daemon_startup_retries, (daemon_startup_retries > 1)? "tries" : "try");
@@ -2265,7 +2292,7 @@ if (f.daemon_listen && !f.inetd_wait_mode)
       sleep(daemon_startup_sleep);
       }
 
-    DEBUG(D_any)
+    DEBUG(any)
       if (wildcard)
         debug_printf("listening on all interfaces (IPv%c) port %d\n",
           af == AF_INET6 ? '6' : '4', ipa->port);
@@ -2281,7 +2308,7 @@ if (f.daemon_listen && !f.inetd_wait_mode)
        && setsockopt(fd, IPPROTO_TCP, TCP_FASTOPEN,
 		    &smtp_connect_backlog, sizeof(smtp_connect_backlog)))
       {
-      DEBUG(D_any) debug_printf("setsockopt FASTOPEN: %s\n", strerror(errno));
+      DEBUG(any) debug_printf("setsockopt FASTOPEN: %s\n", strerror(errno));
       f.tcp_fastopen_ok = FALSE;
       }
 #endif
@@ -2291,7 +2318,7 @@ if (f.daemon_listen && !f.inetd_wait_mode)
       if (  f.tcp_fastopen_ok
 	 && setsockopt(fd, IPPROTO_TCP, TCP_FASTOPEN, &on, sizeof(on)))
 	{
-	DEBUG(D_any) debug_printf("setsockopt FASTOPEN: %s\n", strerror(errno));
+	DEBUG(any) debug_printf("setsockopt FASTOPEN: %s\n", strerror(errno));
 	f.tcp_fastopen_ok = FALSE;
 	}
 #endif
@@ -2306,12 +2333,12 @@ if (f.daemon_listen && !f.inetd_wait_mode)
     where the IPv6 socket accepts both kinds of call. */
 
     if (!check_special_case(errno, addresses, ipa, TRUE))
-      log_write_die(0, LOG_PANIC_DIE, "listen() failed on interface %s: %s",
+      log_write_die(LOG_PANIC_DIE, "listen() failed on interface %s: %s",
         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 "
+    DEBUG(any) debug_printf("wildcard IPv4 listen() failed after IPv6 "
       "listen() success; EADDRINUSE ignored\n");
     (void)close(fd);
 
@@ -2358,7 +2385,9 @@ if (f.running_in_test_harness || write_pid)
      || real_uid == root_uid
      || (real_uid == exim_uid && !override_pid_file_path)) ? PID_WRITE : PID_CHECK;
   if (!operate_on_pid_file(operation, getpid()))
-    DEBUG(D_any) debug_printf("%s pid file %s: %s\n", (operation == PID_WRITE) ? "write" : "check", pid_file_path, strerror(errno));
+    DEBUG(any) debug_printf("%s pid file %s: %s\n",
+			      operation == PID_WRITE ? "write" : "check",
+			      pid_file_path, strerror(errno));
   }
 
 /* Set up the handler for SIGHUP, which causes a restart of the daemon. */
@@ -2415,17 +2444,17 @@ must be set up. */
 
 if (f.inetd_wait_mode)
   {
-  uschar *p = big_buffer;
+  uschar * p = big_buffer;
 
   if (inetd_wait_timeout >= 0)
     sprintf(CS p, "terminating after %d seconds", inetd_wait_timeout);
   else
     sprintf(CS p, "with no wait timeout");
 
-  log_write(0, LOG_MAIN,
-    "exim %s daemon started: pid=%ld, launched with listening socket, %s",
+  log_write(LOG_MAIN, "exim %s daemon started: pid=" PID_T_FMT
+			  ", launched with listening socket, %s",
     version_string, getpid(), big_buffer);
-  set_process_info("daemon(%s): pre-listening socket", version_string);
+  daemon_process_info = US"pre-listening socket";
 
   /* set up the timeout logic */
   sigalrm_seen = TRUE;
@@ -2447,6 +2476,7 @@ else if (f.daemon_listen)
   listings separate. */
 
   for (int j = 0, i; j < 2; j++)
+    {
     for (i = 0, ipa = addresses; i < 10 && ipa; i++, ipa = ipa->next)
       {
       /* First time round, look for SMTP ports; second time round, look for
@@ -2479,6 +2509,9 @@ else if (f.daemon_listen)
 	else				/* check for previously-seen IP */
 	  {
 	  ip_address_item * i2;
+
+	  /* Look for same-IP combinations of ports */
+
 	  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
@@ -2501,6 +2534,39 @@ else if (f.daemon_listen)
 	}
       }
 
+    /* Now look for same-port (or portlist) combinations of IPs */
+
+    for (i = 0, ipa = addresses; i < 10 && ipa; i++, ipa = ipa->next)
+      if (host_is_tls_on_connect_port(ipa->port) == (j > 0))
+	{
+	const uschar * portlist, * i2_plist;
+
+	if (ipa->log && (portlist = Ustrrchr(ipa->log, ':')))
+	  {
+	  const uschar * iplist = ipa->log + 1;	/* skip leading space */
+	  int iplen = portlist - iplist;
+
+	  for (ip_address_item * i2 = addresses; i2 != ipa; i2 = i2->next)
+	    if (  host_is_tls_on_connect_port(i2->port) == (j > 0)
+	       && i2->log
+	       && (i2_plist = Ustrrchr(i2->log, ':'))
+	       && Ustrcmp(portlist, i2_plist) == 0
+	       )
+	      {
+	      BOOL is_list = i2->log[1] == '{';		/*}*/
+	      const uschar * i2list = i2->log + (is_list ? 2 : 1);
+	      int i2len = i2_plist - i2list - (is_list ? 1 : 0);
+
+	      i2->log = string_sprintf(" {%.*s %.*s}%s",
+					  i2len, i2list, iplen, iplist,
+					  portlist);
+
+	      ipa->log = NULL;
+	      }
+	  }
+	}
+    }
+
   p = big_buffer;
   for (int j = 0, i; j < 2; j++)
     {
@@ -2528,22 +2594,25 @@ else if (f.daemon_listen)
       p += sprintf(CS p, " ...");
     }
 
-  log_write(0, LOG_MAIN,
-    "exim %s daemon started: pid=%ld, %s, listening for %s",
+  log_write(LOG_MAIN,
+    "exim %s daemon started: pid=" PID_T_FMT ", %s, listening for %s",
     version_string, getpid(), qinfo, big_buffer);
-  set_process_info("daemon(%s): %s, listening for %s",
-    version_string, qinfo, big_buffer);
+  daemon_process_info =
+    string_sprintf("%s, listening for %s", qinfo, big_buffer);
   }
 
 else	/* no listening sockets, only queue-runs */
   {
   const uschar * s = describe_queue_runners();
-  log_write(0, LOG_MAIN,
-    "exim %s daemon started: pid=%ld, %s, not listening for SMTP",
+  log_write(LOG_MAIN,
+    "exim %s daemon started: pid=" PID_T_FMT ", %s, not listening for SMTP",
     version_string, getpid(), s);
-  set_process_info("daemon(%s): %s, not listening", version_string, s);
+  daemon_process_info = string_sprintf("%s, not listening", s);
   }
 
+set_process_info("daemon(%s): [%d+%d] %s",
+      version_string, smtp_accept_count, queue_run_count, daemon_process_info);
+
 /* Do any work it might be useful to amortize over our children
 (eg: compile regex) */
 
@@ -2581,7 +2650,7 @@ closes the log afterwards, for the same reason. */
 
 log_close_all();
 
-DEBUG(D_any) debug_print_ids(US"daemon running with");
+DEBUG(any) debug_print_ids(US"daemon running with");
 
 /* Any messages accepted via this route are going to be SMTP. */
 
@@ -2628,7 +2697,7 @@ for (;;)
     int lcount;
     BOOL select_failed = FALSE;
 
-    DEBUG(D_any) debug_printf("Listening...\n");
+    DEBUG(any) debug_printf("Listening...\n");
 
     /* In rare cases we may have had a SIGCHLD signal in the time between
     setting the handler (below) and getting back here. If so, pretend that the
@@ -2723,7 +2792,7 @@ for (;;)
 	    if (  smtp_backlog_monitor > 0
 	       && getsockopt(p->fd, SOL_SOCKET, SO_LISTENQLEN, &backlog, &blen) == 0)
 	      {
-	      DEBUG(D_interface)
+	      DEBUG(interface)
 		debug_printf("listen fd %d queue curr %d\n", p->fd, backlog);
 	      smtp_listen_backlog = backlog;
 	      }
@@ -2738,7 +2807,7 @@ for (;;)
 	    if (  smtp_backlog_monitor > 0
 	       && getsockopt(p->fd, IPPROTO_TCP, TCP_INFO, &ti, &tlen) == 0)
 	      {
-	      DEBUG(D_interface) debug_printf("listen fd %d queue max %u curr %u\n",
+	      DEBUG(interface) debug_printf("listen fd %d queue max %u curr %u\n",
 		      p->fd, ti.tcpi_sacked, ti.tcpi_unacked);
 	      smtp_listen_backlog = ti.tcpi_unacked;
 	      }
@@ -2770,7 +2839,7 @@ for (;;)
 		|| select_failed != accept_retry_select_failed
 		|| accept_retry_count >= 50)
 	  {
-	  log_write(0, LOG_MAIN | (accept_retry_count >= 50 ? LOG_PANIC : 0),
+	  log_write(LOG_MAIN | (accept_retry_count >= 50 ? LOG_PANIC : 0),
 	    "%d %s() failure%s: %s",
 	    accept_retry_count,
 	    accept_retry_select_failed ? "select" : "accept",
@@ -2785,7 +2854,7 @@ for (;;)
         }
       else if (accept_retry_count > 0)
 	{
-	log_write(0, LOG_MAIN, "%d %s() failure%s: %s",
+	log_write(LOG_MAIN, "%d %s() failure%s: %s",
 	  accept_retry_count,
 	  accept_retry_select_failed ? "select" : "accept",
 	  accept_retry_count == 1 ? "" : "s",
@@ -2844,7 +2913,7 @@ for (;;)
 
   if (sighup_seen)
     {
-    log_write(0, LOG_MAIN, "pid %ld: SIGHUP received: re-exec daemon",
+    log_write(LOG_MAIN, "pid " PID_T_FMT ": SIGHUP received: re-exec daemon",
       getpid());
     close_daemon_sockets(daemon_notifier_fd, fd_polls, listen_socket_count);
     unlink_notifier_socket();
@@ -2853,7 +2922,7 @@ for (;;)
     sighup_argv[0] = exim_path;
     exim_nullstd();
     execv(CS exim_path, (char *const *)sighup_argv);
-    log_write_die(0, LOG_MAIN, "pid %ld: exec of %s failed: %s",
+    log_write_die(LOG_MAIN, "pid " PID_T_FMT ": exec of %s failed: %s",
       getpid(), exim_path, strerror(errno));
     /*NOTREACHED*/
     }
diff --git a/src/src/dane-openssl.c b/src/src/dane-openssl.c
index fb2527958..354bc7e90 100644
--- a/src/src/dane-openssl.c
+++ b/src/src/dane-openssl.c
@@ -909,7 +909,7 @@ if (gens)
 	continue;
       if (!(dane->mhost = OPENSSL_strdup(certid)))
 	matched = -1;
-      DEBUG(D_tls) debug_printf("Dane name_check: matched SAN %s\n", certid);
+      DEBUG(tls) debug_printf("Dane name_check: matched SAN %s\n", certid);
       break;
       }
     }
@@ -925,7 +925,7 @@ 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);
+    DEBUG(tls) debug_printf("Dane name_check: matched SN %s\n", certid);
     dane->mhost = OPENSSL_strdup(certid);
     }
   if (certid)
@@ -948,7 +948,7 @@ 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");
+DEBUG(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);
@@ -1008,7 +1008,7 @@ else
    */
   if (leaf_rrs)
     matched = match(leaf_rrs, xn, 0);
-  if (matched) DEBUG(D_tls) debug_printf("Dane verify_chain: matched EE\n");
+  if (matched) DEBUG(tls) debug_printf("Dane verify_chain: matched EE\n");
 
   if (!matched && issuer_rrs)
     for (n = chain_length-1; !matched && n >= 0; --n)
@@ -1017,7 +1017,7 @@ else
       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",
+  if (matched) DEBUG(tls) debug_printf("Dane verify_chain: matched %s\n",
     n>0 ? "CA" : "selfisssued EE");
 
   if (!matched)
@@ -1077,7 +1077,7 @@ 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");
+DEBUG(tls) debug_printf("Dane verify_cert\n");
 
 if (ssl_idx < 0)
   ssl_idx = SSL_get_ex_data_X509_STORE_CTX_idx();
@@ -1234,7 +1234,7 @@ DANESSL_cleanup(SSL *ssl)
 {
 ssl_dane *dane;
 
-DEBUG(D_tls) debug_printf("Dane lib-cleanup\n");
+DEBUG(tls) debug_printf("Dane lib-cleanup\n");
 
 if (dane_idx < 0 || !(dane = SSL_get_ex_data(ssl, dane_idx)))
   return;
@@ -1373,7 +1373,7 @@ 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 %q\n",
+DEBUG(tls) debug_printf("Dane add-tlsa: usage %u sel %u mdname %q\n",
 			  usage, selector, mdname);
 
 if(dane_idx < 0 || !(dane = SSL_get_ex_data(ssl, dane_idx)))
@@ -1560,7 +1560,7 @@ DANESSL_init(SSL *ssl, const char *sni_domain, const char **hostnames)
 {
 ssl_dane *dane;
 
-DEBUG(D_tls) debug_printf("Dane ssl_init\n");
+DEBUG(tls) debug_printf("Dane ssl_init\n");
 if (dane_idx < 0)
   {
   DANEerr(DANESSL_F_INIT, DANESSL_R_LIBRARY_INIT);
@@ -1632,7 +1632,7 @@ Return
 int
 DANESSL_CTX_init(SSL_CTX *ctx)
 {
-DEBUG(D_tls) debug_printf("Dane ctx-init\n");
+DEBUG(tls) debug_printf("Dane ctx-init\n");
 if (dane_idx >= 0)
   {
   SSL_CTX_set_cert_verify_callback(ctx, verify_cert, 0);
@@ -1723,7 +1723,7 @@ DANESSL_library_init(void)
 {
 static CRYPTO_ONCE once = CRYPTO_ONCE_STATIC_INIT;
 
-DEBUG(D_tls) debug_printf("Dane lib-init\n");
+DEBUG(tls) debug_printf("Dane lib-init\n");
 (void) CRYPTO_THREAD_run_once(&once, dane_init);
 
 #if defined(LN_sha256)
diff --git a/src/src/dbfn.c b/src/src/dbfn.c
index 7f6951ee3..0c50c03a6 100644
--- a/src/src/dbfn.c
+++ b/src/src/dbfn.c
@@ -17,25 +17,29 @@ with database files like $spooldirectory/db/<name> */
 
 
 /* Functions for accessing Exim's hints database, which consists of a number of
-different DBM files. This module does not contain code for reading DBM files
-for (e.g.) alias expansion. That is all contained within the general search
-functions. As Exim now has support for several DBM interfaces, all the relevant
+different DBM files.  Also used for the readonly access for dbm lookup
+expansions via the general search functions, for non-hints DBM file.
+
+As Exim now has support for several DBM interfaces, all the relevant
 functions are called as inlinable functions from an included file.
 
-All the data in Exim's database is in the nature of *hints*. Therefore it
+All the data in Exim's hists database is in the nature of *hints*. Therefore it
 doesn't matter if it gets destroyed by accident. These functions are not
 supposed to implement a "safe" database.
 
-Keys are passed in as C strings, and the terminating zero *is* used when
-building the dbm files. This just makes life easier when scanning the files
+For hints, keys are passed in as C strings - and the terminating zero *is* used
+when building the dbm files. This just makes life easier when scanning the files
 sequentially.
 
-Synchronization is required on the database files, and this is achieved by
-means of locking on independent lock files. (Earlier attempts to lock on the
-DBM files themselves were never completely successful.) Since callers may in
-general want to do more than one read or write while holding the lock, there
-are separate open and close functions. However, the calling modules should
-arrange to hold the locks for the bare minimum of time.
+For many of the DBM interfaces, synchronization is required on the database
+files; and this is achieved by means of locking on independent lock files.
+(Earlier attempts to lock on the DBM files themselves were never completely
+successful.) Since callers may in general want to do more than one read or write
+while holding the lock, there are separate open and close functions.
+However, the calling modules should arrange to hold the locks for the bare
+minimum of time.
+A predicate call is provided to tell if this locking is required, as opposed
+to being built-in to the DBM.
 
 API:
   exim_lockfile_needed			facilities predicate
@@ -62,6 +66,13 @@ Users:
   peer capability cache
   callout & quota cache
   DBM lookup type
+
+
+NOTE: the autoreply transport accesses a DBM database using
+exim_db{open,close,get,put} directly, not using this layer.
+
+The DBM interface is selected at build time from one of the
+files src/hintsdb/hints_*.h
 */
 
 
@@ -113,7 +124,7 @@ priv_restore();
 
 if (*fdp < 0)
   {
-  log_write(0, LOG_MAIN, "%s",
+  log_write(LOG_MAIN, "%s",
     string_open_failed("database lock file %s", filename));
   errno = 0;      /* Indicates locking failure */
   return FALSE;
@@ -125,7 +136,7 @@ lock that times out. */
 lock_data.l_type = rdonly ? 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)
+DEBUG(hints_lookup|retry|route|deliver)
   debug_printf_indent("locking %s\n", filename);
 
 sigalrm_seen = FALSE;
@@ -136,7 +147,7 @@ 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",
+  log_write(LOG_MAIN|LOG_PANIC, "Failed to get %s lock for %s: %s",
     rdonly ? "read" : "write", filename,
     errno == ETIMEDOUT ? "timed out" : strerror(errno));
   (void)close(*fdp); *fdp = -1;
@@ -144,7 +155,7 @@ if (rc < 0)
   return FALSE;
   }
 
-DEBUG(D_hints_lookup) debug_printf_indent("locked  %s\n", filename);
+DEBUG(hints_lookup) debug_printf_indent("locked  %s\n", filename);
 return TRUE;
 }
 
@@ -173,7 +184,7 @@ dbfn_open(const uschar * name, int flags, open_db * dbblock,
 int save_errno, dlen, flen;
 uschar dirname[PATHLEN], filename[PATHLEN];
 
-DEBUG(D_hints_lookup) acl_level++;
+DEBUG(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
@@ -202,7 +213,7 @@ else
 	    flen, name);
   if (!lockfile_take(dbblock, filename, flags == O_RDONLY, panic))
     {
-    DEBUG(D_hints_lookup) acl_level--;
+    DEBUG(hints_lookup) acl_level--;
     return NULL;
     }
   }
@@ -225,7 +236,7 @@ dbblock->dbptr = dbblock->readonly && !exim_lockfile_needed()
 
 if (!dbblock->dbptr && errno == ENOENT && flags & O_CREAT)
   {
-  DEBUG(D_hints_lookup)
+  DEBUG(hints_lookup)
     debug_printf_indent("%s appears not to exist: trying to create\n", filename);
   dbblock->dbptr = exim_dbopen(filename, dirname, flags, EXIMDB_MODE);
   }
@@ -240,10 +251,10 @@ if (!dbblock->dbptr)
   {
   errno = save_errno;
   if (lof && save_errno != ENOENT)
-    log_write(0, LOG_MAIN, "%s", string_open_failed("DB file %s",
+    log_write(LOG_MAIN, "%s", string_open_failed("DB file %s",
         filename));
   else
-    DEBUG(D_hints_lookup)
+    DEBUG(hints_lookup)
       debug_printf_indent("%s\n", CS string_open_failed("DB file %s",
           filename));
   (void)close(dbblock->lockfd);
@@ -254,7 +265,7 @@ if (!dbblock->dbptr)
 /* Pass back the block containing the opened database handle and the open fd
 for the lock. */
 
-DEBUG(D_hints_lookup) acl_level--;
+DEBUG(hints_lookup) acl_level--;
 return dbblock;
 }
 
@@ -270,7 +281,7 @@ dbfn_open_multi(const uschar * name, int flags, open_db * dbblock)
 int save_errno, dlen;
 uschar dirname[PATHLEN], filename[PATHLEN];
 
-DEBUG(D_hints_lookup) acl_level++;
+DEBUG(hints_lookup) acl_level++;
 
 dbblock->lockfd = -1;
 dbblock->readonly = (flags & O_ACCMODE) == O_RDONLY;
@@ -283,7 +294,7 @@ priv_drop_temp(exim_uid, exim_gid);
 dbblock->dbptr = exim_dbopen_multi(filename, dirname, flags & O_ACCMODE, EXIMDB_MODE);
 if (!dbblock->dbptr && errno == ENOENT && flags & O_CREAT)
   {
-  DEBUG(D_hints_lookup)
+  DEBUG(hints_lookup)
     debug_printf_indent("%s appears not to exist: trying to create\n", filename);
   dbblock->dbptr = exim_dbopen_multi(filename, dirname, flags, EXIMDB_MODE);
   }
@@ -298,17 +309,17 @@ if (!dbblock->dbptr)
   {
   errno = save_errno;
   if (save_errno != ENOENT)
-    log_write(0, LOG_MAIN, "%s", string_open_failed("DB file %s",
+    log_write(LOG_MAIN, "%s", string_open_failed("DB file %s",
         filename));
   else
-    DEBUG(D_hints_lookup)
+    DEBUG(hints_lookup)
       debug_printf_indent("%s\n", CS string_open_failed("DB file %s",
           filename));
   dbblock =  NULL;
   }
 
 /* Pass back the block containing the opened database handle */
-DEBUG(D_hints_lookup) acl_level--;
+DEBUG(hints_lookup) acl_level--;
 return dbblock;
 }
 
@@ -317,14 +328,14 @@ return dbblock;
 BOOL
 dbfn_transaction_start(open_db * dbp)
 {
-DEBUG(D_hints_lookup) debug_printf_indent("dbfn_transaction_start\n");
+DEBUG(hints_lookup) debug_printf_indent("dbfn_transaction_start\n");
 if (!dbp->readonly) return exim_dbtransaction_start(dbp->dbptr);
 return FALSE;
 }
 void
 dbfn_transaction_commit(open_db * dbp)
 {
-DEBUG(D_hints_lookup) debug_printf_indent("dbfn_transaction_commit\n");
+DEBUG(hints_lookup) debug_printf_indent("dbfn_transaction_commit\n");
 if (!dbp->readonly) exim_dbtransaction_commit(dbp->dbptr);
 }
 
@@ -352,7 +363,7 @@ else
   exim_dbclose(dbp->dbptr);
 
 if (*fdp >= 0) (void)close(*fdp);
-DEBUG(D_hints_lookup)
+DEBUG(hints_lookup)
   debug_printf_indent("closed hints database%s\n",
 		      *fdp < 0 ? "" : " and lockfile");
 *fdp = -1;
@@ -363,7 +374,7 @@ void
 dbfn_close_multi(open_db * dbp)
 {
 exim_dbclose_multi(dbp->dbptr);
-DEBUG(D_hints_lookup)
+DEBUG(hints_lookup)
   debug_printf_indent("closed hints database\n");
 }
 
@@ -381,30 +392,40 @@ no guarantee of alignment. Since all the records used by Exim need
 to be properly aligned to pick out the timestamps, etc., we might as
 well do the copying centrally here.
 
+Callers:
+	dbfn_read_with_length		(below)
+	  dbfn_read			dbfunctions.h
+	   (assorted hintsdb uses)
+	  (assorted hintsdb uses)
+	  dbfn_read_enforce_length	(below)
+	    (assorted hintsdb uses)
+	  exim_dbutil.c			utils
+	lookups/dbmdb.c			lookup dbm
+
 Arguments:
   dbblock   a pointer to an open database block
   key       the key of the record to be read
   klen	    length of key including a terminating NUL (if present)
   length    a pointer to an int into which to return the length, if not NULL
+  hintsdb   TRUE for hints DB use, FALSE for lookup dbm use
 
 Returns: a pointer to the retrieved record, or
          NULL if the record is not found
 */
 
 void *
-dbfn_read_klen(open_db * dbblock, const uschar * key, int klen, int * length)
+dbfn_read_klen(open_db * dbblock, const uschar * key, int klen, int * length,
+  BOOL hintsdb)
 {
 void * yield;
 EXIM_DATUM key_datum, result_datum;
 uschar * key_copy = store_get(klen, key);
 unsigned dlen;
+BOOL tainted;
 
 memcpy(key_copy, key, klen);
 
-/*XXX the %.*s will terminate early on a key with embedded NUL (legit for
-lookup dbmjz).  We do not have a convenient function; maybe extend
-string_printing2() ? */
-DEBUG(D_hints_lookup) debug_printf_indent("dbfn_read: key=%.*s\n", klen, key);
+DEBUG(hints_lookup) debug_printf_indent("dbfn_read: key=%.*W\n", klen, key);
 
 exim_datum_init(&key_datum);         /* Some DBM libraries require the datum */
 exim_datum_init(&result_datum);      /* to be cleared before use. */
@@ -413,17 +434,39 @@ exim_datum_size_set(&key_datum, klen);
 
 if (!exim_dbget(dbblock->dbptr, &key_datum, &result_datum))
   {
-  DEBUG(D_hints_lookup) debug_printf_indent("dbfn_read: null return\n");
+  DEBUG(hints_lookup) debug_printf_indent("dbfn_read: null return\n");
   return NULL;
   }
 
-/* Assume the data store could have been tainted.  Properly, we should
-store the taint status with the data. */
-
 dlen = exim_datum_size_get(&result_datum);
-DEBUG(D_hints_lookup) debug_printf_indent("dbfn_read: size %u return\n", dlen);
+DEBUG(hints_lookup) debug_printf_indent("dbfn_read: size %u return\n", dlen);
+
+if (hintsdb)
+  {
+  dbdata_generic *  gp = (dbdata_generic *) exim_datum_data_get(&result_datum);
 
-yield = store_get(dlen+1, GET_TAINTED);
+  if (dlen < sizeof(dbdata_generic))
+    {
+    DEBUG(hints_lookup)
+      debug_printf_indent("dbfn_read: bad record size %u\n", dlen);
+    return NULL;
+    }
+  if (gp->version != HINTS_VERSION)
+    {
+    DEBUG(hints_lookup)
+      debug_printf_indent("dbfn_read: bad record version %u\n", gp->version);
+    return NULL;
+    }
+
+  /* Hintsdb uses store the taint of the payload of the value in the value.
+  For non-hints uses we have no provenance, so assume untainted */
+
+  tainted = gp->tainted;
+  }
+else
+  tainted = FALSE;
+
+yield = store_get(dlen+1, tainted ? GET_TAINTED : GET_UNTAINTED);
 memcpy(yield, exim_datum_data_get(&result_datum), dlen);
 ((uschar *)yield)[dlen] = '\0';
 if (length) *length = dlen;
@@ -451,7 +494,7 @@ Returns: a pointer to the retrieved record, or
 void *
 dbfn_read_with_length(open_db * dbblock, const uschar * key, int * lenp)
 {
-return dbfn_read_klen(dbblock, key, Ustrlen(key)+1, lenp);
+return dbfn_read_klen(dbblock, key, Ustrlen(key)+1, lenp, TRUE);
 }
 
 
@@ -478,7 +521,7 @@ void * yield = dbfn_read_with_length(dbblock, key, &rlen);
 if (yield)
   {
   if (rlen == length) return yield;
-  log_write(0, LOG_MAIN|LOG_PANIC, "Bad db record size for '%s'", key);
+  log_write(LOG_MAIN|LOG_PANIC, "Bad db record size for '%s'", key);
   dbfn_delete(dbblock, key);
   }
 return NULL;
@@ -489,6 +532,8 @@ return NULL;
 *************************************************/
 
 /*
+All callers are writing a hintsdb record.
+
 Arguments:
   dbblock   a pointer to an open database block
   key       the key of the record to be written
@@ -500,17 +545,20 @@ Returns:    the yield of the underlying dbm or db "write" function. If this
 */
 
 int
-dbfn_write(open_db *dbblock, const uschar *key, void *ptr, int length)
+dbfn_write_ts(open_db * dbblock, const uschar * key, void * ptr, int length,
+  time_t ts)
 {
 EXIM_DATUM key_datum, value_datum;
-dbdata_generic *gptr = (dbdata_generic *)ptr;
+dbdata_generic * gptr = (dbdata_generic *)ptr;
 int klen = Ustrlen(key) + 1;
 uschar * key_copy = store_get(klen, key);
 
 memcpy(key_copy, key, klen);
-gptr->time_stamp = time(NULL);
+gptr->version = HINTS_VERSION;
+gptr->tainted = is_tainted(ptr);
+gptr->time_stamp = ts;
 
-DEBUG(D_hints_lookup)
+DEBUG(hints_lookup)
   debug_printf_indent("dbfn_write: key=%s datalen %d\n", key, length);
 
 exim_datum_init(&key_datum);         /* Some DBM libraries require the datum */
@@ -522,6 +570,12 @@ exim_datum_size_set(&value_datum, length);
 return exim_dbput(dbblock->dbptr, &key_datum, &value_datum);
 }
 
+int
+dbfn_write(open_db * dbblock, const uschar * key, void * ptr, int length)
+{
+return dbfn_write_ts(dbblock, key, ptr, length, time(NULL));
+}
+
 
 
 /*************************************************
@@ -543,14 +597,14 @@ int klen = Ustrlen(key) + 1, rc;
 uschar * key_copy = store_get(klen, key);
 EXIM_DATUM key_datum;
 
-DEBUG(D_hints_lookup) debug_printf_indent("dbfn_delete: key=%s\n", key);
+DEBUG(hints_lookup) debug_printf_indent("dbfn_delete: key=%s\n", key);
 
 memcpy(key_copy, key, klen);
 exim_datum_init(&key_datum);         /* Some DBM libraries require clearing */
 exim_datum_data_set(&key_datum, key_copy);
 exim_datum_size_set(&key_datum, klen);
 rc = exim_dbdel(dbblock->dbptr, &key_datum);
-DEBUG(D_hints_lookup) if (rc != EXIM_DBPUTB_OK)
+DEBUG(hints_lookup) if (rc != EXIM_DBPUTB_OK)
   debug_printf_indent(" exim_dbdel: fail\n");
 return rc;
 }
@@ -584,7 +638,7 @@ dbfn_scan(open_db *dbblock, BOOL start, EXIM_CURSOR **cursor)
 EXIM_DATUM key_datum, value_datum;
 uschar *yield;
 
-DEBUG(D_hints_lookup) debug_printf_indent("dbfn_scan\n");
+DEBUG(hints_lookup) debug_printf_indent("dbfn_scan\n");
 
 /* Some dbm require an initialization */
 
@@ -637,9 +691,14 @@ if (argc != 2)
 /* Initialize */
 
 spool_directory = argv[1];
-debug_selector = D_all - D_memory;
+debug_modify_channel(US"");
+debug_modify_channel(US"+all-memory");
 debug_file = stderr;
-big_buffer = malloc(big_buffer_size);
+if (!(big_buffer = malloc(big_buffer_size)))
+  {
+  perror("malloc");
+  return 1;
+  }
 
 for (i = 0; i < max_db; i++) dbblock[i].dbptr = NULL;
 
@@ -743,6 +802,7 @@ while (Ufgets(buffer, 256, stdin) != NULL)
     Uskip_whitespace(&data);
 
     dbwait = (dbdata_wait *)(&structbuffer);
+    memset(dbwait, 0, sizeof(dbdata_generic));
     Ustrcpy(dbwait->text, data);
 
     start = clock();
diff --git a/src/src/dbfunctions.h b/src/src/dbfunctions.h
index 1b0e446f7..c9622881a 100644
--- a/src/src/dbfunctions.h
+++ b/src/src/dbfunctions.h
@@ -18,11 +18,12 @@ int      dbfn_delete(open_db *, const uschar *);
 open_db *dbfn_open(const uschar *, int, open_db *, BOOL, BOOL);
 open_db * dbfn_open_path(const uschar *, open_db *);
 open_db *dbfn_open_multi(const uschar *, int, open_db *);
-void    *dbfn_read_klen(open_db *, const uschar *, int, int *);
+void    *dbfn_read_klen(open_db *, const uschar *, int, int *, BOOL);
 void    *dbfn_read_with_length(open_db *, const uschar *, int *);
 void    *dbfn_read_enforce_length(open_db *, const uschar *, size_t);
 uschar  *dbfn_scan(open_db *, BOOL, EXIM_CURSOR **);
 int      dbfn_write(open_db *, const uschar *, void *, int);
+int      dbfn_write_ts(open_db *, const uschar *, void *, int, time_t);
 BOOL	 dbfn_transaction_start(open_db *);
 void	 dbfn_transaction_commit(open_db *);
 
diff --git a/src/src/dcc.c b/src/src/dcc.c
index 8af6c3cda..7426d2a15 100644
--- a/src/src/dcc.c
+++ b/src/src/dcc.c
@@ -37,15 +37,15 @@ static int flushbuffer
 int rsp;
 
 rsp = write(socket, buffer->s, buffer->ptr);
-DEBUG(D_acl)
+DEBUG(acl)
   debug_printf("DCC: flushbuffer(): Result of the write() = %d\n", rsp);
 if(rsp < 0)
   {
-  DEBUG(D_acl)
+  DEBUG(acl)
     debug_printf("DCC: flushbuffer(): Error writing buffer to socket: %s\n", strerror(errno));
   return errno;
   }
-DEBUG(D_acl)
+DEBUG(acl)
   debug_printf("DCC: flushbuffer(): Wrote buffer to socket:\n%.*s\n", buffer->ptr, buffer->s);
 return 0;
 }
@@ -103,8 +103,7 @@ for (int i = 0; i < 2; i++)
 if (!data_file)
   {
   /* error while spooling */
-  log_write(0, LOG_MAIN|LOG_PANIC,
-	 "DCC: error while opening spool file");
+  log_write(LOG_MAIN|LOG_PANIC, "DCC: error while opening spool file");
   return DEFER;
   }
 
@@ -118,7 +117,7 @@ if (dccifd_address)
     if(sscanf(CS dccifd_address, "%" mac_expanded_string(SOCKIP_USE) "s %u",
 	      sockip, &portnr) != 2)
       {
-      log_write(0, LOG_MAIN,
+      log_write(LOG_MAIN,
 	"DCC: warning - invalid dccifd address: '%s'", dccifd_address);
       (void)fclose(data_file);
       return DEFER;
@@ -132,21 +131,21 @@ if (((override_client_ip = expand_string(US"$acl_m_dcc_override_client_ip")) !=
      (override_client_ip[0] != '\0'))
   {
   Ustrncpy(client_ip, override_client_ip, sizeof(client_ip)-1);
-  DEBUG(D_acl)
+  DEBUG(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(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);
-  DEBUG(D_acl)
+  DEBUG(acl)
     debug_printf("DCC: Client IP (default): %s\n", client_ip);
   }
 /* build options block */
@@ -176,9 +175,9 @@ if(Ustrcmp(sockip, ""))
   serv_addr_in.sin_port = htons(portnr);
   if ((sockfd = socket(AF_INET, SOCK_STREAM,0)) < 0)
     {
-    DEBUG(D_acl)
+    DEBUG(acl)
       debug_printf("DCC: Creating TCP socket connection failed: %s\n", strerror(errno));
-    log_write(0,LOG_PANIC,"DCC: Creating TCP socket connection failed: %s\n", strerror(errno));
+    log_write(LOG_PANIC,"DCC: Creating TCP socket connection failed: %s\n", strerror(errno));
     /* if we cannot create the socket, defer the mail */
     (void)fclose(data_file);
     return retval;
@@ -186,9 +185,9 @@ if(Ustrcmp(sockip, ""))
   /* Now connecting the socket (INET) */
   if (connect(sockfd, (struct sockaddr *)&serv_addr_in, sizeof(serv_addr_in)) < 0)
     {
-    DEBUG(D_acl)
+    DEBUG(acl)
       debug_printf("DCC: Connecting to TCP socket failed: %s\n", strerror(errno));
-    log_write(0,LOG_PANIC,"DCC: Connecting to TCP socket failed: %s\n", strerror(errno));
+    log_write(LOG_PANIC,"DCC: Connecting to TCP socket failed: %s\n", strerror(errno));
     /* if we cannot contact the socket, defer the mail */
     (void)fclose(data_file);
     return retval;
@@ -202,9 +201,9 @@ else
   Ustrncpy(US serv_addr.sun_path, sockpath, sizeof(serv_addr.sun_path));
   if ((sockfd = socket(AF_UNIX, SOCK_STREAM,0)) < 0)
     {
-    DEBUG(D_acl)
+    DEBUG(acl)
       debug_printf("DCC: Creating UNIX socket connection failed: %s\n", strerror(errno));
-    log_write(0,LOG_PANIC,"DCC: Creating UNIX socket connection failed: %s\n", strerror(errno));
+    log_write(LOG_PANIC,"DCC: Creating UNIX socket connection failed: %s\n", strerror(errno));
     /* if we cannot create the socket, defer the mail */
     (void)fclose(data_file);
     return retval;
@@ -212,23 +211,23 @@ else
   /* Now connecting the socket (UNIX) */
   if (connect(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0)
     {
-    DEBUG(D_acl)
+    DEBUG(acl)
       debug_printf("DCC: Connecting to UNIX socket failed: %s\n", strerror(errno));
-    log_write(0,LOG_PANIC,"DCC: Connecting to UNIX socket failed: %s\n", strerror(errno));
+    log_write(LOG_PANIC,"DCC: Connecting to UNIX socket failed: %s\n", strerror(errno));
     /* if we cannot contact the socket, defer the mail */
     (void)fclose(data_file);
     return retval;
     }
   }
 /* the socket is open, now send the options to dccifd*/
-DEBUG(D_acl)
+DEBUG(acl)
   debug_printf("DCC: -----------------------------------\nDCC: Socket opened; now sending input\n"
 	       "DCC: -----------------------------------\n");
 
 /* let's send each of the recipients to dccifd */
 for (int i = 0; i < recipients_count; i++)
   {
-  DEBUG(D_acl)
+  DEBUG(acl)
     debug_printf("DCC: recipient = %s\n",recipients_list[i].address);
   dcc_headers = string_append(dcc_headers, 2, recipients_list[i].address, "\n");
   }
@@ -236,7 +235,7 @@ for (int i = 0; i < recipients_count; i++)
 dcc_headers = string_catn(dcc_headers, US"\n", 1);
 
 /* Now we send the input buffer */
-DEBUG(D_acl)
+DEBUG(acl)
   debug_printf("DCC: ***********************************\nDCC: Sending options:\n%Y"
 	       "DCC: ***********************************\n", dcc_headers);
 if (flushbuffer(sockfd, dcc_headers) != 0)
@@ -247,7 +246,7 @@ if (flushbuffer(sockfd, dcc_headers) != 0)
 
 /* now send the message */
 /* First send the headers */
-DEBUG(D_acl)
+DEBUG(acl)
   debug_printf("DCC: ***********************************\nDCC: Sending headers:\n");
 sendbuf = string_get(8192);
 sendbuf = string_catn(sendbuf, mail_headers->text, mail_headers->slen);
@@ -257,7 +256,7 @@ while((mail_headers=mail_headers->next))
 /* a blank line separates header from body */
 sendbuf = string_catn(sendbuf, US"\r\n", 2);
 gstring_release_unused(sendbuf);
-DEBUG(D_acl)
+DEBUG(acl)
   debug_printf("%YDCC: ***********************************\n", sendbuf);
 if (flushbuffer(sockfd, sendbuf) != 0)
   {
@@ -266,7 +265,7 @@ if (flushbuffer(sockfd, sendbuf) != 0)
   }
 
 /* now send the body */
-DEBUG(D_acl)
+DEBUG(acl)
   debug_printf("DCC: ***********************************\nDCC: Writing body:\n");
 (void)fseek(data_file, spool_data_start_offset(message_id), SEEK_SET);
 
@@ -278,21 +277,21 @@ while((filebuf.ptr = fread(filebuf.s, 1, filebuf.size, data_file)) > 0)
     (void)fclose(data_file);
     return retval;
     }
-DEBUG(D_acl)
+DEBUG(acl)
   debug_printf("DCC: ***********************************\n");
 
 /* shutdown() the socket */
 if(shutdown(sockfd, SHUT_WR) < 0)
   {
-  DEBUG(D_acl)
+  DEBUG(acl)
     debug_printf("DCC: Couldn't shutdown socket: %s\n", strerror(errno));
-  log_write(0,LOG_MAIN,"DCC: Couldn't shutdown socket: %s\n", strerror(errno));
+  log_write(LOG_MAIN,"DCC: Couldn't shutdown socket: %s\n", strerror(errno));
   /* If there is a problem with the shutdown()
    * defer the mail. */
   (void)fclose(data_file);
   return retval;
   }
-DEBUG(D_acl)
+DEBUG(acl)
   debug_printf("DCC: Input sent.\n"
 	       "DCC: +++++++++++++++++++++++++++++++++++\n"
 	       "DCC: Now receiving output from server\n"
@@ -321,7 +320,7 @@ dcc_header_str = string_get(DCC_HEADER_LIMIT + 2);
 while((dcc_resplen = read(sockfd, big_buffer, big_buffer_size-1)) > 0)
   {
   /* make the answer 0-terminated. only needed for debug_printf */
-  DEBUG(D_acl)
+  DEBUG(acl)
     debug_printf("DCC: Length of the output buffer is: %d\nDCC: Output buffer is:\n"
 		 "DCC: -----------------------------------\n%.*s\n"
 		 "DCC: -----------------------------------\n", dcc_resplen, dcc_resplen, big_buffer);
@@ -345,27 +344,27 @@ while((dcc_resplen = read(sockfd, big_buffer, big_buffer_size-1)) > 0)
 	  switch (big_buffer[bufoffset])
 	    {
 	    case 'A':
-	      DEBUG(D_acl)
+	      DEBUG(acl)
 		debug_printf("DCC: Overall result = A\treturning OK\n");
 	      dcc_return_text = US"Mail accepted by DCC";
 	      dcc_result = US"A";
 	      retval = OK;
 	      break;
 	    case 'R':
-	      DEBUG(D_acl)
+	      DEBUG(acl)
 		debug_printf("DCC: Overall result = R\treturning FAIL\n");
 	      dcc_return_text = US"Rejected by DCC";
 	      dcc_result = US"R";
 	      retval = FAIL;
 	      if(sender_host_name)
-		log_write(0, LOG_MAIN, "H=%s [%s] F=<%s>: rejected by DCC",
+		log_write(LOG_MAIN, "H=%s [%s] F=<%s>: rejected by DCC",
 			   sender_host_name, sender_host_address, sender_address);
 	      else
-		log_write(0, LOG_MAIN, "H=[%s] F=<%s>: rejected by DCC",
+		log_write(LOG_MAIN, "H=[%s] F=<%s>: rejected by DCC",
 			   sender_host_address, sender_address);
 	      break;
 	    case 'S':
-	      DEBUG(D_acl)
+	      DEBUG(acl)
 		debug_printf("DCC: Overall result  = S\treturning OK\n");
 	      dcc_return_text = US"Not all recipients accepted by DCC";
 	      /* Since we're in an ACL we want a global result so we accept for all */
@@ -373,27 +372,27 @@ while((dcc_resplen = read(sockfd, big_buffer, big_buffer_size-1)) > 0)
 	      retval = OK;
 	      break;
 	    case 'G':
-	      DEBUG(D_acl)
+	      DEBUG(acl)
 		debug_printf("DCC: Overall result  = G\treturning FAIL\n");
 	      dcc_return_text = US"Greylisted by DCC";
 	      dcc_result = US"G";
 	      retval = FAIL;
 	      break;
 	    case 'T':
-	      DEBUG(D_acl)
+	      DEBUG(acl)
 		debug_printf("DCC: Overall result = T\treturning DEFER\n");
 	      dcc_return_text = US"Temporary error with DCC";
 	      dcc_result = US"T";
 	      retval = DEFER;
-	      log_write(0,LOG_MAIN,"Temporary error with DCC: %s\n", big_buffer);
+	      log_write(LOG_MAIN,"Temporary error with DCC: %s\n", big_buffer);
 	      break;
 	    default:
-	      DEBUG(D_acl)
+	      DEBUG(acl)
 		debug_printf("DCC: Overall result = something else\treturning DEFER\n");
 	      dcc_return_text = US"Unknown DCC response";
 	      dcc_result = US"T";
 	      retval = DEFER;
-	      log_write(0,LOG_MAIN,"Unknown DCC response: %s\n", big_buffer);
+	      log_write(LOG_MAIN,"Unknown DCC response: %s\n", big_buffer);
 	      break;
 	    }
 	}
@@ -401,9 +400,9 @@ while((dcc_resplen = read(sockfd, big_buffer, big_buffer_size-1)) > 0)
 	  {
 	  /* 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 bufoffset = %d != 0"
+	  DEBUG(acl) debug_printf("DCC: Line = %d but bufoffset = %d != 0"
 	      "  character is %c - This is wrong!\n", line, bufoffset, big_buffer[bufoffset]);
-	  log_write(0,LOG_MAIN,"Wrong header from DCC, output is %s\n", big_buffer);
+	  log_write(LOG_MAIN,"Wrong header from DCC, output is %s\n", big_buffer);
 	  }
 	}
       else if(line == 2)
@@ -425,7 +424,7 @@ while((dcc_resplen = read(sockfd, big_buffer, big_buffer_size-1)) > 0)
     if (gstring_length(dcc_header_str) + dcc_resplen > DCC_HEADER_LIMIT)
       {
       dcc_resplen = DCC_HEADER_LIMIT - gstring_length(dcc_header_str);
-      DEBUG(D_acl) debug_printf("DCC: We got more output than we can store"
+      DEBUG(acl) debug_printf("DCC: We got more output than we can store"
 			 "in the X-DCC header. Truncating at 120 characters.\n");
       }
     dcc_header_str = string_catn(dcc_header_str, &big_buffer[bufoffset], dcc_resplen);
@@ -435,7 +434,7 @@ while((dcc_resplen = read(sockfd, big_buffer, big_buffer_size-1)) > 0)
 /* fail on read error */
 if(dcc_resplen < 0)
   {
-  DEBUG(D_acl)
+  DEBUG(acl)
     debug_printf("DCC: Error reading from socket: %s\n", strerror(errno));
   (void)fclose(data_file);
   return retval;
@@ -446,7 +445,7 @@ dcc_header_str = string_catn(dcc_header_str, US"\n", 1);
 
 (void) string_from_gstring(dcc_header_str);
 /* Now let's sum up what we've got. */
-DEBUG(D_acl)
+DEBUG(acl)
   debug_printf("\nDCC: --------------------------\nDCC: Overall result = %d\n"
 	       "DCC: X-DCC header: %YReturn message: %s\nDCC: dcc_result: %s\n",
 		 retval, dcc_header_str, dcc_return_text, dcc_result);
@@ -463,7 +462,7 @@ if(!(Ustrncmp(dcc_header_str->s, "X-DCC", 5)))
     }
   }
 else
-  DEBUG(D_acl)
+  DEBUG(acl)
     debug_printf("DCC: Wrong format of the X-DCC header: %Y\n", dcc_header_str);
 
 /* check if we should add additional headers passed in acl_m_dcc_add_header */
@@ -476,14 +475,14 @@ if (dcc_direct_add_header)
     if (dcc_xtra_hdrs->s[dcc_xtra_hdrs->ptr - 1] != '\n')
       dcc_xtra_hdrs = string_catn(dcc_xtra_hdrs, US"\n", 1);
     header_add(' ', "%s", string_from_gstring(dcc_xtra_hdrs));
-    DEBUG(D_acl)
+    DEBUG(acl)
       debug_printf("DCC: adding additional headers in $acl_m_dcc_add_header: %Y", dcc_xtra_hdrs);
     }
   }
 
 dcc_ok = 1;
 /* Now return to exim main process */
-DEBUG(D_acl)
+DEBUG(acl)
   debug_printf("DCC: Before returning to exim main process:\nDCC: return_text = %s - retval = %d\n"
 	       "DCC: dcc_result = %s\n", dcc_return_text, retval, dcc_result);
 
diff --git a/src/src/debug.c b/src/src/debug.c
index 487531e96..affff12aa 100644
--- a/src/src/debug.c
+++ b/src/src/debug.c
@@ -138,14 +138,14 @@ void
 debug_print_string(uschar *debug_string)
 {
 if (!debug_string) return;
-HDEBUG(D_any|D_v)
+HDEBUG(any|v)
   {
   uschar * s = expand_string(debug_string);
   if (!s)
     debug_printf("failed to expand debug_output %q: %s\n", debug_string,
       expand_string_message);
-  else if (s[0] != 0)
-    debug_printf("%s%s", s, (s[Ustrlen(s)-1] == '\n')? "" : "\n");
+  else if (*s)
+    debug_printf("%s%s", s, s[Ustrlen(s)-1] == '\n' ? "" : "\n");
   }
 }
 
@@ -161,15 +161,18 @@ Returns:    nothing
 */
 
 void
-debug_print_ids(uschar *s)
+debug_print_ids(uschar * s)
 {
-debug_printf("%s uid=%ld gid=%ld euid=%ld egid=%ld\n", s,
+debug_printf("%s uid=%ld gid=%ld euid=%ld egid=%ld", s,
   (long int)getuid(), (long int)getgid(), (long int)geteuid(),
   (long int)getegid());
+if (config_lineno)
+  debug_printf(" (%s %d)\n", config_filename, config_lineno);
+else
+  debug_printf("\n", config_filename, config_lineno);
 }
 
 /************************************************/
-
 /* Give a string for a return-code */
 
 const uschar *
@@ -179,6 +182,18 @@ return rc < 0 || rc >= nelem(rc_names) ? US"?" : rc_names[rc];
 }
 
 
+/************************************************/
+/* Print the receive processing stack */
+
+void
+debug_print_processing_stack(void)
+{
+debug_printf_indent("inp: ");
+for (const in_processing * inp = rx_prc; inp; inp = inp->in_lower)
+  debug_printf("/%s", inp->in_layer_name);
+debug_printf("\n");
+}
+
 
 
 
@@ -217,7 +232,7 @@ va_end(ap);
 }
 
 void
-debug_vprintf(int indent, const char *format, va_list ap)
+debug_vprintf(int indent, const char * format, va_list ap)
 {
 int save_errno = errno;
 
@@ -230,7 +245,7 @@ timestamp buffer, which may contain something useful. (This was a bug fix: the
 
 if (debug_ptr == debug_buffer)
   {
-  DEBUG(D_timestamp)
+  DEBUG(timestamp)
     {
     struct timeval now;
     time_t tmp;
@@ -244,12 +259,12 @@ if (debug_ptr == debug_buffer)
       t->tm_hour, t->tm_min, t->tm_sec, (int)(now.tv_usec/1000));
     }
 
-  DEBUG(D_pid)
-    debug_ptr += sprintf(CS debug_ptr, "%5d ", (int)getpid());
+  DEBUG(pid)
+    debug_ptr += sprintf(CS debug_ptr, PID_T_FMT " ", getpid());
 
   /* Set up prefix if outputting for host checking and not debugging */
 
-  if (host_checking && debug_selector == 0)
+  if (host_checking && !ANY_DEBUG)
     debug_ptr = Ustpcpy(debug_ptr, US">>> ");
 
   debug_prefix_length = debug_ptr - debug_buffer;
@@ -258,7 +273,7 @@ if (debug_ptr == debug_buffer)
 if (indent > 0)
   {
   for (int i = indent >> 2; i > 0; i--)
-    DEBUG(D_noutf8)
+    DEBUG(noutf8)
       {
       debug_ptr = Ustpcpy(debug_ptr, US"   !");
       debug_prefix_length += 4;
@@ -309,10 +324,10 @@ if (debug_ptr[-1] == '\n')
   {
   if (debug_prefix_length > 0)
     {
-    uschar *p = debug_buffer;
     int left = sizeof(debug_buffer) - (debug_ptr - debug_buffer) - 1;
-    while ((p = Ustrchr(p, '\n') + 1) != debug_ptr &&
-           left >= debug_prefix_length)
+    for (uschar * p = debug_buffer;
+	 (p = Ustrchr(p, '\n') + 1) != debug_ptr && left >= debug_prefix_length;
+	)
       {
       int len = debug_ptr - p;
       memmove(p + debug_prefix_length, p, len + 1);
@@ -353,10 +368,8 @@ if (debug_ptr[-1] == '\n')
       }
     }
   else
-    {
     fprintf(debug_file, "%s", CS debug_buffer);
-    fflush(debug_file);
-    }
+
   debug_ptr = debug_buffer;
   debug_prefix_length = 0;
   }
@@ -501,4 +514,79 @@ debug_pretrigger_discard();
 }
 
 
+/******************************************************************************/
+
+BOOL
+is_debug(const uschar * channels)
+{
+int sep = '|';
+uschar buf[64];		/* static buffer to avoid allocs */
+const uschar * chan;
+
+/* The "any" request is special (and somewhat a misnomer).
+Check its summary bit. */
+
+while (chan = string_nextinlist(&channels, &sep, buf, sizeof(buf)))
+  {
+  unsigned bit =
+    Ustrcmp(chan, "any") == 0
+    ? BIT_TABLE_IDX_IS_ANY
+    : chan_name_to_num(chan, Ustrlen(chan), debug_channels, debug_chan_count);
+  if (bit && DEBUG_BIT(bit)) return TRUE;
+  }
+return FALSE;
+}
+
+void
+debug_decode_bits(bitmask_word_t ** selector, const uschar * string, int flags)
+{
+if (!*selector)
+  {
+  size_t len = sizeof(bitmask_word_t) * DEBUG_SELECTOR_SIZE;
+  *selector = store_get_perm(len, GET_UNTAINTED);
+  memset(*selector, 0, len);
+  }
+
+decode_bits(*selector, DEBUG_SELECTOR_SIZE, debug_notall_names, string,
+          debug_channels, debug_chan_count, DCB_DEBUG | flags);
+}
+
+void
+debug_modify_channel(const uschar * string)
+{
+debug_decode_bits(&debug_selector, string, 0);
+}
+
+void
+debug_set_default_bits(bitmask_word_t ** selector)
+{
+debug_decode_bits(selector, US"=0", 0);
+debug_decode_bits(selector, debug_defaults, 0);
+}
+
+BOOL
+debug_disable(void)
+{
+BOOL is_debugging = !!ANY_DEBUG;
+bit_clear(debug_selector, BIT_TABLE_IDX_NONZERO);
+return is_debugging;
+}
+
+void
+debug_enable(void)
+{
+bit_set(debug_selector, BIT_TABLE_IDX_NONZERO);
+}
+
+gstring *
+debug_selector_dump(gstring * g)
+{
+g = string_fmt_append(g, "-d=0x" PR_EXIM_BITMASK, debug_selector[0]);
+for (int i = 1; i < DEBUG_SELECTOR_SIZE; i++)
+  g = string_fmt_append(g, ",0x" PR_EXIM_BITMASK, debug_selector[i]);
+return g;
+}
+
 /* End of debug.c */
+/* vi: ai aw sw=2
+ */
diff --git a/src/src/deliver.c b/src/src/deliver.c
index 21a610859..81718e56b 100644
--- a/src/src/deliver.c
+++ b/src/src/deliver.c
@@ -174,8 +174,10 @@ deliver_set_expansions(address_item * addr)
 {
 if (!addr)
   {
-  const uschar ***p = address_expansions;
-  while (*p) **p++ = NULL;
+  for (const uschar ***p = address_expansions; *p; ) **p++ = NULL;
+  deliver_host_port = 0;
+  deliver_recipients = NULL;
+  router_var = NULL;
   return;
   }
 
@@ -206,13 +208,6 @@ router_var = addr->prop.variables;
 deliver_domain = addr->domain;
 self_hostname = addr->self_hostname;
 
-#ifdef EXPERIMENTAL_BRIGHTMAIL
-bmi_deliver = 1;    /* deliver by default */
-bmi_alt_location = NULL;
-bmi_base64_verdict = NULL;
-bmi_base64_tracker_verdict = NULL;
-#endif
-
 /* If there's only one address we can set everything. */
 
 if (!addr->next)
@@ -269,17 +264,6 @@ if (!addr->next)
       }
     }
 
-#ifdef EXPERIMENTAL_BRIGHTMAIL
-    /* Set expansion variables related to Brightmail AntiSpam */
-    bmi_base64_verdict = bmi_get_base64_verdict(deliver_localpart_orig, deliver_domain_orig);
-    bmi_base64_tracker_verdict = bmi_get_base64_tracker_verdict(bmi_base64_verdict);
-    /* 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)
-      bmi_alt_location = bmi_get_alt_location(bmi_base64_verdict);
-#endif
-
   }
 
 /* For multiple addresses, don't set local part, and leave the domain and
@@ -334,7 +318,7 @@ static int
 open_msglog_file(uschar *filename, int mode, uschar **error)
 {
 if (Ustrstr(filename, US"/../"))
-  log_write_die(0, LOG_MAIN,
+  log_write_die(LOG_MAIN,
     "Attempt to open msglog file path with upward-traversal: '%s'\n", filename);
 
 for (int i = 2; i > 0; i--)
@@ -391,7 +375,7 @@ Returns:       nothing
 */
 
 void
-deliver_msglog(const char *format, ...)
+deliver_msglog(const char * format, ...)
 {
 va_list ap;
 if (!message_logs) return;
@@ -737,7 +721,7 @@ while (addr->parent)
   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);
+  DEBUG(deliver) debug_printf("%s: children all complete\n", addr->address);
   }
 }
 
@@ -863,7 +847,7 @@ event_raise(const uschar * action, const uschar * event, const uschar * ev_data,
 if (action)
   {
   const uschar * s;
-  DEBUG(D_deliver)
+  DEBUG(deliver)
     debug_printf("Event(%s): event_action=|%s| delivery_IP=%s\n",
       event,
       action, deliver_host_address);
@@ -872,7 +856,7 @@ if (action)
   event_data = ev_data;
 
   if (!(s = expand_string(action)) && *expand_string_message)
-    log_write(0, LOG_MAIN|LOG_PANIC,
+    log_write(LOG_MAIN|LOG_PANIC,
       "failed to expand event_action %s in %s: %s\n",
       event, transport_name ? transport_name : US"main", expand_string_message);
 
@@ -884,7 +868,7 @@ if (action)
 
   if (s && *s)
     {
-    DEBUG(D_deliver)
+    DEBUG(deliver)
       debug_printf("Event(%s): event_action returned %q\n", event, s);
     if (errnop)
       *errnop = ERRNO_EVENT;
@@ -1059,7 +1043,7 @@ else
     s = addr->domain;
 #ifdef SUPPORT_I18N
     if (testflag(addr, af_utf8_downcvt))
-      s = string_localpart_utf8_to_alabel(s, NULL);
+      s = string_domain_utf8_to_alabel(US s, NULL);
 #endif
     g = string_cat(g, s);
     }
@@ -1113,6 +1097,20 @@ return g;
 
 
 
+static gstring *
+d_dsnlog(gstring * g, const address_item * addr)
+{
+return LOGGING(dsn)
+  ? dsn_ret || dsn_envid || addr->dsn_flags & rf_dsnflags || addr->dsn_orcpt
+    ? string_append(g, 2, US" DSN=",
+	addr->dsn_flags & rf_dsnlasthop ? US"notrouter"
+	: addr->dsn_aware == dsn_support_unknown ? US"notsmtp"
+	: addr->dsn_aware == dsn_support_no ? US"notpeer"
+	: US"yes")
+    : g
+  : g;
+}
+
 /******************************************************************************/
 
 
@@ -1253,12 +1251,14 @@ else
     g = string_catn(g, US" K", 2);
   }
 
+g = d_dsnlog(g, addr);
+
 #ifndef DISABLE_DKIM
-  if (addr->dkim_used && LOGGING(dkim_verbose))
-    {
-    g = string_catn(g, US" DKIM=", 6);
-    g = string_cat(g, addr->dkim_used);
-    }
+if (addr->dkim_used && LOGGING(dkim_verbose))
+  {
+  g = string_catn(g, US" DKIM=", 6);
+  g = string_cat(g, addr->dkim_used);
+  }
 #endif
 
 /* confirmation message (SMTP (host_used) and LMTP (driver_name)) */
@@ -1292,15 +1292,14 @@ if (LOGGING(queue_time))
 if (LOGGING(deliver_time))
   g = string_append(g, 2, US" DT=", string_timediff(&addr->delivery_time));
 
-/* string_cat() always leaves room for the terminator. Release the
-store we used to build the line after writing it. */
-
-log_write(0, flags, "%Y", g);
+log_write(flags, "%Y", g);
 
 #ifndef DISABLE_EVENT
 if (!msg) msg_event_raise(US"msg:delivery", addr);
 #endif
 
+/*  Release the store we used to build the line after writing it. */
+
 store_reset(reset_point);
 return;
 }
@@ -1312,7 +1311,7 @@ deferral_log(address_item * addr, uschar * now,
   int logflags, uschar * driver_name, uschar * driver_kind)
 {
 rmark reset_point = store_mark();
-gstring * g = string_get(256);
+gstring * g = string_get_tainted(256, GET_TAINTED);
 
 /* Build up the line that is used for both the message log and the main
 log. */
@@ -1340,14 +1339,16 @@ if (driver_name)
 else if (driver_kind)
   g = string_append(g, 2, US" ", driver_kind);
 
-g = string_fmt_append(g, " defer (%d)", addr->basic_errno);
+g = string_catn(g, US" defer", 6);
 
-if (addr->basic_errno > 0)
-  g = string_append(g, 2, US": ", US strerror(addr->basic_errno));
+if (addr->basic_errno != 0 && !addr->message)
+  g = string_fmt_append(g, " (%s)", exim_errstr(addr->basic_errno));
 
 if (addr->host_used)
   g = d_hostlog(g, addr);
 
+g = d_dsnlog(g, addr);
+
 if (LOGGING(deliver_time))
   g = string_append(g, 2, US" DT=", string_timediff(&addr->delivery_time));
 
@@ -1367,8 +1368,8 @@ 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,
-  "== %Y", g);
+if (addr->basic_errno > ERRNO_RETRY_BASE || LOGGING(retry_defer))
+  log_write(logflags, "== %Y", g);
 
 store_reset(reset_point);
 return;
@@ -1380,7 +1381,7 @@ static void
 failure_log(address_item * addr, uschar * driver_kind, uschar * now)
 {
 rmark reset_point = store_mark();
-gstring * g = string_get(256);
+gstring * g = string_get_tainted(256, GET_TAINTED);	
 
 #ifndef DISABLE_EVENT
 /* Message failures for which we will send a DSN get their event raised
@@ -1425,11 +1426,13 @@ if (LOGGING(protocol_detail) && addr->protocol_sequence)
 g = d_tlslog(g, addr);
 #endif
 
-if (addr->basic_errno > 0)
-  g = string_append(g, 2, US" : ", US strerror(addr->basic_errno));
+g = d_dsnlog(g, addr);
 
 if (addr->message)
   g = string_append(g, 2, US" : ", addr->message);
+else if (addr->basic_errno != 0)
+  g = string_fmt_append(g, " (%s)", exim_errstr(addr->basic_errno));
+
 
 if (LOGGING(deliver_time))
   g = string_append(g, 2, US" DT=", string_timediff(&addr->delivery_time));
@@ -1442,7 +1445,7 @@ if (driver_kind)
 else
   deliver_msglog("%s %.*s\n", now, g->ptr, g->s);
 
-log_write(0, LOG_MAIN, "** %Y", g);
+log_write(LOG_MAIN, "** %Y", g);
 
 store_reset(reset_point);
 return;
@@ -1476,7 +1479,7 @@ uschar * now = tod_stamp(tod_log);
 uschar * driver_kind = NULL;
 uschar * driver_name = NULL;
 
-DEBUG(D_deliver)
+DEBUG(deliver)
   debug_printf("post-process %s (%s)\n", addr->address, rc_names[result]);
 
 /* Set up driver kind and name for logging. Disable logging if the router or
@@ -1513,9 +1516,12 @@ malformed, it won't ever have gone near LDAP.) */
 if (addr->message)
   {
   const uschar * s = string_printing(addr->message);
+  uschar * t;
+
+  /* deconst cast ok IF string_printing known to have alloc'n'copied */
 
-  /* deconst cast ok as string_printing known to have alloc'n'copied */
-  addr->message = expand_hide_passwords(US s);
+  t = s == addr->message ? string_copy(s) : US s;
+  addr->message = expand_hide_passwords(t);
   }
 
 /* If we used a transport that has one of the "return_output" options set, and
@@ -1534,7 +1540,8 @@ if (addr->return_file >= 0 && addr->return_filename)
   {
   BOOL return_output = FALSE;
   struct stat statbuf;
-  (void)EXIMfsync(addr->return_file);
+
+  (void) EXIMfsync(addr->return_file);
 
   /* If there is no output, do nothing. */
 
@@ -1551,7 +1558,7 @@ if (addr->return_file >= 0 && addr->return_filename)
       {
       FILE * f = Ufopen(addr->return_filename, "rb");
       if (!f)
-        log_write(0, LOG_MAIN|LOG_PANIC, "failed to open %s to log output "
+        log_write(LOG_MAIN|LOG_PANIC, "failed to open %s to log output "
           "from %s transport: %s", addr->return_filename, tb->drinst.name,
           strerror(errno));
       else
@@ -1563,7 +1570,7 @@ if (addr->return_file >= 0 && addr->return_filename)
           while (p > big_buffer && isspace(p[-1])) p--;
           *p = 0;
           sp = string_printing(big_buffer);
-          log_write(0, LOG_MAIN, "<%s>: %s transport output: %s",
+          log_write(LOG_MAIN, "<%s>: %s transport output: %s",
             addr->address, tb->drinst.name, sp);
           }
 	(void)fclose(f);
@@ -1611,7 +1618,7 @@ if (result == OK)
   last child to complete. */
 
   address_done(addr, now);
-  DEBUG(D_deliver) debug_printf("%s delivered\n", addr->address);
+  DEBUG(deliver) debug_printf("%s delivered\n", addr->address);
 
   if (!addr->parent)
     deliver_msglog("%s %s: %s%s succeeded\n", now, addr->address,
@@ -1792,7 +1799,7 @@ for (address_item * addr2 = addr->next; addr2; addr2 = addr2->next)
   addr2->message = addr->message;
   }
 
-if (logit) log_write(0, LOG_MAIN|LOG_PANIC, "%s", addr->message);
+if (logit) log_write(LOG_MAIN|LOG_PANIC, "%s", addr->message);
 deliver_set_expansions(NULL);
 }
 
@@ -2015,9 +2022,9 @@ if (expand_string_message)
 else if (size_limit > 0 && message_size > size_limit)
   {
   rc = FAIL;
+  addr->basic_errno = ERRNO_TPTLIMIT;
   addr->message =
-    string_sprintf("message is too big (transport limit = %d)",
-      size_limit);
+    string_sprintf("message is too big (transport limit = %d)", size_limit);
   }
 
 return rc;
@@ -2052,7 +2059,7 @@ uschar * s = string_sprintf("%s/%s",
 
 if (tree_search(tree_nonrecipients, s) != 0)
   {
-  DEBUG(D_deliver|D_route|D_transport)
+  DEBUG(deliver|route|transport)
     debug_printf("%s was previously delivered (%s transport): discarded\n",
     addr->address, addr->transport->drinst.name);
   if (!testing) child_done(addr, tod_stamp(tod_log));
@@ -2256,7 +2263,7 @@ if (  !shadowing
 
   addr->return_filename =
     spool_fname(US"msglog", message_subdir, message_id,
-      string_sprintf("-%ld-%d", (long)getpid(), return_count++));
+      string_sprintf("-" PID_T_FMT "-%d", getpid(), return_count++));
 
   if ((addr->return_file = open_msglog_file(addr->return_filename, 0400, &error)) < 0)
     {
@@ -2313,7 +2320,7 @@ if ((pid = exim_fork(US"delivery-local")) == 0)
 # ifdef SETRLIMIT_NOT_SUPPORTED
     if (errno != ENOSYS && errno != ENOTSUP)
 # endif
-      log_write(0, LOG_MAIN|LOG_PANIC, "setrlimit(RLIMIT_CORE) failed: %s",
+      log_write(LOG_MAIN|LOG_PANIC, "setrlimit(RLIMIT_CORE) failed: %s",
         strerror(errno));
     }
 #endif
@@ -2355,7 +2362,7 @@ if ((pid = exim_fork(US"delivery-local")) == 0)
     string_sprintf("local delivery to %s <%s> transport=%s", addr->local_part,
       addr->address, addr->transport->drinst.name));
 
-  DEBUG(D_deliver)
+  DEBUG(deliver)
     {
     debug_printf("  home=%s current=%s\n", deliver_home, working_directory);
     for (address_item * batched = addr->next; batched; batched = batched->next)
@@ -2442,7 +2449,7 @@ if ((pid = exim_fork(US"delivery-local")) == 0)
 	     )
 	 )
       )
-      log_write(0, LOG_MAIN|LOG_PANIC, "Failed writing transport results to pipe: %s",
+      log_write(LOG_MAIN|LOG_PANIC, "Failed writing transport results to pipe: %s",
 	ret == -1 ? strerror(errno) : "short write");
 
     /* Now any messages */
@@ -2453,7 +2460,7 @@ if ((pid = exim_fork(US"delivery-local")) == 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",
+        log_write(LOG_MAIN|LOG_PANIC, "Failed writing transport results to pipe: %s",
 	  ret == -1 ? strerror(errno) : "short write");
       }
     }
@@ -2470,7 +2477,7 @@ better than returning an error - if forking is failing it is probably best
 not to try other deliveries for this message. */
 
 if (pid < 0)
-  log_write_die(0, LOG_MAIN, "Fork failed for local delivery to %s",
+  log_write_die(LOG_MAIN, "Fork failed for local delivery to %s",
     addr->address);
 
 /* Read the pipe to get the delivery status codes and error messages. Our copy
@@ -2506,7 +2513,7 @@ for (addr2 = addr; addr2; addr2 = addr2->next)
 	 || llen > 64*4	/* limit from rfc 5821, times I18N factor */
          )
 	{
-	log_write(0, LOG_MAIN|LOG_PANIC, "bad local_part length read"
+	log_write(LOG_MAIN|LOG_PANIC, "bad local_part length read"
 	  " from delivery subprocess");
 	break;
 	}
@@ -2514,7 +2521,7 @@ for (addr2 = addr; addr2; addr2 = addr2->next)
       /* coverity[tainted_data] */
       if (read(pfd[pipe_read], big_buffer, llen) != llen)
 	{
-	log_write(0, LOG_MAIN|LOG_PANIC, "bad local_part read"
+	log_write(LOG_MAIN|LOG_PANIC, "bad local_part read"
 	  " from delivery subprocess");
 	break;
 	}
@@ -2528,16 +2535,21 @@ for (addr2 = addr; addr2; addr2 = addr2->next)
       len = read(pfd[pipe_read], &message_length, sizeof(int));
       if (message_length > 0)
         {
-        len = read(pfd[pipe_read], big_buffer, message_length);
+        len = read(pfd[pipe_read], big_buffer, MIN(message_length, big_buffer_size));
 	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);
+	  while ((message_length -= len) > 0)		/* dump oversize data */
+	    len = read(pfd[pipe_read], big_buffer, big_buffer_size);
+	  }
         }
       }
     }
 
   else
     {
-    log_write(0, LOG_MAIN|LOG_PANIC, "failed to read delivery status for %s "
+    log_write(LOG_MAIN|LOG_PANIC, "failed to read delivery status for %s "
       "from delivery subprocess", addr2->unique);
     break;
     }
@@ -2566,17 +2578,17 @@ if (!shadowing)
 
       testharness_pause_ms(300);
 
-      DEBUG(D_deliver) debug_printf("journalling %s", big_buffer);
+      DEBUG(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",
+	log_write(LOG_MAIN|LOG_PANIC, "failed to update journal for %s: %s",
 	  big_buffer, strerror(errno));
       }
 
   /* Ensure the journal file is pushed out to disk. */
 
   if (EXIMfsync(journal_fd) < 0)
-    log_write(0, LOG_MAIN|LOG_PANIC, "failed to fsync journal: %s",
+    log_write(LOG_MAIN|LOG_PANIC, "failed to fsync journal: %s",
       strerror(errno));
   }
 
@@ -2591,7 +2603,7 @@ 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",
+    log_write(LOG_MAIN, "%s transport process vanished unexpectedly",
       addr->transport->drinst.driver_name);
     status = 0;
     break;
@@ -2604,7 +2616,7 @@ if ((status & 0xffff) != 0)
   int code = (msb == 0)? (lsb & 0x7f) : msb;
   if (msb != 0 || (code != SIGTERM && code != SIGKILL && code != SIGQUIT))
     addr->special_action = SPECIAL_FREEZE;
-  log_write(0, LOG_MAIN|LOG_PANIC, "%s transport process returned non-zero "
+  log_write(LOG_MAIN|LOG_PANIC, "%s transport process returned non-zero "
     "status 0x%04x: %s %d",
     addr->transport->drinst.driver_name,
     status,
@@ -2623,10 +2635,10 @@ if (addr->special_action == SPECIAL_WARN)
     int fd;
     pid_t ch_pid;
 
-    DEBUG(D_deliver) debug_printf("Warning message requested by transport\n");
+    DEBUG(deliver) debug_printf("Warning message requested by transport\n");
 
     if (!(warn_message = expand_string(warn_message)))
-      log_write(0, LOG_MAIN|LOG_PANIC, "Failed to expand %q (warning "
+      log_write(LOG_MAIN|LOG_PANIC, "Failed to expand %q (warning "
 	"message for %s transport): %s", addr->transport->warn_message,
 	addr->transport->drinst.name, expand_string_message);
 
@@ -2656,10 +2668,12 @@ 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. */
+the key for the hints database used for the concurrency count
+and a string for observability. */
 
 static BOOL
-tpt_parallel_check(transport_instance * tp, address_item * addr, uschar ** key)
+tpt_parallel_check(const transport_instance * tp, address_item * addr,
+  uschar ** key, uschar ** state)
 {
 const uschar * trname = tp->drinst.name;
 unsigned max_parallel;
@@ -2670,7 +2684,7 @@ 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 "
+  log_write(LOG_MAIN|LOG_PANIC, "Failed to expand max_parallel option "
 	"in %s transport (%s): %s", trname, addr->address,
 	expand_string_message);
   return TRUE;
@@ -2679,22 +2693,22 @@ if (expand_string_message)
 if (max_parallel > 0)
   {
   uschar * serialize_key = string_sprintf("tpt-serialize-%s", trname);
-  if (!enq_start(serialize_key, max_parallel))
+  unsigned running;
+  if (!(running = enq_start(serialize_key, max_parallel)))
     {
-    address_item * next;
-    DEBUG(D_transport)
+    DEBUG(transport)
       debug_printf("skipping tpt %s because concurrency limit %u reached\n",
 		  trname, 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));
+      } while ((addr = addr->next));
     return TRUE;
     }
   *key = serialize_key;
+  *state = string_sprintf(" (%u/max_parallel:%u)", running, max_parallel);
   }
 return FALSE;
 }
@@ -2731,7 +2745,7 @@ while (addr_local)
   int logflags = LOG_MAIN;
   int logchar = f.dont_deliver? '*' : '=';
   transport_instance * tp;
-  uschar * serialize_key = NULL;
+  uschar * serialize_key = NULL, * tpt_dummy;
   const uschar * trname;
 
   /* Pick the first undelivered address off the chain */
@@ -2740,7 +2754,7 @@ while (addr_local)
   addr_local = addr->next;
   addr->next = NULL;
 
-  DEBUG(D_deliver|D_transport)
+  DEBUG(deliver|transport)
     debug_printf("--------> %s <--------\n", addr->address);
 
   /* An internal disaster if there is no transport. Should not occur! */
@@ -2796,7 +2810,7 @@ while (addr_local)
       deliver_set_expansions(NULL);
       if (!batch_id)
         {
-        log_write(0, LOG_MAIN|LOG_PANIC, "Failed to expand batch_id option "
+        log_write(LOG_MAIN|LOG_PANIC, "Failed to expand batch_id option "
           "in %s transport (%s): %s", trname, addr->address,
           expand_string_message);
         batch_count = tp->batch_max;
@@ -2853,7 +2867,7 @@ while (addr_local)
         deliver_set_expansions(NULL);
         if (!bid)
           {
-          log_write(0, LOG_MAIN|LOG_PANIC, "Failed to expand batch_id option "
+          log_write(LOG_MAIN|LOG_PANIC, "Failed to expand batch_id option "
             "in %s transport (%s): %s", trname, next->address,
             expand_string_message);
           ok = FALSE;
@@ -2906,11 +2920,11 @@ while (addr_local)
 
   if (continue_retry_db && continue_retry_db != (open_db *)-1)
     {
-    DEBUG(D_hints_lookup) debug_printf("using cached retry hintsdb handle\n");
+    DEBUG(hints_lookup) debug_printf("using cached retry hintsdb handle\n");
     dbm_file = continue_retry_db;
     }
   else if (!(dbm_file = dbfn_open(US"retry", O_RDONLY, &dbblock, FALSE, TRUE)))
-    DEBUG(D_deliver|D_retry|D_hints_lookup)
+    DEBUG(deliver|retry|hints_lookup)
       debug_printf("no retry data available\n");
 
   addr2 = addr;
@@ -2919,65 +2933,57 @@ while (addr_local)
     {
     BOOL ok = TRUE;   /* to deliver this address */
 
-    if (f.queue_2stage)
-      {
-      DEBUG(D_deliver)
-	debug_printf_indent("no router retry check (ph1 qrun)\n");
-      }
-    else
+    /* Set up the retry key to include the domain or not, and change its
+    leading character from "R" to "T". Must make a copy before doing this,
+    because the old key may be pointed to from a "delete" retry item after
+    a routing delay. */
+    uschar * retry_key = string_copy(tp->retry_use_local_part
+		      ? addr2->address_retry_key : addr2->domain_retry_key);
+    *retry_key = 'T';
+
+    /* Inspect the retry data. If there is no hints file, delivery happens. */
+
+    if (dbm_file)
       {
-      /* Set up the retry key to include the domain or not, and change its
-      leading character from "R" to "T". Must make a copy before doing this,
-      because the old key may be pointed to from a "delete" retry item after
-      a routing delay. */
-      uschar * retry_key = string_copy(tp->retry_use_local_part
-			? addr2->address_retry_key : addr2->domain_retry_key);
-      *retry_key = 'T';
+      dbdata_retry * retry_record = dbfn_read(dbm_file, retry_key);
 
-      /* Inspect the retry data. If there is no hints file, delivery happens. */
+      /* If there is no retry record, delivery happens. If there is,
+      remember it exists so it can be deleted after a successful delivery. */
 
-      if (dbm_file)
+      if (retry_record)
 	{
-	dbdata_retry * retry_record = dbfn_read(dbm_file, retry_key);
+	setflag(addr2, af_lt_retry_exists);
 
-	/* If there is no retry record, delivery happens. If there is,
-	remember it exists so it can be deleted after a successful delivery. */
+	/* A retry record exists for this address. If queue running and not
+	forcing, inspect its contents. If the record is too old, or if its
+	retry time has come, or if it has passed its cutoff time, delivery
+	will go ahead. */
 
-	if (retry_record)
+	DEBUG(retry)
 	  {
-	  setflag(addr2, af_lt_retry_exists);
-
-	  /* A retry record exists for this address. If queue running and not
-	  forcing, inspect its contents. If the record is too old, or if its
-	  retry time has come, or if it has passed its cutoff time, delivery
-	  will go ahead. */
-
-	  DEBUG(D_retry)
-	    {
-	    debug_printf("retry record exists: age=%s ",
-	      readconf_printtime(now - retry_record->time_stamp));
-	    debug_printf("(max %s)\n", readconf_printtime(retry_data_expire));
-	    debug_printf("  time to retry = %s expired = %d\n",
-	      readconf_printtime(retry_record->next_try - now),
-	      retry_record->expired);
-	    }
+	  debug_printf("retry record exists: age=%s ",
+	    readconf_printtime(now - retry_record->gen.time_stamp));
+	  debug_printf("(max %s)\n", readconf_printtime(retry_data_expire));
+	  debug_printf("  time to retry = %s expired = %d\n",
+	    readconf_printtime(retry_record->next_try - now),
+	    retry_record->expired);
+	  }
 
-	  if (f.queue_running && !f.deliver_force)
-	    {
-	    ok = (now - retry_record->time_stamp > retry_data_expire)
-	      || (now >= retry_record->next_try)
-	      || retry_record->expired;
+	if (f.queue_running && !f.deliver_force)
+	  {
+	  ok = (now - retry_record->gen.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. */
 
-	    if (!ok)
-	      ok = retry_ultimate_address_timeout(retry_key, addr2->domain,
-		  retry_record, now);
-	    }
+	  if (!ok)
+	    ok = retry_ultimate_address_timeout(retry_key, addr2->domain,
+		retry_record, now);
 	  }
-	else DEBUG(D_retry) debug_printf("no retry record exists\n");
 	}
+      else DEBUG(retry) debug_printf("no retry record exists\n");
       }
 
     /* This address is to be delivered. Leave it on the chain. */
@@ -3007,7 +3013,7 @@ while (addr_local)
     if (dbm_file != continue_retry_db)
       { dbfn_close(dbm_file); dbm_file = NULL; }
     else
-      DEBUG(D_hints_lookup) debug_printf("retaining retry hintsdb handle\n");
+      DEBUG(hints_lookup) debug_printf("retaining retry hintsdb handle\n");
 
   /* If there are no addresses left on the chain, they all deferred. Loop
   for the next set of addresses. */
@@ -3018,7 +3024,7 @@ while (addr_local)
   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 (tpt_parallel_check(tp, addr, &serialize_key, &tpt_dummy))
     {
     if (expand_string_message)
       {
@@ -3066,7 +3072,7 @@ while (addr_local)
       if (Ustrcmp(stp->drinst.name, tp->shadow) == 0) break;
 
     if (!stp)
-      log_write(0, LOG_MAIN|LOG_PANIC, "shadow transport %q not found ",
+      log_write(LOG_MAIN|LOG_PANIC, "shadow transport %q not found ",
         tp->shadow);
 
     /* Pick off the addresses that have succeeded, and make clones. Put into
@@ -3096,7 +3102,7 @@ while (addr_local)
       const uschar * s_trname = stp->drinst.name;
       int save_count = transport_count;
 
-      DEBUG(D_deliver|D_transport)
+      DEBUG(deliver|transport)
         debug_printf(">>>>>>>>>>>>>>>> Shadow delivery >>>>>>>>>>>>>>>>\n");
       deliver_local(shadow_addr, TRUE);
 
@@ -3119,12 +3125,12 @@ while (addr_local)
 	      ? US"unknown error"
 	      : US"");
 
-        DEBUG(D_deliver|D_transport)
+        DEBUG(deliver|transport)
           debug_printf("%s shadow transport returned %s for %s\n",
             s_trname, rc_to_string(sresult), shadow_addr->address);
         }
 
-      DEBUG(D_deliver|D_transport)
+      DEBUG(deliver|transport)
         debug_printf(">>>>>>>>>>>>>>>> End shadow delivery >>>>>>>>>>>>>>>>\n");
 
       transport_count = save_count;   /* Restore original transport count */
@@ -3148,7 +3154,7 @@ while (addr_local)
     int result = addr2->transport_return;
     nextaddr = addr2->next;
 
-    DEBUG(D_deliver|D_transport)
+    DEBUG(deliver|transport)
       debug_printf("%s transport returned %s for %s\n",
         trname, rc_to_string(result), addr2->address);
 
@@ -3276,7 +3282,7 @@ while (  *aptr
   if (!*aptr) *aptr = moved;
   }
 
-DEBUG(D_deliver)
+DEBUG(deliver)
   {
   debug_printf("remote addresses after sorting:\n");
   for (address_item * addr = addr_remote; addr; addr = addr->next)
@@ -3354,7 +3360,7 @@ same channel (pipe).
 
 */
 
-DEBUG(D_deliver) debug_printf("reading pipe for subprocess %ld (%s)\n",
+DEBUG(deliver) debug_printf("reading pipe for subprocess %ld (%s)\n",
   (long)p->pid, eop? "ended" : "not ended yet");
 
 while (!done)
@@ -3367,7 +3373,7 @@ while (!done)
   size_t required = PIPE_HEADER_SIZE; /* first the pipehaeder, later the data */
   ssize_t got;
 
-  DEBUG(D_deliver)
+  DEBUG(deliver)
     debug_printf("expect %lu bytes (pipeheader) from tpt process %ld\n",
     (u_long)required, (long)pid);
 
@@ -3386,7 +3392,7 @@ while (!done)
     }
 
   pipeheader[PIPE_HEADER_SIZE] = '\0';
-  DEBUG(D_deliver)
+  DEBUG(deliver)
     debug_printf("got %ld bytes (pipeheader) '%c' from transport process %ld\n",
       (long) got, *id, (long)pid);
 
@@ -3405,7 +3411,7 @@ while (!done)
     }
   }
 
-  DEBUG(D_deliver)
+  DEBUG(deliver)
     debug_printf("expect %lu bytes (pipedata) from transport process %ld\n",
       (u_long)required, (long)pid);
 
@@ -3455,7 +3461,7 @@ while (!done)
     case 'R':
       if (!addr) goto ADDR_MISMATCH;
 
-      DEBUG(D_deliver|D_retry)
+      DEBUG(deliver|retry)
 	debug_printf("reading retry information for %s from subprocess\n",
 	  ptr+1);
 
@@ -3466,7 +3472,7 @@ while (!done)
 	  {
 	  if (!(r->flags & rf_delete)) break;	   /* It was not "delete" */
 	  *rp = r->next;                           /* Excise a delete item */
-	  DEBUG(D_deliver|D_retry)
+	  DEBUG(deliver|retry)
 	    debug_printf("  existing delete item dropped\n");
 	  }
 
@@ -3486,13 +3492,13 @@ while (!done)
 	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",
+	DEBUG(deliver|retry) debug_printf("  added %s item\n",
 	    r->flags & rf_delete ? "delete" : "retry");
 	}
 
       else
 	{
-	DEBUG(D_deliver|D_retry)
+	DEBUG(deliver|retry)
 	  debug_printf("  delete item not added: non-delete item exists\n");
 	ptr++;
 	while(*ptr++);
@@ -3602,7 +3608,7 @@ while (!done)
       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: addr->dsn_aware = %d (%s)\n",
+      DEBUG(deliver) debug_printf("DSN: addr->dsn_aware = %d (%s)\n",
 			addr->dsn_aware, dsn_aware_names[addr->dsn_aware]);
       break;
 
@@ -3658,7 +3664,7 @@ while (!done)
 #endif
 
 	case '0':	/* results of trying to send to this address */
-	  DEBUG(D_deliver) debug_printf("A0 %s tret %d (%s)\n",
+	  DEBUG(deliver) debug_printf("A0 %s tret %d (%s)\n",
 					  addr->address, *ptr, rc_names[*ptr]);
 	  addr->transport_return = *ptr++;
 	  addr->special_action = *ptr++;
@@ -3738,19 +3744,19 @@ while (!done)
 	{
 	case '0':			/* End marker */
 	  done = TRUE;
-	  DEBUG(D_deliver) debug_printf("Z0%c item read\n", *ptr);
+	  DEBUG(deliver) debug_printf("Z0%c item read\n", *ptr);
 	  break;
 	case '1':			/* Suggested continuation message */
 	  Ustrncpy(continue_next_id, ptr, MESSAGE_ID_LENGTH);
 	  continue_sequence = atoi(CS ptr + MESSAGE_ID_LENGTH + 1);
-	  DEBUG(D_deliver) debug_printf("continue_next_id: %s seq %d\n",
+	  DEBUG(deliver) debug_printf("continue_next_id: %s seq %d\n",
 					continue_next_id, continue_sequence);
 	  break;
 	case '2':			/* Continued transport, host & addr */
 	  {
 	  int recvd_fd;
 
-	  DEBUG(D_any) if (Ustrcmp(process_purpose, "continued-delivery") != 0)
+	  DEBUG(any) if (Ustrcmp(process_purpose, "continued-delivery") != 0)
 	    debug_printf("%s becomes continued-delivery\n", process_purpose);
 	  process_purpose = US"continued-delivery";
 	  continue_transport = string_copy(ptr);	while (*ptr++) ;
@@ -3761,11 +3767,12 @@ while (!done)
 	  dup2((recvd_fd = recv_fd_from_sock(fd)), 0);
 	  close(recvd_fd);
 
-	  DEBUG(D_deliver)
+	  /*XXX continue_host_port relies on a preceding A0 record */
+	  DEBUG(deliver)
 	    debug_printf("continue: fd %d tpt %s host '%s' addr '%s':%d"
 			 " seq %d\n",
 			  recvd_fd, continue_transport, continue_hostname,
-			  continue_host_address, deliver_host_port,
+			  continue_host_address, continue_host_port,
 			  continue_sequence);
 	  break;
 	  }
@@ -3855,7 +3862,7 @@ if (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",
+    log_write(LOG_MAIN|LOG_PANIC, "Delivery status for %s: %s\n",
 	      addr->address, addr->message);
     }
 
@@ -3954,7 +3961,7 @@ while (addr)
     addr->host_list = addr->fallback_hosts;
     addr->next = addr_fallback;
     addr_fallback = addr;
-    DEBUG(D_deliver) debug_printf("%s queued for fallback host(s)\n", addr->address);
+    DEBUG(deliver) debug_printf("%s queued for fallback host(s)\n", addr->address);
     }
 
   /* If msg is set (=> unexpected problem), set it in the address before
@@ -3996,20 +4003,22 @@ can be created, or when waiting for the last ones to complete. It must wait for
 the completion of one subprocess, empty the control block slot, and return a
 pointer to the address chain.
 
-Arguments:    none
-Returns:      pointer to the chain of addresses handled by the process;
-              NULL if no subprocess found - this is an unexpected error
+Arguments:
+  reason:	observability: reason for call
+
+Returns:	pointer to the chain of addresses handled by the process;
+		NULL if no subprocess found - this is an unexpected error
 */
 
 static address_item *
-par_wait(void)
+par_wait(const uschar * reason)
 {
 int poffset, status;
 address_item * addrlist;
 pid_t pid;
 
 set_process_info("delivering %s: waiting for a remote delivery subprocess "
-  "to finish", message_id);
+  "to finish (%s)", message_id, reason);
 
 /* Loop until either a subprocess completes, or there are no subprocesses in
 existence - in which case give an error return. We cannot proceed just by
@@ -4077,7 +4086,7 @@ for (;;)   /* Normally we do not repeat this loop */
       {
       if (errno != ECHILD) continue;   /* Repeats the waitpid() */
 
-      DEBUG(D_deliver)
+      DEBUG(deliver)
         debug_printf("waitpid() returned -1/ECHILD: checking explicitly "
           "for process existence\n");
 
@@ -4085,7 +4094,7 @@ for (;;)   /* Normally we do not repeat this loop */
         {
         if ((pid = parlist[poffset].pid) != 0 && kill(pid, 0) == 0)
           {
-          DEBUG(D_deliver) debug_printf("process %ld still exists: assume "
+          DEBUG(deliver) debug_printf("process %ld still exists: assume "
             "stolen by strace\n", (long)pid);
           break;   /* With poffset set */
           }
@@ -4093,7 +4102,7 @@ for (;;)   /* Normally we do not repeat this loop */
 
       if (poffset >= remote_max_parallel)
         {
-        DEBUG(D_deliver) debug_printf("*** no delivery children found\n");
+        DEBUG(deliver) debug_printf("*** no delivery children found\n");
 	return NULL;	/* This is the error return */
         }
       }
@@ -4103,7 +4112,7 @@ for (;;)   /* Normally we do not repeat this loop */
     subprocess, but there are no completed subprocesses. See if any pipes are
     ready with any data for reading. */
 
-    DEBUG(D_deliver) debug_printf("polling subprocess pipes\n");
+    DEBUG(deliver) debug_printf("polling subprocess pipes\n");
 
     for (poffset = 0; poffset < remote_max_parallel; poffset++)
       if (parlist[poffset].pid != 0)
@@ -4146,7 +4155,7 @@ for (;;)   /* Normally we do not repeat this loop */
             pid_t endedpid = waitpid(pid, &status, 0);
             if (endedpid == pid) goto PROCESS_DONE;
             if (endedpid != (pid_t)(-1) || errno != EINTR)
-              log_write_die(0, LOG_MAIN, "Unexpected error return "
+              log_write_die(LOG_MAIN, "Unexpected error return "
                 "%d (errno = %d) from waitpid() for process %ld",
                 (int)endedpid, errno, (long)pid);
             }
@@ -4170,7 +4179,7 @@ for (;;)   /* Normally we do not repeat this loop */
   /* This situation is an error, but it's probably better to carry on looking
   for another process than to give up (as we used to do). */
 
-  log_write(0, LOG_MAIN|LOG_PANIC, "Process %ld finished: not found in remote "
+  log_write(LOG_MAIN|LOG_PANIC, "Process %ld finished: not found in remote "
     "transport process list", (long)pid);
   }  /* End of the "for" loop */
 
@@ -4179,7 +4188,7 @@ the process in pid has been wait()ed for. */
 
 PROCESS_DONE:
 
-DEBUG(D_deliver)
+DEBUG(deliver)
   {
   if (status == 0)
     debug_printf("remote delivery process %ld ended\n", (long)pid);
@@ -4256,19 +4265,20 @@ log and proceed as if all done.
 Arguments:
   max         maximum number of subprocesses to leave running
   fallback    TRUE if processing fallback hosts
+  reason      observability: reason for call
 
 Returns:      nothing
 */
 
 static void
-par_reduce(int max, BOOL fallback)
+par_reduce(int max, BOOL fallback, const uschar * reason)
 {
 while (parcount > max)
   {
-  address_item * doneaddr = par_wait();
+  address_item * doneaddr = par_wait(reason);
   if (!doneaddr)
     {
-    log_write(0, LOG_MAIN|LOG_PANIC,
+    log_write(LOG_MAIN|LOG_PANIC,
       "remote delivery process count got out of step");
     parcount = 0;
     }
@@ -4300,7 +4310,7 @@ ssize_t ret;
 /* complain to log if someone tries with buffer sizes we can't handle*/
 
 if (size > BIG_BUFFER_SIZE-1)
-  log_write_die(0, LOG_MAIN,
+  log_write_die(LOG_MAIN,
     "Failed writing transport result to pipe: can't handle buffers > %d bytes. truncating!\n",
       BIG_BUFFER_SIZE-1);
   /*NOTREACHED*/
@@ -4311,13 +4321,13 @@ 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_die(0, LOG_MAIN, "header snprintf failed\n");
+  log_write_die(LOG_MAIN, "header snprintf failed\n");
 
-DEBUG(D_deliver) debug_printf("header write id:%c,subid:%c,size:%ld,final:%s\n",
+DEBUG(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_die(0, LOG_MAIN,
+  log_write_die(LOG_MAIN,
     "Failed writing transport result to pipe (%ld of %ld bytes): %s",
     (long)ret, (long)total_len, ret == -1 ? strerror(errno) : "short write");
 }
@@ -4357,6 +4367,7 @@ static BOOL
 do_remote_deliveries(BOOL fallback)
 {
 int parmax, poffset;
+const uschar * plimit_reason = US"remote_max_parallel";
 
 parcount = 0;    /* Number of executing subprocesses */
 
@@ -4364,7 +4375,8 @@ parcount = 0;    /* Number of executing subprocesses */
 We use a local variable (parmax) to hold the maximum number of processes;
 this gets reduced from remote_max_parallel if we can't create enough pipes. */
 
-if (continue_transport) remote_max_parallel = 1;
+if (continue_transport)
+  { remote_max_parallel = 1; plimit_reason = US"continue_transport"; }
 parmax = remote_max_parallel;
 
 /* If the data for keeping a list of processes hasn't yet been
@@ -4388,19 +4400,19 @@ for (int delivery_count = 0; addr_remote; delivery_count++)
   uid_t uid;
   gid_t gid;
   int pfd[2];
-  int address_count = 1, address_count_max;
+  unsigned address_count = 1, address_count_max;
   BOOL pipe_done = FALSE, multi_domain, use_initgroups;
   transport_instance * tp;
   address_item ** anchor = &addr_remote;
   address_item * addr = addr_remote, * last = addr, * next;
-  uschar * serialize_key = NULL, * panicmsg;
+  uschar * serialize_key = NULL, * tpt_parallel_level, * panicmsg;
 
   /* Pull the first address right off the list. */
 
   addr_remote = addr->next;
   addr->next = NULL;
 
-  DEBUG(D_deliver|D_transport)
+  DEBUG(deliver|transport)
     debug_printf("--------> %s <--------\n", addr->address);
 
   /* If no transport has been set, there has been a big screw-up somewhere. */
@@ -4444,7 +4456,7 @@ So look out for the place it gets used.
   if (tp->expand_multi_domain)
     deliver_set_expansions(addr);
 
-  if (exp_bool(addr, US"transport", tp->drinst.name, D_transport,
+  if (exp_bool(addr, US"transport", tp->drinst.name, IS_DEBUG(transport),
 		US"multi_domain", tp->multi_domain, tp->expand_multi_domain,
 		&multi_domain) != OK)
     {
@@ -4457,7 +4469,7 @@ So look out for the place it gets used.
   unlimited, which is forced for the MUA wrapper case and if the
   value could vary depending on the messages.
   For those, we only split (below) by (tpt,dest,erraddr,hdrs) and rely on the
-  transport splitting further by max_rcp.  So we potentially lose some
+  transport splitting further by max_rcpt.  So we potentially lose some
   parallellism. */
 
   GET_OPTION("max_rcpt");
@@ -4558,8 +4570,8 @@ Does that also apply to address_data?
        && (  !multi_domain
 	  || (  (
 		(void)(!tp->expand_multi_domain || ((void)deliver_set_expansions(next), 1)),
-	        exp_bool(addr,
-		    US"transport", next->transport->drinst.name, D_transport,
+	        exp_bool(addr, US"transport", next->transport->drinst.name,
+		    IS_DEBUG(transport),
 		    US"multi_domain", next->transport->multi_domain,
 		    next->transport->expand_multi_domain, &md) == OK
 	        )
@@ -4573,7 +4585,8 @@ Does that also apply to address_data?
       last = next;
       address_count++;
       }
-    else anchor = &(next->next);
+    else
+      anchor = &next->next;
     deliver_set_expansions(NULL);
     }
 
@@ -4591,7 +4604,8 @@ Does that also apply to address_data?
   The hints DB entry is decremented in par_reduce(), when we reap the
   transport process. */
 
-  if (tpt_parallel_check(tp, addr, &serialize_key))
+  tpt_parallel_level = US"";
+  if (tpt_parallel_check(tp, addr, &serialize_key, &tpt_parallel_level))
     if ((panicmsg = expand_string_message))
       goto panic_continue;
     else
@@ -4618,8 +4632,10 @@ Does that also apply to address_data?
       return_path = new_return_path;
     else if (!f.expand_string_forcedfail)
       {
-      panicmsg = string_sprintf("Failed to expand return path %q: %s",
-	tp->return_path, expand_string_message);
+      common_error(FALSE, addr, ERRNO_EXPANDFAIL,
+		    US "Failed to expand return path %q: %s",
+		    tp->return_path, expand_string_message);
+      panicmsg = addr->message;
       goto enq_continue;
       }
     }
@@ -4654,7 +4670,7 @@ Does that also apply to address_data?
 
   if (cutthrough.cctx.sock >= 0 && cutthrough.callout_hold_only)
     {
-    DEBUG(D_deliver)
+    DEBUG(deliver)
       debug_printf("lazy-callout-close: have conn still open from verification\n");
     continue_transport = cutthrough.transport;
     continue_hostname = string_copy(cutthrough.host.name);
@@ -4713,7 +4729,7 @@ Does that also apply to address_data?
 
     if (!ok)
       {
-      DEBUG(D_deliver) debug_printf("not suitable for continue_transport (%s)\n",
+      DEBUG(deliver) debug_printf("not suitable for continue_transport (%s)\n",
 	Ustrcmp(continue_transport, tp->drinst.name) != 0
 	? string_sprintf("tpt %s vs %s", continue_transport, tp->drinst.name)
 	: string_sprintf("no host matching %s", continue_hostname));
@@ -4724,7 +4740,7 @@ Does that also apply to address_data?
 	for (next = addr; ; next = next->next)
           {
           next->host_list = next->fallback_hosts;
-          DEBUG(D_deliver)
+          DEBUG(deliver)
 	    debug_printf("%s queued for fallback host(s)\n", next->address);
           if (!next->next) break;
           }
@@ -4736,7 +4752,7 @@ Does that also apply to address_data?
 	{
 	for (next = addr; ; next = next->next)
 	  {
-	  DEBUG(D_deliver) debug_printf(" %s to def list\n", next->address);
+	  DEBUG(deliver) debug_printf(" %s to def list\n", next->address);
           if (!next->next) break;
 	  }
 	next->next = addr_defer;
@@ -4765,7 +4781,7 @@ parmax * tpt-max is exceeded? */
 	    { f.continue_more = TRUE; break; }
       }
     }
-  else DEBUG(D_deliver)
+  else DEBUG(deliver)
     debug_printf(
       "not reached parallelism limit (%d/%d) so not setting continue_more\n",
       parcount+1, remote_max_parallel);
@@ -4788,9 +4804,12 @@ parmax * tpt-max is exceeded? */
 
   while (!pipe_done)
     {
-    if (socketpair(AF_UNIX, SOCK_STREAM, 0, pfd) == 0) pipe_done = TRUE;
-    else if (parcount > 0) parmax = parcount;
-    else break;
+    if (socketpair(AF_UNIX, SOCK_STREAM, 0, pfd) == 0)
+      pipe_done = TRUE;
+    else if (parcount > 0)
+      { parmax = parcount; plimit_reason = US"fildesc limit"; }
+    else
+      break;
 
     /* We need to make the reading end of the pipe non-blocking. There are
     two different options for this. Exim is cunningly (I hope!) coded so
@@ -4811,7 +4830,7 @@ all pipes, so I do not see a reason to use non-blocking IO here
     to finish. If we ran out of file descriptors, parmax will have been reduced
     from its initial value of remote_max_parallel. */
 
-    par_reduce(parmax - 1, fallback);
+    par_reduce(parmax - 1, fallback, plimit_reason);
     }
 
   /* If we failed to create a pipe and there were no processes to wait
@@ -4900,8 +4919,8 @@ do_remote_deliveries par_reduce par_wait par_read_pipe
 
     /* Show pids on debug output if parallelism possible */
 
-    if (parmax > 1 && (parcount > 0 || addr_remote))
-      DEBUG(D_any|D_v) debug_selector |= D_pid;
+    if (parmax > 1 && (parcount > 0 || addr_remote)) DEBUG(any|v)
+      debug_modify_channel(US"+pid");
 
     /* Reset the random number generator, so different processes don't all
     have the same sequence. In the test harness we want different, but
@@ -4936,7 +4955,7 @@ do_remote_deliveries par_reduce par_wait par_read_pipe
 
     if (  (deliver_datafile = Uopen(fname, EXIM_CLOEXEC | O_RDWR | O_APPEND, 0))
 	< 0)
-      log_write_die(0, LOG_MAIN, "Failed to reopen %s for remote "
+      log_write_die(LOG_MAIN, "Failed to reopen %s for remote "
         "parallel delivery: %s", fname, strerror(errno));
     }
 
@@ -4956,7 +4975,9 @@ do_remote_deliveries par_reduce par_wait par_read_pipe
     of bytes written. */
 
     (void)close(pfd[pipe_read]);
-    set_process_info("delivering %s using %s", message_id, tp->drinst.name);
+    set_process_info("delivering %s (%u addr%s) using %s%s",
+		    message_id, address_count, address_count>1?"s":"",
+		    tp->drinst.name, tpt_parallel_level);
     debug_print_string(tp->debug_string);
 
       {
@@ -5136,7 +5157,7 @@ do_remote_deliveries par_reduce par_wait par_read_pipe
 #ifndef DISABLE_DKIM
       if (addr->dkim_used && LOGGING(dkim_verbose))
 	{
-	DEBUG(D_deliver) debug_printf("dkim used: %s\n", addr->dkim_used);
+	DEBUG(deliver) debug_printf("dkim used: %s\n", addr->dkim_used);
 	ptr = big_buffer + sprintf(CS big_buffer, "%.128s", addr->dkim_used) + 1;
         rmt_dlv_checked_write(fd, 'A', '4', big_buffer, ptr - big_buffer);
 	}
@@ -5144,7 +5165,7 @@ do_remote_deliveries par_reduce par_wait par_read_pipe
 
       if (testflag(addr, af_new_conn) || testflag(addr, af_cont_conn))
 	{
-	DEBUG(D_deliver) debug_printf("%scontinued-connection\n",
+	DEBUG(deliver) debug_printf("%scontinued-connection\n",
 	  testflag(addr, af_new_conn) ? "non-" : "");
 	big_buffer[0] = testflag(addr, af_new_conn) ? BIT(1) : BIT(2);
         rmt_dlv_checked_write(fd, 'A', '3', big_buffer, 1);
@@ -5156,9 +5177,9 @@ do_remote_deliveries par_reduce par_wait par_read_pipe
 	ptr = big_buffer;
 	if (proxy_local_address)
 	  {
-	  DEBUG(D_deliver) debug_printf("proxy_local_address '%s'\n", proxy_local_address);
+	  DEBUG(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);
+	  DEBUG(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);
 	  }
@@ -5172,11 +5193,11 @@ do_remote_deliveries par_reduce par_wait par_read_pipe
 /*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);
+	DEBUG(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);
+	  DEBUG(deliver) debug_printf("helo_response '%s'\n", addr->helo_response);
 	  ptr += sprintf(CS ptr, "%.128s", addr->helo_response) + 1;
 	  }
 	else
@@ -5187,7 +5208,7 @@ do_remote_deliveries par_reduce par_wait par_read_pipe
 
       /* The rest of the information goes in an 'A0' item. */
 #ifdef notdef
-      DEBUG(D_deliver)
+      DEBUG(deliver)
 	debug_printf("%s %s for MAIL\n",
 	  addr->special_action == '=' ? "initial RCPT"
 	  : addr->special_action == '-' ? "additional RCPT" : "?",
@@ -5337,6 +5358,7 @@ do_remote_deliveries par_reduce par_wait par_read_pipe
     (void)close(pfd[pipe_read]);
     panicmsg = string_sprintf("fork failed for remote delivery to %s: %s",
         addr->domain, strerror(errno));
+    addr->basic_errno = errno;
     goto enq_continue;
     }
 
@@ -5364,7 +5386,7 @@ do_remote_deliveries par_reduce par_wait par_read_pipe
 
   if (continue_transport)
     {
-    par_reduce(0, fallback);
+    par_reduce(0, fallback, US"continue_transport wait-complete");
     if (!*continue_next_id && continue_wait_db)
 	{ dbfn_close_multi(continue_wait_db); continue_wait_db = NULL; }
 
@@ -5393,7 +5415,7 @@ panic_continue:
 /* Reached the end of the list of addresses. Wait for all the subprocesses that
 are still running and post-process their addresses. */
 
-par_reduce(0, fallback);
+par_reduce(0, fallback, US"delivery wait-all-complete");
 return TRUE;
 }
 
@@ -5488,7 +5510,7 @@ if (percent_hack_domains)
     addr->unique = string_copy(new_address);
     addr->domain = deliver_domain;
     addr->cc_local_part = local_part;
-    DEBUG(D_deliver) debug_printf("%%-hack changed address to: %s\n",
+    DEBUG(deliver) debug_printf("%%-hack changed address to: %s\n",
       addr->address);
     }
   }
@@ -5539,7 +5561,7 @@ for (;;)
 if ((yield = expand_string(string_from_gstring(para))))
   return yield;
 
-log_write(0, LOG_MAIN|LOG_PANIC, "Failed to expand string from "
+log_write(LOG_MAIN|LOG_PANIC, "Failed to expand string from "
   "bounce_message_file or warn_message_file (%s): %s", which,
   expand_string_message);
 return NULL;
@@ -5719,9 +5741,8 @@ while (*s)
 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.
+If af_pass_message and addr->message are set, we assume the latter contains,
+as a trailing component, the error information.
 
 Arguments:
   addr         the address
@@ -5731,7 +5752,7 @@ Returns:       nothing
 */
 
 static void
-print_dsn_diagnostic_code(const address_item *addr, FILE *f)
+print_dsn_diagnostic_code(const address_item * addr, FILE * f)
 {
 uschar * s = testflag(addr, af_pass_message) ? addr->message : NULL;
 unsigned cnt;
@@ -5740,10 +5761,15 @@ unsigned cnt;
 if (!s)
   return;
 
-DEBUG(D_deliver)
+DEBUG(deliver)
   debug_printf("DSN Diagnostic-Code: addr->message = %s\n", addr->message);
 
 /* search first ": ". we assume to find the remote-MTA answer there */
+
+/*XXX Fragile.  Very easy for what is in addr->message to be changed
+in maintenance, eg. adding an earlier ": ".  But, also, a remote reponse
+could include one.  Do we need a separate copy of the response? */
+
 if (!(s = Ustrstr(addr->message, ": ")))
   return;				/* not found, bail out */
 
@@ -5754,7 +5780,7 @@ while (*s)
   {
   if (cnt > 950)	/* RFC line length limit: 998 */
     {
-    DEBUG(D_deliver) debug_printf("print_dsn_diagnostic_code() truncated line\n");
+    DEBUG(deliver) debug_printf("print_dsn_diagnostic_code() truncated line\n");
     fputs("[truncated]", f);
     break;
     }
@@ -5806,7 +5832,7 @@ while ((addr = *anchor))
     anchor = &addr->next;
   else if ((tnode = tree_search(tree_duplicates, addr->unique)))
     {
-    DEBUG(D_deliver|D_route)
+    DEBUG(deliver|route)
       debug_printf("%s is a duplicate address: discarded\n", addr->unique);
     *anchor = addr->next;
     addr->dupof = tnode->data.ptr;
@@ -5856,7 +5882,7 @@ int qt;
 if (  f.running_in_test_harness && *fudged_queue_times
    && (qt = readconf_readtime(fudged_queue_times, '/', FALSE)) >= 0)
   {
-  DEBUG(D_deliver) debug_printf("fudged queue_times = %s\n",
+  DEBUG(deliver) debug_printf("fudged queue_times = %s\n",
     fudged_queue_times);
   return qt;
   }
@@ -5873,14 +5899,14 @@ const uschar * s = expand_string(filename);
 FILE * fp = NULL;
 
 if (!s || !*s)
-  log_write(0, LOG_MAIN|LOG_PANIC,
+  log_write(LOG_MAIN|LOG_PANIC,
     "Failed to expand %s: '%s'\n", optname, filename);
 else if (*s != '/' || is_tainted(s))
-  log_write(0, LOG_MAIN|LOG_PANIC,
+  log_write(LOG_MAIN|LOG_PANIC,
     "%s is not %s after expansion: '%s'\n",
     optname, *s == '/' ? "untainted" : "absolute", s);
 else if (!(fp = Ufopen(s, "rb")))
-  log_write(0, LOG_MAIN|LOG_PANIC, "Failed to open %s for %s "
+  log_write(LOG_MAIN|LOG_PANIC, "Failed to open %s for %s "
     "message texts: %s", s, reason, strerror(errno));
 return fp;
 }
@@ -5912,6 +5938,16 @@ fprintf(fp, "%s\n", wrap_header(string_from_gstring(g), 79, 1023, US" ", 1));
 *              Send a bounce message             *
 *************************************************/
 
+static uschar *
+get_bounce_charset(void)
+{
+uschar * charset;
+GET_OPTION("bounce_charset");
+return   !(charset = expand_string(bounce_charset))
+      || Ustrcmp(charset, "us-ascii") != 0 && Ustrcmp(charset, "UTF-8") != 0
+ ? US"us-ascii" : charset;
+}
+
 /* 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 accessed by $bounce_recipient
@@ -5929,9 +5965,10 @@ if (!(bounce_recipient = addr_failed->prop.errors_address))
 /* Make a subprocess to send a message, using its stdin */
 
 if ((pid = child_open_exim(&fd, US"bounce-message")) < 0)
-  log_write_die(0, LOG_MAIN, "Process %ld (parent %ld) failed to "
+  log_write_die(LOG_MAIN,
+    "Process " PID_T_FMT " (parent " PID_T_FMT ") failed to "
     "create child process to send failure message: %s",
-    (long)getpid(), (long)getppid(), strerror(errno));
+    getpid(), getppid(), strerror(errno));
 
 /* Creation of child succeeded */
 
@@ -5950,7 +5987,7 @@ else
   address_item * msgchain = NULL, ** pmsgchain = &msgchain;
   address_item * handled_addr = NULL;
 
-  DEBUG(D_deliver)
+  DEBUG(deliver)
     debug_printf("sending error message to: %s\n", bounce_recipient);
 
   /* Scan the addresses for all that have the same errors address, removing
@@ -6033,9 +6070,10 @@ else
       to_sender? ": returning message to sender" : "");
 
   /* output human readable part as text/plain section */
+  GET_OPTION("bounce_charset");
   fprintf(fp, "--%s\n"
-      "Content-type: text/plain; charset=us-ascii\n\n",
-    bound);
+      "Content-type: text/plain; charset=%s\n\n",
+    bound, get_bounce_charset());
 
   if ((emf_text = next_emf(emf, US"intro")))
     fprintf(fp, "%s", CS emf_text);
@@ -6119,7 +6157,7 @@ wording. */
     else
       fprintf(fp,
 	"The following text was generated during the delivery "
-	"attempt%s:\n", (filecount > 1)? "s" : "");
+	"attempt%s:\n", filecount > 1 ? "s" : "");
 
     for (address_item * addr = msgchain; addr; addr = nextaddr)
       {
@@ -6336,7 +6374,7 @@ wording. */
       }
     deliver_msglog("Process failed (%d) when writing error message "
       "to %s%s", rc, bounce_recipient, s);
-    log_write(0, LOG_MAIN, "Process failed (%d) when writing error message "
+    log_write(LOG_MAIN, "Process failed (%d) when writing error message "
       "to %s%s", rc, bounce_recipient, s);
     }
 
@@ -6406,8 +6444,8 @@ else
 
 /* output human readable part as text/plain section */
 fprintf(f, "--%s\n"
-    "Content-type: text/plain; charset=us-ascii\n\n",
-  bound);
+    "Content-type: text/plain; charset=%s\n\n",
+  bound, get_bounce_charset());
 
 if ((wmf_text = next_emf(wmf, US"intro")))
   fprintf(f, "%s", CS wmf_text);
@@ -6536,21 +6574,21 @@ return child_close(pid, 0) == 0;
 *              Send a success-DSN                *
 *************************************************/
 
-static void
-maybe_send_dsn(const address_item * const addr_succeed)
+static BOOL
+maybe_send_dsn(const address_item * const addr_succeed, BOOL has_privs)
 {
 address_item * addr_senddsn = NULL;
 
 for (const address_item * a = addr_succeed; a; a = a->next)
   {
   /* af_ignore_error not honored here. it's not an error */
-  DEBUG(D_deliver) debug_printf("DSN: processing router : %s\n"
+  DEBUG(deliver) debug_printf("DSN: processing router : %s\n"
       "DSN: processing successful delivery address: %s\n"
       "DSN: Sender_address: %s\n"
       "DSN: orcpt: %s  flags: 0x%x\n"
       "DSN: envid: %s  ret: %d\n"
       "DSN: Final recipient: %s\n"
-      "DSN: Remote SMTP server supports DSN: %d\n",
+      "DSN: Remote SMTP server supports DSN: %s\n",
       a->router ? a->router->drinst.name : US"(unknown)",
       a->address,
       sender_address,
@@ -6558,7 +6596,7 @@ for (const address_item * a = addr_succeed; a; a = a->next)
       a->dsn_flags,
       dsn_envid ? dsn_envid : US"NULL", dsn_ret,
       a->address,
-      a->dsn_aware
+      dsn_aware_names[a->dsn_aware]
       );
 
   /* send report if next hop not DSN aware or a router flagged "last DSN hop"
@@ -6575,23 +6613,32 @@ for (const address_item * a = addr_succeed; a; a = a->next)
     addr_senddsn->next = addr_next;
     }
   else
-    DEBUG(D_deliver) debug_printf("DSN: not sending DSN success message\n");
+    DEBUG(deliver) debug_printf("DSN: not sending DSN success message\n");
   }
 
 if (addr_senddsn)
   {				/* create exim process to send message */
   int fd;
-  pid_t pid = child_open_exim(&fd, US"DSN");
+  pid_t pid;
+
+  if (has_privs)
+    {
+    exim_setugid(exim_uid, exim_gid, FALSE, US"post-delivery DSN gen");
+    has_privs = FALSE;
+    }
+
+  pid = child_open_exim(&fd, US"DSN");
 
-  DEBUG(D_deliver) debug_printf("DSN: child_open_exim returns: %ld\n", (long)pid);
+  DEBUG(deliver) debug_printf("DSN: child_open_exim returns: " PID_T_FMT "\n", pid);
 
   if (pid < 0)  /* Creation of child failed */
     {
-    log_write_die(0, LOG_MAIN, "Process %ld (parent %ld) failed to "
+    log_write_die(LOG_MAIN,
+      "Process " PID_T_FMT " (parent " PID_T_FMT ") failed to "
       "create child process to send success-dsn message: %s",
-      (long)getpid(), (long)getppid(), strerror(errno));
+      getpid(), getppid(), strerror(errno));
 
-    DEBUG(D_deliver) debug_printf("DSN: child_open_exim failed\n");
+    DEBUG(deliver) debug_printf("DSN: child_open_exim failed\n");
     }
   else  /* Creation of child succeeded */
     {
@@ -6600,12 +6647,12 @@ if (addr_senddsn)
     uschar * bound;
     transport_ctx tctx = {{0}};
 
-    DEBUG(D_deliver)
+    DEBUG(deliver)
       debug_printf("sending success-dsn to: %s\n", sender_address);
 
     /* build unique id for MIME boundary */
     bound = string_sprintf(TIME_T_FMT "-eximdsn-%d", time(NULL), rand());
-    DEBUG(D_deliver) debug_printf("DSN: MIME boundary: %s\n", bound);
+    DEBUG(deliver) debug_printf("DSN: MIME boundary: %s\n", bound);
 
     if (errors_reply_to)
       fprintf(f, "Reply-To: %s\n", errors_reply_to);
@@ -6621,11 +6668,11 @@ if (addr_senddsn)
 	"MIME-Version: 1.0\n\n"
 
 	"--%s\n"
-	"Content-type: text/plain; charset=us-ascii\n\n"
+	"Content-type: text/plain; charset=%s\n\n"
 
 	"This message was created automatically by mail delivery software.\n"
 	" ----- The following addresses had successful delivery notifications -----\n",
-      bound, bound);
+      bound, bound, get_bounce_charset());
 
     for (address_item * a = addr_senddsn; a; a = a->next)
       fprintf(f, "<%s> (relayed %s)\n\n",
@@ -6686,6 +6733,15 @@ if (addr_senddsn)
     (void) child_close(pid, 0);     /* Waits for child to close, no timeout */
     }
   }
+return has_privs;
+}
+
+static inline BOOL
+drop_privs(const uschar * why, BOOL has_privs)
+{
+if (has_privs)
+  exim_setugid(exim_uid, exim_gid, FALSE, US"post-delivery warn gen");
+return FALSE;
 }
 
 /*************************************************
@@ -6734,8 +6790,13 @@ time_t now;
 address_item * addr_last;
 uschar * filter_message, * info;
 open_db dbblock, * dbm_file = NULL;
+BOOL has_privs = TRUE;
 rmark reset_point;
 
+#ifndef DISABLE_EVENT
+(void) event_raise(event_action, US"proc:deliver", id, NULL);
+#endif
+
 CONTINUED_ID:
 reset_point = store_mark();
 
@@ -6759,9 +6820,9 @@ D_queue_run is set or in verbose mode. */
 
 set_process_info("%s", info);
 
-if (  !(debug_selector & D_process_info)
-   && (debug_selector & (D_deliver|D_queue_run|D_v))
-   )
+DEBUG(process_info)
+  ;
+else DEBUG(deliver|queue_run|v)
   debug_printf("%s\n", info);
 
 /* Ensure that we catch any subprocesses that are created. Although Exim
@@ -6835,13 +6896,13 @@ give up; if the message has been around for sufficiently long, remove it. */
       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: "
+	log_write(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);
+	log_write(LOG_MAIN, "Format error in spool file %s", spoolname);
       }
     else
-      log_write(0, LOG_MAIN, "Error reading spool file %s: %s", spoolname,
+      log_write(LOG_MAIN, "Error reading spool file %s: %s", spoolname,
 	strerror(errno));
 
     /* If we managed to read the envelope data, received_time contains the
@@ -6864,7 +6925,7 @@ give up; if the message has been around for sufficiently long, remove it. */
       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",
+      log_write(LOG_MAIN, "Message removed because older than %s",
 	readconf_printtime(keep_malformed));
       }
 
@@ -6898,7 +6959,7 @@ Otherwise it might be needed again. */
       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 "
+      DEBUG(deliver) debug_printf("Previously delivered address %s taken from "
 	"journal file\n", big_buffer);
       }
     rewind(jread);
@@ -6912,7 +6973,7 @@ Otherwise it might be needed again. */
     }
   else if (errno != ENOENT)
     {
-    log_write(0, LOG_MAIN|LOG_PANIC, "attempt to open journal for reading gave: "
+    log_write(LOG_MAIN|LOG_PANIC, "attempt to open journal for reading gave: "
       "%s", strerror(errno));
     return continue_closedown();   /* yields DELIVER_NOT_ATTEMPTED */
     }
@@ -6923,7 +6984,7 @@ Otherwise it might be needed again. */
     {
     (void)close(deliver_datafile);
     deliver_datafile = -1;
-    log_write(0, LOG_MAIN, "Spool error: no recipients for %s", fname);
+    log_write(LOG_MAIN, "Spool error: no recipients for %s", fname);
     return continue_closedown();   /* yields DELIVER_NOT_ATTEMPTED */
     }
   }
@@ -6953,7 +7014,7 @@ if (f.deliver_freeze)
 
   if (timeout_frozen_after > 0 && message_age >= timeout_frozen_after)
     {
-    log_write(0, LOG_MAIN, "cancelled by timeout_frozen_after");
+    log_write(LOG_MAIN, "cancelled by timeout_frozen_after");
     process_recipients = RECIP_FAIL_TIMEOUT;
     }
 
@@ -6962,7 +7023,7 @@ if (f.deliver_freeze)
   fails. */
 
   else if (!*sender_address && message_age >= ignore_bounce_errors_after)
-    log_write(0, LOG_MAIN, "Unfrozen by errmsg timer");
+    log_write(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
@@ -6982,7 +7043,7 @@ if (f.deliver_freeze)
       {
       (void)close(deliver_datafile);
       deliver_datafile = -1;
-      log_write(L_skip_delivery, LOG_MAIN, "Message is frozen");
+      if (LOGGING(skip_delivery)) log_write(LOG_MAIN, "Message is frozen");
       return continue_closedown();   /* yields DELIVER_NOT_ATTEMPTED */
       }
 
@@ -6992,9 +7053,9 @@ if (f.deliver_freeze)
     if (forced)
       {
       f.deliver_manual_thaw = TRUE;
-      log_write(0, LOG_MAIN, "Unfrozen by forced delivery");
+      log_write(LOG_MAIN, "Unfrozen by forced delivery");
       }
-    else log_write(0, LOG_MAIN, "Unfrozen by auto-thaw");
+    else log_write(LOG_MAIN, "Unfrozen by auto-thaw");
     }
 
   /* We get here if any of the rules for unfreezing have triggered. */
@@ -7017,7 +7078,7 @@ if (message_logs)
 
   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(LOG_MAIN|LOG_PANIC, "Couldn't %s message log %s: %s", error,
       fname, strerror(errno));
     return continue_closedown();   /* yields DELIVER_NOT_ATTEMPTED */
     }
@@ -7026,7 +7087,7 @@ if (message_logs)
 
   if (!(message_log = fdopen(fd, "a")))
     {
-    log_write(0, LOG_MAIN|LOG_PANIC, "Couldn't fdopen message log %s: %s",
+    log_write(LOG_MAIN|LOG_PANIC, "Couldn't fdopen message log %s: %s",
       fname, strerror(errno));
     return continue_closedown();   /* yields DELIVER_NOT_ATTEMPTED */
     }
@@ -7039,7 +7100,7 @@ the addresses. */
 if (give_up)
   {
   struct passwd *pw = getpwuid(real_uid);
-  log_write(0, LOG_MAIN, "cancelled by %s",
+  log_write(LOG_MAIN, "cancelled by %s",
       pw ? US pw->pw_name : string_sprintf("uid %ld", (long int)real_uid));
   process_recipients = RECIP_FAIL;
   }
@@ -7086,7 +7147,7 @@ else if (system_filter && process_recipients != RECIP_FAIL_TIMEOUT)
   redirect.pw = NULL;
   redirect.modemask = 0;
 
-  DEBUG(D_deliver|D_filter) debug_printf("running system filter\n");
+  DEBUG(deliver|filter) debug_printf("running system filter\n");
 
   rc = rda_interpret(
     &redirect,              /* Where the data is */
@@ -7105,13 +7166,13 @@ else if (system_filter && process_recipients != RECIP_FAIL_TIMEOUT)
     &filtertype,            /* Will always be set to FILTER_EXIM for this call */
     US"system filter");     /* For error messages */
 
-  DEBUG(D_deliver|D_filter) debug_printf("system filter returned %d\n", rc);
+  DEBUG(deliver|filter) debug_printf("system filter returned %d\n", rc);
 
   if (rc == FF_ERROR || rc == FF_NONEXIST)
     {
     (void)close(deliver_datafile);
     deliver_datafile = -1;
-    log_write(0, LOG_MAIN|LOG_PANIC, "Error in system filter: %s",
+    log_write(LOG_MAIN|LOG_PANIC, "Error in system filter: %s",
       string_printing(filter_message));
     return continue_closedown();   /* yields DELIVER_NOT_ATTEMPTED */
     }
@@ -7135,7 +7196,7 @@ else if (system_filter && process_recipients != RECIP_FAIL_TIMEOUT)
     {
     process_recipients = RECIP_DEFER;
     deliver_msglog("Delivery deferred by system filter\n");
-    log_write(0, LOG_MAIN, "Delivery deferred by system filter");
+    log_write(LOG_MAIN, "Delivery deferred by system filter");
     }
 
   /* The filter can request that a message be frozen, but this does not
@@ -7188,7 +7249,7 @@ else if (system_filter && process_recipients != RECIP_FAIL_TIMEOUT)
         }
       }
 
-    log_write(0, LOG_MAIN, "cancelled by system filter%s%.*s", colon, loglen,
+    log_write(LOG_MAIN, "cancelled by system filter%s%.*s", colon, loglen,
       logmsg);
     }
 
@@ -7199,9 +7260,9 @@ else if (system_filter && process_recipients != RECIP_FAIL_TIMEOUT)
     {
     process_recipients = RECIP_IGNORE;
     if (addr_new)
-      log_write(0, LOG_MAIN, "original recipients ignored (system filter)");
+      log_write(LOG_MAIN, "original recipients ignored (system filter)");
     else
-      log_write(0, LOG_MAIN, "=> discarded (system filter)");
+      log_write(LOG_MAIN, "=> discarded (system filter)");
     }
 
   /* If any new addresses were created by the filter, fake up a "parent"
@@ -7232,7 +7293,7 @@ else if (system_filter && process_recipients != RECIP_FAIL_TIMEOUT)
     while (p)
       {
       if (parent->child_count == USHRT_MAX)
-        log_write_die(0, LOG_MAIN, "system filter generated more "
+        log_write_die(LOG_MAIN, "system filter generated more "
           "than %d delivery addresses", USHRT_MAX);
       parent->child_count++;
       p->parent = parent;
@@ -7327,7 +7388,7 @@ else if (system_filter && process_recipients != RECIP_FAIL_TIMEOUT)
 
       /* Either a non-pfr delivery, or we found a transport */
 
-      DEBUG(D_deliver|D_filter)
+      DEBUG(deliver|filter)
         debug_printf("system filter added %s\n", p->address);
 
       addr_last = p;
@@ -7336,6 +7397,7 @@ else if (system_filter && process_recipients != RECIP_FAIL_TIMEOUT)
     }
   }
 
+/* debug_print_ids(US"after system filter:"); */
 
 /* Scan the recipients list, and for every one that is not in the non-
 recipients tree, add an addr item to the chain of new addresses. If the pno
@@ -7376,7 +7438,7 @@ if (process_recipients != RECIP_IGNORE)
 	{
 	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",
+	DEBUG(deliver) debug_printf("utf8, downconvert %s\n",
 	  new->prop.utf8_downcvt ? "yes"
 	  : new->prop.utf8_downcvt_maybe ? "ifneeded"
 	  : "no");
@@ -7391,7 +7453,7 @@ if (process_recipients != RECIP_IGNORE)
 
       new->dsn_flags = r->dsn_flags & rf_dsnflags;
       new->dsn_orcpt = r->orcpt;
-      DEBUG(D_deliver) debug_printf("DSN: set orcpt: %s  flags: 0x%x\n",
+      DEBUG(deliver) debug_printf("DSN: set orcpt: %s  flags: 0x%x\n",
 	new->dsn_orcpt ? new->dsn_orcpt : US"", new->dsn_flags);
 
       switch (process_recipients)
@@ -7475,7 +7537,7 @@ if (process_recipients != RECIP_IGNORE)
 	int start, end, dom;
 
 	if (!parse_extract_address(addr, &errmsg, &start, &end, &dom, TRUE))
-	  log_write(0, LOG_MAIN|LOG_PANIC,
+	  log_write(LOG_MAIN|LOG_PANIC,
                 "failed to parse address '%.100s': %s\n", addr, errmsg);
 	else
 	  {
@@ -7493,7 +7555,7 @@ if (process_recipients != RECIP_IGNORE)
       }
     }
 
-DEBUG(D_deliver)
+DEBUG(deliver)
   {
   debug_printf("Delivery address list:\n");
   for (address_item * p = addr_new; p; p = p->next)
@@ -7552,42 +7614,37 @@ while (addr_new)           /* Loop until all addresses dealt with */
   address_item * addr, * parent;
 
   /* Failure to open the retry database is treated the same as if it does
-  not exist. In both cases, dbm_file is NULL.  For the first stage of a 2-phase
-  queue run don't bother checking domain- or address-retry info; they will take
-  effect on the second stage. */
-
-  if (!f.queue_2stage)
-    {
-    /* If we have transaction-capable hintsdbs, open the retry db without
-    locking, and leave open for the transport process and for subsequent
-    deliveries. Use a writeable open as we can keep it open all the way through
-    to writing retry records if needed due to message fails.
-    If the open fails, tag that explicitly for the transport but retry the open
-    next time around, in case it was created in the interim.
-    If non-transaction, we are only reading records at this stage and
-    we close the db before running the transport.
-    Either way we do a non-creating open. */
-
-    if (continue_retry_db == (open_db *)-1)
-      continue_retry_db = NULL;
-
-    if (continue_retry_db)
-      {
-      DEBUG(D_hints_lookup) debug_printf("using cached retry hintsdb handle\n");
-      dbm_file = continue_retry_db;
-      }
-    else if (!exim_lockfile_needed())
-      {
-      dbm_file = dbfn_open_multi(US"retry", O_RDWR, &dbblock);
-      continue_retry_db = dbm_file ? dbm_file : (open_db *)-1;
-      }
-    else
-      dbm_file = dbfn_open(US"retry", O_RDONLY, &dbblock, FALSE, TRUE);
+  not exist. In both cases, dbm_file is NULL. */
 
-    if (!dbm_file)
-      DEBUG(D_deliver|D_retry|D_route|D_hints_lookup)
-	debug_printf("no retry data available\n");
+  /* If we have transaction-capable hintsdbs, open the retry db without
+  locking, and leave open for the transport process and for subsequent
+  deliveries. Use a writeable open as we can keep it open all the way through
+  to writing retry records if needed due to message fails.
+  If the open fails, tag that explicitly for the transport but retry the open
+  next time around, in case it was created in the interim.
+  If non-transaction, we are only reading records at this stage and
+  we close the db before running the transport.
+  Either way we do a non-creating open. */
+
+  if (continue_retry_db == (open_db *)-1)
+    continue_retry_db = NULL;
+
+  if (continue_retry_db)
+    {
+    DEBUG(hints_lookup) debug_printf("using cached retry hintsdb handle\n");
+    dbm_file = continue_retry_db;
+    }
+  else if (!exim_lockfile_needed())
+    {
+    dbm_file = dbfn_open_multi(US"retry", O_RDWR, &dbblock);
+    continue_retry_db = dbm_file ? dbm_file : (open_db *)-1;
     }
+  else
+    dbm_file = dbfn_open(US"retry", O_RDONLY, &dbblock, FALSE, TRUE);
+
+  if (!dbm_file)
+    DEBUG(deliver|retry|route|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. */
@@ -7601,7 +7658,7 @@ while (addr_new)           /* Loop until all addresses dealt with */
     addr = addr_new;
     addr_new = addr->next;
 
-    DEBUG(D_deliver|D_retry|D_route)
+    DEBUG(deliver|retry|route)
       {
       debug_printf(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
       debug_printf("Considering: %s\n", addr->address);
@@ -7650,7 +7707,7 @@ while (addr_new)           /* Loop until all addresses dealt with */
 
       else if ((tnode = tree_search(tree_duplicates, addr->unique)))
         {
-        DEBUG(D_deliver|D_route)
+        DEBUG(deliver|route)
           debug_printf("%s is a duplicate address: discarded\n", addr->address);
         addr->dupof = tnode->data.ptr;
         addr->next = addr_duplicate;
@@ -7658,13 +7715,13 @@ while (addr_new)           /* Loop until all addresses dealt with */
         continue;
         }
 
-      DEBUG(D_deliver|D_route) debug_printf("unique = %s\n", addr->unique);
+      DEBUG(deliver|route) debug_printf("unique = %s\n", addr->unique);
 
       /* Check for previous delivery */
 
       if (tree_search(tree_nonrecipients, addr->unique))
         {
-        DEBUG(D_deliver|D_route)
+        DEBUG(deliver|route)
           debug_printf("%s was previously delivered: discarded\n", addr->address);
         child_done(addr, tod_stamp(tod_log));
         continue;
@@ -7741,7 +7798,7 @@ while (addr_new)           /* Loop until all addresses dealt with */
       /* Pipe, file, or autoreply delivery is to go ahead as a normal local
       delivery. */
 
-      DEBUG(D_deliver|D_route)
+      DEBUG(deliver|route)
         debug_printf_indent("queued for %s transport\n", addr->transport->drinst.name);
       addr->next = addr_local;
       addr_local = addr;
@@ -7815,92 +7872,84 @@ while (addr_new)           /* Loop until all addresses dealt with */
 
     for (uschar * p = Ustrrchr(addr->unique, '@'); *p; p++) *p = tolower(*p);
 
-    DEBUG(D_deliver|D_route) debug_printf("unique = %s\n", addr->unique);
+    DEBUG(deliver|route) debug_printf("unique = %s\n", addr->unique);
 
     if (tree_search(tree_nonrecipients, addr->unique))
       {
-      DEBUG(D_deliver|D_route)
+      DEBUG(deliver|route)
         debug_printf("%s was previously delivered: discarded\n", addr->unique);
       child_done(addr, tod_stamp(tod_log));
       continue;
       }
 
-    if (f.queue_2stage)
+    /* Get the routing retry status, saving the two retry keys (with and
+    without the local part) for subsequent use. If there is no retry record
+    for the standard address routing retry key, we look for the same key with
+    the sender attached, because this form is used by the smtp transport after
+    a 4xx response to RCPT when address_retry_include_sender is true. */
+
+    DEBUG(deliver|retry)
       {
-      DEBUG(D_deliver)
-	debug_printf_indent("no router retry check (ph1 qrun)\n");
+      debug_printf_indent("checking router retry status\n");
+      acl_level++;
       }
-    else
-      {
-      /* Get the routing retry status, saving the two retry keys (with and
-      without the local part) for subsequent use. If there is no retry record
-      for the standard address routing retry key, we look for the same key with
-      the sender attached, because this form is used by the smtp transport after
-      a 4xx response to RCPT when address_retry_include_sender is true. */
+    addr->domain_retry_key = string_sprintf("R:%s", addr->domain);
+    addr->address_retry_key = string_sprintf("R:%s@%s", addr->local_part,
+      addr->domain);
 
-      DEBUG(D_deliver|D_retry)
+    if (dbm_file)
+      {
+      domain_retry_record = dbfn_read(dbm_file, addr->domain_retry_key);
+      if (  domain_retry_record
+	 && now - domain_retry_record->gen.time_stamp > retry_data_expire
+	 )
 	{
-	debug_printf_indent("checking router retry status\n");
-	acl_level++;
+	DEBUG(deliver|retry)
+	  debug_printf_indent("domain retry record present but expired\n");
+	domain_retry_record = NULL;    /* Ignore if too old */
 	}
-      addr->domain_retry_key = string_sprintf("R:%s", addr->domain);
-      addr->address_retry_key = string_sprintf("R:%s@%s", addr->local_part,
-	addr->domain);
 
-      if (dbm_file)
+      address_retry_record = dbfn_read(dbm_file, addr->address_retry_key);
+      if (  address_retry_record
+	 && now - address_retry_record->gen.time_stamp > retry_data_expire
+	 )
 	{
-	domain_retry_record = dbfn_read(dbm_file, addr->domain_retry_key);
-	if (  domain_retry_record
-	   && now - domain_retry_record->time_stamp > retry_data_expire
-	   )
-	  {
-	  DEBUG(D_deliver|D_retry)
-	    debug_printf_indent("domain retry record present but expired\n");
-	  domain_retry_record = NULL;    /* Ignore if too old */
-	  }
+	DEBUG(deliver|retry)
+	  debug_printf_indent("address retry record present but expired\n");
+	address_retry_record = NULL;   /* Ignore if too old */
+	}
 
-	address_retry_record = dbfn_read(dbm_file, addr->address_retry_key);
+      if (!address_retry_record)
+	{
+	const uschar * altkey = string_sprintf("%s:<%s>",
+				  addr->address_retry_key, sender_address);
+	address_retry_record = dbfn_read(dbm_file, altkey);
 	if (  address_retry_record
-	   && now - address_retry_record->time_stamp > retry_data_expire
-	   )
+	   && now - address_retry_record->gen.time_stamp > retry_data_expire)
 	  {
-	  DEBUG(D_deliver|D_retry)
-	    debug_printf_indent("address retry record present but expired\n");
+	  DEBUG(deliver|retry)
+	    debug_printf_indent("address<sender> retry record present but expired\n");
 	  address_retry_record = NULL;   /* Ignore if too old */
 	  }
-
-	if (!address_retry_record)
-	  {
-	  const uschar * altkey = string_sprintf("%s:<%s>",
-				    addr->address_retry_key, sender_address);
-	  address_retry_record = dbfn_read(dbm_file, altkey);
-	  if (  address_retry_record
-	     && now - address_retry_record->time_stamp > retry_data_expire)
-	    {
-	    DEBUG(D_deliver|D_retry)
-	      debug_printf_indent("address<sender> retry record present but expired\n");
-	    address_retry_record = NULL;   /* Ignore if too old */
-	    }
-	  }
 	}
+      }
 
-      DEBUG(D_deliver|D_retry)
-	{
-	if (!domain_retry_record)
-	  debug_printf_indent("no   domain  retry record\n");
-	else
-	  debug_printf_indent("have domain  retry record; next_try = now%+d\n",
-			f.running_in_test_harness ? 0 :
-			(int)(domain_retry_record->next_try - now));
+    DEBUG(deliver|retry)
+      {
+      if (!domain_retry_record)
+	debug_printf_indent("no   domain  retry record\n");
+      else
+	debug_printf_indent("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_indent("no   address retry record\n");
-	else
-	  debug_printf_indent("have address retry record; next_try = now%+d\n",
-			f.running_in_test_harness ? 0 :
-			(int)(address_retry_record->next_try - now));
-	acl_level--;
-	}
+      if (!address_retry_record)
+	debug_printf_indent("no   address retry record\n");
+      else
+	debug_printf_indent("have address retry record; next_try = now%+d\n",
+		      f.running_in_test_harness ? 0 :
+		      (int)(address_retry_record->next_try - now));
+      acl_level--;
       }
 
     /* If we are sending a message down an existing SMTP connection, we must
@@ -7999,7 +8048,7 @@ while (addr_new)           /* Loop until all addresses dealt with */
         setflag(addr, af_dr_retry_exists);
       addr->next = addr_route;
       addr_route = addr;
-      DEBUG(D_deliver|D_route)
+      DEBUG(deliver|route)
         debug_printf("%s: queued for routing\n", addr->address);
       }
     }
@@ -8012,7 +8061,7 @@ while (addr_new)           /* Loop until all addresses dealt with */
     if (exim_lockfile_needed())
       { dbfn_close(dbm_file); continue_retry_db = dbm_file = NULL; }
     else
-      DEBUG(D_hints_lookup) debug_printf("retaining retry hintsdb handle\n");
+      DEBUG(hints_lookup) debug_printf("retaining retry hintsdb handle\n");
 
   /* 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.
@@ -8122,7 +8171,7 @@ while (addr_new)           /* Loop until all addresses dealt with */
        && tree_search(tree_nonrecipients, addr_r->unique) != 0
        )
       {
-      DEBUG(D_deliver|D_route) debug_printf("%s was previously delivered: "
+      DEBUG(deliver|route) debug_printf("%s was previously delivered: "
         "discarded\n", addr_r->address);
       if (addr_remote == addr_r) addr_remote = addr_r->next;
       else if (addr_local == addr_r) addr_local = addr_r->next;
@@ -8168,7 +8217,7 @@ while (addr_new)           /* Loop until all addresses dealt with */
         copyflag(addr2, addr_r, af_hide_child);
         copyflag(addr2, addr_r, af_local_host_removed);
 
-        DEBUG(D_deliver|D_route)
+        DEBUG(deliver|route)
           debug_printf(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"
                        "routing %s\n"
                        "Routing for %s copied from %s\n",
@@ -8180,7 +8229,7 @@ while (addr_new)           /* Loop until all addresses dealt with */
 
 /* Debugging: show the results of the routing */
 
-DEBUG(D_deliver|D_retry|D_route)
+DEBUG(deliver|retry|route)
   {
   debug_printf(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
   debug_printf("After routing:\n  Local deliveries:\n");
@@ -8262,7 +8311,7 @@ if (  mua_wrapper
   need to do the failure logging. */
 
   if (addr != addr_failed)
-    log_write(0, LOG_MAIN, "** %s routing yielded a %s delivery",
+    log_write(LOG_MAIN, "** %s routing yielded a %s delivery",
       addr->address, which);
 
   /* Always write an error to the caller */
@@ -8284,7 +8333,7 @@ delivery, test continue_sequence rather than continue_transport. */
 
 if (continue_sequence > 1 && addr_local)
   {
-  DEBUG(D_deliver|D_retry|D_route)
+  DEBUG(deliver|retry|route)
     debug_printf("deferring local deliveries due to continued-transport\n");
   if (addr_defer)
     {
@@ -8341,7 +8390,7 @@ if (addr_local || addr_remote)
     if ((journal_fd = Uopen(fname,
 	      EXIM_CLOEXEC | 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",
+      log_write(LOG_MAIN|LOG_PANIC, "Couldn't open journal file %s: %s",
 	fname, strerror(errno));
       return DELIVER_NOT_ATTEMPTED;
       }
@@ -8357,11 +8406,11 @@ if (addr_local || addr_remote)
 #endif
       )
       {
-      int ret = Uunlink(fname);
-      log_write(0, LOG_MAIN|LOG_PANIC, "Couldn't set perms on journal file %s: %s",
-	fname, strerror(errno));
+      int fault_errno = errno, ret = Uunlink(fname);
+      log_write(LOG_MAIN|LOG_PANIC, "Couldn't set perms on journal file %s: %s",
+	fname, strerror(fault_errno));
       if(ret  &&  errno != ENOENT)
-	log_write_die(0, LOG_MAIN, "failed to unlink %s: %s",
+	log_write_die(LOG_MAIN, "failed to unlink %s: %s",
 	  fname, strerror(errno));
       return DELIVER_NOT_ATTEMPTED;
       }
@@ -8392,7 +8441,7 @@ if (!regex_IGNOREQUOTA)
 
 if (addr_local)
   {
-  DEBUG(D_deliver|D_transport)
+  DEBUG(deliver|transport)
     debug_printf(">>>>>>>>>>>>>>>> Local deliveries >>>>>>>>>>>>>>>>\n");
   do_local_deliveries();
   f.disable_logging = FALSE;
@@ -8416,7 +8465,7 @@ if (f.queue_run_local)
 
 if (addr_remote)
   {
-  DEBUG(D_deliver|D_transport)
+  DEBUG(deliver|transport)
     debug_printf(">>>>>>>>>>>>>>>> Remote deliveries >>>>>>>>>>>>>>>>\n");
 
   /* Precompile some regex that are used to recognize parameters in response
@@ -8431,7 +8480,7 @@ if (addr_remote)
   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 "
+    log_write(LOG_MAIN, "** mua_wrapper is set but recipients cannot all "
       "be delivered in one transaction");
     fprintf(stderr, "delivery to smarthost failed (configuration problem)\n");
 
@@ -8447,7 +8496,7 @@ if (addr_remote)
 
   if (addr_fallback && !mua_wrapper)
     {
-    DEBUG(D_deliver) debug_printf("Delivering to fallback hosts\n");
+    DEBUG(deliver) debug_printf("Delivering to fallback hosts\n");
     addr_remote = addr_fallback;
     addr_fallback = NULL;
     if (remote_sort_domains) sort_remote_deliveries();
@@ -8460,13 +8509,23 @@ if (addr_remote)
 /* All deliveries are now complete. Ignore SIGTERM during this tidying up
 phase, to minimize cases of half-done things. */
 
-DEBUG(D_deliver)
+DEBUG(deliver)
   debug_printf(">>>>>>>>>>>>>>>> deliveries are done >>>>>>>>>>>>>>>>\n");
 cancel_cutthrough_connection(TRUE, US"deliveries are done");
 
-/* Root privilege is no longer needed */
+/* We used to always drop privs here, from root to exim, but the introduction
+of handling a further transport-suggested message requires we retain root.
+On the other hand, we want bounce messages to be sent by the Exim user.  So, if
+there is need to send a response (bounce, warn or DSN) drop at that point and
+do a re-exec for the next chained message.
+This arises because the reponse (presumably) is labelled with the
+reuid not (say) the euid - so we cannot use priv_drop_temp(). */
 
-exim_setugid(exim_uid, exim_gid, FALSE, US"post-delivery tidying");
+if (addr_failed)		/* We will be generating a bounce */
+  {
+  exim_setugid(exim_uid, exim_gid, FALSE, US"post-delivery tidying");
+  has_privs = FALSE;
+  }
 
 set_process_info("tidying up after delivering %s", message_id);
 signal(SIGTERM, SIG_IGN);
@@ -8484,7 +8543,7 @@ if (mua_wrapper)
     address_item * nextaddr;
     for (address_item * addr = addr_defer; addr; addr = nextaddr)
       {
-      log_write(0, LOG_MAIN, "** %s mua_wrapper forced failure for deferred "
+      log_write(LOG_MAIN, "** %s mua_wrapper forced failure for deferred "
         "delivery", addr->address);
       nextaddr = addr->next;
       addr->next = addr_failed;
@@ -8538,90 +8597,94 @@ else if (!f.dont_deliver)
 
 /* Send DSN for successful messages if requested */
 
-maybe_send_dsn(addr_succeed);
+has_privs = maybe_send_dsn(addr_succeed, has_privs);
 
 /* 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)
+if (addr_failed)
   {
-  const uschar * logtod = tod_stamp(tod_log);
-  address_item * addr;
+  has_privs = drop_privs(US"post-delivery bounce gen", has_privs);
 
-  /* There are weird cases when logging is disabled in the transport. However,
-  there may not be a transport (address failed by a router). */
+  do
+    {
+    const uschar * logtod = tod_stamp(tod_log);
+    address_item * addr;
 
-  f.disable_logging = FALSE;
-  if (addr_failed->transport)
-    f.disable_logging = addr_failed->transport->disable_logging;
+    /* There are weird cases when logging is disabled in the transport.
+    However, there may not be a transport (address failed by a router). */
 
-  DEBUG(D_deliver)
-    debug_printf("processing failed address %s\n", addr_failed->address);
+    f.disable_logging = addr_failed->transport
+      ? addr_failed->transport->disable_logging : FALSE;
 
-  /* There are only two ways an address in a bounce message can get here:
+    DEBUG(deliver)
+      debug_printf("processing failed address %s\n", addr_failed->address);
 
-  (1) When delivery was initially deferred, but has now timed out (in the call
-      to retry_update() above). We can detect this by testing for
-      af_retry_timedout. If the address does not have its own errors address,
-      we arrange to ignore the error.
+    /* There are only two ways an address in a bounce message can get here:
 
-  (2) If delivery failures for bounce messages are being ignored. We can detect
-      this by testing for af_ignore_error. This will also be set if a bounce
-      message has been autothawed and the ignore_bounce_errors_after time has
-      passed. It might also be set if a router was explicitly configured to
-      ignore errors (errors_to = "").
+    (1) When delivery was initially deferred, but has now timed out (in the
+	call to retry_update() above). We can detect this by testing for
+	af_retry_timedout. If the address does not have its own errors
+	address, we arrange to ignore the error.
 
-  If neither of these cases obtains, something has gone wrong. Log the
-  incident, but then ignore the error. */
+    (2) If delivery failures for bounce messages are being ignored. We can
+	detect this by testing for af_ignore_error. This will also be set if a
+	bounce message has been autothawed and the ignore_bounce_errors_after
+	time has passed. It might also be set if a router was explicitly
+	configured to ignore errors (errors_to = "").
 
-  if (sender_address[0] == 0 && !addr_failed->prop.errors_address)
-    {
-    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)");
+    If neither of these cases obtains, something has gone wrong. Log the
+    incident, but then ignore the error. */
 
-    addr_failed->prop.ignore_error = TRUE;
-    }
+    if (!*sender_address && !addr_failed->prop.errors_address)
+      {
+      if (  !testflag(addr_failed, af_retry_timedout)
+	 && !addr_failed->prop.ignore_error)
+	log_write(LOG_MAIN|LOG_PANIC, "internal error: bounce message "
+	  "failure is neither frozen nor ignored (it's been ignored)");
 
-  /* 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. */
+      addr_failed->prop.ignore_error = TRUE;
+      }
 
-  if (  addr_failed->prop.ignore_error
-     ||    addr_failed->dsn_flags & rf_dsnflags
-	&& !(addr_failed->dsn_flags & rf_notify_failure)
-     )
-    {
-    addr = addr_failed;
-    addr_failed = addr->next;
-    if (addr->return_filename) Uunlink(addr->return_filename);
+    /* 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 (  addr_failed->prop.ignore_error
+       ||    addr_failed->dsn_flags & rf_dsnflags
+	  && !(addr_failed->dsn_flags & rf_notify_failure)
+       )
+      {
+      addr = addr_failed;
+      addr_failed = addr->next;
+      if (addr->return_filename) Uunlink(addr->return_filename);
 
 #ifndef DISABLE_EVENT
-    msg_event_raise(US"msg:fail:delivery", addr);
+      msg_event_raise(US"msg:fail:delivery", addr);
 #endif
-    log_write(0, LOG_MAIN, "%s%s%s%s: error ignored%s",
-      addr->address,
-      !addr->parent ? US"" : US" <",
-      !addr->parent ? US"" : addr->parent->address,
-      !addr->parent ? US"" : US">",
-      addr->prop.ignore_error
-      ? US"" : US": RFC 3461 DSN, failure notify not requested");
-
-    address_done(addr, logtod);
-    child_done(addr, logtod);
-    /* Panic-dies on error */
-    (void)spool_write_header(message_id, SW_DELIVERING, NULL);
-    }
+      log_write(LOG_MAIN, "%s%s%s%s: error ignored%s",
+	addr->address,
+	!addr->parent ? US"" : US" <",
+	!addr->parent ? US"" : addr->parent->address,
+	!addr->parent ? US"" : US">",
+	addr->prop.ignore_error
+	? US"" : US": RFC 3461 DSN, failure notify not requested");
 
-  /* 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. */
+      address_done(addr, logtod);
+      child_done(addr, logtod);
+      /* Panic-dies on error */
+      (void) spool_write_header(message_id, SW_DELIVERING, NULL);
+      }
 
-  else
-    send_bounce_message(now, logtod);
+    /* 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. */
+
+    else
+      send_bounce_message(now, logtod);
+    } while (addr_failed);
   }
 
 f.disable_logging = FALSE;  /* In case left set */
@@ -8632,7 +8695,7 @@ DELIVERY_TIDYUP:
 
 if (dbm_file)		/* Can only be continue_retry_db */
   {
-  DEBUG(D_hints_lookup) debug_printf("final close of cached retry db\n");
+  DEBUG(hints_lookup) debug_printf("final close of cached retry db\n");
   dbfn_close_multi(continue_retry_db);
   continue_retry_db = dbm_file = NULL;
   }
@@ -8661,12 +8724,12 @@ if (!addr_defer)
         rc = Urename(fname, moname);
         }
       if (rc < 0)
-        log_write_die(0, LOG_MAIN, "failed to move %s to the "
+        log_write_die(LOG_MAIN, "failed to move %s to the "
           "msglog.OLD directory", fname);
       }
     else
       if (Uunlink(fname) < 0)
-        log_write_die(0, LOG_MAIN, "failed to unlink %s: %s",
+        log_write_die(LOG_MAIN, "failed to unlink %s: %s",
 		  fname, strerror(errno));
     }
 
@@ -8674,19 +8737,19 @@ if (!addr_defer)
 
   fname = spool_fname(US"input", message_subdir, id, US"-D");
   if (Uunlink(fname) < 0)
-    log_write_die(0, LOG_MAIN, "failed to unlink %s: %s",
+    log_write_die(LOG_MAIN, "failed to unlink %s: %s",
       fname, strerror(errno));
   fname = spool_fname(US"input", message_subdir, id, US"-H");
   if (Uunlink(fname) < 0)
-    log_write_die(0, LOG_MAIN, "failed to unlink %s: %s",
+    log_write_die(LOG_MAIN, "failed to unlink %s: %s",
       fname, strerror(errno));
 
   /* Log the end of this message, with queue time if requested. */
 
   if (LOGGING(queue_time_overall))
-    log_write(0, LOG_MAIN, "Completed QT=%s", string_timesince(&received_time));
+    log_write(LOG_MAIN, "Completed QT=%s", string_timesince(&received_time));
   else
-    log_write(0, LOG_MAIN, "Completed");
+    log_write(LOG_MAIN, "Completed");
 
   /* Unset deliver_freeze so that we won't try to move the spool files further down */
   f.deliver_freeze = FALSE;
@@ -8780,9 +8843,10 @@ else if (addr_defer != (address_item *)(+1))
       if (  rnum >= 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",
+        DEBUG(deliver) debug_printf("one_time: adding %s in place of %s\n",
           otaddr->address, otaddr->parent->address);
-        receive_add_recipient(otaddr->address, t);
+	/*XXX should we copy the parent dsn_flags,orcpt ? */
+        receive_add_recipient(otaddr->address, t, rf_notify_unset, NULL);
         recipients_list[recipients_count-1].errors_to = otaddr->prop.errors_address;
         tree_add_nonrecipient(otaddr->parent->address);
         update_spool = TRUE;
@@ -8846,7 +8910,7 @@ else if (addr_defer != (address_item *)(+1))
 	count += extra;
 	}
 
-      DEBUG(D_deliver)
+      DEBUG(deliver)
 	{
 	debug_printf("time on queue = %s  id %s  addr %s\n",
 	  readconf_printtime(queue_time), message_id, addr_defer->address);
@@ -8859,11 +8923,14 @@ else if (addr_defer != (address_item *)(+1))
       have been. */
 
       if (warning_count < count)
+	{
+	has_privs = drop_privs(US"post-delivery warn gen", has_privs);
 	if (send_warning_message(recipients, queue_time, show_time))
 	  {
 	  warning_count = count;
 	  update_spool = TRUE;    /* Ensure spool rewritten */
 	  }
+	}
       }
     }
 
@@ -8906,6 +8973,8 @@ else if (addr_defer != (address_item *)(+1))
         else
 	  ss++;
 
+      has_privs = drop_privs(US"post-delivery freezemsg gen", has_privs);
+
       moan_tell_someone(freeze_tell, addr_defer, US"Message frozen",
         "Message %s has been frozen%s.\nThe sender is <%s>.\n", message_id,
         s, sender_address);
@@ -8915,7 +8984,7 @@ else if (addr_defer != (address_item *)(+1))
     of a race problem. */
 
     deliver_msglog("*** Frozen%s\n", frozen_info);
-    log_write(0, LOG_MAIN, "Frozen%s", frozen_info);
+    log_write(LOG_MAIN, "Frozen%s", frozen_info);
     }
 
   /* If there have been any updates to the non-recipients list, or other things
@@ -8924,7 +8993,7 @@ else if (addr_defer != (address_item *)(+1))
   was more than one address being delivered, the header_change update is done
   earlier, in case one succeeds and then something crashes. */
 
-  DEBUG(D_deliver)
+  DEBUG(deliver)
     debug_printf("delivery deferred: update_spool=%d header_rewritten=%d\n",
       update_spool, f.header_rewritten);
 
@@ -8957,7 +9026,7 @@ if (remove_journal)
   uschar * fname = spool_fname(US"input", message_subdir, id, US"-J");
 
   if (Uunlink(fname) < 0 && errno != ENOENT)
-    log_write_die(0, LOG_MAIN, "failed to unlink %s: %s", fname,
+    log_write_die(LOG_MAIN, "failed to unlink %s: %s", fname,
       strerror(errno));
 
   /* Move the message off the spool if requested */
@@ -8974,7 +9043,7 @@ to try delivery. */
 
 (void)close(deliver_datafile);
 deliver_datafile = -1;
-DEBUG(D_deliver) debug_printf("end delivery of %s\n", id);
+DEBUG(deliver) debug_printf("end delivery of %s\n", id);
 #ifdef MEASURE_TIMING
 report_time_since(&timestamp_startup, US"delivery end"); /* testcase 0005 */
 #endif
@@ -8982,20 +9051,46 @@ report_time_since(&timestamp_startup, US"delivery end"); /* testcase 0005 */
 /* If the transport suggested another message to deliver, go round again. */
 
 if (final_yield == DELIVER_ATTEMPTED_NORMAL && *continue_next_id)
-  {
-  addr_defer = addr_failed = addr_succeed = NULL;
-  tree_duplicates = NULL;	/* discard dups info from old message */
+  if (has_privs)
+    {
+    addr_defer = addr_failed = addr_succeed = NULL;
 
-  spool_clear_header_globals();
-  deliver_set_expansions(NULL);
-  deliver_host_address = return_path = bounce_recipient = NULL;
+    tree_duplicates = NULL;	/* discard dups info from old message */
+    addr_duplicate = NULL;
+
+    spool_clear_header_globals();
+    deliver_set_expansions(NULL);
+    deliver_host_address = return_path = bounce_recipient = NULL;
 
-  store_reset(reset_point);
+    store_reset(reset_point);
 
-  id = string_copyn(continue_next_id, MESSAGE_ID_LENGTH);
-  continue_next_id[0] = '\0';
-  goto CONTINUED_ID;
-  }
+    id = string_copyn(continue_next_id, MESSAGE_ID_LENGTH);
+    continue_next_id[0] = '\0';
+    goto CONTINUED_ID;
+    }
+  else
+    {
+    cutthrough.peer_options = smtp_peer_options;
+    cutthrough.is_tls = !!continue_proxy_cipher;
+    cutthrough.snd_ip = sending_ip_address;
+    cutthrough.snd_port = sending_port;
+    cutthrough.cipher = continue_proxy_cipher;
+    cutthrough.sni = continue_proxy_sni;
+    cutthrough.is_dane = continue_proxy_dane;
+    cutthrough.transport = US continue_transport;
+    cutthrough.host.name = continue_hostname;
+    cutthrough.host.address = continue_host_address;
+    cutthrough.host.port = continue_host_port;
+
+    transport_do_pass_socket(continue_next_id, 0);
+
+    /* Control never returns here. */
+    }
+
+/* Root privilege is no longer needed */
+
+if (has_privs)
+  exim_setugid(exim_uid, exim_gid, FALSE, US"post-delivery tidying");
 
 /* It is unlikely that there will be any cached resources, since they are
 released after routing, and in the delivery subprocesses. However, it's
@@ -9034,17 +9129,15 @@ if (cutthrough.cctx.sock >= 0 && cutthrough.callout_hold_only)
   {
   int channel_fd = cutthrough.cctx.sock;
 
-  smtp_peer_options = cutthrough.peer_options;
   continue_sequence = 0;
 
 #ifndef DISABLE_TLS
   if (cutthrough.is_tls)
     {
-    int pfd[2], pid;
+    int pfd[2];
+    pid_t pid;
 
-    smtp_peer_options |= OPTION_TLS;
-    sending_ip_address = cutthrough.snd_ip;
-    sending_port = cutthrough.snd_port;
+    cutthrough.peer_options |= OPTION_TLS;
 
     where = US"socketpair";
     if (socketpair(AF_UNIX, SOCK_STREAM, 0, pfd) != 0)
@@ -9052,12 +9145,12 @@ if (cutthrough.cctx.sock >= 0 && cutthrough.callout_hold_only)
 
     where = US"fork";
     testharness_pause_ms(150);
-    if ((pid = exim_fork(US"tls-proxy-interproc")) < 0)
+    if ((pid = exim_fork(US"tls-proxy-interproc/d")) < 0)
       goto fail;
 
     if (pid == 0)	/* child: will fork again to totally disconnect */
       {
-      smtp_proxy_tls(cutthrough.cctx.tls_ctx, big_buffer, big_buffer_size,
+      smtp_proxy_tls(&cutthrough.cctx, big_buffer, big_buffer_size,
 		      pfd, 5*60, cutthrough.host.name);
       /* does not return */
       }
@@ -9069,8 +9162,7 @@ if (cutthrough.cctx.sock >= 0 && cutthrough.callout_hold_only)
     }
 #endif
 
-  transport_do_pass_socket(cutthrough.transport, cutthrough.host.name,
-    cutthrough.host.address, cutthrough.host.port, message_id, channel_fd);
+  transport_do_pass_socket(message_id, channel_fd);
   }
 else
   {
@@ -9081,8 +9173,7 @@ return;		/* compiler quietening; control does not reach here. */
 
 #ifndef DISABLE_TLS
 fail:
-  log_write(0,
-    LOG_MAIN | (exec_type == CEE_EXEC_EXIT ? LOG_PANIC : LOG_PANIC_DIE),
+  log_write(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.
@@ -9092,6 +9183,39 @@ fail:
 #endif
 }
 
+
+/* When running with debug_store this is called on every store_reset(). Walk all
+the address lists we maintain checking that none of the pointers are in the region
+being freed. */
+/*XXX what about all the pointers contained in the addr?
+If implemented, beware unbounded recursion. */
+
+static void
+check_addr_list(const uschar * name, const address_item * a,
+  void (*f)(const uschar*, const uschar*, void*), void * ctx)
+{
+while (a)
+  {
+  f(name, CUS a, ctx);	/* We lie about the data type */
+  a = a->next;
+  }
+}
+
+void
+check_deliver_addrs_not_freed(void (*f)(const uschar*, const uschar*, void*), void * ctx)
+{
+if (addr_defer != (address_item *)(+1))
+  check_addr_list(US"(addr_defer)",	addr_defer,	f, ctx);
+check_addr_list(US"(addr_failed)",	addr_failed,	f, ctx);
+check_addr_list(US"(addr_fallback)",	addr_fallback,	f, ctx);
+check_addr_list(US"(addr_local)",	addr_local,	f, ctx);
+check_addr_list(US"(addr_new)",		addr_new,	f, ctx);
+check_addr_list(US"(addr_remote)",	addr_remote,	f, ctx);
+check_addr_list(US"(addr_route)",	addr_route,	f, ctx);
+check_addr_list(US"(addr_succeed)",	addr_succeed,	f, ctx);
+check_addr_list(US"(addr_duplicate)",	addr_duplicate, f, ctx);
+}
+
 /* vi: aw ai sw=2
 */
 /* End of deliver.c */
diff --git a/src/src/directory.c b/src/src/directory.c
index 45e18615f..054b125b4 100644
--- a/src/src/directory.c
+++ b/src/src/directory.c
@@ -88,7 +88,7 @@ while (c && *p)
 return TRUE;
 
 bad:
-  if (panic) log_write_die(0, LOG_MAIN,
+  if (panic) log_write_die(LOG_MAIN,
     "Failed to %s directory %q: %s\n", p, path, exim_errstr(errno));
   return FALSE;
 }
diff --git a/src/src/dns.c b/src/src/dns.c
index 4c8f1dd14..6b233f85b 100644
--- a/src/src/dns.c
+++ b/src/src/dns.c
@@ -62,7 +62,7 @@ if (stat(CS utilname, &statbuf) >= 0)
   int infd, outfd, rc;
   uschar *argv[5];
 
-  DEBUG(D_dns) debug_printf_indent("DNS lookup of %s (%s) using fakens\n",
+  DEBUG(dns) debug_printf_indent("DNS lookup of %s (%s) using fakens\n",
 		name, dns_text_type(type));
 
   argv[0] = utilname;
@@ -73,7 +73,7 @@ if (stat(CS utilname, &statbuf) >= 0)
 
   pid = child_open(argv, NULL, 0000, &infd, &outfd, FALSE, US"fakens-search");
   if (pid < 0)
-    log_write_die(0, LOG_MAIN, "failed to run fakens: %s",
+    log_write_die(LOG_MAIN, "failed to run fakens: %s",
       strerror(errno));
 
   len = 0;
@@ -93,7 +93,7 @@ if (stat(CS utilname, &statbuf) >= 0)
       len += rc;
 
   if (rc < 0)
-    log_write_die(0, LOG_MAIN, "read from fakens failed: %s",
+    log_write_die(LOG_MAIN, "read from fakens failed: %s",
       strerror(errno));
 
   switch(child_close(pid, 0))
@@ -105,17 +105,17 @@ if (stat(CS utilname, &statbuf) >= 0)
     case 3: h_errno = NO_RECOVERY; return -1;
     case 4: h_errno = NO_DATA; return -1;
     case 5: /* Pass on to res_search() */
-    DEBUG(D_dns) debug_printf_indent("fakens returned PASS_ON\n");
+    DEBUG(dns) debug_printf_indent("fakens returned PASS_ON\n");
     }
   }
 else
   {
-  DEBUG(D_dns) debug_printf_indent("fakens (%s) not found\n", utilname);
+  DEBUG(dns) debug_printf_indent("fakens (%s) not found\n", utilname);
   }
 
 /* fakens utility not found, or it returned "pass on" */
 
-DEBUG(D_dns) debug_printf_indent("passing %s on to res_search()\n", domain);
+DEBUG(dns) debug_printf_indent("passing %s on to res_search()\n", domain);
 
 return res_search(CS domain, C_IN, type, answerptr, size);
 }
@@ -144,10 +144,10 @@ res_state resp = os_get_dns_resolver_res();
 
 if ((resp->options & RES_INIT) == 0)
   {
-  DEBUG(D_resolver) resp->options |= RES_DEBUG;     /* For Cygwin */
+  DEBUG(resolver) resp->options |= RES_DEBUG;     /* For Cygwin */
   os_put_dns_resolver_res(resp);
   res_init();
-  DEBUG(D_resolver) resp->options |= RES_DEBUG;
+  DEBUG(resolver) resp->options |= RES_DEBUG;
   os_put_dns_resolver_res(resp);
   }
 
@@ -164,13 +164,13 @@ if (dns_use_edns0 >= 0)
     resp->options |= RES_USE_EDNS0;
   else
     resp->options &= ~RES_USE_EDNS0;
-  DEBUG(D_resolver)
+  DEBUG(resolver)
     debug_printf_indent("Coerced resolver EDNS0 support %s.\n",
         dns_use_edns0 ? "on" : "off");
   }
 #else
 if (dns_use_edns0 >= 0)
-  DEBUG(D_resolver)
+  DEBUG(resolver)
     debug_printf_indent("Unable to %sset EDNS0 without resolver support.\n",
         dns_use_edns0 ? "" : "un");
 #endif
@@ -186,7 +186,7 @@ if (dns_dnssec_ok >= 0)
   {
   if (dns_use_edns0 == 0 && dns_dnssec_ok != 0)
     {
-    DEBUG(D_resolver)
+    DEBUG(resolver)
       debug_printf_indent("CONFLICT: dns_use_edns0 forced false, dns_dnssec_ok forced true, ignoring latter!\n");
     }
   else
@@ -195,17 +195,17 @@ if (dns_dnssec_ok >= 0)
       resp->options |= RES_USE_DNSSEC;
     else
       resp->options &= ~RES_USE_DNSSEC;
-    DEBUG(D_resolver) debug_printf_indent("Coerced resolver DNSSEC support %s.\n",
+    DEBUG(resolver) debug_printf_indent("Coerced resolver DNSSEC support %s.\n",
         dns_dnssec_ok ? "on" : "off");
     }
   }
 # else
 if (dns_dnssec_ok >= 0)
-  DEBUG(D_resolver)
+  DEBUG(resolver)
     debug_printf_indent("Unable to %sset DNSSEC without resolver support.\n",
         dns_dnssec_ok ? "" : "un");
 if (use_dnssec)
-  DEBUG(D_resolver)
+  DEBUG(resolver)
     debug_printf_indent("Unable to set DNSSEC without resolver support.\n");
 # endif
 #endif /* DISABLE_DNSSEC */
@@ -318,6 +318,20 @@ dnss_inc_aptr(const dns_answer * dnsa, dns_scan * dnss, unsigned delta)
 return dnsa_bad_ptr(dnsa, dnss->aptr += delta);
 }
 
+static int
+dns_rr_expand_taint(const uschar * msg, const uschar * eom,
+  const uschar * comp_dn, dns_scan * dnss)
+{
+uschar buf[DNS_MAXNAME];
+int ret;
+
+if ((ret = dn_expand(msg, eom, comp_dn,
+		    (DN_EXPAND_ARG4_TYPE) buf, sizeof(buf))) < 0)
+  return ret;
+dnss->srr.name = string_copy_taint(buf, GET_TAINTED);
+return ret;
+}
+
 /*************************************************
 *       Get next DNS record from answer block    *
 *************************************************/
@@ -339,11 +353,12 @@ dns_record *
 dns_next_rr(const dns_answer * dnsa, dns_scan * dnss, int reset)
 {
 const HEADER * h = (const HEADER *)dnsa->answer;
+const uschar * eom = dnsa->answer + dnsa->answerlen;
 int namelen;
 
 char * trace = NULL;
 #ifdef rr_trace
-# define TRACE DEBUG(D_dns)
+# define TRACE DEBUG(dns)
 #else
 # define TRACE if (FALSE)
 #endif
@@ -361,9 +376,8 @@ if (reset != RESET_NEXT)
   while (dnss->rrcount-- > 0)
     {
     TRACE trace = "Q-namelen";
-    namelen = dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen,
-      dnss->aptr, (DN_EXPAND_ARG4_TYPE) &dnss->srr.name, DNS_MAXNAME);
-    if (namelen < 0) goto null_return;
+    if ((namelen = dns_rr_expand_taint(dnsa->answer, eom, dnss->aptr, dnss)) <0)
+      goto null_return;
     /* skip name & type & class */
     TRACE trace = "Q-skip";
     if (dnss_inc_aptr(dnsa, dnss, namelen+4)) goto null_return;
@@ -392,9 +406,9 @@ if (reset != RESET_NEXT)
     while (dnss->rrcount-- > 0)
       {
       TRACE trace = "A-namelen";
-      namelen = dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen,
-        dnss->aptr, (DN_EXPAND_ARG4_TYPE) &dnss->srr.name, DNS_MAXNAME);
-      if (namelen < 0) goto null_return;
+      if (  (namelen = dns_rr_expand_taint(dnsa->answer, eom, dnss->aptr, dnss))
+	  < 0)
+	goto null_return;
 
       /* skip name, type, class & TTL */
       TRACE trace = "A-hdr";
@@ -426,9 +440,8 @@ if (dnss->rrcount-- <= 0) return NULL;
 (something safe). */
 
 TRACE trace = "R-namelen";
-namelen = dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, dnss->aptr,
-  (DN_EXPAND_ARG4_TYPE) &dnss->srr.name, DNS_MAXNAME);
-if (namelen < 0) goto null_return;
+if ((namelen = dns_rr_expand_taint(dnsa->answer, eom, dnss->aptr, dnss)) < 0)
+  goto null_return;
 
 /* Move the pointer past the name and fill in the rest of the data structure
 from the following bytes.  We seem to be assuming here that the RR blob passed
@@ -482,9 +495,9 @@ Scan the whole AUTHORITY section, since it may contain other records
 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_extract_auth_name(const dns_answer * dnsa)
 {
-dns_scan dnss;
+dns_scan dnss = {0};
 const HEADER * h = (const HEADER *) dnsa->answer;
 
 if (h->nscount && h->aa)
@@ -514,7 +527,7 @@ BOOL
 dns_is_secure(const dns_answer * dnsa)
 {
 #ifdef DISABLE_DNSSEC
-DEBUG(D_dns)
+DEBUG(dns)
   debug_printf_indent("DNSSEC support disabled at build-time; dns_is_secure() false\n");
 return FALSE;
 #else
@@ -542,7 +555,7 @@ if (  !h->aa
    )
   return FALSE;
 
-DEBUG(D_dns) debug_printf_indent("DNS faked the AD bit "
+DEBUG(dns) debug_printf_indent("DNS faked the AD bit "
   "(got AA and matched with dns_trust_aa (%s in %s))\n",
   auth_name, dns_trust_aa);
 
@@ -666,7 +679,7 @@ else
   (void)tree_insertnode(&tree_dns_fails, new);
   }
 
-DEBUG(D_dns) debug_printf_indent(" %s neg-cache entry for %s, ttl %d\n",
+DEBUG(dns) debug_printf_indent(" %s neg-cache entry for %s, ttl %d\n",
   previous ? "update" : "writing",
   node_name, expiry ? (int)(expiry - time(NULL)) : -1);
 e->expiry = expiry;
@@ -693,7 +706,7 @@ e = previous->data.ptr;
 val = e->data.val;
 rc = e->expiry && e->expiry <= time(NULL) ? -1 : val;
 
-DEBUG(D_dns) debug_printf_indent("DNS lookup of %.255s (%s): %scached value %s%s\n",
+DEBUG(dns) debug_printf_indent("DNS lookup of %.255s (%s): %scached value %s%s\n",
   name, dns_text_type(type),
   rc == -1 ? "" : "using ",
   dns_rc_names[val],
@@ -717,7 +730,7 @@ the packet length if the packet header looks plausible.
 
 Return TRUE iff it seemed ok */
 
-static BOOL
+BOOL
 fake_dnsa_len_for_fail(dns_answer * dnsa, int type)
 {
 const HEADER * h = (const HEADER *)dnsa->answer;
@@ -731,12 +744,12 @@ if (  h->qr == 1				/* a response */
    && ntohs(h->ancount) == 0			/* no answer records */
    && ntohs(h->nscount) >= 1)			/* authority records */
   {
-  DEBUG(D_dns) debug_printf_indent("faking res_search(%s) response length as %d\n",
+  DEBUG(dns) debug_printf_indent("faking res_search(%s) response length as %d\n",
     dns_text_type(type), (int)sizeof(dnsa->answer));
   dnsa->answerlen = sizeof(dnsa->answer);
   return TRUE;
   }
-DEBUG(D_dns) debug_printf_indent("DNS: couldn't fake dnsa len\n");
+DEBUG(dns) debug_printf_indent("DNS: couldn't fake dnsa len\n");
 /* Maybe we should just do a second lookup for an SOA? */
 return FALSE;
 }
@@ -750,7 +763,7 @@ bother doing a separate lookup; if not found return a forever TTL.
 time_t
 dns_expire_from_soa(dns_answer * dnsa, int type)
 {
-dns_scan dnss;
+dns_scan dnss = {0};
 
 if (fake_dnsa_len_for_fail(dnsa, type))
   for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_AUTHORITY);
@@ -783,7 +796,7 @@ if (fake_dnsa_len_for_fail(dnsa, type))
     return time(NULL) + ttl;
     }
 
-DEBUG(D_dns) debug_printf_indent("DNS: no SOA record found for neg-TTL\n");
+DEBUG(dns) debug_printf_indent("DNS: no SOA record found for neg-TTL\n");
 return 0;
 }
 
@@ -841,15 +854,14 @@ if ((rc = dns_fail_cache_hit(name, type)) > 0)
 #ifdef SUPPORT_I18N
 /* Convert all names to a-label form before doing lookup */
   {
-  uschar * alabel;
+  const uschar * alabel;
   uschar * errstr = NULL;
-  DEBUG(D_dns) if (string_is_utf8(name))
+  DEBUG(dns) if (string_is_utf8(name))
     debug_printf_indent("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_indent("DNS name '%s' utf8 conversion to alabel failed: %s\n", name,
-        errstr);
+    DEBUG(dns) debug_printf_indent(
+      "DNS name '%s' utf8 conversion to alabel failed: %s\n", name, errstr);
     f.host_find_failed_syntax = TRUE;
     return DNS_NOMATCH;
     }
@@ -876,7 +888,7 @@ if (check_dns_names_pattern[0] != 0 && type != T_PTR && type != T_TXT)
   dns_pattern_init();
   if (!regex_match(regex_check_dns_names, name, -1, NULL))
     {
-    DEBUG(D_dns)
+    DEBUG(dns)
       debug_printf_indent("DNS name syntax check failed: %s (%s)\n", name,
         dns_text_type(type));
     f.host_find_failed_syntax = TRUE;
@@ -909,7 +921,7 @@ dnsa->answerlen = f.running_in_test_harness
 
 if (dnsa->answerlen > (int) sizeof(dnsa->answer))
   {
-  DEBUG(D_dns) debug_printf_indent("DNS lookup of %s (%s) resulted in overlong packet"
+  DEBUG(dns) debug_printf_indent("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);
@@ -918,12 +930,12 @@ if (dnsa->answerlen > (int) sizeof(dnsa->answer))
 if (dnsa->answerlen < 0) switch (h_errno)
   {
   case HOST_NOT_FOUND:
-    DEBUG(D_dns) debug_printf_indent("DNS lookup of %s (%s) gave HOST_NOT_FOUND\n"
+    DEBUG(dns) debug_printf_indent("DNS lookup of %s (%s) gave HOST_NOT_FOUND\n"
       "returning DNS_NOMATCH\n", name, dns_text_type(type));
     return dns_fail_return(name, type, dns_expire_from_soa(dnsa, type), DNS_NOMATCH);
 
   case TRY_AGAIN:
-    DEBUG(D_dns) debug_printf_indent("DNS lookup of %s (%s) gave TRY_AGAIN\n",
+    DEBUG(dns) debug_printf_indent("DNS lookup of %s (%s) gave TRY_AGAIN\n",
       name, dns_text_type(type));
 
     /* Cut this out for various test programs */
@@ -937,7 +949,7 @@ if (dnsa->answerlen < 0) switch (h_errno)
       {
       if (try_again_recursion)
 	{
-	log_write(0, LOG_MAIN|LOG_PANIC,
+	log_write(LOG_MAIN|LOG_PANIC,
 	  "dns_again_means_nonexist recursion seen for %s"
 	  " (assuming nonexist)", name);
 	return dns_fail_return(name, type, dns_expire_from_soa(dnsa, type),
@@ -955,10 +967,10 @@ if (dnsa->answerlen < 0) switch (h_errno)
 
     if (rc != OK)
       {
-      DEBUG(D_dns) debug_printf_indent("returning DNS_AGAIN\n");
+      DEBUG(dns) debug_printf_indent("returning DNS_AGAIN\n");
       return dns_fail_return(name, type, 0, DNS_AGAIN);
       }
-    DEBUG(D_dns) debug_printf_indent("%s is in dns_again_means_nonexist: returning "
+    DEBUG(dns) debug_printf_indent("%s is in dns_again_means_nonexist: returning "
       "DNS_NOMATCH\n", name);
     return dns_fail_return(name, type, dns_expire_from_soa(dnsa, type), DNS_NOMATCH);
 
@@ -967,22 +979,22 @@ if (dnsa->answerlen < 0) switch (h_errno)
 #endif
 
   case NO_RECOVERY:
-    DEBUG(D_dns) debug_printf_indent("DNS lookup of %s (%s) gave NO_RECOVERY\n"
+    DEBUG(dns) debug_printf_indent("DNS lookup of %s (%s) gave NO_RECOVERY\n"
       "returning DNS_FAIL\n", name, dns_text_type(type));
     return dns_fail_return(name, type, 0, DNS_FAIL);
 
   case NO_DATA:
-    DEBUG(D_dns) debug_printf_indent("DNS lookup of %s (%s) gave NO_DATA\n"
+    DEBUG(dns) debug_printf_indent("DNS lookup of %s (%s) gave NO_DATA\n"
       "returning DNS_NODATA\n", name, dns_text_type(type));
     return dns_fail_return(name, type, dns_expire_from_soa(dnsa, type), DNS_NODATA);
 
   default:
-    DEBUG(D_dns) debug_printf_indent("DNS lookup of %s (%s) gave unknown DNS error %d\n"
+    DEBUG(dns) debug_printf_indent("DNS lookup of %s (%s) gave unknown DNS error %d\n"
       "returning DNS_FAIL\n", name, dns_text_type(type), h_errno);
     return dns_fail_return(name, type, 0, DNS_FAIL);
   }
 
-DEBUG(D_dns) debug_printf_indent("DNS lookup of %s (%s) succeeded\n",
+DEBUG(dns) debug_printf_indent("DNS lookup of %s (%s) succeeded\n",
   name, dns_text_type(type));
 
 return DNS_SUCCEED;
@@ -1046,8 +1058,8 @@ former will work. */
 for (int i = 0; i <= dns_cname_loops; i++)
   {
   uschar * data;
-  dns_record cname_rr, type_rr;
-  dns_scan dnss;
+  dns_record cname_rr = {0}, type_rr = {0};
+  dns_scan dnss = {0};
 
   /* DNS lookup failures get passed straight back. */
 
@@ -1060,7 +1072,6 @@ for (int i = 0; i <= dns_cname_loops; i++)
   contents of any rr blocks returned by dns_next_rr() as they use the same
   area in the dnsa block. */
 
-  cname_rr.data = type_rr.data = NULL;
   for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS);
        rr; rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
     if (rr->type == type)
@@ -1121,16 +1132,23 @@ for (int i = 0; i <= dns_cname_loops; i++)
     }
   name = data;
 
+  for (int i = 256-50; i > 0; i--)	/* Usually much smaller, so find end */
+    if (!*data++)
+      {
+      store_release_above(data);	/* and release excess allocation */
+      break;
+      }
+
   if (!dns_is_secure(dnsa))
     secure_so_far = FALSE;
 
-  DEBUG(D_dns) debug_printf_indent("CNAME found: change to %s\n", name);
+  DEBUG(dns) debug_printf_indent("CNAME found: change to %s\n", name);
   }       /* Loop back to do another lookup */
 
 /* Control reaches here after 10 times round the CNAME loop. Something isn't
 right... */
 
-log_write(0, LOG_MAIN, "CNAME loop for %s encountered", orig_name);
+log_write(LOG_MAIN, "CNAME loop for %s encountered", orig_name);
 errstr = US"cname_loop";
 
 not_good:
@@ -1240,10 +1258,10 @@ switch (type)
     uschar * namesuff, * tld;
     int priority, dummy_weight, port, limit, rc, i;
     BOOL ipv6;
-    dns_record *rr;
-    dns_scan dnss;
+    dns_record * rr;
+    dns_scan dnss = {0};
 
-    DEBUG(D_dns) debug_printf_indent("CSA lookup of %s\n", name);
+    DEBUG(dns) debug_printf_indent("CSA lookup of %s\n", name);
 
     srvname = string_sprintf("_client._smtp.%s", name);
     rc = dns_lookup(dnsa, srvname, T_SRV, NULL);
@@ -1281,7 +1299,7 @@ switch (type)
 	limit = 3;
 	}
 
-    DEBUG(D_dns) debug_printf_indent("CSA TLD %s\n", tld);
+    DEBUG(dns) debug_printf_indent("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
@@ -1308,7 +1326,7 @@ switch (type)
 	  if (--namesuff <= name) return DNS_NOMATCH;
 	while (*namesuff != '.');
 
-      DEBUG(D_dns) debug_printf_indent("CSA parent search at %s\n", namesuff + 1);
+      DEBUG(dns) debug_printf_indent("CSA parent search at %s\n", namesuff + 1);
 
       srvname = string_sprintf("_client._smtp.%s", namesuff + 1);
       rc = dns_lookup(dnsa, srvname, T_SRV, NULL);
diff --git a/src/src/dnsbl.c b/src/src/dnsbl.c
index dc4a0bea0..7a885927d 100644
--- a/src/src/dnsbl.c
+++ b/src/src/dnsbl.c
@@ -65,12 +65,12 @@ Returns:         OK if lookup succeeded
 */
 
 static int
-one_check_dnsbl(uschar *domain, uschar *domain_txt, uschar *keydomain,
-  uschar *prepend, uschar *iplist, BOOL bitmask, int match_type,
+one_check_dnsbl(uschar * domain, uschar * domain_txt, uschar * keydomain,
+  uschar * prepend, uschar * iplist, BOOL bitmask, int match_type,
   int defer_return)
 {
 dns_answer * dnsa = store_get_dns_answer();
-dns_scan dnss;
+dns_scan dnss = {0};
 tree_node *t;
 dnsbl_cache_block *cb;
 int old_pool = store_pool;
@@ -82,7 +82,7 @@ int qlen, yield;
 query = string_sprintf("%s.%s", prepend, domain);
 if ((qlen = Ustrlen(query)) >= 256)
   {
-  log_write(0, LOG_MAIN|LOG_PANIC, "dnslist query is too long "
+  log_write(LOG_MAIN|LOG_PANIC, "dnslist query is too long "
     "(ignored): %s...", query);
   yield = FAIL;
   goto out;
@@ -97,7 +97,7 @@ if (  (t = tree_search(dnsbl_cache, query))
 /* Previous lookup was cached */
 
   {
-  HDEBUG(D_dnsbl) debug_printf("dnslists: using result of previous lookup\n");
+  HDEBUG(dnsbl) debug_printf("dnslists: using result of previous lookup\n");
   }
 
 /* If not cached from a previous lookup, we must do a DNS lookup, and
@@ -111,7 +111,7 @@ else
 
   if (t)
     {
-    HDEBUG(D_dnsbl) debug_printf("cached data found but past valid time; ");
+    HDEBUG(dnsbl) debug_printf("cached data found but past valid time; ");
     }
 
   else
@@ -124,7 +124,7 @@ else
 
   /* Do the DNS lookup . */
 
-  HDEBUG(D_dnsbl) debug_printf("new DNS lookup for %s\n", query);
+  HDEBUG(dnsbl) debug_printf("new DNS lookup for %s\n", query);
   cb->rc = dns_basic_lookup(dnsa, query, T_A);
   cb->text_set = FALSE;
   cb->text = NULL;
@@ -190,7 +190,7 @@ else
     }
 
   store_pool = old_pool;
-  HDEBUG(D_dnsbl) debug_printf("dnslists: wrote cache entry, ttl=%d\n",
+  HDEBUG(dnsbl) debug_printf("dnslists: wrote cache entry, ttl=%d\n",
     (int)(cb->expiry - time(NULL)));
   }
 
@@ -202,17 +202,17 @@ list (introduced by "&"), or a negative bitmask list (introduced by "!&").*/
 
 if (cb->rc == DNS_SUCCEED)
   {
-  dns_address * da = NULL;
-  uschar *addlist = cb->rhs->address;
+  gstring * addlist = NULL;
 
   /* For A and AAAA records, there may be multiple addresses from multiple
   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; da = da->next)
-    addlist = string_sprintf("%s, %s", addlist, da->address);
+  for (dns_address * da = cb->rhs; da; da = da->next)
+    addlist = string_append2_listele_n(addlist, US", ",
+					da->address, Ustrlen(da->address));
 
-  HDEBUG(D_dnsbl) debug_printf("DNS lookup for %s succeeded (yielding %s)\n",
+  HDEBUG(dnsbl) debug_printf("DNS lookup for %s succeeded (yielding %Y)\n",
     query, addlist);
 
   /* Address list check; this can be either for equality, or via a bitmask.
@@ -220,6 +220,7 @@ if (cb->rc == DNS_SUCCEED)
 
   if (iplist)
     {
+    dns_address * da;
     for (da = cb->rhs; da; da = da->next)
       {
       int ipsep = ',';
@@ -241,7 +242,7 @@ if (cb->rc == DNS_SUCCEED)
 
         if (host_aton(da->address, address) == 1)
 	  if ((address[0] & 0xff000000) != 0x7f000000)    /* 127.0.0.0/8 */
-	    log_write(0, LOG_MAIN,
+	    log_write(LOG_MAIN,
 	      "DNS list lookup for %s at %s returned %s;"
 	      " not in 127.0/8 and discarded",
 	      keydomain, domain, da->address);
@@ -284,7 +285,7 @@ if (cb->rc == DNS_SUCCEED)
 
     if ((match_type == MT_NOT || match_type == MT_ALL) != (da == NULL))
       {
-      HDEBUG(D_dnsbl)
+      HDEBUG(dnsbl)
         {
         uschar *res = NULL;
         switch(match_type)
@@ -315,7 +316,7 @@ if (cb->rc == DNS_SUCCEED)
   else
     {
     BOOL ok = FALSE;
-    for (da = cb->rhs; da; da = da->next)
+    for (dns_address * da = cb->rhs; da; da = da->next)
       {
       int address[4];
 
@@ -324,7 +325,7 @@ if (cb->rc == DNS_SUCCEED)
 	 )
 	ok = TRUE;
       else
-	log_write(0, LOG_MAIN,
+	log_write(LOG_MAIN,
 	    "DNS list lookup for %s at %s returned %s;"
 	    " not in 127.0/8 and discarded",
 	    keydomain, domain, da->address);
@@ -363,16 +364,16 @@ if (cb->rc == DNS_SUCCEED)
 	  int len = (rr->data)[0];
 	  if (len > 511) len = 127;
 	  store_pool = POOL_PERM;
-	  cb->text = string_copyn_taint(CUS (rr->data+1), len, GET_TAINTED);
+	  cb->text = string_copyn(CUS (rr->data+1), len);
 	  store_pool = old_pool;
 	  break;
 	  }
     }
 
   /* $dnslist_* likely apply to the conn not a message, so we want them not
-  destroyed by reset_store() between messages */
+  destroyed by reset_store() between messages. Use the perm pool. */
 
-  dnslist_value = addlist ? string_copy_perm(addlist, FALSE) : NULL;
+  dnslist_value = addlist ? string_copy_perm(addlist->s, FALSE) : NULL;
   dnslist_text = cb->text ? string_copy_perm(cb->text, FALSE) : NULL;
   yield = OK;
   goto out;
@@ -382,7 +383,7 @@ if (cb->rc == DNS_SUCCEED)
 
 if (cb->rc != DNS_NOMATCH && cb->rc != DNS_NODATA)
   {
-  log_write(L_dnslist_defer, LOG_MAIN,
+  if (LOGGING(dnslist_defer)) log_write(LOG_MAIN,
     "DNS list lookup defer (probably timeout) for %s: %s", query,
     defer_return == OK ?   US"assumed in list" :
     defer_return == FAIL ? US"assumed not in list" :
@@ -393,7 +394,7 @@ if (cb->rc != DNS_NOMATCH && cb->rc != DNS_NODATA)
 
 /* No entry was found in the DNS; continue for next domain */
 
-HDEBUG(D_dnsbl)
+HDEBUG(dnsbl)
   {
   debug_printf("DNS lookup for %s failed\n", query);
   debug_printf("=> that means %s is not listed at %s\n",
@@ -492,7 +493,7 @@ if (!(s = expand_string(s)))
     list, expand_string_message);
   return f.search_find_defer ? DEFER : ERROR;
   }
-HDEBUG(D_acl) if (s != list) debug_printf_indent("expanded list: %s\n", s);
+HDEBUG(acl) if (s != list) debug_printf_indent("expanded list: %s\n", s);
 list = s;
 
 /* Loop through all the domains supplied, until something matches */
@@ -502,12 +503,9 @@ while ((domain = string_nextinlist(&list, &sep, NULL, 0)))
   int rc;
   BOOL bitmask = FALSE;
   int match_type = 0;
-  uschar *domain_txt;
-  uschar *comma;
-  uschar *iplist;
-  uschar *key;
+  uschar * domain_txt, * comma, * iplist, * key;
 
-  HDEBUG(D_dnsbl) debug_printf("dnslists check: %s\n", domain);
+  HDEBUG(dnsbl) debug_printf("dnslists check: %s\n", domain);
 
   /* Deal with special values that change the behaviour on defer */
 
@@ -517,7 +515,7 @@ while ((domain = string_nextinlist(&list, &sep, NULL, 0)))
     else if (strcmpic(domain, US"+exclude_unknown") == 0) defer_return = FAIL;
     else if (strcmpic(domain, US"+defer_unknown") == 0)   defer_return = DEFER;
     else
-      log_write(0, LOG_MAIN|LOG_PANIC, "unknown item in dnslist (ignored): %s",
+      log_write(LOG_MAIN|LOG_PANIC, "unknown item in dnslist (ignored): %s",
         domain);
     continue;
     }
@@ -576,7 +574,7 @@ while ((domain = string_nextinlist(&list, &sep, NULL, 0)))
   for (const uschar * t = domain; *t; t++)
     if (!isalnum(*t) && *t != '-' && *t != '.' && *t != '_')
       {
-      log_write(0, LOG_MAIN, "dnslists domain %q contains "
+      log_write(LOG_MAIN, "dnslists domain %q contains "
         "strange characters - is this right?", domain);
       break;
       }
@@ -586,7 +584,7 @@ while ((domain = string_nextinlist(&list, &sep, NULL, 0)))
   if (domain_txt != domain) for (const uschar * t = domain_txt; *t; t++)
     if (!isalnum(*t) && *t != '-' && *t != '.' && *t != '_')
       {
-      log_write(0, LOG_MAIN, "dnslists domain %q contains "
+      log_write(LOG_MAIN, "dnslists domain %q contains "
         "strange characters - is this right?", domain_txt);
       break;
       }
@@ -611,7 +609,7 @@ while ((domain = string_nextinlist(&list, &sep, NULL, 0)))
       {
       dnslist_domain = domain_txt ? string_copy_perm(domain_txt, FALSE) : NULL;
       dnslist_matched = string_copy_perm(sender_host_address, FALSE);
-      HDEBUG(D_dnsbl) debug_printf("=> that means %s is listed at %s\n",
+      HDEBUG(dnsbl) debug_printf("=> that means %s is listed at %s\n",
         sender_host_address, dnslist_domain);
       }
     if (rc != FAIL) return rc;     /* OK or DEFER */
@@ -643,7 +641,7 @@ while ((domain = string_nextinlist(&list, &sep, NULL, 0)))
         {
         dnslist_domain = domain_txt ? string_copy_perm(domain_txt, FALSE) :NULL;
         dnslist_matched = keydomain ? string_copy_perm(keydomain, FALSE) : NULL;
-        HDEBUG(D_dnsbl) debug_printf("=> that means %s is listed at %s\n",
+        HDEBUG(dnsbl) debug_printf("=> that means %s is listed at %s\n",
           keydomain, dnslist_domain);
         return OK;
         }
diff --git a/src/src/drtables.c b/src/src/drtables.c
index adbf7d539..66b4e0a1d 100644
--- a/src/src/drtables.c
+++ b/src/src/drtables.c
@@ -31,200 +31,52 @@ transport_info * transports_available = NULL;
 
 #ifndef MACRO_PREDEF
 
-gstring *
-auth_show_supported(gstring * g)
+static gstring *
+dr_show_list(gstring * g, const uschar ** list, const uschar * label,
+  const uschar * class)
 {
-uschar * b = US""               /* static-build authenticatornames */
-#if defined(AUTH_CRAM_MD5) && AUTH_CRAM_MD5!=2
-  " cram_md5"
-#endif
-#if defined(AUTH_CYRUS_SASL) && AUTH_CYRUS_SASL!=2
-  " cyrus_sasl"
-#endif
-#if defined(AUTH_DOVECOT) && AUTH_DOVECOT!=2
-  " dovecot"
-#endif
-#if defined(AUTH_EXTERNAL) && AUTH_EXTERNAL!=2
-  " external"
-#endif
-#if defined(AUTH_GSASL) && AUTH_GSASL!=2
-  " gsasl"
-#endif
-#if defined(AUTH_HEIMDAL_GSSAPI) && AUTH_HEIMDAL_GSSAPI!=2
-  " heimdal_gssapi"
-#endif
-#if defined(AUTH_PLAINTEXT) && AUTH_PLAINTEXT!=2
-  " plaintext"
-#endif
-#if defined(AUTH_SPA) && AUTH_SPA!=2
-  " spa"
-#endif
-#if defined(AUTH_TLS) && AUTH_TLS!=2
-  " tls"
-#endif
-  ;
-
-uschar * d = US""		/* dynamic-module authenticator names */
-#if defined(AUTH_CRAM_MD5) && AUTH_CRAM_MD5==2
-  " cram_md5"
-#endif
-#if defined(AUTH_CYRUS_SASL) && AUTH_CYRUS_SASL==2
-  " cyrus_sasl"
-#endif
-#if defined(AUTH_DOVECOT) && AUTH_DOVECOT==2
-  " dovecot"
-#endif
-#if defined(AUTH_EXTERNAL) && AUTH_EXTERNAL==2
-  " external"
-#endif
-#if defined(AUTH_GSASL) && AUTH_GSASL==2
-  " gsasl"
-#endif
-#if defined(AUTH_HEIMDAL_GSSAPI) && AUTH_HEIMDAL_GSSAPI==2
-  " heimdal_gssapi"
-#endif
-#if defined(AUTH_PLAINTEXT) && AUTH_PLAINTEXT==2
-  " plaintext"
-#endif
-#if defined(AUTH_SPA) && AUTH_SPA==2
-  " spa"
-#endif
-#if defined(AUTH_TLS) && AUTH_TLS==2
-  " tls"
-#endif
-  ;
+if (*list)
+  {
+  const uschar ** ele = list;
+  g = string_fmt_append(g, "%s (%s): ", class, label);
+  while (*ele) ele++;
+  while (--ele >= list) g = string_fmt_append(g, " %s", *ele);
+  g = string_catn(g, US"\n", 1);
+  }
+return g;
+}
 
-if (*b) g = string_fmt_append(g, "Authenticators (built-in):%s\n", b);
-if (*d) g = string_fmt_append(g, "Authenticators (dynamic): %s\n", d);
+static gstring *
+dr_show_supported(gstring * g,
+  const uschar ** statics, const uschar ** dynamics, const uschar * class)
+{
+g = dr_show_list(g, statics, US"built-in", class);
+g = dr_show_list(g, dynamics, US"dynamic", class);
 return g;
 }
 
 gstring *
-route_show_supported(gstring * g)
+auth_show_supported(gstring * g)
 {
-uschar * b = US""		/* static-build router names */
-#if defined(ROUTER_ACCEPT) && ROUTER_ACCEPT!=2
-  " accept"
-#endif
-#if defined(ROUTER_DNSLOOKUP) && ROUTER_DNSLOOKUP!=2
-  " dnslookup"
-#endif
-# if defined(ROUTER_IPLITERAL) && ROUTER_IPLITERAL!=2
-  " ipliteral"
-#endif
-#if defined(ROUTER_IPLOOKUP) && ROUTER_IPLOOKUP!=2
-  " iplookup"
-#endif
-#if defined(ROUTER_MANUALROUTE) && ROUTER_MANUALROUTE!=2
-  " manualroute"
-#endif
-#if defined(ROUTER_REDIRECT) && ROUTER_REDIRECT!=2
-  " redirect"
-#endif
-#if defined(ROUTER_QUERYPROGRAM) && ROUTER_QUERYPROGRAM!=2
-  " queryprogram"
-#endif
-  ;
-
-uschar * d = US""		/* dynamic-module router names */
-#if defined(ROUTER_ACCEPT) && ROUTER_ACCEPT==2
-  " accept"
-#endif
-#if defined(ROUTER_DNSLOOKUP) && ROUTER_DNSLOOKUP==2
-  " dnslookup"
-#endif
-# if defined(ROUTER_IPLITERAL) && ROUTER_IPLITERAL==2
-  " ipliteral"
-#endif
-#if defined(ROUTER_IPLOOKUP) && ROUTER_IPLOOKUP==2
-  " iplookup"
-#endif
-#if defined(ROUTER_MANUALROUTE) && ROUTER_MANUALROUTE==2
-  " manualroute"
-#endif
-#if defined(ROUTER_REDIRECT) && ROUTER_REDIRECT==2
-  " redirect"
-#endif
-#if defined(ROUTER_QUERYPROGRAM) && ROUTER_QUERYPROGRAM==2
-  " queryprogram"
-#endif
-  ;
+return dr_show_supported(g, avail_static_auths, avail_dynamic_auths,
+  US"Authenticators");
+}
 
-if (*b) g = string_fmt_append(g, "Routers (built-in):%s\n", b);
-if (*d) g = string_fmt_append(g, "Routers (dynamic): %s\n", d);
-return g;
+gstring *
+route_show_supported(gstring * g)
+{
+return dr_show_supported(g, avail_static_routers, avail_dynamic_routers,
+  US"Routers");
 }
 
 gstring *
 transport_show_supported(gstring * g)
 {
-uschar * b = US""		/* static-build transportnames */
-#if defined(TRANSPORT_APPENDFILE) && TRANSPORT_APPENDFILE!=2
-  " appendfile"
-# ifdef SUPPORT_MAILDIR
-    "/maildir"
-# endif
-# ifdef SUPPORT_MAILSTORE
-    "/mailstore"
-# endif
-# ifdef SUPPORT_MBX
-    "/mbx"
-# endif
-#endif
-#if defined(TRANSPORT_AUTOREPLY) && TRANSPORT_AUTOREPLY!=2
-  " autoreply"
-#endif
-#if defined(TRANSPORT_LMTP) && TRANSPORT_LMTP!=2
-  " lmtp"
-#endif
-#if defined(TRANSPORT_PIPE) && TRANSPORT_PIPE!=2
-  " pipe"
-#endif
-#if defined(EXPERIMENTAL_QUEUEFILE) && EXPERIMENTAL_QUEUEFILE!=2
-  " queuefile"
-#endif
-#if defined(TRANSPORT_SMTP) && TRANSPORT_SMTP!=2
-  " smtp"
-#endif
-  ;
-
-uschar * d = US""		/* dynamic-module transportnames */
-#if defined(TRANSPORT_APPENDFILE) && TRANSPORT_APPENDFILE==2
-  " appendfile"
-# ifdef SUPPORT_MAILDIR
-    "/maildir"
-# endif
-# ifdef SUPPORT_MAILSTORE
-    "/mailstore"
-# endif
-# ifdef SUPPORT_MBX
-    "/mbx"
-# endif
-#endif
-#if defined(TRANSPORT_AUTOREPLY) && TRANSPORT_AUTOREPLY==2
-  " autoreply"
-#endif
-#if defined(TRANSPORT_LMTP) && TRANSPORT_LMTP==2
-  " lmtp"
-#endif
-#if defined(TRANSPORT_PIPE) && TRANSPORT_PIPE==2
-  " pipe"
-#endif
-#if defined(EXPERIMENTAL_QUEUEFILE) && EXPERIMENTAL_QUEUEFILE==2
-  " queuefile"
-#endif
-#if defined(TRANSPORT_SMTP) && TRANSPORT_SMTP==2
-  " smtp"
-#endif
-  ;
-
-if (*b) g = string_fmt_append(g, "Transports (built-in):%s\n", b);
-if (*d) g = string_fmt_append(g, "Transports (dynamic): %s\n", d);
-return g;
+return dr_show_supported(g, avail_static_transports, avail_dynamic_transports,
+  US"Transports");
 }
 
 
-
 static void
 add_lookup_to_tree(lookup_info * li)
 {
@@ -235,7 +87,7 @@ Ustrcpy(new->name, li->name);
 if (tree_insertnode(&lookups_tree, new))
   li->acq_num = lookup_list_count++;
 else
-  log_write(0, LOG_MAIN|LOG_PANIC, "Duplicate lookup name '%s'", li->name);
+  log_write(LOG_MAIN|LOG_PANIC, "Duplicate lookup name '%s'", li->name);
 }
 
 
@@ -271,77 +123,6 @@ return li;
 
 
 
-/* These need to be at file level for old versions of gcc (2.95.2 reported),
-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_CDB) && LOOKUP_CDB!=2
-extern lookup_module_info cdb_lookup_module_info;
-#endif
-#if defined(LOOKUP_DBM) && LOOKUP_DBM!=2
-extern lookup_module_info dbmdb_lookup_module_info;
-#endif
-#if defined(LOOKUP_DNSDB) && LOOKUP_DNSDB!=2
-extern lookup_module_info dnsdb_lookup_module_info;
-#endif
-#if defined(LOOKUP_DSEARCH) && LOOKUP_DSEARCH!=2
-extern lookup_module_info dsearch_lookup_module_info;
-#endif
-#if defined(LOOKUP_IBASE) && LOOKUP_IBASE!=2
-extern lookup_module_info ibase_lookup_module_info;
-#endif
-#if defined(LOOKUP_JSON) && LOOKUP_JSON!=2
-extern lookup_module_info json_lookup_module_info;
-#endif
-#if defined(LOOKUP_LDAP) && LOOKUP_LDAP!=2
-extern lookup_module_info ldap_lookup_module_info;
-#endif
-#if defined(LOOKUP_LSEARCH) && LOOKUP_LSEARCH!=2
-extern lookup_module_info lsearch_lookup_module_info;
-#endif
-#if defined(LOOKUP_MYSQL) && LOOKUP_MYSQL!=2
-extern lookup_module_info mysql_lookup_module_info;
-#endif
-#if defined(LOOKUP_NIS) && LOOKUP_NIS!=2
-extern lookup_module_info nis_lookup_module_info;
-#endif
-#if defined(LOOKUP_NISPLUS) && LOOKUP_NISPLUS!=2
-extern lookup_module_info nisplus_lookup_module_info;
-#endif
-#if defined(EXPERIMENTAL_NMH) && EXPERIMENTAL_NMH!=2
-extern lookup_module_info nmh_lookup_module_info;
-#endif
-#if defined(LOOKUP_ORACLE) && LOOKUP_ORACLE!=2
-extern lookup_module_info oracle_lookup_module_info;
-#endif
-#if defined(LOOKUP_PASSWD) && LOOKUP_PASSWD!=2
-extern lookup_module_info passwd_lookup_module_info;
-#endif
-#if defined(LOOKUP_PGSQL) && LOOKUP_PGSQL!=2
-extern lookup_module_info pgsql_lookup_module_info;
-#endif
-#if defined(LOOKUP_REDIS) && LOOKUP_REDIS!=2
-extern lookup_module_info redis_lookup_module_info;
-#endif
-#if defined(LOOKUP_LMDB) && LOOKUP_LMDB!=2
-extern lookup_module_info lmdb_lookup_module_info;
-#endif
-#if defined(EXIM_HAVE_SPF)
-extern lookup_module_info spf_lookup_module_info;	/* see below */
-#endif
-#if defined(LOOKUP_SQLITE) && LOOKUP_SQLITE!=2
-extern lookup_module_info sqlite_lookup_module_info;
-#endif
-#if defined(LOOKUP_TESTDB) && LOOKUP_TESTDB!=2
-extern lookup_module_info testdb_lookup_module_info;
-#endif
-#if defined(LOOKUP_WHOSON) && LOOKUP_WHOSON!=2
-extern lookup_module_info whoson_lookup_module_info;
-#endif
-
-extern lookup_module_info readsock_lookup_module_info;
-
-
 #ifdef LOOKUP_MODULE_DIR
 static void *
 mod_open(const uschar * name, const uschar * class, uschar ** errstr)
@@ -371,7 +152,7 @@ return dl;
 /* Try to load a lookup module with the given name.
 
 Arguments:
-    name		name of the lookup
+    name		name of the module
     errstr		if not NULL, place "open fail" error message here
 
 Return: boolean success
@@ -381,32 +162,36 @@ static BOOL
 lookup_mod_load(const uschar * name, uschar ** errstr)
 {
 void * dl;
-struct lookup_module_info * info;
+lookup_module_info * info;
 const char * errormsg;
 
 if (!(dl = mod_open(name, US"lookup", errstr)))
   return FALSE;
 
-info = (struct lookup_module_info *) dlsym(dl, "_lookup_module_info");
+info = (lookup_module_info *) dlsym(dl, "_lookup_module_info");
 if ((errormsg = dlerror()))
   {
-  EARLY_DEBUG(D_any, "%s does not appear to be a lookup module (%s)\n", name, errormsg);
-  log_write(0, LOG_MAIN|LOG_PANIC, "%s does not appear to be a lookup module (%s)", name, errormsg);
+  EARLY_DEBUG(any, "%s does not appear to be a lookup module (%s)\n", name, errormsg);
+  log_write(LOG_MAIN|LOG_PANIC, "%s does not appear to be a lookup module (%s)", name, errormsg);
   dlclose(dl);
   return FALSE;
   }
 if (info->magic != LOOKUP_MODULE_INFO_MAGIC)
   {
-  EARLY_DEBUG(D_any, "Lookup module %s is not compatible with this version of Exim\n", name);
-  log_write(0, LOG_MAIN|LOG_PANIC, "Lookup module %s is not compatible with this version of Exim", name);
+  EARLY_DEBUG(any, "Lookup module %s is not compatible with this version of Exim\n", name);
+  log_write(LOG_MAIN|LOG_PANIC, "Lookup module %s is not compatible with this version of Exim", name);
   dlclose(dl);
   return FALSE;
   }
 
 addlookupmodule(info);
-EARLY_DEBUG(D_lookup, "Loaded %q (%d lookup type%s)\n",
+if (debug_startup)
+  { EARLY_DEBUG(lookup, "Loaded %q (%d lookup type%s)\n",
 				    name, info->lookupcount,
-				    info->lookupcount > 1 ? "s" : "");
+				    info->lookupcount > 1 ? "s" : ""); }
+else
+  DEBUG(lookup) debug_printf_indent("Loaded module %q\n", name);
+
 return TRUE;
 }
 
@@ -424,8 +209,89 @@ if (!lookup_mod_load(name, errstr)) return FALSE;
 return TRUE;
 }
 
+
+/* Try to load a module of any class, given just the name.
+This is used for the daemon_modules_load option.
+Do not bother with class auth,router,transport - readconf will load if used.
+*/
+
+void
+mod_load_anyclass(const uschar * name)
+{
+DIR * dd;
+const pcre2_code * regex_class;
+
+/* Find the class for this name, by checking the lookup modules dir.
+We assume the .so files there are properly worded, and that the names
+are distinct. */
+
+if (!(dd = open_module_dir())) return;
+
+regex_class = regex_must_compile(
+  string_sprintf("^%s_(lookup|miscmod)\\." DYNLIB_FN_EXT "$", name),
+  MCS_NOFLAGS, TRUE);
+
+for (struct dirent * ent; ent = readdir(dd); )
+  if (regex_match_and_setup(regex_class, US ent->d_name, 0, 0))
+    if (Ustrcmp(expand_nstring[1], "miscmod") == 0)
+      (void) misc_mod_find(name, NULL);
+    else	/* assume "lookup" */
+      if (!tree_search(lookups_tree, name)) lookup_one_mod_load(name, NULL);
+}
 #endif	/*LOOKUP_MODULE_DIR*/
 
+const lookup_info *
+lookup_find(const uschar * name, uschar ** errstr)
+{
+#ifdef LOOKUP_MODULE_DIR
+const lookup_info * li;
+if ((li = lookup_findonly(name))) return li;
+lookup_mod_load(name, errstr);
+#endif
+return lookup_findonly(name);
+}
+
+/* Look at all the lookup module files and add a name from each lookup type */
+
+gstring *
+lookup_dynamic_supported(gstring * g)
+{
+#ifdef LOOKUP_MODULE_DIR
+DIR * dd;
+const pcre2_code * regex_islookupmod = regex_must_compile(
+  US"^([a-z0-9]+)_lookup\\." DYNLIB_FN_EXT "$", MCS_NOFLAGS, TRUE);
+
+if (!(dd = open_module_dir()))
+  g = string_cat(g, US"FAIL exim_opendir");
+else
+  {
+  for (struct dirent * ent; ent = readdir(dd); )
+    {
+    void * dl;
+    uschar * errstr;
+
+    if (  regex_match_and_setup(regex_islookupmod, US ent->d_name, 0, 0)
+       && (dl = mod_open(expand_nstring[1], US"lookup", &errstr))
+       )
+      {
+      lookup_module_info * lmi=
+	(lookup_module_info *) dlsym(dl, "_lookup_module_info");
+
+      if (  ! dlerror()
+	 && lmi->magic == LOOKUP_MODULE_INFO_MAGIC
+         )
+	for (lookup_info ** lip = lmi->lookups;
+	    lip < lmi->lookups + lmi->lookupcount; lip++)
+	  g = string_fmt_append(g, " %s", (*lip)->name);
+
+      dlclose(dl);
+      }
+    }
+  }
+#endif	/*!LOOKUP_MODULE_DIR*/
+return g;
+}
+
 
 
 misc_module_info * misc_module_list = NULL;
@@ -438,17 +304,17 @@ misc_module_list = mi;
 
 if (mi->init)
   {
-  EARLY_DEBUG(D_any, "Module init: %q\n", mi->name);
+  EARLY_DEBUG(start, "Module init: %q\n", mi->name);
   expand_level++;
   if (!mi->init(mi))
-    EARLY_DEBUG(D_any, "module init call failed for %q\n", mi->name);
+    EARLY_DEBUG(any, "module init call failed for %q\n", mi->name);
   expand_level--;
   }
 
 if (mi->lib_vers_report)
-  DEBUG(D_any) debug_printf_indent("%Y", mi->lib_vers_report(NULL));
+  DEBUG(start) debug_printf_indent("%Y", mi->lib_vers_report(NULL));
 
-/* EARLY_DEBUG(D_any, "added %q\n", mi->name); */
+/* EARLY_DEBUG(any, "added %q\n", mi->name); */
 }
 
 
@@ -463,10 +329,10 @@ void * dl;
 struct misc_module_info * mi;
 const char * errormsg;
 
-EARLY_DEBUG(D_any, "loading module %q\n", name);
+EARLY_DEBUG(any, "Loading module %q\n", name);
 if (!(dl = mod_open(name, US"miscmod", errstr)))
   {
-  if (errstr) EARLY_DEBUG(D_any, " mod_open: %s\n", *errstr);
+  if (errstr && *errstr) EARLY_DEBUG(any, " mod_open: %s\n", *errstr);
   return NULL;
   }
 
@@ -474,22 +340,22 @@ mi = (struct misc_module_info *) dlsym(dl,
 				    CS string_sprintf("%s_module_info", name));
 if ((errormsg = dlerror()))
   {
-  EARLY_DEBUG(D_any, "%s does not appear to be a '%s' module (%s)\n",
+  EARLY_DEBUG(any, "%s does not appear to be a '%s' module (%s)\n",
 	  name, name, errormsg);
-  log_write(0, LOG_MAIN|LOG_PANIC,
+  log_write(LOG_MAIN|LOG_PANIC,
     "%s does not contain the expected module info symbol (%s)", name, errormsg);
   dlclose(dl);
   return NULL;
   }
 if (mi->dyn_magic != MISC_MODULE_MAGIC)
   {
-  EARLY_DEBUG(D_any, "Module %s is not compatible with this version of Exim\n", name);
-  log_write(0, LOG_MAIN|LOG_PANIC, "Module %s is not compatible with this version of Exim", name);
+  EARLY_DEBUG(any, "Module %s is not compatible with this version of Exim\n", name);
+  log_write(LOG_MAIN|LOG_PANIC, "Module %s is not compatible with this version of Exim", name);
   dlclose(dl);
   return FALSE;
   }
 
-EARLY_DEBUG(D_lookup, "loaded %q\n", name);
+EARLY_DEBUG(lookup, "Loaded module %q\n", name);
 misc_mod_add(mi);
 return mi;
 }
@@ -521,7 +387,9 @@ if ((mi = misc_mod_findonly(name))) return mi;
 #ifdef LOOKUP_MODULE_DIR
 return misc_mod_load(name, errstr);
 #else
-*errstr = string_sprintf("module '%s' not found", name);
+if (errstr)
+  *errstr = string_sprintf("module %q not built-in, and"
+	  " no setting for LOOKUP_MODULE_DIR", name);
 return NULL;
 #endif	/*LOOKUP_MODULE_DIR*/
 }
@@ -531,7 +399,7 @@ return NULL;
 
 int
 misc_mod_conn_init(const uschar * sender_helo_name,
-  const uschar * sender_host_address, const uschar * * errstr)
+  const uschar * sender_host_address, const uschar ** errstr)
 {
 for (const misc_module_info * mi = misc_module_list; mi; mi = mi->next)
   if (mi->conn_init)
@@ -612,108 +480,17 @@ if (lookup_list_init_done)
   return;
 lookup_list_init_done = TRUE;
 
-#if defined(LOOKUP_CDB) && LOOKUP_CDB!=2
-addlookupmodule(&cdb_lookup_module_info);
-#endif
-
-#if defined(LOOKUP_DBM) && LOOKUP_DBM!=2
-addlookupmodule(&dbmdb_lookup_module_info);
-#endif
-
-#if defined(LOOKUP_DNSDB) && LOOKUP_DNSDB!=2
-addlookupmodule(&dnsdb_lookup_module_info);
-#endif
-
-#if defined(LOOKUP_DSEARCH) && LOOKUP_DSEARCH!=2
-addlookupmodule(&dsearch_lookup_module_info);
-#endif
-
-#if defined(LOOKUP_IBASE) && LOOKUP_IBASE!=2
-addlookupmodule(&ibase_lookup_module_info);
-#endif
-
-#if defined(LOOKUP_LDAP) && LOOKUP_LDAP!=2
-addlookupmodule(&ldap_lookup_module_info);
-#endif
-
-#if defined(LOOKUP_JSON) && LOOKUP_JSON!=2
-addlookupmodule(&json_lookup_module_info);
-#endif
-
-#if defined(LOOKUP_LSEARCH) && LOOKUP_LSEARCH!=2
-addlookupmodule(&lsearch_lookup_module_info);
-#endif
-
-#if defined(LOOKUP_MYSQL) && LOOKUP_MYSQL!=2
-addlookupmodule(&mysql_lookup_module_info);
-#endif
-
-#if defined(LOOKUP_NIS) && LOOKUP_NIS!=2
-addlookupmodule(&nis_lookup_module_info);
-#endif
-
-#if defined(LOOKUP_NISPLUS) && LOOKUP_NISPLUS!=2
-addlookupmodule(&nisplus_lookup_module_info);
-#endif
-
-#if defined(EXPERIMENTAL_NMH) && EXPERIMENTAL_NMH!=2
-addlookupmodule(&nmh_lookup_module_info);
-#endif
-
-#if defined(LOOKUP_ORACLE) && LOOKUP_ORACLE!=2
-addlookupmodule(&oracle_lookup_module_info);
-#endif
-
-#if defined(LOOKUP_PASSWD) && LOOKUP_PASSWD!=2
-addlookupmodule(&passwd_lookup_module_info);
-#endif
-
-#if defined(LOOKUP_PGSQL) && LOOKUP_PGSQL!=2
-addlookupmodule(&pgsql_lookup_module_info);
-#endif
-
-#if defined(LOOKUP_REDIS) && LOOKUP_REDIS!=2
-addlookupmodule(&redis_lookup_module_info);
-#endif
-
-#if defined(LOOKUP_LMDB) && LOOKUP_LMDB!=2
-addlookupmodule(&lmdb_lookup_module_info);
-#endif
-
-#if defined(LOOKUP_SQLITE) && LOOKUP_SQLITE!=2
-addlookupmodule(&sqlite_lookup_module_info);
-#endif
-
-#if defined(LOOKUP_TESTDB) && LOOKUP_TESTDB!=2
-addlookupmodule(&testdb_lookup_module_info);
-#endif
-
-#if defined(LOOKUP_WHOSON) && LOOKUP_WHOSON!=2
-addlookupmodule(&whoson_lookup_module_info);
-#endif
-
-/* This is provided by the spf "misc" module, and the lookup aspect is always
-linked statically whether or not the "misc" module (and hence libspf2) is
-dynamic-load. */
-
-#ifdef EXIM_HAVE_SPF
-addlookupmodule(&spf_lookup_module_info);
-#endif
-
-/* This is a custom expansion, and not available as either
-a list-syntax lookup or a lookup expansion. However, it is
-implemented by a lookup module. */
-
-addlookupmodule(&readsock_lookup_module_info);
+for (lookup_module_info ** avi = avail_static_lookups; *avi; avi++)
+  addlookupmodule(*avi);
 
-DEBUG(D_lookup) debug_printf_indent("Total %d built-in lookups\n", lookup_list_count);
+DEBUG(lookup) debug_printf_indent("Total %d built-in lookups\n", lookup_list_count);
 
 
 #ifdef LOOKUP_MODULE_DIR
-if (!(dd = exim_opendir(CUS LOOKUP_MODULE_DIR)))
+if (!(dd = open_module_dir()))
   {
-  EARLY_DEBUG(D_lookup, "Couldn't open %s: not loading lookup modules\n", LOOKUP_MODULE_DIR);
-  log_write(0, LOG_MAIN|LOG_PANIC,
+  EARLY_DEBUG(lookup, "Couldn't open %s: not loading lookup modules\n", LOOKUP_MODULE_DIR);
+  log_write(LOG_MAIN|LOG_PANIC,
 	  "Couldn't open %s: not loading lookup modules\n", LOOKUP_MODULE_DIR);
   }
 else
@@ -725,27 +502,22 @@ else
   const pcre2_code * regex_islookupmod = regex_must_compile(
     US"(lsearch|ldap|nis)_lookup\\." DYNLIB_FN_EXT "$", MCS_NOFLAGS, TRUE);
 
-  EARLY_DEBUG(D_lookup, "Loading lookup modules from %s\n", LOOKUP_MODULE_DIR);
+  EARLY_DEBUG(lookup, "Loading lookup modules from %s\n", LOOKUP_MODULE_DIR);
   while ((ent = readdir(dd)))
-    {
-    char * name = ent->d_name;
-    int len = (int)strlen(name);
-    if (regex_match_and_setup(regex_islookupmod, US name, 0, 0))
+    if (regex_match_and_setup(regex_islookupmod, US ent->d_name, 0, 0))
       {
       uschar * errstr;
       if (lookup_mod_load(expand_nstring[1], &errstr))
 	countmodules++;
       else
 	{
-	EARLY_DEBUG(D_any, "%s\n", errstr);
-	log_write(0, LOG_MAIN|LOG_PANIC, "%s", errstr);
+	EARLY_DEBUG(any, "%s\n", errstr);
+	log_write(LOG_MAIN|LOG_PANIC, "%s", errstr);
 	}
       }
-    }
-  closedir(dd);
   }
 
-EARLY_DEBUG(D_lookup, "Loaded %d lookup modules\n", countmodules);
+EARLY_DEBUG(lookup, "Loaded %d dynamic lookup modules\n", countmodules);
 #endif
 }
 
@@ -756,7 +528,7 @@ built as static */
 #if !defined(DISABLE_DKIM) && (!defined(SUPPORT_DKIM) || SUPPORT_DKIM!=2)
 extern misc_module_info dkim_module_info;
 #endif
-#if defined(SUPPORT_DMARC) && SUPPORT_DMARC!=2
+#if defined(EXIM_HAVE_DMARC) && EXIM_HAVE_DMARC!=2
 extern misc_module_info dmarc_module_info;
 #endif
 #if defined(EXIM_HAVE_SPF) && EXIM_HAVE_SPF!=2
@@ -765,8 +537,8 @@ extern misc_module_info spf_module_info;
 #if defined(EXPERIMENTAL_ARC) && (!defined(SUPPORT_ARC) || SUPPORT_ARC!=2)
 extern misc_module_info arc_module_info;
 #endif
-#if defined(RADIUS_CONFIG_FILE) && (!defined(SUPPORT_RADIUS) || SUPPORT_RADIUS!=2)
-extern misc_module_info radius_module_info;
+#if defined(SUPPORT_DSCP) && SUPPORT_DSCP!=2
+extern misc_module_info dscp_module_info;
 #endif
 #if defined(SUPPORT_PAM) && SUPPORT_PAM!=2
 extern misc_module_info pam_module_info;
@@ -774,6 +546,19 @@ extern misc_module_info pam_module_info;
 #if defined(EXIM_PERL) && (!defined(SUPPORT_PERL) || SUPPORT_PERL!=2)
 extern misc_module_info perl_module_info;
 #endif
+#if defined(SUPPORT_PROXY) && SUPPORT_PROXY!=2
+extern misc_module_info proxy_module_info;
+#endif
+#if defined(RADIUS_CONFIG_FILE) && (!defined(SUPPORT_RADIUS) || SUPPORT_RADIUS!=2)
+extern misc_module_info radius_module_info;
+#endif
+#if defined(SUPPORT_SOCKS) && SUPPORT_SOCKS!=2
+extern misc_module_info socks_module_info;
+#endif
+#if defined(EXPERIMENTAL_XCLIENT) && EXPERIMENTAL_XCLIENT!=2
+extern misc_module_info xclient_module_info;
+#endif
+
 #if !defined(DISABLE_EXIM_FILTER) && (!defined(SUPPORT_EXIM_FILTER) || SUPPORT_EXIM_FILTER!=2)
 extern misc_module_info exim_filter_module_info;
 #endif
@@ -794,28 +579,71 @@ onetime = TRUE;
 #if defined(EXIM_HAVE_SPF) && EXIM_HAVE_SPF!=2
   misc_mod_add(&spf_module_info);
 #endif
-#if defined(SUPPORT_DMARC) && SUPPORT_DMARC!=2
-/* dmarc depends on spf so this add must go after, for the both-static case */
-  misc_mod_add(&dmarc_module_info);
-#endif
 #if defined(EXPERIMENTAL_ARC) && (!defined(SUPPORT_ARC) || SUPPORT_ARC!=2)
   misc_mod_add(&arc_module_info);
 #endif
-#if defined(RADIUS_CONFIG_FILE) && (!defined(SUPPORT_RADIUS) || SUPPORT_RADIUS!=2)
-  misc_mod_add(&radius_module_info);
+#if defined(EXIM_HAVE_DMARC) && EXIM_HAVE_DMARC!=2
+/* dmarc depends on spf/dkim/arc so this add must go after, for the both-static case */
+  misc_mod_add(&dmarc_module_info);
+#endif
+#if defined(SUPPORT_DSCP) && SUPPORT_DSCP!=2
+  misc_mod_add(&dscp_module_info);
 #endif
 #if defined(SUPPORT_PAM) && SUPPORT_PAM!=2
   misc_mod_add(&pam_module_info);
 #endif
+#if defined(EXPERIMENTAL_XCLIENT) && EXPERIMENTAL_XCLIENT!=2
+  misc_mod_add(&xclient_module_info);
+#endif
 #if defined(EXIM_PERL) && (!defined(SUPPORT_PERL) || SUPPORT_PERL!=2)
   misc_mod_add(&perl_module_info);
 #endif
+#if defined(SUPPORT_PROXY) && SUPPORT_PROXY!=2
+  misc_mod_add(&proxy_module_info);
+#endif
 #if !defined(DISABLE_EXIM_FILTER) && (!defined(SUPPORT_EXIM_FILTER) || SUPPORT_EXIM_FILTER!=2)
   misc_mod_add(&exim_filter_module_info);
 #endif
 #if !defined(DISABLE_SIEVE_FILTER) && (!defined(SUPPORT_SIEVE_FILTER) || SUPPORT_SIEVE_FILTER!=2)
   misc_mod_add(&sieve_filter_module_info);
 #endif
+#if defined(SUPPORT_SOCKS) && SUPPORT_SOCKS!=2
+  misc_mod_add(&socks_module_info);
+#endif
+#if defined(RADIUS_CONFIG_FILE) && (!defined(SUPPORT_RADIUS) || SUPPORT_RADIUS!=2)
+  misc_mod_add(&radius_module_info);
+#endif
+}
+
+
+void
+mod_names(FILE * stream)
+{
+#ifdef LOOKUP_MODULE_DIR
+DIR * dd;
+gstring * list = NULL;
+const pcre2_code * regex_ismodule = regex_must_compile(
+  US"^([a-z0-9]+)_(?:lookup|auth|router|transport|miscmod)\\."
+    DYNLIB_FN_EXT "$",
+  MCS_NOFLAGS, TRUE);
+
+if ((dd = open_module_dir()))
+  {
+  for (struct dirent * ent; ent = readdir(dd); )
+    if (regex_match_and_setup(regex_ismodule, US ent->d_name, 0, 0))
+      list = string_append_listele(list, ' ', expand_nstring[1]);
+
+  if (list)
+    fprintf(stream, "Installed modules: %s\n", string_from_gstring(list));
+  else
+    fprintf(stream, "No modules are installed\n");
+  }
+else
+  fprintf(stream, "Modules directory not readable\n");
+
+#else
+fprintf(stream, "Loadable-module support is not available\n");
+#endif
 }
 
 
diff --git a/src/src/enq.c b/src/src/enq.c
index 842f2d6ba..9dab03ac3 100644
--- a/src/src/enq.c
+++ b/src/src/enq.c
@@ -23,26 +23,26 @@
 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 increasing the count would take us past the given limit
-value return FALSE.  If not, bump it and return TRUE.  If not found, create
+value return FALSE.  If not, bump it and return the new count.  If not found, create
 one with value 1 and return TRUE.
 
 Arguments:
   key            string on which to serialize
   lim            parallelism limit
 
-Returns:         TRUE if OK to proceed; FALSE otherwise
+Returns:         >0 if OK to proceed; 0 otherwise
 */
 
 
-BOOL
-enq_start(uschar *key, unsigned lim)
+unsigned
+enq_start(uschar * key, unsigned lim)
 {
 const dbdata_serialize * serial_record;
 dbdata_serialize new_record;
 open_db dbblock;
-open_db *dbm_file;
+open_db * dbm_file;
 
-DEBUG(D_transport) debug_printf("check serialized: %s\n", key);
+DEBUG(transport) debug_printf("check serialized: %s\n", key);
 
 /* Open and lock the waiting information database. */
 
@@ -53,14 +53,14 @@ if (!(dbm_file = dbfn_open(US"misc", O_RDWR|O_CREAT, &dbblock, TRUE, TRUE)))
 proceed with the connection unless the record is very old. */
 
 serial_record = dbfn_read_enforce_length(dbm_file, key, sizeof(dbdata_serialize));
-if (serial_record && time(NULL) - serial_record->time_stamp < 6*60*60)
+if (serial_record && time(NULL) - serial_record->gen.time_stamp < 6*60*60)
   {
   if (serial_record->count >= lim)
     {
     dbfn_close(dbm_file);
-    DEBUG(D_transport) debug_printf("outstanding serialization record for %s\n",
+    DEBUG(transport) debug_printf("outstanding serialization record for %s\n",
       key);
-    return FALSE;
+    return 0;
     }
   new_record.count = serial_record->count + 1;
   }
@@ -69,11 +69,11 @@ else
 
 /* We can proceed - insert a new record or update the old one. */
 
-DEBUG(D_transport) debug_printf("write serialization record for %s val %d\n",
+DEBUG(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;
+return new_record.count;
 }
 
 
@@ -98,7 +98,7 @@ open_db dbblock;
 open_db *dbm_file;
 dbdata_serialize *serial_record;
 
-DEBUG(D_transport) debug_printf("end serialized: %s\n", key);
+DEBUG(transport) debug_printf("end serialized: %s\n", key);
 
 if (  !(dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE, TRUE))
    || !(serial_record = dbfn_read_enforce_length(dbm_file, key, sizeof(dbdata_serialize)))
@@ -106,13 +106,13 @@ if (  !(dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE, TRUE))
   return;
 if (--serial_record->count > 0)
   {
-  DEBUG(D_transport) debug_printf("write serialization record for %s val %d\n",
+  DEBUG(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);
+  DEBUG(transport) debug_printf("remove serialization record for %s\n", key);
   dbfn_delete(dbm_file, key);
   }
 dbfn_close(dbm_file);
diff --git a/src/src/environment.c b/src/src/environment.c
index 9cbd352ec..d768dee43 100644
--- a/src/src/environment.c
+++ b/src/src/environment.c
@@ -42,9 +42,9 @@ if (!keep_environment || !*keep_environment)
 else if (Ustrcmp(keep_environment, "*") != 0)
   {
   rmark reset_point = store_mark();
-  unsigned deb = debug_selector;
-  BOOL hc = host_checking;
-  debug_selector = 0;			/* quieten this clearout */
+  BOOL hc = host_checking, dbg;
+
+  dbg = debug_disable();		/* quieten this clearout */
   host_checking = FALSE;
 
   if (environ) for (uschar ** p = USS environ; *p; /* see below */)
@@ -64,14 +64,18 @@ else if (Ustrcmp(keep_environment, "*") != 0)
       else if (os_unsetenv(name) == 0)
 	p = USS environ;	/* RESTART from the beginning */
       else
-	{ debug_selector = deb; host_checking = hc; return FALSE; }
+	{
+	host_checking = hc;
+	if (dbg) debug_enable();
+	return FALSE;
+	}
       }
     }
-  debug_selector = deb;
   host_checking = hc;
+  if (dbg) debug_enable();
   store_reset(reset_point);
   }
-DEBUG(D_expand)
+DEBUG(expand)
   {
   debug_printf("environment after trimming:\n");
   if (environ) for (uschar ** p = USS environ; *p; p++)
@@ -86,7 +90,7 @@ if (add_environment)
 
   for (const uschar * p; p = string_nextinlist(&envlist, &sep, NULL, 0); )
     {
-    DEBUG(D_expand) debug_printf("adding %s\n", p);
+    DEBUG(expand) debug_printf("adding %s\n", p);
     putenv(CS p);
     }
   store_pool = old_pool;
@@ -97,3 +101,6 @@ tls_clean_env();
 
 return TRUE;
 }
+
+/* vi: aw ai sw=2
+*/
diff --git a/src/src/exim.c b/src/src/exim.c
index b1d77576f..627b0d646 100644
--- a/src/src/exim.c
+++ b/src/src/exim.c
@@ -51,7 +51,7 @@ static void *
 function_store_malloc(PCRE2_SIZE size, void * tag)
 {
 if (size > INT_MAX)
-  log_write_die(0, LOG_MAIN, "excessive memory alloc request");
+  log_write_die(LOG_MAIN, "excessive memory alloc request");
 return store_malloc((int)size);
 }
 
@@ -67,7 +67,7 @@ static void *
 function_store_get(PCRE2_SIZE size, void * tag)
 {
 if (size > INT_MAX)
-  log_write_die(0, LOG_MAIN, "excessive memory alloc request");
+  log_write_die(LOG_MAIN, "excessive memory alloc request");
 return store_get((int)size, GET_UNTAINTED);	/* loses track of taint */
 }
 
@@ -85,7 +85,14 @@ function_store_nullfree(void * block, void * tag)
 *************************************************/
 
 enum commandline_info { CMDINFO_NONE=0,
-  CMDINFO_HELP, CMDINFO_SIEVE, CMDINFO_DSCP };
+  CMDINFO_HELP, CMDINFO_MODULES,
+#ifndef DISABLE_SIEVE_FILTER
+  CMDINFO_SIEVE,
+#endif
+#ifdef SUPPORT_DSCP
+  CMDINFO_DSCP
+#endif
+};
 
 
 
@@ -153,7 +160,7 @@ if ((yield = (res >= 0)))
     }
   expand_nmax--;
   }
-else if (res != PCRE2_ERROR_NOMATCH) DEBUG(D_any)
+else if (res != PCRE2_ERROR_NOMATCH) DEBUG(any)
   {
   uschar errbuf[128];
   pcre2_get_error_message(res, errbuf, sizeof(errbuf));
@@ -217,7 +224,7 @@ int len;
 uschar * s;
 va_list ap;
 
-g = string_fmt_append(&gs, "%5d ", (int)getpid());
+g = string_fmt_append(&gs, PID_T_FMT " ", getpid());
 len = gstring_length(g);
 va_start(ap, format);
 if (!string_vformat(g, 0, format, ap))
@@ -227,7 +234,7 @@ if (!string_vformat(g, 0, format, ap))
   }
 g = string_catn(g, US"\n", 1);
 process_info_len = len_string_from_gstring(g, &s);
-DEBUG(D_process_info) debug_printf("set_process_info: %s", process_info);
+DEBUG(process_info) debug_printf("set_process_info: %s", process_info);
 va_end(ap);
 }
 
@@ -255,8 +262,8 @@ void * buf[STACKDUMP_MAX];
 char ** ss;
 int nptrs = backtrace(buf, STACKDUMP_MAX);
 
-log_write(0, LOG_MAIN|LOG_PANIC, "backtrace");
-log_write(0, LOG_MAIN|LOG_PANIC, "---");
+log_write(LOG_MAIN|LOG_PANIC, "backtrace");
+log_write(LOG_MAIN|LOG_PANIC, "---");
 
 /* This function is officially not callable from a signal handler, as it
 calls malloc() for the returned data. However, it seems to work - and we
@@ -266,12 +273,12 @@ A alternative might be backtrace_symbols_fd(). */
 if ((ss = backtrace_symbols(buf, nptrs)))
   {
   for (int i = 0; i < nptrs; i++)
-    log_write(0, LOG_MAIN|LOG_PANIC, "\t%s", ss[i]);
+    log_write(LOG_MAIN|LOG_PANIC, "\t%s", ss[i]);
   free(ss);
   }
 else
-  log_write(0, LOG_MAIN|LOG_PANIC, "backtrace_symbols: %s", strerror(errno));
-log_write(0, LOG_MAIN|LOG_PANIC, "---");
+  log_write(LOG_MAIN|LOG_PANIC, "backtrace_symbols: %s", strerror(errno));
+log_write(LOG_MAIN|LOG_PANIC, "---");
 #endif
 }
 #undef STACKDUMP_MAX
@@ -283,25 +290,25 @@ segv_handler(int sig, siginfo_t * info, void * uctx)
 {
 if (!panic_coredump)
   {
-  log_write(0, LOG_MAIN|LOG_PANIC, "SIGSEGV (fault address: %p)", info->si_addr);
+  log_write(LOG_MAIN|LOG_PANIC, "SIGSEGV (fault address: %p)", info->si_addr);
   # if defined(SEGV_MAPERR) && defined(SEGV_ACCERR) && defined(SEGV_BNDERR) && defined(SEGV_PKUERR)
   switch (info->si_code)
     {
-    case SEGV_MAPERR: log_write(0, LOG_MAIN|LOG_PANIC, "SEGV_MAPERR"); break;
-    case SEGV_ACCERR: log_write(0, LOG_MAIN|LOG_PANIC, "SEGV_ACCERR"); break;
-    case SEGV_BNDERR: log_write(0, LOG_MAIN|LOG_PANIC, "SEGV_BNDERR"); break;
-    case SEGV_PKUERR: log_write(0, LOG_MAIN|LOG_PANIC, "SEGV_PKUERR"); break;
+    case SEGV_MAPERR: log_write(LOG_MAIN|LOG_PANIC, "SEGV_MAPERR"); break;
+    case SEGV_ACCERR: log_write(LOG_MAIN|LOG_PANIC, "SEGV_ACCERR"); break;
+    case SEGV_BNDERR: log_write(LOG_MAIN|LOG_PANIC, "SEGV_BNDERR"); break;
+    case SEGV_PKUERR: log_write(LOG_MAIN|LOG_PANIC, "SEGV_PKUERR"); break;
     }
   # endif
   }
 if (panic_coredump)
-  log_write(0, LOG_MAIN|LOG_PANIC, "SIGSEGV (deliberate trap)");
+  log_write(LOG_MAIN|LOG_PANIC, "SIGSEGV (deliberate trap)");
 else if (US info->si_addr < US 4096)
-  log_write(0, LOG_MAIN|LOG_PANIC, "SIGSEGV (null pointer indirection)");
+  log_write(LOG_MAIN|LOG_PANIC, "SIGSEGV (null pointer indirection)");
 else
-  log_write(0, LOG_MAIN|LOG_PANIC, "SIGSEGV (maybe attempt to write to immutable memory)");
+  log_write(LOG_MAIN|LOG_PANIC, "SIGSEGV (maybe attempt to write to immutable memory)");
 if (process_info_len > 0)
-  log_write(0, LOG_MAIN|LOG_PANIC, "SIGSEGV (%s: %.*s)",
+  log_write(LOG_MAIN|LOG_PANIC, "SIGSEGV (%s: %.*s)",
     process_purpose, process_info_len, process_info);
 stackdump();
 signal(SIGSEGV, SIG_DFL);
@@ -311,9 +318,9 @@ kill(getpid(), sig);
 #else
 segv_handler(int sig)
 {
-log_write(0, LOG_MAIN|LOG_PANIC, "SIGSEGV (maybe attempt to write to immutable memory)");
+log_write(LOG_MAIN|LOG_PANIC, "SIGSEGV (maybe attempt to write to immutable memory)");
 if (process_info_len > 0)
-  log_write(0, LOG_MAIN|LOG_PANIC, "SIGSEGV (%.*s)", process_info_len, process_info);
+  log_write(LOG_MAIN|LOG_PANIC, "SIGSEGV (%.*s)", process_info_len, process_info);
 stackdump();
 signal(SIGSEGV, SIG_DFL);
 kill(getpid(), sig);
@@ -405,19 +412,19 @@ Returns:   nothing
 */
 
 static void
-milliwait(struct itimerval *itval)
+milliwait(struct itimerval * itval)
 {
 sigset_t sigmask;
 sigset_t old_sigmask;
 int save_errno = errno;
 
-if (itval->it_value.tv_usec < 50 && itval->it_value.tv_sec == 0)
+if (itval->it_value.tv_usec < 50 && 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 */
 if (setitimer(ITIMER_REAL, itval, NULL) < 0)           /* Start timer */
-  log_write_die(0, LOG_MAIN,
+  log_write_die(LOG_MAIN,
     "setitimer() failed: %s", strerror(errno));
 (void)sigfillset(&sigmask);                            /* All signals */
 (void)sigdelset(&sigmask, SIGALRM);                    /* Remove SIGALRM */
@@ -580,7 +587,7 @@ while (exim_tvcmp(&now_tv, prev_tv) <= 0)
     itval.it_value.tv_sec -= 1;
     }
 
-  DEBUG(D_transport|D_receive)
+  DEBUG(transport|receive)
     {
     if (!f.running_in_test_harness)
       {
@@ -665,7 +672,7 @@ for (int i = 0; i <= 2; i++)
   if (fstat(i, &statbuf) < 0 && errno == EBADF)
     {
     if (devnull < 0) devnull = open("/dev/null", O_RDWR);
-    if (devnull < 0) log_write_die(0, LOG_MAIN, "%s",
+    if (devnull < 0) log_write_die(LOG_MAIN, "%s",
       string_open_failed("/dev/null", NULL));
     if (devnull != i) (void)dup2(devnull, i);
     }
@@ -726,9 +733,12 @@ if (smtp_input)
   }
 else
   {
-  (void)close(0);                                          /* stdin */
-  if ((debug_selector & D_resolver) == 0) (void)close(1);  /* stdout */
-  if (debug_selector == 0)                                 /* stderr */
+  (void)close(0);					/* stdin */
+  DEBUG(resolver)
+    ;
+  else
+    (void)close(1);					/* stdout */
+  if (!ANY_DEBUG)					/* stderr */
     {
     if (!f.synchronous_delivery)
       {
@@ -777,34 +787,35 @@ if (euid == root_uid || euid != uid || egid != gid || igflag)
     {
     struct passwd * pw = getpwuid(uid);
     if (!pw)
-      log_write_die(0, LOG_MAIN, "cannot run initgroups(): "
+      log_write_die(LOG_MAIN, "cannot run initgroups(): "
 	"no passwd entry for uid=%ld", (long int)uid);
 
     if (initgroups(pw->pw_name, gid) != 0)
-      log_write_die(0,LOG_MAIN,"initgroups failed for uid=%ld: %s",
+      log_write_die(LOG_MAIN,"initgroups failed for uid=%ld: %s",
 	(long int)uid, strerror(errno));
     }
 
   if (setgid(gid) < 0 || setuid(uid) < 0)
-    log_write_die(0, LOG_MAIN, "unable to set gid=%ld or uid=%ld "
+    log_write_die(LOG_MAIN, "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 */
 
-DEBUG(D_uid)
+DEBUG(uid)
   {
   int group_count, save_errno;
   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->%ld gid %ld->%ld pid=" PID_T_FMT "\n",
+    msg, (long)euid, (long)geteuid(),
+    (long)egid, (long)getegid(), getpid());
   group_count = getgroups(nelem(group_list), group_list);
   save_errno = errno;
   debug_printf("  auxiliary group list:");
   if (group_count > 0)
     for (int i = 0; i < group_count; i++) debug_printf(" %d", (int)group_list[i]);
-  else if (group_count < 0)
-    debug_printf(" <error: %s>", strerror(save_errno));
+  else if (group_count < 0) debug_printf(" <error: %s>", strerror(save_errno));
   else debug_printf(" <none>");
   debug_printf("\n");
   }
@@ -832,10 +843,10 @@ exim_exit(int rc)
 smtp_fflush(SFF_NO_UNCORK);
 search_tidyup();
 store_exit();
-DEBUG(D_any)
-  debug_printf(">>>>>>>>>>>>>>>> Exim pid=%d (%s) terminating with rc=%d "
+DEBUG(any)
+  debug_printf(">>>>>>>>>>>>>>>> Exim pid=" PID_T_FMT " (%s) terminating with rc=%d "
     ">>>>>>>>>>>>>>>>\n",
-    (int)getpid(), process_purpose, rc);
+    getpid(), process_purpose, rc);
 exit(rc);
 }
 
@@ -844,10 +855,10 @@ void
 exim_underbar_exit(int rc)
 {
 store_exit();
-DEBUG(D_any)
-  debug_printf(">>>>>>>>>>>>>>>> Exim pid=%d (%s) terminating with rc=%d "
+DEBUG(any)
+  debug_printf(">>>>>>>>>>>>>>>> Exim pid=" PID_T_FMT " (%s) terminating with rc=%d "
     ">>>>>>>>>>>>>>>>\n",
-    (int)getpid(), process_purpose, rc);
+    getpid(), process_purpose, rc);
 _exit(rc);
 }
 
@@ -914,7 +925,7 @@ exim_chown_failure(int fd, const uschar *name, uid_t owner, gid_t group)
 {
 int saved_errno = errno;  /* from the preceeding chown call */
 #if 1
-log_write(0, LOG_MAIN|LOG_PANIC,
+log_write(LOG_MAIN|LOG_PANIC,
   __FILE__ ":%d: chown(%s, %d:%d) failed (%s)."
   " Please contact the authors and refer to https://bugs.exim.org/show_bug.cgi?id=2391",
   __LINE__, name?name:US"<unknown>", owner, group, strerror(errno));
@@ -929,9 +940,9 @@ struct stat buf;
 if (0 == (fd < 0 ? stat(name, &buf) : fstat(fd, &buf)))
   {
   if (buf.st_uid == owner && buf.st_gid == group) return 0;
-  log_write(0, LOG_MAIN|LOG_PANIC, "Wrong ownership on %s", name);
+  log_write(LOG_MAIN|LOG_PANIC, "Wrong ownership on %s", name);
   }
-else log_write(0, LOG_MAIN|LOG_PANIC, "Stat failed on %s: %s", name, strerror(errno));
+else log_write(LOG_MAIN|LOG_PANIC, "Stat failed on %s: %s", name, strerror(errno));
 
 #endif
 errno = saved_errno;
@@ -988,13 +999,13 @@ has the effect of collapsing source routes.
 Arguments:
   s            the address string
   flags        flag bits for verify_address()
-  exit_value   to be set for failures
+  ret	       exit value so far
 
-Returns:       nothing
+Returns:       updated exit value
 */
 
-static void
-test_address(const uschar * s, int flags, int * exit_value)
+static int
+test_address(const uschar * s, int flags, int ret)
 {
 int start, end, domain;
 uschar * parse_error = NULL;
@@ -1003,16 +1014,17 @@ const uschar * address = parse_extract_address(s, &parse_error,
 if (!address)
   {
   fprintf(stdout, "syntax error: %s\n", parse_error);
-  *exit_value = 2;
+  ret = 2;
   }
 else
   {
   /* NB we lose stdio buffering here */
   int rc = verify_address(deliver_make_addr(address,TRUE), fileno(stdout),
     flags, -1, -1, -1, NULL, NULL, NULL);
-  if (rc == FAIL) *exit_value = 2;
-  else if (rc == DEFER && *exit_value == 0) *exit_value = 1;
+  if (rc == FAIL) ret = 2;
+  else if (rc == DEFER && ret == EXIT_SUCCESS) ret = EXIT_FAILURE;
   }
+return ret;
 }
 
 
@@ -1036,7 +1048,7 @@ show_db_version(gstring * g)
 {
 g = string_cat(g, US"Hints DB:\n");
 #ifdef DB_VERSION_STRING
-DEBUG(D_any)
+DEBUG(any)
   {
   g = string_fmt_append(g, " Library version: BDB: Compile: %s\n", DB_VERSION_STRING);
   g = string_fmt_append(g, "                       Runtime: %s\n",
@@ -1074,153 +1086,12 @@ lookup_show_supported(gstring * g)
 {
 gstring * b = NULL, * d = NULL;
 
-#ifdef LOOKUP_LSEARCH
-# if LOOKUP_LSEARCH!=2
-  b = string_cat(b, US" lsearch wildlsearch nwildlsearch iplsearch");
-# else
-  d = string_cat(d, US" lsearch wildlsearch nwildlsearch iplsearch");
-# endif
-#endif
-#ifdef LOOKUP_CDB
-# if LOOKUP_CDB!=2
-  b = string_cat(b, US" cdb");
-# else
-  d = string_cat(d, US" cdb");
-# endif
-#endif
-#ifdef LOOKUP_DBM
-# if LOOKUP_DBM!=2
-  b = string_cat(b, US" dbm dbmjz dbmnz");
-# else
-  d = string_cat(d, US" dbm dbmjz dbmnz");
-# endif
-#endif
-#ifdef LOOKUP_DNSDB
-# if LOOKUP_DNSDB!=2
-  b = string_cat(b, US" dnsdb");
-# else
-  d = string_cat(d, US" dnsdb");
-# endif
-#endif
-#ifdef LOOKUP_DSEARCH
-# if LOOKUP_DSEARCH!=2
-  b = string_cat(b, US" dsearch");
-# else
-  d = string_cat(d, US" dsearch");
-# endif
-#endif
-#ifdef LOOKUP_IBASE
-# if LOOKUP_IBASE!=2
-  b = string_cat(b, US" ibase");
-# else
-  d = string_cat(d, US" ibase");
-# endif
-#endif
-#ifdef LOOKUP_JSON
-# if LOOKUP_JSON!=2
-  b = string_cat(b, US" json");
-# else
-  d = string_cat(d, US" json");
-# endif
-#endif
-#ifdef LOOKUP_LDAP
-# if LOOKUP_LDAP!=2
-  b = string_cat(b, US" ldap ldapdn ldapm");
-# else
-  d = string_cat(d, US" ldap ldapdn ldapm");
-# endif
-#endif
-#ifdef LOOKUP_LMDB
-# if LOOKUP_LMDB!=2
-  b = string_cat(b, US" lmdb");
-# else
-  d = string_cat(d, US" lmdb");
-# endif
-#endif
-#ifdef LOOKUP_MYSQL
-# if LOOKUP_MYSQL!=2
-  b = string_cat(b, US" mysql");
-# else
-  d = string_cat(d, US" mysql");
-# endif
-#endif
-#ifdef LOOKUP_NIS
-# if LOOKUP_NIS!=2
-  b = string_cat(b, US" nis nis0");
-# else
-  d = string_cat(d, US" nis nis0");
-# endif
-#endif
-#ifdef LOOKUP_NISPLUS
-# if LOOKUP_NISPLUS!=2
-  b = string_cat(b, US" nisplus");
-# else
-  d = string_cat(d, US" nisplus");
-# endif
-#endif
-#ifdef EXPERIMENTAL_NMH
-# if EXPERIMENTAL_NMH!=2
-  b = string_cat(b, US" nmh");
-# else
-  d = string_cat(d, US" nmh");
-# endif
-#endif
-#ifdef LOOKUP_ORACLE
-# if LOOKUP_ORACLE!=2
-  b = string_cat(b, US" oracle");
-# else
-  d = string_cat(d, US" oracle");
-# endif
-#endif
-#ifdef LOOKUP_PASSWD
-# if LOOKUP_PASSWD!=2
-  b = string_cat(b, US" passwd");
-# else
-  d = string_cat(d, US" passwd");
-# endif
-#endif
-#ifdef LOOKUP_PGSQL
-# if LOOKUP_PGSQL!=2
-  b = string_cat(b, US" pgsql");
-# else
-  d = string_cat(d, US" pgsql");
-# endif
-#endif
-#ifdef LOOKUP_REDIS
-# if LOOKUP_REDIS!=2
-  b = string_cat(b, US" redis");
-# else
-  d = string_cat(d, US" redis");
-# endif
-#endif
-#ifdef EXIM_HAVE_SPF
-# if EXIM_HAVE_SPF !=2
-  b = string_cat(b, US" spf");
-# else
-  d = string_cat(d, US" spf");
-# endif
-#endif
-#ifdef LOOKUP_SQLITE
-# if LOOKUP_SQLITE!=2
-  b = string_cat(b, US" sqlite");
-# else
-  d = string_cat(d, US" sqlite");
-# endif
-#endif
-#ifdef LOOKUP_TESTDB
-# if LOOKUP_TESTDB!=2
-  b = string_cat(b, US" testdb");
-# else
-  d = string_cat(d, US" testdb");
-# endif
-#endif
-#ifdef LOOKUP_WHOSON
-# if LOOKUP_WHOSON!=2
-  b = string_cat(b, US" whoson");
-# else
-  d = string_cat(d, US" whoson");
-# endif
-#endif
+for (lookup_module_info ** lmip = avail_static_lookups; *lmip; lmip++)
+  for (lookup_info ** lip = (*lmip)->lookups;
+      lip < (*lmip)->lookups + (*lmip)->lookupcount; lip++)
+    b = string_fmt_append(b, " %s", (*lip)->name);
+
+d = lookup_dynamic_supported(d);
 
 if (b) g = string_fmt_append(g, "Lookups (built-in):%Y\n", b);
 if (d) g = string_fmt_append(g, "Lookups (dynamic): %Y\n", d);
@@ -1260,12 +1131,15 @@ the main pool. */
 store_pool = POOL_MAIN;
 reset_point = store_mark();
 
-DEBUG(D_any) {} else g = show_db_version(g);
+DEBUG(any) {} else g = show_db_version(g);
 
 g = string_cat(g, US"Support for:");
 #ifdef WITH_CONTENT_SCAN
   g = string_cat(g, US" Content_Scanning");
 #endif
+#ifdef HAVE_LOCAL_SCAN
+  g = string_cat(g, US" Local_Scan");
+#endif
 #ifndef DISABLE_EXIM_FILTER
   g = string_cat(g, US" Exim_filter");
 #endif
@@ -1299,9 +1173,6 @@ g = string_cat(g, US"Support for:");
 #ifdef USE_OPENSSL
   g = string_cat(g, US" OpenSSL");
 #endif
-#if defined(CYRUS_PWCHECK_SOCKET)
-  g = string_cat(g, US" pwcheck");
-#endif
 #if defined(RADIUS_CONFIG_FILE)
   g = string_cat(g, US" radius");
 #endif
@@ -1320,12 +1191,15 @@ g = string_cat(g, US"Support for:");
 #ifndef DISABLE_DKIM
   g = string_cat(g, US" DKIM");
 #endif
-#ifdef SUPPORT_DMARC
+#ifdef EXIM_HAVE_DMARC
   g = string_cat(g, US" DMARC");
 #endif
 #ifndef DISABLE_DNSSEC
   g = string_cat(g, US" DNSSEC");
 #endif
+#ifdef SUPPORT_DSCP
+  g = string_cat(g, US" DSCP");
+#endif
 #ifndef DISABLE_ESMTP_LIMITS
   g = string_cat(g, US" ESMTP_Limits");
 #endif
@@ -1369,9 +1243,6 @@ g = string_cat(g, US"Support for:");
 #ifdef EXPERIMENTAL_ARC
   g = string_cat(g, US" Experimental_ARC");
 #endif
-#ifdef EXPERIMENTAL_BRIGHTMAIL
-  g = string_cat(g, US" Experimental_Brightmail");
-#endif
 #ifdef EXPERIMENTAL_DCC
   g = string_cat(g, US" Experimental_DCC");
 #endif
@@ -1397,7 +1268,8 @@ g = transport_show_supported(g);
 #ifdef WITH_CONTENT_SCAN
 g = malware_show_supported(g);
 #endif
-show_string(is_stdout, g); g = NULL;
+show_string(is_stdout, g);
+gstring_reset(g);
 
 if (fixed_never_users[0] > 0)
   {
@@ -1410,11 +1282,14 @@ if (fixed_never_users[0] > 0)
 
 g = string_fmt_append(g, "Configure owner: %d:%d\n", config_uid, config_gid);
 
-g = string_fmt_append(g, "Size of off_t: " SIZE_T_FMT "\n", sizeof(off_t));
+g = string_fmt_append(g, "Size of off_t:" SIZE_T_FMT
+      " ptr:" SIZE_T_FMT " long:" SIZE_T_FMT " int:" SIZE_T_FMT "\n",
+      sizeof(off_t), sizeof(void *), sizeof(long), sizeof(int));
 
 /* Everything else is details which are only worth reporting when debugging.
 Perhaps the tls_version_report should move into this too. */
-DEBUG(D_any)
+
+DEBUG(any)
   {
 
 /* clang defines __GNUC__ (at least, for me) so test for it first */
@@ -1429,6 +1304,7 @@ DEBUG(D_any)
 #else
   g = string_cat(g, US"Compiler: <unknown>\n");
 #endif
+/*XXX Sun Studio compiler? */
 
 #if defined(__GLIBC__) && !defined(__UCLIBC__)
   g = string_fmt_append(g, "Library version: Glibc: Compile: %d.%d\n",
@@ -1451,7 +1327,7 @@ DEBUG(D_any)
 Currently they are output in misc_mod_add() */
 
   show_string(is_stdout, g);
-  g = NULL;
+  gstring_reset(g);
 
   for (auth_info * ai = auths_available; ai; ai = (auth_info *)ai->drinfo.next)
     if (ai->version_report)
@@ -1463,28 +1339,26 @@ Currently they are output in misc_mod_add() */
 #ifndef PCRE_PRERELEASE
 # define PCRE_PRERELEASE
 #endif
-#define QUOTE(X) #X
-#define EXPAND_AND_QUOTE(X) QUOTE(X)
     {
     uschar buf[24];
     pcre2_config(PCRE2_CONFIG_VERSION, buf);
     g = string_fmt_append(g, "Library version: PCRE2: Compile: %d.%d%s\n"
 		"                        Runtime: %s\n",
 	    PCRE2_MAJOR, PCRE2_MINOR,
-	    EXPAND_AND_QUOTE(PCRE2_PRERELEASE) "",
+	    mac_expanded_string(PCRE2_PRERELEASE) "",
 	    buf);
     }
-#undef QUOTE
-#undef EXPAND_AND_QUOTE
 
   show_string(is_stdout, g);
-  g = NULL;
+  gstring_reset(g);
+
+  /* Has to be before the lookups as the spf lookup calls into the spf module */
+  init_misc_mod_list();
 
   init_lookup_list();
   tree_walk(lookups_tree, lookup_version_report_cb, &g);
   show_string(is_stdout, g);
-  g = NULL;
-  init_misc_mod_list();
+  gstring_reset(g);
 
 #ifdef WHITELIST_D_MACROS
   g = string_fmt_append(g, "WHITELIST_D_MACROS: %q\n", WHITELIST_D_MACROS);
@@ -1495,6 +1369,10 @@ Currently they are output in misc_mod_add() */
   g = string_fmt_append(g, "TRUSTED_CONFIG_LIST: %q\n", TRUSTED_CONFIG_LIST);
 #else
   g = string_cat(g, US"TRUSTED_CONFIG_LIST unset\n");
+#endif
+#ifdef HAVE_LOCAL_SCAN
+  g = string_cat(g, US"Local-Scan API: "
+		    mac_expanded_string(LOCAL_SCAN_ABI_VERSION) "\n");
 #endif
   }
 
@@ -1522,23 +1400,39 @@ switch(request)
 "If the string is not recognised, you'll get this help (on stderr).\n"
 "\n"
 "  exim -bI:help    this information\n"
+#ifdef SUPPORT_DSCP
 "  exim -bI:dscp    list of known dscp value keywords\n"
+#endif
+"  exim -bI:modules list of loadable modules\n"
+#ifndef DISABLE_SIEVE_FILTER
 "  exim -bI:sieve   list of supported sieve extensions\n"
+#endif
 );
     return;
+  case CMDINFO_MODULES:
+    mod_names(stream);
+    return;
+#ifndef DISABLE_SIEVE_FILTER
   case CMDINFO_SIEVE:
     {
-    const misc_module_info * mi;
+    misc_module_info * mi = misc_mod_find(US"sieve_filter", NULL);
     typedef void (*fn_t)(FILE *);
-    if ((mi = misc_mod_find(US"sieve_filter", NULL)))
+    if (mi)
       (((fn_t *) mi->functions)[SIEVE_EXTENSIONS]) (stream);
     else
       fprintf(stream, "Sieve filtering not available\n");
     }
     return;
+#endif
+#ifdef SUPPORT_DSCP
   case CMDINFO_DSCP:
-    dscp_list_to_stream(stream);
+    {
+    misc_module_info * mi = misc_mod_find(US"dscp", NULL);
+    typedef void (*fn_t)(FILE *);
+    if (mi) ((fn_t *) mi->functions)[DSCP_KEYWORDS] (stream);
+    }
     return;
+#endif
   }
 }
 
@@ -1627,7 +1521,7 @@ if (dlhandle)
   *fn_addhist_ptr = (void(*)(const char*))dlsym(dlhandle, "add_history");
   }
 else
-  DEBUG(D_any) debug_printf("failed to load readline: %s\n", dlerror());
+  DEBUG(any) debug_printf("failed to load readline: %s\n", dlerror());
 
 return dlhandle;
 }
@@ -1842,7 +1736,8 @@ for (macro_item * m = macros_user; m; m = m->next) if (m->command_line)
   if (!regex_match(regex_whitelisted_macro, m->replacement, len, NULL))
     return FALSE;
   }
-DEBUG(D_any) debug_printf("macros_trusted overridden to true by whitelisting\n");
+DEBUG(start)
+  debug_printf("macros_trusted overridden to true by whitelisting\n");
 return TRUE;
 #endif
 }
@@ -1870,10 +1765,6 @@ len = Ustrlen(big_buffer);
 
 (void) macros_expand(0, &len, &macro_found);
 
-#ifdef LOOKUP_MODULE_DIR
-//mod_load_check(big_buffer);
-#endif
-
 if (isupper(big_buffer[0]) && !(macro_found & MACRO_FIRST))
   {
   if (macro_read_assignment(big_buffer))
@@ -1918,6 +1809,20 @@ return qrunners;
 }
 
 
+static void
+set_debug_stream(void)
+{
+debug_file = stderr;
+debug_fd = fileno(debug_file);
+}
+
+static void
+set_verb_opt_mode(void)
+{
+debug_modify_channel(US"+v");
+set_debug_stream();
+}
+
 /*************************************************
 *          Entry point and high-level code       *
 *************************************************/
@@ -1941,71 +1846,37 @@ int
 main(int argc, char ** cargv)
 {
 const uschar ** argv = CUSS cargv;
-int  arg_receive_timeout = -1;
-int  arg_smtp_receive_timeout = -1;
-int  arg_error_handling = error_handling;
-int  filter_sfd = -1;
-int  filter_ufd = -1;
-int  group_count;
-int  i, rv;
-int  list_queue_option = QL_BASIC;
-int  msg_action = 0;
-int  msg_action_arg = -1;
-int  namelen = argv[0] ? Ustrlen(argv[0]) : 0;
-int  queue_only_reason = 0;
+int  arg_receive_timeout = -1, arg_smtp_receive_timeout = -1,
+	arg_error_handling = error_handling, filter_sfd = -1, filter_ufd = -1,
+	group_count, i, rv, list_queue_option = QL_BASIC, msg_action = 0,
+	msg_action_arg = -1, namelen = argv[0] ? Ustrlen(argv[0]) : 0,
+	queue_only_reason = 0, rcpt_dsn_flags = 0, recipients_arg = argc,
+	sender_address_domain = 0, test_retry_arg = -1, test_rewrite_arg = -1;
 #ifdef EXIM_PERL
 int  perl_start_option = 0;
 #endif
-int  recipients_arg = argc;
-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 count_queue = FALSE;
-BOOL expansion_test = FALSE;
-BOOL extract_recipients = FALSE;
-BOOL flag_G = FALSE;
-BOOL flag_n = FALSE;
-BOOL forced_delivery = FALSE;
-BOOL f_end_dot = FALSE;
-BOOL deliver_give_up = FALSE;
-BOOL list_queue = FALSE;
-BOOL list_options = FALSE;
-BOOL list_config = FALSE;
-BOOL local_queue_only;
-BOOL one_msg_action = FALSE;
-BOOL opt_D_used = FALSE;
-BOOL queue_only_set = FALSE;
-BOOL receiving_message = TRUE;
-BOOL sender_ident_set = FALSE;
-BOOL session_local_queue_only;
-BOOL unprivileged;
-BOOL removed_privilege = FALSE;
-BOOL usage_wanted = FALSE;
-BOOL verify_address_mode = FALSE;
-BOOL verify_as_sender = FALSE;
-BOOL rcpt_verify_quota = FALSE;
-BOOL version_printed = FALSE;
-const uschar * alias_arg = NULL;
-const uschar * called_as = US"";
-const uschar * cmdline_syslog_name = NULL;
-const uschar * start_queue_run_id = NULL;
-const uschar * stop_queue_run_id = NULL;
-const uschar * expansion_test_message = NULL;
-const uschar * ftest_domain = NULL;
-const uschar * ftest_localpart = NULL;
-const uschar * ftest_prefix = NULL;
-const uschar * ftest_suffix = NULL;
+BOOL arg_queue_only = FALSE, bi_option = FALSE, checking = FALSE,
+	count_queue = FALSE, extract_recipients = FALSE, flag_G = FALSE,
+	flag_n = FALSE, forced_delivery = FALSE, f_end_dot = FALSE,
+	deliver_give_up = FALSE, list_queue = FALSE, list_options = FALSE,
+	list_config = FALSE, local_queue_only, one_msg_action = FALSE,
+	opt_D_used = FALSE, queue_only_set = FALSE, receiving_message = TRUE,
+	sender_ident_set = FALSE, session_local_queue_only, unprivileged,
+	removed_privilege = FALSE, usage_wanted = FALSE,
+	verify_address_mode = FALSE, verify_as_sender = FALSE,
+	rcpt_verify_quota = FALSE, version_printed = FALSE;
+const uschar * alias_arg = NULL, * called_as = US"",
+	* cmdline_syslog_name = NULL, * start_queue_run_id = NULL,
+	* stop_queue_run_id = NULL, * expansion_test_message = NULL,
+	* ftest_domain = NULL, * ftest_localpart = NULL,
+	* ftest_prefix = NULL, * ftest_suffix = NULL,
+	* malware_test_file = NULL, * real_sender_address;
 uschar * log_oneline = NULL;
-const uschar * malware_test_file = NULL;
-const uschar * real_sender_address;
 uschar * originator_home = US"/";
 size_t sz;
 
-struct passwd *pw;
+struct passwd * pw;
 struct stat statbuf;
 pid_t passed_qr_pid = (pid_t)0;
 int passed_qr_pipe = -1;
@@ -2017,13 +1888,13 @@ BOOL info_stdout = FALSE;
 
 /* Possible options for -R and -S */
 
-static uschar *rsopts[] = { US"f", US"ff", US"r", US"rf", US"rff" };
+static uschar * rsopts[] = { US"f", US"ff", US"r", US"rf", US"rff" };
 
 /* Need to define this in case we need to change the environment in order
 to get rid of a bogus time zone. We have to make it char rather than uschar
-because some OS define it in /usr/include/unistd.h. */
+because some OS' define it in /usr/include/unistd.h. */
 
-extern char **environ;
+extern char ** environ;
 
 #ifdef MEASURE_TIMING
 (void)gettimeofday(&timestamp_startup, NULL);
@@ -2121,7 +1992,7 @@ if (!(log_buffer = US malloc(LOG_BUFFER_SIZE)))
 
 /* Initialize the default log options. */
 
-bits_set(log_selector, log_selector_size, log_default);
+logging_set_defaults();
 
 /* 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
@@ -2335,6 +2206,10 @@ unprivileged = (real_uid != root_uid && original_euid != root_uid);
  int old_pool = store_pool;
  store_pool = POOL_PERM;
 
+/* Allocate an empty bitmask for debug channels */
+
+ debug_modify_channel(US"");
+
 /* Scan the program's arguments. Some can be dealt with right away; others are
 simply recorded for checking and handling afterwards. Do a high-level switch
 on the second character (the one after '-'), to save some effort. */
@@ -2483,7 +2358,7 @@ on the second character (the one after '-'), to save some effort. */
 	   -bem: Ditto, but read a message from a file first
 	*/
 	case 'e':
-	  expansion_test = checking = TRUE;
+	  f.expansion_test = checking = TRUE;
 	  if (*argrest == 'm')
 	    {
 	    if (++i >= argc) { badarg = TRUE; break; }
@@ -2559,16 +2434,16 @@ on the second character (the one after '-'), to save some effort. */
 	    const uschar * p = argrest+1;
 	    info_flag = CMDINFO_HELP;
 	    if (Ustrlen(p))
-	      if (strcmpic(p, CUS"sieve") == 0)
-		{
-		info_flag = CMDINFO_SIEVE;
-		info_stdout = TRUE;
-		}
+	      if (strcmpic(p, CUS"modules") == 0)
+		{ info_flag = CMDINFO_MODULES; info_stdout = TRUE; }
+#ifndef DISABLE_SIEVE_FILTER
+	      else if (strcmpic(p, CUS"sieve") == 0)
+		{ info_flag = CMDINFO_SIEVE; info_stdout = TRUE; }
+#endif
+#ifdef SUPPORT_DSCP
 	      else if (strcmpic(p, CUS"dscp") == 0)
-		{
-		info_flag = CMDINFO_DSCP;
-		info_stdout = TRUE;
-		}
+		{ info_flag = CMDINFO_DSCP; info_stdout = TRUE; }
+#endif
 	      else if (strcmpic(p, CUS"help") == 0)
 		info_stdout = TRUE;
 	    }
@@ -2657,8 +2532,7 @@ on the second character (the one after '-'), to save some effort. */
 	  else
 	    {
 	    list_options = TRUE;
-	    debug_selector |= D_v;
-	    debug_file = stderr;
+	    set_verb_opt_mode();
 	    }
 	  break;
 
@@ -2914,14 +2788,9 @@ on the second character (the one after '-'), to save some effort. */
 
     case 'd':
 
-    /* -dropcr: Set this option.  Now a no-op, retained for compatibility only. */
-
-    if (Ustrcmp(argrest, "ropcr") == 0)
-      /* drop_cr = TRUE */ ;
-
     /* -dp: Set up a debug pretrigger buffer with given size. */
 
-    else if (Ustrcmp(argrest, "p") == 0)
+    if (Ustrcmp(argrest, "p") == 0)
       if (++i >= argc)
 	badarg = TRUE;
       else
@@ -2929,6 +2798,7 @@ on the second character (the one after '-'), to save some effort. */
 
     /* -dS: enable startup-time debugging (before the config is read) */
     /* Another debug channel per "-d" would be nicer, but we're at 32 already */
+    /*XXX*/
 
     else if (Ustrcmp(argrest, "S") == 0)
       debug_startup = TRUE;
@@ -2946,17 +2816,19 @@ on the second character (the one after '-'), to save some effort. */
       /* Use an intermediate variable so that we don't set debugging while
       decoding the debugging bits. */
 
-      unsigned int selector = D_default;
-      debug_selector = 0;
+      bitmask_word_t * selector = NULL;
+
       debug_file = NULL;
       if (*argrest == 'd')
         {
         f.debug_daemon = TRUE;
         argrest++;
         }
+
+      debug_set_default_bits(&selector);
       if (*argrest)
-        decode_bits(&selector, 1, debug_notall, argrest,
-          debug_options, debug_options_count, US"debug", 0);
+	debug_decode_bits(&selector, argrest, 0);
+
       debug_selector = selector;
       }
     break;
@@ -3157,7 +3029,16 @@ on the second character (the one after '-'), to save some effort. */
 
       if (!continue_proxy_cipher)
 	if (getsockname(0, (struct sockaddr *)(&tmp_sock), &size) == 0)
-	  sending_ip_address = host_ntoa(-1, &tmp_sock, NULL, &sending_port);
+	  switch (tmp_sock.v0.sa_family)
+	    {
+	    case AF_INET:
+	    case AF_INET6:
+	      sending_ip_address =
+		    host_ntoa(-1, &tmp_sock, NULL, &sending_port);
+	      break;
+	    default:
+	      exim_fail("non-INET socket on stdin for -MC option");
+	    }
 	else
 	  exim_fail("getsockname() failed after -MC option: %s",
 	    strerror(errno));
@@ -3222,6 +3103,8 @@ on the second character (the one after '-'), to save some effort. */
 
 #ifdef SUPPORT_SOCKS
     /* -MCp: Socks proxy in use; nearside IP, port, external IP, port */
+    /*XXX Neatness might have this code in the socks module, but having to
+    load a dynamic version puely for this?  However, of logging moved there...*/
 	case 'p': proxy_session = TRUE;
 		  if (++i < argc)
 		    {
@@ -3439,8 +3322,7 @@ on the second character (the one after '-'), to save some effort. */
     if (!*argrest)
       {
       f.dont_deliver = TRUE;
-      debug_selector |= D_v;
-      debug_file = stderr;
+      set_verb_opt_mode();
       }
     else badarg = TRUE;
     break;
@@ -3497,6 +3379,77 @@ on the second character (the one after '-'), to save some effort. */
 	}
 	break;
 
+      /* -oDSN: RFC 3461 DSN options:
+	  -oDSN N=[never,success,failure,delay]
+	  -oDSN R=[full,hdrs]
+	  -oDSN V=<envid>
+      */
+      case 'D':
+	{
+	const uschar * s;
+	uschar c;
+
+	if (Ustrcmp(argrest, "SN") != 0)
+	  {
+	  badarg = TRUE;
+	  break;
+	  }
+	if (i+1 >= argc) exim_fail("string expected after -oDSN");
+	switch (c = *(s = argv[++i]))
+	  {
+	  default:  exim_fail("only subcases N,R,V expected for -oDSN");
+	  case 'N': case 'R': case 'V':
+		    if (*++s == '=') break;
+		    exim_fail("equals expected after -oDSN subcase letter");
+	  }
+	s++;
+	switch (c)
+	  {
+	  case 'N':
+	    while (*s)
+	      {
+	      if (Ustrncmp(s, "never", 5) == 0)
+		if (*(s += 5) || rcpt_dsn_flags)
+		  exim_fail("DSN NOTIFY=never may not be combined");
+		else
+		  rcpt_dsn_flags = rf_notify_never;
+	      else if (Ustrncmp(s, "success", 7) == 0)
+		{ rcpt_dsn_flags |= rf_notify_success; s += 7; }
+	      else if (Ustrncmp(s, "failure", 7) == 0)
+		{ rcpt_dsn_flags |= rf_notify_failure; s += 7; }
+	      else if (Ustrncmp(s, "delay", 5) == 0)
+		{ rcpt_dsn_flags |= rf_notify_delay;   s += 5; }
+	      else
+		exim_fail("DSN NOTIFY bad value");
+
+	      if (!s) break;
+	      if (*s == ',') s++;
+	      }
+	    break;
+
+	  case 'R':
+	    if (Ustrcmp(s, "full") == 0)
+	      dsn_ret = dsn_ret_full;
+	    else if (Ustrcmp(s, "hdrs") == 0)
+	      dsn_ret = dsn_ret_hdrs;
+	    else
+	      exim_fail("bad value for DSN RET");
+	    break;
+
+	  case 'V':	/* per rfc 3461 4.4 - 100 char max, after xtext-enc
+			and the "ENVID=" prefix */
+	    {
+	    unsigned n = 0;
+	    for (const uschar * t = s; *t; t++, n++)
+	      if (!isprint(*t)) exim_fail("DSN ENVID has bad character");
+	    dsn_envid = xtextencode(s, n);
+	    if (Ustrlen(dsn_envid) > 100-6) exim_fail("DSN ENVID is oversize");
+	    break;
+	    }
+	  }
+	}
+	break;
+
       /* -odb: background delivery */
 
       case 'd':
@@ -3963,14 +3916,11 @@ on the second character (the one after '-'), to save some effort. */
     break;
 
 
-    /* -v: verify things - this is a very low-level debugging */
+    /* -v: verbose - this is a very gentle-level debugging */
 
     case 'v':
     if (!*argrest)
-      {
-      debug_selector |= D_v;
-      debug_file = stderr;
-      }
+      set_verb_opt_mode();
     else badarg = TRUE;
     break;
 
@@ -4070,9 +4020,9 @@ if (	 (smtp_input || extract_recipients || recipients_arg < argc)
    ||	 smtp_input
       && (sender_address || filter_test != FTEST_NONE || extract_recipients)
    || deliver_selectstring && !qrunners
-   || msg_action == MSG_LOAD && (!expansion_test || expansion_test_message)
+   || msg_action == MSG_LOAD && (!f.expansion_test || expansion_test_message)
    ||	 atrn_mode
-      && (  f.daemon_listen || expansion_test || filter_test != FTEST_NONE
+      && (  f.daemon_listen || f.expansion_test || filter_test != FTEST_NONE
 	 || checking /* || bi_option || info_stdout || receiving_message
 	 || malware_test_file || list_queue || list_config || list_options
 	 || version_printed || msg_action_arg > 0 || qrunners
@@ -4085,18 +4035,18 @@ if (	 (smtp_input || extract_recipients || recipients_arg < argc)
 child processes. It should, of course, be 2 for stderr. Also, force the daemon
 to run in the foreground. */
 
-if (debug_selector != 0)
+if (ANY_DEBUG)
   {
-  debug_file = stderr;
-  debug_fd = fileno(debug_file);
+  set_debug_stream();
   f.background_daemon = FALSE;
   testharness_pause_ms(100);   /* lets caller finish */
-  if (debug_selector != D_v)    /* -v only doesn't show this */
+
+  if (DEBUG_BIT(BIT_TABLE_IDX_NONVERB))		/* -v only doesn't show this */
     {
-    debug_printf("Exim version %s uid=%ld gid=%ld pid=%d D=%x\n",
-      version_string, (long int)real_uid, (long int)real_gid, (int)getpid(),
-      debug_selector);
-    if (!version_printed)
+    debug_printf("Exim version %s uid=%ld gid=%ld pid=" PID_T_FMT " %Y\n",
+      version_string, (long int)real_uid, (long int)real_gid, getpid(),
+      debug_selector_dump(NULL));
+    DEBUG(start) if (!version_printed)
       show_whats_supported(FALSE);
     }
   }
@@ -4109,7 +4059,7 @@ change some of these limits. */
 
 if (unprivileged)
   {
-  DEBUG(D_any) debug_print_ids(US"Exim has no root privilege:");
+  DEBUG(any) debug_print_ids(US"Exim has no root privilege:");
   }
 else
   {
@@ -4118,7 +4068,7 @@ else
 #ifdef RLIMIT_NOFILE
   if (getrlimit(RLIMIT_NOFILE, &rlp) < 0)
     {
-    log_write(0, LOG_MAIN|LOG_PANIC, "getrlimit(RLIMIT_NOFILE) failed: %s",
+    log_write(LOG_MAIN|LOG_PANIC, "getrlimit(RLIMIT_NOFILE) failed: %s",
       strerror(errno));
     rlp.rlim_cur = rlp.rlim_max = 0;
     }
@@ -4134,7 +4084,7 @@ else
       {
       rlp.rlim_cur = rlp.rlim_max = 256;
       if (setrlimit(RLIMIT_NOFILE, &rlp) < 0)
-        log_write(0, LOG_MAIN|LOG_PANIC, "setrlimit(RLIMIT_NOFILE) failed: %s",
+        log_write(LOG_MAIN|LOG_PANIC, "setrlimit(RLIMIT_NOFILE) failed: %s",
           strerror(errno));
       }
     }
@@ -4143,7 +4093,7 @@ else
 #ifdef RLIMIT_NPROC
   if (getrlimit(RLIMIT_NPROC, &rlp) < 0)
     {
-    log_write(0, LOG_MAIN|LOG_PANIC, "getrlimit(RLIMIT_NPROC) failed: %s",
+    log_write(LOG_MAIN|LOG_PANIC, "getrlimit(RLIMIT_NPROC) failed: %s",
       strerror(errno));
     rlp.rlim_cur = rlp.rlim_max = 0;
     }
@@ -4158,7 +4108,7 @@ else
     rlp.rlim_cur = rlp.rlim_max = 1000;
 # endif
     if (setrlimit(RLIMIT_NPROC, &rlp) < 0)
-      log_write(0, LOG_MAIN|LOG_PANIC, "setrlimit(RLIMIT_NPROC) failed: %s",
+      log_write(LOG_MAIN|LOG_PANIC, "setrlimit(RLIMIT_NPROC) failed: %s",
         strerror(errno));
     }
 #endif
@@ -4224,15 +4174,14 @@ recompile each time, or to patch in an actual configuration file name and other
 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 */
-    (!f.trusted_config ||                          /* Config changed, or */
-     !macros_trusted(opt_D_used)) &&		 /*  impermissible macros and */
-    real_uid != root_uid &&                      /* Not root, and */
-    !f.running_in_test_harness                     /* Not fudged */
-    ) ||                                         /*   OR   */
-    expansion_test                               /* expansion testing */
-    ||                                           /*   OR   */
-    filter_test != FTEST_NONE)                   /* Filter testing */
+if (  (					/* EITHER */
+        (  !f.trusted_config			  /* Config changed */
+	|| !macros_trusted(opt_D_used))		  /* or impermissible macros */
+      && real_uid != root_uid			/* and Not root */
+      && !f.running_in_test_harness		/* and Not fudged */
+      )
+    || f.expansion_test			/* OR expansion testing */
+    || filter_test != FTEST_NONE)	/* OR Filter testing */
   {
   setgroups(group_count, group_list);
   exim_setugid(real_uid, real_gid, FALSE,
@@ -4340,8 +4289,7 @@ defined) */
 /* Now in directory "/" */
 
 if (cleanup_environment() == FALSE)
-  log_write_die(0, LOG_PANIC_DIE, "Can't cleanup environment");
-
+  log_write_die(LOG_PANIC_DIE, "Can't cleanup environment");
 
 /* If an action on specific messages is requested, or if a daemon or queue
 runner is being started, we need to know if Exim was called by an admin user.
@@ -4393,15 +4341,15 @@ if (checking && commandline_checks_require_admin && !f.admin_user)
 
 /* Handle the decoding of logging options. */
 
-decode_bits(log_selector, log_selector_size, log_notall,
-  log_selector_string, log_options, log_options_count, US"log", 0);
+decode_bits(log_selector, log_selector_size, log_notall_names,
+  log_selector_string, log_channels, log_chan_count, DCB_LOG);
 
-DEBUG(D_any)
+DEBUG(start)
   {
   debug_printf("configuration file is %s\n", config_main_filename);
   debug_printf("log selectors =");
   for (int i = 0; i < log_selector_size; i++)
-    debug_printf(" %08x", log_selector[i]);
+    debug_printf(" 0x" PR_EXIM_BITMASK, log_selector[i]);
   debug_printf("\n");
   }
 
@@ -4439,28 +4387,24 @@ log_write() from here will cause the ultimate panic collapse if the complete
 file name exceeds the buffer length. */
 
 if (Ustrlen(log_file_path) > 200)
-  log_write_die(0, LOG_MAIN,
-    "log_file_path is longer than 200 chars: aborting");
+  log_write_die(LOG_MAIN, "log_file_path is longer than 200 chars: aborting");
 
 if (Ustrlen(pid_file_path) > 200)
-  log_write_die(0, LOG_MAIN,
-    "pid_file_path is longer than 200 chars: aborting");
+  log_write_die(LOG_MAIN, "pid_file_path is longer than 200 chars: aborting");
 
 if (Ustrlen(spool_directory) > 200)
-  log_write_die(0, LOG_MAIN,
-    "spool_directory is longer than 200 chars: aborting");
+  log_write_die(LOG_MAIN, "spool_directory is longer than 200 chars: aborting");
 
 /* Length check on the process name given to syslog for its TAG field,
 which is only permitted to be 32 characters or less. See RFC 3164. */
 
 if (Ustrlen(syslog_processname) > 32)
-  log_write_die(0, LOG_MAIN,
-    "syslog_processname is longer than 32 chars: aborting");
+  log_write_die(LOG_MAIN, "syslog_processname is longer than 32 chars: aborting");
 
 if (log_oneline)
   if (f.admin_user)
     {
-    log_write(0, LOG_MAIN, "%s", log_oneline);
+    log_write(LOG_MAIN, "%s", log_oneline);
     return EXIT_SUCCESS;
     }
   else
@@ -4482,7 +4426,7 @@ EXIM_TMPDIR by the build scripts.
       uschar * newp = store_malloc(Ustrlen(EXIM_TMPDIR) + 8);
       sprintf(CS newp, "TMPDIR=%s", EXIM_TMPDIR);
       *p = newp;
-      DEBUG(D_any) debug_printf("reset TMPDIR=%s in environment\n", EXIM_TMPDIR);
+      DEBUG(any) debug_printf("reset TMPDIR=%s in environment\n", EXIM_TMPDIR);
       }
 #endif
 
@@ -4523,7 +4467,7 @@ else
     *newp = NULL;
     environ = CSS new;
     tzset();
-    DEBUG(D_any) debug_printf("Reset TZ to %s: time is %s\n", timezone_string,
+    DEBUG(any) debug_printf("Reset TZ to %s: time is %s\n", timezone_string,
       tod_stamp(tod_log));
     }
   }
@@ -4555,7 +4499,7 @@ if (  removed_privilege
   if (deliver_drop_privilege)
     f.really_exim = TRUE; /* let logging work normally */
   else
-    log_write(0, LOG_MAIN|LOG_PANIC,
+    log_write(LOG_MAIN|LOG_PANIC,
       "exim user lost privilege for using %s option",
       f.trusted_config? "-D" : "-C");
 
@@ -4577,10 +4521,10 @@ a debugging feature for finding out what arguments certain MUAs actually use.
 Don't attempt it if logging is disabled, or if listing variables or if
 verifying/testing addresses or expansions. */
 
-if (  (debug_selector & D_any  ||  LOGGING(arguments))
+if (  (IS_DEBUG(any) || LOGGING(arguments))
    && f.really_exim && !list_options && !checking)
   {
-  uschar *p = big_buffer;
+  uschar * p = big_buffer;
   Ustrcpy(p, US"cwd= (failed)");
 
   if (!initial_cwd)
@@ -4602,7 +4546,7 @@ if (  (debug_selector & D_any  ||  LOGGING(arguments))
     if (p + len + 8 >= big_buffer + big_buffer_size)
       {
       Ustrcpy(p, US" ...");
-      log_write(0, LOG_MAIN, "%s", big_buffer);
+      log_write(LOG_MAIN, "%s", big_buffer);
       Ustrcpy(big_buffer, US"...");
       p = big_buffer + 3;
       }
@@ -4619,7 +4563,7 @@ if (  (debug_selector & D_any  ||  LOGGING(arguments))
     }
 
   if (LOGGING(arguments))
-    log_write(0, LOG_MAIN, "%s", big_buffer);
+    log_write(LOG_MAIN, "%s", big_buffer);
   else
     debug_printf("%s\n", big_buffer);
   }
@@ -4657,7 +4601,7 @@ if (bi_option)
     setgroups(group_count, group_list);
     exim_setugid(real_uid, real_gid, FALSE, US"running bi_command");
 
-    DEBUG(D_exec) debug_printf("exec '%.256s' %s%.256s%s\n", bi_argv[0],
+    DEBUG(exec) debug_printf("exec '%.256s' %s%.256s%s\n", bi_argv[0],
       bi_argv[1] ? "'" : "",
       bi_argv[1] ? bi_argv[1] : US"",
       bi_argv[1] ? "'" : "");
@@ -4667,7 +4611,7 @@ if (bi_option)
     }
   else
     {
-    DEBUG(D_any) debug_printf("-bi used but bi_command not set; exiting\n");
+    DEBUG(any) debug_printf("-bi used but bi_command not set; exiting\n");
     exit(EXIT_SUCCESS);
     }
   }
@@ -4676,8 +4620,11 @@ if (bi_option)
 configuration file.  We leave these prints here to ensure that syslog setup,
 logfile setup, and so on has already happened. */
 
-if (f.trusted_caller) DEBUG(D_any) debug_printf("trusted user\n");
-if (f.admin_user) DEBUG(D_any) debug_printf("admin user\n");
+DEBUG(any)
+  {
+  if (f.trusted_caller)	debug_printf("trusted user\n");
+  if (f.admin_user)	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
@@ -4689,7 +4636,8 @@ count. Only an admin user can use the test interface to scan for email
 
 if (!f.admin_user)
   {
-  BOOL debugset = (debug_selector & ~D_v) != 0;
+  BOOL debugset = !!DEBUG_BIT(BIT_TABLE_IDX_NONVERB);
+
   if (  deliver_give_up || f.daemon_listen || malware_test_file
      || count_queue && queue_list_requires_admin
      || list_queue && queue_list_requires_admin
@@ -4746,7 +4694,7 @@ if (flag_G)
   if (f.trusted_caller)
     {
     f.suppress_local_fixups = f.suppress_local_fixups_default = TRUE;
-    DEBUG(D_acl) debug_printf("suppress_local_fixups forced on by -G\n");
+    DEBUG(acl) debug_printf("suppress_local_fixups forced on by -G\n");
     }
   else
     exim_fail("permission denied (-G requires a trusted user)");
@@ -4777,7 +4725,7 @@ if (smtp_input)
 
       if (real_uid == root_uid || real_uid == exim_uid || interface_port < 1024)
         {
-        if (mua_wrapper) log_write_die(0, LOG_MAIN, "Input from "
+        if (mua_wrapper) log_write_die(LOG_MAIN, "Input from "
           "inetd is not supported when mua_wrapper is set");
         f.is_inetd = TRUE;
         sender_host_address = host_ntoa(-1, (struct sockaddr *)(&inetd_sock),
@@ -4843,7 +4791,7 @@ if (  !unprivileged				/* originally had root AND */
 else
   {
   int rv;
-  DEBUG(D_any) debug_printf("dropping to exim gid; retaining priv uid\n");
+  DEBUG(start) debug_printf("dropping to exim gid; retaining priv uid\n");
   rv = setgid(exim_gid);
   /* Impact of failure is that some stuff might end up with an incorrect group.
   We track this for failures from root, since any attempt to change privilege
@@ -4854,7 +4802,7 @@ else
     if (!(unprivileged || removed_privilege))
       exim_fail("changing group failed: %s", strerror(errno));
     else
-      DEBUG(D_any) debug_printf("changing group to %ld failed: %s\n",
+      DEBUG(any) debug_printf("changing group to %ld failed: %s\n",
           (long int)exim_gid, strerror(errno));
   }
 
@@ -4960,7 +4908,7 @@ needed in transports so we lost the optimisation. */
   /* -be can add macro definitions, needing to link to the macro structure
   chain.  Otherwise, make the memory used for config data readonly. */
 
-  if (!expansion_test)
+  if (!f.expansion_test)
     store_writeprotect(POOL_CONFIG);
 
 #ifdef MEASURE_TIMING
@@ -5256,14 +5204,14 @@ for (i = 0;;)
             expand_nmax = -1;
             if (new_name)
               {
-              DEBUG(D_receive) debug_printf("user name %q extracted from "
+              DEBUG(receive) debug_printf("user name %q extracted from "
                 "gecos field %q\n", new_name, name);
               name = new_name;
               }
-            else DEBUG(D_receive) debug_printf("failed to expand gecos_name string "
+            else DEBUG(receive) debug_printf("failed to expand gecos_name string "
               "%q: %s\n", gecos_name, expand_string_message);
             }
-          else DEBUG(D_receive) debug_printf("gecos_pattern %q did not match "
+          else DEBUG(receive) debug_printf("gecos_pattern %q did not match "
             "gecos field %q\n", gecos_pattern, name);
           store_free((void *)re);
           }
@@ -5299,7 +5247,7 @@ if (!originator_login || f.running_in_test_harness)
     if (!originator_name) originator_name = US"";
     }
   if (!originator_login)
-    log_write_die(0, LOG_MAIN, "Failed to get user name for uid %d",
+    log_write_die(LOG_MAIN, "Failed to get user name for uid %d",
       (int)real_uid);
   }
 
@@ -5315,7 +5263,7 @@ read in from the spool. */
 originator_uid = real_uid;
 originator_gid = real_gid;
 
-DEBUG(D_receive) debug_printf("originator: uid=%d gid=%d login=%s name=%s\n",
+DEBUG(receive) debug_printf("originator: uid=%d gid=%d login=%s name=%s\n",
   (int)originator_uid, (int)originator_gid, originator_login, originator_name);
 
 /* Run in daemon and/or queue-running mode. The function daemon_go() never
@@ -5328,7 +5276,7 @@ if (f.daemon_listen || f.inetd_wait_mode || is_multiple_qrun())
   if (mua_wrapper)
     {
     fprintf(stderr, "Daemon cannot be run when mua_wrapper is set\n");
-    log_write_die(0, LOG_MAIN, "Daemon cannot be run when "
+    log_write_die(LOG_MAIN, "Daemon cannot be run when "
       "mua_wrapper is set");
     }
 
@@ -5432,7 +5380,7 @@ if (sender_address && *sender_address && sender_address_domain == 0)
   sender_address = string_sprintf("%s@%s", local_part_quote(sender_address),
     qualify_domain_sender);
 
-DEBUG(D_receive) debug_printf("sender address = %s\n", sender_address);
+DEBUG(receive) debug_printf("sender address = %s\n", sender_address);
 
 /* Handle a request to verify a list of addresses, or test them for delivery.
 This must follow the setting of the sender address, since routers can be
@@ -5442,51 +5390,50 @@ stdin. Set debug_level to at least D_v to get full output for address testing.
 
 if (verify_address_mode || f.address_test_mode)
   {
-  int exit_value = 0;
-  int flags = vopt_qualify;
+  int exit_value = EXIT_SUCCESS, flags = vopt_qualify;
 
   if (verify_address_mode)
     {
     if (!verify_as_sender) flags |= vopt_is_recipient;
-    DEBUG(D_verify) debug_print_ids(US"Verifying:");
+    DEBUG(verify) debug_print_ids(US"Verifying:");
     }
 
   else
     {
     flags |= vopt_is_recipient;
-    debug_selector |= D_v;
-    debug_file = stderr;
-    debug_fd = fileno(debug_file);
-    DEBUG(D_verify) debug_print_ids(US"Address testing:");
+    set_verb_opt_mode();
+    DEBUG(verify) debug_print_ids(US"Address testing:");
     }
 
-  if (recipients_arg < argc)
+  if (recipients_arg < argc)			/* addresses on cmdline */
     while (recipients_arg < argc)
       {
       /* Supplied addresses are tainted since they come from a user */
       uschar * s = string_copy_taint(
-	exim_str_fail_toolong(argv[recipients_arg++], EXIM_DISPLAYMAIL_MAX, "address verification"),
+	exim_str_fail_toolong(argv[recipients_arg++], EXIM_DISPLAYMAIL_MAX,
+			      "address verification"),
 	GET_TAINTED);
       while (*s)
         {
         BOOL finished = FALSE;
-        uschar *ss = parse_find_address_end(s, FALSE);
+        uschar * ss = parse_find_address_end(s, FALSE);
         if (*ss == ',') *ss = 0; else finished = TRUE;
-        test_address(s, flags, &exit_value);
+        exit_value = test_address(s, flags, exit_value);
         s = ss;
         if (!finished)
           while (*++s == ',' || isspace(*s)) ;
         }
       }
 
-  else for (;;)
+  else for (;;)					/* addresses from stdin */
     {
     const uschar * s = get_stdinput(NULL, NULL);
     if (!s) break;
-    test_address(string_copy_taint(
-	exim_str_fail_toolong(s, EXIM_DISPLAYMAIL_MAX, "address verification (stdin)"),
+    exit_value = test_address(string_copy_taint(
+	exim_str_fail_toolong(s, EXIM_DISPLAYMAIL_MAX,
+			      "address verification (stdin)"),
 	GET_TAINTED),
-      flags, &exit_value);
+      flags, exit_value);
     }
 
   route_tidyup();
@@ -5498,7 +5445,7 @@ from stdin if there aren't any. If -Mset was specified, load the message so
 that its variables can be used, but restrict this facility to admin users.
 Otherwise, if -bem was used, read a message from stdin. */
 
-if (expansion_test)
+if (f.expansion_test)
   {
   set_process_info("expansion-test");
   dns_init(FALSE, FALSE, FALSE);
@@ -5534,7 +5481,7 @@ if (expansion_test)
     filter_test = FTEST_USER;      /* Fudge to make it look like filter test */
     message_ended = END_NOTENDED;
     recipients_max_expanded = atoi(CCS rme);
-    read_message_body(receive_msg(extract_recipients));
+    read_message_body(receive_msg(extract_recipients, rf_notify_unset));
     message_linecount += body_linecount;
     (void)dup2(save_stdin, 0);
     (void)close(save_stdin);
@@ -5605,7 +5552,7 @@ if (raw_active_hostname)
   if (!nah)
     {
     if (!f.expand_string_forcedfail)
-      log_write_die(0, LOG_MAIN, "failed to expand %q "
+      log_write_die(LOG_MAIN, "failed to expand %q "
         "(smtp_active_hostname): %s", raw_active_hostname,
         expand_string_message);
     }
@@ -5650,8 +5597,8 @@ if (host_checking)
   smtp_out_fd = fileno(stdout);
   f.sender_local = FALSE;
   f.sender_host_notsocket = TRUE;
-  debug_file = stderr;
-  debug_fd = fileno(debug_file);
+  set_debug_stream();
+
   dprintf(smtp_out_fd, "\n**** SMTP testing session as if from host %s\n"
     "**** but without any ident (RFC 1413) callback.\n"
     "**** This is not for real!\n\n",
@@ -5659,12 +5606,11 @@ if (host_checking)
 
   set_connection_id();
   memset(sender_host_cache, 0, sizeof(sender_host_cache));
+
   if (verify_check_host(&hosts_connection_nolog) == OK)
-    {
-    BIT_CLEAR(log_selector, log_selector_size, Li_smtp_connection);
-    BIT_CLEAR(log_selector, log_selector_size, Li_smtp_no_mail);
-    }
-  log_write(L_smtp_connection, LOG_MAIN, "%s", smtp_get_connection_info());
+    logging_modify_channels(US"-smtp_connection -smtp_no_mail");
+  if (LOGGING(smtp_connection))
+    log_write(LOG_MAIN, "%s", smtp_get_connection_info());
 
   /* NOTE: We do *not* call smtp_log_no_mail() if smtp_start_session() fails,
   because a log line has already been written for all its failure exists
@@ -5677,7 +5623,7 @@ if (host_checking)
     for (; (reset_point = store_mark()); store_reset(reset_point))
       {
       if (smtp_setup_msg() <= 0) break;
-      if (!receive_msg(FALSE)) break;
+      if (!receive_msg(FALSE, rf_notify_unset)) break;
 
       return_path = sender_address = NULL;
       dnslist_domain = dnslist_matched = NULL;
@@ -5855,11 +5801,10 @@ if (smtp_input)
 
   memset(sender_host_cache, 0, sizeof(sender_host_cache));
   if (verify_check_host(&hosts_connection_nolog) == OK)
-    {
-    BIT_CLEAR(log_selector, log_selector_size, Li_smtp_connection);
-    BIT_CLEAR(log_selector, log_selector_size, Li_smtp_no_mail);
-    }
-  log_write(L_smtp_connection, LOG_MAIN, "%s", smtp_get_connection_info());
+    logging_modify_channels(US"-smtp_connection -smtp_no_mail");
+  if (LOGGING(smtp_connection))
+    log_write(LOG_MAIN, "%s", smtp_get_connection_info());
+
   if (!smtp_start_session())
     exim_exit(EXIT_SUCCESS);
   }
@@ -5873,10 +5818,10 @@ else
   thismessage_size_limit = expand_string_integer(message_size_limit, TRUE);
   if (expand_string_message)
     if (thismessage_size_limit == -1)
-      log_write_die(0, LOG_MAIN, "failed to expand "
+      log_write_die(LOG_MAIN, "failed to expand "
         "message_size_limit: %s", expand_string_message);
     else
-      log_write_die(0, LOG_MAIN, "invalid value for "
+      log_write_die(LOG_MAIN, "invalid value for "
         "message_size_limit: %s", expand_string_message);
 
   setvbuf(stdin, NULL, _IONBF, 0);
@@ -5977,7 +5922,7 @@ for (BOOL more = TRUE; more; )
 
       /* Now get the data for the message */
 
-      more = receive_msg(extract_recipients);
+      more = receive_msg(extract_recipients, rcpt_dsn_flags);
       if (!message_id[0])
         {
 	cancel_cutthrough_connection(TRUE, US"receive dropped");
@@ -6025,7 +5970,7 @@ for (BOOL more = TRUE; more; )
       uschar * errmess;
       /* There can be multiple addresses, so EXIM_DISPLAYMAIL_MAX (tuned for 1) is too short.
       We'll still want to cap it to something, just in case. */
-      const uschar * s = string_copy_taint(
+      uschar * s = string_copy_taint(
 	exim_str_fail_toolong(list[i], BIG_BUFFER_SIZE, "address argument"),
 	GET_TAINTED);
 
@@ -6044,7 +5989,7 @@ for (BOOL more = TRUE; more; )
         if (  recipients_max_expanded > 0 && ++rcount > recipients_max_expanded
 	   && !extract_recipients)
 	  {
-	  DEBUG(D_all) debug_printf("excess reipients (max %d)\n",
+	  DEBUG(all) debug_printf("excess reipients (max %d)\n",
 	    recipients_max_expanded);
 
           if (error_handling == ERRORS_STDERR)
@@ -6082,7 +6027,7 @@ for (BOOL more = TRUE; more; )
 
         if (!recipient)
 	  {
-	  DEBUG(D_all) debug_printf("bad recipient address %q: %s\n",
+	  DEBUG(all) debug_printf("bad recipient address %q: %s\n",
 	    string_printing(list[i]), errmess);
 
           if (error_handling == ERRORS_STDERR)
@@ -6103,7 +6048,10 @@ for (BOOL more = TRUE; more; )
             }
 	  }
 
-        receive_add_recipient(string_copy_taint(recipient, GET_TAINTED), -1);
+	/*XXX no way to set orcpt from cmdline, yet */
+        receive_add_recipient(string_copy_taint(recipient, GET_TAINTED), -1,
+	  rcpt_dsn_flags, NULL);
+
         s = ss;
         if (!finished)
           while (*++s && (*s == ',' || isspace(*s)));
@@ -6112,7 +6060,7 @@ for (BOOL more = TRUE; more; )
 
     /* Show the recipients when debugging */
 
-    DEBUG(D_receive)
+    DEBUG(receive)
       {
       if (sender_address) debug_printf("Sender: %s\n", sender_address);
       if (recipients_list)
@@ -6151,7 +6099,7 @@ for (BOOL more = TRUE; more; )
     spool. */
 
     message_ended = END_NOTENDED;
-    more = receive_msg(extract_recipients);
+    more = receive_msg(extract_recipients, rcpt_dsn_flags);
 
     /* more is always FALSE here (not SMTP message) when reading a message
     for real; when reading the headers of a message for filter testing,
@@ -6191,7 +6139,8 @@ for (BOOL more = TRUE; more; )
         ftest_prefix ? ftest_prefix : US"",
         deliver_localpart,
         ftest_suffix ? ftest_suffix : US"",
-        deliver_domain), -1);
+        deliver_domain),
+      -1, rcpt_dsn_flags, NULL);
 
     printf("Recipient   = %s\n", recipients_list[0].address);
     if (ftest_prefix) printf("Prefix    = %s\n", ftest_prefix);
@@ -6199,7 +6148,7 @@ for (BOOL more = TRUE; more; )
 
     if (chdir("/"))   /* Get away from wherever the user is running this from */
       {
-      DEBUG(D_receive) debug_printf("chdir(\"/\") failed\n");
+      DEBUG(receive) debug_printf("chdir(\"/\") failed\n");
       exim_exit(EXIT_FAILURE);
       }
 
@@ -6264,19 +6213,17 @@ for (BOOL more = TRUE; more; )
   if (local_queue_only)
     {
     cancel_cutthrough_connection(TRUE, US"no delivery; queueing");
-    switch(queue_only_reason)
+    if (LOGGING(delay_delivery)) 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);
+	log_write(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",
+	log_write(LOG_MAIN, "no immediate delivery: load average %.2f",
 		(double)load_average/1000.0);
-      break;
+	break;
       }
     }
 
@@ -6322,7 +6269,7 @@ for (BOOL more = TRUE; more; )
     if (pid < 0)
       {
       cancel_cutthrough_connection(TRUE, US"delivery fork failed");
-      log_write(0, LOG_MAIN|LOG_PANIC, "failed to fork automatic delivery "
+      log_write(LOG_MAIN|LOG_PANIC, "failed to fork automatic delivery "
         "process: %s", strerror(errno));
       }
     else
@@ -6337,9 +6284,9 @@ for (BOOL more = TRUE; more; )
 	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);
+	  log_write(LOG_MAIN|LOG_PANIC,
+	    "process " PID_T_FMT " crashed with signal %d while delivering %s",
+	    pid, status & 0x00ff, message_id);
 	if (mua_wrapper && (status & 0xffff) != 0)
 	  exim_exit(EXIT_FAILURE);
 	}
@@ -6365,6 +6312,7 @@ MORELOOP:
 #ifdef WITH_CONTENT_SCAN
   malware_name = NULL;
   regex_vars_clear();
+  spam_action = spam_report = spam_bar = spam_score = spam_score_int = NULL;
 #endif
   callout_address = NULL;
   sending_ip_address = NULL;
diff --git a/src/src/exim.h b/src/src/exim.h
index 8109c2a7c..70911b0f8 100644
--- a/src/src/exim.h
+++ b/src/src/exim.h
@@ -155,7 +155,8 @@ The driver name is a name of a router/transport/authenticator etc in the
 configuration file.  We also use this for some other short strings, such
 as queue names.
 Also TLS ciphersuite name (no real known limit since the protocols use
-integers, but max seen in reality is 45 octets).
+integers, but max seen so far is 70 octets.  Likely to increase further with
+postquantum methods).
 
 RFC 1413 gives us the 512 limit on IDENT protocol userids.
 */
@@ -168,7 +169,7 @@ RFC 1413 gives us the 512 limit on IDENT protocol userids.
 #define EXIM_HUMANNAME_MAX     256
 #define EXIM_DISPLAYMAIL_MAX  1024
 #define EXIM_DRIVERNAME_MAX     64
-#define EXIM_CIPHERNAME_MAX     64
+#define EXIM_CIPHERNAME_MAX    128
 #define EXIM_IDENTUSER_MAX     512
 
 
@@ -534,16 +535,11 @@ config.h, mytypes.h, and store.h, so we don't need to mention them explicitly.
 #if !defined(MACRO_PREDEF) && !defined(COMPILE_UTILITY)
 # include "hash.h"
 #endif
-#include "globals.h"
-#include "functions.h"
 #if !defined(MACRO_PREDEF) && !defined(COMPILE_UTILITY)
 # include "dbfunctions.h"
 #endif
 #include "osfunctions.h"
 
-#ifdef EXPERIMENTAL_BRIGHTMAIL
-# include "bmi_spam.h"
-#endif
 #if defined(SUPPORT_SPF) || defined(EXPERIMENTAL_SPF_PERL)
 # include "miscmods/spf.h"
 # include "miscmods/spf_api.h"
@@ -552,16 +548,18 @@ config.h, mytypes.h, and store.h, so we don't need to mention them explicitly.
 # include "miscmods/dkim.h"
 # include "miscmods/dkim_api.h"
 #endif
-#ifdef SUPPORT_DMARC
+#if defined(SUPPORT_DMARC) || defined(EXPERIMENTAL_DMARC_NATIVE)
 # include "miscmods/dmarc.h"
 # include "miscmods/dmarc_api.h"
-# include <opendmarc/dmarc.h>
+# ifdef SUPPORT_DMARC
+#  include <opendmarc/dmarc.h>
+# endif
 #endif
 #ifdef EXPERIMENTAL_ARC
 # include "miscmods/arc_api.h"
 #endif
-#ifdef RADIUS_CONFIG_FILE
-# include "miscmods/radius_api.h"
+#ifdef SUPPORT_DSCP
+# include "miscmods/dscp_api.h"
 #endif
 #ifdef SUPPORT_PAM
 # include "miscmods/pam_api.h"
@@ -569,9 +567,24 @@ config.h, mytypes.h, and store.h, so we don't need to mention them explicitly.
 #ifdef EXIM_PERL
 # include "miscmods/perl_api.h"
 #endif
+#ifdef SUPPORT_PROXY
+# include "miscmods/proxy_api.h"
+#endif
+#ifdef SUPPORT_SOCKS
+# include "miscmods/socks_api.h"
+#endif
+#ifdef RADIUS_CONFIG_FILE
+# include "miscmods/radius_api.h"
+#endif
+#ifdef EXPERIMENTAL_XCLIENT
+# include "miscmods/xclient_api.h"
+#endif
 #include "miscmods/exim_filter_api.h"
 #include "miscmods/sieve_filter_api.h"
 
+#include "globals.h"
+#include "functions.h"
+
 /* The following stuff must follow the inclusion of config.h because it
 requires various things that are set therein. */
 
diff --git a/src/src/exim_dbmbuild.c b/src/src/exim_dbmbuild.c
index 02754d0ee..13a1a948d 100644
--- a/src/src/exim_dbmbuild.c
+++ b/src/src/exim_dbmbuild.c
@@ -44,7 +44,7 @@ uschar *
 readconf_printtime(int t)
 { return NULL; }
 const uschar * expand_string_2(const uschar * string, BOOL * textonly_p)
-{return NULL; }
+{ return NULL; }
 void *
 store_get_3(int size, const void * proto_mem, const char *filename, int linenumber)
 { return NULL; }
@@ -69,12 +69,15 @@ string_format_trc(uschar * buf, int len, const uschar * func, unsigned line,
   const char * fmt, ...)
 { return FALSE; }
 void
-log_write(unsigned int selector, int flags, const char *format, ...)
+log_write(int flags, const char *format, ...)
 { }
+const uschar * parse_find_address_end_gen(const uschar * s, BOOL b)
+{return NULL; }
+
 
 
 struct global_flags	f;
-unsigned int		log_selector[1];
+bitmask_word_t		log_selector[1];
 uschar *		queue_name;
 BOOL			split_spool_directory;
 
@@ -95,7 +98,9 @@ const uschar *hex_digits = CUS"0123456789abcdef";
 *   Debug output   *
 *******************/
 
-unsigned int debug_selector = 0;	/* set -1 for debugging */
+bitmask_word_t * debug_selector = {0};	/* set -1 for debugging */
+
+inline BOOL is_debug(const uschar * channels) { return TRUE; }
 
 void
 debug_printf(const char * fmt, ...)
@@ -208,6 +213,9 @@ uschar  dirname[512];
 uschar *buffer = malloc(max_outsize);
 uschar *line = malloc(max_insize);
 
+if (!buffer || !line)
+  { perror("malloc"); exit(EXIT_FAILURE); }
+
 while (argc > 1)
   {
   if      (Ustrcmp(argv[arg], "-nolc") == 0)     lowercase = FALSE;
diff --git a/src/src/exim_dbutil.c b/src/src/exim_dbutil.c
index 339d022bb..d3ae57f34 100644
--- a/src/src/exim_dbutil.c
+++ b/src/src/exim_dbutil.c
@@ -30,31 +30,29 @@ There are a number of common subroutines, followed by three main programs,
 whose inclusion is controlled by -D on the compilation command. */
 
 
+#include <libgen.h>
 #include "exim.h"
 #include "hintsdb.h"
 
 
 /* Identifiers for the different database types. */
 
-#define type_retry     1
-#define type_wait      2
-#define type_misc      3
-#define type_callout   4
-#define type_ratelimit 5
-#define type_tls       6
-#define type_seen      7
-
+enum dbtype { type_retry = 1, type_wait, type_misc, type_callout,
+	      type_ratelimit, type_tls, type_seen, type_dbm
+};
 
 /* This is used by our cut-down dbfn_open(). */
 
-uschar *spool_directory;
+uschar * spool_directory = NULL;
+const uschar * lockfile_name = NULL;
 
+BOOL dbmdb = FALSE;
 BOOL keyonly = FALSE;
 BOOL utc = FALSE;
 
 
 /******************************************************************************/
-      /* dummies needed by Solaris build */
+	/* dummies needed by Solaris build */
 void
 millisleep(int msec)
 {}
@@ -62,24 +60,12 @@ uschar *
 readconf_printtime(int t)
 { return NULL; }
 const uschar * expand_string_2(const uschar * string, BOOL * textonly_p)
-{return NULL; }
-gstring *
-string_catn(gstring * g, const uschar * s, int count)
 { return NULL; }
-gstring *
-string_vformat_trc(gstring * g, const uschar * func, unsigned line,
-  unsigned size_limit, unsigned flags, const char *format, va_list ap)
-{ return NULL; }
-uschar *
-string_sprintf_trc(const char * fmt, const uschar * func, unsigned line, ...)
-{ return NULL; }
-BOOL
-string_format_trc(uschar * buf, int len, const uschar * func, unsigned line,
-  const char * fmt, ...)
-{ return FALSE; }
+const uschar * parse_find_address_end_gen(const uschar * s, BOOL b)
+{return NULL; }
 
 struct global_flags	f;
-unsigned int		log_selector[1];
+bitmask_word_t		log_selector[1];
 uschar *		queue_name;
 BOOL			split_spool_directory;
 
@@ -108,7 +94,15 @@ sigalrm_seen = 1;
 static void
 usage(const uschar * name, const uschar * options)
 {
-printf("Usage: exim_%s%s  <spool-directory> <database-name>\n", name, options);
+uschar * s = store_get(Ustrlen(options), options), * t = s;
+
+if (Ustrchr(options, 'L'))
+  printf("Usage: exim_%s -L <database-path>\n\n", name);
+
+/* drop the L from options */
+do { if (*options != 'L') *t++ = *options; } while (*options++);
+
+printf("Usage: exim_%s%s  <spool-directory> <database-name>\n", name, s);
 printf("  <database-name> = retry | misc | wait-<transport-name> | callout | ratelimit | tls | seen\n");
 exit(EXIT_FAILURE);
 }
@@ -119,7 +113,9 @@ exit(EXIT_FAILURE);
 *   Debug output   *
 *******************/
 
-unsigned int debug_selector = 0;	/* set -1 for debugging */
+bitmask_word_t * debug_selector = {0};	/* set -1 for debugging */
+
+inline BOOL is_debug(const uschar * channels) { return TRUE; }
 
 void
 debug_printf(const char * fmt, ...)
@@ -144,6 +140,8 @@ second of them to be sure it is a known database name. */
 static int
 check_args(int argc, uschar **argv, const uschar * name, const uschar * options)
 {
+if (dbmdb && argc - optind == 1) return type_dbm;
+
 if (argc - optind == 2)
   {
   const uschar * aname = argv[optind + 1];
@@ -170,9 +168,10 @@ opterr = 0;
 while ((opt = getopt(argc, (char * const *)argv, CCS opts)) != -1)
   switch (opt)
   {
+  case 'L':	dbmdb = TRUE; break;
   case 'k':	keyonly = TRUE; break;
   case 'z':	utc = TRUE; break;
-  default:	usage(name, US" [-z] [-k]");
+  default:	usage(name, US" [-kLz]");
   }
 }
 
@@ -189,7 +188,6 @@ from modules such as store.c when things go drastically wrong (e.g. malloc()
 failing). In normal use they won't get obeyed.
 
 Arguments:
-  selector  not relevant when running a utility
   flags     not relevant when running a utility
   format    a printf() format
   ...       arguments for format
@@ -198,7 +196,7 @@ Returns:    nothing
 */
 
 void
-log_write(unsigned int selector, int flags, const char *format, ...)
+log_write(int flags, const char *format, ...)
 {
 va_list ap;
 va_start(ap, format);
@@ -208,7 +206,7 @@ va_end(ap);
 }
 
 void
-log_write_die(unsigned int selector, int flags, const char *format, ...)
+log_write_die(int flags, const char *format, ...)
 {
 va_list ap;
 va_start(ap, format);
@@ -320,29 +318,41 @@ dbfn_open(const uschar * name, int flags, open_db * dbblock,
 {
 int rc;
 struct flock lock_data;
-uschar * dirname, * filename;
+uschar * dname, * 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;
-if no lockfile we infer there is no database and error out.  We open the
+if no lockfile and a create fails, we assume it was the spooldir perms (and
+we have no write perms) and there is no database - and error out.  We open the
 lockfile using the r/w mode requested for the DB, users lacking permission
 for the DB access mode will error out here. */
 
-if (  asprintf(CSS &dirname, "%s/db", spool_directory) < 0
-   || asprintf(CSS &filename, "%s/%s.lockfile", dirname, name) < 0)
-  return NULL;
+if (dbmdb)
+  {
+  filename = string_sprintf("%s.lockfile", name);
+  dname = Ustrchr(name, '/')		/* path has dirs */
+    ? US dirname(CS string_copy(name))
+    : US ".";
+  }
+else
+  {
+  dname = string_sprintf("%s/db", spool_directory);
+  filename = string_sprintf("%s/%s.lockfile", dname, name);
+  }
 
-dbblock->readonly = (flags & (O_WRONLY|O_RDWR)) == O_RDONLY;
+dbblock->readonly = (flags & O_ACCMODE) == O_RDONLY;
 dbblock->lockfd = -1;
 if (exim_lockfile_needed())
   {
-  if ((dbblock->lockfd = Uopen(filename, flags, 0)) < 0)
+  if ((dbblock->lockfd = Uopen(filename, dbmdb ? flags | O_CREAT : flags,
+			      0600)) < 0)
     {
     printf("** Failed to open database lock file %s: %s\n", filename,
       strerror(errno));
     return NULL;
     }
+  lockfile_name = string_copy(filename);
 
   /* Now we must get a lock on the opened lock file; do this with a blocking
   lock that times out. */
@@ -364,6 +374,7 @@ if (exim_lockfile_needed())
       filename,
       errno == ETIMEDOUT ? "timed out" : strerror(errno));
     (void)close(dbblock->lockfd);
+    unlink(CS filename);
     return NULL;
     }
 
@@ -371,11 +382,12 @@ if (exim_lockfile_needed())
   exclusive access to the database, so we can go ahead and open it. */
   }
 
-if (asprintf(CSS &filename, "%s/%s", dirname, name) < 0) return NULL;
+/* Drop the ".lockfile" off the name */
+*Ustrrchr(filename, '.') = '\0';
 
 if (!(dbblock->dbptr = dbblock->readonly && !exim_lockfile_needed()
-		      ? exim_dbopen_multi(filename, dirname, flags, 0)
-		      : exim_dbopen(filename, dirname, flags, 0)))
+		      ? exim_dbopen_multi(filename, dname, flags, 0)
+		      : exim_dbopen(filename, dname, flags, 0)))
   {
   printf("** Failed to open hintsdb file %s for %s: %s%s\n", filename,
     dbblock->readonly ? "reading" : "writing", strerror(errno),
@@ -386,6 +398,7 @@ if (!(dbblock->dbptr = dbblock->readonly && !exim_lockfile_needed()
 #endif
     );
   if (dbblock->lockfd >= 0) (void)close(dbblock->lockfd);
+  unlink(CCS lockfile_name);
   return NULL;
   }
 
@@ -415,7 +428,9 @@ else
   exim_dbclose(dbp->dbptr);
 
 if (dbp->lockfd >= 0)
-  (void) close(dbp->lockfd);
+  { (void) close(dbp->lockfd); dbp->lockfd = -1; }
+unlink(CCS lockfile_name);
+lockfile_name = NULL;
 }
 
 
@@ -435,7 +450,7 @@ Arguments:
   length    where to put the length (or NULL if length not wanted). Includes overhead.
 
 Returns: a pointer to the retrieved record, or
-         NULL if the record is not found
+         NULL if the record is not found or bad
 */
 
 void *
@@ -446,6 +461,7 @@ EXIM_DATUM key_datum, result_datum;
 int klen = Ustrlen(key) + 1;
 uschar * key_copy = store_get(klen, key);
 unsigned dlen;
+BOOL tainted;
 
 memcpy(key_copy, key, klen);
 
@@ -454,18 +470,42 @@ exim_datum_init(&result_datum);      /* to be cleared before use. */
 exim_datum_data_set(&key_datum, key_copy);
 exim_datum_size_set(&key_datum, klen);
 
-if (!exim_dbget(dbblock->dbptr, &key_datum, &result_datum)) return NULL;
-
-/* Assume for now that anything stored could have been tainted. Properly
-we should store the taint status along with the data. */
+if (!exim_dbget(dbblock->dbptr, &key_datum, &result_datum))
+  return NULL;
 
 dlen = exim_datum_size_get(&result_datum);
-yield = store_get(dlen, GET_TAINTED);
-memcpy(yield, exim_datum_data_get(&result_datum), dlen);
-DEBUG(D_hints_lookup) debug_printf_indent("dbfn_read: size %u return\n", dlen);
+DEBUG(hints_lookup) debug_printf_indent("dbfn_read: size %u return\n", dlen);
 if (length) *length = dlen;
 
-exim_datum_free(&result_datum);    /* Some DBM libs require freeing */
+/* Hintsdb uses store the taint of the payload of the value in the value.
+For non-hints uses we have no provenance, so assume untainted */
+
+if (dbmdb)
+  tainted = FALSE;
+else
+  {
+  dbdata_generic * gp = (dbdata_generic *) exim_datum_data_get(&result_datum);
+  if (dlen < sizeof(dbdata_generic))
+    {
+    DEBUG(hints_lookup)
+      debug_printf_indent("dbfn_read: bad record size %u\n", dlen);
+    return NULL;
+    }
+  if (gp->version != HINTS_VERSION)
+    {
+    DEBUG(hints_lookup)
+      debug_printf_indent("dbfn_read: bad record version %u; deleting\n",
+			    gp->version);
+    return NULL;
+    }
+
+  tainted = gp->tainted;
+  }
+
+yield = store_get(dlen, tainted ? GET_TAINTED : GET_UNTAINTED);
+memcpy(yield, exim_datum_data_get(&result_datum), dlen);
+exim_datum_free(&result_datum);		/* Some DBM libs require freeing */
+
 return yield;
 }
 
@@ -489,15 +529,13 @@ Returns:    the yield of the underlying dbm or db "write" function. If this
 */
 
 int
-dbfn_write(open_db *dbblock, const uschar *key, void *ptr, int length)
+dbfn_write(open_db * dbblock, const uschar * key, void * ptr, int length)
 {
 EXIM_DATUM key_datum, value_datum;
-dbdata_generic *gptr = (dbdata_generic *)ptr;
 int klen = Ustrlen(key) + 1;
 uschar * key_copy = store_get(klen, key);
 
 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. */
@@ -588,26 +626,33 @@ return yield;
 *************************************************/
 
 int
-main(int argc, char **cargv)
+main(int argc, char ** cargv)
 {
-int dbdata_type = 0;
-int yield = 0;
+int dbdata_type = 0, yield = 0;
 open_db dbblock;
-open_db *dbm;
-EXIM_CURSOR *cursor;
-uschar **argv = USS cargv;
+open_db * dbm;
+EXIM_CURSOR * cursor;
+uschar ** argv = USS cargv;
+const uschar * file;
 uschar keybuffer[1024];
 
 store_init();
-options(argc, argv, US"dumpdb", US"kz");
+options(argc, argv, US"dumpdb", US"kLz");
 
 /* Check the arguments, and open the database */
 
-dbdata_type = check_args(argc, argv, US"dumpdb", US" [-z] [-k]");
+dbdata_type = check_args(argc, argv, US"dumpdb", US" [-kLz]");
 argc -= optind; argv += optind;
-spool_directory = argv[0];
 
-if (!(dbm = dbfn_open(argv[1], O_RDONLY, &dbblock, FALSE, TRUE)))
+if (dbmdb)
+  file = argv[0];
+else
+  {
+  spool_directory = argv[0];
+  file = argv[1];
+  }
+
+if (!(dbm = dbfn_open(file, O_RDONLY, &dbblock, FALSE, TRUE)))
   exit(EXIT_FAILURE);
 
 /* Scan the file, formatting the information for each entry. Note
@@ -700,13 +745,47 @@ for (uschar * key = dbfn_scan(dbm, TRUE, &cursor);
 	  printf("%s ", name);
 	  t += MESSAGE_ID_LENGTH;
 	  }
-	printf("\n");
+	putchar('\n');
 	break;
 
       case type_misc:
-	printf("%s %s\n", print_time(((dbdata_generic *)value)->time_stamp),
-	  keybuffer);
+	{
+	dbdata_generic * recp = (dbdata_generic *)value;
+	uschar * dp = US (recp + 1);
+	int dlen = length = sizeof(dbdata_generic);
+
+	printf("%s %s\n", print_time(recp->time_stamp), keybuffer);
+	if (  Ustrncmp(keybuffer, "etrn-", 5) == 0
+	   || Ustrncmp(keybuffer, "tpt-serialize-", 14) == 0
+	   || Ustrncmp(keybuffer, "host-serialize-", 15) == 0
+	   )
+	  printf("serialize %.*s: %d running\n",
+		  (int)(Ustrchr(keybuffer, '-') - keybuffer), keybuffer,
+		  ((dbdata_serialize *)recp)->count);
+	else
+	  {
+	  uschar * s = keybuffer + Ustrlen(keybuffer) - 5;
+#ifndef DISABLE_PIPE_CONNECT
+	  if (s > keybuffer && Ustrcmp(s, ".EHLO") == 0)
+	    {
+	    ehlo_resp_precis * erp = &((dbdata_ehlo_resp *)recp)->data;
+
+	    printf("helo/ehlo %.*s clr %04x/%04x cry %04x/%04x",
+		    (int)(s - keybuffer), keybuffer,
+		    erp->cleartext_features, erp->cleartext_auths,
+		    erp->crypted_features,   erp->crypted_auths);
+
+	    if (sizeof(*erp) > 4 * sizeof(unsigned short))
+	      printf(" lim %05d/%05d/%05d",
+		erp->limit_mail, erp->limit_rcpt, erp->limit_rcptdom);
+	    putchar('\n');
+	    }
+	  else
+#endif
+	    printf("%s\n", string_sprintf("%.*q\n", dlen, dp));
+	  }
 	break;
+	}
 
       case type_callout:
 	callout = (dbdata_callout_cache *)value;
@@ -716,7 +795,7 @@ for (uschar * key = dbfn_scan(dbm, TRUE, &cursor);
 	if (length == sizeof(dbdata_callout_cache_address))
 	  {
 	  printf("%s %s callout=%s\n",
-	    print_time(((dbdata_generic *)value)->time_stamp),
+	    print_time(callout->gen.time_stamp),
 	    keybuffer,
 	    print_cache(callout->result));
 	  }
@@ -726,7 +805,7 @@ for (uschar * key = dbfn_scan(dbm, TRUE, &cursor);
 	else if (length == sizeof(dbdata_callout_cache))
 	  {
 	  printf("%s %s callout=%s postmaster=%s",
-	    print_time(((dbdata_generic *)value)->time_stamp),
+	    print_time(callout->gen.time_stamp),
 	    keybuffer,
 	    print_cache(callout->result),
 	    print_cache(callout->postmaster_result));
@@ -746,7 +825,7 @@ for (uschar * key = dbfn_scan(dbm, TRUE, &cursor);
 	  ratelimit = (dbdata_ratelimit *)value;
 	  rate_unique = (dbdata_ratelimit_unique *)value;
 	  printf("%s.%06d rate: %10.3f epoch: %s size: %u key: %s\n",
-	    print_time(ratelimit->time_stamp),
+	    print_time(ratelimit->gen.time_stamp),
 	    ratelimit->time_usec, ratelimit->rate,
 	    print_time(rate_unique->bloom_epoch), rate_unique->bloom_size,
 	    keybuffer);
@@ -755,7 +834,7 @@ for (uschar * key = dbfn_scan(dbm, TRUE, &cursor);
 	  {
 	  ratelimit = (dbdata_ratelimit *)value;
 	  printf("%s.%06d rate: %10.3f key: %s\n",
-	    print_time(ratelimit->time_stamp),
+	    print_time(ratelimit->gen.time_stamp),
 	    ratelimit->time_usec, ratelimit->rate,
 	    keybuffer);
 	  }
@@ -768,8 +847,11 @@ for (uschar * key = dbfn_scan(dbm, TRUE, &cursor);
 
       case type_seen:
 	seen = (dbdata_seen *)value;
-	printf("%s\t%s\n", keybuffer, print_time(seen->time_stamp));
+	printf("%s\t%s\n", keybuffer, print_time(seen->gen.time_stamp));
 	break;
+
+      case type_dbm:
+	printf("%s\t%.*s\n", keybuffer, length, CS value);
       }
   store_reset(reset_point);
   }
@@ -822,22 +904,30 @@ int dbdata_type;
 uschar **argv = USS cargv;
 uschar buffer[256];
 uschar name[256];
+const uschar * file;
 rmark reset_point;
-uschar * aname;
 
 store_init();
-options(argc, argv, US"fixdb", US"z");
-name[0] = 0;  /* No name set */
+options(argc, argv, US"fixdb", US"Lz");
+*name = '\0';  /* No name set */
 
 /* Sort out the database type, verify what we are working on and then process
 user requests */
 
-dbdata_type = check_args(argc, argv, US"fixdb", US" [-z]");
+dbdata_type = check_args(argc, argv, US"fixdb", US" [-Lz]");
 argc -= optind; argv += optind;
-spool_directory = argv[0];
-aname = argv[1];
 
-printf("Modifying Exim hints database %s/db/%s\n", spool_directory, aname);
+if (dbmdb)
+  {
+  file = argv[0];
+  printf("Modifying Exim dbm database %s\n", file);
+  }
+else
+  {
+  spool_directory = argv[0];
+  file = argv[1];
+  printf("Modifying Exim hints database %s/db/%s\n", spool_directory, file);
+  }
 
 for(; (reset_point = store_mark()); store_reset(reset_point))
   {
@@ -850,9 +940,8 @@ for(; (reset_point = store_mark()); store_reset(reset_point))
   dbdata_ratelimit *ratelimit;
   dbdata_ratelimit_unique *rate_unique;
   dbdata_tls_session *session;
-  int oldlength;
-  uschar *t;
-  uschar field[256], value[256];
+  int oldlength, newlength;
+  uschar * t, field[256], value[256];
 
   printf("> ");
   if (Ufgets(buffer, 256, stdin) == NULL) break;
@@ -866,7 +955,7 @@ for(; (reset_point = store_mark()); store_reset(reset_point))
   if ((isdigit((uschar)buffer[0]) && (buffer[1] == ' ' || buffer[1] == '\0'))
        || Ustrcmp(buffer, "d") == 0)
     {
-    if (name[0] == 0)
+    if (!*name)
       {
       printf("No previous record name is set\n");
       continue;
@@ -875,167 +964,181 @@ for(; (reset_point = store_mark()); store_reset(reset_point))
     }
   else
     {
-    name[0] = 0;
+    *name = '\0';
     (void)sscanf(CS buffer, "%s %s %s", name, field, value);
     }
 
   /* Handle an update request */
 
-  if (field[0] != 0)
+  if (*field)
     {
     int verify = 1;
 
-    if (!(dbm = dbfn_open(aname, O_RDWR|O_CREAT, &dbblock, FALSE, TRUE)))
+    if (!(dbm = dbfn_open(file, O_RDWR|O_CREAT, &dbblock, FALSE, TRUE)))
       continue;
 
     if (Ustrcmp(field, "d") == 0)
       {
-      if (value[0] != 0) printf("unexpected value after \"d\"\n");
-        else printf("%s\n", (dbfn_delete(dbm, name) < 0)?
-          "not found" : "deleted");
+      if (*value)
+	printf("unexpected value after \"d\"\n");
+      else
+	printf("%s\n", dbfn_delete(dbm, name) < 0 ?  "not found" : "deleted");
       dbfn_close(dbm);
       continue;
       }
 
-    else if (isdigit((uschar)field[0]))
+    else if (dbdata_type == type_dbm || isdigit((uschar)field[0]))
       {
-      int fieldno = Uatoi(field);
-      if (value[0] == 0)
+      if (!*value)
         {
         printf("value missing\n");
         dbfn_close(dbm);
         continue;
         }
+      else if (!(record = dbfn_read_with_length(dbm, name, &oldlength)))
+	printf("not found\n");
       else
-        {
-        record = dbfn_read_with_length(dbm, name, &oldlength);
-        if (record == NULL) printf("not found\n"); else
-          {
-          time_t tt;
-          /*int length = 0;      Stops compiler warning */
+	{
+	int fieldno = Uatoi(field);
+	time_t tt;
+	/*int length = 0;      Stops compiler warning */
 
-          switch(dbdata_type)
-            {
-            case type_retry:
-	      retry = (dbdata_retry *)record;
-	      /* length = sizeof(dbdata_retry) + Ustrlen(retry->text); */
+	newlength = oldlength;
+	switch(dbdata_type)
+	  {
+	  case type_retry:
+	    retry = (dbdata_retry *)record;
+	    /* length = sizeof(dbdata_retry) + Ustrlen(retry->text); */
+
+	    switch(fieldno)
+	      {
+	      case 0: retry->basic_errno = Uatoi(value);
+		      break;
+	      case 1: retry->more_errno = Uatoi(value);
+		      break;
+	      case 2: if ((tt = read_time(value)) > 0) retry->first_failed = tt;
+		      else printf("bad time value\n");
+		      break;
+	      case 3: if ((tt = read_time(value)) > 0) retry->last_try = tt;
+		      else printf("bad time value\n");
+		      break;
+	      case 4: if ((tt = read_time(value)) > 0) retry->next_try = tt;
+		      else printf("bad time value\n");
+		      break;
+	      case 5: if (Ustrcmp(value, "yes") == 0) retry->expired = TRUE;
+		      else if (Ustrcmp(value, "no") == 0) retry->expired = FALSE;
+		      else printf("\"yes\" or \"no\" expected=n");
+		      break;
+	      default: printf("unknown field number\n");
+		       verify = 0;
+		       break;
+	      }
+	    break;
 
-	      switch(fieldno)
-		{
-		case 0: retry->basic_errno = Uatoi(value);
-			break;
-		case 1: retry->more_errno = Uatoi(value);
-			break;
-		case 2: if ((tt = read_time(value)) > 0) retry->first_failed = tt;
-			else printf("bad time value\n");
-			break;
-		case 3: if ((tt = read_time(value)) > 0) retry->last_try = tt;
-			else printf("bad time value\n");
-			break;
-		case 4: if ((tt = read_time(value)) > 0) retry->next_try = tt;
-			else printf("bad time value\n");
-			break;
-		case 5: if (Ustrcmp(value, "yes") == 0) retry->expired = TRUE;
-			else if (Ustrcmp(value, "no") == 0) retry->expired = FALSE;
-			else printf("\"yes\" or \"no\" expected=n");
-			break;
-		default: printf("unknown field number\n");
-			 verify = 0;
-			 break;
-		}
-	      break;
+	  case type_wait:
+	    printf("Can't change contents of wait database record\n");
+	    break;
 
-            case type_wait:
-	      printf("Can't change contents of wait database record\n");
-	      break;
+	  case type_misc:
+	    printf("Can't change contents of misc database record\n");
+	    break;
 
-            case type_misc:
-	      printf("Can't change contents of misc database record\n");
+	  case type_callout:
+	    callout = (dbdata_callout_cache *)record;
+	    /* length = sizeof(dbdata_callout_cache); */
+	    switch(fieldno)
+	      {
+	      case 0: callout->result = Uatoi(value);
+		      break;
+	      case 1: callout->postmaster_result = Uatoi(value);
+		      break;
+	      case 2: callout->random_result = Uatoi(value);
+		      break;
+	      default: printf("unknown field number\n");
+		       verify = 0;
+		       break;
+	      }
 	      break;
 
-            case type_callout:
-	      callout = (dbdata_callout_cache *)record;
-	      /* length = sizeof(dbdata_callout_cache); */
-	      switch(fieldno)
-		{
-		case 0: callout->result = Uatoi(value);
-			break;
-		case 1: callout->postmaster_result = Uatoi(value);
-			break;
-		case 2: callout->random_result = Uatoi(value);
-			break;
-		default: printf("unknown field number\n");
-			 verify = 0;
-			 break;
-		}
-		break;
-
-            case type_ratelimit:
-	      ratelimit = (dbdata_ratelimit *)record;
-	      switch(fieldno)
-		{
-		case 0: if ((tt = read_time(value)) > 0) ratelimit->time_stamp = tt;
-			else printf("bad time value\n");
-			break;
-		case 1: ratelimit->time_usec = Uatoi(value);
-			break;
-		case 2: ratelimit->rate = Ustrtod(value, NULL);
+	  case type_ratelimit:
+	    ratelimit = (dbdata_ratelimit *)record;
+	    switch(fieldno)
+	      {
+	      case 0: if ((tt = read_time(value)) > 0)
+			ratelimit->gen.time_stamp = tt;
+		      else
+			printf("bad time value\n");
+		      break;
+	      case 1: ratelimit->time_usec = Uatoi(value);
+		      break;
+	      case 2: ratelimit->rate = Ustrtod(value, NULL);
+		      break;
+	      case 3: if (Ustrstr(name, "/unique/") != NULL
+			  && oldlength >= sizeof(dbdata_ratelimit_unique))
+			{
+			rate_unique = (dbdata_ratelimit_unique *)record;
+			if ((tt = read_time(value)) > 0) rate_unique->bloom_epoch = tt;
+			  else printf("bad time value\n");
 			break;
-		case 3: if (Ustrstr(name, "/unique/") != NULL
-			    && oldlength >= sizeof(dbdata_ratelimit_unique))
+			}
+		      /* else fall through */
+	      case 4:
+	      case 5: if (Ustrstr(name, "/unique/") != NULL
+			  && oldlength >= sizeof(dbdata_ratelimit_unique))
+			{
+			/* see acl.c */
+			BOOL seen;
+			unsigned hash, hinc;
+			uschar md5sum[16];
+			md5 md5info;
+			md5_start(&md5info);
+			md5_end(&md5info, value, Ustrlen(value), md5sum);
+			hash = md5sum[0] <<  0 | md5sum[1] <<  8
+			     | md5sum[2] << 16 | md5sum[3] << 24;
+			hinc = md5sum[4] <<  0 | md5sum[5] <<  8
+			     | md5sum[6] << 16 | md5sum[7] << 24;
+			rate_unique = (dbdata_ratelimit_unique *)record;
+			seen = TRUE;
+			for (unsigned n = 0; n < 8; n++, hash += hinc)
 			  {
-			  rate_unique = (dbdata_ratelimit_unique *)record;
-			  if ((tt = read_time(value)) > 0) rate_unique->bloom_epoch = tt;
-			    else printf("bad time value\n");
-			  break;
-			  }
-			/* else fall through */
-		case 4:
-		case 5: if (Ustrstr(name, "/unique/") != NULL
-			    && oldlength >= sizeof(dbdata_ratelimit_unique))
-			  {
-			  /* see acl.c */
-			  BOOL seen;
-			  unsigned hash, hinc;
-			  uschar md5sum[16];
-			  md5 md5info;
-			  md5_start(&md5info);
-			  md5_end(&md5info, value, Ustrlen(value), md5sum);
-			  hash = md5sum[0] <<  0 | md5sum[1] <<  8
-			       | md5sum[2] << 16 | md5sum[3] << 24;
-			  hinc = md5sum[4] <<  0 | md5sum[5] <<  8
-			       | md5sum[6] << 16 | md5sum[7] << 24;
-			  rate_unique = (dbdata_ratelimit_unique *)record;
-			  seen = TRUE;
-			  for (unsigned n = 0; n < 8; n++, hash += hinc)
+			  int bit = 1 << (hash % 8);
+			  int byte = (hash / 8) % rate_unique->bloom_size;
+			  if ((rate_unique->bloom[byte] & bit) == 0)
 			    {
-			    int bit = 1 << (hash % 8);
-			    int byte = (hash / 8) % rate_unique->bloom_size;
-			    if ((rate_unique->bloom[byte] & bit) == 0)
-			      {
-			      seen = FALSE;
-			      if (fieldno == 5) rate_unique->bloom[byte] |= bit;
-			      }
+			    seen = FALSE;
+			    if (fieldno == 5) rate_unique->bloom[byte] |= bit;
 			    }
-			  printf("%s %s\n",
-			    seen ? "seen" : fieldno == 5 ? "added" : "unseen", value);
-			  break;
 			  }
-			/* else fall through */
-		default: printf("unknown field number\n");
-			 verify = 0;
-			 break;
-		}
-	      break;
+			printf("%s %s\n",
+			  seen ? "seen" : fieldno == 5 ? "added" : "unseen", value);
+			break;
+			}
+		      /* else fall through */
+	      default: printf("unknown field number\n");
+		       verify = 0;
+		       break;
+	      }
+	    break;
 
-            case type_tls:
-	      printf("Can't change contents of tls database record\n");
-	      break;
-            }
+	  case type_tls:
+	    printf("Can't change contents of tls database record\n");
+	    break;
 
-          dbfn_write(dbm, name, record, oldlength);
-          }
-        }
+	  case type_dbm:
+	    record = value;
+	    newlength = Ustrlen(value);
+	    break;
+	  }
+
+	if (dbdata_type != type_dbm)
+	  {
+	  dbdata_generic * p = (dbdata_generic *)record;
+	  p->time_stamp = time(NULL);
+	  p->tainted = FALSE;
+	  }
+
+	dbfn_write(dbm, name, record, newlength);
+	}
       }
 
     else
@@ -1054,18 +1157,21 @@ for(; (reset_point = store_mark()); store_reset(reset_point))
 
   /* Handle a read request, or verify after an update. */
 
-  if (!(dbm = dbfn_open(aname, O_RDONLY, &dbblock, FALSE, TRUE)))
+  if (!(dbm = dbfn_open(file, O_RDONLY, &dbblock, FALSE, TRUE)))
     continue;
 
   if (!(record = dbfn_read_with_length(dbm, name, &oldlength)))
     {
     printf("record %s not found\n", name);
-    name[0] = 0;
+    *name = '\0';
     }
   else
     {
     int count_bad = 0;
-    printf("%s\n", CS print_time(((dbdata_generic *)record)->time_stamp));
+
+    if (dbdata_type != type_dbm)
+      printf("%s\n", CS print_time(((dbdata_generic *)record)->time_stamp));
+
     switch(dbdata_type)
       {
       case type_retry:
@@ -1075,7 +1181,7 @@ for(; (reset_point = store_mark()); store_reset(reset_point))
 	printf("2 first failed: %s\n", print_time(retry->first_failed));
 	printf("3 last try:     %s\n", print_time(retry->last_try));
 	printf("4 next try:     %s\n", print_time(retry->next_try));
-	printf("5 expired:      %s\n", (retry->expired)? "yes" : "no");
+	printf("5 expired:      %s\n", retry->expired ? "yes" : "no");
 	break;
 
       case type_wait:
@@ -1129,7 +1235,7 @@ for(; (reset_point = store_mark()); store_reset(reset_point))
 
       case type_ratelimit:
 	ratelimit = (dbdata_ratelimit *)record;
-	printf("0 time stamp:  %s\n", print_time(ratelimit->time_stamp));
+	printf("0 time stamp:  %s\n", print_time(ratelimit->gen.time_stamp));
 	printf("1 fract. time: .%06d\n", ratelimit->time_usec);
 	printf("2 sender rate: % .3f\n", ratelimit->rate);
 	if (Ustrstr(name, "/unique/") != NULL
@@ -1143,10 +1249,13 @@ for(; (reset_point = store_mark()); store_reset(reset_point))
 	break;
 
       case type_tls:
-	session = (dbdata_tls_session *)value;
-	printf("0 time stamp:  %s\n", print_time(session->time_stamp));
+	session = (dbdata_tls_session *)record;
+	printf("0 time stamp:  %s\n", print_time(session->gen.time_stamp));
 	printf("1 session: .%s\n", session->session);
 	break;
+
+      case type_dbm:
+	printf("0 value:  %.*s\n", oldlength, CS record);
       }
     }
 
@@ -1277,16 +1386,26 @@ the store each time round. */
 
 for (; keychain && (reset_point = store_mark()); store_reset(reset_point))
   {
-  dbdata_generic *value;
+  dbdata_generic * value;
+  int size = 0;
 
   key = keychain->key;
   keychain = keychain->next;
-  value = dbfn_read_with_length(dbm, key, NULL);
+  value = dbfn_read_with_length(dbm, key, &size);
 
   /* A continuation record may have been deleted or renamed already, so
-  non-existence is not serious. */
+  non-existence is not serious. Also, for a back-version record we get
+  a null value - so attempt a delete. */
 
-  if (!value) continue;
+  if (!value)
+    {
+    if (size)
+      {
+      printf(" deleted %s (bad record version)\n", key);
+      dbfn_delete(dbm, key);
+      }
+    continue;
+    }
 
   /* Delete if too old */
 
@@ -1307,10 +1426,10 @@ for (; keychain && (reset_point = store_mark()); store_reset(reset_point))
 
     /* Leave corrupt records alone */
 
-    if (wait->time_stamp > time(NULL))
+    if (wait->gen.time_stamp > time(NULL))
       {
       printf("**** Data for '%s' corrupted\n  time in future: %s\n",
-        key, print_time(((dbdata_generic *)value)->time_stamp));
+        key, print_time(wait->gen.time_stamp));
       continue;
       }
     if (wait->count > WAIT_NAME_MAX)
@@ -1328,7 +1447,7 @@ for (; keychain && (reset_point = store_mark()); store_reset(reset_point))
 
     /* Record over 1 year old; just remove it */
 
-    if (wait->time_stamp < time(NULL) - 365*24*60*60)
+    if (wait->gen.time_stamp < time(NULL) - 365*24*60*60)
       {
       dbfn_delete(dbm, key);
       printf("deleted %s (too old)\n", key);
@@ -1413,6 +1532,8 @@ for (; keychain && (reset_point = store_mark()); store_reset(reset_point))
     if (update)
       {
       printf("updated %s\n", key);
+      wait->gen.time_stamp = time(NULL);
+      /* leave the taint marker unchanged */
       dbfn_write(dbm, key, wait, sizeof(dbdata_wait) +
         wait->count * MESSAGE_ID_LENGTH);
       }
diff --git a/src/src/exim_lock.c b/src/src/exim_lock.c
index f42da23a8..47c87bafb 100644
--- a/src/src/exim_lock.c
+++ b/src/src/exim_lock.c
@@ -299,13 +299,15 @@ if (use_lockfile)
   primary_hostname = s.nodename;
 
   len = (int)strlen(filename);
-  lockname = malloc(len + 8);
+  if (  !(lockname = malloc(len + 8))
+     || !(hitchname = malloc(len + 32 + (int)strlen(primary_hostname))))
+    { perror("malloc"); exit(EXIT_FAILURE); }
+
   sprintf(lockname, "%s.lock", filename);
-  hitchname = malloc(len + 32 + (int)strlen(primary_hostname));
 
   /* Presumably, this must match appendfile.c */
   sprintf(hitchname, "%s.%s.%08x.%08x", lockname, primary_hostname,
-    (unsigned int)now, (unsigned int)getpid());
+    (unsigned int)now, (unsigned long)getpid());
 
   if (verbose)
     printf("exim_lock: lockname =  %s\n           hitchname = %s\n", lockname,
diff --git a/src/src/expand.c b/src/src/expand.c
index 98a8aac18..25664d992 100644
--- a/src/src/expand.c
+++ b/src/src/expand.c
@@ -362,7 +362,6 @@ static uschar *cond_table[] = {
   US"match_local_part",
   US"or",
   US"pam",
-  US"pwcheck",
   US"queue_running",
   US"radius",
   US"saslauthd"
@@ -415,7 +414,6 @@ enum {
   ECOND_MATCH_LOCAL_PART,
   ECOND_OR,
   ECOND_PAM,
-  ECOND_PWCHECK,
   ECOND_QUEUE_RUNNING,
   ECOND_RADIUS,
   ECOND_SASLAUTHD
@@ -461,10 +459,10 @@ static var_entry var_table[] = {
   { "address_file",        vtype_stringptr,   &address_file },
   { "address_pipe",        vtype_stringptr,   &address_pipe },
 #ifdef EXPERIMENTAL_ARC
-  { "arc_domains",         vtype_module,	US"arc" },
-  { "arc_oldest_pass",     vtype_module,	US"arc" },
-  { "arc_state",           vtype_module,	US"arc" },
-  { "arc_state_reason",    vtype_module,	US"arc" },
+  { "arc_domains",         vtype_misc_module,	US"arc" },
+  { "arc_oldest_pass",     vtype_misc_module,	US"arc" },
+  { "arc_state",           vtype_misc_module,	US"arc" },
+  { "arc_state_reason",    vtype_misc_module,	US"arc" },
 #endif
   { "atrn_host",	   vtype_stringptr,   &atrn_host },
   { "atrn_mode",	   vtype_stringptr,   &atrn_mode },
@@ -474,12 +472,6 @@ static var_entry var_table[] = {
   { "authentication_failed",vtype_int,        &authentication_failed },
 #ifdef WITH_CONTENT_SCAN
   { "av_failed",           vtype_int,         &av_failed },
-#endif
-#ifdef EXPERIMENTAL_BRIGHTMAIL
-  { "bmi_alt_location",    vtype_stringptr,   &bmi_alt_location },
-  { "bmi_base64_tracker_verdict", vtype_stringptr, &bmi_base64_tracker_verdict },
-  { "bmi_base64_verdict",  vtype_stringptr,   &bmi_base64_verdict },
-  { "bmi_deliver",         vtype_int,         &bmi_deliver },
 #endif
   { "body_linecount",      vtype_int,         &body_linecount },
   { "body_zerocount",      vtype_int,         &body_zerocount },
@@ -499,36 +491,36 @@ static var_entry var_table[] = {
   { "dcc_result",          vtype_stringptr,   &dcc_result },
 #endif
 #ifndef DISABLE_DKIM
-  { "dkim_algo",           vtype_module,	US"dkim" },
-  { "dkim_bodylength",     vtype_module,	US"dkim" },
-  { "dkim_canon_body",     vtype_module,	US"dkim" },
-  { "dkim_canon_headers",  vtype_module,	US"dkim" },
-  { "dkim_copiedheaders",  vtype_module,	US"dkim" },
-  { "dkim_created",        vtype_module,	US"dkim" },
-  { "dkim_cur_signer",     vtype_module,	US"dkim" },
-  { "dkim_domain",         vtype_module,	US"dkim" },
-  { "dkim_expires",        vtype_module,	US"dkim" },
-  { "dkim_headernames",    vtype_module,	US"dkim" },
-  { "dkim_identity",       vtype_module,	US"dkim" },
-  { "dkim_key_granularity",vtype_module,	US"dkim" },
-  { "dkim_key_length",     vtype_module,	US"dkim" },
-  { "dkim_key_nosubdomains",vtype_module,	US"dkim" },
-  { "dkim_key_notes",      vtype_module,	US"dkim" },
-  { "dkim_key_srvtype",    vtype_module,	US"dkim" },
-  { "dkim_key_testing",    vtype_module,	US"dkim" },
-  { "dkim_selector",       vtype_module,	US"dkim" },
-  { "dkim_signers",        vtype_module,	US"dkim" },
-  { "dkim_verify_reason",  vtype_module,	US"dkim" },
-  { "dkim_verify_signers", vtype_module,	US"dkim" },
-  { "dkim_verify_status",  vtype_module,	US"dkim" },
+  { "dkim_algo",           vtype_misc_module,	US"dkim" },
+  { "dkim_bodylength",     vtype_misc_module,	US"dkim" },
+  { "dkim_canon_body",     vtype_misc_module,	US"dkim" },
+  { "dkim_canon_headers",  vtype_misc_module,	US"dkim" },
+  { "dkim_copiedheaders",  vtype_misc_module,	US"dkim" },
+  { "dkim_created",        vtype_misc_module,	US"dkim" },
+  { "dkim_cur_signer",     vtype_misc_module,	US"dkim" },
+  { "dkim_domain",         vtype_misc_module,	US"dkim" },
+  { "dkim_expires",        vtype_misc_module,	US"dkim" },
+  { "dkim_headernames",    vtype_misc_module,	US"dkim" },
+  { "dkim_identity",       vtype_misc_module,	US"dkim" },
+  { "dkim_key_granularity",vtype_misc_module,	US"dkim" },
+  { "dkim_key_length",     vtype_misc_module,	US"dkim" },
+  { "dkim_key_nosubdomains",vtype_misc_module,	US"dkim" },
+  { "dkim_key_notes",      vtype_misc_module,	US"dkim" },
+  { "dkim_key_srvtype",    vtype_misc_module,	US"dkim" },
+  { "dkim_key_testing",    vtype_misc_module,	US"dkim" },
+  { "dkim_selector",       vtype_misc_module,	US"dkim" },
+  { "dkim_signers",        vtype_misc_module,	US"dkim" },
+  { "dkim_verify_reason",  vtype_misc_module,	US"dkim" },
+  { "dkim_verify_signers", vtype_misc_module,	US"dkim" },
+  { "dkim_verify_status",  vtype_misc_module,	US"dkim" },
 #endif
-#ifdef SUPPORT_DMARC
-  { "dmarc_alignment_dkim",vtype_module,	US"dmarc" },
-  { "dmarc_alignment_spf", vtype_module,	US"dmarc" },
-  { "dmarc_domain_policy", vtype_module,	US"dmarc" },
-  { "dmarc_status",        vtype_module,	US"dmarc" },
-  { "dmarc_status_text",   vtype_module,	US"dmarc" },
-  { "dmarc_used_domain",   vtype_module,	US"dmarc" },
+#ifdef EXIM_HAVE_DMARC
+  { "dmarc_alignment_dkim",vtype_misc_module,	US"dmarc" },
+  { "dmarc_alignment_spf", vtype_misc_module,	US"dmarc" },
+  { "dmarc_domain_policy", vtype_misc_module,	US"dmarc" },
+  { "dmarc_status",        vtype_misc_module,	US"dmarc" },
+  { "dmarc_status_text",   vtype_misc_module,	US"dmarc" },
+  { "dmarc_used_domain",   vtype_misc_module,	US"dmarc" },
 #endif
   { "dnslist_domain",      vtype_stringptr,   &dnslist_domain },
   { "dnslist_matched",     vtype_stringptr,   &dnslist_matched },
@@ -553,8 +545,8 @@ static var_entry var_table[] = {
   { "host",                vtype_stringptr,   &deliver_host },
   { "host_address",        vtype_stringptr,   &deliver_host_address },
   { "host_data",           vtype_stringptr,   &host_data },
-  { "host_lookup_deferred",vtype_int,         &host_lookup_deferred },
-  { "host_lookup_failed",  vtype_int,         &host_lookup_failed },
+  { "host_lookup_deferred",vtype_boolint,     &host_lookup_deferred },
+  { "host_lookup_failed",  vtype_boolint,     &host_lookup_failed },
   { "host_port",           vtype_int,         &deliver_host_port },
   { "initial_cwd",         vtype_stringptr,   &initial_cwd },
   { "inode",               vtype_ino,         &deliver_inode },
@@ -562,7 +554,7 @@ static var_entry var_table[] = {
   { "interface_port",      vtype_int,         &interface_port },
   { "item",                vtype_stringptr,   &iterate_item },
 #ifdef LOOKUP_LDAP
-  { "ldap_dn",             vtype_stringptr,   &eldap_dn },
+  { "ldap_dn",             vtype_lookup_module, US"ldap" },
 #endif
   { "load_average",        vtype_load_avg,    NULL },
   { "local_part",          vtype_stringptr,   &deliver_localpart },
@@ -721,12 +713,13 @@ static var_entry var_table[] = {
   { "spam_score_int",      vtype_stringptr,   &spam_score_int },
 #endif
 #ifdef EXIM_HAVE_SPF
-  { "spf_guess",           vtype_module,	US"spf" },
-  { "spf_header_comment",  vtype_module,	US"spf" },
-  { "spf_received",        vtype_module,	US"spf" },
-  { "spf_result",          vtype_module,	US"spf" },
-  { "spf_result_guessed",  vtype_module,	US"spf" },
-  { "spf_smtp_comment",    vtype_module,	US"spf" },
+  { "spf_guess",           vtype_misc_module,	US"spf" },
+  { "spf_header_comment",  vtype_misc_module,	US"spf" },
+  { "spf_received",        vtype_misc_module,	US"spf" },
+  { "spf_result",          vtype_misc_module,	US"spf" },
+  { "spf_result_guessed",  vtype_misc_module,	US"spf" },
+  { "spf_smtp_comment",    vtype_misc_module,	US"spf" },
+  { "spf_used_domain",	   vtype_misc_module,	US"spf" },
 #endif
   { "spool_directory",     vtype_stringptr,   &spool_directory },
   { "spool_inodes",        vtype_pinodes,     (void *)TRUE },
@@ -1012,7 +1005,7 @@ const uschar * ss = expand_string(condition);
 if (!ss)
   {
   if (!f.expand_string_forcedfail && !f.search_find_defer)
-    log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand condition %q "
+    log_write(LOG_MAIN|LOG_PANIC, "failed to expand condition %q "
       "for %s %s: %s", condition, m1, m2, expand_string_message);
   return FALSE;
   }
@@ -1120,16 +1113,17 @@ Ustrchr() yields non-NULL if the character is zero (which is not something
 I expected). */
 
 static const uschar *
-read_name(uschar * name, int max, const uschar * s, const uschar * extras)
+read_name(uschar * name, size_t max, const uschar * s, const uschar * extras)
 {
 int ptr = 0;
 if (f.running_in_test_harness) assert(!is_tainted(s));
+max -= 2;
 while (*s && (isalnum(*s) || Ustrchr(extras, *s) != NULL))
   {
-  if (ptr < max-1) name[ptr++] = *s;
+  if (ptr < max) name[ptr++] = *s;
   s++;
   }
-name[ptr] = 0;
+name[ptr] = '\0';
 return s;
 }
 
@@ -1155,19 +1149,20 @@ Returns:    a pointer to the first character after the header name
 */
 
 static const uschar *
-read_header_name(uschar *name, int max, const uschar *s)
+read_header_name(uschar * name, size_t max, const uschar * s)
 {
 int prelen = Ustrchr(name, '_') - name + 1;
 int ptr = Ustrlen(name) - prelen;
 if (ptr > 0) memmove(name, name+prelen, ptr);
+max -= 3;
 while (mac_isgraph(*s) && *s != ':')
   {
-  if (ptr < max-1) name[ptr++] = *s;
+  if (ptr < max) name[ptr++] = *s;
   s++;
   }
 if (*s == ':') s++;
 name[ptr++] = ':';
-name[ptr] = 0;
+name[ptr] = '\0';
 return s;
 }
 
@@ -1735,10 +1730,10 @@ charset translation fails. If decoding fails, it returns NULL. */
 
 else
   {
-  uschar * error, * decoded = rfc2047_decode2(rawhdr,
-    check_rfc2047_length, charset, '?', NULL, newsize, &error);
+  uschar * error, * decoded = string_copy(rfc2047_decode2(rawhdr,
+		      check_rfc2047_length, charset, '?', NULL, newsize, &error));
   if (error)
-    DEBUG(D_any) debug_printf("*** error in RFC 2047 decoding: %s\n"
+    DEBUG(any) debug_printf("*** error in RFC 2047 decoding: %s\n"
       "    input was: %s\n", error, rawhdr);
   return decoded ? decoded : rawhdr;
   }
@@ -1844,7 +1839,7 @@ uschar * sname;
 
 if ((fd = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0)
   {
-  DEBUG(D_expand) debug_printf(" socket: %s\n", strerror(errno));
+  DEBUG(expand) debug_printf(" socket: %s\n", strerror(errno));
   return NULL;
   }
 
@@ -1868,7 +1863,7 @@ if (send(fd, buf, 1, 0) < 0) { where = US"send"; goto bad; }
 
 if (poll_one_fd(fd, POLLIN, 2 * 1000) != 1)
   {
-  DEBUG(D_expand) debug_printf("no daemon response; using local evaluation\n");
+  DEBUG(expand) debug_printf("no daemon response; using local evaluation\n");
   len = snprintf(CS buf, sizeof(buf), "%u", queue_count_cached());
   }
 else if ((len = recv(fd, buf, sizeof(buf), 0)) < 0)
@@ -1886,7 +1881,7 @@ bad2:
 #endif
 bad:
   close(fd);
-  DEBUG(D_expand) debug_printf(" %s: %s\n", where, strerror(errno));
+  DEBUG(expand) debug_printf(" %s: %s\n", where, strerror(errno));
   return NULL;
 }
 
@@ -2007,11 +2002,15 @@ switch (vp->type)
     sprintf(CS var_buffer, "%s", *(BOOL *)(val) ? "yes" : "no"); /* bool */
     return var_buffer;
 
+  case vtype_boolint:	/* a logical bool, presented as an int 1/0 */
+    sprintf(CS var_buffer, "%c", *(BOOL *)(val) ? '1' : '0');    /* bool */
+    return var_buffer;
+
   case vtype_stringptr:                      /* Pointer to string */
     return (s = *((uschar **)(val))) ? s : US"";
 
   case vtype_pid:
-    sprintf(CS var_buffer, "%d", (int)getpid()); /* pid */
+    sprintf(CS var_buffer, PID_T_FMT, getpid()); /* pid */
     return var_buffer;
 
   case vtype_load_avg:
@@ -2028,7 +2027,7 @@ switch (vp->type)
     if (!(s = *((uschar **)(val)))) return US"";
     if (!(domain = Ustrrchr(s, '@'))) return s;
     if (domain - s > sizeof(var_buffer) - 1)
-      log_write_die(0, LOG_MAIN, "local part longer than " SIZE_T_FMT
+      log_write_die(LOG_MAIN, "local part longer than " SIZE_T_FMT
 	  " in string expansion", sizeof(var_buffer));
     return string_copyn(s, domain - s);
 
@@ -2070,7 +2069,7 @@ switch (vp->type)
 	  }
 	}
       if (lseek(deliver_datafile, start_offset, SEEK_SET) < 0)
-	log_write_die(0, LOG_MAIN, "deliver_datafile lseek: %s",
+	log_write_die(LOG_MAIN, "deliver_datafile lseek: %s",
 	  strerror(errno));
       if ((len = read(deliver_datafile, body, len)) > 0)
 	{
@@ -2168,7 +2167,7 @@ switch (vp->type)
     }
 #endif
 
-  case vtype_module:
+  case vtype_misc_module:
     {
     uschar * errstr;
     misc_module_info * mi = misc_mod_find(val, &errstr);
@@ -2178,10 +2177,34 @@ switch (vp->type)
       table_count = mi->variables_count;
       goto sublist;
       }
-    log_write(0, LOG_MAIN|LOG_PANIC,
+    log_write(LOG_MAIN|LOG_PANIC,
       "failed to find %s module for %s: %s", US val, name, errstr);
     return US"";
     }
+
+  case vtype_lookup_module:
+    {
+    uschar * errstr = NULL;
+    const tree_node * t = tree_search(lookups_tree, val);
+#ifdef LOOKUP_MODULE_DIR
+    if (!t)
+      {
+      lookup_one_mod_load(val, &errstr);
+      t = tree_search(lookups_tree, val);
+      }
+#endif
+    if (t)
+      {
+      const lookup_info * li = t->data.ptr;
+      table = li->variables;
+      table_count = li->variables_count;
+      goto sublist;
+      }
+    log_write(LOG_MAIN|LOG_PANIC,
+      "failed to find %s module for %s%s%s", US val, name,
+	errstr ? ": " : "", errstr);
+    return US"";
+    }
   }
 
 return NULL;  /* Unknown variable. Silences static checkers. */
@@ -2234,8 +2257,9 @@ Returns:     -1 OK; string pointer updated, but in "skipping" mode
 */
 
 static int
-read_subs(uschar ** sub, int n, int m, const uschar ** sptr, esi_flags flags,
-  BOOL check_end, uschar * name, BOOL * resetok, unsigned * textonly_p)
+read_subs(const uschar ** sub, int n, int m, const uschar ** sptr,
+  esi_flags flags, BOOL check_end, uschar * name,
+  BOOL * resetok, unsigned * textonly_p)
 {
 const uschar * s = *sptr;
 unsigned textonly_l = 0;
@@ -2343,7 +2367,7 @@ while (i < nsub)
   acl_arg[i++] = NULL;
   }
 
-DEBUG(D_expand)
+DEBUG(expand)
   debug_printf_indent("expanding: acl: %s  arg: %s%s\n",
     sub[0],
     acl_narg>0 ? acl_arg[0] : US"<none>",
@@ -2443,7 +2467,7 @@ for (item = s;
 *list = *s ? s+1 : s;
 if (item == s) return NULL;
 item = string_copyn(item, s - item);
-DEBUG(D_expand) debug_printf_indent("  json ele: '%s'\n", item);
+DEBUG(expand) debug_printf_indent("  json ele: '%s'\n", item);
 return US item;
 }
 
@@ -2492,7 +2516,8 @@ if (!name[0])
     "but found \"%.16s\"", s);
   return -1;
   }
-DEBUG(D_expand) debug_printf_indent("cond: %s\n", name);
+DEBUG(expand) if (Ustrcmp(name, "def") != 0)
+  debug_printf_indent("cond: %s\n", name);
 if (opname)
   *opname = string_copy(name);
 
@@ -2746,7 +2771,6 @@ switch(cond_type = identify_operator(&s, &opname))
   case ECOND_PAM:
   case ECOND_RADIUS:
   case ECOND_LDAPAUTH:
-  case ECOND_PWCHECK:
 
   if (Uskip_whitespace(&s) != '{') goto COND_FAILED_CURLY_START; /* }-for-text-editors */
 
@@ -2780,9 +2804,9 @@ switch(cond_type = identify_operator(&s, &opname))
     {
       const uschar *errp;
       const uschar **errpp;
-      DEBUG(D_expand) errpp = &errp; else errpp = 0;
+      DEBUG(expand) errpp = &errp; else errpp = 0;
       if (0 == (rc = string_is_ip_addressX(sub[0], NULL, errpp)))
-        DEBUG(D_expand) debug_printf("failed: %s\n", errp);
+        DEBUG(expand) debug_printf("failed: %s\n", errp);
 
       *yield = ( cond_type == ECOND_ISIP  ? rc != 0 :
                  cond_type == ECOND_ISIP4 ? rc == 4 : rc == 6) == testfor;
@@ -2810,7 +2834,8 @@ switch(cond_type = identify_operator(&s, &opname))
     case ECOND_RADIUS:
 #ifdef RADIUS_CONFIG_FILE
       {
-      const misc_module_info * mi = misc_mod_find(US"radius", NULL);
+      uschar * dummy_errstr;
+      const misc_module_info * mi = misc_mod_find(US"radius", &dummy_errstr);
       typedef int (*fn_t)(const uschar *, uschar **);
       if (!mi)
 	goto COND_FAILED_NOT_COMPILED;
@@ -2841,16 +2866,8 @@ switch(cond_type = identify_operator(&s, &opname))
     goto COND_FAILED_NOT_COMPILED;
     #endif  /* LOOKUP_LDAP */
 
-    case ECOND_PWCHECK:
-    #ifdef CYRUS_PWCHECK_SOCKET
-    rc = auth_call_pwcheck(sub[0], &expand_string_message);
-    goto END_AUTH;
-    #else
-    goto COND_FAILED_NOT_COMPILED;
-    #endif  /* CYRUS_PWCHECK_SOCKET */
-
     #if defined(SUPPORT_PAM) || defined(RADIUS_CONFIG_FILE) || \
-        defined(LOOKUP_LDAP) || defined(CYRUS_PWCHECK_SOCKET)
+        defined(LOOKUP_LDAP)
     END_AUTH:
     if (rc == ERROR || rc == DEFER) goto failout;
     *yield = (rc == OK) == testfor;
@@ -2879,7 +2896,7 @@ switch(cond_type = identify_operator(&s, &opname))
     Uskip_whitespace(&s);
     if (*s++ != '{') goto COND_FAILED_CURLY_START;	/*}*/
 
-    switch(read_subs(sub, nelem(sub), 1, &s,
+    switch(read_subs(CUSS sub, nelem(sub), 1, &s,
 	yield ? ESI_NOFLAGS : ESI_SKIPPING, TRUE, name, resetok, NULL))
       {
       case 1: expand_string_message = US"too few arguments or bracketing "
@@ -2928,7 +2945,7 @@ switch(cond_type = identify_operator(&s, &opname))
     goto COND_FAILED_NOT_COMPILED;
 #else
     {
-    uschar *sub[4];
+    const uschar * sub[4];
     Uskip_whitespace(&s);
     if (*s++ != '{') goto COND_FAILED_CURLY_START;	/* }-for-text-editors */
     switch(read_subs(sub, nelem(sub), 2, &s,
@@ -3019,7 +3036,7 @@ switch(cond_type = identify_operator(&s, &opname))
     if (!(sub[i] = expand_string_internal(s+1, flags, &s, resetok, &textonly)))
       goto failout;
     if (textonly) sub_textonly |= BIT(i);
-    DEBUG(D_expand) if (i == 1 && !sub2_honour_dollar && Ustrchr(sub[1], '$'))
+    DEBUG(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;
@@ -3032,7 +3049,7 @@ switch(cond_type = identify_operator(&s, &opname))
       if (sub[i][0] == 0)
         {
         num[i] = 0;
-        DEBUG(D_expand)
+        DEBUG(expand)
           debug_printf_indent("empty string cast to zero for numerical comparison\n");
         }
       else
@@ -3218,7 +3235,7 @@ switch(cond_type = identify_operator(&s, &opname))
 	if (sublen == 24)
 	  {
 	  uschar *coded = b64encode(CUS digest, 16);
-	  DEBUG(D_auth) debug_printf("crypteq: using MD5+B64 hashing\n"
+	  DEBUG(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);
 	  }
@@ -3227,13 +3244,13 @@ switch(cond_type = identify_operator(&s, &opname))
 	  uschar coded[36];
 	  for (int i = 0; i < 16; i++) sprintf(CS (coded+2*i), "%02X", digest[i]);
 	  coded[32] = 0;
-	  DEBUG(D_auth) debug_printf("crypteq: using MD5+hex hashing\n"
+	  DEBUG(auth) debug_printf("crypteq: using MD5+hex hashing\n"
 	    "  subject=%s\n  crypted=%s\n", coded, sub[1]+5);
 	  tempcond = (strcmpic(coded, sub[1]+5) == 0);
 	  }
 	else
 	  {
-	  DEBUG(D_auth) debug_printf("crypteq: length for MD5 not 24 or 32: "
+	  DEBUG(auth) debug_printf("crypteq: length for MD5 not 24 or 32: "
 	    "fail\n  crypted=%s\n", sub[1]+5);
 	  tempcond = FALSE;
 	  }
@@ -3255,7 +3272,7 @@ switch(cond_type = identify_operator(&s, &opname))
 	if (sublen == 28)
 	  {
 	  uschar *coded = b64encode(CUS digest, 20);
-	  DEBUG(D_auth) debug_printf("crypteq: using SHA1+B64 hashing\n"
+	  DEBUG(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);
 	  }
@@ -3264,13 +3281,13 @@ switch(cond_type = identify_operator(&s, &opname))
 	  uschar coded[44];
 	  for (int i = 0; i < 20; i++) sprintf(CS (coded+2*i), "%02X", digest[i]);
 	  coded[40] = 0;
-	  DEBUG(D_auth) debug_printf("crypteq: using SHA1+hex hashing\n"
+	  DEBUG(auth) debug_printf("crypteq: using SHA1+hex hashing\n"
 	    "  subject=%s\n  crypted=%s\n", coded, sub[1]+6);
 	  tempcond = (strcmpic(coded, sub[1]+6) == 0);
 	  }
 	else
 	  {
-	  DEBUG(D_auth) debug_printf("crypteq: length for SHA-1 not 28 or 40: "
+	  DEBUG(auth) debug_printf("crypteq: length for SHA-1 not 28 or 40: "
 	    "fail\n  crypted=%s\n", sub[1]+6);
 	  tempcond = FALSE;
 	  }
@@ -3308,7 +3325,7 @@ switch(cond_type = identify_operator(&s, &opname))
 
 	#define STR(s) # s
 	#define XSTR(s) STR(s)
-	DEBUG(D_auth) debug_printf("crypteq: using %s()\n"
+	DEBUG(auth) debug_printf("crypteq: using %s()\n"
 	  "  subject=%s\n  crypted=%s\n",
 	  which == 0 ? XSTR(DEFAULT_CRYPT) : which == 1 ? "crypt" : "crypt16",
 	  coded, sub[1]);
@@ -3341,7 +3358,7 @@ switch(cond_type = identify_operator(&s, &opname))
       uschar * save_iterate_item = iterate_item;
       int (*compare)(const uschar *, const uschar *);
 
-      DEBUG(D_expand) debug_printf_indent("condition: %s  item: %s\n", opname, sub[0]);
+      DEBUG(expand) debug_printf_indent("condition: %s  item: %s\n", opname, sub[0]);
 
       /* grab any listsep spec, then expand the list */
 
@@ -3355,7 +3372,7 @@ switch(cond_type = identify_operator(&s, &opname))
 
       while ((iterate_item = string_nextinlist(&list, &sep, NULL, 0)))
 	{
-	DEBUG(D_expand) debug_printf_indent(" compare %s\n", iterate_item);
+	DEBUG(expand) debug_printf_indent(" compare %s\n", iterate_item);
         if (compare(sub[0], iterate_item) == 0)
           {
           tempcond = TRUE;
@@ -3442,7 +3459,7 @@ switch(cond_type = identify_operator(&s, &opname))
     int sep;
     uschar *save_iterate_item = iterate_item;
 
-    DEBUG(D_expand) debug_printf_indent("condition: %s\n", opname);
+    DEBUG(expand) debug_printf_indent("condition: %s\n", opname);
 
     /* First expand the list, apart from a leading change-of-separator
     on non-json lists */
@@ -3503,7 +3520,7 @@ switch(cond_type = identify_operator(&s, &opname))
 	  goto failout;
 	  }
 
-      DEBUG(D_expand) debug_printf_indent("%s: $item = %q\n", opname, iterate_item);
+      DEBUG(expand) debug_printf_indent("%s: $item = %q\n", opname, iterate_item);
       if (!eval_condition(sub[1], resetok, &tempcond))
         {
         expand_string_message = string_sprintf("%s inside %q condition",
@@ -3511,7 +3528,7 @@ switch(cond_type = identify_operator(&s, &opname))
         iterate_item = save_iterate_item;
         goto failout;
         }
-      DEBUG(D_expand) debug_printf_indent("%s: condition evaluated to %s\n", opname,
+      DEBUG(expand) debug_printf_indent("%s: condition evaluated to %s\n", opname,
         tempcond? "true":"false");
 
       if (yield) *yield = (tempcond == testfor);
@@ -3536,15 +3553,15 @@ switch(cond_type = identify_operator(&s, &opname))
   case ECOND_BOOL:
   case ECOND_BOOL_LAX:
     {
-    uschar *sub_arg[1];
-    uschar *t, *t2;
+    uschar * sub_arg[1];
+    uschar * t, * t2;
     uschar *ourname;
     size_t len;
     BOOL boolvalue = FALSE;
 
     if (Uskip_whitespace(&s) != '{') goto COND_FAILED_CURLY_START;	/* }-for-text-editors */
     ourname = cond_type == ECOND_BOOL_LAX ? US"bool_lax" : US"bool";
-    switch(read_subs(sub_arg, 1, 1, &s,
+    switch(read_subs(CUSS sub_arg, 1, 1, &s,
 	    yield ? ESI_NOFLAGS : ESI_SKIPPING, FALSE, ourname, resetok, NULL))
       {
       case 1: expand_string_message = string_sprintf(
@@ -3567,7 +3584,7 @@ switch(cond_type = identify_operator(&s, &opname))
         len = t2 - t;
         }
       }
-    DEBUG(D_expand)
+    DEBUG(expand)
       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
@@ -3595,7 +3612,7 @@ switch(cond_type = identify_operator(&s, &opname))
        "value %q", t);
       goto failout;
       }
-    DEBUG(D_expand) debug_printf_indent("%s: condition evaluated to %s\n", ourname,
+    DEBUG(expand) debug_printf_indent("%s: condition evaluated to %s\n", ourname,
         boolvalue? "true":"false");
     if (yield) *yield = (boolvalue == testfor);
     next = s; goto out;
@@ -3605,7 +3622,7 @@ switch(cond_type = identify_operator(&s, &opname))
   case ECOND_INBOUND_SRS:
     /* ${if inbound_srs {local_part}{secret}  {yes}{no}} */
     {
-    uschar * sub[2];
+    const uschar * sub[2];
     const pcre2_code * re;
     pcre2_match_data * md;
     PCRE2_SIZE * ovec;
@@ -3630,18 +3647,18 @@ switch(cond_type = identify_operator(&s, &opname))
     if (pcre2_match(re, sub[0], PCRE2_ZERO_TERMINATED, 0, PCRE_EOPT,
 		    md, pcre_gen_mtc_ctx) < 0)
       {
-      DEBUG(D_expand) debug_printf("no match for SRS'd local-part pattern\n");
+      DEBUG(expand) debug_printf("no match for SRS'd local-part pattern\n");
       goto srs_result;
       }
     ovec = pcre2_get_ovector_pointer(md);
 
     if (sub[0][0] == '"')
       quoting = 1;
-    else for (uschar * s = sub[0]; *s; s++)
+    else for (const uschar * s = sub[0]; *s; s++)
       if (!isalnum(*s) && Ustrchr(".!#$%&'*+-/=?^_`{|}~", *s) == NULL)
 	{ quoting = 1; break; }
     if (quoting)
-      DEBUG(D_expand) debug_printf_indent("auto-quoting local part\n");
+      DEBUG(expand) debug_printf_indent("auto-quoting local part\n");
 
     /* Record the (quoted, if needed) decoded recipient as $srs_recipient */
 
@@ -3676,7 +3693,7 @@ switch(cond_type = identify_operator(&s, &opname))
 
 	if (((now.tv_sec - d) & 0x3ff) > 10)	/* days since SRS generated */
 	  {
-	  DEBUG(D_expand) debug_printf("SRS too old\n");
+	  DEBUG(expand) debug_printf("SRS too old\n");
 	  goto srs_result;
 	  }
 	}
@@ -3685,7 +3702,7 @@ switch(cond_type = identify_operator(&s, &opname))
 
       if (ovec[3]-ovec[2] != 4)
 	{
-	DEBUG(D_expand) debug_printf("SRS checksum wrong size\n");
+	DEBUG(expand) debug_printf("SRS checksum wrong size\n");
 	goto srs_result;
 	}
 
@@ -3695,7 +3712,7 @@ switch(cond_type = identify_operator(&s, &opname))
       hmac_md5(sub[1], srs_recipient, cksum, sizeof(cksum));
       if (Ustrncmp(cksum, sub[0] + ovec[2], 4) != 0)
 	{
-	DEBUG(D_expand) debug_printf("SRS checksum mismatch\n");
+	DEBUG(expand) debug_printf("SRS checksum mismatch\n");
 	goto srs_result;
 	}
       }
@@ -3729,18 +3746,16 @@ goto failout;
 
 /* A condition requires code that is not compiled */
 
-#if !defined(SUPPORT_PAM) || !defined(RADIUS_CONFIG_FILE) || \
-    !defined(LOOKUP_LDAP) || !defined(CYRUS_PWCHECK_SOCKET) || \
-    !defined(SUPPORT_CRYPTEQ) || !defined(CYRUS_SASLAUTHD_SOCKET)
 COND_FAILED_NOT_COMPILED:
 expand_string_message = string_sprintf("support for %q not compiled",
   opname);
 goto failout;
-#endif
 
 failout:
   next = NULL;
 out:
+  DEBUG(expand) if (yield && next)
+    debug_printf_indent("cond %q res: %s\n", opname, *yield ? "T" : "F");
   expand_level--;
   return next;
 }
@@ -4059,7 +4074,7 @@ hash_source = string_catn(NULL, key_num, 1);
 hash_source = string_catn(hash_source, daystamp, 3);
 hash_source = string_cat(hash_source, address);
 
-DEBUG(D_expand)
+DEBUG(expand)
   debug_printf_indent("prvs: hash source is '%Y'\n", hash_source);
 
 memset(innerkey, 0x36, 64);
@@ -4157,18 +4172,18 @@ return yield;
 static inline void
 eval_dbg_op_2(const uschar * op, int_eximarith_t a, int_eximarith_t b)
 {
-DEBUG(D_expand)
+DEBUG(expand)
   debug_printf_indent("eval " PR_EXIM_ARITH " %s " PR_EXIM_ARITH, a, op, b);
 }
 static inline void
 eval_dbg_res(int_eximarith_t res)
 {
-DEBUG(D_expand) debug_printf(" => " PR_EXIM_ARITH "\n", res);
+DEBUG(expand) debug_printf(" => " PR_EXIM_ARITH "\n", res);
 }
 static inline void
 eval_dbg(const uschar * op, int_eximarith_t res)
 {
-DEBUG(D_expand)
+DEBUG(expand)
   debug_printf_indent("eval '%s' res: " PR_EXIM_ARITH "\n", op, res);
 }
 
@@ -4323,7 +4338,7 @@ if (!*error)
 
       if (y == -1 && x == EXIM_ARITH_MIN && op != '*')
 	{
-	DEBUG(D_expand)
+	DEBUG(expand)
 	  debug_printf("Integer exception dodging: " PR_EXIM_ARITH "%c-1 coerced to " PR_EXIM_ARITH "\n",
 	      EXIM_ARITH_MIN, op, EXIM_ARITH_MAX);
 	x = EXIM_ARITH_MAX;
@@ -4680,7 +4695,7 @@ else if (!opt_perl_started)
   uschar * initerror;
   typedef uschar * (*fn_t)(const uschar *);
 
-  DEBUG(D_any) debug_printf_indent("Starting Perl interpreter\n");
+  DEBUG(any) debug_printf_indent("Starting Perl interpreter\n");
   if ((initerror = (((fn_t *) mi->functions)[PERL_STARTUP]) (startup_pl)))
     {
     expand_string_message =
@@ -4782,7 +4797,7 @@ if (is_tainted(s))
   {
   expand_string_message =
     string_sprintf("attempt to expand tainted string '%s'", s);
-  log_write(0, LOG_MAIN|LOG_PANIC, "%s", expand_string_message);
+  log_write(LOG_MAIN|LOG_PANIC, "%s", expand_string_message);
   goto EXPAND_FAILED;
   }
 
@@ -4797,7 +4812,7 @@ while (*s)	/* known to be untainted */
 
   if (flags & ESI_EXISTS_ONLY && gstring_length(yield) > 0) break;
 
-  DEBUG(D_expand)
+  DEBUG(expand)
     {
     debug_printf_indent("%V%V%s: %W\n",
       first ? "/" : "K",
@@ -4824,7 +4839,7 @@ while (*s)	/* known to be untainted */
       const uschar * t = s + 2;
       for (s = t; *s ; s++) if (*s == '\\' && s[1] == 'N') break;
 
-      DEBUG(D_expand)
+      DEBUG(expand)
 	debug_expansion_interim(US"protected", t, (int)(s - t), flags);
       if (!(flags & ESI_SKIPPING))
 	yield = string_catn(yield, t, s - t);
@@ -4833,7 +4848,7 @@ while (*s)	/* known to be untainted */
     else
       {
       uschar ch[1];
-      DEBUG(D_expand)
+      DEBUG(expand)
 	debug_printf_indent("%Vbackslashed: '\\%c'\n", "K", s[1]);
       ch[0] = string_interpret_escape(&s);
       if (!(flags & ESI_SKIPPING))
@@ -4855,7 +4870,7 @@ while (*s)	/* known to be untainted */
     for (const uschar * t = s+1;
 	*t && *t != '$' && *t != '}' && *t != '\\'; t++) i++;
 
-    DEBUG(D_expand) debug_expansion_interim(US"text", s, i, flags);
+    DEBUG(expand) debug_expansion_interim(US"text", s, i, flags);
 
     if (!(flags & ESI_SKIPPING))
       yield = string_catn(yield, s, i);
@@ -4943,13 +4958,13 @@ while (*s)	/* known to be untainted */
 
     if (flags & ESI_SKIPPING)
       {
-      DEBUG(D_expand)
+      DEBUG(expand)
 	debug_expansion_interim(US"var", name, Ustrlen(name), flags);
       }
     else
       {
       int len = Ustrlen(value);
-      DEBUG(D_expand) debug_expansion_interim(US"value", value, len, flags);
+      DEBUG(expand) debug_expansion_interim(US"value", value, len, flags);
       if (!yield && newsize != 0)
 	{
 	yield = g;
@@ -4970,7 +4985,7 @@ while (*s)	/* known to be untainted */
     s = read_cnumber(&n, s);
     if (n >= 0 && n <= expand_nmax)
       {
-      DEBUG(D_expand) debug_expansion_interim(US"value", expand_nstring[n], expand_nlength[n], flags);
+      DEBUG(expand) debug_expansion_interim(US"value", expand_nstring[n], expand_nlength[n], flags);
       if (!(flags & ESI_SKIPPING))
 	yield = string_catn(yield, expand_nstring[n], expand_nlength[n]);
       }
@@ -4999,7 +5014,7 @@ while (*s)	/* known to be untainted */
       }
     if (n >= 0 && n <= expand_nmax)
       {
-      DEBUG(D_expand) debug_expansion_interim(US"value", expand_nstring[n], expand_nlength[n], flags);
+      DEBUG(expand) debug_expansion_interim(US"value", expand_nstring[n], expand_nlength[n], flags);
       if (!(flags & ESI_SKIPPING))
 	yield = string_catn(yield, expand_nstring[n], expand_nlength[n]);
       }
@@ -5024,7 +5039,7 @@ while (*s)	/* known to be untainted */
   expansion. */
   {
   int expansion_start = gstring_length(yield);
-  switch(item_type)
+  switch (item_type)
     {
     /* Call an ACL from an expansion.  We feed data in via $acl_arg1 - $acl_arg9.
     If the ACL returns accept or reject we return content set by "message ="
@@ -5042,7 +5057,7 @@ while (*s)	/* known to be untainted */
       uschar * user_msg;
       int rc;
 
-      switch(read_subs(sub, nelem(sub), 1, &s, flags, TRUE, name, &resetok, NULL))
+      switch(read_subs(CUSS sub, nelem(sub), 1, &s, flags, TRUE, name, &resetok, NULL))
         {
 	case -1: continue;		/* skipping */
         case 1: goto EXPAND_FAILED_CURLY;
@@ -5055,7 +5070,7 @@ while (*s)	/* known to be untainted */
 	{
 	case OK:
 	case FAIL:
-	  DEBUG(D_expand)
+	  DEBUG(expand)
 	    debug_printf_indent("acl expansion yield: %s\n", user_msg);
 	  if (user_msg)
             yield = string_cat(yield, user_msg);
@@ -5075,7 +5090,7 @@ while (*s)	/* known to be untainted */
     case EITEM_AUTHRESULTS:
       /* ${authresults {mysystemname}} */
       {
-      uschar * sub_arg[1];
+      const uschar * sub_arg[1];
 
       switch(read_subs(sub_arg, nelem(sub_arg), 1, &s, flags, TRUE, name, &resetok, NULL))
         {
@@ -5113,7 +5128,7 @@ while (*s)	/* known to be untainted */
       if (!(next_s = eval_condition(s, &resetok, flags & ESI_SKIPPING ? NULL : &cond)))
 	goto EXPAND_FAILED;  /* message already set */
 
-      DEBUG(D_expand)
+      DEBUG(expand)
 	{
 	debug_expansion_interim(US"condition", s, (int)(next_s - s), flags);
 	debug_expansion_interim(US"result",
@@ -5150,8 +5165,7 @@ while (*s)	/* known to be untainted */
 #ifdef SUPPORT_I18N
     case EITEM_IMAPFOLDER:
       {				/* ${imapfolder {name}{sep}{specials}} */
-      uschar * sub_arg[3];
-      const uschar * encoded;
+      const uschar * sub_arg[3], * encoded;
 
       switch(read_subs(sub_arg, nelem(sub_arg), 1, &s, flags, TRUE, name, &resetok, NULL))
         {
@@ -5376,7 +5390,7 @@ while (*s)	/* known to be untainted */
 
 #else   /* EXIM_PERL */
       {
-      uschar * sub_arg[EXIM_PERL_MAX_ARGS + 2];
+      const uschar * sub_arg[EXIM_PERL_MAX_ARGS + 2];
       gstring * new_yield;
       const misc_module_info * mi;
 
@@ -5405,10 +5419,10 @@ while (*s)	/* known to be untainted */
       sub_arg[EXIM_PERL_MAX_ARGS + 1] = NULL;
 	{
 	typedef gstring * (*fn_t)
-			    (gstring *, uschar **, uschar *, const uschar **);
+			(gstring *, uschar **, const uschar *, const uschar **);
 	new_yield = (((fn_t *) mi->functions)[PERL_CAT])
 					      (yield, &expand_string_message,
-						sub_arg[0], CUSS sub_arg + 1);
+						sub_arg[0], sub_arg + 1);
 	}
 
       /* NULL yield indicates failure; if the message pointer has been set to
@@ -5444,7 +5458,7 @@ while (*s)	/* known to be untainted */
       uschar * sub_arg[3], * domain;
       const uschar * p;
 
-      switch(read_subs(sub_arg, 3, 2, &s, flags, TRUE, name, &resetok, NULL))
+      switch(read_subs(CUSS sub_arg, 3, 2, &s, flags, TRUE, name, &resetok, NULL))
         {
 	case -1: continue;	/* If skipping, we don't actually do anything */
         case 1: goto EXPAND_FAILED_CURLY;
@@ -5496,7 +5510,7 @@ while (*s)	/* known to be untainted */
 
     case EITEM_PRVSCHECK:
       {
-      uschar * sub_arg[3], * p;
+      const uschar * sub_arg[3], * p;
       gstring * g;
       const pcre2_code * re;
 
@@ -5524,7 +5538,7 @@ while (*s)	/* known to be untainted */
         uschar * hash = string_copyn(expand_nstring[3],expand_nlength[3]);
         uschar * domain = string_copyn(expand_nstring[5],expand_nlength[5]);
 
-        DEBUG(D_expand)
+        DEBUG(expand)
 	  {
 	  debug_printf_indent("prvscheck localpart: %s\n", local_part);
 	  debug_printf_indent("prvscheck key number: %s\n", key_num);
@@ -5558,8 +5572,8 @@ while (*s)	/* known to be untainted */
           goto EXPAND_FAILED;
           }
 
-        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);
+        DEBUG(expand) debug_printf_indent("prvscheck: received hash is %s\n", hash);
+        DEBUG(expand) debug_printf_indent("prvscheck:      own hash is %s\n", p);
 
         if (Ustrcmp(p,hash) == 0)
           {
@@ -5577,18 +5591,18 @@ while (*s)	/* known to be untainted */
           if (iexpire >= inow)
             {
             prvscheck_result = US"1";
-            DEBUG(D_expand) debug_printf_indent("prvscheck: success, $prvscheck_result set to 1\n");
+            DEBUG(expand) debug_printf_indent("prvscheck: success, $prvscheck_result set to 1\n");
             }
 	  else
             {
             prvscheck_result = NULL;
-            DEBUG(D_expand) debug_printf_indent("prvscheck: signature expired, $prvscheck_result unset\n");
+            DEBUG(expand) debug_printf_indent("prvscheck: signature expired, $prvscheck_result unset\n");
             }
           }
         else
           {
           prvscheck_result = NULL;
-          DEBUG(D_expand) debug_printf_indent("prvscheck: hash failure, $prvscheck_result unset\n");
+          DEBUG(expand) debug_printf_indent("prvscheck: hash failure, $prvscheck_result unset\n");
           }
 
         /* Now expand the final argument. We leave this till now so that
@@ -5631,7 +5645,7 @@ while (*s)	/* known to be untainted */
     case EITEM_READFILE:
       {
       FILE * f;
-      uschar * sub_arg[2];
+      const uschar * sub_arg[2];
 
       if (expand_forbid & RDO_READFILE)
         {
@@ -5666,7 +5680,7 @@ while (*s)	/* known to be untainted */
     case EITEM_READSOCK:
       {
       const uschar * arg;
-      uschar * sub_arg[4];
+      const uschar * sub_arg[4];
 
       if (expand_forbid & RDO_READSOCK)
         {
@@ -5794,7 +5808,7 @@ while (*s)	/* known to be untainted */
 
     SOCK_FAIL:
       if (*s != '{') goto EXPAND_FAILED;				/*}*/
-      DEBUG(D_any) debug_printf("%s\n", expand_string_message);
+      DEBUG(any) debug_printf("%s\n", expand_string_message);
       if (!(arg = expand_string_internal(s+1,
 		    ESI_BRACE_ENDS | ESI_HONOR_DOLLAR, &s, &resetok, NULL)))
         goto EXPAND_FAILED;
@@ -5857,7 +5871,7 @@ while (*s)	/* known to be untainted */
 	}
       else
 	{
-	DEBUG(D_expand)
+	DEBUG(expand)
 	  debug_printf_indent("args string for ${run} expand before split\n");
 	if (!(arg = expand_string_internal(s,
 		ESI_BRACE_ENDS | ESI_HONOR_DOLLAR | flags, &s, &resetok, NULL)))
@@ -5966,7 +5980,7 @@ while (*s)	/* known to be untainted */
       {
       int oldptr = gstring_length(yield);
       int o2m;
-      uschar * sub[3];
+      const uschar * sub[3];
 
       switch(read_subs(sub, 3, 3, &s, flags, TRUE, name, &resetok, NULL))
         {
@@ -6008,7 +6022,7 @@ while (*s)	/* known to be untainted */
       Ensure that sub[2] is set in the ${length } case. */
 
       sub[2] = NULL;
-      switch(read_subs(sub, item_type == EITEM_LENGTH ? 2:3, 2, &s, flags,
+      switch(read_subs(CUSS sub, item_type == EITEM_LENGTH ? 2:3, 2, &s, flags,
              TRUE, name, &resetok, NULL))
         {
 	case -1: continue;	/* skipping */
@@ -6034,23 +6048,32 @@ while (*s)	/* known to be untainted */
 
       for (int i = 0; i < 2; i++) if (sub[i])
         {
+	BOOL req_pos =
+	  i != 0 || item_type == EITEM_HASH || item_type == EITEM_NHASH;
 	if (is_tainted(sub[i]))
 	  {
 	  expand_string_message =
 	    string_sprintf("attempt to use tainted string '%s' for %s",
 			sub[i], name);
-	  log_write(0, LOG_MAIN|LOG_PANIC, "%s", expand_string_message);
+	  log_write(LOG_MAIN|LOG_PANIC, "%s", expand_string_message);
 	  goto EXPAND_FAILED;
 	  }
         val[i] = (int)Ustrtol(sub[i], &ret, 10);
-        if (*ret != 0  ||  i != 0 && val[i] < 0)
+        if (*ret != 0  ||  req_pos && val[i] < 0)
           {
           expand_string_message = string_sprintf("%q is not a%s number "
-            "(in %q expansion)", sub[i], i != 0 ? " positive" : "", name);
+            "(in %q expansion)", sub[i], req_pos ? " positive" : "", name);
           goto EXPAND_FAILED;
           }
         }
 
+      if (item_type == EITEM_NHASH && val[0] == 0)
+          {
+          expand_string_message = string_sprintf("%q is zero (in %q expansion)",
+						sub[0], name);
+          goto EXPAND_FAILED;
+          }
+
       ret =
         item_type == EITEM_HASH
 	?  compute_hash(sub[2], val[0], val[1], &len)
@@ -6075,14 +6098,15 @@ while (*s)	/* known to be untainted */
 
     case EITEM_HMAC:
       {
-      uschar * sub[3];
+      const uschar * sub[3];
       md5 md5_base;
       hctx sha1_ctx;
       void * use_base;
       int type;
       int hashlen;      /* Number of octets for the hash algorithm's output */
       int hashblocklen; /* Number of octets the hash algorithm processes */
-      uschar * keyptr, * p;
+      const uschar * keyptr;
+      uschar * p;
       unsigned int keylen;
 
       uschar keyhash[MAX_HASHLEN];
@@ -6165,7 +6189,7 @@ while (*s)	/* known to be untainted */
 	*p++ = hex_digits[finalhash[i] & 0x0f];
 	}
 
-      DEBUG(D_any) debug_printf("HMAC[%s](%.*s,%s)=%.*s\n",
+      DEBUG(any) debug_printf("HMAC[%s](%.*s,%s)=%.*s\n",
 	sub[0], (int)keylen, keyptr, sub[2], hashlen*2, finalhash_hex);
 
       yield = string_catn(yield, finalhash_hex, hashlen*2);
@@ -6181,7 +6205,7 @@ while (*s)	/* known to be untainted */
       int moffset, moffsetextra, slen;
       pcre2_match_data * md;
       int emptyopt;
-      uschar * subject, * sub[3];
+      const uschar * subject, * sub[3];
       int save_expand_nmax =
         save_expand_strings(save_expand_nstring, save_expand_nlength);
       unsigned sub_textonly = 0;
@@ -6240,7 +6264,7 @@ while (*s)	/* known to be untainted */
           }
 
         /* Match - set up for expanding the replacement. */
-	DEBUG(D_expand) debug_printf_indent("%s: match\n", name);
+	DEBUG(expand) debug_printf_indent("%s: match\n", name);
 
         if (n == 0) n = EXPAND_MAXN + 1;
         expand_nmax = 0;
@@ -6388,7 +6412,15 @@ while (*s)	/* known to be untainted */
 	      field_number = -1;
 	      p++;
 	      }
-	    while (*p && isdigit(*p)) x = x * 10 + *p++ - '0';
+	    for (int digits = 0; isdigit(*p); digits++)
+	      if (digits > 5)
+		{
+		expand_string_message = US"extract: field number too large";
+		goto EXPAND_FAILED;
+		}
+	      else
+		x = x * 10 + *p++ - '0';
+
 	    if (!*p)
 	      {
 	      field_number *= x;
@@ -6636,7 +6668,7 @@ while (*s)	/* known to be untainted */
 
     case EITEM_LISTQUOTE:
       {
-      uschar * sub[2];
+      const uschar * sub[2];
       switch(read_subs(sub, 2, 2, &s, flags, TRUE, name, &resetok, NULL))
         {
 	case -1: continue;	/* skipping */
@@ -6755,7 +6787,7 @@ while (*s)	/* known to be untainted */
 	goto EXPAND_FAILED_CURLY;					/*}*/
 	}
 
-      DEBUG(D_expand) debug_printf_indent("%s: evaluate input list\n", name);
+      DEBUG(expand) debug_printf_indent("%s: evaluate input list\n", name);
       /* Check for a list-sep spec before expansion */
       sep = matchlist_parse_sep(&s);
 
@@ -6778,7 +6810,7 @@ while (*s)	/* known to be untainted */
 	  expand_string_message = US"missing '{' for second arg of reduce";
 	  goto EXPAND_FAILED_CURLY;					/*}*/
 	  }
-	DEBUG(D_expand) debug_printf_indent("reduce: initial result list\n");
+	DEBUG(expand) debug_printf_indent("reduce: initial result list\n");
         t = expand_string_internal(s,
 	      ESI_BRACE_ENDS | ESI_HONOR_DOLLAR | flags, &s, &resetok, NULL);
         if (!t) goto EXPAND_FAILED;
@@ -6806,7 +6838,7 @@ while (*s)	/* known to be untainted */
       condition for real. For EITEM_MAP and EITEM_REDUCE, do the same, using
       the normal internal expansion function. */
 
-      DEBUG(D_expand) debug_printf_indent("%s: find end of conditionn\n", name);
+      DEBUG(expand) debug_printf_indent("%s: find end of conditionn\n", name);
       if (item_type != EITEM_FILTER)
         temp = expand_string_internal(s,
 	  ESI_BRACE_ENDS | ESI_HONOR_DOLLAR | ESI_SKIPPING, &s, &resetok, NULL);
@@ -6845,7 +6877,7 @@ while (*s)	/* known to be untainted */
         {
         *outsep = (uschar)sep;      /* Separator as a string */
 
-	DEBUG(D_expand) debug_printf_indent("%s: $item = '%s'  $value = '%s'\n",
+	DEBUG(expand) debug_printf_indent("%s: $item = '%s'  $value = '%s'\n",
 			  name, iterate_item, lookup_value);
 
         if (item_type == EITEM_FILTER)
@@ -6863,7 +6895,7 @@ while (*s)	/* known to be untainted */
             goto EXPAND_FAILED;
             }
 	  lookup_value = save_value;
-          DEBUG(D_expand) debug_printf_indent("%s: condition is %s\n", name,
+          DEBUG(expand) debug_printf_indent("%s: condition is %s\n", name,
             condresult? "true":"false");
           if (condresult)
             temp = iterate_item;    /* TRUE => include this item */
@@ -7039,7 +7071,7 @@ while (*s)	/* known to be untainted */
 	const uschar * srcfield, * dstitem;
 	gstring * newlist = NULL, * newkeylist = NULL;
 
-        DEBUG(D_expand) debug_printf_indent("%s: $item = %q\n", name, srcitem);
+        DEBUG(expand) debug_printf_indent("%s: $item = %q\n", name, srcitem);
 
 	/* extract field for comparisons */
 	iterate_item = srcitem;
@@ -7104,8 +7136,8 @@ while (*s)	/* known to be untainted */
 	dstlist = newlist->s;
 	dstkeylist = newkeylist->s;
 
-        DEBUG(D_expand) debug_printf_indent("%s: dstlist = %q\n", name, dstlist);
-        DEBUG(D_expand) debug_printf_indent("%s: dstkeylist = %q\n", name, dstkeylist);
+        DEBUG(expand) debug_printf_indent("%s: dstlist = %q\n", name, dstlist);
+        DEBUG(expand) debug_printf_indent("%s: dstkeylist = %q\n", name, dstkeylist);
 	}
 
       if (dstlist)
@@ -7149,7 +7181,7 @@ while (*s)	/* known to be untainted */
         goto EXPAND_FAILED;
         }
 
-      switch(read_subs(argv, EXPAND_DLFUNC_MAX_ARGS + 2, 2, &s, flags,
+      switch(read_subs(CUSS argv, EXPAND_DLFUNC_MAX_ARGS + 2, 2, &s, flags,
            TRUE, name, &resetok, NULL))
         {
 	case -1: continue;	/* skipping */
@@ -7168,7 +7200,7 @@ while (*s)	/* known to be untainted */
           {
           expand_string_message = string_sprintf("dlopen %q failed: %s",
             argv[0], dlerror());
-          log_write(0, LOG_MAIN|LOG_PANIC, "%s", expand_string_message);
+          log_write(LOG_MAIN|LOG_PANIC, "%s", expand_string_message);
           goto EXPAND_FAILED;
           }
         t = store_get_perm(sizeof(tree_node) + Ustrlen(argv[0]), argv[0]);
@@ -7184,7 +7216,7 @@ while (*s)	/* known to be untainted */
         {
         expand_string_message = string_sprintf("dlsym %q in %q failed: "
           "%s", argv[1], argv[0], dlerror());
-        log_write(0, LOG_MAIN|LOG_PANIC, "%s", expand_string_message);
+        log_write(LOG_MAIN|LOG_PANIC, "%s", expand_string_message);
         goto EXPAND_FAILED;
         }
 
@@ -7205,7 +7237,7 @@ while (*s)	/* known to be untainted */
         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",
+	  log_write(LOG_MAIN|LOG_PANIC, "dlfunc{%s}{%s} failed (%d): %s",
               argv[0], argv[1], status, expand_string_message);
         goto EXPAND_FAILED;
         }
@@ -7254,7 +7286,7 @@ while (*s)	/* known to be untainted */
     case EITEM_SRS_ENCODE:
       /* ${srs_encode {secret} {return_path} {orig_domain}} */
       {
-      uschar * sub[3];
+      const uschar * sub[3];
       uschar cksum[4];
       gstring * g = NULL;
       BOOL quoted = FALSE;
@@ -7296,6 +7328,8 @@ while (*s)	/* known to be untainted */
 					    &start, &end, &domain, FALSE);
 	  uschar * ss;
 
+	  DEBUG(expand) if (expand_string_message)
+			  debug_printf_indent("%s\n", expand_string_message);
 	  if (!t)
 	    goto EXPAND_FAILED;
 
@@ -7306,7 +7340,7 @@ while (*s)	/* known to be untainted */
 	  if ((quoted = Ustrchr(ss, '"') != NULL))
 	    {
 	    gstring * h = NULL;
-	    DEBUG(D_expand) debug_printf_indent("auto-quoting local part\n");
+	    DEBUG(expand) debug_printf_indent("auto-quoting local part\n");
 	    while (*ss)		/* de-quote */
 	      {
 	      while (*ss && *ss != '"') h = string_catn(h, ss++, 1);
@@ -7332,7 +7366,7 @@ while (*s)	/* known to be untainted */
 	yield = string_cat(yield, sub[2]);
 	}
       else
-	DEBUG(D_expand) debug_printf_indent("null return_path for srs-encode\n");
+	DEBUG(expand) debug_printf_indent("null return_path for srs-encode\n");
 
       break;
       }
@@ -7343,7 +7377,7 @@ while (*s)	/* known to be untainted */
     }	/* EITEM_* switch */
     /*NOTREACHED*/
 
-  DEBUG(D_expand)		/* only if not the sole expansion of the line */
+  DEBUG(expand)		/* only if not the sole expansion of the line */
     if (yield && (expansion_start > 0 || *s))
       debug_expansion_interim(US"item-res",
 	  yield->s + expansion_start, yield->ptr - expansion_start,
@@ -7867,6 +7901,7 @@ NOT_ITEM: ;
 	int start, end, domain;
 	const uschar * t = parse_extract_address(sub, &error, &start, &end,
 						&domain, FALSE);
+	DEBUG(expand) if (error) debug_printf_indent("%s\n", error);
 	if (t)
 	  if (c != EOP_DOMAIN)
 	    yield = c == EOP_LOCAL_PART && domain > 0
@@ -7901,6 +7936,7 @@ NOT_ITEM: ;
 	  *p = '\0';
 	  address = parse_extract_address(sub, &error, &start, &end, &domain,
 	    FALSE);
+	  DEBUG(expand) if (error) debug_printf_indent("%s\n", error);
 	  *p = saveend;
 
 	  /* Add the address to the output list that we are building. This is
@@ -8234,7 +8270,7 @@ NOT_ITEM: ;
 	  goto EXPAND_FAILED;
 	  }
 	yield = string_cat(yield, s);
-	DEBUG(D_expand) debug_printf_indent("yield: '%Y'\n", yield);
+	DEBUG(expand) debug_printf_indent("yield: '%Y'\n", yield);
 	break;
 	}
 
@@ -8279,9 +8315,18 @@ NOT_ITEM: ;
       case EOP_EVAL:
       case EOP_EVAL10:
 	{
-	uschar *save_sub = sub;
-	uschar *error = NULL;
-	int_eximarith_t n = eval_expr(&sub, (c == EOP_EVAL10), &error, FALSE);
+	uschar * save_sub = sub, * error = NULL;
+	int_eximarith_t n;
+
+	if (is_tainted(sub))
+	  {
+	  expand_string_message =
+	    string_sprintf("attempt to eval tainted string '%s'", sub);
+	  log_write(LOG_MAIN|LOG_PANIC, "%s", expand_string_message);
+	  goto EXPAND_FAILED;
+	  }
+
+	n = eval_expr(&sub, (c == EOP_EVAL10), &error, FALSE);
 	if (error)
 	  {
 	  expand_string_message = string_sprintf("error in expression "
@@ -8550,7 +8595,7 @@ NOT_ITEM: ;
 	goto EXPAND_FAILED;
       }	/* EOP_* switch */
 
-      DEBUG(D_expand)
+      DEBUG(expand)
 	{
 	const uschar * res = string_from_gstring(yield);
 	const uschar * s = res + expansion_start;
@@ -8652,7 +8697,7 @@ left != NULL, return a pointer to the endpoint in the source string. */
   if (resetok) gstring_release_unused(yield);
   else if (resetok_p) *resetok_p = FALSE;
 
-  DEBUG(D_expand)
+  DEBUG(expand)
     {
     BOOL tainted = is_tainted(res);
     debug_printf_indent("%Vexpanded: %.*W\n",
@@ -8697,7 +8742,7 @@ that is a bad idea, because expand_string_message is in dynamic store. */
 
 EXPAND_FAILED:
 if (left) *left = s;
-DEBUG(D_expand)
+DEBUG(expand)
   {
   debug_printf_indent("%Vfailed to expand: %s\n", "K", orig_string);
   debug_printf_indent("%Verror message: %s\n",
@@ -8773,8 +8818,7 @@ uschar *
 expand_string_copy(const uschar * string)
 {
 const uschar * yield = expand_string(string);
-if (yield == string) yield = string_copy(string);
-return US yield;
+return yield == string ? string_copy(string) : US yield;
 }
 
 
@@ -8846,7 +8890,7 @@ to find at all). */
 if (isspace(*s))
   if (Uskip_whitespace(&s) == '\0')
     {
-      DEBUG(D_expand)
+      DEBUG(expand)
        debug_printf_indent("treating blank string as number 0\n");
       return 0;
     }
@@ -8897,7 +8941,7 @@ Arguments:
   addr       address being routed
   mtype      the module type
   mname      the module name
-  dbg_opt    debug selectors
+  dbg	     output debugging
   oname      the option name
   bvalue     the router's boolean value
   svalue     the router's string value
@@ -8909,31 +8953,30 @@ Returns:     OK     value placed in rvalue
 
 int
 exp_bool(address_item * addr,
-  const uschar * mtype, const uschar * mname, unsigned dbg_opt,
+  const uschar * mtype, const uschar * mname, BOOL dbg,
   uschar * oname, BOOL bvalue,
   const uschar * svalue, BOOL * rvalue)
 {
 const uschar * expanded;
 
-DEBUG(D_expand) debug_printf_indent("try option %s\n", oname);
+DEBUG(expand) debug_printf_indent("try option %s\n", oname);
 if (!svalue) { *rvalue = bvalue; return OK; }
 
 if (!(expanded = expand_string(svalue)))
   {
   if (f.expand_string_forcedfail)
     {
-    DEBUG(dbg_opt) debug_printf("expansion of %q forced failure\n", oname);
+    if(dbg) debug_printf("expansion of %q forced failure\n", oname);
     *rvalue = bvalue;
     return OK;
     }
   addr->message = string_sprintf("failed to expand %q in %s %s: %s",
       oname, mname, mtype, expand_string_message);
-  DEBUG(dbg_opt) debug_printf("%s\n", addr->message);
+  if(dbg) debug_printf("%s\n", addr->message);
   return DEFER;
   }
 
-DEBUG(dbg_opt) debug_printf("expansion of %q yields %q\n", oname,
-  expanded);
+if(dbg) debug_printf("expansion of %q yields %q\n", oname, expanded);
 
 if (strcmpic(expanded, US"true") == 0 || strcmpic(expanded, US"yes") == 0)
   *rvalue = TRUE;
@@ -8952,6 +8995,7 @@ return OK;
 
 
 /* Avoid potentially exposing a password in a string about to be logged */
+/*XXX potential constify? */
 
 uschar *
 expand_hide_passwords(uschar * s)
@@ -8989,7 +9033,7 @@ int fd, off = 0, len;
 
 if ((fd = exim_open2(CS filename, O_RDONLY)) < 0)
   {
-  log_write(0, LOG_MAIN | LOG_PANIC, "unable to open file '%s' for reading: %s",
+  log_write(LOG_MAIN | LOG_PANIC, "unable to open file '%s' for reading: %s",
 	     filename, strerror(errno));
   return NULL;
   }
@@ -8999,7 +9043,7 @@ 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);
+    log_write(LOG_MAIN|LOG_PANIC, "unable to read file: %s", filename);
     return NULL;
     }
   off += len;
@@ -9023,15 +9067,18 @@ typedef struct {
   const uschar *var_data;
 } err_ctx;
 
-/* Called via tree_walk, which allows nonconst name/data.  Our usage is const. */
+/* Called via tree_walk, which allows nonconst name/data. Our usage is const. */
+typedef void (*twalk_compat)(uschar *, uschar *, void *);
+
 static void
-assert_variable_notin(uschar * var_name, uschar * var_data, void * ctx)
+assert_variable_notin(const uschar * var_name, const 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;
+  e->var_name = var_name;
+  e->var_data = var_data;
   }
 }
 
@@ -9042,8 +9089,8 @@ err_ctx e = { .region_start = ptr, .region_end = US ptr + len,
 	      .var_name = NULL, .var_data = NULL };
 
 /* check acl_ variables */
-tree_walk(acl_var_c, assert_variable_notin, &e);
-tree_walk(acl_var_m, assert_variable_notin, &e);
+tree_walk(acl_var_c, (twalk_compat) assert_variable_notin, &e);
+tree_walk(acl_var_m, (twalk_compat) assert_variable_notin, &e);
 
 /* check auth<n> variables.
 assert_variable_notin() treats as const, so deconst is safe. */
@@ -9062,13 +9109,16 @@ for (var_entry * v = var_table; v < var_table + nelem(var_table); v++)
     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);
+tree_walk(tree_dns_fails,     (twalk_compat) assert_variable_notin, &e);
+tree_walk(tree_duplicates,    (twalk_compat) assert_variable_notin, &e);
+tree_walk(tree_nonrecipients, (twalk_compat) assert_variable_notin, &e);
+tree_walk(tree_unusable,      (twalk_compat) assert_variable_notin, &e);
+
+/* check address-lists */
+check_deliver_addrs_not_freed(assert_variable_notin, &e);
 
 if (e.var_name)
-  log_write_die(0, LOG_MAIN,
+  log_write_die(LOG_MAIN,
     "live variable '%s' destroyed by reset_store at %s:%d\n- value '%.64s'",
     e.var_name, filename, linenumber, e.var_data);
 }
@@ -9110,10 +9160,13 @@ int main(int argc, uschar **argv)
 {
 uschar buffer[1024];
 
-debug_selector = D_v;
+debug_modify_channel(US"+v");
 debug_file = stderr;
 debug_fd = fileno(debug_file);
-big_buffer = malloc(big_buffer_size);
+
+if (!(big_buffer = malloc(big_buffer_size)))
+  { perror("malloc"); exit(EXIT_FAILURE); }
+
 store_init();
 
 for (int i = 1; i < argc; i++)
@@ -9124,7 +9177,7 @@ for (int i = 1; i < argc; i++)
     argv[i]++;
     }
   if (isdigit(argv[i][0]))
-    debug_selector = Ustrtol(argv[i], NULL, 0);
+    debug_selector[0] = Ustrtoul(argv[i], NULL, 0);
   else
     if (Ustrspn(argv[i], "abcdefghijklmnopqrtsuvwxyz0123456789-.:/") ==
         Ustrlen(argv[i]))
diff --git a/src/src/filtertest.c b/src/src/filtertest.c
index 2de412156..d7ebc8e22 100644
--- a/src/src/filtertest.c
+++ b/src/src/filtertest.c
@@ -47,11 +47,11 @@ body_len = 0;
 body_linecount = 0;
 header_size = message_size;
 
-if (!dot_ended && !stdin_feof())
+if (!dot_ended && !stdin_feof(NULL))
   {
   if (!f.dot_ends)
     {
-    while ((ch = stdin_getc(GETC_BUFFER_UNLIMITED)) != EOF)
+    while ((ch = stdin_getc(NULL, GETC_BUFFER_UNLIMITED)) != EOF)
       {
       if (ch == 0) body_zerocount++;
       if (ch == '\n') body_linecount++;
@@ -64,7 +64,7 @@ if (!dot_ended && !stdin_feof())
   else
     {
     int ch_state = 1;
-    while ((ch = stdin_getc(GETC_BUFFER_UNLIMITED)) != EOF)
+    while ((ch = stdin_getc(NULL, GETC_BUFFER_UNLIMITED)) != EOF)
       {
       if (ch == 0) body_zerocount++;
       switch (ch_state)
diff --git a/src/src/functions.h b/src/src/functions.h
index e93a25137..6d7f44400 100644
--- a/src/src/functions.h
+++ b/src/src/functions.h
@@ -20,7 +20,6 @@ are in in fact in separate headers. */
 
 
 #ifndef DISABLE_TLS
-extern gstring *	add_tls_info_for_log(gstring *);
 extern const char *	std_dh_prime_default(void);
 extern const char *	std_dh_prime_named(const uschar *);
 
@@ -55,20 +54,13 @@ extern BOOL    tls_client_adjunct_start(host_item *, client_conn_ctx *,
 extern void    tls_client_creds_reload(BOOL);
 
 extern void    tls_close(void *, int);
-extern void    tls_close_notify(void);
 extern BOOL    tls_could_getc(void);
 extern void    tls_daemon_init(void);
 extern int     tls_daemon_tick(void);
 extern BOOL    tls_dropprivs_validate_require_cipher(BOOL);
 extern BOOL    tls_export_cert(uschar *, size_t, void *);
-extern int     tls_feof(void);
-extern int     tls_ferror(void);
 extern uschar *tls_field_from_dn(uschar *, const uschar *);
 extern void    tls_free_cert(void **);
-extern int     tls_getc(unsigned);
-extern uschar *tls_getbuf(unsigned *);
-extern void    tls_get_cache(unsigned);
-extern BOOL    tls_hasc(void);
 extern BOOL    tls_import_cert(const uschar *, void **);
 extern void    tls_state_in_to_out(int, const uschar *, int);
 extern void    tls_state_out_to_in(int, const uschar *, int);
@@ -77,10 +69,10 @@ extern BOOL    tls_is_name_for_cert(const uschar *, void *);
 extern BOOL    tls_openssl_options_parse(const uschar *, long *);
 #  endif
 extern int     tls_read(void *, uschar *, size_t);
+extern void    tls_receive_done(void);
 extern int     tls_server_start(uschar **, gstring *);
 extern void    tls_shutdown_wr(void *);
 extern BOOL    tls_smtp_buffered(void);
-extern int     tls_ungetc(int);
 #  if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT)
 extern void    tls_watch_discard_event(int);
 extern void    tls_watch_invalidate(void);
@@ -107,13 +99,16 @@ extern uschar *acl_standalone_setvar(const uschar *, BOOL);
 
 extern tree_node *acl_var_create(const uschar *);
 extern void    acl_var_write(uschar *, uschar *, void *);
+extern gstring * add_dmarc_info_for_log(gstring *);
 extern void    add_driver_info(driver_info **, const driver_info *, size_t);
+extern gstring * add_dsn_info_for_log(gstring *, int);
+extern gstring * add_spf_info_for_log(gstring *);
+extern gstring * add_tls_info_for_log(gstring *);
 
 extern void    assert_no_variables(void *, int, const char *, int);
 extern void    atrn_handle_customer(void);
 extern int     atrn_handle_provider(uschar **, uschar **);
 
-extern int     auth_call_pwcheck(uschar *, uschar **);
 extern int     auth_call_saslauthd(const uschar *, const uschar *,
 	         const uschar *, const uschar *, uschar **);
 extern int     auth_check_serv_cond(auth_instance *);
@@ -133,18 +128,17 @@ extern gstring *authres_smtpauth(gstring *);
 extern uschar *b64encode(const uschar *, int);
 extern uschar *b64encode_taint(const uschar *, int, const void *);
 extern int     b64decode(const uschar *, uschar **, const void *);
-extern int     bdat_getc(unsigned);
-extern uschar *bdat_getbuf(unsigned *);
-extern BOOL    bdat_hasc(void);
-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    bdat_flush_data(const in_processing *);
+extern void    bdat_push_receive_functions(void);
 
 extern void    cancel_cutthrough_connection(BOOL, const uschar *);
 extern gstring *cat_file(FILE *, gstring *, const uschar *);
 extern gstring *cat_file_tls(void *, gstring *, const uschar *);
+extern unsigned chan_name_to_num(const uschar *, unsigned,
+		  bit_table *, unsigned);
+
+extern void    check_deliver_addrs_not_freed(void (*)(const uschar*, const uschar*, void*), void *);
 extern int     check_host(void *, const uschar *, const uschar **, uschar **);
 extern uschar **child_exec_exim(int, BOOL, int *, BOOL, int, ...);
 extern pid_t   child_open_exim_function(int *, const uschar *);
@@ -173,22 +167,29 @@ extern ssize_t daemon_notifier_sockname(struct sockaddr_un *);
 extern int     dcc_process(uschar **);
 #endif
 
+extern BOOL    debug_disable(void);
+extern void    debug_decode_bits(bitmask_word_t **, const uschar *, int);
+extern void    debug_enable(void);
+
 extern void    debug_logging_activate(const uschar *, const uschar *);
 extern void    debug_logging_from_spool(const uschar *);
 extern void    debug_logging_stop(BOOL);
+extern void    debug_pretrigger_setup(const uschar *);
+extern void    debug_pretrigger_discard(void);
 extern void    debug_print_argv(const uschar **);
 extern void    debug_print_ids(uschar *);
-extern void    debug_printf_indent(const char *, ...) PRINTF_FUNCTION(1,2);
+extern void    debug_print_processing_stack(void);
 extern void    debug_print_string(uschar *);
 extern void    debug_print_tree(const char *, tree_node *);
-extern void    debug_vprintf(int, const char *, va_list);
-extern void    debug_pretrigger_setup(const uschar *);
-extern void    debug_pretrigger_discard(void);
+extern void    debug_printf_indent(const char *, ...) PRINTF_FUNCTION(1,2);
 extern void    debug_print_socket(int);
+extern gstring * debug_selector_dump(gstring *);
+extern void    debug_set_default_bits(bitmask_word_t **);
 extern void    debug_trigger_fire(void);
+extern void    debug_vprintf(int, const char *, va_list);
 
-extern void    decode_bits(unsigned int *, size_t, int *,
-	           const uschar *, bit_table *, int, uschar *, int);
+extern void    decode_bits(bitmask_word_t *, size_t, const uschar * const *,
+	           const uschar *, bit_table *, int, int);
 extern void    delete_pid_file(void);
 extern void    deliver_local(address_item *, BOOL);
 extern address_item *deliver_make_addr(const uschar *, BOOL);
@@ -215,11 +216,9 @@ extern void    dns_pattern_init(void);
 extern int     dns_special_lookup(dns_answer *, const uschar *, int, const uschar **);
 extern dns_record *dns_next_rr(const dns_answer *, dns_scan *, int);
 extern uschar *dns_text_type(int);
-extern void    dscp_list_to_stream(FILE *);
-extern BOOL    dscp_lookup(const uschar *, int, int *, int *, int *);
 
 extern void    enq_end(uschar *);
-extern BOOL    enq_start(uschar *, unsigned);
+extern unsigned enq_start(uschar *, unsigned);
 #ifndef DISABLE_EVENT
 extern uschar *event_raise(const uschar *, const uschar *, const uschar *, int *);
 extern void    msg_event_raise(const uschar *, const address_item *);
@@ -234,24 +233,11 @@ extern void    exim_setugid(uid_t, gid_t, BOOL, const uschar *);
 extern void    exim_underbar_exit(int) NORETURN;
 extern void    exim_wait_tick(struct timeval *, int);
 extern int     exp_bool(address_item *,
-  const uschar *, const uschar *, unsigned, uschar *, BOOL bvalue,
+  const uschar *, const uschar *, BOOL, uschar *, BOOL,
   const uschar *, BOOL *);
 extern BOOL    expand_check_condition(const uschar *, const uschar *, const uschar *);
 extern uschar *expand_file_big_buffer(const uschar *);
 
-extern const uschar *expand_string_2(const uschar *, BOOL *);
-
-static inline uschar * expand_nc_string(uschar * s)
-{ return US expand_string_2(s, NULL); }
-static inline const uschar * expand_c_string(const uschar * s)
-{ return expand_string_2(s, NULL); }
-
-/* A macro that picks which function to use depending on the type of the arg */
-#define expand_string(X) _Generic((X),     \
-	      uschar *:		expand_nc_string, \
-	      const uschar *:	expand_c_string   \
-	      )(X)
-
 extern BOOL   expand_string_nonempty(const uschar *);
 extern uschar *expand_getkeyed(const uschar *, const uschar *);
 
@@ -260,6 +246,7 @@ extern uschar *expand_string_copy(const uschar *);
 extern int_eximarith_t expand_string_integer(uschar *, BOOL);
 extern void    modify_variable(uschar *, void *);
 
+extern BOOL    fake_dnsa_len_for_fail(dns_answer *, int);
 extern BOOL    fd_ready(int, time_t);
 extern BOOL    filter_runtest(int, const uschar *, BOOL, BOOL);
 
@@ -292,16 +279,15 @@ extern int     host_nmtoa(int, const int *, int, uschar *, int);
 extern uschar *host_ntoa(int, const void *, uschar *, int *);
 extern int     host_scan_for_local_hosts(host_item *, host_item **, BOOL *);
 
-extern uschar *imap_utf7_encode(uschar *, const uschar *,
-				 uschar, uschar *, uschar **);
+extern const uschar *imap_utf7_encode(const uschar *, const uschar *,
+				 uschar, const uschar *, uschar **);
 
-extern void    invert_address(uschar *, uschar *);
+extern void    invert_address(uschar *, const uschar *);
 extern int     ip_addr(void *, int, const uschar *, int);
 extern int     ip_bind(int, int, const uschar *, int);
 extern int     ip_connect(int, int, const uschar *, int, int, const blob *);
 extern int     ip_connectedsocket(int, const uschar *, int, int,
                  int, host_item *, uschar **, const blob *);
-extern int     ip_get_address_family(int);
 extern void    ip_keepalive(int, const uschar *, BOOL);
 extern int     ip_recv(client_conn_ctx *, uschar *, int, time_t);
 extern int     ip_socket(int, int);
@@ -316,10 +302,15 @@ extern const uschar *local_part_quote(const uschar *);
 extern void    log_close_all(void);
 extern int     log_open_as_exim(const uschar * const);
 extern gstring *log_portnum(gstring *, int);
-extern void    log_write_die(unsigned, int, const char * format, ...)
+extern void    log_write_die(int, const char * format, ...)
 		PRINTF_FUNCTION(3,4) NORETURN;
+extern void    logging_modify_channels(const uschar *);
+extern void    logging_set_defaults(void);
 
 extern const lookup_info * lookup_with_acq_num(unsigned);
+extern gstring *lookup_dynamic_supported(gstring *);
+extern const lookup_info * lookup_find(const uschar *, uschar **);
+extern const lookup_info * lookup_findonly(const uschar *);
 #ifdef LOOKUP_MODULE_DIR
 extern BOOL    lookup_one_mod_load(const uschar *, uschar **);
 #endif
@@ -356,8 +347,8 @@ extern void    md5_start(md5 *);
 extern void    millisleep(int);
 #ifdef WITH_CONTENT_SCAN
 struct mime_boundary_context;
-extern int     mime_acl_check(uschar *, FILE *,
-                 struct mime_boundary_context *, uschar **, uschar **);
+extern int     mime_acl_check(uschar *, FILE *, struct mime_boundary_context *,
+		uschar **, uschar **, unsigned);
 extern int     mime_decode(const uschar **);
 extern ssize_t mime_decode_base64(FILE *, FILE *, const uschar *);
 extern int     mime_regex(const uschar **, BOOL);
@@ -373,20 +364,23 @@ extern int     misc_mod_msg_init(void);
 extern void    misc_mod_smtp_reset(void);
 
 extern uschar *moan_check_errorcopy(const uschar *);
-extern BOOL    moan_skipped_syntax_errors(uschar *, error_block *, uschar *,
-                 BOOL, uschar *);
+extern BOOL    moan_skipped_syntax_errors(const uschar *, const error_block *,
+		const uschar *, BOOL, const uschar *);
 extern void    moan_smtp_batch(const uschar *, const char *, ...)
 		  PRINTF_FUNCTION(2,3);
-extern BOOL    moan_send_message(const uschar *, int, error_block *eblock,
-		 header_line *, FILE *, const uschar *);
-extern void    moan_tell_someone(const uschar *, address_item *,
-                 const uschar *, const char *, ...) PRINTF_FUNCTION(4,5);
-extern BOOL    moan_to_sender(int, error_block *, header_line *, FILE *, BOOL);
+extern BOOL    moan_send_message(const uschar *, int,
+		const error_block * eblock, const header_line *,
+		FILE *, const uschar *);
+extern void    moan_tell_someone(const uschar *, const address_item *,
+                const uschar *, const char *, ...) PRINTF_FUNCTION(4,5);
+extern BOOL    moan_to_sender(int, const error_block *, const header_line *,
+		FILE *, BOOL);
 extern void    moan_write_from(FILE *);
 extern void    moan_write_references(FILE *, uschar *);
 #ifdef LOOKUP_MODULE_DIR
-//extern void    mod_load_check(const uschar *);
+extern void    mod_load_anyclass(const uschar *);
 #endif
+extern void    mod_names(FILE *);
 extern FILE   *modefopen(const uschar *, const char *, mode_t);
 
 extern int     open_cutthrough_connection(address_item *, BOOL);
@@ -395,7 +389,17 @@ extern uschar *parse_extract_address(const uschar *, uschar **, int *, int *, in
                  BOOL);
 extern int     parse_forward_list(const uschar *, int, address_item **, uschar **,
                  const uschar *, const uschar *, error_block **);
-extern uschar *parse_find_address_end(const uschar *, BOOL);
+
+extern const uschar * parse_find_address_end_gen(const uschar *, BOOL);
+static inline uschar * parse_find_address_end_nc(uschar * s, BOOL b)
+{ return US parse_find_address_end_gen(s, b); }
+static inline const uschar * parse_find_address_end_c(const uschar * s, BOOL b)
+{ return    parse_find_address_end_gen(s, b); }
+#define parse_find_address_end(X, B) _Generic((X),     \
+	      uschar *:		parse_find_address_end_nc, \
+	      const uschar *:	parse_find_address_end_c \
+	      )(X, B)
+
 extern const uschar *parse_find_at(const uschar *);
 extern const uschar *parse_fix_phrase(const uschar *, int);
 extern const uschar *parse_message_id(const uschar *, uschar **, uschar **);
@@ -408,10 +412,6 @@ const misc_module_info * perl_startup(const uschar *);
 
 extern void priv_drop_temp(const uid_t, const gid_t);
 extern void priv_restore(void);
-#ifdef SUPPORT_PROXY
-extern BOOL	proxy_protocol_host(void);
-extern void	proxy_protocol_setup(void);
-#endif
 
 extern BOOL    queue_action(const uschar *, int, const uschar **, int, int);
 extern void    queue_check_only(void);
@@ -446,12 +446,12 @@ extern void    read_message_body(BOOL);
 extern void    receive_bomb_out(const uschar *, uschar *) NORETURN;
 extern BOOL    receive_check_fs(int);
 extern BOOL    receive_check_set_sender(const uschar *);
-extern BOOL    receive_msg(BOOL);
+extern BOOL    receive_msg(BOOL, int);
 extern int_eximarith_t receive_statvfs(BOOL, int *);
 extern void    receive_swallow_smtp(void);
 extern int     recv_fd_from_sock(int);
 #ifdef WITH_CONTENT_SCAN
-extern int     regex(const uschar **, BOOL);
+extern int     exim_regex(const uschar **, BOOL);
 extern void    regex_vars_clear(void);
 #endif
 extern void    regex_at_daemon(const uschar *);
@@ -478,7 +478,7 @@ extern header_line *rewrite_header(header_line *,
 extern const uschar *rewrite_one(const uschar *, int, BOOL *, BOOL, uschar *,
                  rewrite_rule *);
 extern void    rewrite_test(const uschar *);
-extern uschar *rfc2047_decode2(uschar *, BOOL, const uschar *, int, int *,
+extern const uschar *rfc2047_decode2(const uschar *, BOOL, const uschar *, int, int *,
 				     int *, uschar **);
 extern int     route_address(address_item *, address_item **, address_item **,
                  address_item **, address_item **, int);
@@ -524,22 +524,16 @@ extern void    smtp_data_sigint_exit(void) NORETURN;
 extern void    smtp_deliver_init(void);
 extern uschar *smtp_cmd_hist(void);
 extern int     smtp_connect(smtp_connect_args *, const blob *);
-extern int     smtp_feof(void);
-extern int     smtp_ferror(void);
 extern uschar *smtp_get_connection_info(void);
 extern BOOL    smtp_get_interface(const uschar *, int, address_item *,
                  const uschar **, const uschar *);
 extern int     smtp_get_port(const uschar *, address_item *, const uschar *);
-extern int     smtp_getc(unsigned);
-extern uschar *smtp_getbuf(unsigned *);
-extern void    smtp_get_cache(unsigned);
-extern BOOL    smtp_hasc(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 **, BOOL);
 extern void    smtp_notquit_exit(const uschar *, uschar *, const uschar *, ...);
 extern void    smtp_port_for_connect(host_item *, int);
-extern void    smtp_proxy_tls(void *, uschar *, size_t, int *, int, const uschar *) NORETURN;
+extern void    smtp_proxy_tls(client_conn_ctx *, uschar *, size_t, int *, int, const uschar *) NORETURN;
 extern BOOL    smtp_read_response(void *, uschar *, int, int, int);
 rmark	       smtp_reset(rmark);
 extern void    smtp_respond(uschar *, int, BOOL, uschar *);
@@ -547,7 +541,6 @@ extern void    smtp_send_prohibition_message(int, uschar *);
 extern int     smtp_setup_msg(void);
 extern int     smtp_sock_connect(smtp_connect_args *, int, const blob *);
 extern BOOL    smtp_start_session(void);
-extern int     smtp_ungetc(int);
 extern void    smtp_verify_feed(const uschar *, unsigned);
 extern BOOL    smtp_verify_helo(void);
 extern int     smtp_write_atrn(address_item *, cut_t *);
@@ -564,11 +557,13 @@ extern int     spool_open_temp(uschar *);
 extern int     spool_read_header(uschar *, BOOL, BOOL);
 extern uschar *spool_sender_from_msgid(const uschar *);
 extern int     spool_write_header(const uschar *, int, uschar **);
-extern int     stdin_getc(unsigned);
-extern int     stdin_feof(void);
-extern int     stdin_ferror(void);
-extern BOOL    stdin_hasc(void);
-extern int     stdin_ungetc(int);
+extern const uschar *stdin_getbuf(const in_processing *, unsigned *);
+extern int     stdin_getc(const in_processing *, unsigned);
+extern int     stdin_feof(const in_processing *);
+extern int     stdin_ferror(const in_processing *);
+extern BOOL    stdin_hasc(const in_processing *);
+extern BOOL    stdin_refill(const in_processing *, unsigned);
+extern int     stdin_ungetc(const in_processing *, int);
 
 extern void    stackdump(void);
 extern void    store_exit(void);
@@ -588,7 +583,7 @@ extern uschar *string_base62_32(unsigned long int);
 extern uschar *string_base62_64(unsigned long int);
 extern gstring *string_catn(gstring *, const uschar *, int) WARN_UNUSED_RESULT;
 extern int     string_compare_by_pointer(const void *, const void *);
-extern uschar *string_copy_dnsdomain(uschar *);
+extern uschar *string_copy_dnsdomain(const uschar *);
 extern uschar *string_copy_malloc(const uschar *);
 extern uschar *string_dequote(const uschar **);
 extern uschar *string_format_size(int, uschar *);
@@ -602,11 +597,11 @@ extern const uschar *string_printing2(const uschar *, int);
 extern uschar *string_split_message(uschar *);
 extern uschar *string_unprinting(uschar *);
 #ifdef SUPPORT_I18N
-extern uschar *string_address_utf8_to_alabel(const uschar *, uschar **);
+extern const 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 const 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 **);
+extern const uschar *string_localpart_utf8_to_alabel(const uschar *, uschar **);
 #endif
 
 #define string_format(buf, siz, fmt, ...) \
@@ -632,10 +627,14 @@ extern uschar *string_nextinlist_trc(const uschar **listptr, int *separator, usc
 
 extern int     strcmpic(const uschar *, const uschar *);
 extern int     strncmpic(const uschar *, const uschar *, int);
-extern uschar *strstric(const uschar *, const uschar *, BOOL);
-extern const uschar *strstric_c(const uschar *, const uschar *, BOOL);
+extern uschar *       strstric_nc(const uschar *, const uschar *, BOOL);
+extern const uschar * strstric_c(const uschar *, const uschar *, BOOL);
+#define strstric(X, Y, B) _Generic((X),     \
+	      uschar *:		strstric_nc, \
+	      const uschar *:	strstric_c \
+	      )(X, Y, B)
 
-extern int     synprot_error(int, int, uschar *, uschar *);
+extern int     synprot_error(BOOL, int, uschar *, uschar *);
 
 extern int     test_harness_fudged_queue_time(int);
 extern void    tcp_init(void);
@@ -644,14 +643,15 @@ extern void    tfo_probe(void);
 #endif
 #if !defined(COMPILE_UTILITY) && !defined(MACRO_PREDEF)
 extern void    tls_modify_variables(tls_support *);
+extern const in_processing * tls_pop_receive_functions(const in_processing *);
+extern const in_processing * tls_push_receive_functions(const in_processing *);
 #endif
 extern uschar *tod_stamp(int);
 
 extern BOOL    transport_check_waiting(const uschar *, const uschar *, int, uschar *,
                  oicf, void*);
 extern uschar *transport_current_name(void);
-extern void    transport_do_pass_socket(const uschar *, const uschar *,
-		 const uschar *, int, uschar *, int);
+extern void    transport_do_pass_socket(uschar *, int);
 extern void    transport_init(void);
 extern const uschar *transport_rcpt_address(address_item *, BOOL);
 extern BOOL    transport_set_up_command(const uschar ***, const uschar *,
@@ -690,10 +690,10 @@ 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_host(const uschar * const *);
 extern int     verify_check_notblind(BOOL);
 extern int     verify_check_given_host(const uschar **, const host_item *);
-extern int     verify_check_this_host(const uschar **, unsigned int *,
+extern int     verify_check_this_host(const uschar * const *, unsigned int *,
 	         const uschar*, const uschar *, const uschar **);
 extern address_item *verify_checked_sender(const uschar *);
 extern void    verify_get_ident(int);
@@ -708,14 +708,25 @@ extern BOOL    write_chunk(transport_ctx *, const uschar *, int);
 extern ssize_t write_to_fd_buf(int, const uschar *, size_t);
 extern uschar *wrap_header(const uschar *, unsigned, unsigned, const uschar *, unsigned);
 
-#ifdef EXPERIMENTAL_XCLIENT
-extern uschar * xclient_smtp_command(uschar *, int *, BOOL *);
-extern gstring * xclient_smtp_advertise_str(gstring *);
-#endif
 extern uschar *xtextencode(const uschar *, int);
 extern int     xtextdecode(const uschar *, uschar **);
 
 
+/******************************************************************************/
+/* Bit-manipulation in multi-word vectors. */
+
+static inline void
+bit_clear(bitmask_word_t * tbl, unsigned bitnum)
+{ tbl[BITWORD(bitnum)] &= ~BITMASK(bitnum); }
+
+static inline void
+bit_set(bitmask_word_t * tbl, unsigned bitnum)
+{ tbl[BITWORD(bitnum)] |= BITMASK(bitnum); }
+
+static inline bitmask_word_t
+bit_test(bitmask_word_t * tbl, unsigned bitnum)
+{ return tbl[BITWORD(bitnum)] & BITMASK(bitnum); }
+
 /******************************************************************************/
 /* Predicate: if an address is in a tainted pool.
 By extension, a variable pointing to this address is tainted.
@@ -1068,7 +1079,7 @@ if (gstring_last_char(g) == c) gstring_trim(g, 1);
 static inline void
 gstring_reset(gstring * g)
 {
-g->ptr = 0;
+g->s[g->ptr = 0] = '\0';
 }
 
 
@@ -1144,25 +1155,14 @@ return item
 /* Use store_malloc for DNSA structs, and explicit frees. Using the same pool
 for them as the strings we proceed to copy from them meant they could not be
 released, hence blowing 64k for every DNS lookup. That mounted up. With malloc
-we do have to take care over marking tainted all copied strings.  A separate pool
-could be used and would handle that implicitly. */
+we do have to special-case taint checking (see store.c). */
 
+extern dns_answer * store_get_dns_answer_trc(const uschar *, unsigned);
 #define store_get_dns_answer() store_get_dns_answer_trc(CUS __FUNCTION__, __LINE__)
 
-static inline dns_answer *
-store_get_dns_answer_trc(const uschar * func, unsigned line)
-{
-return store_malloc_3(sizeof(dns_answer), CCS func, line);
-}
-
+extern void store_free_dns_answer_trc(dns_answer *, const uschar *, unsigned);
 #define store_free_dns_answer(dnsa) store_free_dns_answer_trc(dnsa, CUS __FUNCTION__, __LINE__)
 
-static inline void
-store_free_dns_answer_trc(dns_answer * dnsa, const uschar * func, unsigned line)
-{
-store_free_3(dnsa, CCS func, line);
-}
-
 
 /* Check for an RR being large enough.  Return TRUE iff bad. */
 static inline BOOL
@@ -1316,8 +1316,8 @@ report_time_since(const struct timeval * t0, const uschar * where)
 # ifdef MEASURE_TIMING
 struct timeval diff;
 timesince(&diff, t0);
-fprintf(stderr, "%d %s:\t%ld.%06ld\n",
-       (uint)getpid(), where, (long)diff.tv_sec, (long)diff.tv_usec);
+fprintf(stderr, PID_T_FMT " %s:\t%ld.%06ld\n",
+       getpid(), where, (long)diff.tv_sec, (long)diff.tv_usec);
 # endif
 }
 
@@ -1334,10 +1334,10 @@ if (f.running_in_test_harness && f.testsuite_delays) millisleep(millisec);
 /* Taint-checked file opens. Return values/errno per open(2). */
 
 static inline int
-exim_open2(const char *pathname, int flags)
+exim_open2(const char * pathname, int flags)
 {
 if (!is_tainted(pathname)) return open(pathname, flags);
-log_write(0, LOG_MAIN|LOG_PANIC, "Tainted filename '%s'", pathname);
+log_write(LOG_MAIN|LOG_PANIC, "Tainted filename '%s'", pathname);
 errno = EACCES;
 return -1;
 }
@@ -1345,16 +1345,16 @@ static inline int
 exim_open(const char *pathname, int flags, mode_t mode)
 {
 if (!is_tainted(pathname)) return open(pathname, flags, mode);
-log_write(0, LOG_MAIN|LOG_PANIC, "Tainted filename '%s'", pathname);
+log_write(LOG_MAIN|LOG_PANIC, "Tainted filename '%s'", pathname);
 errno = EACCES;
 return -1;
 }
 #ifdef EXIM_HAVE_OPENAT
 static inline int
-exim_openat(int dirfd, const char *pathname, int flags)
+exim_openat(int dirfd, const char * pathname, int flags)
 {
 if (!is_tainted(pathname)) return openat(dirfd, pathname, flags);
-log_write(0, LOG_MAIN|LOG_PANIC, "Tainted filename '%s'", pathname);
+log_write(LOG_MAIN|LOG_PANIC, "Tainted filename '%s'", pathname);
 errno = EACCES;
 return -1;
 }
@@ -1362,17 +1362,17 @@ static inline int
 exim_openat4(int dirfd, const char *pathname, int flags, mode_t mode)
 {
 if (!is_tainted(pathname)) return openat(dirfd, pathname, flags, mode);
-log_write(0, LOG_MAIN|LOG_PANIC, "Tainted filename '%s'", pathname);
+log_write(LOG_MAIN|LOG_PANIC, "Tainted filename '%s'", pathname);
 errno = EACCES;
 return -1;
 }
 #endif
 
 static inline FILE *
-exim_fopen(const char *pathname, const char *mode)
+exim_fopen(const char * pathname, const char * mode)
 {
 if (!is_tainted(pathname)) return fopen(pathname, mode);
-log_write(0, LOG_MAIN|LOG_PANIC, "Tainted filename '%s'", pathname);
+log_write(LOG_MAIN|LOG_PANIC, "Tainted filename '%s'", pathname);
 errno = EACCES;
 return NULL;
 }
@@ -1381,11 +1381,23 @@ static inline DIR *
 exim_opendir(const uschar * name)
 {
 if (!is_tainted(name)) return opendir(CCS name);
-log_write(0, LOG_MAIN|LOG_PANIC, "Tainted dirname '%s'", name);
+log_write(LOG_MAIN|LOG_PANIC, "Tainted dirname '%s'", name);
 errno = EACCES;
 return NULL;
 }
 
+#ifdef LOOKUP_MODULE_DIR
+static inline DIR *
+open_module_dir(void)
+{
+if (module_dir)
+  rewinddir(module_dir);
+else
+  module_dir = exim_opendir(CUS LOOKUP_MODULE_DIR);
+return module_dir;
+}
+#endif
+
 /******************************************************************************/
 # if !defined(COMPILE_UTILITY)
 
@@ -1398,7 +1410,7 @@ the generator for it at any time. */
 static inline void
 set_connection_id(void)
 {
-connection_id = string_sprintf("%lu", (u_long)getpid());
+connection_id = string_sprintf(PID_T_FMT, getpid());
 }
 
 
@@ -1408,18 +1420,18 @@ static inline pid_t
 exim_fork(const unsigned char * purpose)
 {
 pid_t pid;
-DEBUG(D_any)
+DEBUG(any)
   debug_printf_indent("%s forking for %s\n", process_purpose, purpose);
 if ((pid = fork()) == 0)
   {
   f.daemon_listen = FALSE;
   process_purpose = purpose;
-  DEBUG(D_any) debug_printf_indent("postfork: %s\n", purpose);
+  DEBUG(any) debug_printf_indent("postfork: %s\n", purpose);
   }
 else
   {
   testharness_pause_ms(100); /* let child work */
-  DEBUG(D_any) debug_printf_indent("%s forked for %s: %d\n",
+  DEBUG(any) debug_printf_indent("%s forked for %s: %d\n",
 				  process_purpose, purpose, (int)pid);
   }
 return pid;
@@ -1469,7 +1481,7 @@ store_pool = old_pool;
 static inline void
 smtp_debug_cmd(const uschar * buf, int mode)
 {
-HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP%c> %s\n",
+HDEBUG(transport|acl|v) debug_printf_indent("  SMTP%c> %s\n",
   mode == SCMD_BUFFER ? '|' : mode == SCMD_MORE ? '+' : '>', buf);
 
 #  ifndef DISABLE_CLIENT_CMD_LOG
@@ -1553,9 +1565,50 @@ is_multiple_qrun(void)
 return qrunners && (qrunners->interval > 0 || qrunners->next);
 }
 
+/******************************************************************************/
+/* Recieve input processing */
+
+static inline int
+inp_getc(const in_processing * inp, unsigned maxbuf)
+{ return inp->in_getc(inp, maxbuf); }
+static inline const uschar *
+inp_getbuf(const in_processing * inp, unsigned * len)
+{ return inp->in_getbuf(inp, len); }
+static inline BOOL
+inp_hasc(const in_processing * inp)
+{ return inp->in_hasc(inp); }
+static inline void
+inp_get_cache(const in_processing * inp, unsigned lim)
+{ inp->in_getcache(inp, lim); }
+static inline int
+inp_ungetc(const in_processing * inp, int ch)
+{ return inp->in_ungetc(inp, ch); }
+static inline int
+inp_feof(const in_processing * inp)
+{ return inp->in_feof(inp); }
+static inline int
+inp_ferror(const in_processing * inp)
+{ return inp->in_ferror(inp); }
+
+static inline int
+receive_getc(unsigned maxbuf) { return inp_getc(rx_prc, maxbuf); }
+static inline const uschar *
+receive_getbuf(unsigned * len) { return inp_getbuf(rx_prc, len); }
+static inline BOOL
+receive_hasc(void) { return inp_hasc(rx_prc); }
+static inline void
+receive_get_cache(unsigned lim) { inp_get_cache(rx_prc, lim); }
+static inline int
+receive_ungetc(int ch) { return inp_ungetc(rx_prc, ch); }
+static inline int
+receive_feof(void) { return inp_feof(rx_prc); }
+static inline int
+receive_ferror(void) { return inp_ferror(rx_prc); }
+
 
 # endif	/* !COMPILE_UTILITY */
 
+
 /******************************************************************************/
 #endif	/* !MACRO_PREDEF */
 
diff --git a/src/src/globals.c b/src/src/globals.c
index 3eda97628..820e89d66 100644
--- a/src/src/globals.c
+++ b/src/src/globals.c
@@ -58,43 +58,6 @@ BOOL    opt_perl_taintmode     = FALSE;
 tree_node *dlobj_anchor        = NULL;
 #endif
 
-#ifdef LOOKUP_IBASE
-uschar *ibase_servers          = NULL;
-#endif
-
-#ifdef LOOKUP_LDAP
-uschar *eldap_ca_cert_dir      = NULL;
-uschar *eldap_ca_cert_file     = NULL;
-uschar *eldap_cert_file        = NULL;
-uschar *eldap_cert_key         = NULL;
-uschar *eldap_cipher_suite     = NULL;
-uschar *eldap_default_servers  = NULL;
-uschar *eldap_require_cert     = NULL;
-int     eldap_version          = -1;
-BOOL    eldap_start_tls        = FALSE;
-#endif
-
-#ifdef LOOKUP_MYSQL
-uschar *mysql_servers          = NULL;
-#endif
-
-#ifdef LOOKUP_ORACLE
-uschar *oracle_servers         = NULL;
-#endif
-
-#ifdef LOOKUP_PGSQL
-uschar *pgsql_servers          = NULL;
-#endif
-
-#ifdef LOOKUP_REDIS
-uschar *redis_servers          = NULL;
-#endif
-
-#ifdef LOOKUP_SQLITE
-uschar *sqlite_dbfile	       = NULL;
-int     sqlite_lock_timeout    = 5;
-#endif
-
 #ifdef SUPPORT_MOVE_FROZEN_MESSAGES
 BOOL    move_frozen_messages   = FALSE;
 #endif
@@ -118,15 +81,15 @@ tls_support tls_out = {
 uschar *dsn_envid              = NULL;
 int     dsn_ret                = 0;
 const pcre2_code  *regex_DSN         = NULL;
-uschar *dsn_advertise_hosts    = NULL;
+const uschar *dsn_advertise_hosts    = NULL;
 
 #ifndef DISABLE_TLS
 BOOL    gnutls_compat_mode     = FALSE;
 BOOL    gnutls_allow_auto_pkcs11 = FALSE;
-uschar *hosts_require_alpn     = NULL;
+const uschar *hosts_require_alpn = NULL;
 uschar *openssl_options        = NULL;
 const pcre2_code *regex_STARTTLS     = NULL;
-uschar *tls_advertise_hosts    = US"*";
+const uschar *tls_advertise_hosts    = US"*";
 uschar *tls_alpn	       = US"smtp:esmtp";
 uschar *tls_certificate        = NULL;
 uschar *tls_crl                = NULL;
@@ -135,7 +98,7 @@ 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;
-uschar *tls_early_banner_hosts = US"";
+const uschar *tls_early_banner_hosts = US"";
 uschar *tls_eccurve            = US"auto";
 # ifndef DISABLE_OCSP
 uschar *tls_ocsp_file          = NULL;
@@ -145,15 +108,15 @@ uschar *tls_privatekey         = NULL;
 BOOL    tls_remember_esmtp     = FALSE;
 uschar *tls_require_ciphers    = NULL;
 # ifndef DISABLE_TLS_RESUME
-uschar *tls_resumption_hosts   = NULL;
+const uschar *tls_resumption_hosts = NULL;
 # endif
-uschar *tls_try_verify_hosts   = NULL;
+const uschar *tls_try_verify_hosts = NULL;
 uschar *tls_verify_certificates= US"system";
-uschar *tls_verify_hosts       = NULL;
+const uschar *tls_verify_hosts = NULL;
 int     tls_watch_fd	       = -1;
 time_t  tls_watch_trigger_time = (time_t)0;
 #else	/*DISABLE_TLS*/
-uschar *tls_advertise_hosts    = NULL;
+const uschar *tls_advertise_hosts = NULL;
 #endif
 
 #ifndef DISABLE_PRDR
@@ -172,18 +135,7 @@ incoming TCP/IP. The defaults use stdin. We never need these for any
 stand-alone tests. */
 
 #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;
-BOOL	(*lwr_receive_hasc)(void)	= stdin_hasc;
-
-int	(*receive_getc)(unsigned) 	= stdin_getc;
-uschar * (*receive_getbuf)(unsigned *) 	= NULL;		/* can return NULL */
-void	(*receive_get_cache)(unsigned)	= NULL;
-BOOL	(*receive_hasc)(void)		= stdin_hasc;
-int	(*receive_ungetc)(int)    	= stdin_ungetc;
-int	(*receive_feof)(void)     	= stdin_feof;
-int	(*receive_ferror)(void)   	= stdin_ferror;
+const in_processing * rx_prc = &stdin_template;
 #endif
 
 
@@ -192,6 +144,8 @@ when verifying one address while routing/verifying another. We have to have
 the size explicit, because it is referenced from more than one module. */
 
 const uschar **address_expansions[ADDRESS_EXPANSIONS_COUNT] = {
+  CUSS &address_file,
+  CUSS &address_pipe,
   CUSS &deliver_address_data,
   CUSS &deliver_domain,
   CUSS &deliver_domain_data,
@@ -202,12 +156,13 @@ const uschar **address_expansions[ADDRESS_EXPANSIONS_COUNT] = {
   CUSS &deliver_localpart_orig,
   CUSS &deliver_localpart_parent,
   CUSS &deliver_localpart_prefix,
+  CUSS &deliver_localpart_prefix_v,
   CUSS &deliver_localpart_suffix,
+  CUSS &deliver_localpart_suffix_v,
   CUSS (uschar **)(&deliver_recipients),
   CUSS &deliver_host,
+  CUSS &deliver_host_address,
   CUSS &deliver_home,
-  CUSS &address_file,
-  CUSS &address_pipe,
   CUSS &self_hostname,
   NULL };
 
@@ -255,7 +210,7 @@ struct global_flags f =
 	.dkim_disable_verify      = FALSE,
 	.dkim_init_done           = FALSE,
 #endif
-#ifdef SUPPORT_DMARC
+#ifdef EXIM_HAVE_DMARC
 	.dmarc_has_been_checked  = FALSE,
 	.dmarc_disable_verify    = FALSE,
 	.dmarc_enable_forensic   = FALSE,
@@ -265,6 +220,7 @@ struct global_flags f =
 
 	.enable_dollar_recipients = FALSE,
 	.expand_string_forcedfail = FALSE,
+	.expansion_test		= FALSE,
 
 	.filter_running         = FALSE,
 
@@ -289,15 +245,15 @@ struct global_flags f =
 	.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_local        = FALSE,
 	.queue_running          = FALSE,
 	.queue_smtp             = FALSE,
+#if defined(SUPPORT_PROXY)
+	.quit_cmd_only		= FALSE,
+#endif
 
 	.really_exim            = TRUE,
 	.receive_call_bombout   = FALSE,
@@ -366,7 +322,6 @@ 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 */
@@ -403,6 +358,7 @@ BOOL    queue_only             = FALSE;
 BOOL    queue_only_load_latch  = TRUE;
 BOOL    queue_only_override    = TRUE;
 BOOL    queue_run_in_order     = FALSE;
+const uschar * queue_run_order = NULL;
 BOOL    recipients_max_reject  = FALSE;
 BOOL    return_path_remove     = TRUE;
 
@@ -560,7 +516,7 @@ uschar *authenticated_fail_id  = NULL;
 uschar *authenticated_id       = NULL;
 uschar *authenticated_sender   = NULL;
 auth_instance  *auths          = NULL;
-uschar *auth_advertise_hosts   = US"*";
+const uschar *auth_advertise_hosts   = US"*";
 auth_instance auth_defaults    = {   /* All unmentioned elements 0/NULL/FALSE */
     0
 };
@@ -585,19 +541,11 @@ uschar *base62_chars= US"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
 uschar *bi_command             = NULL;
 uschar *big_buffer             = NULL;
 int     big_buffer_size        = BIG_BUFFER_SIZE;
-#ifdef EXPERIMENTAL_BRIGHTMAIL
-uschar *bmi_alt_location       = NULL;
-uschar *bmi_base64_tracker_verdict = NULL;
-uschar *bmi_base64_verdict     = NULL;
-uschar *bmi_config_file        = US"/opt/brightmail/etc/brightmail.cfg";
-int     bmi_deliver            = 1;
-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_charset         = US"us-ascii";
 uschar *bounce_message_file    = NULL;
 uschar *bounce_message_text    = NULL;
 const uschar *bounce_recipient = NULL;
@@ -617,7 +565,7 @@ 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"*";
+const uschar *chunking_advertise_hosts = US"*";
 unsigned chunking_datasize     = 0;
 unsigned chunking_data_left    = 0;
 chunking_state_t chunking_state= CHUNKING_NOT_OFFERED;
@@ -661,6 +609,7 @@ unsigned continue_flags	       = 0;
 unsigned continue_limit_mail   = 0;
 unsigned continue_limit_rcpt   = 0;
 unsigned continue_limit_rcptdom= 0;
+#endif
 int	continue_fd	       = -1;
 uschar *continue_proxy_cipher  = NULL;
 BOOL    continue_proxy_dane    = FALSE;
@@ -675,19 +624,13 @@ const uschar *continue_transport = NULL;
 open_db *continue_retry_db     = NULL;
 open_db *continue_wait_db      = NULL;
 #endif
-#endif
 
 uschar *csa_status             = NULL;
-cut_t   cutthrough = {
-  .callout_hold_only =	FALSE,				/* verify-only: normal delivery */
-  .delivery =		FALSE,				/* when to attempt */
-  .tpt_sender =		FALSE,				/* use tpt's sender */
-  .defer_pass =		FALSE,				/* on defer: spool locally */
-  .is_tls =		FALSE,				/* not a TLS conn yet */
+cut_t   cutthrough = {	/* All remaining items 0/FALSE/NULL */
   .cctx =		{.sock = -1},			/* open connection */
-  .nrcpt =		0,				/* number of addresses */
 };
 
+uschar *daemon_modules_load    = NULL;
 int	daemon_notifier_fd     = -1;
 uschar *daemon_smtp_port       = US"smtp";
 int     daemon_startup_retries = 9;
@@ -702,51 +645,77 @@ uschar *dccifd_options         = US"header";
 
 int     debug_fd               = -1;
 FILE   *debug_file             = NULL;
-int     debug_notall[]         = {
-  Di_memory,
-  Di_noutf8,
-  -1
+
+/* List of names for debug channels.  Must be in alphabetical order. */
+
+#define DEBUG_CHAN(chan) {.name = US #chan, .logchan_bit = __LINE__ - D_iota}
+
+enum { D_iota = __LINE__ + 2 - BIT_TABLE_IDX_USABLE };
+bit_table debug_channels[] = {
+  DEBUG_CHAN(acl),		/* 4 */
+  DEBUG_CHAN(auth),
+  DEBUG_CHAN(deliver),
+  DEBUG_CHAN(dns),
+  DEBUG_CHAN(dnsbl),		/* 8 */
+  DEBUG_CHAN(exec),
+  DEBUG_CHAN(expand),
+  DEBUG_CHAN(filter),
+  DEBUG_CHAN(hints_lookup),
+  DEBUG_CHAN(host_lookup),
+  DEBUG_CHAN(ident),
+  DEBUG_CHAN(interface),
+  DEBUG_CHAN(lists),		/* 16 */
+  DEBUG_CHAN(load),
+  DEBUG_CHAN(local_scan),
+  DEBUG_CHAN(lookup),
+  DEBUG_CHAN(macro),
+  DEBUG_CHAN(memory),
+  DEBUG_CHAN(noutf8),
+  DEBUG_CHAN(pid),
+  DEBUG_CHAN(process_info),	/* 24 */
+  DEBUG_CHAN(queue_run),
+  DEBUG_CHAN(receive),
+  DEBUG_CHAN(regex),
+  DEBUG_CHAN(resolver),
+  DEBUG_CHAN(retry),
+  DEBUG_CHAN(rewrite),
+  DEBUG_CHAN(route),
+  DEBUG_CHAN(start),		/* 32 */
+  DEBUG_CHAN(timestamp),
+  DEBUG_CHAN(tls),
+  DEBUG_CHAN(transport),
+  DEBUG_CHAN(uid),
+  DEBUG_CHAN(v),
+  DEBUG_CHAN(verify),
 };
-bit_table debug_options[]      = { /* must be in alphabetical order and use
-				 only the enum values from macros.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),
+#undef DEBUG_CHAN
+int      debug_chan_count	= nelem(debug_channels);
+
+/* Channel settings for "default" debug (just a "-d" used) */
+
+const uschar * debug_defaults =
+  US  "+all"
+      "-expand"
+      "-filter"
+      "-interface"
+      "-load"
+      "-local_scan"
+      "-memory"
+      "-noutf8"
+      "-pid"
+      "-timestamp"
+      "-resolver";
+
+/* List of debug channels that we exclude from "all" */
+
+const uschar * const debug_notall_names[] = {
+  US"macro", US"memory", US"noutf8", US"regex", NULL
 };
-int      debug_options_count	= nelem(debug_options);
+
 uschar   debuglog_name[LOG_NAME_SIZE] = {0};
 unsigned debug_pretrigger_bsize	= 0;
 uschar * debug_pretrigger_buf	= NULL;
-unsigned int debug_selector	= 0;
+bitmask_word_t * debug_selector = NULL;
 BOOL	 debug_startup		= FALSE;
 
 int     delay_warning[DELAY_WARNING_SIZE] = { DELAY_WARNING_SIZE, 1, 24*60*60 };
@@ -857,6 +826,7 @@ volatile sig_atomic_t had_command_sigterm = 0;
 volatile sig_atomic_t had_data_timeout    = 0;
 volatile sig_atomic_t had_data_sigint     = 0;
 const uschar *headers_charset  = US HEADERS_CHARSET;
+const uschar *header_from      = NULL;		/* mainly for dmarc */
 int     header_insert_maxlen   = 64 * 1024;
 header_line  *header_last      = NULL;
 header_line  *header_list      = NULL;
@@ -882,35 +852,35 @@ header_name header_names[] = {
 
 int header_names_size          = nelem(header_names);
 
-uschar *helo_accept_junk_hosts = NULL;
+const uschar *helo_accept_junk_hosts = NULL;
 uschar *helo_allow_chars       = US"";
 uschar *helo_lookup_domains    = US"@ : @[]";
-uschar *helo_try_verify_hosts  = NULL;
-uschar *helo_verify_hosts      = NULL;
+const uschar *helo_try_verify_hosts  = NULL;
+const uschar *helo_verify_hosts      = NULL;
 const uschar *hex_digits       = CUS"0123456789abcdef";
 uschar *hold_domains           = NULL;
 uschar *host_data              = NULL;
-uschar *host_lookup            = NULL;
+const uschar *host_lookup      = NULL;
 uschar *host_lookup_order      = US"bydns:byaddr";
 uschar *host_lookup_msg        = US"";
 int     host_number            = 0;
 uschar *host_number_string     = NULL;
-uschar *host_reject_connection = NULL;
-uschar *hosts_connection_nolog = NULL;
+const uschar *host_reject_connection = NULL;
+const uschar *hosts_connection_nolog = NULL;
 #ifdef SUPPORT_PROXY
 uschar *hosts_proxy            = NULL;
 #endif
 uschar *hosts_treat_as_local   = NULL;
-uschar *hosts_require_helo     = US"*";
+const uschar *hosts_require_helo = US"*";
 #ifdef EXPERIMENTAL_XCLIENT
-uschar *hosts_xclient	       = NULL;
+const uschar *hosts_xclient	= NULL;
 #endif
 tree_node *hostlist_anchor     = NULL;
 int     hostlist_count         = 0;
 
 
 int     ignore_bounce_errors_after = 10*7*24*60*60;  /* 10 weeks */
-uschar *ignore_fromline_hosts  = NULL;
+const uschar *ignore_fromline_hosts = NULL;
 int     inetd_wait_timeout     = -1;
 uschar *initial_cwd            = NULL;
 uschar *interface_address      = NULL;
@@ -923,13 +893,12 @@ uschar *keep_environment       = NULL;
 
 int     keep_malformed         = 4*24*60*60;    /* 4 days */
 
-uschar *eldap_dn               = NULL;
 const uschar *letter_digit_hyphen_dot =
     US"abcdefghijklmnopqrstuvwxyz"
       ".-0123456789"
       "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
 #ifndef DISABLE_ESMTP_LIMITS
-uschar *limits_advertise_hosts = US"*";
+const uschar *limits_advertise_hosts = US"*";
 #endif
 int     load_average           = -2;
 uschar *local_from_prefix      = NULL;
@@ -952,108 +921,119 @@ tree_node *localpartlist_anchor= NULL;
 int     localpartlist_count    = 0;
 uschar *log_buffer             = NULL;
 
-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_msg_id,
-  Li_queue_run,
-  Li_queue_time_exclusive,
-  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
+const uschar * log_default_names[] = {	/* for initializing log_selector */
+  US"acl_warn_skipped",
+  US"connection_reject",
+  US"delay_delivery",
+  US"dkim",
+  US"dnslist_defer",
+  US"etrn",
+  US"host_lookup_failed",
+  US"lost_incoming_connection",
+  US"outgoing_interface", /* see d_log_interface in deliver.c */
+  US"msg_id",
+  US"queue_run",
+  US"queue_time_exclusive",
+  US"rejected_header",
+  US"retry_defer",
+  US"sender_verify_fail",
+  US"size_reject",
+  US"skip_delivery",
+  US"smtp_confirmation",
+  US"tls_certificate_verified",
+  US"tls_cipher",
 };
+int     log_default_count      = nelem(log_default_names);
 
 uschar *log_file_path          = US LOG_FILE_PATH
                            "\0<--------------Space to patch log_file_path->";
 
-int     log_notall[]           = {
-  -1
-};
-bit_table log_options[]        = { /* must be in alphabetical order,
-				with definitions from enum logbit. */
-  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_id),
-  BIT_TABLE(L, connection_reject),
-  BIT_TABLE(L, delay_delivery),
-  BIT_TABLE(L, deliver_time),
-  BIT_TABLE(L, delivery_size),
+const uschar * const log_notall_names[] = { NULL };
+
+/* List of names for logging channels.  Must be in alphabetical order.
+Must match enum logging_test_bit (macros.h). */
+
+#define LOG_CHAN(chan) {.name = US #chan, .logchan_bit = Lt_##chan}
+
+bit_table log_channels[] = {
+  LOG_CHAN(8bitmime),
+  LOG_CHAN(acl_warn_skipped),
+  LOG_CHAN(address_rewrite),
+  LOG_CHAN(all_parents),
+  LOG_CHAN(arguments),
+  LOG_CHAN(connection_id),
+  LOG_CHAN(connection_reject),
+  LOG_CHAN(delay_delivery),
+  LOG_CHAN(deliver_time),
+  LOG_CHAN(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, msg_id),
-  BIT_TABLE(L, msg_id_created),
-  BIT_TABLE(L, outgoing_interface),
-  BIT_TABLE(L, outgoing_port),
-  BIT_TABLE(L, pid),
-  BIT_TABLE(L, pipelining),
-  BIT_TABLE(L, protocol_detail),
+  LOG_CHAN(dkim),
+  LOG_CHAN(dkim_verbose),
+#endif
+#ifdef EXIM_HAVE_DMARC
+  LOG_CHAN(dmarc),
+  LOG_CHAN(dmarc_verbose),
+#endif
+  LOG_CHAN(dnslist_defer),
+  LOG_CHAN(dnssec),
+  LOG_CHAN(dsn),
+  LOG_CHAN(etrn),
+  LOG_CHAN(host_lookup_failed),
+  LOG_CHAN(ident_timeout),
+  LOG_CHAN(incoming_interface),
+  LOG_CHAN(incoming_port),
+  LOG_CHAN(lost_incoming_connection),
+  LOG_CHAN(millisec),
+  LOG_CHAN(msg_id),
+  LOG_CHAN(msg_id_created),
+  LOG_CHAN(outgoing_interface),
+  LOG_CHAN(outgoing_port),
+  LOG_CHAN(pid),
+  LOG_CHAN(pipelining),
+  LOG_CHAN(protocol_detail),
 #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_exclusive),
-  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_on_connect),
-  BIT_TABLE(L, tls_peerdn),
-  BIT_TABLE(L, tls_resumption),
-  BIT_TABLE(L, tls_sni),
-  BIT_TABLE(L, unknown_in_list),
+  LOG_CHAN(proxy),
+#endif
+  LOG_CHAN(queue_run),
+  LOG_CHAN(queue_time),
+  LOG_CHAN(queue_time_exclusive),
+  LOG_CHAN(queue_time_overall),
+  LOG_CHAN(receive_time),
+  LOG_CHAN(received_recipients),
+  LOG_CHAN(received_sender),
+  LOG_CHAN(rejected_header),
+  LOG_CHAN(retry_defer),
+  LOG_CHAN(return_path_on_delivery),
+  LOG_CHAN(sender_on_delivery),
+  LOG_CHAN(sender_verify_fail),
+  LOG_CHAN(size_reject),
+  LOG_CHAN(skip_delivery),
+  LOG_CHAN(smtp_confirmation),
+  LOG_CHAN(smtp_connection),
+  LOG_CHAN(smtp_incomplete_transaction),
+  LOG_CHAN(smtp_mailauth),
+  LOG_CHAN(smtp_no_mail),
+  LOG_CHAN(smtp_protocol_error),
+  LOG_CHAN(smtp_syntax_error),
+#ifdef EXIM_HAVE_SPF
+  LOG_CHAN(spf),
+  LOG_CHAN(spf_verbose),
+#endif
+  LOG_CHAN(subject),
+  LOG_CHAN(tls_certificate_verified),
+  LOG_CHAN(tls_cipher),
+  LOG_CHAN(tls_on_connect),
+  LOG_CHAN(tls_peerdn),
+  LOG_CHAN(tls_resumption),
+  LOG_CHAN(tls_sni),
+  LOG_CHAN(unknown_in_list),
 };
-int     log_options_count      = nelem(log_options);
+#undef LOG_CHAN
+int     log_chan_count = nelem(log_channels);
 
 const uschar *log_ports	       = NULL;
 int     log_reject_target      = 0;
-unsigned int log_selector[log_selector_size]; /* initialized in main() */
+bitmask_word_t log_selector[log_selector_size]; /* initialized from main() */
 uschar *log_selector_string    = NULL;
 FILE   *log_stderr             = NULL;
 uschar *login_sender_address   = NULL;
@@ -1092,22 +1072,25 @@ const uschar *message_reference= NULL;
 /* MIME ACL expandables */
 #ifdef WITH_CONTENT_SCAN
 int     mime_anomaly_level     = 0;
-const uschar *mime_anomaly_text      = NULL;
-uschar *mime_boundary          = NULL;
-uschar *mime_charset           = NULL;
-uschar *mime_content_description = NULL;
-uschar *mime_content_disposition = NULL;
-uschar *mime_content_id        = NULL;
+const uschar *mime_anomaly_text= NULL;
+const uschar *mime_boundary    = NULL;
+const uschar *mime_charset     = NULL;
+const uschar *mime_content_description = NULL;
+const uschar *mime_content_disposition = NULL;
+const uschar *mime_content_id          = NULL;
 unsigned int mime_content_size = 0;
-uschar *mime_content_transfer_encoding = NULL;
-uschar *mime_content_type      = NULL;
+const uschar *mime_content_transfer_encoding = NULL;
+const uschar *mime_content_type= NULL;
 uschar *mime_decoded_filename  = NULL;
-uschar *mime_filename          = NULL;
+const uschar *mime_filename    = NULL;
 int     mime_is_multipart      = 0;
 int     mime_is_coverletter    = 0;
 int     mime_is_rfc822         = 0;
 int     mime_part_count        = -1;
 #endif
+#ifdef LOOKUP_MODULE_DIR
+DIR *	module_dir	       = NULL;
+#endif
 
 uid_t  *never_users            = NULL;
 uschar *notifier_socket        = US"$spool_directory/" NOTIFIER_SOCKET_NAME ;
@@ -1134,9 +1117,9 @@ uschar *percent_hack_domains   = NULL;
 const uschar *pid_file_path    = US PID_FILE_PATH
                            "\0<--------------Space to patch pid_file_path->";
 #ifndef DISABLE_PIPE_CONNECT
-uschar *pipe_connect_advertise_hosts = US"*";
+const uschar *pipe_connect_advertise_hosts = US"*";
 #endif
-uschar *pipelining_advertise_hosts = US"*";
+const uschar *pipelining_advertise_hosts = US"*";
 uschar *primary_hostname       = NULL;
 uschar *process_info;
 int     process_info_len       = 0;
@@ -1218,7 +1201,7 @@ uschar *received_protocol      = NULL;
 struct timeval received_time   = { 0, 0 };
 struct timeval received_time_complete = { 0, 0 };
 uschar *recipient_data         = NULL;
-uschar *recipient_unqualified_hosts = NULL;
+const uschar *recipient_unqualified_hosts = NULL;
 uschar *recipient_verify_failure = NULL;
 int     recipients_count       = 0;
 recipient_item  *recipients_list = NULL;
@@ -1245,7 +1228,7 @@ const pcre2_code *regex_whitelisted_macro = NULL;
 uschar *regex_match_string     = NULL;
 #endif
 int     remote_delivery_count  = 0;
-int     remote_max_parallel    = 4;
+int     remote_max_parallel    = 6;
 uschar *remote_sort_domains    = NULL;
 int     retry_data_expire      = 7*24*60*60;
 int     retry_interval_max     = 24*60*60;
@@ -1253,7 +1236,7 @@ int     retry_maximum_timeout  = 0;        /* set from retry config */
 retry_config  *retries         = NULL;
 const uschar *return_path            = NULL;
 int     rewrite_existflags     = 0;
-uschar *rfc1413_hosts          = US"@[]";
+const uschar *rfc1413_hosts    = US"@[]";
 int     rfc1413_query_timeout  = 0;
 uid_t   root_gid               = ROOT_GID;
 uid_t   root_uid               = ROOT_UID;
@@ -1319,7 +1302,7 @@ uschar *sender_rate            = NULL;
 uschar *sender_rate_limit      = NULL;
 uschar *sender_rate_period     = NULL;
 uschar *sender_rcvhost         = NULL;
-uschar *sender_unqualified_hosts = NULL;
+const uschar *sender_unqualified_hosts = NULL;
 uschar *sender_verify_failure = NULL;
 address_item *sender_verified_list  = NULL;
 address_item *sender_verified_failed = NULL;
@@ -1333,7 +1316,7 @@ int     slow_lookup_log        = 0;	/* millisecs, zero disables */
 int     smtp_accept_count      = 0;
 int     smtp_accept_max        = 20;
 int     smtp_accept_max_nonmail= 10;
-uschar *smtp_accept_max_nonmail_hosts = US"*";
+const uschar *smtp_accept_max_nonmail_hosts = US"*";
 uschar *smtp_accept_max_per_connection = US"1000";
 uschar *smtp_accept_max_per_host = NULL;
 int     smtp_accept_queue      = 0;
@@ -1364,12 +1347,12 @@ int     smtp_max_unknown_commands = 3;
 const uschar *smtp_notquit_reason = NULL;
 unsigned smtp_peer_options     = 0;
 unsigned smtp_peer_options_wrap= 0;
-uschar *smtp_ratelimit_hosts   = NULL;
+const uschar *smtp_ratelimit_hosts = NULL;
 uschar *smtp_ratelimit_mail    = NULL;
 uschar *smtp_ratelimit_rcpt    = NULL;
 int     smtp_receive_timeout   = 5*60;
 uschar *smtp_receive_timeout_s = NULL;
-uschar *smtp_reserve_hosts     = NULL;
+const uschar *smtp_reserve_hosts = NULL;
 int     smtp_rlm_base          = 0;
 double  smtp_rlm_factor        = 0.0;
 int     smtp_rlm_limit         = 0;
@@ -1379,7 +1362,7 @@ double  smtp_rlr_factor        = 0.0;
 int     smtp_rlr_limit         = 0;
 int     smtp_rlr_threshold     = INT_MAX;
 #ifdef SUPPORT_I18N
-uschar *smtputf8_advertise_hosts = US"*";	/* overridden under test-harness */
+const uschar *smtputf8_advertise_hosts = US"*";	/* overridden under test-harness */
 #endif
 
 #ifdef WITH_CONTENT_SCAN
@@ -1486,8 +1469,8 @@ uschar *uucp_from_sender       = US"$1";
 
 uschar *verify_mode	       = NULL;
 uschar *version_copyright      =
- US"Copyright (c) University of Cambridge, 1995 - 2018\n"
-   "(c) The Exim Maintainers and contributors in ACKNOWLEDGMENTS file, 2007 - 2025";
+ US"Copyright (c) The Exim Maintainers and contributors in ACKNOWLEDGMENTS file, 2007 - 2026\n"
+ "Copyright (c) University of Cambridge, 1995 - 2018\n";
 uschar *version_date           = US"?";
 uschar *version_cnumber        = US"????";
 uschar *version_string         = US"?";
@@ -1498,7 +1481,7 @@ const uschar *warnmsg_delay    = NULL;
 const uschar *warnmsg_recipients = NULL;
 
 #ifndef DISABLE_WELLKNOWN
-uschar *wellknown_advertise_hosts = NULL;
+const uschar *wellknown_advertise_hosts = NULL;
 uschar *wellknown_response     = NULL;
 #endif
 
diff --git a/src/src/globals.h b/src/src/globals.h
index cb6b91c5a..71504f070 100644
--- a/src/src/globals.h
+++ b/src/src/globals.h
@@ -40,43 +40,6 @@ extern BOOL    opt_perl_taintmode;     /* Enable taint mode in Perl */
 extern tree_node *dlobj_anchor;        /* Tree of dynamically-loaded objects */
 #endif
 
-#ifdef LOOKUP_IBASE
-extern uschar *ibase_servers;
-#endif
-
-#ifdef LOOKUP_LDAP
-extern uschar *eldap_ca_cert_dir;      /* Directory with CA certificates */
-extern uschar *eldap_ca_cert_file;     /* CA certificate file */
-extern uschar *eldap_cert_file;        /* Certificate file */
-extern uschar *eldap_cert_key;         /* Certificate key file */
-extern uschar *eldap_cipher_suite;     /* Allowed cipher suite */
-extern uschar *eldap_default_servers;  /* List of default servers */
-extern uschar *eldap_require_cert;     /* Peer certificate checking strategy */
-extern BOOL    eldap_start_tls;        /* Use STARTTLS */
-extern int     eldap_version;          /* LDAP version */
-#endif
-
-#ifdef LOOKUP_MYSQL
-extern uschar *mysql_servers;          /* List of servers and connect info */
-#endif
-
-#ifdef LOOKUP_ORACLE
-extern uschar *oracle_servers;         /* List of servers and connect info */
-#endif
-
-#ifdef LOOKUP_PGSQL
-extern uschar *pgsql_servers;          /* List of servers and connect info */
-#endif
-
-#ifdef LOOKUP_REDIS
-extern uschar *redis_servers;          /* List of servers and connect info */
-#endif
-
-#ifdef LOOKUP_SQLITE
-extern uschar *sqlite_dbfile;	       /* Filname for database */
-extern int     sqlite_lock_timeout;    /* Internal lock waiting timeout */
-#endif
-
 #ifdef SUPPORT_MOVE_FROZEN_MESSAGES
 extern BOOL    move_frozen_messages;   /* Get them out of the normal directory */
 #endif
@@ -123,6 +86,7 @@ typedef struct {
   BOOL	  channelbind_exporter:1; /* channelbinding is EXPORTER not UNIQUE */
   BOOL    on_connect:1;         /* For older MTAs that don't STARTTLS */
   BOOL	  verify_override:1;	/* certificate_verified only due to tls_try_verify_hosts */
+  BOOL	  smtp_quit:1;		/* QUIT has been sent or received */
 } tls_support;
 extern tls_support tls_in;
 extern tls_support tls_out;
@@ -131,7 +95,7 @@ extern tls_support tls_out;
 #ifndef DISABLE_TLS
 extern BOOL    gnutls_compat_mode;     /* Less security, more compatibility */
 extern BOOL    gnutls_allow_auto_pkcs11; /* Let GnuTLS autoload PKCS11 modules */
-extern uschar *hosts_require_alpn;     /* Mandatory ALPN successful nogitiation */
+extern const uschar *hosts_require_alpn; /* Mandatory ALPN successful nogitiation */
 extern uschar *openssl_options;        /* OpenSSL compatibility options */
 extern const pcre2_code *regex_STARTTLS;     /* For recognizing STARTTLS settings */
 extern uschar *tls_alpn;	       /* ALPN names acceptable */
@@ -139,7 +103,7 @@ extern uschar *tls_certificate;        /* Certificate file */
 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_early_banner_hosts; /* use Early Data for SMTP banner */
+extern const uschar *tls_early_banner_hosts; /* use Early Data for SMTP banner */
 extern uschar *tls_eccurve;            /* EC curve */
 # ifndef DISABLE_OCSP
 extern uschar *tls_ocsp_file;          /* OCSP stapling proof file */
@@ -149,36 +113,25 @@ extern uschar *tls_privatekey;         /* Private key file */
 extern BOOL    tls_remember_esmtp;     /* For YAEB */
 extern uschar *tls_require_ciphers;    /* So some can be avoided */
 # ifndef DISABLE_TLS_RESUME
-extern uschar *tls_resumption_hosts;   /* TLS session resumption */
+extern const uschar *tls_resumption_hosts; /* TLS session resumption */
 # endif
-extern uschar *tls_try_verify_hosts;   /* Optional client verification */
+extern const 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 */
+extern const uschar *tls_verify_hosts; /* Mandatory client verification */
 extern int     tls_watch_fd;	       /* for inotify of creds files */
 extern time_t  tls_watch_trigger_time; /* non-0: triggered */
 #endif
-extern uschar *tls_advertise_hosts;    /* host for which TLS is advertised */
+const extern uschar *tls_advertise_hosts; /* host for which TLS is advertised */
 
 extern uschar  *dsn_envid;             /* DSN envid string */
 extern int      dsn_ret;               /* DSN ret type*/
 extern const pcre2_code  *regex_DSN;         /* For recognizing DSN settings */
-extern uschar  *dsn_advertise_hosts;   /* host for which TLS is advertised */
+extern const uschar  *dsn_advertise_hosts;   /* host for which TLS is advertised */
 
 /* Input-reading functions for messages, so we can use special ones for
 incoming TCP/IP. */
 
-extern int (*lwr_receive_getc)(unsigned);
-extern uschar * (*lwr_receive_getbuf)(unsigned *);
-extern BOOL (*lwr_receive_hasc)(void);
-extern int (*lwr_receive_ungetc)(int);
-
-extern int (*receive_getc)(unsigned);
-extern uschar * (*receive_getbuf)(unsigned *);
-extern BOOL (*receive_hasc)(void);
-extern void (*receive_get_cache)(unsigned);
-extern int (*receive_ungetc)(int);
-extern int (*receive_feof)(void);
-extern int (*receive_ferror)(void);
+extern const in_processing * rx_prc;
 
 
 /* For clearing, saving, restoring address expansion variables. We have to have
@@ -224,7 +177,7 @@ extern struct global_flags {
  BOOL   dkim_disable_verify		:1; /* Set via ACL control statement. When set, DKIM verification is disabled for the current message */
  BOOL   dkim_init_done			:1; /* lazy-init status */
 #endif
-#ifdef SUPPORT_DMARC
+#ifdef EXIM_HAVE_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 */
@@ -234,6 +187,7 @@ extern struct global_flags {
 
  BOOL   enable_dollar_recipients	:1; /* Make $recipients available */
  BOOL   expand_string_forcedfail	:1; /* TRUE if failure was "expected" */
+ BOOL   expansion_test			:1; /* TRUE for -be test mode */
 
  BOOL   filter_running			:1; /* TRUE while running a filter */
 
@@ -258,15 +212,15 @@ extern struct global_flags {
  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_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)*/
+#ifdef SUPPORT_PROXY
+ BOOL   quit_cmd_only			:1; /* TRUE if required proxy negotiation failed */
+#endif
 
  BOOL   really_exim			:1; /* FALSE in utilities */
  BOOL   receive_call_bombout		:1; /* Flag for crashing log */
@@ -379,7 +333,7 @@ extern uschar *authenticated_id;       /* ID that was authenticated */
 extern uschar *authenticated_sender;   /* From AUTH on MAIL */
 extern BOOL    authentication_failed;  /* TRUE if AUTH was tried and failed */
 extern uschar *authenticator_name;     /* for debug and error messages */
-extern uschar *auth_advertise_hosts;   /* Only advertise to these */
+extern const uschar *auth_advertise_hosts; /* Only advertise to these */
 extern auth_info *auths_available;	/* List of available auth mechanisms */
 extern auth_instance *auths;           /* Chain of instantiated auths */
 extern auth_instance auth_defaults;    /* Default values */
@@ -392,21 +346,22 @@ extern int     av_failed;              /* TRUE if the AV process failed */
 extern uschar *av_scanner;             /* AntiVirus scanner to use for the malware condition */
 #endif
 
+extern lookup_module_info * avail_static_lookups[];/* List of built-in lookup drivers */
+
+extern const uschar * avail_static_auths[];	/* List of built-in driver names */
+extern const uschar * avail_dynamic_auths[];	/* List of dynamic-load driver names */
+extern const uschar * avail_static_routers[];
+extern const uschar * avail_dynamic_routers[];
+extern const uschar * avail_static_transports[];
+extern const uschar * avail_dynamic_transports[];
+
 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 int     big_buffer_size;        /* Current size (can expand) */
-#ifdef EXPERIMENTAL_BRIGHTMAIL
-extern uschar *bmi_alt_location;       /* expansion variable that contains the alternate location for the rcpt (available during routing) */
-extern uschar *bmi_base64_tracker_verdict; /* expansion variable with base-64 encoded OLD verdict string (available during routing) */
-extern uschar *bmi_base64_verdict;     /* expansion variable with base-64 encoded verdict string (available during routing) */
-extern uschar *bmi_config_file;        /* Brightmail config file */
-extern int     bmi_deliver;            /* Flag that determines if the message should be delivered to the rcpt (available during routing) */
-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_charset;	       /* value for Content-Type: headers */
 extern uschar *bounce_message_file;    /* Template file */
 extern uschar *bounce_message_text;    /* One-liner */
 extern const uschar *bounce_recipient; /* When writing an errmsg */
@@ -428,7 +383,7 @@ 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 int_eximarith_t check_spool_space; /* Minimum for message acceptance */
-extern uschar *chunking_advertise_hosts;    /* RFC 3030 CHUNKING */
+extern const uschar *chunking_advertise_hosts; /* RFC 3030 CHUNKING */
 extern unsigned chunking_datasize;
 extern unsigned chunking_data_left;
 extern chunking_state_t chunking_state;
@@ -476,11 +431,14 @@ extern open_db *continue_wait_db;      /* Hintsdb for wait-transport */
 extern uschar *csa_status;             /* Client SMTP Authorization result */
 
 typedef struct {
-  unsigned     callout_hold_only:1;    /* Conn is only for verify callout */
-  unsigned     delivery:1;             /* When to attempt */
-  unsigned     tpt_sender:1;           /* Use tpt-defined sender */
-  unsigned     defer_pass:1;           /* Pass 4xx to caller rather than spooling */
-  unsigned     is_tls:1;	       /* Conn has TLS active */
+  BOOL		callout_hold_only:1;    /* Conn is only for verify callout */
+  BOOL		delivery:1;             /* When to attempt */
+  BOOL		tpt_sender:1;           /* Use tpt-defined sender */
+  BOOL		defer_pass:1;           /* Pass 4xx to caller rather than spooling */
+  BOOL		is_tls:1;	       /* Conn has TLS active */
+  BOOL		is_dane:1;
+  uschar *	sni;
+  uschar *	cipher;
   client_conn_ctx cctx;                /* Open connection */
   int          nrcpt;                  /* Count of addresses */
   uschar *     transport;	       /* Name of transport */
@@ -493,6 +451,7 @@ typedef struct {
 } cut_t;
 extern cut_t cutthrough;               /* Deliver-concurrently */
 
+extern uschar *daemon_modules_load;    /* Dyn-load modules to preload */
 extern int     daemon_notifier_fd;     /* Unix socket for notifications */
 extern uschar *daemon_smtp_port;       /* Can be a list of ports */
 extern int     daemon_startup_retries; /* Number of times to retry */
@@ -506,11 +465,15 @@ extern uschar *dccifd_address;         /* address of the dccifd daemon */
 extern uschar *dccifd_options;         /* options for the dccifd daemon */
 #endif
 
+extern bit_table debug_channels[];     /* names + numbers */
+extern const uschar * debug_defaults;			/* chans for "-d" */
+extern const uschar * const debug_notall_names[];	/* chans not in "all" */
+extern bitmask_word_t * debug_selector;   /* Debugging bits */
+
+
 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 int     debug_chan_count;       /* Size of table */
 extern unsigned debug_pretrigger_bsize;
 extern uschar *debug_pretrigger_buf;   /* circular buffer for precapture */
 extern BOOL    debug_startup;	       /* Pre-config-read debugging */
@@ -575,8 +538,6 @@ 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 */
 
-/* This option is now a no-opt, retained for compatibility */
-extern BOOL    drop_cr;                /* For broken local MUAs */
 extern const uschar *driver_srcfile;   /* For debug & errors */
 extern int     driver_srcline;	       /* For debug & errors */
 
@@ -634,32 +595,33 @@ 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 const uschar *header_from;      /* Text of the From: header */
 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 uschar *helo_accept_junk_hosts; /* Allowed to use junk arg */
+extern const 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_verify_hosts;      /* Hard check HELO argument for these */
+extern const uschar *helo_try_verify_hosts; /* Soft check HELO argument for these */
+extern const 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 *host_data;              /* Obtained from lookup in ACL */
-extern uschar *host_lookup;            /* For which IP addresses are always looked up */
+extern const uschar *host_lookup;      /* For which IP addresses are always looked up */
 extern BOOL    host_lookup_deferred;   /* TRUE if lookup deferred */
 extern BOOL    host_lookup_failed;     /* TRUE if lookup failed */
 extern uschar *host_lookup_order;      /* Order of host lookup types */
 extern uschar *host_lookup_msg;        /* Text for why it failed */
 extern int     host_number;            /* For sharing spools */
 extern uschar *host_number_string;     /* For expanding */
-extern uschar *host_reject_connection; /* Reject these hosts */
-extern uschar *hosts_connection_nolog; /* Limits the logging option */
-extern uschar *hosts_require_helo;     /* check for HELO/EHLO before MAIL */
+extern const uschar *host_reject_connection; /* Reject these hosts */
+extern const uschar *hosts_connection_nolog; /* Limits the logging option */
+extern const uschar *hosts_require_helo;     /* check for HELO/EHLO before MAIL */
 extern uschar *hosts_treat_as_local;   /* For routing */
 #ifdef EXPERIMENTAL_XCLIENT
-extern uschar *hosts_xclient;	       /* Allow XCLIENT command for specified hosts */
+extern const uschar *hosts_xclient;    /* Allow XCLIENT command for specified hosts */
 #endif
 extern tree_node *hostlist_anchor;     /* Tree of defined host lists */
 extern int     hostlist_count;         /* Number defined */
@@ -667,7 +629,7 @@ extern int     hostlist_count;         /* Number defined */
 
 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 const uschar *ignore_fromline_hosts; /* Hosts permitted to send "From "*/
 extern int     inetd_wait_timeout;     /* Timeout for inetd wait mode */
 extern uschar *initial_cwd;            /* The directory we where in at startup */
 extern uschar *iterate_item;           /* Item from iterate list */
@@ -677,10 +639,9 @@ extern int     journal_fd;             /* Fd for journal file */
 extern uschar *keep_environment;       /* Whitelist for environment variables */
 extern int     keep_malformed;         /* Time to keep malformed messages */
 
-extern uschar *eldap_dn;               /* Where LDAP DNs are left */
 extern const uschar *letter_digit_hyphen_dot; /* Legitimate DNS host name chars */
 #ifndef DISABLE_ESMTP_LIMITS
-extern uschar *limits_advertise_hosts; /* for banner/EHLO pipelining */
+extern const uschar *limits_advertise_hosts; /* for banner/EHLO pipelining */
 #endif
 extern int     load_average;           /* Most recently read load average */
 extern BOOL    local_from_check;       /* For adding Sender: (global value) */
@@ -699,14 +660,15 @@ 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 int     log_default[];          /* Initialization list for log_selector */
+extern const uschar * log_default_names[]; /* Init list for log_selector */
+extern int     log_default_count;      /* Size of table */
 extern uschar *log_file_path;          /* If unset, use default */
 extern const uschar *log_ports;	       /* If set, port numbers to log */
-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 bit_table log_channels[];       /* Table of options */
+extern int     log_chan_count;         /* Size of table */
+extern const uschar * const log_notall_names[];	/* chans not in "all" */
 extern int     log_reject_target;      /* Target log for ACL rejections */
-extern unsigned int log_selector[];    /* Bit map of logging options */
+extern bitmask_word_t log_selector[];  /* Bit map of logging options */
 extern uschar *log_selector_string;    /* As supplied in the config */
 extern FILE   *log_stderr;             /* Copy of stderr for log use, or NULL */
 extern BOOL    log_timezone;           /* TRUE to include the timezone in log lines */
@@ -754,21 +716,24 @@ extern const uschar *message_reference;/* Reference for error messages */
 #ifdef WITH_CONTENT_SCAN
 extern int     mime_anomaly_level;
 extern const uschar *mime_anomaly_text;
-extern uschar *mime_boundary;
-extern uschar *mime_charset;
-extern uschar *mime_content_description;
-extern uschar *mime_content_disposition;
-extern uschar *mime_content_id;
+extern const uschar *mime_boundary;
+extern const uschar *mime_charset;
+extern const uschar *mime_content_description;
+extern const uschar *mime_content_disposition;
+extern const uschar *mime_content_id;
 extern unsigned int mime_content_size;
-extern uschar *mime_content_transfer_encoding;
-extern uschar *mime_content_type;
+extern const uschar *mime_content_transfer_encoding;
+extern const uschar *mime_content_type;
 extern uschar *mime_decoded_filename;
-extern uschar *mime_filename;
+extern const uschar *mime_filename;
 extern int     mime_is_multipart;
 extern int     mime_is_coverletter;
 extern int     mime_is_rfc822;
 extern int     mime_part_count;
 #endif
+#ifdef LOOKUP_MODULE_DIR
+extern DIR *   module_dir;
+#endif
 
 extern BOOL    mua_wrapper;            /* TRUE when Exim is wrapping an MUA */
 
@@ -803,9 +768,9 @@ extern pcre2_compile_context * pcre_mlc_cmp_ctx;
 extern uschar *percent_hack_domains;   /* Local domains for which '% operates */
 extern const uschar *pid_file_path;    /* For writing daemon pids */
 #ifndef DISABLE_PIPE_CONNECT
-extern uschar *pipe_connect_advertise_hosts; /* for banner/EHLO pipelining */
+extern const uschar *pipe_connect_advertise_hosts; /* for banner/EHLO pipelining */
 #endif
-extern uschar *pipelining_advertise_hosts; /* As it says */
+extern const uschar *pipelining_advertise_hosts; /* As it says */
 #ifndef DISABLE_PRDR
 extern BOOL    prdr_enable;            /* As it says */
 extern BOOL    prdr_requested;         /* Connecting mail server wants PRDR */
@@ -854,7 +819,8 @@ 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_run_in_order;     /* As opposed to random */
+extern BOOL    queue_run_in_order;     /* (obsolete) As opposed to random */
+extern const uschar * queue_run_order; /* undef/old/random/new-first */
 extern uschar *queue_run_max;          /* Max queue runners */
 extern unsigned queue_size;            /* items in queue */
 extern time_t  queue_size_next;        /* next time to evaluate queue_size */
@@ -884,7 +850,7 @@ extern int     received_headers_max;   /* Max count of Received: headers */
 extern struct timeval received_time;   /* Time the message started to be received */
 extern struct timeval received_time_complete; /* Time the message completed reception */
 extern uschar *recipient_data;         /* lookup data for recipients */
-extern uschar *recipient_unqualified_hosts; /* Permitted unqualified recipients */
+extern const uschar *recipient_unqualified_hosts; /* Permitted unqualified recipients */
 extern uschar *recipient_verify_failure; /* What went wrong */
 extern int     recipients_list_max;    /* Maximum number fitting in list */
 extern uschar *recipients_max;         /* Max permitted */
@@ -923,7 +889,7 @@ extern int     retry_maximum_timeout;  /* The maximum timeout */
 extern const uschar *return_path;            /* Return path for a message */
 extern BOOL    return_path_remove;     /* Remove return-path headers */
 extern int     rewrite_existflags;     /* Indicate which headers have rewrites */
-extern uschar *rfc1413_hosts;          /* RFC hosts */
+extern const uschar *rfc1413_hosts;    /* RFC hosts */
 extern int     rfc1413_query_timeout;  /* Timeout on RFC 1413 calls */
 /* extern BOOL    rfc821_domains;  */       /* If set, syntax is 821, not 822 => being abolished */
 extern uid_t   root_gid;               /* The gid for root */
@@ -956,7 +922,7 @@ 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_unqualified_hosts; /* Permitted unqualified senders */
+extern const 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 address_item *sender_verified_failed; /* The one that caused denial */
@@ -970,7 +936,7 @@ 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_max_nonmail;/* Max non-mail commands in one con */
-extern uschar *smtp_accept_max_nonmail_hosts; /* Limit non-mail cmds from these hosts */
+extern const uschar *smtp_accept_max_nonmail_hosts; /* Limit non-mail cmds from these hosts */
 extern uschar *smtp_accept_max_per_connection; /* Max msgs per connection */
 extern uschar *smtp_accept_max_per_host; /* Max SMTP cons from one IP addr */
 extern int     smtp_accept_queue;      /* Queue after so many connections */
@@ -1001,12 +967,12 @@ extern int     smtp_max_unknown_commands; /* As it says */
 extern uschar *smtp_names[];	       /* decode for command codes */
 extern const uschar *smtp_notquit_reason; /* Global for disconnect reason */
 extern int     smtp_out_fd;	       /* Incoming SMTP output file */
-extern uschar *smtp_ratelimit_hosts;   /* Rate limit these hosts */
+extern const uschar *smtp_ratelimit_hosts; /* Rate limit these hosts */
 extern uschar *smtp_ratelimit_mail;    /* Parameters for MAIL limiting */
 extern uschar *smtp_ratelimit_rcpt;    /* Parameters for RCPT limiting */
 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 const 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 double  smtp_rlm_factor;        /* Factor for MAIL rate limit */
@@ -1019,7 +985,7 @@ extern int     smtp_rlr_threshold;     /* Threshold for RCPT rate limit */
 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 */
+extern const uschar *smtputf8_advertise_hosts; /* ingress control */
 #endif
 
 #ifdef WITH_CONTENT_SCAN
@@ -1037,6 +1003,7 @@ extern BOOL    spool_wireformat;       /* can write wireformat -D files */
 #ifdef SUPPORT_SRS
 extern uschar *srs_recipient;          /* SRS recipient */
 #endif
+extern in_processing stdin_template;   /* stackable input processing methods */
 extern BOOL    strict_acl_vars;        /* ACL variables have to be set before being used */
 extern int     string_datestamp_offset;/* After insertion by string_format */
 extern int     string_datestamp_length;/* After insertion by string_format */
@@ -1114,7 +1081,7 @@ extern uschar *version_string;         /* Version string */
 extern int     warning_count;          /* Delay warnings sent for this msg */
 
 #ifndef DISABLE_WELLKNOWN
-extern uschar *wellknown_advertise_hosts;/* Allow WELLKNOWN command for specified hosts */
+extern const uschar *wellknown_advertise_hosts; /* Allow WELLKNOWN command for specified hosts */
 extern uschar *wellknown_response;     /* SMTP response for WELLKNOWN verb */
 #endif
 
diff --git a/src/src/hash.c b/src/src/hash.c
index e5abada37..b8a804be2 100644
--- a/src/src/hash.c
+++ b/src/src/hash.c
@@ -836,7 +836,9 @@ for (i = 0; i < sizeof(tests)/sizeof(uschar *); i ++)
 
 /* 1 000 000 repetitions of "a" */
 
-ctest = malloc(1000000);
+if (!(ctest = malloc(1000000)))
+  { perror("malloc"); exit(EXIT_FAILURE); }
+
 memset(ctest, 'a', 1000000);
 
 printf("1 000 000 repetitions of 'a'\n");
diff --git a/src/src/header.c b/src/src/header.c
index 3a513fd2a..e9d83c092 100644
--- a/src/src/header.c
+++ b/src/src/header.c
@@ -109,7 +109,7 @@ gs.size = HEADER_ADD_BUFFER_SIZE;
 gs.ptr = 0;
 
 if (!string_vformat(&gs, SVFMT_REBUFFER, format, ap))
-  log_write_die(0, LOG_MAIN, "string too long in header_add: "
+  log_write_die(LOG_MAIN, "string too long in header_add: "
     "%.100Y ...", &gs);
 
 if (gs.s != buf) store_release_above(buf);
diff --git a/src/src/hintsdb.h b/src/src/hintsdb.h
index 969d0ebfe..6bea085ac 100644
--- a/src/src/hintsdb.h
+++ b/src/src/hintsdb.h
@@ -77,7 +77,6 @@ static inline BOOL is_tainted(const void *);
 # include "hintsdb/hints_sqlite.h"
 
 #elif defined(USE_TDB)
-
 # if defined(USE_DB) || defined(USE_GDBM) || defined(USE_SQLITE)
 #  error USE_TDB conflict with alternate definition
 # endif
@@ -111,7 +110,7 @@ exim_dbopen(const uschar * name, const uschar * dirname, int flags,
   unsigned mode)
 {
 void * dbp;
-DEBUG(D_hints_lookup)
+DEBUG(hints_lookup)
   debug_printf_indent("EXIM_DBOPEN: file <%s> dir <%s> flags=%s\n",
     name, dirname,
     flags == O_RDONLY ? "O_RDONLY"
@@ -120,13 +119,13 @@ DEBUG(D_hints_lookup)
     : "??");
 if (is_tainted(name) || is_tainted(dirname))
   {
-  log_write(0, LOG_MAIN|LOG_PANIC, "Tainted name for DB file not permitted");
+  log_write(LOG_MAIN|LOG_PANIC, "Tainted name for DB file not permitted");
   dbp = NULL;
   }
 else
   dbp = exim_dbopen__(name, dirname, flags, mode);
 
-DEBUG(D_hints_lookup) debug_printf_indent("returned from EXIM_DBOPEN: %p\n", dbp);
+DEBUG(hints_lookup) debug_printf_indent("returned from EXIM_DBOPEN: %p\n", dbp);
 return dbp;
 }
 
@@ -135,7 +134,7 @@ exim_dbopen_multi(const uschar * name, const uschar * dirname, int flags,
   unsigned mode)
 {
 void * dbp;
-DEBUG(D_hints_lookup)
+DEBUG(hints_lookup)
   debug_printf_indent("EXIM_DBOPEN_MULTI: file <%s> dir <%s> flags=%s\n",
     name, dirname,
     flags == O_RDONLY ? "O_RDONLY"
@@ -144,26 +143,26 @@ DEBUG(D_hints_lookup)
     : "??");
 if (is_tainted(name) || is_tainted(dirname))
   {
-  log_write(0, LOG_MAIN|LOG_PANIC, "Tainted name for DB file not permitted");
+  log_write(LOG_MAIN|LOG_PANIC, "Tainted name for DB file not permitted");
   dbp = NULL;
   }
 else
   dbp = exim_dbopen_multi__(name, dirname, flags, mode);
 
-DEBUG(D_hints_lookup) debug_printf_indent("returned from EXIM_DBOPEN_MULTI: %p\n", dbp);
+DEBUG(hints_lookup) debug_printf_indent("returned from EXIM_DBOPEN_MULTI: %p\n", dbp);
 return dbp;
 }
 
 static inline void
 exim_dbclose(EXIM_DB * dbp)
 {
-DEBUG(D_hints_lookup) debug_printf_indent("EXIM_DBCLOSE(%p)\n", dbp);
+DEBUG(hints_lookup) debug_printf_indent("EXIM_DBCLOSE(%p)\n", dbp);
 exim_dbclose__(dbp);
 }
 static inline void
 exim_dbclose_multi(EXIM_DB * dbp)
 {
-DEBUG(D_hints_lookup) debug_printf_indent("EXIM_DBCLOSE_MULTI(%p)\n", dbp);
+DEBUG(hints_lookup) debug_printf_indent("EXIM_DBCLOSE_MULTI(%p)\n", dbp);
 exim_dbclose_multi__(dbp);
 }
 
diff --git a/src/src/hintsdb/hints_bdb.h b/src/src/hintsdb/hints_bdb.h
index 7285e85f9..dd5cd888f 100644
--- a/src/src/hintsdb/hints_bdb.h
+++ b/src/src/hintsdb/hints_bdb.h
@@ -72,7 +72,7 @@ at DB release 4.3. */
 static inline void
 dbfn_bdb_error_callback(const DB_ENV * dbenv, const char * pfx, const char * msg)
 {
-log_write(0, LOG_MAIN, "Berkeley DB error: %s", msg);
+log_write(LOG_MAIN, "Berkeley DB error: %s", msg);
 }
 
 
@@ -115,11 +115,11 @@ if (db_create(&b, dbp, 0) == 0)
   if (b->open(b, NULL, CS name, NULL,
 	      flags & O_CREAT ? DB_HASH : DB_UNKNOWN,
 	      flags & O_CREAT ? DB_CREATE
-	      : flags & (O_WRONLY|O_RDWR) ? 0 : DB_RDONLY,
+	      : (flags & O_ACCMODE) == O_RDONLY ? DB_RDONLY : 0,
 	      mode) == 0
 	  )
     return dbp;
-  else DEBUG(D_hints_lookup)
+  else DEBUG(hints_lookup)
     debug_printf_indent("bdb_open(flags 0x%x mode %04o) %s\n",
 	      flags, mode, strerror(errno));
 
@@ -266,7 +266,7 @@ return db_create(&dbp, NULL, 0) == 0
 	dbp->open(dbp, CS name, NULL,
 	  flags & O_CREAT ? DB_HASH : DB_UNKNOWN,
 	  flags & O_CREAT ? DB_CREATE
-	  : flags & (O_WRONLY|O_RDWR) ? 0 : DB_RDONLY,
+	  : (flags & O_ACCMODE) == O_RDONLY ? DB_RDONLY : 0,
 	  mode)
      ) == 0
   ? dbp : NULL;
diff --git a/src/src/hintsdb/hints_gdbm.h b/src/src/hintsdb/hints_gdbm.h
index 00c94a777..11a2e655e 100644
--- a/src/src/hintsdb/hints_gdbm.h
+++ b/src/src/hintsdb/hints_gdbm.h
@@ -58,12 +58,12 @@ if (dbp)
   dbp->lkey.dptr = NULL;
   dbp->gdbm = gdbm_open(CS name, 0,
     flags & O_CREAT ? GDBM_WRCREAT
-    : flags & (O_RDWR|O_WRONLY) ? GDBM_WRITER : GDBM_READER,
+    : (flags & O_ACCMODE) == O_RDONLY ? GDBM_READER : GDBM_WRITER,
     mode, 0);
   if (dbp->gdbm)
     return dbp;
 
-  DEBUG(D_hints_lookup)
+  DEBUG(hints_lookup)
     debug_printf_indent("gdbm_open(flags 0x%x mode %04o) %s\n",
 	      flags, mode, strerror(errno));
   free(dbp);
diff --git a/src/src/hintsdb/hints_ndbm.h b/src/src/hintsdb/hints_ndbm.h
index ceee975e1..2a28509c7 100644
--- a/src/src/hintsdb/hints_ndbm.h
+++ b/src/src/hintsdb/hints_ndbm.h
@@ -62,7 +62,7 @@ else if ((res = dbm_open(CS name, flags, mode)))
   return res;
 
 else
-  DEBUG(D_hints_lookup)
+  DEBUG(hints_lookup)
     debug_printf_indent("ndbm_open(flags 0x%x mode %04o) %s\n",
 	      flags, mode, strerror(errno));
 return NULL;
diff --git a/src/src/hintsdb/hints_sqlite.h b/src/src/hintsdb/hints_sqlite.h
index 773d71ded..13c409214 100644
--- a/src/src/hintsdb/hints_sqlite.h
+++ b/src/src/hintsdb/hints_sqlite.h
@@ -39,7 +39,8 @@ exim_dbopen_multi__(const uschar * name, const uschar * dirname, int flags,
   unsigned mode)
 {
 EXIM_DB * dbp;
-int ret, sflags = flags & O_RDWR ? SQLITE_OPEN_READWRITE : SQLITE_OPEN_READONLY;
+int ret, sflags = (flags & O_ACCMODE) == O_RDONLY
+		  ? SQLITE_OPEN_READONLY : SQLITE_OPEN_READWRITE;
 
 if (flags & O_CREAT) sflags |= SQLITE_OPEN_CREATE;
 if ((ret = sqlite3_open_v2(CCS name, &dbp, sflags, NULL)) == SQLITE_OK)
@@ -54,7 +55,7 @@ if ((ret = sqlite3_open_v2(CCS name, &dbp, sflags, NULL)) == SQLITE_OK)
   /* in case we are migrating, drop the old table, return code not needed */
   (void) sqlite3_exec(dbp, "DROP TABLE IF EXISTS tbl;", NULL, NULL, NULL);
   }
-else DEBUG(D_hints_lookup)
+else DEBUG(hints_lookup)
   debug_printf_indent("sqlite_open(flags 0x%x mode %04o) %s\n",
 		      flags, mode, sqlite3_errmsg(dbp));
 return ret == SQLITE_OK ? dbp : NULL;
@@ -114,7 +115,7 @@ if (SQLITE_OK != sqlite3_prepare_v2(dbp, query, strlen(query), &stmt, NULL))
   }
 
 # ifdef SQL_DEBUG
-DEBUG(D_hints_lookup) debug_printf_indent("prepared SQL: %s\n", sqlite3_sql(stmt));
+DEBUG(hints_lookup) debug_printf_indent("prepared SQL: %s\n", sqlite3_sql(stmt));
 # endif
 
 stmt = exim_sqlbind_blob(dbp, stmt, &bindcol, key);
@@ -123,7 +124,7 @@ stmt = exim_sqlbind_blob(dbp, stmt, &bindcol, data);
 # ifdef SQL_DEBUG
 if (stmt)
   {
-  DEBUG(D_hints_lookup) debug_printf_indent("expanded SQL: %s\n", sqlite3_expanded_sql(stmt));
+  DEBUG(hints_lookup) debug_printf_indent("expanded SQL: %s\n", sqlite3_expanded_sql(stmt));
   }
 # endif
 
@@ -186,6 +187,11 @@ return more;
 }
 
 /* EXIM_DBGET - returns the value associated with the key. */
+/* Callers:
+	dbfn_read_klen			lookup-dbm, hints general
+	autoreply_transport_entry	autoreply tpt
+	dbfn_read_with_length		utils
+*/
 static inline BOOL
 exim_dbget(EXIM_DB * dbp, EXIM_DATUM * key, EXIM_DATUM * res)
 {
@@ -207,11 +213,17 @@ return (SQLITE_DONE == exim_sqlprep_step(dbp, sql, key, data, NULL )) ? EXIM_DBP
 }
 
 /* EXIM_DBPUT - returns nothing useful, assumes replace mode */
+/* Callers:
+	dbfn_write				hints general
+	autoreply_transport_entry		autoreply tpt
+	main(dbmbuild)				utils
+	dbfn_write				utils
+*/
 static inline int
 exim_dbput(EXIM_DB * dbp, EXIM_DATUM * key, EXIM_DATUM * data)
 {
 # ifdef SQL_DEBUG
-DEBUG(D_hints_lookup) debug_printf_indent(EXIM_DBTYPE " put: key:%.*W data:%.*W\n", key->len, key->data, data->len, data->data );
+DEBUG(hints_lookup) debug_printf_indent(EXIM_DBTYPE " put: key:%.*W data:%.*W\n", key->len, key->data, data->len, data->data );
 # endif
 (void) exim_s_dbp(dbp, key, data, "INSERT OR REPLACE INTO tblblob (ky, dat) VALUES(?, ?)");
 return 0;
@@ -228,6 +240,10 @@ return exim_s_dbp(dbp, key, data, "INSERT OR ABORT INTO tblblob (ky, dat) VALUES
 }
 
 /* EXIM_DBDEL */
+/* Callers
+	dbfn_delete			hints general
+	dnfn_delete			utils
+*/
 static inline int
 exim_dbdel(EXIM_DB * dbp, EXIM_DATUM * key)
 {
@@ -245,7 +261,7 @@ EXIM_CURSOR * cursor;
 cursor = exim_sqlprep(dbp, "SELECT ky FROM tblblob ORDER BY ky", NULL, NULL );
 if (!cursor) return NULL;
 # ifdef SQL_DEBUG
-DEBUG(D_hints_lookup) debug_printf_indent("prepared query: %s\n", sqlite3_sql(cursor));
+DEBUG(hints_lookup) debug_printf_indent("prepared query: %s\n", sqlite3_sql(cursor));
 # endif
 
 return cursor;
@@ -288,7 +304,7 @@ exim_dbclose_multi__(dbp);
 
 /* Datum access */
 
-static uschar *
+static inline uschar *
 exim_datum_data_get(EXIM_DATUM * dp)
 { return US dp->data; }
 
@@ -299,7 +315,8 @@ exim_datum_data_set(EXIM_DATUM * dp, void * s)
 static unsigned
 exim_datum_size_get(EXIM_DATUM * dp)
 { return dp->len; }
-static void
+
+static inline void
 exim_datum_size_set(EXIM_DATUM * dp, unsigned n)
 { dp->len = n; }
 
diff --git a/src/src/hintsdb/hints_tdb.h b/src/src/hintsdb/hints_tdb.h
index 388904a55..322466973 100644
--- a/src/src/hintsdb/hints_tdb.h
+++ b/src/src/hintsdb/hints_tdb.h
@@ -43,12 +43,12 @@ exim_dbopen__(const uschar * name, const uschar * dirname, int flags,
 EXIM_DB * db = tdb_open(CS name, 0, TDB_DEFAULT, flags, mode);
 int e;
 
-DEBUG(D_hints_lookup) if (!db)
+DEBUG(hints_lookup) if (!db)
   debug_printf_indent("tdb_open(flags 0x%x mode %04o) %s\n",
 	      flags, mode, strerror(errno));
 if (!db || tdb_transaction_start(db) == 0) return db;
 e = errno;
-DEBUG(D_hints_lookup) if (db)
+DEBUG(hints_lookup) if (db)
   debug_printf_indent("tdb_transaction_start: %s\n", tdb_errorstr(db));
 tdb_close(db);
 errno = e;
@@ -60,7 +60,7 @@ exim_dbopen_multi__(const uschar * name, const uschar * dirname, int flags,
   unsigned mode)
 {
 EXIM_DB * db = tdb_open(CS name, 0, TDB_DEFAULT, flags, mode);
-DEBUG(D_hints_lookup) if (!db)
+DEBUG(hints_lookup) if (!db)
   debug_printf_indent("tdb_open(flags 0x%x mode %04o) %s\n",
 	      flags, mode, strerror(errno));
 return db;
@@ -79,7 +79,7 @@ static inline BOOL
 exim_dbtransaction_start(EXIM_DB * db)
 {
 BOOL ok = tdb_transaction_start(db) == 0;
-DEBUG(D_hints_lookup) if (!ok)
+DEBUG(hints_lookup) if (!ok)
   debug_printf_indent("tdb_transaction_start: %s\n", tdb_errorstr(db));
 return ok;
 }
@@ -88,7 +88,7 @@ static inline void
 exim_dbtransaction_commit(EXIM_DB * db)
 {
 BOOL ok = tdb_transaction_commit(db) == 0;
-DEBUG(D_hints_lookup) if (!ok)
+DEBUG(hints_lookup) if (!ok)
   debug_printf_indent("tdb_transaction_commit: %s\n", tdb_errorstr(db));
 return;
 }
@@ -100,7 +100,7 @@ static inline int
 exim_dbput(EXIM_DB * dbp, EXIM_DATUM * key, EXIM_DATUM * data)
 {
 int rc = tdb_store(dbp, *key, *data, TDB_REPLACE);
-DEBUG(D_hints_lookup) if (rc != 0)
+DEBUG(hints_lookup) if (rc != 0)
   debug_printf_indent("tdb_store: %s\n", tdb_errorstr(dbp));
 return rc;
 }
@@ -126,6 +126,7 @@ exim_dbcreate_cursor(EXIM_DB * dbp)
 {
 # ifdef COMPILE_UTILITY
 EXIM_CURSOR * c = malloc(sizeof(TDB_DATA));
+if (!c) return c;
 # else
 EXIM_CURSOR * c = store_malloc(sizeof(TDB_DATA));
 # endif
@@ -162,7 +163,7 @@ static inline void
 exim_dbclose_multi__(EXIM_DB * db)
 {
 int rc = tdb_close(db);
-DEBUG(D_hints_lookup) if (rc != 0)
+DEBUG(hints_lookup) if (rc != 0)
   debug_printf_indent("tdb_close: %s\n", tdb_errorstr(db));
 }
 
@@ -170,10 +171,10 @@ static inline void
 exim_dbclose__(EXIM_DB * db)
 {
 int rc = tdb_transaction_commit(db);
-DEBUG(D_hints_lookup) if (rc != 0)
+DEBUG(hints_lookup) if (rc != 0)
   debug_printf_indent("tdb_transaction_commit: %s\n", tdb_errorstr(db));
 rc = tdb_close(db);
-DEBUG(D_hints_lookup) if (rc != 0)
+DEBUG(hints_lookup) if (rc != 0)
   debug_printf_indent("tdb_close: %s\n", tdb_errorstr(db));
 }
 
diff --git a/src/src/hintsdb_structs.h b/src/src/hintsdb_structs.h
index 3eb8b3386..fdca1f468 100644
--- a/src/src/hintsdb_structs.h
+++ b/src/src/hintsdb_structs.h
@@ -25,20 +25,26 @@ typedef struct {
 } open_db;
 
 
-/* Structures for records stored in exim database dbm files. They all
-start with the same fields, described in the generic type. */
+/* Structures for records stored in exim database hints files. They all
+start with the same fields, described in the generic type.
+DBM databases are used for hints files.
+*/
 
 
 typedef struct {
-  time_t time_stamp;      /* Timestamp of writing */
+  unsigned version;
+  BOOL	 tainted;	/* metadata for the "value" part of the record */
+  time_t time_stamp;	/* Timestamp of writing */
 } dbdata_generic;
 
+#define	HINTS_VERSION	2
+
 
 /* This structure keeps track of retry information for a host or a local
 address. */
 
 typedef struct {
-  time_t time_stamp;
+  dbdata_generic gen;
   /*************/
   time_t first_failed;    /* Time of first failure */
   time_t last_try;        /* Time of last try */
@@ -70,7 +76,7 @@ handle the old type of record, we retain the old definition. The different
 kinds of record can be distinguished by their different lengths. */
 
 typedef struct {
-  time_t time_stamp;
+  dbdata_generic gen;
   /*************/
   int   result;
   int   postmaster_result; /* Postmaster is accepted */
@@ -78,7 +84,7 @@ typedef struct {
 } dbdata_callout_cache_obs;
 
 typedef struct {
-  time_t time_stamp;       /* Timestamp of last address check */
+  dbdata_generic gen;		/* Timestamp of last address check */
   /*************/
   int   result;            /* accept or reject */
 } dbdata_callout_cache_address;
@@ -88,7 +94,7 @@ last so that if somebody reverts to an older Exim, the new records will
 still make sense because they match the old layout. */
 
 typedef struct {
-  time_t time_stamp;       /* Time stamp of last connection */
+  dbdata_generic gen;		/* Time stamp of last connection */
   /*************/
   int   result;            /* Domain reject or accept */
   int   postmaster_result; /* Postmaster result */
@@ -101,7 +107,7 @@ typedef struct {
 host for a particular transport. */
 
 typedef struct {
-  time_t time_stamp;
+  dbdata_generic gen;
   /*************/
   int    count;           /* Count of message ids */
   int    sequence;        /* Sequence for continued records */
@@ -121,7 +127,7 @@ at present in use. The same structure is used for recording a running ETRN
 process. */
 
 typedef struct {
-  time_t time_stamp;
+  dbdata_generic gen;
   /*************/
   int    count;           /* Reserved for possible connection count */
 } dbdata_serialize;
@@ -131,7 +137,7 @@ typedef struct {
 ACL condition. */
 
 typedef struct {
-  time_t time_stamp;
+  dbdata_generic gen;
   /*************/
   int    time_usec;       /* Fractional part of time, from gettimeofday() */
   double rate;            /* Smoothed sending rate at that time */
@@ -149,7 +155,7 @@ typedef struct {
 
 /* For "seen" ACL condition */
 typedef struct {
-  time_t time_stamp;
+  dbdata_generic gen;
 } dbdata_seen;
 
 #ifndef DISABLE_PIPE_CONNECT
@@ -172,14 +178,14 @@ typedef struct {
 } ehlo_resp_precis;
 
 typedef struct {
-  time_t time_stamp;
+  dbdata_generic gen;
   /*************/
   ehlo_resp_precis data;
 } dbdata_ehlo_resp;
 #endif
 
 typedef struct {
-  time_t time_stamp;
+  dbdata_generic gen;
   /*************/
   uschar verify_override:1;
   uschar ocsp:3;
diff --git a/src/src/host.c b/src/src/host.c
index cfa2620df..c7f8ba961 100644
--- a/src/src/host.c
+++ b/src/src/host.c
@@ -19,6 +19,11 @@ of Exim. */
 #include "exim.h"
 
 
+static int
+host_find_bydns_internal(host_item *, const uschar *,
+  int, const uschar *, const uschar *, const uschar *, const dnssec_domains *,
+  const uschar **, BOOL *, dns_answer *);
+
 /* Static variable for preserving the list of interface addresses in case it is
 used more than once. */
 
@@ -89,7 +94,7 @@ if (random_seed == 0)
     random_seed = 42;
   else
     {
-    int p = (int)getpid();
+    pid_t p = getpid();
     random_seed = (int)time(NULL) ^ ((p << 16) | p);
     }
 random_seed = 1103515245 * random_seed + 12345;
@@ -108,7 +113,7 @@ 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",
+log_write(LOG_MAIN, "Long %s lookup for '%s': %lu msec",
   type, data, msec);
 }
 
@@ -180,9 +185,9 @@ uschar *adds;
 uschar **alist;
 struct hostent *yield;
 dns_answer * dnsa = store_get_dns_answer();
-dns_scan dnss;
+dns_scan dnss = {0};
 
-DEBUG(D_host_lookup)
+DEBUG(host_lookup)
   debug_printf_indent("using host_fake_gethostbyname for %s (%s)\n", name,
     af == AF_INET ? "IPv4" : "IPv6");
 
@@ -413,7 +418,7 @@ else if (Ustrchr(h->name, ':') == p)
   h->name = string_copyn(h->name, p - h->name);
 else return PORT_NONE;
 
-DEBUG(D_route|D_host_lookup) debug_printf("host=%s port=%d\n", h->name, port);
+DEBUG(route|host_lookup) debug_printf_indent("host=%s port=%d\n", h->name, port);
 return port;
 }
 
@@ -583,7 +588,7 @@ sender_rcvhost = string_copy_perm(rcvhost, TRUE);
 
 store_reset(reset_point);
 
-DEBUG(D_host_lookup)
+DEBUG(host_lookup)
   {
   debug_printf_indent("sender_fullhost = %s\n", sender_fullhost);
   debug_printf_indent("sender_rcvhost = %s\n", sender_rcvhost);
@@ -679,7 +684,7 @@ while ((s = string_nextinlist(&list, &sep, NULL, 0)))
   int port = host_address_extract_port(s);            /* Leaves just the IP address */
 
   if (!(ipv = string_is_ip_address(s, NULL)))
-    log_write_die(0, LOG_MAIN, "Malformed IP address %q in %s",
+    log_write_die(LOG_MAIN, "Malformed IP address %q in %s",
       s, name);
 
   /* Skip IPv6 addresses if IPv6 is disabled. */
@@ -790,9 +795,9 @@ if (!local_interface_data)
     else
       {
       local_interface_data = add_unique_interface(local_interface_data, ipa);
-      DEBUG(D_interface)
+      DEBUG(interface)
         {
-        debug_printf("Configured local interface: address=%s", ipa->address);
+        debug_printf_indent("Configured local interface: address=%s", ipa->address);
         if (ipa->port != 0) debug_printf(" port=%d", ipa->port);
         debug_printf("\n");
         }
@@ -836,6 +841,7 @@ Returns:     pointer to character string
 uschar *
 host_ntoa(int type, const void * arg, uschar * buffer, int * portptr)
 {
+#define NTOA_BSIZE 46
 uschar * yield;
 
 /* The new world. It is annoying that we have to fish out the address from
@@ -845,7 +851,7 @@ function inet_ntoa() returns just uschar *, and some picky compilers insist
 on warning if one assigns a const uschar * to a uschar *. Hence the casts. */
 
 #if HAVE_IPV6
-uschar addr_buffer[46];
+uschar addr_buffer[NTOA_BSIZE];
 if (type < 0)
   {
   int family = ((struct sockaddr *)arg)->sa_family;
@@ -888,7 +894,7 @@ else
 
 /* If there is no buffer, put the string into some new store. */
 
-if (!buffer) buffer = store_get(46, GET_UNTAINTED);
+if (!buffer) buffer = store_get(NTOA_BSIZE, GET_UNTAINTED);
 
 /* Callers of this function with a non-NULL buffer must ensure that it is
 large enough to hold an IPv6 address, namely, at least 46 bytes. That's what
@@ -896,9 +902,10 @@ makes this use of strcpy() OK.
 If the library returned apparently an apparently tainted string, clean it;
 we trust IP addresses. */
 
-string_format_nt(buffer, 46, "%s", yield);
+string_format_nt(buffer, NTOA_BSIZE, "%s", yield);
 return buffer;
 }
+#undef NTOA_BSIZE
 
 
 
@@ -953,7 +960,7 @@ if (Ustrchr(address, ':') != NULL)
     {
     int len = Ustrcspn(p, ":%");
     if (len == 0) nulloffset = ci;
-    if (ci > 7) log_write_die(0, LOG_MAIN,
+    if (ci > 7) log_write_die(LOG_MAIN,
       "Internal error: invalid IPv6 address %q passed to host_aton()",
       address);
     else component[ci++] = p;
@@ -1354,13 +1361,13 @@ FOUND_LOCAL:
 
 if (!prev)
   {
-  HDEBUG(D_host_lookup) debug_printf_indent(h->mx >= 0
+  HDEBUG(host_lookup) debug_printf_indent(h->mx >= 0
     ? "local host has lowest MX\n"
     : "local host found for non-MX address\n");
   return HOST_FOUND_LOCAL;
   }
 
-HDEBUG(D_host_lookup)
+HDEBUG(host_lookup)
   {
   debug_printf_indent("local host in host list - removed hosts:\n");
   for (h = prev->next; h != last->next; h = h->next)
@@ -1406,7 +1413,7 @@ while (host != *lastptr)
       if (h->next->address != NULL &&
           Ustrcmp(h->next->address, host->address) == 0)
         {
-        DEBUG(D_host_lookup) debug_printf("duplicate IP address %s (MX=%d) "
+        DEBUG(host_lookup) debug_printf_indent("duplicate IP address %s (MX=%d) "
           "removed\n", host->address, h->next->mx);
         if (h->next == *lastptr) *lastptr = h;
         h->next = h->next->next;
@@ -1452,7 +1459,7 @@ if (Ustrchr(sender_host_address, ':') != NULL)
   {
   struct in6_addr addr6;
   if (inet_pton(AF_INET6, CS sender_host_address, &addr6) != 1)
-    log_write_die(0, LOG_MAIN, "unable to parse %q as an "
+    log_write_die(LOG_MAIN, "unable to parse %q as an "
       "IPv6 address", sender_host_address);
   #if HAVE_GETIPNODEBYADDR
   hosts = getipnodebyaddr(CS &addr6, sizeof(addr6), AF_INET6, &h_errno);
@@ -1463,7 +1470,7 @@ if (Ustrchr(sender_host_address, ':') != NULL)
 else
   {
   if (inet_pton(AF_INET, CS sender_host_address, &addr) != 1)
-    log_write_die(0, LOG_MAIN, "unable to parse %q as an "
+    log_write_die(LOG_MAIN, "unable to parse %q as an "
       "IPv4 address", sender_host_address);
   #if HAVE_GETIPNODEBYADDR
   hosts = getipnodebyaddr(CS &addr, sizeof(addr), AF_INET, &h_errno);
@@ -1488,7 +1495,7 @@ if (  slow_lookup_log
 
 if (!hosts)
   {
-  HDEBUG(D_host_lookup) debug_printf("IP address lookup failed: h_errno=%d\n",
+  HDEBUG(host_lookup) debug_printf_indent("IP address lookup failed: h_errno=%d\n",
     h_errno);
   return (h_errno == TRY_AGAIN || h_errno == NO_RECOVERY) ? DEFER : FAIL;
   }
@@ -1499,7 +1506,7 @@ empty string; in others as a single dot. */
 
 if (!hosts->h_name || !hosts->h_name[0] || hosts->h_name[0] == '.')
   {
-  HDEBUG(D_host_lookup)
+  HDEBUG(host_lookup)
     debug_printf_indent("IP address lookup yielded an empty name: "
       "treated as non-existent host name\n");
   return FAIL;
@@ -1585,11 +1592,11 @@ uschar **aliases;
 uschar *ordername;
 const uschar *list = host_lookup_order;
 dns_answer * dnsa = store_get_dns_answer();
-dns_scan dnss;
+dns_scan dnss = {0};
 
 sender_host_dnssec = host_lookup_deferred = host_lookup_failed = FALSE;
 
-HDEBUG(D_host_lookup)
+HDEBUG(host_lookup)
   debug_printf_indent("looking up host name for %s\n", sender_host_address);
 expand_level++;
 
@@ -1599,8 +1606,8 @@ reserved IP address. */
 if (f.running_in_test_harness &&
     Ustrcmp(sender_host_address, "99.99.99.99") == 0)
   {
-  HDEBUG(D_host_lookup)
-    debug_printf("Test harness: host name lookup returns DEFER\n");
+  HDEBUG(host_lookup)
+    debug_printf_indent("Test harness: host name lookup returns DEFER\n");
   host_lookup_deferred = TRUE;
   yield = DEFER;
   goto out;
@@ -1632,7 +1639,7 @@ while ((ordername = string_nextinlist(&list, &sep, NULL, 0)))
       int old_pool = store_pool;
 
       sender_host_dnssec = dns_is_secure(dnsa);
-      DEBUG(D_dns)
+      DEBUG(dns)
         debug_printf_indent("Reverse DNS security status: %s\n",
             sender_host_dnssec ? "DNSSEC verified (AD)" : "unverified");
 
@@ -1663,7 +1670,7 @@ while ((ordername = string_nextinlist(&list, &sep, NULL, 0)))
         if (dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen,
              US rr->data, (DN_EXPAND_ARG4_TYPE)(s), ssize) < 0)
           {
-          log_write(0, LOG_MAIN, "host name alias list truncated for %s",
+          log_write(LOG_MAIN, "host name alias list truncated for %s",
             sender_host_address);
           break;
           }
@@ -1671,13 +1678,13 @@ while ((ordername = string_nextinlist(&list, &sep, NULL, 0)))
         store_release_above(s + (slen = Ustrlen(s)) + 1);
         if (!*s)
           {
-          HDEBUG(D_host_lookup) debug_printf_indent("IP address lookup yielded "
+          HDEBUG(host_lookup) debug_printf_indent("IP address lookup yielded "
 	    "an empty name: treated as non-existent host name\n");
           continue;
           }
 	if (Ustrspn(s, letter_digit_hyphen_dot) != slen)
           {
-          HDEBUG(D_host_lookup) debug_printf_indent("IP address lookup yielded "
+          HDEBUG(host_lookup) debug_printf_indent("IP address lookup yielded "
 	    "an illegal name (bad char): treated as non-existent host name\n");
           continue;
           }
@@ -1698,8 +1705,8 @@ while ((ordername = string_nextinlist(&list, &sep, NULL, 0)))
 
     if (rc == DNS_AGAIN)
       {
-      HDEBUG(D_host_lookup)
-        debug_printf("IP address PTR lookup gave temporary error\n");
+      HDEBUG(host_lookup)
+        debug_printf_indent("IP address PTR lookup gave temporary error\n");
       host_lookup_deferred = TRUE;
       yield = DEFER;
       goto out;
@@ -1710,8 +1717,8 @@ while ((ordername = string_nextinlist(&list, &sep, NULL, 0)))
 
   else if (strcmpic(ordername, US"byaddr") == 0)
     {
-    HDEBUG(D_host_lookup)
-      debug_printf("IP address lookup using gethostbyaddr()\n");
+    HDEBUG(host_lookup)
+      debug_printf_indent("IP address lookup using gethostbyaddr()\n");
     rc = host_name_lookup_byaddr();
     if (rc == DEFER)
       {
@@ -1728,16 +1735,16 @@ NB host_lookup_msg must be in permanent store.  */
 
 if (!sender_host_name)
   {
-  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);
+  if ((host_checking || !f.log_testing_mode) && LOGGING(host_lookup_failed))
+    log_write(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)";
   host_lookup_failed = TRUE;
   yield = FAIL;
   goto out;
   }
 
-HDEBUG(D_host_lookup)
+HDEBUG(host_lookup)
   {
   uschar ** aliases = sender_host_aliases;
   debug_printf_indent("IP address lookup yielded %q\n", sender_host_name);
@@ -1767,44 +1774,45 @@ for (uschar * hname = sender_host_name; hname; hname = *aliases++)
   dnssec_domains d =
     { .request = sender_host_dnssec ? US"*" : NULL, .require = NULL };
 
-  if (  (rc = host_find_bydns(&h, NULL, HOST_FIND_BY_A | HOST_FIND_BY_AAAA,
-	  NULL, NULL, NULL, &d, NULL, NULL)) == HOST_FOUND
+  if (  (rc = host_find_bydns_internal(&h, NULL,
+	  HOST_FIND_BY_A | HOST_FIND_BY_AAAA,
+	  NULL, NULL, NULL, &d, NULL, NULL, dnsa)) == HOST_FOUND
      || rc == HOST_FOUND_LOCAL
      )
     {
-    HDEBUG(D_host_lookup)
+    HDEBUG(host_lookup)
       debug_printf_indent("checking addresses for %s\n", hname);
 
     /* If the forward lookup was not secure we cancel the is-secure variable */
 
-    DEBUG(D_dns) debug_printf_indent("Forward DNS security status: %s\n",
+    DEBUG(dns) debug_printf_indent("Forward DNS security status: %s\n",
 	  h.dnssec_used == DS_YES ? "DNSSEC verified (AD)" : "unverified");
     if (h.dnssec_used != DS_YES) sender_host_dnssec = FALSE;
 
     for (host_item * 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);
+        HDEBUG(host_lookup) debug_printf_indent("  %s OK\n", hh->address);
         ok = TRUE;
         break;
         }
       else
-        HDEBUG(D_host_lookup) debug_printf("  %s\n", hh->address);
+        HDEBUG(host_lookup) debug_printf_indent("  %s\n", hh->address);
 
-    if (!ok) HDEBUG(D_host_lookup)
-      debug_printf("no IP address for %s matched %s\n", hname,
+    if (!ok) HDEBUG(host_lookup)
+      debug_printf_indent("no IP address for %s matched %s\n", hname,
         sender_host_address);
     }
   else if (rc == HOST_FIND_AGAIN)
     {
-    HDEBUG(D_host_lookup) debug_printf("temporary error for host name lookup\n");
+    HDEBUG(host_lookup) debug_printf_indent("temporary error for host name lookup\n");
     host_lookup_deferred = TRUE;
     sender_host_name = NULL;
     yield = DEFER;
     goto out;
     }
   else
-    HDEBUG(D_host_lookup) debug_printf("no IP addresses found for %s\n", hname);
+    HDEBUG(host_lookup) debug_printf_indent("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. */
@@ -1832,8 +1840,8 @@ if (sender_host_name) { yield = OK; goto out; }
 
 /* We have failed to find an address that matches. */
 
-HDEBUG(D_host_lookup)
-  debug_printf("%s does not match any IP address for %s\n",
+HDEBUG(host_lookup)
+  debug_printf_indent("%s does not match any IP address for %s\n",
     sender_host_address, save_hostname);
 
 /* This message must be in permanent store */
@@ -1848,6 +1856,7 @@ yield = FAIL;
 
 out:
   expand_level--;
+  store_free_dns_answer(dnsa);
   return yield;
 }
 
@@ -2008,7 +2017,7 @@ for (int i = 1; i <= times;
       default: error = US"?"; break;
       }
 
-    DEBUG(D_host_lookup) debug_printf_indent("%s(af=%s) returned %d (%s)\n",
+    DEBUG(host_lookup) debug_printf_indent("%s(af=%s) returned %d (%s)\n",
       f.running_in_test_harness ? "host_fake_gethostbyname" :
 #if HAVE_IPV6
 # if HAVE_GETIPNODEBYNAME
@@ -2048,8 +2057,8 @@ for (int i = 1; i <= times;
        && verify_check_this_host(&ignore_target_hosts, NULL, host->name,
 	    text_address, NULL) == OK)
       {
-      DEBUG(D_host_lookup)
-        debug_printf("ignored host %s [%s]\n", host->name, text_address);
+      DEBUG(host_lookup)
+        debug_printf_indent("ignored host %s [%s]\n", host->name, text_address);
       continue;
       }
     #endif
@@ -2103,7 +2112,7 @@ so we pass that back. */
 
 if (!host->address)
   {
-  uschar *msg =
+  uschar * msg =
 #ifndef STAND_ALONE
     !message_id[0] && smtp_in_fd >= 0
       ? string_sprintf("no IP address found for host %s (during %s)", host->name,
@@ -2111,10 +2120,10 @@ if (!host->address)
 #endif
     string_sprintf("no IP address found for host %s", host->name);
 
-  HDEBUG(D_host_lookup) debug_printf_indent("%s\n", msg);
+  HDEBUG(host_lookup) debug_printf_indent("%s\n", msg);
   if (temp_error) goto RETURN_AGAIN;
-  if (host_checking || !f.log_testing_mode)
-    log_write(L_host_lookup_failed, LOG_MAIN, "%s", msg);
+  if ((host_checking || !f.log_testing_mode) && LOGGING(host_lookup_failed))
+    log_write(LOG_MAIN, "%s", msg);
   return HOST_FIND_FAILED;
   }
 
@@ -2125,11 +2134,11 @@ host_remove_duplicates(host, &last);
 yield = local_host_check?
   host_scan_for_local_hosts(host, &last, NULL) : HOST_FOUND;
 
-HDEBUG(D_host_lookup)
+HDEBUG(host_lookup)
   {
   if (fully_qualified_name)
-    debug_printf("fully qualified name = %s\n", *fully_qualified_name);
-  debug_printf("%s looked up these IP addresses:\n",
+    debug_printf_indent("fully qualified name = %s\n", *fully_qualified_name);
+  debug_printf_indent("%s looked up these IP addresses:\n",
     #if HAVE_IPV6
       #if HAVE_GETIPNODEBYNAME
       "getipnodebyname"
@@ -2141,7 +2150,7 @@ HDEBUG(D_host_lookup)
     #endif
     );
   for (const host_item * h = host; h != last->next; h = h->next)
-    debug_printf("  name=%s address=%s\n", h->name,
+    debug_printf_indent("  name=%s address=%s\n", h->name,
       h->address ? h->address : US"<null>");
   }
 
@@ -2163,7 +2172,7 @@ RETURN_AGAIN:
   deliver_domain = save;
   if (rc == OK)
     {
-    DEBUG(D_host_lookup) debug_printf("%s is in dns_again_means_nonexist: "
+    DEBUG(host_lookup) debug_printf_indent("%s is in dns_again_means_nonexist: "
       "returning HOST_FIND_FAILED\n", host->name);
     return HOST_FIND_FAILED;
     }
@@ -2195,6 +2204,7 @@ function as it may be called to set the addresses of hosts taken from MX
 records.
 
 Arguments:
+  dnsa			a dns_answer area to use (avoid alloc another)
   host                  points to the host item we're filling in
   lastptr               points to pointer to last host item in a chain of
                           host items (may be updated if host is last and gets
@@ -2216,7 +2226,7 @@ Returns:       HOST_FIND_FAILED     couldn't find A record
 */
 
 static int
-set_address_from_dns(host_item *host, host_item **lastptr,
+set_address_from_dns(dns_answer * dnsa, host_item *host, host_item **lastptr,
   const uschar *ignore_target_hosts, BOOL allow_ip,
   const uschar **fully_qualified_name,
   BOOL dnssec_request, BOOL dnssec_require, int whichrrs)
@@ -2225,7 +2235,6 @@ host_item * thishostlast = NULL;    /* Indicates not yet filled in anything */
 BOOL v6_find_again = FALSE;
 BOOL dnssec_fail = FALSE;
 int i;
-dns_answer * dnsa;
 
 #ifndef DISABLE_TLS
 /* Copy the host name at this point to the value which is used for
@@ -2251,8 +2260,6 @@ if (allow_ip && string_is_ip_address(host->name, NULL) != 0)
   return HOST_FOUND;
   }
 
-dnsa = store_get_dns_answer();
-
 /* 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.
@@ -2284,18 +2291,18 @@ for (; i >= 0; i--)
   int type = types[i];
   int randoffset = i == (whichrrs & HOST_FIND_IPV4_FIRST ? 1 : 0)
     ? 500 : 0;  /* Ensures v6/4 sort order */
-  dns_scan dnss;
+  dns_scan dnss = {0};
 
   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";
 
-  DEBUG(D_dns)
+  DEBUG(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);
+      debug_printf_indent("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
@@ -2326,7 +2333,7 @@ for (; i >= 0; i--)
     {
     if (dns_is_secure(dnsa))
       {
-      DEBUG(D_host_lookup) debug_printf("%s A DNSSEC\n", host->name);
+      DEBUG(host_lookup) debug_printf_indent("%s A DNSSEC\n", host->name);
       if (host->dnssec_used == DS_UNK) /* set in host_find_bydns() */
 	host->dnssec_used = DS_YES;
       }
@@ -2335,13 +2342,13 @@ for (; i >= 0; i--)
       if (dnssec_require)
 	{
 	dnssec_fail = TRUE;
-	DEBUG(D_host_lookup) debug_printf("dnssec fail on %s for %.256s",
+	DEBUG(host_lookup) debug_printf_indent("dnssec fail on %s for %.256s",
 		i>0 ? "AAAA" : "A", host->name);
 	continue;
 	}
       if (host->dnssec_used == DS_YES) /* set in host_find_bydns() */
 	{
-	DEBUG(D_host_lookup) debug_printf("%s A cancel DNSSEC\n", host->name);
+	DEBUG(host_lookup) debug_printf_indent("%s A cancel DNSSEC\n", host->name);
 	host->dnssec_used = DS_NO;
 	lookup_dnssec_authenticated = US"no";
 	}
@@ -2361,8 +2368,8 @@ for (; i >= 0; i--)
     {
     dns_address * da = dns_address_from_rr(dnsa, rr);
 
-    DEBUG(D_host_lookup)
-      if (!da) debug_printf("no addresses extracted from A6 RR for %s\n",
+    DEBUG(host_lookup)
+      if (!da) debug_printf_indent("no addresses extracted from A6 RR for %s\n",
 	  host->name);
 
     /* This loop runs only once for A and AAAA records, but may run
@@ -2375,8 +2382,8 @@ for (; i >= 0; i--)
 	    verify_check_this_host(&ignore_target_hosts, NULL,
 	      host->name, da->address, NULL) == OK)
 	{
-	DEBUG(D_host_lookup)
-	  debug_printf("ignored host %s [%s]\n", host->name, da->address);
+	DEBUG(host_lookup)
+	  debug_printf_indent("ignored host %s [%s]\n", host->name, da->address);
 	continue;
 	}
       #endif
@@ -2464,7 +2471,6 @@ i = host->address
   : HOST_IGNORED;
 
 out:
-  store_free_dns_answer(dnsa);
   return i;
 }
 
@@ -2515,24 +2521,24 @@ Returns:                HOST_FIND_FAILED  Failed to find the host or domain;
                                           an address of the local host
 */
 
-int
-host_find_bydns(host_item * host, const uschar * ignore_target_hosts,
+static int
+host_find_bydns_internal(host_item * host, const uschar * ignore_target_hosts,
   int whichrrs,
   const uschar * srv_svclist, const uschar * srv_fail_domains,
   const uschar * mx_fail_domains, const dnssec_domains * dnssec_d,
-  const uschar ** fully_qualified_name, BOOL * removed)
+  const uschar ** fully_qualified_name, BOOL * removed,
+  dns_answer * dnsa)
 {
 host_item * h, * last;
 #ifdef EXPERIMENTAL_SRV_SMTPS
 BOOL srv_smtps = FALSE;
 #endif
 int rc = DNS_FAIL, ind_type = 0, yield;
-dns_answer * dnsa = store_get_dns_answer();
-dns_scan dnss;
+dns_scan dnss = {0};
 BOOL dnssec_require, dnssec_request;
 dnssec_status_t dnssec;
 
-HDEBUG(D_host_lookup)
+HDEBUG(host_lookup)
   {
   debug_printf_indent("check dnssec require list\n");
   expand_level++;
@@ -2541,7 +2547,7 @@ dnssec_require = dnssec_d
   && match_isinlist(host->name, CUSS &dnssec_d->require,
 		  0, &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL) == OK;
 
-HDEBUG(D_host_lookup)
+HDEBUG(host_lookup)
   {
   expand_level--;
   debug_printf_indent("check dnssec request list\n");
@@ -2551,7 +2557,7 @@ dnssec_request = dnssec_require
     || (  dnssec_d
        && match_isinlist(host->name, CUSS &dnssec_d->request,
 		    0, &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL) == OK);
-HDEBUG(D_host_lookup)
+HDEBUG(host_lookup)
   expand_level--;
 
 /* Set the default fully qualified name to the incoming name, initialize the
@@ -2591,7 +2597,7 @@ if (whichrrs & HOST_FIND_BY_SRV)
     rc = dns_lookup_timerwrap(dnsa, temp_fully_qualified_name, ind_type,
 	  CUSS &temp_fully_qualified_name);
 
-    DEBUG(D_dns)
+    DEBUG(dns)
       if ((dnssec_request || dnssec_require)
 	  && !dns_is_secure(dnsa)
 	  && dns_is_aa(dnsa))
@@ -2613,7 +2619,7 @@ if (whichrrs & HOST_FIND_BY_SRV)
 
     if (rc == DNS_SUCCEED && dnssec_require && !dns_is_secure(dnsa))
       {
-      log_write(L_host_lookup_failed, LOG_MAIN,
+      if (LOGGING(host_lookup_failed)) log_write(LOG_MAIN,
 		  "dnssec fail on SRV for %.256s", host->name);
       rc = DNS_FAIL;
       }
@@ -2624,7 +2630,7 @@ if (whichrrs & HOST_FIND_BY_SRV)
 	  &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL) != OK)
 #endif
 	{ yield = HOST_FIND_AGAIN; goto out; }
-      DEBUG(D_host_lookup) debug_printf_indent("DNS_%s treated as DNS_NODATA "
+      DEBUG(host_lookup) debug_printf_indent("DNS_%s treated as DNS_NODATA "
 	"(domain in srv_fail_domains)\n", rc == DNS_FAIL ? "FAIL":"AGAIN");
       }
     else if (rc == DNS_SUCCEED)
@@ -2652,7 +2658,7 @@ if (rc != DNS_SUCCEED  &&  whichrrs & HOST_FIND_BY_MX)
   lookup_dnssec_authenticated = NULL;
   rc = dns_lookup_timerwrap(dnsa, host->name, ind_type, fully_qualified_name);
 
-  DEBUG(D_dns)
+  DEBUG(dns)
     if (  (dnssec_request || dnssec_require)
        && !dns_is_secure(dnsa)
        && dns_is_aa(dnsa))
@@ -2661,7 +2667,7 @@ if (rc != DNS_SUCCEED  &&  whichrrs & HOST_FIND_BY_MX)
   if (dnssec_request)
     if (dns_is_secure(dnsa))
       {
-      DEBUG(D_host_lookup)
+      DEBUG(host_lookup)
 	debug_printf_indent("%s (MX resp) DNSSEC\n", host->name);
       dnssec = DS_YES; lookup_dnssec_authenticated = US"yes";
       }
@@ -2679,7 +2685,7 @@ if (rc != DNS_SUCCEED  &&  whichrrs & HOST_FIND_BY_MX)
     case DNS_SUCCEED:
       if (!dnssec_require || dns_is_secure(dnsa))
 	break;
-      DEBUG(D_host_lookup)
+      DEBUG(host_lookup)
 	debug_printf_indent("dnssec fail on MX for %.256s\n", host->name);
 #ifndef STAND_ALONE
       if (match_isinlist(host->name, CUSS &mx_fail_domains, 0,
@@ -2696,7 +2702,7 @@ if (rc != DNS_SUCCEED  &&  whichrrs & HOST_FIND_BY_MX)
 	  &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL) != OK)
 #endif
 	{ yield = HOST_FIND_AGAIN; goto out; }
-      DEBUG(D_host_lookup) debug_printf_indent("DNS_%s treated as DNS_NODATA "
+      DEBUG(host_lookup) debug_printf_indent("DNS_%s treated as DNS_NODATA "
 	"(domain in mx_fail_domains)\n", (rc == DNS_FAIL)? "FAIL":"AGAIN");
       break;
     }
@@ -2710,7 +2716,7 @@ if (rc != DNS_SUCCEED)
   {
   if (!(whichrrs & (HOST_FIND_BY_A | HOST_FIND_BY_AAAA)))
     {
-    DEBUG(D_host_lookup) debug_printf_indent("Address records are not being sought\n");
+    DEBUG(host_lookup) debug_printf_indent("Address records are not being sought\n");
     yield = HOST_FIND_FAILED;
     goto out;
     }
@@ -2723,7 +2729,7 @@ if (rc != DNS_SUCCEED)
   host->tls_needs = SRV_TLS_UNK;
 #endif
   lookup_dnssec_authenticated = NULL;
-  rc = set_address_from_dns(host, &last, ignore_target_hosts, FALSE,
+  rc = set_address_from_dns(dnsa, host, &last, ignore_target_hosts, FALSE,
     fully_qualified_name, dnssec_request, dnssec_require, whichrrs);
 
   /* If one or more address records have been found, check that none of them
@@ -2737,7 +2743,7 @@ if (rc != DNS_SUCCEED)
   else if (rc == HOST_IGNORED)
     rc = HOST_FIND_FAILED;  			/* No special action */
 
-  DEBUG(D_host_lookup)
+  DEBUG(host_lookup)
     if (host->address)
       {
       if (fully_qualified_name)
@@ -2782,6 +2788,9 @@ for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS);
   int port = PORT_NONE;		/* MX lookups get PORT_NONE */
   const uschar * s = rr->data;	/* MUST be unsigned for GETSHORT */
   host_item * next;
+
+  /* Properly, should be tainted. string_copy_dnsdomain() call below must be
+  the only outside use if data placed here by dn_expand(). */
   uschar data[256];
 
   if (rr_bad_size(rr, sizeof(uint16_t))) continue;
@@ -2809,8 +2818,9 @@ for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS);
 
   /* Get the name of the host pointed to. */
 
-  (void)dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, s,
-    (DN_EXPAND_ARG4_TYPE)data, sizeof(data));
+  if (dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, s,
+      (DN_EXPAND_ARG4_TYPE)data, sizeof(data)) < 0)
+    continue;
 
   /* Check that we haven't already got this host on the chain; if we have,
   keep only the lower precedence. This situation shouldn't occur, but you
@@ -2824,7 +2834,7 @@ for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS);
     for (h = host; h != last->next; prev = h, h = h->next)
       if (strcmpic(h->name, data) == 0)
         {
-        DEBUG(D_host_lookup)
+        DEBUG(host_lookup)
           debug_printf_indent("discarded duplicate host %s (MX=%d)\n", data,
             precedence > h->mx ? precedence : h->mx);
         if (precedence >= h->mx) goto NEXT_MX_RR; /* Skip greater precedence */
@@ -2925,12 +2935,12 @@ if (ind_type == T_SRV)
 
   if (host == last && host->name[0] == 0)
     {
-    DEBUG(D_host_lookup) debug_printf_indent("the single SRV record is \".\"\n");
+    DEBUG(host_lookup) debug_printf_indent("the single SRV record is \".\"\n");
     yield = HOST_FIND_FAILED;
     goto out;
     }
 
-  DEBUG(D_host_lookup)
+  DEBUG(host_lookup)
     {
     debug_printf_indent("original ordering of hosts from SRV records:\n");
     for (h = host; h != last->next; h = h->next)
@@ -3042,7 +3052,7 @@ for (h = host; h != last->next; h = h->next)
   {
   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(dnsa, h, &last, ignore_target_hosts, allow_mx_to_ip,
     NULL, dnssec_request, dnssec_require,
     whichrrs & HOST_FIND_IPV4_ONLY
     ?  HOST_FIND_BY_A  :  HOST_FIND_BY_A | HOST_FIND_BY_AAAA);
@@ -3142,7 +3152,7 @@ host_remove_duplicates(host, &last);
 rc = host_scan_for_local_hosts(host, &last, removed);
 if (rc != HOST_FIND_FAILED) yield = rc;
 
-DEBUG(D_host_lookup)
+DEBUG(host_lookup)
   {
   if (fully_qualified_name)
     debug_printf_indent("fully qualified name = %s\n", *fully_qualified_name);
@@ -3167,6 +3177,20 @@ DEBUG(D_host_lookup)
 out:
 
 dns_init(FALSE, FALSE, FALSE);	/* clear the dnssec bit for getaddrbyname */
+return yield;
+}
+
+int
+host_find_bydns(host_item * host, const uschar * ignore_target_hosts,
+  int whichrrs,
+  const uschar * srv_svclist, const uschar * srv_fail_domains,
+  const uschar * mx_fail_domains, const dnssec_domains * dnssec_d,
+  const uschar ** fully_qualified_name, BOOL * removed)
+{
+dns_answer * dnsa = store_get_dns_answer();
+int yield = host_find_bydns_internal(host, ignore_target_hosts, whichrrs,
+  srv_svclist, srv_fail_domains, mx_fail_domains, dnssec_d,
+  fully_qualified_name, removed, dnsa);
 store_free_dns_answer(dnsa);
 return yield;
 }
@@ -3195,8 +3219,8 @@ BOOL sec;
 
 rc = dns_lookup_timerwrap(dnsa, buffer, T_TLSA, &fullname);
 sec = dns_is_secure(dnsa);
-DEBUG(D_transport)
-  debug_printf("TLSA lookup ret %s %sDNSSEC\n", dns_rc_names[rc], sec ? "" : "not ");
+DEBUG(transport)
+  debug_printf_indent("TLSA lookup ret %s %sDNSSEC\n", dns_rc_names[rc], sec ? "" : "not ");
 
 switch (rc)
   {
@@ -3206,9 +3230,9 @@ switch (rc)
   case DNS_SUCCEED:
     if (sec)
       {
-      DEBUG(D_transport)
+      DEBUG(transport)
 	{
-	dns_scan dnss;
+	dns_scan dnss = {0};
 	for (dns_record * 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)
@@ -3219,13 +3243,13 @@ switch (rc)
 
 	    if (payload_length > MAX_TLSA_EXPANDED_SIZE)
 	      payload_length = MAX_TLSA_EXPANDED_SIZE;
-	    debug_printf(" %d %d %d %.*H\n",
+	    debug_printf_indent(" %d %d %d %.*H\n",
 			  usage, selector, matching_type, payload_length, p);
 	    }
 	}
       return OK;
       }
-    log_write(0, LOG_MAIN,
+    log_write(LOG_MAIN,
       "DANE error: TLSA lookup for %s not DNSSEC", host->name);
     /*FALLTRHOUGH*/
 
@@ -3266,14 +3290,14 @@ disable_ipv6 = FALSE;
 primary_hostname = US"";
 store_init();
 store_pool = POOL_MAIN;
-debug_selector = D_host_lookup|D_interface;
+debug_modify_channel(US"+host_lookup +interface");
 debug_file = stdout;
 debug_fd = fileno(debug_file);
 
 printf("Exim stand-alone host functions test\n");
 
 host_find_interfaces();
-debug_selector = D_host_lookup | D_dns;
+debug_modify_channel(US"-interface +dns");
 
 if (argc > 1) primary_hostname = argv[1];
 
diff --git a/src/src/imap_utf7.c b/src/src/imap_utf7.c
index 09a32e689..a1abec7a0 100644
--- a/src/src/imap_utf7.c
+++ b/src/src/imap_utf7.c
@@ -11,14 +11,14 @@
 
 #ifdef SUPPORT_I18N
 
-uschar *
-imap_utf7_encode(uschar *string, const uschar *charset, uschar sep,
-  uschar *specials, uschar **error)
+const uschar *
+imap_utf7_encode(const uschar * string, const uschar * charset, uschar sep,
+  const uschar * specials, uschar ** error)
 {
 static uschar encode_base64[64] =
   "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,";
 size_t slen;
-uschar *sptr;
+const uschar * sptr;
 gstring * yield = NULL;
 int i = 0;	/* compiler quietening */
 uschar c = 0;	/* compiler quietening */
@@ -26,7 +26,7 @@ BOOL base64mode = FALSE;
 BOOL lastsep = FALSE;
 uschar utf16buf[256];
 uschar *utf16ptr;
-uschar *s;
+const uschar * s;
 uschar outbuf[256];
 uschar *outptr = outbuf;
 #if HAVE_ICONV
diff --git a/src/src/ip.c b/src/src/ip.c
index f48fa82a9..70ec05a39 100644
--- a/src/src/ip.c
+++ b/src/src/ip.c
@@ -41,7 +41,7 @@ ip_socket(int type, int af)
 {
 int sock = socket(af, type, 0);
 if (sock < 0)
-  log_write(0, LOG_MAIN, "IPv%c socket creation failed: %s",
+  log_write(LOG_MAIN, "IPv%c socket creation failed: %s",
     (af == AF_INET6)? '6':'4', strerror(errno));
 return sock;
 }
@@ -77,7 +77,7 @@ ip_addrinfo(const uschar *address, struct sockaddr_in6 *saddr)
 #ifdef IPV6_USE_INET_PTON
 
   if (inet_pton(AF_INET6, CCS address, &saddr->sin6_addr) != 1)
-    log_write_die(0, LOG_MAIN, "unable to parse %q as an "
+    log_write_die(LOG_MAIN, "unable to parse %q as an "
       "IP address", address);
   saddr->sin6_family = AF_INET6;
 
@@ -90,7 +90,7 @@ ip_addrinfo(const uschar *address, struct sockaddr_in6 *saddr)
   hints.ai_socktype = SOCK_STREAM;
   hints.ai_flags = AI_NUMERICHOST;
   if ((rc = getaddrinfo(CCS address, NULL, &hints, &res)) != 0 || res == NULL)
-    log_write_die(0, LOG_MAIN, "unable to parse %q as an "
+    log_write_die(LOG_MAIN, "unable to parse %q as an "
       "IP address: %s", address,
       rc == 0 ? "NULL result returned" : gai_strerror(rc));
   else
@@ -163,7 +163,7 @@ union sockaddr_46 sin;
 int s_len = ip_addr(&sin, af, address, port);
 int rc = bind(sock, (struct sockaddr *)&sin, s_len);
 if (rc < 0)
-  log_write(0, LOG_MAIN, "bind of [%s]:%d failed", address, port);
+  log_write(LOG_MAIN, "bind of [%s]:%d failed", address, port);
 return rc;
 }
 
@@ -249,7 +249,7 @@ if (fastopen_blob && f.tcp_fastopen_ok)
 	/* 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(transport|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 */
@@ -263,7 +263,7 @@ if (fastopen_blob && f.tcp_fastopen_ok)
 	/* 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 ? */
-      DEBUG(D_transport|D_v) debug_printf(" TFO mode sendto, %s data: EINPROGRESS\n",
+      DEBUG(transport|v) debug_printf(" TFO mode sendto, %s data: EINPROGRESS\n",
 	fastopen_blob->len > 0 ? "with"  : "no");
       if (!fastopen_blob->data)
 	{
@@ -275,12 +275,12 @@ if (fastopen_blob && f.tcp_fastopen_ok)
       break;
 
     case EOPNOTSUPP:
-      DEBUG(D_transport)
+      DEBUG(transport)
 	debug_printf("Tried TCP Fast Open but apparently not enabled by sysctl\n");
       goto legacy_connect;
 
     case EPIPE:
-      DEBUG(D_transport)
+      DEBUG(transport)
 	debug_printf("Tried TCP Fast Open but kernel too old to support it\n");
       goto legacy_connect;
     }
@@ -290,14 +290,14 @@ if (fastopen_blob && f.tcp_fastopen_ok)
 
   if (setsockopt(sock, IPPROTO_TCP, TCP_FASTOPEN, &on, sizeof(on)) < 0)
     {
-    DEBUG(D_transport)
+    DEBUG(transport)
       debug_printf("Tried TCP Fast Open but apparently not enabled by sysctl\n");
     goto legacy_connect;
     }
   if ((rc = sendto(sock, fastopen_blob->data, fastopen_blob->len, 0,
 		    s_ptr, s_len)) >= 0)
     {
-    DEBUG(D_transport|D_v)
+    DEBUG(transport|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;
@@ -315,18 +315,18 @@ if (fastopen_blob && f.tcp_fastopen_ok)
   if ((rc = connectx(sock, &ends, SAE_ASSOCID_ANY,
 	     CONNECT_DATA_IDEMPOTENT, &iov, 1, &len, NULL)) == 0)
     {
-    DEBUG(D_transport|D_v)
+    DEBUG(transport|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(transport|v)
 	debug_printf(" only queued %lu data!\n", (unsigned long)len);
     }
   else if (errno == EINPROGRESS)
     {
-    DEBUG(D_transport|D_v) debug_printf(" TFO mode connectx, %s data: EINPROGRESS\n",
+    DEBUG(transport|v) debug_printf(" TFO mode connectx, %s data: EINPROGRESS\n",
       fastopen_blob->len > 0 ? "with"  : "no");
     if (!fastopen_blob->data)
       {
@@ -345,7 +345,7 @@ else
 legacy_connect:
 #endif
 
-  DEBUG(D_transport|D_v) if (fastopen_blob)
+  DEBUG(transport|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)
@@ -573,7 +573,7 @@ ip_keepalive(int sock, const uschar *address, BOOL torf)
 int fodder = 1;
 if (setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE,
     US (&fodder), sizeof(fodder)) != 0)
-  log_write(0, LOG_MAIN, "setsockopt(SO_KEEPALIVE) on connection %s %s "
+  log_write(LOG_MAIN, "setsockopt(SO_KEEPALIVE) on connection %s %s "
     "failed: %s", torf? "to":"from", address, strerror(errno));
 }
 
@@ -604,7 +604,7 @@ if (time_left <= 0)
 
 do
   {
-  /*DEBUG(D_transport) debug_printf("waiting for data on fd\n");*/
+  /*DEBUG(transport) debug_printf("waiting for data on fd\n");*/
   rc = poll_one_fd(fd, POLLIN, time_left * 1000);
 
   /* If some interrupt arrived, just retry. We presume this to be rare,
@@ -619,7 +619,7 @@ do
 
   if (rc < 0 && errno == EINTR)
     {
-    DEBUG(D_transport) debug_printf("EINTR while waiting for socket data\n");
+    DEBUG(transport) debug_printf("EINTR while waiting for socket data\n");
 
     /* Watch out, 'continue' jumps to the condition, not to the loops top */
     if ((time_left = timelimit - time(NULL)) > 0) continue;
@@ -681,179 +681,6 @@ return -1;
 
 
 
-
-/*************************************************
-*    Lookup address family of potential socket   *
-*************************************************/
-
-/* Given a file-descriptor, check to see if it's a socket and, if so,
-return the address family; detects IPv4 vs IPv6.  If not a socket then
-return -1.
-
-The value 0 is typically AF_UNSPEC, which should not be seen on a connected
-fd.  If the return is -1, the errno will be from getsockname(); probably
-ENOTSOCK or ECONNRESET.
-
-Arguments:     socket-or-not fd
-Returns:       address family or -1
-*/
-
-int
-ip_get_address_family(int fd)
-{
-struct sockaddr_storage ss;
-socklen_t sslen = sizeof(ss);
-
-if (getsockname(fd, (struct sockaddr *) &ss, &sslen) < 0)
-  return -1;
-
-return (int) ss.ss_family;
-}
-
-
-
-
-/*************************************************
-*       Lookup DSCP settings for a socket        *
-*************************************************/
-
-struct dscp_name_tableentry {
-  const uschar *name;
-  int value;
-};
-/* Keep both of these tables sorted! */
-static struct dscp_name_tableentry dscp_table[] = {
-#ifdef IPTOS_DSCP_AF11
-    { CUS"af11", IPTOS_DSCP_AF11 },
-    { CUS"af12", IPTOS_DSCP_AF12 },
-    { CUS"af13", IPTOS_DSCP_AF13 },
-    { CUS"af21", IPTOS_DSCP_AF21 },
-    { CUS"af22", IPTOS_DSCP_AF22 },
-    { CUS"af23", IPTOS_DSCP_AF23 },
-    { CUS"af31", IPTOS_DSCP_AF31 },
-    { CUS"af32", IPTOS_DSCP_AF32 },
-    { CUS"af33", IPTOS_DSCP_AF33 },
-    { CUS"af41", IPTOS_DSCP_AF41 },
-    { CUS"af42", IPTOS_DSCP_AF42 },
-    { CUS"af43", IPTOS_DSCP_AF43 },
-    { CUS"ef", IPTOS_DSCP_EF },
-#endif
-#ifdef IPTOS_LOWCOST
-    { CUS"lowcost", IPTOS_LOWCOST },
-#endif
-    { CUS"lowdelay", IPTOS_LOWDELAY },
-#ifdef IPTOS_MINCOST
-    { CUS"mincost", IPTOS_MINCOST },
-#endif
-    { CUS"reliability", IPTOS_RELIABILITY },
-    { CUS"throughput", IPTOS_THROUGHPUT }
-};
-static int dscp_table_size =
-  sizeof(dscp_table) / sizeof(struct dscp_name_tableentry);
-
-/* DSCP values change by protocol family, and so do the options used for
-setsockopt(); this utility does all the lookups.  It takes an unexpanded
-option string, expands it, strips off affix whitespace, then checks if it's
-a number.  If all of what's left is a number, then that's how the option will
-be parsed and success/failure is a range check.  If it's not all a number,
-then it must be a supported keyword.
-
-Arguments:
-  dscp_name   a string, so far unvalidated
-  af          address_family in use
-  level       setsockopt level to use
-  optname     setsockopt name to use
-  dscp_value  value for dscp_name
-
-Returns: TRUE if okay to setsockopt(), else FALSE
-
-*level and *optname may be set even if FALSE is returned
-*/
-
-BOOL
-dscp_lookup(const uschar *dscp_name, int af,
-    int *level, int *optname, int *dscp_value)
-{
-uschar *dscp_lookup, *p;
-int first, last;
-long rawlong;
-
-if (af == AF_INET)
-  {
-  *level = IPPROTO_IP;
-  *optname = IP_TOS;
-  }
-#if HAVE_IPV6 && defined(IPV6_TCLASS)
-else if (af == AF_INET6)
-  {
-  *level = IPPROTO_IPV6;
-  *optname = IPV6_TCLASS;
-  }
-#endif
-else
-  {
-  DEBUG(D_transport)
-    debug_printf("Unhandled address family %d in dscp_lookup()\n", af);
-  return FALSE;
-  }
-if (!dscp_name)
-  {
-  DEBUG(D_transport)
-    debug_printf("[empty DSCP]\n");
-  return FALSE;
-  }
-dscp_lookup = expand_string(US dscp_name);
-if (dscp_lookup == NULL || *dscp_lookup == '\0')
-  return FALSE;
-
-p = dscp_lookup + Ustrlen(dscp_lookup) - 1;
-while (isspace(*p)) *p-- = '\0';
-while (isspace(*dscp_lookup) && dscp_lookup < p) dscp_lookup++;
-if (*dscp_lookup == '\0')
-  return FALSE;
-
-rawlong = Ustrtol(dscp_lookup, &p, 0);
-if (p != dscp_lookup && *p == '\0')
-  {
-  /* We have six bits available, which will end up shifted to fit in 0xFC mask.
-  RFC 2597 defines the values unshifted. */
-  if (rawlong < 0 || rawlong > 0x3F)
-    {
-    DEBUG(D_transport)
-      debug_printf("DSCP value %ld out of range, ignored.\n", rawlong);
-    return FALSE;
-    }
-  *dscp_value = rawlong << 2;
-  return TRUE;
-  }
-
-first = 0;
-last = dscp_table_size;
-while (last > first)
-  {
-  int middle = (first + last)/2;
-  int c = Ustrcmp(dscp_lookup, dscp_table[middle].name);
-  if (c == 0)
-    {
-    *dscp_value = dscp_table[middle].value;
-    return TRUE;
-    }
-  else if (c > 0)
-    first = middle + 1;
-  else
-    last = middle;
-  }
-return FALSE;
-}
-
-void
-dscp_list_to_stream(FILE *stream)
-{
-for (int i = 0; i < dscp_table_size; ++i)
-  fprintf(stream, "%s\n", dscp_table[i].name);
-}
-
-
 /* End of ip.c */
 /* vi: aw ai sw=2
 */
diff --git a/src/src/local_scan.h b/src/src/local_scan.h
index a97fb4612..f79efd3cf 100644
--- a/src/src/local_scan.h
+++ b/src/src/local_scan.h
@@ -29,6 +29,7 @@ store.c
 settings, and the store functions. */
 
 #include <stdarg.h>
+#include <stdint.h>
 #include <sys/types.h>
 #include "config.h"
 #include "mytypes.h"
@@ -43,7 +44,7 @@ ABI is changed in a non backward compatible way. The minor number is increased
 each time a new feature is added (in a way that doesn't break backward
 compatibility). */
 
-#define LOCAL_SCAN_ABI_VERSION_MAJOR 6
+#define LOCAL_SCAN_ABI_VERSION_MAJOR 9
 #define LOCAL_SCAN_ABI_VERSION_MINOR 0
 #define LOCAL_SCAN_ABI_VERSION \
   LOCAL_SCAN_ABI_VERSION_MAJOR.LOCAL_SCAN_ABI_VERSION_MINOR
@@ -94,10 +95,10 @@ codes that dynamically-loaded ${dlfunc functions must return. */
 #define LOG_REJECT     16    /* Write to the reject log, with headers */
 
 
-/* Accessible debugging bits */
+/* Accessible debugging channels */
 
-#define D_v                          0x00000001
-#define D_local_scan                 0x00000002
+#define D_v                          US"v"
+#define D_local_scan                 US"local_scan"
 
 
 /* Option types that can be used for local_scan_options. The boolean ones
@@ -158,18 +159,13 @@ typedef struct recipient_item {
   const uschar *address;        /* the recipient address */
   int     pno;                  /* parent number for "one_time" alias, or -1 */
   const uschar *errors_to;      /* the errors_to address or NULL */
-  uschar *orcpt;                /* DSN orcpt */
+  const uschar *orcpt;          /* DSN orcpt */
   int     dsn_flags;            /* DSN flags */
-#ifdef EXPERIMENTAL_BRIGHTMAIL
-  uschar *bmi_optin;
-#endif
 } recipient_item;
 
 
 /* Global variables that are documented as visible in the function. */
 
-extern unsigned int debug_selector;    /* Debugging bits */
-
 extern int     body_linecount;         /* Line count in body */
 extern int     body_zerocount;         /* Binary zero count in body */
 extern uschar *expand_string_message;  /* Error info for failing expansion */
@@ -195,23 +191,37 @@ extern BOOL    smtp_input;             /* TRUE if input is via SMTP */
 /* Functions that are documented as visible in local_scan(). */
 
 extern int     child_close(pid_t, int);
+extern void    debug_modify_channel(const uschar *);
 extern void    debug_printf(const char *, ...) PRINTF_FUNCTION(1,2);
-extern uschar *expand_string(uschar *);
+extern BOOL    is_debug(const uschar *);
+
+extern const uschar * expand_string_2(const uschar *, BOOL *);
+static inline uschar * expand_nc_string(uschar * s)
+{ return US expand_string_2(s, NULL); }
+static inline const uschar * expand_c_string(const uschar * s)
+{ return expand_string_2(s, NULL); }
+
+/* A macro that picks which function to use depending on the type of the arg */
+#define expand_string(X) _Generic((X),     \
+	      uschar *:		expand_nc_string, \
+	      const uschar *:	expand_c_string   \
+	      )(X)
+
 extern void    header_add(int, const char *, ...);
 extern void    header_add_at_position(BOOL, uschar *, BOOL, int, const char *, ...);
 extern void    header_remove(int, const uschar *);
 extern BOOL    header_testname(const header_line *, const uschar *, int, BOOL);
 extern BOOL    header_testname_incomplete(const header_line *, const uschar *, int, BOOL);
-extern void    log_write(unsigned int, int, const char *format, ...) PRINTF_FUNCTION(3,4);
+extern void    log_write(int, const char *format, ...) PRINTF_FUNCTION(2,3);
 extern int     lss_b64decode(uschar *, uschar **);
 extern uschar *lss_b64encode(uschar *, int);
 extern int     lss_match_domain(uschar *, uschar *);
 extern int     lss_match_local_part(uschar *, uschar *, BOOL);
 extern int     lss_match_address(uschar *, uschar *, BOOL);
 extern int     lss_match_host(uschar *, uschar *, uschar *);
-extern void    receive_add_recipient(const uschar *, int);
+extern void    receive_add_recipient(const uschar *, int, int, const uschar *);
 extern BOOL    receive_remove_recipient(const uschar *);
-extern uschar *rfc2047_decode(uschar *, BOOL, const uschar *, int, int *,
+extern const uschar *rfc2047_decode(const uschar *, BOOL, const uschar *, int, int *,
 			      uschar **);
 extern int     smtp_fflush(BOOL);
 extern void    smtp_printf(const char *, BOOL, ...) PRINTF_FUNCTION(1,3);
diff --git a/src/src/log.c b/src/src/log.c
index c485fc285..490b965dd 100644
--- a/src/src/log.c
+++ b/src/src/log.c
@@ -55,7 +55,7 @@ number definitions in macros.h */
 
 static const uschar * exim_errstrings[] = {
   [0] = US"",
-  [- ERRNO_UNKNOWNERROR] =	US"unknown error",
+  [- ERRNO_UNKNOWNERROR] =	US"error-code not set",
   [- ERRNO_USERSLASH] =		US"user slash",
   [- ERRNO_EXISTRACE] =		US"exist race",
   [- ERRNO_NOTREGULAR] =	US"not regular",
@@ -81,7 +81,7 @@ static const uschar * exim_errstrings[] = {
   [- ERRNO_FILTER_FAIL] =	US"Delivery filter process failure",
   [- ERRNO_CHHEADER_FAIL] =	US"Delivery add/remove header failure",
   [- ERRNO_WRITEINCOMPLETE] =	US"Delivery write incomplete error",
-  [- ERRNO_EXPANDFAIL] =	US"Some expansion failed",
+  [- ERRNO_EXPANDFAIL] =	US"string expansion failed",
   [- ERRNO_GIDFAIL] =		US"Failed to get gid",
   [- ERRNO_UIDFAIL] =		US"Failed to get uid",
   [- ERRNO_BADTRANSPORT] =	US"Unset or non-existent transport",
@@ -105,6 +105,12 @@ static const uschar * exim_errstrings[] = {
   [- ERRNO_AUTHPROB] =		US"Authenticator 'other' failure",
   [- ERRNO_UTF8_FWD] =		US"target not supporting SMTPUTF8",
   [- ERRNO_HOST_IS_LOCAL] =	US"host is local",
+  [- ERRNO_PASSONE] =		US"pass1",
+  [- ERRNO_NOROUTER] =		US"no handling router",
+  [- ERRNO_ROUTERDEFER] =	US"rt-defer",
+  [- ERRNO_ROUTERFAIL] =	US"rt-fail",
+  [- ERRNO_MXDEFER] =		US"mxdefer",
+  [- ERRNO_TPTLIMIT] =		US"tpt-lim",
   [- ERRNO_TAINT] =		US"tainted filename",
 
   [- ERRNO_RRETRY] =		US"Not time for routing",
@@ -260,9 +266,6 @@ subprocess when the original process is root.
 Arguments:
   name         the file name
 
-The file name has been build in a working buffer, so it is permissible to
-overwrite it temporarily if it is necessary to create the directory.
-
 Returns:       a file descriptor, or < 0 on failure (errno set)
 */
 
@@ -286,15 +289,14 @@ problem. */
 if (fd < 0 && errno == ENOENT)
   {
   BOOL created;
-  uschar *lastslash = Ustrrchr(name, '/');
-  *lastslash = 0;
-  created = directory_make(NULL, name, LOG_DIRECTORY_MODE, FALSE);
-  DEBUG(D_any)
+  const uschar * lastslash = Ustrrchr(name, '/');
+  uschar * dirname = string_copyn(name, lastslash - name);
+  created = directory_make(NULL, dirname, LOG_DIRECTORY_MODE, FALSE);
+  DEBUG(any)
     if (created)
-      debug_printf("created log directory %s\n", name);
+      debug_printf("created log directory %s\n", dirname);
     else
-      debug_printf("failed to create log directory %s: %s\n", name, strerror(errno));
-  *lastslash = '/';
+      debug_printf("failed to create log directory %s: %s\n", dirname, strerror(errno));
   if (created) fd = Uopen(name, flags, LOG_MODE);
   }
 
@@ -466,15 +468,14 @@ to create the file; if running as root, this must be done in a subprocess to
 avoid races.
 
 Arguments:
-  fd         where to return the resulting file descriptor
   type       lt_main, lt_reject, lt_panic, or lt_debug
   tag        optional tag to include in the name (only hooked up for debug)
 
-Returns:   nothing
+Returns:   fd
 */
 
-static void
-open_log(int * fd, int type, const uschar * tag)
+static int
+open_log(int type, const uschar * tag)
 {
 uid_t euid;
 BOOL ok, ok2;
@@ -559,8 +560,10 @@ if (!ok)
 
 /* We now have the file name. After a successful open, return. */
 
-if ((*fd = log_open_as_exim(buffer)) >= 0)
-  return;
+ {
+  int fd = log_open_as_exim(buffer);
+  if (fd >= 0) return fd;
+ }
 
 euid = geteuid();
 
@@ -571,10 +574,7 @@ just bombing out, force the log to stderr and carry on if stderr is available.
 */
 
 if (euid != root_uid && euid != exim_uid && log_stderr)
-  {
-  *fd = fileno(log_stderr);
-  return;
-  }
+  return fileno(log_stderr);
 
 /* Otherwise this is a disaster. This call is deliberately ONLY to the panic
 log. If possible, save a copy of the original line that was being logged. If we
@@ -587,7 +587,7 @@ if (!panic_save_buffer)
   if ((panic_save_buffer = US malloc(LOG_BUFFER_SIZE)))
     memcpy(panic_save_buffer, log_buffer, LOG_BUFFER_SIZE);
 
-log_write_die(0, LOG_PANIC_DIE, "Cannot open %s log file %q: %s: "
+log_write_die(LOG_PANIC_DIE, "Cannot open %s log file %q: %s: "
   "euid=%d egid=%d", log_names[type], buffer, strerror(errno), euid, getegid());
 /* Never returns */
 }
@@ -707,7 +707,7 @@ if (!panic_save_buffer)
   if ((panic_save_buffer = US malloc(LOG_BUFFER_SIZE)))
     memcpy(panic_save_buffer, log_buffer, LOG_BUFFER_SIZE);
 
-log_write_die(0, LOG_PANIC_DIE, "failed to write to %s: length=%d result=%d "
+log_write_die(LOG_PANIC_DIE, "failed to write to %s: length=%d result=%d "
   "errno=%d (%s)", name, length, rc, save_errno,
   save_errno == 0 ? "write incomplete" : strerror(save_errno));
 /* Never returns */
@@ -769,27 +769,22 @@ static void
 set_file_path(void)
 {
 int sep = ':';              /* Fixed separator - outside use */
-uschar *t;
-const uschar *tt = US LOG_FILE_PATH;
+const uschar * t, * 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;
-  }
+  if (Ustrcmp(t, "syslog") != 0 && *t)
+    { file_path = string_copy(t); break; }
 }
 
 
 /* Close mainlog, unless we do not see a chance to open the file mainlog later
 again.  This will happen if we log from a transport process (which has dropped
 privs); something we traditionally avoid, but the introduction of taint-tracking
-and resulting detection of errors is makinng harder. */
+and resulting detection of errors is making harder. */
 
 void
 mainlog_close(void)
 {
-if (mainlogfd < 0
-   || !(geteuid() == 0 || geteuid() == exim_uid))
+if (mainlogfd < 0 || !(geteuid() == 0 || geteuid() == exim_uid))
   return;
 (void)close(mainlogfd);
 mainlogfd = -1;
@@ -835,8 +830,6 @@ Malloc is used directly because the store functions may call log_write().
 If a message_id exists, we include it after the timestamp.
 
 Arguments:
-  selector  write to main log or LOG_INFO only if this value is zero, or if
-              its bit is set in log_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
@@ -854,12 +847,11 @@ Returns:    nothing
 */
 
 static void
-log_vwrite(unsigned int selector, int flags, const char * format, va_list ap)
+log_vwrite(int flags, const char * format, va_list ap)
 {
 int paniclogfd;
 ssize_t written_len;
-gstring gs = { .size = LOG_BUFFER_SIZE-2, .ptr = 0, .s = log_buffer };
-gstring * g = &gs;
+gstring gs = { .size = LOG_BUFFER_SIZE-2 }, * g = &gs;
 
 /* If panic_recurseflag is set, we have failed to open the panic log. This is
 the ultimate disaster. First try to write the message to a debug file and/or
@@ -887,6 +879,7 @@ if (!log_buffer)
     fprintf(stderr, "exim: failed to get store for log buffer\n");
     exim_exit(EXIT_FAILURE);
     }
+gs.s = log_buffer;
 
 /* 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
@@ -955,7 +948,7 @@ if (!path_inspected)
   should work since we have now set up the routing. */
 
   if (multiple)
-    log_write(0, LOG_MAIN|LOG_PANIC,
+    log_write(LOG_MAIN|LOG_PANIC,
       "More than one path given in log_file_path: using %s", file_path);
   }
 
@@ -967,20 +960,11 @@ if (flags & LOG_PANIC && dtrigger_selector & BIT(DTi_panictrigger))
 /* If debugging, show all log entries, but don't show headers. Do it all
 in one go so that it doesn't get split when multi-processing. */
 
-DEBUG(D_any|D_v)
+DEBUG(any|v)
   {
   va_list aq;
   string_fmt_append_noextend(g, "LOG:");
 
-  /* Show the selector that was passed into the call. */
-
-  for (int i = 0; i < log_options_count; i++)
-    {
-    unsigned int bitnum = log_options[i].bit;
-    if (bitnum < BITWORDSIZE && selector == BIT(bitnum))
-      string_fmt_append_noextend(g, " %s", log_options[i].name);
-    }
-
   string_fmt_append_noextend(g, "%s%s%s%s\n  ",
     flags & LOG_MAIN ?    " MAIN"   : "",
     flags & LOG_PANIC ?   " PANIC"  : "",
@@ -1011,13 +995,13 @@ DEBUG(D_any|D_v)
 /* If no log file is specified, we are in a mess. */
 
 if (!(flags & (LOG_MAIN|LOG_PANIC|LOG_REJECT)))
-  log_write_die(0, LOG_MAIN, "log_write called with no log flags set");
+  log_write_die(LOG_MAIN, "log_write called with no log flags set");
 
 /* There are some weird circumstances in which logging is disabled. */
 
 if (f.disable_logging)
   {
-  DEBUG(D_any) debug_printf("log writing disabled\n");
+  DEBUG(any) debug_printf("log writing disabled\n");
   if ((flags & LOG_PANIC_DIE) == LOG_PANIC_DIE) exim_exit(EXIT_FAILURE);
   return;
   }
@@ -1034,7 +1018,7 @@ string_fmt_append_noextend(&gs, "%s ", tod_stamp(tod_log));
 if (LOGGING(pid))
   {
   if (!syslog_pid) pid_position[0] = gstring_length(g);	/* remember begin … */
-  string_fmt_append_noextend(g, "[%ld] ", (long)getpid());
+  string_fmt_append_noextend(g, "[" PID_T_FMT "] ", getpid());
   if (!syslog_pid) pid_position[1] = gstring_length(g);	/*  … and end+1 of the PID */
   }
 
@@ -1089,12 +1073,10 @@ or unless there is no log_stderr (expn called from daemon, for example). */
 
 if (!f.really_exim || f.log_testing_mode)
   {
-  if (  !debug_selector
-     && log_stderr
-     && (selector == 0 || (selector & log_selector[0]) != 0)
-    )
+  if (!ANY_DEBUG && log_stderr)
     if (host_checking)
-      fprintf(log_stderr, "LOG: %s", CS(log_buffer + 20));  /* no timestamp */
+/*XXX +20 wrong if logging millisec or with-TZ */
+      fprintf(log_stderr, "LOG: %s", CS log_buffer + 20);  /* no timestamp */
     else
       fprintf(log_stderr, "%s", CS log_buffer);
 
@@ -1108,8 +1090,7 @@ been opened, but we don't want to keep on writing to it for too long after it
 has been renamed. Therefore, do a stat() and see if the inode has changed, and
 if so, re-open. */
 
-if (  flags & LOG_MAIN
-   && (!selector ||  selector & log_selector[0]))
+if (flags & LOG_MAIN)
   {
   if (  logging_mode & LOG_MODE_SYSLOG
      && (syslog_duplication || !(flags & (LOG_REJECT|LOG_PANIC))))
@@ -1148,7 +1129,7 @@ if (  flags & LOG_MAIN
 
     if (mainlogfd < 0)
       {
-      open_log(&mainlogfd, lt_main, NULL);     /* No return on error */
+      mainlogfd = open_log(lt_main, NULL);     /* No return on error */
       if (fstat(mainlogfd, &statbuf) >= 0) mainlog_inode = statbuf.st_ino;
       }
 
@@ -1251,7 +1232,7 @@ if (flags & LOG_REJECT)
 
     if (rejectlogfd < 0)
       {
-      open_log(&rejectlogfd, lt_reject, NULL); /* No return on error */
+      rejectlogfd = open_log(lt_reject, NULL); /* No return on error */
       if (fstat(rejectlogfd, &statbuf) >= 0) rejectlog_inode = statbuf.st_ino;
       }
 
@@ -1284,12 +1265,12 @@ if (flags & LOG_PANIC)
   if (logging_mode & LOG_MODE_FILE)
     {
     panic_recurseflag = TRUE;
-    open_log(&paniclogfd, lt_panic, NULL);  /* Won't return on failure */
+    paniclogfd = open_log(lt_panic, NULL);  /* Won't return on failure */
     panic_recurseflag = FALSE;
 
     if (panic_save_buffer)
       if (write(paniclogfd, panic_save_buffer, Ustrlen(panic_save_buffer)) < 0)
-	DEBUG(D_any) debug_printf("sucks");
+	DEBUG(any) debug_printf("sucks");
 
     if (  (written_len = write_gstring_to_fd_buf(paniclogfd, g))
        != gstring_length(g))
@@ -1318,24 +1299,24 @@ if (flags & LOG_PANIC)
 /* The public interface */
 
 void
-log_write(unsigned int selector, int flags, const char * format, ...)
+log_write(int flags, const char * format, ...)
 {
 va_list ap;
 va_start(ap, format);
-log_vwrite(selector, flags, format, ap);
+log_vwrite(flags, format, ap);
 va_end(ap);
 }
 
 /* As the above, but adds in LOG_PANIC_DIE.
-We have this as a wripper so that we can mark it as never returning,
+We have this as a wrapper so that we can mark it as never returning,
 for the benefit of static analysers. */
 
 void
-log_write_die(unsigned int selector, int flags, const char * format, ...)
+log_write_die(int flags, const char * format, ...)
 {
 va_list ap;
 va_start(ap, format);
-log_vwrite(selector, flags | LOG_PANIC_DIE, format, ap);
+log_vwrite(flags | LOG_PANIC_DIE, format, ap);
 UNREACHABLE;
 }
 
@@ -1359,37 +1340,44 @@ syslog_open = FALSE;
 
 
 /*************************************************
-*             Multi-bit set or clear             *
+*         Decode bit settings for log/debug      *
 *************************************************/
 
-/* These functions take a list of bit indexes (terminated by -1) and
-clear or set the corresponding bits in the selector.
+/* Locate a word in a channel list.
 
-Arguments:
-  selector       address of the bit string
-  selsize        number of words in the bit string
-  bits           list of bits to set
+Arguments:	word		The name to search for
+		len		Number of chars in name
+		channels	List of channel names + numbers
+		count		Number of list entries, incl. leading specials
+Return channel number, or zero for not-found
 */
 
-void
-bits_clear(unsigned int *selector, size_t selsize, int *bits)
+unsigned
+chan_name_to_num(const uschar * word, unsigned len,
+  bit_table * channels, unsigned count)
 {
-for(; *bits != -1; ++bits)
-  BIT_CLEAR(selector, selsize, *bits);
-}
+bit_table * start = channels;
+bit_table * end = channels + count;
 
-void
-bits_set(unsigned int *selector, size_t selsize, int *bits)
-{
-for(; *bits != -1; ++bits)
-  BIT_SET(selector, selsize, *bits);
-}
+while (start < end)
+  {
+  bit_table * middle = start + (end - start)/2;
+  int c;
 
+  if ((c = Ustrncmp(word, middle->name, len)) == 0)
+    if (middle->name[len])
+      c = -1;
+    else
+      return middle->logchan_bit;
 
+  if (c < 0)
+    end = middle;
+  else
+    start = middle + 1;
+  }  /* Loop to match selector name */
 
-/*************************************************
-*         Decode bit settings for log/debug      *
-*************************************************/
+return 0;			/* Fail */
+}
 
 /* This function decodes a string containing bit settings in the form of +name
 and/or -name sequences, and sets/unsets bits in a bit string accordingly. It
@@ -1397,9 +1385,11 @@ also recognizes a numeric setting of the form =<number>, but this is not
 intended for user use. It's an easy way for Exim to pass the debug settings
 when it is re-exec'ed.
 
-The 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 option table is a list of names, with offsets corresponding to bit numbers
+in the bit string.  The name "all" means "set all bits, except for those listed
+in notall". The notall list is terminated by -1.
+
+For the debug bitstring we also set various summary bits.
 
 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
@@ -1409,33 +1399,48 @@ we treat it as an unknown option: error message to stderr and die.
 Arguments:
   selector       address of the bit string
   selsize        number of words in the bit string
-  notall         list of bits to exclude from "all"
+  notall         list of words to exclude from "all"
   string         the configured string
-  options        the table of option names
+  options        table of option names & channel-numbers
   count          size of table
-  which          "log" or "debug"
-  flags          DEBUG_FROM_CONFIG
+  flags          DCB_LOG, DCB_DEBUG, DCB_FROM_CONFIG
 
 Returns:         nothing on success - bomb out on failure
 */
 
 void
-decode_bits(unsigned int * selector, size_t selsize, int * notall,
-  const uschar * string, bit_table * options, int count, uschar * which,
-  int flags)
+decode_bits(bitmask_word_t * selector, size_t selsize,
+  const uschar * const * notall, const uschar * string,
+  bit_table * options, int count, int flags)
 {
-uschar *errmsg;
+uschar * errmsg;
+
 if (!string) return;
 
 if (*string == '=')
   {
-  char *end;    /* Not uschar */
+  int n;
+  const uschar * s = string + 1;
+
   memset(selector, 0, sizeof(*selector)*selsize);
-  *selector = strtoul(CCS string+1, &end, 0);
-  if (!*end) return;
-  errmsg = string_sprintf("malformed numeric %s_selector setting: %s", which,
-    string);
-  goto ERROR_RETURN;
+
+  for (unsigned idx = 0;
+       idx < selsize
+       && sscanf(CCS s, SC_EXIM_BITMASK "%n", selector+idx, &n) == 1;
+       idx++)
+    {
+    s += n;
+    switch (*s)
+      {
+      case '\0':	return;		/* no more words; finished */
+      case ',':		s++; break;	/* next word */
+      default:
+	errmsg = string_sprintf(
+	  "malformed numeric %s_selector setting: %q (n %d, fmt %q)",
+	  flags & DCB_LOG ? "log" : "debug", string, n, SC_EXIM_BITMASK);
+	goto ERROR_RETURN;
+      }
+    }
   }
 
 /* Handle symbolic setting */
@@ -1445,80 +1450,113 @@ else for(;;)
   BOOL adding;
   const uschar * s;
   int len;
-  bit_table * start, * end;
 
   Uskip_whitespace(&string);
-  if (!*string) return;
-
-  if (*string != '+' && *string != '-')
+  switch (*string++)
     {
-    errmsg = string_sprintf("malformed %s_selector setting: "
-      "+ or - expected but found %q", which, string);
-    goto ERROR_RETURN;
+    case '\0':	return;			/* No more string to handle */
+    case '+':	adding = TRUE; break;
+    case '-':	adding = FALSE; break;
+    default:	errmsg = string_sprintf("malformed %s_selector setting: "
+		  "+ or - expected but found %q", flags & DCB_LOG ? "log" : "debug", string);
+		goto ERROR_RETURN;
     }
 
-  adding = *string++ == '+';
-  s = string;
-  while (isalnum(*string) || *string == '_') string++;
+  for (s = string; isalnum(*string) || *string == '_'; ) string++;
   len = string - s;
 
-  start = options;
-  end = options + count;
+  if (Ustrncmp(s, "all", len) == 0)
+    if (adding)
+      {
+      memset(selector, -1, sizeof(*selector)*selsize);
+      for (const uschar * const * p = notall; *p; p++)
+	bit_clear(selector, chan_name_to_num(*p, Ustrlen(*p), options, count));
+      }
+    else
+      memset(selector, 0, sizeof(*selector)*selsize);
 
-  while (start < end)
+  else
     {
-    bit_table *middle = start + (end - start)/2;
-    int c = Ustrncmp(s, middle->name, len);
-    if (c == 0)
-      if (middle->name[len] != 0) c = -1; else
-        {
-        unsigned int bit = middle->bit;
+    unsigned idx = chan_name_to_num(s, len, options, count);
+    if (!idx)
+      {
+      errmsg = string_sprintf("unknown %s_selector setting: %c%.*s",
+	flags & DCB_LOG ? "log" : "debug", adding ? '+' : '-', len, s);
+      goto ERROR_RETURN;
+      }
 
-	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);
+    if (adding)
+      {
+      bit_set(selector, idx);
 
-        break;  /* Out of loop to match selector name */
-        }
-    if (c < 0) end = middle; else start = middle + 1;
-    }  /* Loop to match selector name */
+      if (flags & DCB_DEBUG)
+	{
+	bit_set(selector, BIT_TABLE_IDX_NONZERO);
 
-  if (start >= end)
-    {
-    errmsg = string_sprintf("unknown %s_selector setting: %c%.*s", which,
-      adding? '+' : '-', len, s);
-    goto ERROR_RETURN;
+	if (Ustrncmp(s, "v", len) != 0)
+	  {
+	  bit_set(selector, BIT_TABLE_IDX_NONVERB);
+
+	  /*XXX this might be a table in globals.c */
+	  if (  Ustrncmp(s, "pid", len) != 0 && Ustrncmp(s, "noutf8", len)  != 0
+	     && Ustrncmp(s, "timestamp", len) != 0)
+	  bit_set(selector, BIT_TABLE_IDX_IS_ANY);
+	  }
+	}
+      }
+    else
+      bit_clear(selector, idx);
     }
   }    /* Loop for selector names */
 
+/*NOTREACHED*/
+return;					/* stupid compiler */
+
 /* Handle disasters */
 
 ERROR_RETURN:
-if (Ustrcmp(which, "debug") == 0)
+if (flags & DCB_DEBUG)
   {
-  if (flags & DEBUG_FROM_CONFIG)
+  if (flags & DCB_FROM_CONFIG)
     {
-    log_write(0, LOG_CONFIG|LOG_PANIC, "%s", errmsg);
+    log_write(LOG_CONFIG|LOG_PANIC, "%s", errmsg);
     return;
     }
   fprintf(stderr, "exim: %s\n", errmsg);
   exim_exit(EXIT_FAILURE);
   }
-else log_write_die(0, LOG_CONFIG, "%s", errmsg);
+else
+  log_write_die(LOG_CONFIG, "%s", errmsg);
 }
 
 
+/* Channel configuration */
+
+void
+logging_modify_channels(const uschar * string)
+{
+decode_bits(log_selector, log_selector_size, log_notall_names, string,
+          log_channels, log_chan_count, DCB_LOG);
+}
+
+
+/* Called during init.
+We could instead just feed one long string to logging_enable_channels().
+We might save some startup time by doing this in buildconfig.
+*/
+
+void
+logging_set_defaults(void)
+{
+memset(log_selector, 0, sizeof(bitmask_word_t) * log_selector_size);
+bit_set(log_selector, BIT_TABLE_IDX_NONZERO);
+
+for (const uschar * const * p = log_default_names;
+     p < log_default_names + log_default_count; p++)
+  bit_set(log_selector,
+   chan_name_to_num(*p, Ustrlen(*p), log_channels, log_chan_count));
+}
+
 
 /*************************************************
 *        Activate a debug logfile (late)         *
@@ -1547,33 +1585,38 @@ if (debug_file)
   {
   debug_printf("DEBUGGING ACTIVATED FROM WITHIN CONFIG.\n"
       "DEBUG: Tag=%q opts=%q\n", tag_name, opts ? opts : US"");
+  debug_print_ids(US"");
   return;
   }
 
 if (tag_name && (Ustrchr(tag_name, '/') != NULL))
   {
-  log_write(0, LOG_MAIN|LOG_PANIC, "debug tag may not contain a '/' in: %s",
+  log_write(LOG_MAIN|LOG_PANIC, "debug tag may not contain a '/' in: %s",
       tag_name);
+  log_write(LOG_MAIN|LOG_PANIC,
+		  "debug tag may not contain a '/' in: %s", tag_name);
   return;
   }
 
-debug_selector = D_default;
+debug_set_default_bits(&debug_selector);
 if (opts)
-  decode_bits(&debug_selector, 1, debug_notall, opts,
-      debug_options, debug_options_count, US"debug", DEBUG_FROM_CONFIG);
+  debug_decode_bits(&debug_selector, opts, DCB_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 */
+do not segfault; note that nondefault log locations (set via log_file_path)
+will not work for that case. */
 
-if (!*file_path) set_file_path();
+if (!*file_path)
+  set_file_path();
 
-open_log(&debug_fd, lt_debug, tag_name);
+if ((debug_fd = open_log(lt_debug, tag_name)) < 0)
+  log_write(LOG_MAIN|LOG_PANIC, "unable to open debug log");
 
-if (debug_fd != -1)
-  debug_file = fdopen(debug_fd, "w");
-else
-  log_write(0, LOG_MAIN|LOG_PANIC, "unable to open debug log");
+debug_file = fdopen(debug_fd, "w");
+setbuf(debug_file, NULL);
+
+debug_print_ids(US"debug enabled:");
 }
 
 
@@ -1583,12 +1626,15 @@ debug_logging_from_spool(const uschar * filename)
 if (debug_fd < 0)
   {
   Ustrncpy(debuglog_name, filename, sizeof(debuglog_name)-1);
-  if ((debug_fd = log_open_as_exim(filename)) >= 0)
-    debug_file = fdopen(debug_fd, "w");
-  DEBUG(D_deliver) debug_printf("debug enabled by spoolfile\n");
+  if ((debug_fd = log_open_as_exim(filename)) < 0)
+    return;
+
+  debug_file = fdopen(debug_fd, "w");
+  setbuf(debug_file, NULL);
+  DEBUG(deliver) debug_print_ids(US"debug enabled by spoolfile\n");
   }
 /*
-else DEBUG(D_deliver)
+else DEBUG(deliver)
   debug_printf("debug already active; ignoring spoolfile '%s'\n", filename);
 */
 }
@@ -1601,7 +1647,7 @@ debug_printf("debug terminated by %s\n", kill ? "kill" : "stop");
 debug_pretrigger_discard();
 if (!debug_file || !debuglog_name[0]) return;
 
-debug_selector = 0;
+debug_decode_bits(&debug_selector, US"=0", 0);
 fclose(debug_file);
 debug_file = NULL;
 debug_fd = -1;
@@ -1610,5 +1656,5 @@ if (kill) unlink_log(lt_debug);
 
 
 /* End of log.c */
-/* vi: sw ai sw=2
+/* vi: aw ai sw=2
 */
diff --git a/src/src/lookupapi.h b/src/src/lookupapi.h
index 2e857c5d3..84f150e03 100644
--- a/src/src/lookupapi.h
+++ b/src/src/lookupapi.h
@@ -18,9 +18,10 @@
 */
 
 typedef struct lookup_info {
-  uschar *name;                   /* e.g. "lsearch" */
-  int type;                       /* query/singlekey/abs-file */
-  unsigned acq_num;		  /* acquisition number */
+  uschar *	name;			/* e.g. "lsearch" */
+  int		type;			/* query/singlekey/abs-file */
+  unsigned	acq_num;		/* acquisition number */
+
   void *(*open)(                  /* open function */
     const uschar *,               /* file name for those that have one */
     uschar **);                   /* for error message */
@@ -49,20 +50,26 @@ typedef struct lookup_info {
     unsigned);			  /* lookup type index */
   gstring * (*version_report)(    /* diagnostic function */
     gstring *);                   /* string to appand to */
+
+  void *	options;
+  unsigned	options_count;
+  void *	variables;
+  unsigned	variables_count;
 } lookup_info;
 
 /* This magic number is used by the following lookup_module_info structure
    for checking API compatibility. It used to be equivalent to the string"LMM3" */
-#define LOOKUP_MODULE_INFO_MAGIC 0x4c4d4935
+#define LOOKUP_MODULE_INFO_MAGIC 0x4c4d4936
 /* Version 2 adds: version_report */
 /* Version 3 change: non/cache becomes TTL in seconds */
 /* Version 4 add: index on quoting function */
 /* Version 5 change: version report now adds to a gstring */
+/* Version 6 add: options */
 
 typedef struct lookup_module_info {
-  uint magic;
+  uint		magic;
   lookup_info **lookups;
-  uint lookupcount;
+  uint		lookupcount;
 } lookup_module_info;
 
 /* End of lookupapi.h */
diff --git a/src/src/lookups/Makefile b/src/src/lookups/Makefile
index 74e705e37..560a5cba5 100644
--- a/src/src/lookups/Makefile
+++ b/src/src/lookups/Makefile
@@ -8,17 +8,39 @@
 #     extra variable definitions and prepended to it and module build rules
 #     interpolated below. This is done by scripts/lookups-Makefile.
 
-# When adding a new driver here, attend also to scripts/lookups-Makefile
-# and scripts/MakeLinks
+# When adding a new driver here, attend also to scripts/lookups-Makefile,
+# and scripts/MakeLinks.
 
 # MAGIC-TAG-MODS-OBJ-RULES-GO-HERE
 
-all:             Makefile lookups.a $(MODS)
+OBJS = $(OBJ) $(OBJ_ALWAYS)
 
-lookups.a:       $(OBJ)
+# This assumes that the driver and object-file names match
+AVAIL= $(OBJS:.o=)
+
+all:	Makefile lookups.a $(MODS)
+	$(FE):
+
+# We assume here that the driver name is used as the prefix for
+# its module_info struct.
+avail_static_lookups.c: Makefile
+	@echo "make $@"
+	$(FE)echo '/* File built by lookups/Makefile */' >$@
+	$(FE)echo '#include "../exim.h"' >>$@
+	$(FE)for f in $(AVAIL); do \
+	 echo "extern lookup_module_info $${f}_lookup_module_info;" >>$@; \
+	done
+	$(FE)echo 'lookup_module_info * avail_static_lookups[] = {' >>$@
+	$(FE)for f in $(AVAIL); do \
+	 echo "& $${f}_lookup_module_info," >>$@; \
+	done
+	$(FE)echo 'NULL };' >>$@
+avail_static_lookups.o: $(HDRS) avail_static_lookups.c
+
+lookups.a:       avail_static_lookups.o $(OBJS)
 		 @$(RM_COMMAND) -f lookups.a
 		 @echo "$(AR) lookups.a"
-		 @$(AR) lookups.a $(OBJ)
+		 $(FE)$(AR) lookups.a avail_static_lookups.o $(OBJS)
 		 $(RANLIB) $@
 
 .SUFFIXES:       .o .c .so
@@ -26,13 +48,14 @@ lookups.a:       $(OBJ)
 		 $(FE)$(CC) -c $(CFLAGS) $(INCLUDE) $*.c
 
 .c.so:;          @echo "$(CC) -shared $*.c"
-		 $(FE)$(CC) $(LOOKUP_$*_INCLUDE) $(LOOKUP_$*_LIBS) -DDYNLOOKUP $(CFLAGS_DYNAMIC) $(CFLAGS) $(INCLUDE) $(DLFLAGS) $*.c -o $@
+		 $(FE)$(CC) -DDYNLOOKUP $(CFLAGS_DYNAMIC) $(CFLAGS) \
+		 	$(LOOKUP_$*_INCLUDE) $(INCLUDE) $(DLFLAGS) $*.c \
+		 	$(LOOKUP_$*_LIBS) -o $@
 
 cdb.o cdb.so:           $(HDRS) cdb.c
 dbmdb.o dbmdb.so:       $(HDRS) dbmdb.c
 dnsdb.o dnsdb.so:       $(HDRS) dnsdb.c
 dsearch.o dsearch.so:   $(HDRS) dsearch.c
-ibase.o ibase.so:       $(HDRS) ibase.c
 ldap.o ldap.so:         $(HDRS) ldap.c
 lmdb.o lmdb.so:         $(HDRS) lmdb.c
 json.o json.so:         $(HDRS) json.c
@@ -44,6 +67,7 @@ nmh.o nmh.so:		$(HDRS) nmh.c
 oracle.o oracle.so:     $(HDRS) oracle.c
 passwd.o passwd.so:     $(HDRS) passwd.c
 pgsql.o pgsql.so:       $(HDRS) pgsql.c
+psl.o psl.so:       	$(HDRS) psl.c
 readsock.o readsock.so: $(HDRS) readsock.c
 redis.o redis.so:       $(HDRS) redis.c
 spf.o spf.so:           $(HDRS) spf.c
diff --git a/src/src/lookups/cdb.c b/src/src/lookups/cdb.c
index b56306a7f..0a91f640a 100644
--- a/src/src/lookups/cdb.c
+++ b/src/src/lookups/cdb.c
@@ -206,7 +206,7 @@ if ((mapbuf = mmap(NULL, statbuf.st_size, PROT_READ, MAP_SHARED, fileno, 0))
 /* If we got here the map failed.  Basically we can ignore this since we fall
 back to slower methods....  However lets debug log it...  */
 
-DEBUG(D_lookup) debug_printf_indent("cdb mmap failed - %d\n", errno);
+DEBUG(lookup) debug_printf_indent("cdb mmap failed - %d\n", errno);
 #endif /* HAVE_MMAP */
 
 /* In this case we have either not got MMAP allowed, or it failed */
@@ -293,7 +293,7 @@ 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_indent("%s\n", *errmsg);
+  DEBUG(lookup) debug_printf_indent("%s\n", *errmsg);
   return DEFER;
   }
 
@@ -462,10 +462,8 @@ if (cdbp->cdb_map)
 gstring *
 cdb_version_report(gstring * g)
 {
-#ifdef DYNLOOKUP
-g = string_fmt_append(g, "Library version: CDB: Exim version %s\n", EXIM_VERSION_STR);
-#endif
-return g;
+return string_fmt_append(g, "Library version: CDB: Exim %s builtin\n",
+			  EXIM_VERSION_STR);
 }
 
 
diff --git a/src/src/lookups/dbmdb.c b/src/src/lookups/dbmdb.c
index d3b26cf0a..4108d024b 100644
--- a/src/src/lookups/dbmdb.c
+++ b/src/src/lookups/dbmdb.c
@@ -91,7 +91,8 @@ dbmdb_find(void * handle, const uschar * filename, const uschar * keystring,
   const uschar * opts)
 {
 open_db * d = (open_db *)handle;
-return (*result = dbfn_read_klen(d, keystring, length+1, NULL)) ? OK : FAIL;
+return (*result = dbfn_read_klen(d, keystring, length+1, NULL, FALSE))
+	? OK : FAIL;
 }
 
 
@@ -181,7 +182,7 @@ if (key_p == key_buffer)
 empty element to put one in. Boundary: key length 1, is a NULL */
 key_item_len = key_p - key_buffer - 1;
 
-DEBUG(D_lookup) debug_printf_indent("NUL-joined key length: %d\n", key_item_len);
+DEBUG(lookup) debug_printf_indent("NUL-joined key length: %d\n", key_item_len);
 
 /* beware that dbmdb_find() adds 1 to length to get back terminating NUL, so
 because we've calculated the real length, we need to subtract one more here */
diff --git a/src/src/lookups/dnsdb.c b/src/src/lookups/dnsdb.c
index d08f6122e..37d7d1747 100644
--- a/src/src/lookups/dnsdb.c
+++ b/src/src/lookups/dnsdb.c
@@ -134,22 +134,19 @@ dnsdb_find(void * handle, const uschar * filename, const uschar * keystring,
  int length, uschar ** result, uschar ** errmsg, uint * do_cache,
  const uschar * opts)
 {
-int rc;
-int sep = 0;
 int defer_mode = PASS, dnssec_mode = PASS;
-int save_retrans = dns_retrans, save_retry =   dns_retry;
-int type;
-int failrc = FAIL;
+int save_retrans = dns_retrans, save_retry = dns_retry;
+int sep = 0, rc, type, failrc = FAIL;
 const uschar * outsep = CUS"\n", * outsep2 = NULL;
 uschar * equals, * domain, * found;
 
 dns_answer * dnsa = store_get_dns_answer();
-dns_scan dnss;
+dns_scan dnss = {0};
 
 /* Because we're working in the search pool, we try to reclaim as much
 store as possible later, so we preallocate the result here */
 
-gstring * yield = string_get(256);
+gstring * yield = string_get_tainted(256, GET_TAINTED);
 
 /* If the string starts with '>' we change the output separator.
 If it's followed by ';' or ',' we set the TXT output separator. */
@@ -326,7 +323,7 @@ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0)))
 
   do
     {
-    DEBUG(D_lookup) debug_printf_indent("dnsdb key: %s\n", domain);
+    DEBUG(lookup) debug_printf_indent("dnsdb key: %s\n", domain);
 
     /* Do the lookup and sort out the result. There are four special types that
     are handled specially: T_CSA, T_ZNS, T_ADDRESSES and T_MXH.
@@ -439,9 +436,14 @@ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0)))
       else   /* T_CNAME, T_CSA, T_MX, T_MXH, T_NS, T_PTR, T_SOA, T_SRV */
         {
         int priority, weight, port;
-        uschar s[264];
         uschar * p = US rr->data;
 
+	/* NB: this memory is released implicitly by the call
+	gstring_release_unused(yield) below. We used to use a stack-auto, but
+	I want to track taint wherever possible. */
+#define LCL_BUF_SIZE 264
+	uschar * buf = store_get(LCL_BUF_SIZE, GET_TAINTED);
+
 	switch (type)
 	  {
 	  case T_MXH:
@@ -453,8 +455,7 @@ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0)))
 	  case T_MX:
 	    if (rr_bad_size(rr, sizeof(uint16_t))) continue;
 	    GETSHORT(priority, p);
-	    sprintf(CS s, "%d%c", priority, *outsep2);
-	    yield = string_cat(yield, s);
+	    yield = string_fmt_append(yield, "%d%c", priority, *outsep2);
 	    break;
 
 	  case T_SRV:
@@ -462,9 +463,8 @@ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0)))
 	    GETSHORT(priority, p);
 	    GETSHORT(weight, p);
 	    GETSHORT(port, p);
-	    sprintf(CS s, "%d%c%d%c%d%c", priority, *outsep2,
+	    yield = string_fmt_append(yield, "%d%c%d%c%d%c", priority, *outsep2,
 			      weight, *outsep2, port, *outsep2);
-	    yield = string_cat(yield, s);
 	    break;
 
 	  case T_CSA:
@@ -481,20 +481,18 @@ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0)))
 	    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 */
-	      }
+	      yield = string_catn(yield, port & 1
+				  ? US"X " /* explicit authorization required */
+				  : US"? ", /* no subdomain assertions here */
+				  2);
+	    else if (weight > 3)
+	      continue;
 	    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);
+	      yield = string_catn(yield,
+				    weight < 2  ? US"N "  /* not authorized */
+				  : weight == 2 ? US"Y "  /* authorized */
+				  :		  US"? ", /* unauthorizable */
+				  2);
 	    break;
 
 	  default:
@@ -504,18 +502,18 @@ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0)))
         /* 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)buf, LCL_BUF_SIZE);
 
         /* If an overlong response was received, the data will have been
         truncated and dn_expand may fail. */
 
         if (rc < 0)
           {
-          log_write(0, LOG_MAIN, "host name alias list truncated: type=%s "
+          log_write(LOG_MAIN, "host name alias list truncated: type=%s "
             "domain=%s", dns_text_type(type), domain);
           break;
           }
-        else yield = string_cat(yield, s);
+        else yield = string_cat(yield, buf);
 
 	if (type == T_SOA && outsep2 != NULL)
 	  {
@@ -525,14 +523,14 @@ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0)))
 	  yield = string_catn(yield, outsep2, 1);
 
 	  rc = dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, p,
-	    (DN_EXPAND_ARG4_TYPE)s, sizeof(s));
+	    (DN_EXPAND_ARG4_TYPE)buf, LCL_BUF_SIZE);
 	  if (rc < 0)
 	    {
-	    log_write(0, LOG_MAIN, "responsible-mailbox truncated: type=%s "
+	    log_write(LOG_MAIN, "responsible-mailbox truncated: type=%s "
 	      "domain=%s", dns_text_type(type), domain);
 	    break;
 	    }
-	  else yield = string_cat(yield, s);
+	  else yield = string_cat(yield, buf);
 
 	  p += rc;
 	  if (!rr_bad_increment(rr, p, 5 * sizeof(uint32_t)))
@@ -540,12 +538,12 @@ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0)))
 	    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",
+	  yield = string_fmt_append(yield, "%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);
 	  }
         }
+#undef LCL_BUF_SIZE
       }    /* Loop for list of returned records */
 
            /* Loop for set of A-lookup types */
@@ -564,7 +562,7 @@ dns_retrans = save_retrans;
 dns_retry = save_retry;
 dns_init(FALSE, FALSE, FALSE);	/* clear the dnssec bit for getaddrbyname */
 
-if (!yield || !yield->ptr)
+if (gstring_length(yield) == 0)
   rc = failrc;
 else
   {
@@ -591,10 +589,8 @@ return rc;
 gstring *
 dnsdb_version_report(gstring * g)
 {
-#ifdef DYNLOOKUP
-g = string_fmt_append(g, "Library version: DNSDB: Exim version %s\n", EXIM_VERSION_STR);
-#endif
-return g;
+return string_fmt_append(g, "Library version: DNSDB: Exim %s builtin\n",
+			    EXIM_VERSION_STR);
 }
 
 
diff --git a/src/src/lookups/dsearch.c b/src/src/lookups/dsearch.c
index 99e1c8648..f373d5bbf 100644
--- a/src/src/lookups/dsearch.c
+++ b/src/src/lookups/dsearch.c
@@ -32,7 +32,7 @@ struct stat statbuf;
 
 if (is_tainted(dirname))
   {
-  log_write(0, LOG_MAIN|LOG_PANIC, "Tainted dirname '%s'", dirname);
+  log_write(LOG_MAIN|LOG_PANIC, "Tainted dirname '%s'", dirname);
   errno = EACCES;
   }
 else if (Ustat(dirname, &statbuf) >= 0)
@@ -106,22 +106,33 @@ if (opts)
       flags |= ALLOW_PATH;
   }
 
+/* For key=path, disallow any ".." component.  One could be leading, embedded or
+last in the path.  For non-path, disallow any path separators. */
+
 if (flags & ALLOW_PATH)
   {
-  if (Ustrstr(keystring, "/../") != NULL || Ustrstr(keystring, "/./"))
+  if (regex_match(
+	    regex_must_compile(US"(?:^|/)\\.\\.(?:/|$)", MCS_CACHEABLE, FALSE),
+	    keystring, -1, NULL))
     {
     *errmsg = string_sprintf(
       "key for dsearch lookup contains bad component: %s", keystring);
-    return DEFER;
+    DEBUG(lookup) debug_printf_indent("%s\n", *errmsg);
+    return FAIL;
     }
   }
 else if (Ustrchr(keystring, '/') != NULL)
   {
   *errmsg = string_sprintf("key for dsearch lookup contains a slash: %s",
     keystring);
-  return DEFER;
+  DEBUG(lookup) debug_printf_indent("%s\n", *errmsg);
+  return FAIL;
   }
 
+/* See if the file exists. Apply filtering: for "file" require a regular file,
+for "dir" or "subdir" require a directory and for "subdir" disallow "." and ".."
+*/
+
 filename = string_sprintf("%s/%s", dirname, keystring);
 if (  Ulstat(filename, &statbuf) >= 0
    && (  !(flags & FILTER_TYPE)
@@ -134,7 +145,9 @@ if (  Ulstat(filename, &statbuf) >= 0
    )  )  )  )
   {
   /* Since the filename exists in the filesystem, we can return a
-  non-tainted result. */
+  non-tainted result. For "ret=full" return the whole built-up path; otherwise
+  return just the key. */
+
   *result = string_copy_taint(flags & RET_FULL ? filename : keystring, GET_UNTAINTED);
   return OK;
   }
@@ -172,10 +185,8 @@ handle = handle;   /* Avoid compiler warning */
 gstring *
 dsearch_version_report(gstring * g)
 {
-#ifdef DYNLOOKUP
-g = string_fmt_append(g, "Library version: dsearch: Exim version %s\n", EXIM_VERSION_STR);
-#endif
-return g;
+return string_fmt_append(g, "Library version: dsearch: Exim %s builtin\n",
+			  EXIM_VERSION_STR);
 }
 
 
diff --git a/src/src/lookups/ibase.c b/src/src/lookups/ibase.c
deleted file mode 100644
index 141681aab..000000000
--- a/src/src/lookups/ibase.c
+++ /dev/null
@@ -1,550 +0,0 @@
-/*************************************************
-*     Exim - an Internet mail transport agent    *
-*************************************************/
-
-/* Copyright (c) The Exim Maintainers 2020 - 2025 */
-/* Copyright (c) University of Cambridge 1995 - 2018 */
-/* See the file NOTICE for conditions of use and distribution. */
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-
-/* The code in this module was contributed by Ard Biesheuvel. */
-
-#include "../exim.h"
-#include "lf_functions.h"
-
-#include <ibase.h>              /* The system header */
-
-/* Structure and anchor for caching connections. */
-
-typedef struct ibase_connection {
-    struct ibase_connection *next;
-    uschar *server;
-    isc_db_handle dbh;
-    isc_tr_handle transh;
-} ibase_connection;
-
-static ibase_connection *ibase_connections = NULL;
-
-
-#if defined(_LP64) || defined(__LP64__) || defined(__arch64__) || defined(_WIN64)
-# define ISC_NULL 0
-#else
-# define ISC_NULL NULL
-#endif
-
-/*************************************************
-*              Open entry point                  *
-*************************************************/
-
-/* See local README for interface description. */
-
-static void *ibase_open(const uschar * filename, uschar ** errmsg)
-{
-return (void *) (1);        /* Just return something non-null */
-}
-
-
-
-/*************************************************
-*               Tidy entry point                 *
-*************************************************/
-
-/* See local README for interface description. */
-
-static void
-ibase_tidy(void)
-{
-ibase_connection *cn;
-ISC_STATUS status[20];
-
-while ((cn = ibase_connections))
-  {
-  ibase_connections = cn->next;
-  DEBUG(D_lookup) debug_printf_indent("close Interbase connection: %s\n",
-			       cn->server);
-  isc_commit_transaction(status, &cn->transh);
-  isc_detach_database(status, &cn->dbh);
-  }
-}
-
-static int
-fetch_field(uschar * buffer, int buffer_size, XSQLVAR * var)
-{
-if (buffer_size < var->sqllen)
-  return 0;
-
-switch (var->sqltype & ~1)
-  {
-  case SQL_VARYING:
-      strncpy(CS buffer, &var->sqldata[2], *(short *) var->sqldata);
-      return *(short *) var->sqldata;
-  case SQL_TEXT:
-      strncpy(CS buffer, var->sqldata, var->sqllen);
-      return var->sqllen;
-  case SQL_SHORT:
-      return sprintf(CS buffer, "%d", *(short *) var->sqldata);
-  case SQL_LONG:
-      return sprintf(CS buffer, "%ld", *(ISC_LONG *) var->sqldata);
-  #ifdef SQL_INT64
-  case SQL_INT64:
-      return sprintf(CS buffer, "%lld", *(ISC_INT64 *) var->sqldata);
-  #endif
-  default:
-      /* not implemented */
-      return 0;
-  }
-}
-
-/*************************************************
-*        Internal search function                *
-*************************************************/
-
-/* 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
-
-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
-overwritten.
-
-Returns:       OK, FAIL, or DEFER
-*/
-
-static int
-perform_ibase_search(const uschar * query, uschar * server, uschar ** resultptr,
-                     uschar ** errmsg, BOOL * defer_break)
-{
-isc_stmt_handle stmth;
-XSQLDA *out_sqlda;
-XSQLVAR *var;
-int i;
-rmark reset_point;
-
-uschar buffer[256];
-ISC_STATUS status[20], *statusp = status;
-
-gstring * result = NULL;
-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. */
-
-for (int i = 2; i > 0; i--)
-  {
-  uschar * pp = Ustrrchr(server, '|');
-
-  if (!pp)
-    {
-    *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 */
-  }
-sdata[0] = server;          /* What's left at the start */
-
-/* See if we have a cached connection to the server */
-
-for (cn = ibase_connections; cn; cn = cn->next)
-  if (Ustrcmp(cn->server, server_copy) == 0)
-    break;
-
-/* Use a previously cached connection ? */
-
-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), CS 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_indent("Interbase using cached connection for %s\n",
-		     server_copy);
-  }
-else
-  {
-  cn = store_get(sizeof(ibase_connection), GET_UNTAINTED);
-  cn->server = server_copy;
-  cn->dbh = ISC_NULL;
-  cn->transh = ISC_NULL;
-  cn->next = ibase_connections;
-  ibase_connections = cn;
-  }
-
-/* If no cached connection, we must set one up. */
-
-if (!cn->dbh || !cn->transh)
-  {
-  uschar * dpb;
-  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++ = Ustrlen(sdata[1]);
-  for (uschar * p = sdata[1]; *p;) *dpb++ = *p++;
-  *dpb++ = isc_dpb_password;
-  *dpb++ = Ustrlen(sdata[2]);
-  for (uschar * p = sdata[2]; *p;) *dpb++ = *p++;
-  dpb_length = dpb - buffer;
-
-  DEBUG(D_lookup)
-      debug_printf_indent("new Interbase connection: database=%s user=%s\n",
-		   sdata[0], sdata[1]);
-
-  /* Connect to the database */
-  if (isc_attach_database(status, 0, CS sdata[0], &cn->dbh,
-			  dpb_length, CS buffer))
-    {
-    isc_interprete(CS 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(CS buffer, &statusp);
-    isc_detach_database(status, &cn->dbh);
-    *errmsg = string_sprintf("Interbase start_transaction() failed: %s",
-		       buffer);
-    *defer_break = FALSE;
-    goto IBASE_EXIT;
-    }
-  }
-
-/* Run the query */
-if (isc_dsql_allocate_statement(status, &cn->dbh, &stmth))
-  {
-  isc_interprete(CS buffer, &statusp);
-  *errmsg = string_sprintf("Interbase alloc_statement() failed: %s", buffer);
-  *defer_break = FALSE;
-  goto IBASE_EXIT;
-  }
-
-/* Lacking any information, assume that the data is untainted */
-reset_point = store_mark();
-out_sqlda = store_get(XSQLDA_LENGTH(1), GET_UNTAINTED);
-out_sqlda->version = SQLDA_VERSION1;
-out_sqlda->sqln = 1;
-
-if (isc_dsql_prepare(status, &cn->transh, &stmth, 0, CCS query, 1, out_sqlda))
-  {
-  isc_interprete(CS buffer, &statusp);
-  reset_point = store_reset(reset_point);
-  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 */
-if (out_sqlda->sqln < out_sqlda->sqld)
-  {
-  XSQLDA *new_sqlda = store_get(XSQLDA_LENGTH(out_sqlda->sqld), GET_UNTAINTED);
-  if (isc_dsql_describe
-      (status, &stmth, out_sqlda->version, new_sqlda))
-    {
-    isc_interprete(CS buffer, &statusp);
-    isc_dsql_free_statement(status, &stmth, DSQL_drop);
-    reset_point = store_reset(reset_point);
-    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 */
-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, GET_UNTAINTED);
-	break;
-    case SQL_TEXT:
-	var->sqldata = CS store_get(sizeof(char) * var->sqllen, GET_UNTAINTED);
-	break;
-    case SQL_SHORT:
-	var->sqldata = CS  store_get(sizeof(short), GET_UNTAINTED);
-	break;
-    case SQL_LONG:
-	var->sqldata = CS  store_get(sizeof(ISC_LONG), GET_UNTAINTED);
-	break;
-#ifdef SQL_INT64
-    case SQL_INT64:
-	var->sqldata = CS  store_get(sizeof(ISC_INT64), GET_UNTAINTED);
-	break;
-#endif
-    case SQL_FLOAT:
-	var->sqldata = CS  store_get(sizeof(float), GET_UNTAINTED);
-	break;
-    case SQL_DOUBLE:
-	var->sqldata = CS  store_get(sizeof(double), GET_UNTAINTED);
-	break;
-#ifdef SQL_TIMESTAMP
-    case SQL_DATE:
-	var->sqldata = CS  store_get(sizeof(ISC_QUAD), GET_UNTAINTED);
-	break;
-#else
-    case SQL_TIMESTAMP:
-	var->sqldata = CS  store_get(sizeof(ISC_TIMESTAMP), GET_UNTAINTED);
-	break;
-    case SQL_TYPE_DATE:
-	var->sqldata = CS  store_get(sizeof(ISC_DATE), GET_UNTAINTED);
-	break;
-    case SQL_TYPE_TIME:
-	var->sqldata = CS  store_get(sizeof(ISC_TIME), GET_UNTAINTED);
-	break;
-  #endif
-    }
-  if (var->sqltype & 1)
-    var->sqlind = (short *) store_get(sizeof(short), GET_UNTAINTED);
-  }
-
-/* finally, we're ready to execute the statement */
-if (isc_dsql_execute(status, &cn->transh, &stmth, out_sqlda->version, NULL))
-  {
-  isc_interprete(CS 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(CS 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)
-    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 (int 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)
-	{
-	result = string_catn(result, US "\"", 1);
-	for (int j = 0; j < len; j++)
-	  {
-	  if (buffer[j] == '\"' || buffer[j] == '\\')
-	      result = string_catn(result, US "\\", 1);
-	  result = string_catn(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)
-  {
-  yield = FAIL;
-  *errmsg = US "Interbase: no data found";
-  }
-else
-  gstring_release_unused(result);
-
-
-/* Get here by goto from various error checks. */
-
-IBASE_EXIT:
-
-if (stmth)
-  isc_dsql_free_statement(status, &stmth, DSQL_drop);
-
-/* Non-NULL result indicates a successful result */
-
-if (result)
-  {
-  *resultptr = string_from_gstring(result);
-  return OK;
-  }
-else
-  {
-  DEBUG(D_lookup) debug_printf_indent("%s\n", *errmsg);
-  return yield;           /* FAIL or DEFER */
-  }
-}
-
-
-
-
-/*************************************************
-*               Find entry point                 *
-*************************************************/
-
-/* See local README for interface description. The handle and filename
-arguments are not used. Loop through a list of servers while the query is
-deferred with a retryable error. */
-
-static int
-ibase_find(void * handle, const uschar * filename, const uschar * query,
-  int length, uschar ** result, uschar ** errmsg, uint * do_cache,
-  const uschar * opts)
-{
-uschar * server;
-const uschar * list = ibase_servers;
-
-DEBUG(D_lookup) debug_printf_indent("Interbase query: %s\n", query);
-
-for (int sep = 0; server = string_nextinlist(&list, &sep, NULL, 0); )
-  {
-  BOOL defer_break = FALSE;
-  int rc = perform_ibase_search(query, server, result, errmsg, &defer_break);
-  if (rc != DEFER || defer_break)
-    return rc;
-  }
-
-if (!ibase_servers)
-  *errmsg = US "no Interbase servers defined (ibase_servers option)";
-
-return DEFER;
-}
-
-
-
-/*************************************************
-*               Quote entry point                *
-*************************************************/
-
-/* The only characters that need to be quoted (with backslash) are newline,
-tab, carriage return, backspace, backslash itself, and the quote characters.
-Percent, and underscore and not escaped. They 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.
-Sadly, MySQL doesn't seem to behave like other programs. If you use something
-like "where id="ab\%cd" it does not treat the string as "ab%cd". So you really
-can't quote "on spec".
-
-Arguments:
-  s          the string to be quoted
-  opt        additional option text or NULL if none
-  idx	     lookup type index
-
-Returns:     the processed string or NULL for a bad option
-*/
-
-static uschar *
-ibase_quote(uschar * s, const uschar * opt, unsigned idx)
-{
-gstring * quoted = store_get_quoted(1, s, idx, US"ibase");
-
-if (opt)
-  return NULL;            /* No options recognized */
-
-for (uschar c; c = *s; s++)
-  {
-  if (c == '\'') quoted = string_catn(quoted, US"\\", 1);
-  quoted = string_catn(quoted, s, 1);
-  }
-gstring_release_unused(quoted);
-return(string_from_gstring(quoted));
-}
-
-
-
-/*************************************************
-*         Version reporting entry point          *
-*************************************************/
-
-/* See local README for interface description. */
-
-#include "../version.h"
-
-gstring *
-ibase_version_report(gstring * g)
-{
-#ifdef DYNLOOKUP
-g = string_fmt_append(g, "Library version: ibase: Exim version %s\n", EXIM_VERSION_STR));
-#endif
-return g;
-}
-
-
-static lookup_info _lookup_info = {
-  .name = US"ibase",			/* lookup name */
-  .type = lookup_querystyle,		/* query-style lookup */
-  .open = ibase_open,			/* open function */
-  .check = NULL,			/* no check function */
-  .find = ibase_find,			/* find function */
-  .close = NULL,			/* no close function */
-  .tidy = ibase_tidy,			/* tidy function */
-  .quote = ibase_quote,			/* quoting function */
-  .version_report = ibase_version_report           /* version reporting */
-};
-
-#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 };
-
-/* End of lookups/ibase.c */
diff --git a/src/src/lookups/json.c b/src/src/lookups/json.c
index 43575cacf..e257120e6 100644
--- a/src/src/lookups/json.c
+++ b/src/src/lookups/json.c
@@ -105,7 +105,7 @@ for (int k = 1;  (key = string_nextinlist(&keystring, &sep, NULL, 0)); k++)
 	: json_object_get(j, CCS key)
      ) )
     {
-    DEBUG(D_lookup) debug_printf_indent("%s, for key %d: '%s'\n",
+    DEBUG(lookup) debug_printf_indent("%s, for key %d: '%s'\n",
       numeric
       ? US"bad index, or not json array"
       : US"no such key, or not json object",
@@ -157,12 +157,11 @@ json_close(void *handle)
 
 /* See local README for interface description. */
 
-#include "../version.h"
-
 gstring *
 json_version_report(gstring * g)
 {
-return string_fmt_append(g, "Library version: json: Jansonn version %s\n", JANSSON_VERSION);
+return string_fmt_append(g, "Library version: json: Jansonn version %s\n",
+			  JANSSON_VERSION);
 }
 
 
diff --git a/src/src/lookups/ldap.c b/src/src/lookups/ldap.c
index 624b947d6..29f9f5a1d 100644
--- a/src/src/lookups/ldap.c
+++ b/src/src/lookups/ldap.c
@@ -74,6 +74,19 @@ LDAP_LIB_OPENLDAP1
 SEARCH_LDAP_DN lookup). */
 
 
+static uschar * eldap_ca_cert_dir      = NULL; /* Directory with CA certificates */
+static uschar * eldap_ca_cert_file     = NULL; /* CA certificate file */
+static uschar * eldap_cert_file        = NULL; /* Certificate file */
+static uschar * eldap_cert_key         = NULL; /* Certificate key file */
+static uschar * eldap_cipher_suite     = NULL; /* Allowed cipher suite */
+static uschar * eldap_default_servers  = NULL; /* List of default servers */
+static uschar * eldap_require_cert     = NULL; /* Peer certificate checking strategy */
+static int      eldap_version          = -1;	/* Use STARTTLS */
+static BOOL     eldap_start_tls        = FALSE; /* LDAP version */
+
+static uschar * eldap_dn;			/* Where LDAP DNs are left */
+
+
 /* Structure and anchor for caching connections. */
 
 typedef struct ldap_connection {
@@ -87,7 +100,7 @@ typedef struct ldap_connection {
   LDAP *ld;
 } LDAP_CONNECTION;
 
-static LDAP_CONNECTION *ldap_connections = NULL;
+static LDAP_CONNECTION * ldap_connections = NULL;
 
 
 
@@ -168,7 +181,7 @@ int    rescount = 0;
 BOOL   attribute_found = FALSE;
 BOOL   ldapi = FALSE;
 
-DEBUG(D_lookup) debug_printf_indent("perform_ldap_search:"
+DEBUG(lookup) debug_printf_indent("perform_ldap_search:"
     " ldap%s URL = %q server=%s port=%d "
     "sizelimit=%d timelimit=%d tcplimit=%d\n",
     search_type == SEARCH_LDAP_MULTIPLE ? "m" :
@@ -212,7 +225,7 @@ else
   port = ludp->lud_port;
   }
 
-DEBUG(D_lookup) debug_printf_indent("after ldap_url_parse: host=%s port=%d\n",
+DEBUG(lookup) debug_printf_indent("after ldap_url_parse: host=%s port=%d\n",
   host, port);
 
 if (port == 0) port = LDAP_PORT;      /* Default if none given */
@@ -334,7 +347,7 @@ if (!lcp)
    {
     const uschar * s = string_from_gstring(g);
 
-    DEBUG(D_lookup) debug_printf_indent("ldap_initialize with URL %s\n", s);
+    DEBUG(lookup) debug_printf_indent("ldap_initialize with URL %s\n", s);
     if ((rc = ldap_initialize(&ld, CS s)) != LDAP_SUCCESS)
       {
       *errmsg = string_sprintf("ldap_initialize: (error %d) URL %q\n",
@@ -410,7 +423,7 @@ if (!lcp)
   ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, (void *)&eldap_version);
 #endif
 
-  DEBUG(D_lookup) debug_printf_indent("initialized for LDAP (v%d) server %s%s\n",
+  DEBUG(lookup) debug_printf_indent("initialized for LDAP (v%d) server %s%s\n",
     eldap_version, host, porttext);
 
   /* If not using ldapi and TLS is available, set appropriate TLS options: hard
@@ -430,7 +443,7 @@ if (!lcp)
 	: Ustrcmp(eldap_require_cert, "try")    == 0 ? LDAP_OPT_X_TLS_TRY
 	: LDAP_OPT_X_TLS_NEVER;
 
-      DEBUG(D_lookup) debug_printf_indent(
+      DEBUG(lookup) debug_printf_indent(
 	"Require certificate overrides LDAP_OPT_X_TLS option (%d)\n",
 	tls_option);
       }
@@ -439,13 +452,13 @@ if (!lcp)
     if (strncmp(ludp->lud_scheme, "ldaps", 5) == 0)
       {
       tls_option = LDAP_OPT_X_TLS_HARD;
-      DEBUG(D_lookup)
+      DEBUG(lookup)
         debug_printf_indent("LDAP_OPT_X_TLS_HARD set due to ldaps:// URI\n");
       }
     else
       {
       tls_option = LDAP_OPT_X_TLS_TRY;
-      DEBUG(D_lookup)
+      DEBUG(lookup)
         debug_printf_indent("LDAP_OPT_X_TLS_TRY set due to ldap:// URI\n");
       }
     ldap_set_option(ld, LDAP_OPT_X_TLS, (void *)&tls_option);
@@ -488,14 +501,14 @@ if (!lcp)
      * default that loaded at instantiation). */
     rc = ldap_set_option(ldsetctx, LDAP_OPT_X_TLS_REQUIRE_CERT, &cert_option);
     if (rc)
-      DEBUG(D_lookup)
+      DEBUG(lookup)
         debug_printf_indent("Unable to set TLS require cert_option(%d) globally: %s\n",
           cert_option, ldap_err2string(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(lookup)
       debug_printf_indent("Unable to reload TLS context %d: %s\n",
                    rc, ldap_err2string(rc));
   #endif
@@ -517,7 +530,7 @@ if (!lcp)
 /* Found cached connection */
 
 else
-  DEBUG(D_lookup)
+  DEBUG(lookup)
     debug_printf_indent("re-using cached connection to LDAP server %s%s\n",
       host, porttext);
 
@@ -533,7 +546,7 @@ if (  !lcp->bound
    || lcp->password && password && Ustrcmp(lcp->password, password) != 0
    )
   {
-  DEBUG(D_lookup) debug_printf_indent("%sbinding with user=%s password=%s\n",
+  DEBUG(lookup) debug_printf_indent("%sbinding with user=%s password=%s\n",
     lcp->bound ? "re-" : "", user, password);
 
   if (eldap_start_tls && !lcp->is_start_tls_called && !ldapi)
@@ -552,7 +565,7 @@ if (  !lcp->bound
       }
     lcp->is_start_tls_called = TRUE;
 #else
-    DEBUG(D_lookup) debug_printf_indent("TLS initiation not supported with this Exim"
+    DEBUG(lookup) debug_printf_indent("TLS initiation not supported with this Exim"
       " and your LDAP library.\n");
 #endif
     }
@@ -580,7 +593,7 @@ if (  !lcp->bound
 
   if (search_type == SEARCH_LDAP_AUTH && rc == LDAP_INVALID_CREDENTIALS)
     {
-    DEBUG(D_lookup)
+    DEBUG(lookup)
       debug_printf_indent("Invalid credentials: ldapauth returns FAIL\n");
     error_yield = FAIL;
     goto RETURN_ERROR_NOMSG;
@@ -610,7 +623,7 @@ if (  !lcp->bound
 
 if (search_type == SEARCH_LDAP_AUTH)
   {
-  DEBUG(D_lookup) debug_printf_indent("Bind succeeded: ldapauth returns OK\n");
+  DEBUG(lookup) debug_printf_indent("Bind succeeded: ldapauth returns OK\n");
   *res = US"";
   goto RETURN_OK;
   }
@@ -643,7 +656,7 @@ ldap_set_option(lcp->ld, LDAP_OPT_REFERRALS, referrals);
 
 /* Start the search on the server. */
 
-DEBUG(D_lookup) debug_printf_indent("Start search\n");
+DEBUG(lookup) debug_printf_indent("Start search\n");
 
 msgid = ldap_search(lcp->ld, ludp->lud_dn, ludp->lud_scope, ludp->lud_filter,
   ludp->lud_attrs, 0);
@@ -675,7 +688,7 @@ while ((rc = ldap_result(lcp->ld, msgid, 0, timeoutptr, &result)) ==
                     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_indent("LDAP result loop\n");
+  DEBUG(lookup) debug_printf_indent("LDAP result loop\n");
 
   for(e = ldap_first_entry(lcp->ld, result), valuecount = 0;
       e;
@@ -684,7 +697,7 @@ while ((rc = ldap_result(lcp->ld, msgid, 0, timeoutptr, &result)) ==
     uschar *new_dn;
     BOOL insert_space = FALSE;
 
-    DEBUG(D_lookup) debug_printf_indent("LDAP entry loop\n");
+    DEBUG(lookup) debug_printf_indent("LDAP entry loop\n");
 
     rescount++;   /* Count results */
 
@@ -733,7 +746,7 @@ while ((rc = ldap_result(lcp->ld, msgid, 0, timeoutptr, &result)) ==
     else for (uschar * attr = US ldap_first_attribute(lcp->ld, e, &ber);
               attr; attr = US ldap_next_attribute(lcp->ld, e, ber))
       {
-      DEBUG(D_lookup) debug_printf_indent("LDAP attr loop\n");
+      DEBUG(lookup) debug_printf_indent("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 */
@@ -761,7 +774,7 @@ while ((rc = ldap_result(lcp->ld, msgid, 0, timeoutptr, &result)) ==
             int len = Ustrlen(value);
             ++valuecount;
 
-            DEBUG(D_lookup) debug_printf_indent("LDAP value loop %s:%s\n", attr, value);
+            DEBUG(lookup) debug_printf_indent("LDAP value loop %s:%s\n", attr, value);
 
             /* In case we requested one attribute only but got several times
             into that attr loop, we need to append the additional values.
@@ -854,7 +867,7 @@ if (dn)
 #endif
   }
 
-DEBUG(D_lookup) debug_printf_indent("search ended by ldap_result yielding %d\n",rc);
+DEBUG(lookup) debug_printf_indent("search ended by ldap_result yielding %d\n",rc);
 
 if (rc == 0)
   {
@@ -876,7 +889,7 @@ methods of handling error codes and generating error messages. */
 if (rc == -1 || !result)
   {
   int err;
-  DEBUG(D_lookup) debug_printf_indent("ldap_result failed\n");
+  DEBUG(lookup) debug_printf_indent("ldap_result failed\n");
 
 #if defined LDAP_LIB_SOLARIS || defined LDAP_LIB_OPENLDAP2
     ldap_get_option(lcp->ld, LDAP_OPT_ERROR_NUMBER, &err);
@@ -919,7 +932,7 @@ We need to parse the message to find out exactly what's happened. */
   ldap_rc = rc;
   ldap_parse_rc = ldap_parse_result(lcp->ld, result, &rc, CSS &matched,
     CSS &error2, NULL, NULL, 0);
-  DEBUG(D_lookup) debug_printf_indent("ldap_parse_result: %d\n", ldap_parse_rc);
+  DEBUG(lookup) debug_printf_indent("ldap_parse_result: %d\n", ldap_parse_rc);
   if (ldap_parse_rc < 0 &&
       (ldap_parse_rc != LDAP_NO_RESULTS_RETURNED
       #ifdef LDAP_RES_SEARCH_REFERENCE
@@ -961,7 +974,7 @@ We need to parse the message to find out exactly what's happened. */
       the lookup, so return DEFER (which is the default in error_yield).
 */
 
-DEBUG(D_lookup) debug_printf_indent("ldap_parse_result yielded %d: %s\n",
+DEBUG(lookup) debug_printf_indent("ldap_parse_result yielded %d: %s\n",
   rc, ldap_err2string(rc));
 
 if (rc != LDAP_SUCCESS && rc != LDAP_SIZELIMIT_EXCEEDED
@@ -987,7 +1000,7 @@ if (rc != LDAP_SUCCESS && rc != LDAP_SIZELIMIT_EXCEEDED
 #endif
 
     {
-    DEBUG(D_lookup) debug_printf_indent("lookup failure forced\n");
+    DEBUG(lookup) debug_printf_indent("lookup failure forced\n");
     error_yield = FAIL;
     }
   goto RETURN_ERROR;
@@ -1023,7 +1036,7 @@ if (!attribute_found)
 
 /* Otherwise, it's all worked */
 
-DEBUG(D_lookup) debug_printf_indent("LDAP search: returning: %s\n", data->s);
+DEBUG(lookup) debug_printf_indent("LDAP search: returning: %s\n", data->s);
 *res = data->s;
 
 RETURN_OK:
@@ -1037,7 +1050,7 @@ RETURN_ERROR_BREAK:
 *defer_break = TRUE;
 
 RETURN_ERROR:
-DEBUG(D_lookup) debug_printf_indent("%s\n", *errmsg);
+DEBUG(lookup) debug_printf_indent("%s\n", *errmsg);
 
 RETURN_ERROR_NOMSG:
 if (result) ldap_msgfree(result);
@@ -1139,7 +1152,7 @@ while (strncmpic(url, US"ldap", 4) != 0)
         {
         *errmsg = string_sprintf("LDAP_OP_DEREF not defined in this LDAP "
           "library - cannot use \"dereference\"");
-        DEBUG(D_lookup) debug_printf_indent("%s\n", *errmsg);
+        DEBUG(lookup) debug_printf_indent("%s\n", *errmsg);
         return DEFER;
         }
       #endif
@@ -1152,7 +1165,7 @@ while (strncmpic(url, US"ldap", 4) != 0)
         else
           {
           *errmsg = US"LDAP option REFERRALS is not \"follow\" or \"nofollow\"";
-          DEBUG(D_lookup) debug_printf_indent("%s\n", *errmsg);
+          DEBUG(lookup) debug_printf_indent("%s\n", *errmsg);
           return DEFER;
           }
         }
@@ -1161,7 +1174,7 @@ while (strncmpic(url, US"ldap", 4) != 0)
         {
         *errmsg = string_sprintf("LDAP_OP_REFERRALS not defined in this LDAP "
           "library - cannot use \"referrals\"");
-        DEBUG(D_lookup) debug_printf_indent("%s\n", *errmsg);
+        DEBUG(lookup) debug_printf_indent("%s\n", *errmsg);
         return DEFER;
         }
       #endif
@@ -1171,7 +1184,7 @@ while (strncmpic(url, US"ldap", 4) != 0)
         *errmsg =
           string_sprintf("unknown parameter \"%.*s\" precedes LDAP URL",
             namelen, name);
-        DEBUG(D_lookup) debug_printf_indent("LDAP query error: %s\n", *errmsg);
+        DEBUG(lookup) debug_printf_indent("LDAP query error: %s\n", *errmsg);
         return DEFER;
         }
       Uskip_whitespace(&url);
@@ -1179,7 +1192,7 @@ while (strncmpic(url, US"ldap", 4) != 0)
       }
     }
   *errmsg = US"malformed parameter setting precedes LDAP URL";
-  DEBUG(D_lookup) debug_printf_indent("LDAP query error: %s\n", *errmsg);
+  DEBUG(lookup) debug_printf_indent("LDAP query error: %s\n", *errmsg);
   return DEFER;
   }
 
@@ -1209,7 +1222,7 @@ if (user)
   *t = 0;
   }
 
-DEBUG(D_lookup)
+DEBUG(lookup)
   debug_printf_indent("LDAP parameters: user=%s pass=%s size=%d time=%d connect=%d "
     "dereference=%d referrals=%s\n", user, password, sizelimit, timelimit,
     tcplimit, dereference, referrals == LDAP_OPT_ON ? "on" : "off");
@@ -1227,7 +1240,7 @@ if (search_type == SEARCH_LDAP_AUTH)
     }
   if (!*password)
     {
-    DEBUG(D_lookup) debug_printf_indent("Empty password: ldapauth returns FAIL\n");
+    DEBUG(lookup) debug_printf_indent("Empty password: ldapauth returns FAIL\n");
     return FAIL;
     }
   }
@@ -1240,7 +1253,7 @@ if (Ustrncmp(p, "://", 3) != 0)
   {
   *errmsg = string_sprintf("LDAP URL does not start with \"ldap://\", "
     "\"ldaps://\", or \"ldapi://\" (it starts with \"%.16s...\")", url);
-  DEBUG(D_lookup) debug_printf_indent("LDAP query error: %s\n", *errmsg);
+  DEBUG(lookup) debug_printf_indent("LDAP query error: %s\n", *errmsg);
   return DEFER;
   }
 
@@ -1345,7 +1358,7 @@ eldap_dn = NULL;
 
 for (LDAP_CONNECTION *lcp; lcp = ldap_connections; ldap_connections = lcp->next)
   {
-  DEBUG(D_lookup) debug_printf_indent("unbind LDAP connection to %s:%d\n",
+  DEBUG(lookup) debug_printf_indent("unbind LDAP connection to %s:%d\n",
     lcp->host, lcp->port);
   if(lcp->bound) ldap_unbind(lcp->ld);
   }
@@ -1558,29 +1571,71 @@ return quoted;
 
 /* See local README for interface description. */
 
-#include "../version.h"
-
 gstring *
 ldap_version_report(gstring * g)
 {
-#ifdef DYNLOOKUP
-/*XXX it would be nice to haul a version string for the underlying ldap library */
-g = string_fmt_append(g, "Library version: LDAP: Exim version %s\n", EXIM_VERSION_STR);
-#endif
+#ifdef LDAP_LIB_OPENLDAP2
+LDAP * ld = ldap_init("", 0);
+LDAPAPIInfo info = {.ldapai_info_version = LDAP_API_INFO_VERSION};
+
+g = string_fmt_append(g, "Library version: LDAP: Compile %s %u.%u.%u\n",
+    LDAP_VENDOR_NAME, LDAP_VENDOR_VERSION_MAJOR,
+    LDAP_VENDOR_VERSION_MINOR, LDAP_VENDOR_VERSION_PATCH);
+
+if (ldap_get_option(ld, LDAP_OPT_API_INFO, &info) == LDAP_OPT_SUCCESS)
+  {
+  g = string_fmt_append(g, "                       Runtime %s %d\n",
+      info.ldapai_vendor_name, info.ldapai_vendor_version);
+
+  for (char ** sp = info.ldapai_extensions; *sp; sp++)
+    ldap_memfree(*sp);
+  ldap_memfree(info.ldapai_extensions);
+  ldap_memfree(info.ldapai_vendor_name);
+  }
 return g;
+
+#else
+return string_cat(g, "Library version: LDAP: (unknown)\n");
+#endif
 }
 
 
+/******************************************************************************/
+/* Module API */
+
+static optionlist eldap_options[] = {
+  { "ldap_ca_cert_dir",         opt_stringptr,   {&eldap_ca_cert_dir} },
+  { "ldap_ca_cert_file",        opt_stringptr,   {&eldap_ca_cert_file} },
+  { "ldap_cert_file",           opt_stringptr,   {&eldap_cert_file} },
+  { "ldap_cert_key",            opt_stringptr,   {&eldap_cert_key} },
+  { "ldap_cipher_suite",        opt_stringptr,   {&eldap_cipher_suite} },
+  { "ldap_default_servers",     opt_stringptr,   {&eldap_default_servers} },
+  { "ldap_require_cert",        opt_stringptr,   {&eldap_require_cert} },
+  { "ldap_start_tls",           opt_bool,        {&eldap_start_tls} },
+  { "ldap_version",             opt_int,         {&eldap_version} },
+};
+
+static var_entry eldap_variables[] = {
+  { "ldap_dn",             vtype_stringptr,   &eldap_dn },
+};
+
 static lookup_info ldap_lookup_info = {
-  .name = US"ldap",			/* lookup name */
-  .type = lookup_querystyle,		/* query-style lookup */
-  .open = eldap_open,			/* open function */
-  .check = NULL,			/* check function */
-  .find = eldap_find,			/* find function */
-  .close = NULL,			/* no close function */
-  .tidy = eldap_tidy,			/* tidy function */
-  .quote = eldap_quote,			/* quoting function */
-  .version_report = ldap_version_report            /* version reporting */
+  .name =	US"ldap",			/* lookup name */
+  .type =	lookup_querystyle,		/* query-style lookup */
+  .open =	eldap_open,			/* open function */
+  .check =	NULL,				/* check function */
+  .find =	eldap_find,			/* find function */
+  .close =	NULL,				/* no close function */
+  .tidy =	eldap_tidy,			/* tidy function */
+  .quote =	eldap_quote,			/* quoting function */
+  .version_report = ldap_version_report,	/* version reporting */
+
+  /* This lookup name matches the modules name, so access options here */
+  .options =	eldap_options,
+  .options_count = nelem(eldap_options),
+
+  .variables =	eldap_variables,
+  .variables_count = nelem(eldap_variables),
 };
 
 static lookup_info ldapdn_lookup_info = {
@@ -1592,7 +1647,7 @@ static lookup_info ldapdn_lookup_info = {
   .close = NULL,			/* no close function */
   .tidy = eldap_tidy,			/* sic */    /* tidy function */
   .quote = eldap_quote,			/* sic */    /* quoting function */
-  .version_report = NULL                           /* no version reporting (redundant) */
+  .version_report = NULL		/* no version reporting (redundant) */
 };
 
 static lookup_info ldapm_lookup_info = {
@@ -1604,7 +1659,7 @@ static lookup_info ldapm_lookup_info = {
   .close = NULL,			/* no close function */
   .tidy = eldap_tidy,			/* sic */    /* tidy function */
   .quote = eldap_quote,			/* sic */    /* quoting function */
-  .version_report = NULL                           /* no version reporting (redundant) */
+  .version_report = NULL		/* no version reporting (redundant) */
 };
 
 static lookup_info ldapauth_lookup_info = {
@@ -1616,7 +1671,7 @@ static lookup_info ldapauth_lookup_info = {
   .close = NULL,			/* no close function */
   .tidy = eldap_tidy,			/* sic */    /* tidy function */
   .quote = NULL,			/* NO quoting function */
-  .version_report = NULL                           /* no version reporting (redundant) */
+  .version_report = NULL		/* no version reporting (redundant) */
 };
 
 #ifdef DYNLOOKUP
diff --git a/src/src/lookups/lf_sqlperform.c b/src/src/lookups/lf_sqlperform.c
index 2dedb2011..f7f703905 100644
--- a/src/src/lookups/lf_sqlperform.c
+++ b/src/src/lookups/lf_sqlperform.c
@@ -55,7 +55,7 @@ int rc;
 uschar * server;
 BOOL defer_break = FALSE;
 
-DEBUG(D_lookup) debug_printf_indent("%s query: %q opts '%s'\n", name, query, opts);
+DEBUG(lookup) debug_printf_indent("%s query: %q opts '%s'\n", name, query, opts);
 
 /* Handle queries that do have server information at the start (old style). */
 
@@ -64,7 +64,7 @@ if (Ustrncmp(query, "servers", 7) == 0)
   int qsep = 0;
   const uschar * s, * ss, * qserverlist;
 
-  log_write(0, LOG_MAIN|LOG_CONFIG_IN, "WARNING: obsolete syntax used for lookup");
+  log_write(LOG_MAIN|LOG_CONFIG_IN, "WARNING: obsolete syntax used for lookup");
 
   s = query + 7;
   skip_whitespace(&s);
diff --git a/src/src/lookups/lmdb.c b/src/src/lookups/lmdb.c
index 3a3eebcba..21aef3b47 100644
--- a/src/src/lookups/lmdb.c
+++ b/src/src/lookups/lmdb.c
@@ -87,24 +87,24 @@ Lmdbstrct * lmdb_p = handle;
 dbkey.mv_data = CS keystring;
 dbkey.mv_size = length;
 
-DEBUG(D_lookup) debug_printf_indent("LMDB: lookup key: %s\n", CS keystring);
+DEBUG(lookup) debug_printf_indent("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_indent("LMDB: lookup result: %s\n", *result);
+  DEBUG(lookup) debug_printf_indent("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_indent("%s\n", *errmsg);
+  DEBUG(lookup) debug_printf_indent("%s\n", *errmsg);
   return FAIL;
   }
 else
   {
   *errmsg = string_sprintf("LMDB: lookup error: %s", mdb_strerror(ret));
-  DEBUG(D_lookup) debug_printf_indent("%s\n", *errmsg);
+  DEBUG(lookup) debug_printf_indent("%s\n", *errmsg);
   return DEFER;
   }
 }
@@ -128,16 +128,13 @@ mdb_env_close(db_env);
 *         Version reporting entry point          *
 *************************************************/
 
-#include "../version.h"
-
 gstring *
 lmdb_version_report(gstring * g)
 {
 g = string_fmt_append(g, "Library version: LMDB: Compile: %d.%d.%d\n",
-			  MDB_VERSION_MAJOR, MDB_VERSION_MINOR, MDB_VERSION_PATCH);
-#ifdef DYNLOOKUP
-g = string_fmt_append(g, "                        Exim version %s\n", EXIM_VERSION_STR);
-#endif
+		      MDB_VERSION_MAJOR, MDB_VERSION_MINOR, MDB_VERSION_PATCH);
+g = string_fmt_append(g, "                       Runtime: %s\n",
+		      mdb_version(NULL, NULL, NULL));
 return g;
 }
 
diff --git a/src/src/lookups/lsearch.c b/src/src/lookups/lsearch.c
index c85cda5d5..fc7ebaf62 100644
--- a/src/src/lookups/lsearch.c
+++ b/src/src/lookups/lsearch.c
@@ -418,10 +418,8 @@ lsearch_close(void *handle)
 gstring *
 lsearch_version_report(gstring * g)
 {
-#ifdef DYNLOOKUP
-g = string_fmt_append(g, "Library version: lsearch: Exim version %s\n", EXIM_VERSION_STR);
-#endif
-return g;
+return string_fmt_append(g, "Library version: lsearch: Exim %s builtin\n",
+			    EXIM_VERSION_STR);
 }
 
 
@@ -474,7 +472,7 @@ static lookup_info wildlsearch_lookup_info = {
 };
 
 #ifdef DYNLOOKUP
-#define lsearch_lookup_module_info _lookup_module_info
+# define lsearch_lookup_module_info _lookup_module_info
 #endif
 
 static lookup_info *_lookup_list[] = { &iplsearch_lookup_info,
diff --git a/src/src/lookups/mysql.c b/src/src/lookups/mysql.c
index bbf98cb0c..0e3335d86 100644
--- a/src/src/lookups/mysql.c
+++ b/src/src/lookups/mysql.c
@@ -64,6 +64,8 @@ with versions before 10.2, as they do not define there there specific symbols.
 #endif
 
 
+static uschar * mysql_servers = NULL;	/* List of servers and connect info */
+
 /* Structure and anchor for caching connections. */
 
 typedef struct mysql_connection {
@@ -103,7 +105,7 @@ mysql_connection *cn;
 while ((cn = mysql_connections))
   {
   mysql_connections = cn->next;
-  DEBUG(D_lookup) debug_printf_indent("close MYSQL connection: %s\n", cn->server);
+  DEBUG(lookup) debug_printf_indent("close MYSQL connection: %s\n", cn->server);
   mysql_close(cn->handle);
   }
 }
@@ -233,7 +235,7 @@ if (!cn)
 
   if (sdata[1][0] == 0) sdata[1] = NULL;
 
-  DEBUG(D_lookup)
+  DEBUG(lookup)
     debug_printf_indent("MYSQL new connection: host=%s port=%d socket=%s "
       "database=%s user=%s\n", sdata[0], port, socket, sdata[1], sdata[2]);
 
@@ -264,7 +266,7 @@ if (!cn)
 
 /* Else use a previously cached connection */
 
-else DEBUG(D_lookup)
+else DEBUG(lookup)
   debug_printf_indent("MYSQL using cached connection for %s\n", server_copy);
 
 /* Run the query */
@@ -289,7 +291,7 @@ if (!(mysql_result = mysql_use_result(mysql_handle)))
   {
   if (mysql_field_count(mysql_handle) == 0)
     {
-    DEBUG(D_lookup) debug_printf_indent("MYSQL: query was not one that returns data\n");
+    DEBUG(lookup) debug_printf_indent("MYSQL: query was not one that returns data\n");
     result = string_cat(result,
 	       string_sprintf("%lld", mysql_affected_rows(mysql_handle)));
     *do_cache = 0;
@@ -342,7 +344,7 @@ while((i = mysql_next_result(mysql_handle)) >= 0)
     goto MYSQL_EXIT;
     }
   else	/* just ignore more results */
-    DEBUG(D_lookup) debug_printf_indent("MYSQL: got unexpected more results\n");
+    DEBUG(lookup) debug_printf_indent("MYSQL: got unexpected more results\n");
 
 /* 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()
@@ -374,7 +376,7 @@ if (result)
   }
 else
   {
-  DEBUG(D_lookup) debug_printf_indent("%s\n", *errmsg);
+  DEBUG(lookup) debug_printf_indent("%s\n", *errmsg);
   return yield;      /* FAIL or DEFER */
   }
 }
@@ -467,35 +469,39 @@ return quoted;
 
 /* See local README for interface description. */
 
-#include "../version.h"
-
 gstring *
 mysql_version_report(gstring * g)
 {
 g = string_fmt_append(g,
   "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
-g = string_fmt_append(g,
-  "                        Exim version %s\n", EXIM_VERSION_STR);
-#endif
+      (long)EXIM_MxSQL_VERSION_ID, EXIM_MxSQL_VERSION_STR, EXIM_MxSQL_BASE_STR,
+      mysql_get_client_version(), mysql_get_client_info());
 return g;
 }
 
+/******************************************************************************/
+/* Module API */
+
+static optionlist mysql_glbl_options[] = {
+  { "mysql_servers",	opt_stringptr,	{&mysql_servers} }
+};
+
 /* These are the lookup_info blocks for this driver */
 
 static lookup_info mysql_lookup_info = {
-  .name = US"mysql",			/* lookup name */
-  .type = lookup_querystyle,		/* query-style lookup */
-  .open = mysql_open,			/* open function */
-  .check = NULL,			/* no check function */
-  .find = mysql_find,			/* find function */
-  .close = NULL,			/* no close function */
-  .tidy = mysql_tidy,			/* tidy function */
-  .quote = mysql_quote,			/* quoting function */
-  .version_report = mysql_version_report           /* version reporting */
+  .name =	US"mysql",			/* lookup name */
+  .type =	lookup_querystyle,		/* query-style lookup */
+  .open =	mysql_open,			/* open function */
+  .check =	NULL,				/* no check function */
+  .find =	mysql_find,			/* find function */
+  .close =	NULL,				/* no close function */
+  .tidy =	mysql_tidy,			/* tidy function */
+  .quote =	mysql_quote,			/* quoting function */
+  .version_report = mysql_version_report,	/* version reporting */
+
+  .options =	mysql_glbl_options,
+  .options_count = nelem(mysql_glbl_options),
 };
 
 #ifdef DYNLOOKUP
diff --git a/src/src/lookups/nis.c b/src/src/lookups/nis.c
index aa12f4742..77f6b0f68 100644
--- a/src/src/lookups/nis.c
+++ b/src/src/lookups/nis.c
@@ -101,6 +101,7 @@ return (rc == YPERR_KEY || rc == YPERR_MAP)? FAIL : DEFER;
 gstring *
 nis_version_report(gstring * g)
 {
+/* maybe part of glibc? */
 #ifdef DYNLOOKUP
 g = string_fmt_append(g, "Library version: NIS: Exim version %s\n", EXIM_VERSION_STR);
 #endif
diff --git a/src/src/lookups/nmh.c b/src/src/lookups/nmh.c
index c2fd0e61c..fc01a4ecf 100644
--- a/src/src/lookups/nmh.c
+++ b/src/src/lookups/nmh.c
@@ -106,7 +106,7 @@ if (connect(fd, (const struct sockaddr *)&s_un, (socklen_t)slen) < 0)
   {
   (void) close(fd);
   *errmsg= string_sprintf("connect '%s': %s", server, strerror(errno));
-  log_write(0, LOG_MAIN|LOG_PANIC, "nmh lookup: %s\n", *errmsg);
+  log_write(LOG_MAIN|LOG_PANIC, "nmh lookup: %s\n", *errmsg);
   return -1;
   }
 return fd;
@@ -268,7 +268,7 @@ if (!cn)
   cn->next = nmh_connections;
   nmh_connections = cn;
   }
-else DEBUG(D_lookup)
+else DEBUG(lookup)
   debug_printf_indent("cached socket\n");
 
 /* Build and send the query string */
@@ -277,7 +277,7 @@ g = string_fmt_append(NULL, "%s%c%n%c%s",
       table, '\0', &i, mode, keystring);
 s = string_from_gstring(g);
 
-DEBUG(D_lookup)
+DEBUG(lookup)
   debug_printf("%s %d: send '%s\\0%s'\n", __FUNCTION__, __LINE__, s, s + i);
 
 i = write(sock, s, gstring_length(g));
@@ -292,7 +292,7 @@ if (i != gstring_length(g))
 if (!poll_one_fd(sock, POLLIN, read_timeout * 1000))
   {
   *errmsg = US"read timed out";
-  log_write(0, LOG_MAIN|LOG_PANIC, "Timeout on nmh lookup on %q\n", filename);
+  log_write(LOG_MAIN|LOG_PANIC, "Timeout on nmh lookup on %q\n", filename);
   return DEFER;
   }
 if (read(sock, resp, 1) != 1)
@@ -301,7 +301,7 @@ if (read(sock, resp, 1) != 1)
   return DEFER;
   }
 
-DEBUG(D_lookup)
+DEBUG(lookup)
   debug_printf("%s %d: recv '%.1s'\n", __FUNCTION__, __LINE__, resp);
 
 switch (resp[0])
@@ -328,7 +328,7 @@ nmh_connection *cn;
 while ((cn = nmh_connections))
   {
   nmh_connections = cn->next;
-  DEBUG(D_lookup) debug_printf_indent("close NMH connection: %s\n", cn->server);
+  DEBUG(lookup) debug_printf_indent("close NMH connection: %s\n", cn->server);
   close(cn->socket);
   }
 }
@@ -347,6 +347,7 @@ while ((cn = nmh_connections))
 gstring *
 nmh_version_report(gstring * g)
 {
+/* NMH has no version api! */
 #ifdef DYNLOOKUP
 g = string_fmt_append(g, "Library version: NMH: Exim version %s\n", EXIM_VERSION_STR);
 #endif
diff --git a/src/src/lookups/oracle.c b/src/src/lookups/oracle.c
index e4eedbe28..995f83ab1 100644
--- a/src/src/lookups/oracle.c
+++ b/src/src/lookups/oracle.c
@@ -69,6 +69,11 @@ typedef struct Ora_Define {
   ub2   col_retlen, col_retcode;
 } Ora_Define;
 
+
+
+static uschar * oracle_servers = NULL;	/* List of servers and connect info */
+
+
 /* Structure and anchor for caching connections. */
 
 typedef struct oracle_connection {
@@ -218,7 +223,7 @@ oracle_connection *cn;
 while ((cn = oracle_connections))
   {
   oracle_connections = cn->next;
-  DEBUG(D_lookup) debug_printf_indent("close ORACLE connection: %s\n", cn->server);
+  DEBUG(lookup) debug_printf_indent("close ORACLE connection: %s\n", cn->server);
   ologof(cn->handle);
   }
 }
@@ -302,7 +307,7 @@ for (cn = oracle_connections; cn; cn = cn->next)
 
 if (!cn)
   {
-  DEBUG(D_lookup) debug_printf_indent("ORACLE new connection: host=%s database=%s "
+  DEBUG(lookup) debug_printf_indent("ORACLE new connection: host=%s database=%s "
     "user=%s\n", sdata[0], sdata[1], sdata[2]);
 
   /* Get store for a new connection, initialize it, and connect to the server */
@@ -342,7 +347,7 @@ if (!cn)
 /* Else use a previously cached connection - we can write to the server string
 to obliterate the password because it is in a nextinlist temporary buffer. */
 
-else DEBUG(D_lookup)
+else DEBUG(lookup)
   debug_printf_indent("ORACLE using cached connection for %s\n", server_copy);
 
 /* We have a connection. Open a cursor and run the query */
@@ -485,7 +490,7 @@ if (result)
   }
 else
   {
-  DEBUG(D_lookup) debug_printf_indent("%s\n", *errmsg);
+  DEBUG(lookup) debug_printf_indent("%s\n", *errmsg);
   return yield;      /* FAIL or DEFER */
   }
 }
@@ -511,7 +516,7 @@ uschar *list = oracle_servers;
 
 do_cache = do_cache;   /* Placate picky compilers */
 
-DEBUG(D_lookup) debug_printf_indent("ORACLE query: %s\n", query);
+DEBUG(lookup) debug_printf_indent("ORACLE query: %s\n", query);
 
 while ((server = string_nextinlist(&list, &sep, NULL, 0)))
   {
@@ -604,16 +609,26 @@ return g;
 }
 
 
+/******************************************************************************/
+/* Module API */
+
+static optionlist oracle_options[] = {
+  { "oracle_servers",	opt_stringptr,	{&oracle_servers} }
+};
+
 static lookup_info _lookup_info = {
-  .name = US"oracle",			/* lookup name */
-  .type = lookup_querystyle,		/* query-style lookup */
-  .open = oracle_open,			/* open function */
-  .check = NULL,			/* check function */
-  .find = oracle_find,			/* find function */
-  .close = NULL,			/* no close function */
-  .tidy = oracle_tidy,			/* tidy function */
-  .quote = oracle_quote,		/* quoting function */
-  .version_report = oracle_version_report          /* version reporting */
+  .name =	US"oracle",			/* lookup name */
+  .type =	lookup_querystyle,		/* query-style lookup */
+  .open =	oracle_open,			/* open function */
+  .check =	NULL,				/* check function */
+  .find =	oracle_find,			/* find function */
+  .close =	NULL,				/* no close function */
+  .tidy =	oracle_tidy,			/* tidy function */
+  .quote =	oracle_quote,			/* quoting function */
+  .version_report = oracle_version_report,	/* version reporting */
+
+  .options =	oracle_options,
+  .options_count = nelem(oracle_options),
 };
 
 #ifdef DYNLOOKUP
diff --git a/src/src/lookups/passwd.c b/src/src/lookups/passwd.c
index 7bf499920..c22c2d4cc 100644
--- a/src/src/lookups/passwd.c
+++ b/src/src/lookups/passwd.c
@@ -58,10 +58,8 @@ return OK;
 gstring *
 passwd_version_report(gstring * g)
 {
-#ifdef DYNLOOKUP
-g = string_fmt_append(g, "Library version: passwd: Exim version %s\n", EXIM_VERSION_STR);
-#endif
-return g;
+return string_fmt_append(g, "Library version: passwd: Exim %s builtin\n",
+			    EXIM_VERSION_STR);
 }
 
 static lookup_info _lookup_info = {
diff --git a/src/src/lookups/pgsql.c b/src/src/lookups/pgsql.c
index a578645ff..009f95ad9 100644
--- a/src/src/lookups/pgsql.c
+++ b/src/src/lookups/pgsql.c
@@ -16,6 +16,9 @@ socket extension. */
 
 #include <libpq-fe.h>       /* The system header */
 
+
+static uschar * pgsql_servers = NULL;	/* List of servers and connect info */
+
 /* Structure and anchor for caching connections. */
 
 typedef struct pgsql_connection {
@@ -55,7 +58,7 @@ pgsql_connection *cn;
 while ((cn = pgsql_connections))
   {
   pgsql_connections = cn->next;
-  DEBUG(D_lookup) debug_printf_indent("close PGSQL connection: %s\n", cn->server);
+  DEBUG(lookup) debug_printf_indent("close PGSQL connection: %s\n", cn->server);
   PQfinish(cn->handle);
   }
 }
@@ -79,7 +82,7 @@ static void
 notice_processor(void *arg, const char *message)
 {
 arg = arg;   /* Keep compiler happy */
-DEBUG(D_lookup) debug_printf_indent("PGSQL: %s\n", message);
+DEBUG(lookup) debug_printf_indent("PGSQL: %s\n", message);
 }
 
 
@@ -185,7 +188,7 @@ if (!cn)
     last_slash = Ustrrchr(server, '/');
     last_dot = Ustrrchr(server, '.');
 
-    DEBUG(D_lookup) debug_printf_indent("PGSQL new connection: socket=%s "
+    DEBUG(lookup) debug_printf_indent("PGSQL new connection: socket=%s "
       "database=%s user=%s\n", server, sdata[0], sdata[1]);
 
     /* A valid socket name looks like this: /var/run/postgresql/.s.PGSQL.5432
@@ -234,7 +237,7 @@ if (!cn)
       return DEFER;
       }
 
-    DEBUG(D_lookup) debug_printf_indent("PGSQL new connection: host=%s port=%s "
+    DEBUG(lookup) debug_printf_indent("PGSQL new connection: host=%s port=%s "
       "database=%s user=%s\n", server, port, sdata[0], sdata[1]);
     }
 
@@ -281,7 +284,7 @@ if (!cn)
 
 /* Else use a previously cached connection */
 
-else DEBUG(D_lookup)
+else DEBUG(lookup)
   debug_printf_indent("PGSQL using cached connection for %s\n", server_copy);
 
 /* Run the query */
@@ -298,7 +301,7 @@ switch(PQresultStatus(pg_result))
 
     result = string_cat(result, US PQcmdTuples(pg_result));
     *do_cache = 0;
-    DEBUG(D_lookup) debug_printf_indent("PGSQL: command does not return any data "
+    DEBUG(lookup) debug_printf_indent("PGSQL: command does not return any data "
       "but was successful. Rows affected: %Y\n", result);
     break;
 
@@ -373,7 +376,7 @@ if (result)
   }
 else
   {
-  DEBUG(D_lookup) debug_printf_indent("%s\n", *errmsg);
+  DEBUG(lookup) debug_printf_indent("%s\n", *errmsg);
   return yield;      /* FAIL or DEFER */
   }
 }
@@ -472,14 +475,10 @@ return quoted;
 
 /* See local README for interface description. */
 
-#include "../version.h"
-
 gstring *
 pgsql_version_report(gstring * g)
 {
-#ifdef DYNLOOKUP
-g = string_fmt_append(g, "Library version: PostgreSQL: Exim version %s\n", EXIM_VERSION_STR);
-#endif
+int ver = PQlibVersion();
 
 /* Version reporting: there appears to be no available information about
 the client library in libpq-fe.h; once you have a connection object, you
@@ -487,20 +486,33 @@ can access the server version and the chosen protocol version, but those
 aren't really what we want.  It might make sense to debug_printf those
 when the connection is established though? */
 
+/* version 9.1 onwards */
+g = string_fmt_append(g, "Library version: PostgreSQL: Runtime: %d.%d\n",
+			ver/10000, ver%10000);
 return g;
 }
 
 
+/******************************************************************************/
+/* Module API */
+
+static optionlist pgsql_options[] = {
+  { "pgsql_servers",	opt_stringptr,	{&pgsql_servers} }
+};
+
 static lookup_info _lookup_info = {
-  .name = US"pgsql",			/* lookup name */
-  .type = lookup_querystyle,		/* query-style lookup */
-  .open = pgsql_open,			/* open function */
-  .check = NULL,			/* no check function */
-  .find = pgsql_find,			/* find function */
-  .close = NULL,			/* no close function */
-  .tidy = pgsql_tidy,			/* tidy function */
-  .quote = pgsql_quote,			/* quoting function */
-  .version_report = pgsql_version_report           /* version reporting */
+  .name =	US"pgsql",			/* lookup name */
+  .type =	lookup_querystyle,		/* query-style lookup */
+  .open =	pgsql_open,			/* open function */
+  .check =	NULL,				/* no check function */
+  .find =	pgsql_find,			/* find function */
+  .close =	NULL,				/* no close function */
+  .tidy =	pgsql_tidy,			/* tidy function */
+  .quote =	pgsql_quote,			/* quoting function */
+  .version_report = pgsql_version_report,	/* version reporting */
+
+  .options =	pgsql_options,
+  .options_count = nelem(pgsql_options),
 };
 
 #ifdef DYNLOOKUP
diff --git a/src/src/lookups/psl.c b/src/src/lookups/psl.c
new file mode 100644
index 000000000..cf01a21bb
--- /dev/null
+++ b/src/src/lookups/psl.c
@@ -0,0 +1,250 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) The Exim Maintainers 2025 */
+/* See the file NOTICE for conditions of use and distribution. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "../exim.h"
+
+#ifndef SUPPORT_I18N
+# error PSL lookup requires internationalisation support
+#endif
+
+
+/*************************************************
+*              Open entry point                  *
+*************************************************/
+
+/* See local README for interface description */
+
+static void *
+psl_open(const uschar * filename, uschar ** errmsg)
+{
+FILE * f = fopen(CCS filename, "r");
+if (f) return (void *) f;
+*errmsg = US strerror(errno);
+return NULL;
+}
+
+static void
+psl_close(void * handle)
+{
+(void) fclose(handle);
+}
+
+
+
+
+/*************************************************
+*         Generic "find" implementation          *
+*************************************************/
+
+static int
+psl_gen_find(void * handle, const uschar * keystring,
+  int length, uschar ** result, uschar ** errmsg, BOOL is_regdom)
+{
+uschar rulebuf[128], * res = NULL;
+const uschar * s, * k, * kmatch = NULL;
+unsigned res_label_cnt = 0, nlabels;
+BOOL key_utf8;
+
+/* Ensure key is punycode and lowercase */
+
+if ((key_utf8 = string_is_utf8(keystring)))
+  {
+  DEBUG(lookup) debug_printf_indent("converting utf8 key %q\n", keystring);
+  if (!(keystring = string_domain_utf8_to_alabel(keystring, errmsg)))
+    return FAIL;
+  length = Ustrlen(keystring);
+  DEBUG(lookup) debug_printf_indent(" result %q\n", keystring);
+  }
+else
+  for (k = keystring; *k; k++)
+    if (isupper(*k)) { keystring = string_copylc(keystring); break; }
+
+while ((s = US fgets(CS rulebuf, sizeof(rulebuf), handle)))
+  {
+  const uschar * r;
+
+  if (!*s || *s == '\n') continue;		/* empty line */
+  if (s[0] == '/' && s[1] == '/') continue;	/* comment line */
+
+  nlabels = 1;
+  if ((r = US strsep(CSS &s, " \n\t")))
+    {
+    BOOL exception = *r == '!';
+    const uschar * t;
+
+    /* We convert any utf8 to punycode before starting comparison. It might
+    be more efficient to wait until hitting a top-bit-set byte? */
+    if (!(t = string_domain_utf8_to_alabel(r, errmsg)))
+      goto fail;
+    if (t != r)
+      {
+      DEBUG(lookup) debug_printf_indent("converting utf8 psl entry %q\n"
+	" result %q\n", r, t);
+      r = t;
+      }
+
+    for (s = r + Ustrlen(r), k = keystring + length; ; )
+      {
+      uschar rch = s[-1];
+
+      if (rch == '.')		/* label separator */
+	nlabels++;
+      if (rch == '*')		/* wildcard in rule (assume leading) */
+	{
+	/* take the current label from the key */
+	while (k > keystring && k[-1] != '.')
+	  k--;
+	s = k;
+	/* s is the match */
+	/* k is the key trail after the regdom. label */
+	/* nlabels describes k */
+	break;			/* - match */
+	}
+      if (rch == '!')		/* exception rule */
+	{
+	if (!is_regdom)
+	  /* Remove the LH label then treat as a match */
+	  while (TRUE)
+	    if (!*s || *s++ == '.') break;
+
+	/* s is the match, or the regdom */
+	/* nlabels don't care */
+	res = string_copy_taint(s, GET_UNTAINTED);
+	goto found;		/* ER's have priority; stop reading file */
+	}
+
+      s--; k--;
+
+      if (rch != *k)		/* character difference */
+	goto nonmatch;
+      if (k <= keystring && !exception)	/* ran out of key */
+	goto nonmatch;
+      if (s == r)		/* run out of rule */
+	if (k[-1] != '.')	/* key has prefix on rule */
+	  goto nonmatch;
+	else
+	  break;		/* out of rule: s is the match */
+      }
+
+    if (nlabels > res_label_cnt)
+      {				/* new longest match (by cnt of labels) */
+      res = string_copy_taint(s, GET_UNTAINTED);
+      res_label_cnt = nlabels;
+      kmatch = k;
+      }
+
+    nonmatch: ;
+    }
+  }
+    /* kmatch is the key trail after the regdom. label */
+    /* res_label_cnt describes kmatch */
+
+if (is_regdom && res)		/* prepend label from key to pub-suffix */
+  {
+  s = kmatch;
+  /* back up one label in key */
+  if (s-- <= keystring) goto fail;	/* there must ba a dot */
+  if (s-- <= keystring) goto fail;	/* there must ba at least one ch */
+  while (s > keystring && s[-1] != '.') s--;
+  res = string_sprintf("%.*s%s", (int)(kmatch - s), s, res);
+  }
+
+found:
+
+if (key_utf8 && res)
+  {
+  if (!(*result = string_domain_alabel_to_utf8(res, errmsg)))
+    goto fail;
+  DEBUG(lookup)
+    debug_printf_indent("utf8 converting result %q\n to %q\n", res, *result);
+  }
+else
+  *result = res;
+
+rewind(handle);
+return OK;
+
+fail:
+rewind(handle);
+return FAIL;
+}
+
+
+
+/*************************************************
+*  Find entry points for pub-suffix and regdom   *
+*************************************************/
+
+/* See local README for interface description */
+
+static int
+psl_find(void * handle, const uschar * filename, const uschar * keystring,
+  int length, uschar ** result, uschar ** errmsg, uint * do_cache,
+  const uschar * opts)
+{
+return psl_gen_find(handle, keystring, length, result, errmsg, FALSE);
+}
+
+static int
+regdom_find(void * handle, const uschar * filename, const uschar * keystring,
+  int length, uschar ** result, uschar ** errmsg, uint * do_cache,
+  const uschar * opts)
+{
+return psl_gen_find(handle, keystring, length, result, errmsg, TRUE);
+}
+
+
+
+
+/*************************************************
+*         Version reporting entry point          *
+*************************************************/
+
+/* See local README for interface description. */
+
+#include "../version.h"
+
+gstring *
+psl_version_report(gstring * g)
+{
+return string_fmt_append(g, "Library version: psl: Exim %s builtin\n",
+				EXIM_VERSION_STR);
+}
+
+static lookup_info psl_lookup_info = {
+  .name =	US"psl",		/* lookup name */
+  .type =	lookup_absfile,		/* lookup from file */
+  .open =	psl_open,		/* open function */
+  .check =	NULL,			/* no check function */
+  .find =	psl_find,		/* find function */
+  .close =	psl_close,		/* close function */
+  .tidy =	NULL,			/* no tidy function */
+  .quote =	NULL,			/* no quoting function */
+  .version_report = psl_version_report	/* version reporting */
+};
+
+static lookup_info regdom_lookup_info = {
+  .name =	US"regdom",		/* lookup name */
+  .type =	lookup_absfile,		/* lookup from file */
+  .open =	psl_open,		/* open function */
+  .check =	NULL,			/* no check function */
+  .find =	regdom_find,		/* find function */
+  .close =	psl_close,		/* close function */
+  .tidy =	NULL,			/* no tidy function */
+  .quote =	NULL,			/* no quoting function */
+  .version_report = NULL		/* no version reporting (redundant) */
+};
+
+#ifdef DYNLOOKUP
+#define psl_lookup_module_info _lookup_module_info
+#endif
+
+static lookup_info *_lookup_list[] = { &psl_lookup_info, &regdom_lookup_info };
+lookup_module_info psl_lookup_module_info = { LOOKUP_MODULE_INFO_MAGIC, _lookup_list, 2 };
+
+/* End of lookups/psl.c */
diff --git a/src/src/lookups/readsock.c b/src/src/lookups/readsock.c
index 6b9061b8b..3f6922014 100644
--- a/src/src/lookups/readsock.c
+++ b/src/src/lookups/readsock.c
@@ -23,7 +23,7 @@ if (Ustrncmp(sspec, "inet:", 5) == 0)
   int port;
   uschar * port_name;
 
-  DEBUG(D_lookup)
+  DEBUG(lookup)
     debug_printf_indent("  new inet socket needed for readsocket\n");
 
   server_name = sspec + 5;
@@ -81,7 +81,7 @@ else
   struct sockaddr_un sockun;         /* don't call this "sun" ! */
   int rc;
 
-  DEBUG(D_lookup)
+  DEBUG(lookup)
     debug_printf_indent("  new unix socket needed for readsocket\n");
 
   if ((cctx->sock = socket(PF_UNIX, SOCK_STREAM, 0)) == -1)
@@ -120,7 +120,7 @@ if (do_tls)
     goto bad;
 #endif
 
-DEBUG(D_expand|D_lookup) debug_printf_indent("  connected to socket %s\n", sspec);
+DEBUG(expand|lookup) debug_printf_indent("  connected to socket %s\n", sspec);
 return OK;
 
 bad:
@@ -145,7 +145,7 @@ readsock_open(const uschar * filename, uschar ** errmsg)
 client_conn_ctx * cctx = store_get(sizeof(*cctx), GET_UNTAINTED);
 cctx->sock = -1;
 cctx->tls_ctx = NULL;
-DEBUG(D_lookup) debug_printf_indent("readsock: allocated context\n");
+DEBUG(lookup) debug_printf_indent("readsock: allocated context\n");
 return cctx;
 }
 
@@ -176,7 +176,7 @@ int timeout = 5;
 gstring * yield;
 int ret = DEFER;
 
-DEBUG(D_lookup)
+DEBUG(lookup)
   debug_printf_indent("readsock: file=%q key=%q len=%d opts=%q\n",
     filename, keystring, length, opts);
 
@@ -252,7 +252,7 @@ if (!cctx->tls_ctx)
   FILE * fp = fdopen(cctx->sock, "rb");
   if (!fp)
     {
-    log_write(0, LOG_MAIN|LOG_PANIC, "readsock fdopen: %s\n", strerror(errno));
+    log_write(LOG_MAIN|LOG_PANIC, "readsock fdopen: %s\n", strerror(errno));
     goto out;
     }
   ALARM(timeout);
diff --git a/src/src/lookups/redis.c b/src/src/lookups/redis.c
index 4b11475d0..03c290ee2 100644
--- a/src/src/lookups/redis.c
+++ b/src/src/lookups/redis.c
@@ -15,9 +15,7 @@
 
 #include <hiredis/hiredis.h>
 
-#ifndef nele
-# define nele(arr) (sizeof(arr) / sizeof(*arr))
-#endif
+static uschar * redis_servers = NULL;	/* List of servers and connect info */
 
 /* Structure and anchor for caching connections. */
 typedef struct redis_connection {
@@ -26,7 +24,7 @@ typedef struct redis_connection {
   redisContext    *handle;
 } redis_connection;
 
-static redis_connection *redis_connections = NULL;
+static redis_connection * redis_connections = NULL;
 
 
 static void *
@@ -49,7 +47,7 @@ redis_connection *cn;
 while ((cn = redis_connections))
   {
   redis_connections = cn->next;
-  DEBUG(D_lookup) debug_printf_indent("close REDIS connection: %s\n", cn->server);
+  DEBUG(lookup) debug_printf_indent("close REDIS connection: %s\n", cn->server);
   redisFree(cn->handle);
   }
 }
@@ -155,7 +153,7 @@ if (!cn)
     return DEFER;
     }
 
-  DEBUG(D_lookup)
+  DEBUG(lookup)
     debug_printf_indent("REDIS new connection: host=%s port=%d socket=%s database=%s\n",
       sdata[0], port, socket, sdata[1]);
 
@@ -177,7 +175,7 @@ if (!cn)
   cn->next = redis_connections;
   redis_connections = cn;
   }
-else DEBUG(D_lookup)
+else DEBUG(lookup)
   debug_printf_indent("REDIS using cached connection for %s\n", server_copy);
 
 /* Authenticate if there is a password */
@@ -198,7 +196,7 @@ if(sdata[1])
     *defer_break = FALSE;
     goto REDIS_EXIT;
     }
-  DEBUG(D_lookup) debug_printf_indent("REDIS: Selecting database=%s\n", sdata[1]);
+  DEBUG(lookup) debug_printf_indent("REDIS: Selecting database=%s\n", sdata[1]);
   }
 
 /* split string on whitespace into argv */
@@ -210,7 +208,7 @@ if(sdata[1])
 
   Uskip_whitespace(&s);
 
-  for (i = 0; *s && i < nele(argv); i++)
+  for (i = 0; *s && i < nelem(argv); i++)
     {
     gstring * g;
 
@@ -219,7 +217,7 @@ if(sdata[1])
 	g = string_catn(g, s, 1);
     argv[i] = string_from_gstring(g);
 
-    DEBUG(D_lookup) debug_printf_indent("REDIS: argv[%d] '%s'\n", i, argv[i]);
+    DEBUG(lookup) debug_printf_indent("REDIS: argv[%d] '%s'\n", i, argv[i]);
     Uskip_whitespace(&s);
     }
 
@@ -242,7 +240,7 @@ switch (redis_reply->type)
     /* trap MOVED cluster responses and follow them */
     if (Ustrncmp(redis_reply->str, "MOVED", 5) == 0)
       {
-      DEBUG(D_lookup)
+      DEBUG(lookup)
         debug_printf_indent("REDIS: cluster redirect %s\n", redis_reply->str);
       /* follow redirect
       This is cheating, we simply set defer_break = FALSE to move on to
@@ -257,7 +255,7 @@ switch (redis_reply->type)
     /* NOTREACHED */
 
   case REDIS_REPLY_NIL:
-    DEBUG(D_lookup)
+    DEBUG(lookup)
       debug_printf_indent("REDIS: query was not one that returned any data\n");
     result = string_catn(result, US"", 1);
     *do_cache = 0;
@@ -310,19 +308,19 @@ switch (redis_reply->type)
 		result = string_catn(result, US tentry->str, tentry->len);
 		break;
 	      case REDIS_REPLY_ARRAY:
-		DEBUG(D_lookup)
+		DEBUG(lookup)
 		  debug_printf_indent("REDIS: result has nesting of arrays which"
 		    " is not supported. Ignoring!\n");
 		break;
 	      default:
-		DEBUG(D_lookup) debug_printf_indent(
+		DEBUG(lookup) debug_printf_indent(
 			  "REDIS: result has unsupported type. Ignoring!\n");
 		break;
 	      }
 	    }
 	    break;
 	  default:
-	    DEBUG(D_lookup) debug_printf_indent("REDIS: query returned unsupported type\n");
+	    DEBUG(lookup) debug_printf_indent("REDIS: query returned unsupported type\n");
 	    break;
 	  }
 	}
@@ -354,7 +352,7 @@ if (result)
   }
 else
   {
-  DEBUG(D_lookup) debug_printf_indent("%s\n", *errmsg);
+  DEBUG(lookup) debug_printf_indent("%s\n", *errmsg);
   /* NOTE: Required to close connection since it needs to be reopened */
   return yield;      /* FAIL or DEFER */
   }
@@ -428,33 +426,37 @@ return quoted;
 /*************************************************
 *         Version reporting entry point          *
 *************************************************/
-#include "../version.h"
 
 gstring *
 redis_version_report(gstring * g)
 {
-g = string_fmt_append(g,
-  "Library version: REDIS: Compile: %d [%d]\n", HIREDIS_MAJOR, HIREDIS_MINOR);
-#ifdef DYNLOOKUP
-g = string_fmt_append(g,
-  "                        Exim version %s\n", EXIM_VERSION_STR);
-#endif
-return g;
+return string_fmt_append(g, "Library version: REDIS: Compile: %d.%d.%d\n",
+    HIREDIS_MAJOR, HIREDIS_MINOR, HIREDIS_PATCH);
 }
 
 
 
+/******************************************************************************/
+/* Module API */
+
+static optionlist redis_options[] = {
+  { "redis_servers",	opt_stringptr,	{&redis_servers} }
+};
+
 /* These are the lookup_info blocks for this driver */
 static lookup_info redis_lookup_info = {
-  .name = US"redis",			/* lookup name */
-  .type = lookup_querystyle,		/* query-style lookup */
-  .open = redis_open,			/* open function */
-  .check = NULL,			/* no check function */
-  .find = redis_find,			/* find function */
-  .close = NULL,			/* no close function */
-  .tidy = redis_tidy,			/* tidy function */
-  .quote = redis_quote,			/* quoting function */
-  .version_report = redis_version_report           /* version reporting */
+  .name =	US"redis",			/* lookup name */
+  .type =	lookup_querystyle,		/* query-style lookup */
+  .open =	redis_open,			/* open function */
+  .check =	NULL,				/* no check function */
+  .find =	redis_find,			/* find function */
+  .close =	NULL,				/* no close function */
+  .tidy =	redis_tidy,			/* tidy function */
+  .quote =	redis_quote,			/* quoting function */
+  .version_report = redis_version_report,	/*  version reporting */
+
+  .options =	redis_options,
+  .options_count = nelem(redis_options),
 };
 
 #ifdef DYNLOOKUP
diff --git a/src/src/lookups/spf.c b/src/src/lookups/spf.c
index ac03983c2..131486622 100644
--- a/src/src/lookups/spf.c
+++ b/src/src/lookups/spf.c
@@ -15,13 +15,6 @@ of the License, or (at your option) any later version.
 */
 
 #include "../exim.h"
-
-#ifndef EXIM_HAVE_SPF
-static void dummy(int x);
-static void dummy2(int x) { dummy(x-1); }
-static void dummy(int x) { dummy2(x-1); }
-#else
-
 #include "lf_functions.h"
 
 #ifndef EXPERIMENTAL_SPF_PERL
@@ -42,7 +35,7 @@ static void *
 spf_open(const uschar * filename, uschar ** errmsg)
 {
 misc_module_info * mi;
-DEBUG(D_lookup) debug_printf_indent("spf lookup spf_open\n");
+DEBUG(lookup) debug_printf_indent("spf lookup spf_open\n");
 if ((mi = misc_mod_find(US"spf", errmsg)))
   {
   typedef void * (*fn_t)(const uschar *, uschar **);
@@ -88,35 +81,32 @@ return FAIL;
 
 /* See local README for interface description. */
 
-#include "../version.h"
-
 gstring *
 spf_version_report(gstring * g)
 {
-#ifdef DYNLOOKUP
-g = string_fmt_append(g, "Library version: SPF: Exim version %s\n", EXIM_VERSION_STR));
-#endif
-return g;
+misc_module_info * mi = misc_mod_find(US"spf", NULL);
+return mi && mi->lib_vers_report ? mi->lib_vers_report(g) : g;
 }
 
 
+
 static lookup_info spf_lookup_info = {
-  .name = US"spf",			/* lookup name */
-  .type = 0,				/* not absfile, not query style */
-  .open = spf_open,			/* open function */
-  .check = NULL,			/* no check function */
-  .find = spf_find,			/* find function */
-  .close = spf_close,			/* close function */
-  .tidy = NULL,				/* no tidy function */
-  .quote = NULL,			/* no quoting function */
-  .version_report = spf_version_report             /* version reporting */
+  .name =	US"spf",		/* lookup name */
+  .type =	0,			/* not absfile, not query style */
+  .open =	spf_open,		/* open function */
+  .check =	NULL,			/* no check function */
+  .find =	spf_find,		/* find function */
+  .close =	spf_close,		/* close function */
+  .tidy =	NULL,			/* no tidy function */
+  .quote =	NULL,			/* no quoting function */
+  .version_report = spf_version_report	/* version reporting */
 };
 
-#ifdef notdef_DYNLOOKUP
-#define spf_lookup_module_info _lookup_module_info
+#ifdef DYNLOOKUP
+# define spf_lookup_module_info _lookup_module_info
 #endif
 
 static lookup_info *_lookup_list[] = { &spf_lookup_info };
-lookup_module_info spf_lookup_module_info = { LOOKUP_MODULE_INFO_MAGIC, _lookup_list, 1 };
+lookup_module_info spf_lookup_module_info =
+  { LOOKUP_MODULE_INFO_MAGIC, _lookup_list, 1 };
 
-#endif /* EXIM_HAVE_SPF */
diff --git a/src/src/lookups/sqlite.c b/src/src/lookups/sqlite.c
index 722916cf3..9deacf905 100644
--- a/src/src/lookups/sqlite.c
+++ b/src/src/lookups/sqlite.c
@@ -12,6 +12,9 @@
 
 #include <sqlite3.h>
 
+static uschar * sqlite_dbfile = NULL;	/* Filename for database */
+static int	sqlite_lock_timeout = 5;/* Internal lock waiting timeout */
+
 
 /*************************************************
 *              Open entry point                  *
@@ -27,7 +30,7 @@ int ret;
 
 if (!filename || !*filename)
   {
-  DEBUG(D_lookup) debug_printf_indent("Using sqlite_dbfile: %s\n", sqlite_dbfile);
+  DEBUG(lookup) debug_printf_indent("Using sqlite_dbfile: %s\n", sqlite_dbfile);
   filename = sqlite_dbfile;
   }
 if (!filename || *filename != '/')
@@ -37,7 +40,7 @@ else if ((ret = sqlite3_open(CCS filename, &db)) != 0)
   *errmsg = string_copy(US sqlite3_errmsg(db));
   sqlite3_close(db);
   db = NULL;
-  DEBUG(D_lookup) debug_printf_indent("Error opening database: %s\n", *errmsg);
+  DEBUG(lookup) debug_printf_indent("Error opening database: %s\n", *errmsg);
   }
 
 if (db)
@@ -163,8 +166,6 @@ return quoted;
 
 /* See local README for interface description. */
 
-#include "../version.h"
-
 gstring *
 sqlite_version_report(gstring * g)
 {
@@ -172,27 +173,34 @@ g = string_fmt_append(g,
   "Library version: SQLite: Compile: %s\n"
   "                         Runtime: %s\n",
         SQLITE_VERSION, sqlite3_libversion());
-#ifdef DYNLOOKUP
-g = string_fmt_append(g,
-  "                         Exim version %s\n", EXIM_VERSION_STR);
-#endif
 return g;
 }
 
+/******************************************************************************/
+/* Module API */
+
+static optionlist sqlite_options[] = {
+  { "sqlite_dbfile",            opt_stringptr,   {&sqlite_dbfile} },
+  { "sqlite_lock_timeout",      opt_int,         {&sqlite_lock_timeout} },
+};
+
 static lookup_info _lookup_info = {
-  .name = US"sqlite",			/* lookup name */
-  .type = lookup_absfilequery,		/* query-style lookup, starts with file name */
-  .open = sqlite_open,			/* open function */
-  .check = NULL,			/* no check function */
-  .find = sqlite_find,			/* find function */
-  .close = sqlite_close,		/* close function */
-  .tidy = NULL,				/* no tidy function */
-  .quote = sqlite_quote,		/* quoting function */
-  .version_report = sqlite_version_report          /* version reporting */
+  .name =	US"sqlite",			/* lookup name */
+  .type =	lookup_absfilequery,		/* query-style lookup, starts with file name */
+  .open =	sqlite_open,			/* open function */
+  .check =	NULL,				/* no check function */
+  .find =	sqlite_find,			/* find function */
+  .close =	sqlite_close,			/* close function */
+  .tidy =	NULL,				/* no tidy function */
+  .quote =	sqlite_quote,			/* quoting function */
+  .version_report = sqlite_version_report,	/* version reporting */
+
+  .options =	sqlite_options,
+  .options_count = nelem(sqlite_options),
 };
 
 #ifdef DYNLOOKUP
-#define sqlite_lookup_module_info _lookup_module_info
+# define sqlite_lookup_module_info _lookup_module_info
 #endif
 
 static lookup_info *_lookup_list[] = { &_lookup_info };
diff --git a/src/src/lookups/testdb.c b/src/src/lookups/testdb.c
index 44d77b8c8..b0046af55 100644
--- a/src/src/lookups/testdb.c
+++ b/src/src/lookups/testdb.c
@@ -44,13 +44,13 @@ testdb_find(void * handle, const uschar * filename, const uschar * query,
 if (Ustrcmp(query, "fail") == 0)
   {
   *errmsg = US"testdb lookup forced FAIL";
-  DEBUG(D_lookup) debug_printf_indent("%s\n", *errmsg);
+  DEBUG(lookup) debug_printf_indent("%s\n", *errmsg);
   return FAIL;
   }
 if (Ustrcmp(query, "defer") == 0)
   {
   *errmsg = US"testdb lookup forced DEFER";
-  DEBUG(D_lookup) debug_printf_indent("%s\n", *errmsg);
+  DEBUG(lookup) debug_printf_indent("%s\n", *errmsg);
   return DEFER;
   }
 
@@ -83,10 +83,8 @@ return quoted;
 gstring *
 testdb_version_report(gstring * g)
 {
-#ifdef DYNLOOKUP
-g = string_fmt_append(g, "Library version: TestDB: Exim version %s\n", EXIM_VERSION_STR);
-#endif
-return g;
+return string_fmt_append(g, "Library version: TestDB: Exim version %s\n",
+			EXIM_VERSION_STR);
 }
 
 
@@ -111,7 +109,7 @@ static lookup_info testdb2_lookup_info = {
   .close = NULL,			/* no close function */
   .tidy = NULL,				/* no tidy function */
   .quote = testdb_quote,		/* same quoting function */
-  .version_report = testdb_version_report          /* version reporting */
+  .version_report = NULL          /* version reporting */
 };
 
 static lookup_info testdb3_lookup_info = {
@@ -123,7 +121,7 @@ static lookup_info testdb3_lookup_info = {
   .close = NULL,			/* no close function */
   .tidy = NULL,				/* no tidy function */
   .quote = NULL,			/* NO quoting function */
-  .version_report = testdb_version_report          /* version reporting */
+  .version_report = NULL          /* version reporting */
 };
 
 #ifdef DYNLOOKUP
diff --git a/src/src/lookups/whoson.c b/src/src/lookups/whoson.c
index cd6c7e85c..dcd48db2d 100644
--- a/src/src/lookups/whoson.c
+++ b/src/src/lookups/whoson.c
@@ -63,18 +63,11 @@ switch (wso_query(CS query, CS buffer, sizeof(buffer)))
 
 /* See local README for interface description. */
 
-#include "../version.h"
-
 gstring *
 whoson_version_report(gstring * g)
 {
-g = string_fmt_append(g,
+return string_fmt_append(g,
   "Library version: Whoson: Runtime: %s\n", wso_version());
-#ifdef DYNLOOKUP
-g = string_fmt_append(g,
-  "                         Exim version %s\n", EXIM_VERSION_STR);
-#endif
-return g;
 }
 
 static lookup_info _lookup_info = {
diff --git a/src/src/macro_predef.c b/src/src/macro_predef.c
index 48287131a..59894cbb3 100644
--- a/src/src/macro_predef.c
+++ b/src/src/macro_predef.c
@@ -8,7 +8,7 @@
 /* SPDX-License-Identifier: GPL-2.0-or-later */
 
 /* Create a static data structure with the predefined macros, to be
-included in the main Exim build */
+included in the main Exim build. Also, table for various drivers. */
 
 #include "exim.h"
 #include "macro_predef.h"
@@ -20,9 +20,12 @@ unsigned mp_index = 0;
 void fn_smtp_receive_timeout(const uschar * name, const uschar * str) {}
 uschar * syslog_facility_str;
 
-/* Solaris needs this one for the macro expand_string() */
+/* Solaris needs these for the macros expand_string, parse_find_address_end */
 const uschar * expand_string_2(const uschar * string, BOOL * textonly_p)
 {return NULL; }
+const uschar * parse_find_address_end_gen(const uschar * s, BOOL b)
+{return NULL; }
+
 
 /******************************************************************************/
 
@@ -155,7 +158,7 @@ due to conflicts with other common macros. */
 #ifndef DISABLE_DKIM
   builtin_macro_create(US"_HAVE_DKIM");
 #endif
-#ifdef SUPPORT_DMARC
+#ifdef EXIM_HAVE_DMARC
   builtin_macro_create(US"_HAVE_DMARC");
 #endif
 #ifndef DISABLE_DNSSEC
@@ -203,15 +206,15 @@ due to conflicts with other common macros. */
 #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 SUPPORT_DSCP
+  builtin_macro_create(US"_HAVE_DSCP");
+#endif
 #ifdef EXPERIMENTAL_DSN_INFO
   builtin_macro_create(US"_HAVE_DSN_INFO");
 #endif
@@ -252,9 +255,6 @@ due to conflicts with other common macros. */
 #ifdef LOOKUP_DSEARCH
   builtin_macro_create(US"_HAVE_LOOKUP_DSEARCH");
 #endif
-#ifdef LOOKUP_IBASE
-  builtin_macro_create(US"_HAVE_LOOKUP_IBASE");
-#endif
 #ifdef LOOKUP_LMDB
   builtin_macro_create(US"_HAVE_LMDB");
   builtin_macro_create(US"_HAVE_LOOKUP_LMDB");
@@ -283,6 +283,10 @@ due to conflicts with other common macros. */
 #ifdef LOOKUP_PGSQL
   builtin_macro_create(US"_HAVE_LOOKUP_PGSQL");
 #endif
+#ifdef LOOKUP_PSL
+  builtin_macro_create(US"_HAVE_LOOKUP_PSL");
+  builtin_macro_create(US"_HAVE_LOOKUP_REGDOM");
+#endif
 #ifdef LOOKUP_REDIS
   builtin_macro_create(US"_HAVE_LOOKUP_REDIS");
 #endif
@@ -322,9 +326,6 @@ exp_features(void)
 #ifdef EXPERIMENTAL_ARC
   builtin_macro_create(US"_EXP_ARC");
 #endif
-#ifdef EXPERIMENTAL_BRIGHTMAIL
-  builtin_macro_create(US"_EXP_BMI");
-#endif
 #ifdef EXPERIMENTAL_DCC
   builtin_macro_create(US"_EXP_DCC");
 #endif
@@ -358,6 +359,42 @@ params_dkim();
 #endif
 }
 
+/******************************************************************************/
+static void
+avail_append(const driver_info * di)
+{
+const uschar * avail_string = di->avail_string;
+if (!avail_string) avail_string = di->driver_name;
+  printf(" US\"%s\",", avail_string);
+}
+
+static void
+avail_list(const driver_info * drtable, const uschar * tag,
+  const uschar * group, BOOL magic)
+{
+printf("const uschar * avail_%s_%s[] = {", group, tag);
+for (const driver_info * di = drtable; di; di = di->next)
+  if (!!di->dyn_magic == magic) avail_append(di);
+printf(" NULL};\n");
+}
+
+static void
+avail_drs(const driver_info * drtable, const uschar * tag)
+{
+avail_list(drtable, tag, US"static", FALSE);
+avail_list(drtable, tag, US"dynamic", TRUE);
+}
+
+/* Make lists of the drivers (by class, and static/dynamic) we are building */
+static void
+avail(void)
+{
+avail_drs((driver_info *)auths_available, US"auths");
+avail_drs((driver_info *)routers_available, US"routers");
+avail_drs((driver_info *)transports_available, US"transports");
+}
+
+/******************************************************************************/
 
 int
 main(void)
@@ -368,8 +405,10 @@ exp_features();
 options();
 expansions();
 params();
-
 printf("macro_item * macros = &p%u;\n", mp_index-1);
 printf("macro_item * mlast = &p0;\n");
+
+avail();
+
 exit(0);
 }
diff --git a/src/src/macros.h b/src/src/macros.h
index 75563d86b..76f2c5619 100644
--- a/src/src/macros.h
+++ b/src/src/macros.h
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) The Exim Maintainers 2020 - 2025 */
+/* Copyright (c) The Exim Maintainers 2020 - 2026 */
 /* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 /* SPDX-License-Identifier: GPL-2.0-or-later */
@@ -98,20 +98,6 @@ don't make the file descriptors two-way. */
 
 #define mac_islookup(li,b) ((li)->type & (b))
 
-/* Debugging control */
-
-#define LOG_NAME_SIZE 256
-#define IS_DEBUG(x)	(debug_selector & (x))
-#define DEBUG(x)	if (IS_DEBUG(x))
-#define HDEBUG(x)	if (host_checking || IS_DEBUG(x))
-
-#define EARLY_DEBUG(x, fmt, ...) \
-  if (debug_startup) \
-    if (debug_fd < 0) \
-      fprintf(stderr, "%s", string_sprintf(fmt, __VA_ARGS__)); \
-    else DEBUG(x) \
-      debug_printf_indent(fmt, __VA_ARGS__);
-
 /* The default From: text for DSNs */
 
 #define DEFAULT_DSN_FROM "Mail Delivery System <Mailer-Daemon@$qualify_domain>"
@@ -120,7 +106,7 @@ don't make the file descriptors two-way. */
 verifying. This has to be explicit because it is referenced in more than one
 source module. */
 
-#define ADDRESS_EXPANSIONS_COUNT 19
+#define ADDRESS_EXPANSIONS_COUNT 21
 
 /* The maximum permitted number of command-line (-D) macro definitions. We
 need a limit only to make it easier to generate argument vectors for re-exec
@@ -146,6 +132,8 @@ enough to hold all the headers from a normal kind of message. */
 
 #define LOG_BUFFER_SIZE 8192
 
+#define LOG_NAME_SIZE 256
+
 /* The size of the circular buffer that remembers recent SMTP commands */
 
 #define SMTP_HBUFF_SIZE 20
@@ -240,10 +228,8 @@ enum {
   ERRMESS_TOOBIG,           /* Message too big */
   ERRMESS_TOOMANYRECIP,     /* Too many recipients */
   ERRMESS_LOCAL_SCAN,       /* Rejected by local scan */
-  ERRMESS_LOCAL_ACL         /* Rejected by non-SMTP ACL */
-#ifdef SUPPORT_DMARC
- ,ERRMESS_DMARC_FORENSIC    /* DMARC Forensic Report */
-#endif
+  ERRMESS_LOCAL_ACL,        /* Rejected by non-SMTP ACL */
+  ERRMESS_DMARC_FORENSIC    /* DMARC Forensic Report */
 };
 
 /* Error handling styles - set by option, and apply only when receiving
@@ -330,26 +316,46 @@ for having to swallow the rest of an SMTP message is whether the value is
 
 /* Bit masks for debug and log selectors */
 
-/* Assume words are 32 bits wide. Tiny waste of space on 64 bit
+#ifndef bitmask_word_t
+# define bitmask_word_t          uint64_t
+#endif
+
+/* Assume words are at least 32 bits wide. Tiny waste of space on 64 bit
 platforms, but this ensures bit vectors always work the same way. */
-#define BITWORDSIZE 32
+#ifdef EXIM_BITMAP_WORD_BITS
+# define BITWORDSIZE EXIM_BITMAP_WORD_BITS
+#else
+# define BITWORDSIZE 64
+#endif
 
 /* This macro is for single-word bit vectors: the debug selector,
-and the first word of the log selector. */
-#define BIT(n) (1UL << (n))
+and the first word of the log selector. For multi-word vectors we
+use inlinable functions. */
+
+#define BIT(n) ((bitmask_word_t)1 << (n))
 
-/* And these are for multi-word vectors. */
-#define BITWORD(n) (      (n) / BITWORDSIZE)
-#define BITMASK(n) (1U << (n) % BITWORDSIZE)
+#define BITWORD(n) (    (n) / BITWORDSIZE)
+#define BITMASK(n) (BIT((n) % BITWORDSIZE))
+
+/* Debugging control */
 
-#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))
+#define DEBUG_SELECTOR_SIZE		(BITWORD(debug_chan_count) + 1)
 
-/* 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. */
+static inline bitmask_word_t bit_test(bitmask_word_t *, unsigned);
+extern bitmask_word_t * debug_selector;   /* Debugging bits */
+#define DEBUG_BIT(n)	(debug_selector && bit_test(debug_selector, n))
+#define ANY_DEBUG	DEBUG_BIT(BIT_TABLE_IDX_NONZERO)
 
-#define BIT_TABLE(T,name) { US #name, T##i_##name }
+extern BOOL    is_debug(const uschar *);
+#define IS_DEBUG(list)	(ANY_DEBUG && is_debug(US # list))
+#define DEBUG(list)	if (IS_DEBUG(list))
+#define HDEBUG(list)	if (host_checking || IS_DEBUG(list))
+
+#define EARLY_DEBUG(x, fmt, ...) \
+  if (debug_fd >= 0) \
+    { DEBUG(x) debug_printf_indent(fmt, __VA_ARGS__); } \
+  else if (debug_startup) \
+    fprintf(stderr, "%s", string_sprintf(fmt, __VA_ARGS__));
 
 /* IOTA allows us to keep an implicit sequential count, like a simple enum,
 but we can have sequentially numbered identifiers which are not declared
@@ -359,75 +365,33 @@ 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 = (int)BIT(Di_##name)
-
-enum {
-  Di_all        = -1,
-  Di_v          = 0,
-  Di_local_scan = 1,
-
-  Di_iota = IOTA_INIT(2),
-  DEBUG_BIT(acl),		/* 2 */
-  DEBUG_BIT(auth),
-  DEBUG_BIT(deliver),
-  DEBUG_BIT(dns),
-  DEBUG_BIT(dnsbl),
-  DEBUG_BIT(exec),		/* 7 */
-  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),		/* 15 */
-  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),		/* 23 */
-  DEBUG_BIT(retry),
-  DEBUG_BIT(rewrite),
-  DEBUG_BIT(route),
-  DEBUG_BIT(timestamp),
-  DEBUG_BIT(tls),
-  DEBUG_BIT(transport),
-  DEBUG_BIT(uid),
-  DEBUG_BIT(verify),		/* 31 */
-};
-
-/* Multi-bit debug masks */
+/* Options bits for debugging.
+Because of the history of debug within Exim, we have to generate various
+meta-info bits.  Also, we want to be able to determine quickly when debug
+is not enabled (that being the common case).  On the other hand we want to
+have many individually selectable debug channels; possibly more than there
+are bits in a single word. So we use a multiword array of bits, and reserve
+some in the first word for the meta-info. One of those is a single bit OR
+of all the channel bits, giving us the fast test. Code testing the specific
+channel enables is then out-of-line and can be slow.  We use the channel
+names in the sourcecode, converting to channel numbers to do the test against
+the array of bits. */
+
+/* Special bits used for debug and logging control. These are summarizing sets
+of enabled channels, and are in the first word of the selector array for quick
+access. */
+
+#define BIT_TABLE_IDX_ALL	0	/* code for nearly-all-bits-set */
+#define BIT_TABLE_IDX_NONZERO	1	/* at least one bit is set */
+#define BIT_TABLE_IDX_NONVERB	2	/* a bit apart from "v" is set */
+#define BIT_TABLE_IDX_IS_ANY	3	/* a bit apart from "v'-like ones is set */
+#define BIT_TABLE_IDX_USABLE	4	/* first named bit */
+
+#define BITMASK_IDX_TO_BIT(idx) ((bitmask_word_t)1 << (idx))
+#define BIT_TABLE_BIT(class, name) \
+			class##i_##name = IOTA(class##i_iota), \
+			class##_##name = BITMASK_IDX_TO_BIT(class##i_##name)
 
-#define D_all                        0xffffffff
-
-#define D_any                        (D_all & \
-                                       ~(D_v           | \
-					 D_noutf8      | \
-                                         D_pid         | \
-                                         D_timestamp)  )
-
-#define D_default                    (0xffffffff & \
-                                       ~(D_expand      | \
-                                         D_filter      | \
-                                         D_interface   | \
-                                         D_load        | \
-                                         D_local_scan  | \
-                                         D_memory      | \
-					 D_noutf8      | \
-                                         D_pid         | \
-                                         D_timestamp   | \
-                                         D_resolver))
 
 /* Bits for debug triggers */
 
@@ -439,14 +403,28 @@ enum {
 /* 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().
+declarations. The "all" name string is recognized specially by decode_bits().
 Add also to log_options[] when creating new ones. */
 
-#define LOG_BIT(name) Li_##name = IOTA(Li_iota), L_##name = BIT(Li_##name)
+/*XXX The use of this facility in calls to log_write() forces the presence and
+maintenance of this separate table.  It would be good to lose it.  Why cannot
+the LOGWRITE() macro be tested at the call point?
+- currently we debug-output the log call even when a requested log channel
+  is not enabled. A naive replacement would lose that.
+- the fn arg only controls LOG_MAIN log output, not LOG_REJECT or LOG_PANIC
+  - are there such cases?
+    - receive.c 2359, 3330
+    - smtp_in.c 2620, 3706, 5088, 5136
+    - all main+reject
+    - though the docs say only "a log line is written", so a quiet behaviour
+      change could be argued for
+*/
+
+#define LOG_BIT(name) BIT_TABLE_BIT(L, name)
 
-enum logbit {
-  Li_all = -1,
+/* Bit numbers used by calls to log_write() */
 
+enum logwrite_bit {
   Li_iota = IOTA_INIT(0),
   LOG_BIT(address_rewrite),
   LOG_BIT(all_parents),
@@ -455,7 +433,7 @@ enum logbit {
   LOG_BIT(dnslist_defer),
   LOG_BIT(etrn),
   LOG_BIT(host_lookup_failed),
-  LOG_BIT(lost_incoming_connection),
+  LOG_BIT(lost_incoming_connection),	/* 7 */
   LOG_BIT(queue_run),
   LOG_BIT(retry_defer),
   LOG_BIT(size_reject),
@@ -463,55 +441,82 @@ enum logbit {
   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_connection_id,
-  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_msg_id,
-  Li_msg_id_created,
-  Li_outgoing_interface,
-  Li_outgoing_port,
-  Li_pid,
-  Li_pipelining,
-  Li_protocol_detail,
-  Li_proxy,
-  Li_queue_time,
-  Li_queue_time_exclusive,
-  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_on_connect,
-  Li_tls_peerdn,
-  Li_tls_resumption,
-  Li_tls_sni,
-  Li_unknown_in_list,
-
-  log_selector_size = BITWORD(Li_unknown_in_list) + 1
+  LOG_BIT(smtp_syntax_error),		/* 15 */
 };
 
-#define LOGGING(opt) BIT_TEST(log_selector, log_selector_size, Li_##opt)
+/* Bit numbers used by the LOGGING() macro */
+
+enum logging_test_bit {	/* names matching log_channels[] */
+  Lt_all = BIT_TABLE_IDX_ALL,		/* 0 */
+
+  Lt_8bitmime = BIT_TABLE_IDX_USABLE,	/* 4 */
+  Lt_acl_warn_skipped,
+  Lt_address_rewrite,
+  Lt_all_parents,			/* 7 */
+  Lt_arguments,
+  Lt_connection_id,
+  Lt_connection_reject,
+  Lt_delay_delivery,
+  Lt_deliver_time,
+  Lt_delivery_size,
+  Lt_dkim,
+  Lt_dkim_verbose,			/* 15 */
+  Lt_dmarc,
+  Lt_dmarc_verbose,
+  Lt_dnslist_defer,
+  Lt_dnssec,
+  Lt_dsn,
+  Lt_etrn,
+  Lt_host_lookup_failed,
+  Lt_ident_timeout,
+  Lt_incoming_interface,
+  Lt_incoming_port,
+  Lt_lost_incoming_connection,
+  Lt_millisec,
+  Lt_msg_id,
+  Lt_msg_id_created,
+  Lt_outgoing_interface,
+  Lt_outgoing_port,			/* 31 */
+  Lt_pid,
+  Lt_pipelining,
+  Lt_protocol_detail,
+  Lt_proxy,
+  Lt_queue_run,
+  Lt_queue_time,
+  Lt_queue_time_exclusive,
+  Lt_queue_time_overall,
+  Lt_receive_time,
+  Lt_received_recipients,
+  Lt_received_sender,
+  Lt_rejected_header,
+  Lt_retry_defer,
+  Lt_return_path_on_delivery,
+  Lt_sender_on_delivery,
+  Lt_sender_verify_fail,		/* 47 */
+  Lt_size_reject,
+  Lt_skip_delivery,
+  Lt_smtp_confirmation,
+  Lt_smtp_connection,
+  Lt_smtp_incomplete_transaction,
+  Lt_smtp_mailauth,
+  Lt_smtp_no_mail,
+  Lt_smtp_protocol_error,
+  Lt_smtp_syntax_error,
+  Lt_spf,
+  Lt_spf_verbose,
+  Lt_subject,
+  Lt_tls_certificate_verified,
+  Lt_tls_cipher,
+  Lt_tls_on_connect,
+  Lt_tls_peerdn,			/* 63 */
+  Lt_tls_resumption,
+  Lt_tls_sni,
+  Lt_unknown_in_list,
+
+  log_selector_size = BITWORD(Lt_unknown_in_list) + 1
+};
+
+#define LOGGING(opt) bit_test(log_selector, Lt_##opt)
 
 /* Private error numbers for delivery failures, set negative so as not
 to conflict with system errno values.  Take care to maintain the string
@@ -567,20 +572,26 @@ table exim_errstrings[] in log.c */
 #define ERRNO_AUTHPROB       (-48)   /* Authenticator "other" failure */
 #define ERRNO_UTF8_FWD       (-49)   /* target not supporting SMTPUTF8 */
 #define ERRNO_HOST_IS_LOCAL  (-50)   /* Transport refuses to talk to localhost */
-#define ERRNO_TAINT          (-51)   /* Transport refuses to talk use tainted filename */
+#define ERRNO_PASSONE        (-51)   /* first-pass-only routing */
+#define ERRNO_NOROUTER       (-52)   /* ran out of routers */
+#define ERRNO_ROUTERDEFER    (-53)   /* redirect router :defer: */
+#define ERRNO_ROUTERFAIL     (-54)   /* redirect router :fail: */
+#define ERRNO_MXDEFER	     (-55)   /* missing MX, defer requested */
+#define ERRNO_TPTLIMIT       (-57)   /* transport limit */
+#define ERRNO_TAINT          (-58)   /* Transport refuses to use tainted filename */
 
 /* These must be last, so all retry deferments can easily be identified */
 
-#define ERRNO_RETRY_BASE     (-52)   /* Base to test against */
-#define ERRNO_RRETRY         (-52)   /* Not time for routing */
+#define ERRNO_RETRY_BASE     (-59)   /* Base to test against */
+#define ERRNO_RRETRY         (-59)   /* Not time for routing */
 
-#define ERRNO_WARN_BASE      (-53)   /* Base to test against */
-#define ERRNO_LRETRY         (-53)   /* Not time for local delivery */
-#define ERRNO_HRETRY         (-54)   /* Not time for any remote host */
-#define ERRNO_LOCAL_ONLY     (-55)   /* Local-only delivery */
-#define ERRNO_QUEUE_DOMAIN   (-56)   /* Domain in queue_domains */
-#define ERRNO_TRETRY         (-57)   /* Transport concurrency limit */
-#define ERRNO_EVENT	     (-58)   /* Event processing request alternate response */
+#define ERRNO_WARN_BASE      (-60)   /* Base to test against */
+#define ERRNO_LRETRY         (-60)   /* Not time for local delivery */
+#define ERRNO_HRETRY         (-61)   /* Not time for any remote host */
+#define ERRNO_LOCAL_ONLY     (-62)   /* Local-only delivery */
+#define ERRNO_QUEUE_DOMAIN   (-63)   /* Domain in queue_domains */
+#define ERRNO_TRETRY         (-64)   /* Transport concurrency limit */
+#define ERRNO_EVENT	     (-65)   /* Event processing request alternate response */
 
 
 
@@ -706,7 +717,8 @@ can be easily tested as a group. That is the only use of opt_bool_last. */
 enum { opt_bit = 32, opt_bool_verify, opt_bool_set, opt_expand_bool,
   opt_bool_last,
   opt_rewrite, opt_timelist, opt_uid, opt_gid, opt_uidlist, opt_gidlist,
-  opt_expand_uid, opt_expand_gid, opt_func, opt_void, opt_module };
+  opt_expand_uid, opt_expand_gid, opt_func, opt_void,
+  opt_lookup_module, opt_misc_module };
 
 /* 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
@@ -822,8 +834,11 @@ local_scan.h */
 #define LOG_CONFIG_FOR  (256+128) /* Add " for" instead of ":\n" */
 #define LOG_CONFIG_IN   (512+128) /* Add " in line x[ of file y]" */
 
-/* and for debug_bits() logging action control: */
-#define DEBUG_FROM_CONFIG       0x0001
+/* flags for decode_bits */
+
+#define DCB_LOG		0x0001
+#define DCB_DEBUG	0x0002
+#define DCB_FROM_CONFIG	0x0003
 
 /* SMTP command identifiers for the smtp_connection_had field that records the
 most recent SMTP commands. SCH_NONE is "empty".  The smtp_names array must have
@@ -908,6 +923,7 @@ enum {
 
 /* Flags for recipient_block, used in DSN support */
 
+#define rf_notify_unset		0x00
 #define rf_dsnlasthop           0x01  /* Do not propagate DSN any further */
 #define rf_notify_never         0x02  /* NOTIFY= settings */
 #define rf_notify_success       0x04
@@ -1078,6 +1094,7 @@ enum { FILTER_UNSET, FILTER_FORWARD, FILTER_EXIM, FILTER_SIEVE };
 #define UTF8_RIGHT_TRIANGLE	"\xE2\x96\xB6"
 #define UTF8_LIGHT_SHADE	"\xE2\x96\x91"
 #define UTF8_L_ARROW_HOOK	"\xE2\x86\xA9"
+#define UTF8_COMB_BRIDGE_BELOW	"\xCC\xAA"
 
 
 /* Options on tls_close */
@@ -1096,10 +1113,10 @@ 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 \
+    ANY_DEBUG \
     ? (sigalarm_setter = CUS __FUNCTION__, alarm(seconds)) : alarm(seconds);
 # define ALARM_CLR(seconds) \
-    debug_selector & D_any \
+    ANY_DEBUG \
     ? (sigalarm_setter = NULL, alarm(seconds)) : alarm(seconds);
 #endif
 
@@ -1219,7 +1236,7 @@ When doing en extended loop of matching, release store periodically. */
 /* Debug an option access. Use for non-list ones about to be expanded
 (lists have their own debugging, under D_list). */
 #define GET_OPTION(name) \
-  DEBUG(D_expand) debug_printf_indent("try option '" name "'\n");
+  DEBUG(expand) debug_printf_indent("try option '" name "'\n");
 
 
 #ifdef EXPERIMENTAL_SRV_SMTPS
diff --git a/src/src/malware.c b/src/src/malware.c
index 3effeab41..b07a5214a 100644
--- a/src/src/malware.c
+++ b/src/src/malware.c
@@ -3,7 +3,7 @@
 *************************************************/
 
 /*
- * Copyright (c) The Exim Maintainers 2015 - 2025
+ * Copyright (c) The Exim Maintainers 2015 - 2026
  * Copyright (c) Tom Kistner <tom@duncanthrax.net> 2003 - 2015
  * License: GPL
  * SPDX-License-Identifier: GPL-2.0-or-later
@@ -15,33 +15,9 @@
 #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
@@ -62,33 +38,9 @@ static struct scan
   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
@@ -147,32 +99,6 @@ typedef struct 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 pcre2_code * 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 pcre2_code * 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 pcre2_code * kav_re_sus = NULL;
-static const pcre2_code * 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(.*)";
@@ -182,33 +108,10 @@ static const pcre2_code * ava_re_virus = NULL;
 static const pcre2_code * 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 pcre2_code * fprot6d_re_error = NULL;
-static const pcre2_code * 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
@@ -236,13 +139,13 @@ for (; *p; ++p)
 static inline int
 malware_panic_defer(const uschar * str)
 {
-log_write(0, LOG_MAIN|LOG_PANIC, "malware acl condition: %s", str);
+log_write(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);
+log_write(LOG_MAIN, "malware acl condition: %s", str);
 return DEFER;
 }
 /* --- m_*_defer --- */
@@ -258,7 +161,7 @@ static inline int
 m_panic_defer_3(struct scan * scanent, const uschar * hostport,
   const uschar * str, int fd_to_close)
 {
-DEBUG(D_acl) debug_print_socket(fd_to_close);
+DEBUG(acl) debug_print_socket(fd_to_close);
 (void) close(fd_to_close);
 return m_panic_defer(scanent, hostport, str);
 }
@@ -334,7 +237,7 @@ if (!(list_ele = string_nextinlist(list, sep, NULL, 0)))
   *errstr = US listerr;
 else
   {
-  DEBUG(D_acl) debug_printf_indent("%15s%10s'%s'\n", "", "RE: ",
+  DEBUG(acl) debug_printf_indent("%15s%10s'%s'\n", "", "RE: ",
     string_printing(list_ele));
   cre = m_pcre_compile(CUS list_ele, cacheable, errstr);
   }
@@ -358,6 +261,8 @@ uschar * p = buffer;
 ssize_t rcv;
 BOOL ok = FALSE;
 
+*buffer = '\0';
+
 if (!fd_ready(fd, tmo))
   return -2;
 
@@ -373,7 +278,7 @@ while ((rcv = read(fd, p, 1)) > 0)
   }
 if (!ok)
   {
-  DEBUG(D_acl)
+  DEBUG(acl)
     {
     debug_printf_indent("Malware scan: read %s (%s)\n",
 		rcv==0 ? "EOF" : "error", strerror(errno));
@@ -383,153 +288,10 @@ if (!ok)
   }
 *p = '\0';
 
-DEBUG(D_acl) debug_printf_indent("Malware scan: read '%s'\n", buffer);
+DEBUG(acl) debug_printf_indent("Malware scan: read '%s'\n", buffer);
 return p - buffer;
 }
 
-/* return TRUE iff size as requested */
-#ifndef DISABLE_MAL_DRWEB
-static BOOL
-recv_len(int sock, void * buf, int size, time_t tmo)
-{
-return fd_ready(sock, tmo)
-  ? recv(sock, buf, size, 0) == size
-  : FALSE;
-}
-#endif
-
-
-
-#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, time_t 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);
-  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,
-  time_t 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)
@@ -631,7 +393,7 @@ if (*av_scanner == '$')
 	 string_sprintf("av_scanner starts with $, but expansion failed: %s",
 	 expand_string_message));
 
-  DEBUG(D_acl)
+  DEBUG(acl)
     debug_printf_indent("Expanded av_scanner global: %s\n", av_scanner_work);
   /* disable result caching in this case */
   malware_name = NULL;
@@ -656,7 +418,7 @@ if (!malware_ok)
 	scanner_name));
     if (strcmpic(scanner_name, US scanent->name) != 0)
       continue;
-    DEBUG(D_acl) debug_printf_indent("Malware scan:  %s tmo=%s\n",
+    DEBUG(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)))
@@ -664,7 +426,7 @@ if (!malware_ok)
     if (scanent->conn == MC_NONE)
       break;
 
-    DEBUG(D_acl) debug_printf_indent("%15s%10s%s\n", "", "socket: ", scanner_options);
+    DEBUG(acl) debug_printf_indent("%15s%10s%s\n", "", "socket: ", scanner_options);
     switch(scanent->conn)
     {
     case MC_TCP:
@@ -683,575 +445,6 @@ if (!malware_ok)
 
   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, * linebuffer, * scanrequest;
-      uschar av_buffer[1024];
-
-      if ((!fprot6d_re_virus && !(fprot6d_re_virus = m_pcre_compile(fprot6d_re_virus_str, FALSE, &errstr)))
-        || (!fprot6d_re_error && !(fprot6d_re_error = m_pcre_compile(fprot6d_re_error_str, FALSE, &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);
-
-      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 = exim_open2(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 = store_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);
-	  store_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)
-	  {
-	  store_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);
-	  }
-	store_free(drweb_fbuf);
-	}
-      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)
-	{
-	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, FALSE, &errstr);
-
-	/* read and concatenate virus names into one string */
-	for (int i = 0; i < drweb_vnum; i++)
-	  {
-	  pcre2_match_data * md = pcre2_match_data_create(2, pcre_gen_ctx);
-
-	  /* 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);
-
-	  /* assume tainted, since it is external input */
-	  tmpbuf = store_get(drweb_slen, GET_TAINTED);
-
-	  /* 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 = pcre2_match(drweb_re, (PCRE2_SPTR)tmpbuf, PCRE2_ZERO_TERMINATED,
-				0, 0, md, pcre_gen_mtc_ctx);
-	  if (result >= 2)
-	    {
-	    PCRE2_SIZE * ovec = pcre2_get_ovector_pointer(md);
-
-	    if (i==0)	/* the first name we just copy to malware_name */
-	      g = string_catn(NULL, US ovec[2], ovec[3] - ovec[2]);
-
-	    else	/* concatenate each new virus name to previous */
-	      {
-	      g = string_catn(g, US"/", 1);
-	      g = string_catn(g, US ovec[2], ovec[3] - ovec[2]);
-	      }
-	    }
-	  /* pcre2_match_data_free(md);	gen ctx needs no free */
-	  }
-	  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, 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);
-	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 (int 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, FALSE, &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)) < 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 pcre2_code *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, FALSE, &errstr);
-	      kav_re = kav_re_sus;
-	      }
-	    else
-	      {
-	      if (!kav_re_inf) kav_re_inf = m_pcre_compile(kav_re_inf_str, FALSE, &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 ---------------------------------- */
       {
@@ -1297,7 +490,7 @@ badseek:  err = errno;
       /* redirect STDERR too */
       commandline = string_sprintf("%s 2>&1", commandline);
 
-      DEBUG(D_acl) debug_printf_indent("Malware scan: issuing %s scan [%s]\n",
+      DEBUG(acl) debug_printf_indent("Malware scan: issuing %s scan [%s]\n",
 	      scanner_name, commandline);
 
       /* store exims signal handlers */
@@ -1385,53 +578,6 @@ badseek:  err = errno;
       }	/* 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)) <= 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 ----------------------------------- */
       {
@@ -1446,7 +592,7 @@ badseek:  err = errno;
 * the TCP-connected daemon is actually local; otherwise we use zINSTREAM
 * See Exim bug 926 for details.  */
 
-      uschar *p, *vname, *result_tag;
+      uschar *p, *vname;
       int bread=0;
       uschar av_buffer[1024];
       uschar *hostname = US"";
@@ -1575,7 +721,7 @@ badseek:  err = errno;
 	  int i = random_number(num_servers);
 	  clamd_address * cd = cv[i];
 
-	  DEBUG(D_acl) debug_printf_indent("trying server name %s, port %u\n",
+	  DEBUG(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
@@ -1637,7 +783,7 @@ badseek:  err = errno;
 	chunks, <n> a 4-byte number (network order), terminated by a zero-length
 	chunk. We only send one chunk. */
 
-	DEBUG(D_acl) debug_printf_indent(
+	DEBUG(acl) debug_printf_indent(
 	    "Malware scan: issuing %s new-style remote scan (zINSTREAM)\n",
 	    scanner_name);
 
@@ -1747,7 +893,7 @@ badseek:  err = errno;
 	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(
+	DEBUG(acl) debug_printf_indent(
 	    "Malware scan: issuing %s local-path scan [%s]\n",
 	    scanner_name, scanner_options);
 
@@ -1811,7 +957,7 @@ badseek:  err = errno;
       p = av_buffer + Ustrlen(av_buffer) - 1;
       if (*p == '\n') *p = '\0';
 
-      DEBUG(D_acl) debug_printf_indent("Malware response: %s\n", av_buffer);
+      DEBUG(acl) debug_printf_indent("Malware response: %s\n", av_buffer);
 
       while (isspace(*--p) && (p > av_buffer))
 	*p = '\0';
@@ -1823,18 +969,30 @@ badseek:  err = errno;
 		  "ClamAV returned malformed result (missing colon): %s",
 		  av_buffer));
 
+      /* do the OK check first and bail early if we can */
+      if (Ustrcmp(++p, " OK") == 0)
+	{
+	/* Everything should be OK */
+	malware_name = NULL;
+	DEBUG(acl) debug_printf_indent("Malware not found\n");
+        break;
+	}
+
       /* strip filename */
-      while (*p && isspace(*++p)) /**/;
+      while (isspace(*p)) 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)
+      /* treat lack of space as unparseable error */
+      if ( !p )
+	return m_panic_defer(scanent, CUS callout_address,
+	  string_sprintf("unparseable response from ClamAV: {%s}", av_buffer));
+
+      if (Ustrcmp(p, " 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
@@ -1848,20 +1006,13 @@ badseek:  err = errno;
 	    *p = '\0';
 	  }
 	malware_name = string_copy(vname);
-	DEBUG(D_acl) debug_printf_indent("Malware found, name %q\n", malware_name);
+	DEBUG(acl) debug_printf_indent("Malware found, name %q\n", malware_name);
 
 	}
-      else if (Ustrcmp(result_tag, "ERROR") == 0)
+      else if (Ustrcmp(p, " 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));
@@ -1899,7 +1050,7 @@ badseek:  err = errno;
       }
       else
 	sockline_scanner = sockline_scanner_default;
-      DEBUG(D_acl) debug_printf_indent("%15s%10s'%s'\n", "", "cmdline: ",
+      DEBUG(acl) debug_printf_indent("%15s%10s'%s'\n", "", "cmdline: ",
 	string_printing(sockline_scanner));
 
       /* find scanner output trigger */
@@ -1916,7 +1067,7 @@ badseek:  err = errno;
 
       /* prepare scanner call - security depends on expansions check above */
       commandline = string_sprintf( CS sockline_scanner, eml_filename);
-      DEBUG(D_acl) debug_printf_indent("%15s%10s'%s'\n", "", "expanded: ",
+      DEBUG(acl) debug_printf_indent("%15s%10s'%s'\n", "", "expanded: ",
 	string_printing(commandline));
 
       /* Pass the command string to the socket */
@@ -1936,7 +1087,7 @@ badseek:  err = errno;
 		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: ",
+      DEBUG(acl) debug_printf_indent("%15s%10s'%s'\n", "", "answer: ",
 	string_printing(linebuffer));
 
       /* try trigger match */
@@ -1944,7 +1095,7 @@ badseek:  err = errno;
 	{
 	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: ",
+	DEBUG(acl) debug_printf_indent("%15s%10s'%s'\n", "", "name: ",
 	  string_printing(malware_name));
 	}
       else /* no virus found */
@@ -1953,41 +1104,6 @@ badseek:  err = errno;
       }
 #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 ----------------------------------- */
       {
@@ -2087,19 +1203,19 @@ badseek:  err = errno;
 		{
                 if (Ustrcmp(scanrequest, "pass_unscanned") == 0)
                   {
-                  DEBUG(D_acl) debug_printf_indent("pass unscanned files as clean\n");
+                  DEBUG(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);
+		DEBUG(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);
+		DEBUG(acl) debug_printf_indent("send to avast REQUEST: SCAN %s\n", eml_dir);
 		}
 
 	      /* send config-cmd or scan-request to socket */
@@ -2128,7 +1244,7 @@ badseek:  err = errno;
               if ((malware_name = m_pcre_exec(ava_re_virus, buf)))
                 {
                 unescape(malware_name);
-                DEBUG(D_acl)
+                DEBUG(acl)
                   debug_printf_indent("unescaped malware name: '%s'\n", malware_name);
                 break;
                 }
@@ -2138,23 +1254,23 @@ badseek:  err = errno;
                 if ((malware_name = m_pcre_exec(ava_re_error, buf)))
                   {
                   unescape(malware_name);
-                  DEBUG(D_acl)
+                  DEBUG(acl)
                     debug_printf_indent("unescaped error message: '%s'\n", malware_name);
                   break;
                   }
                 }
               else if (regex_match(ava_re_error, buf, slen, NULL))
                 {
-                log_write(0, LOG_MAIN, "internal scanner error (ignored): %s", buf);
+                log_write(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);
+              DEBUG(acl) debug_printf("avast response not handled: '%s'\n", buf);
 
 	      goto endloop;
 
-	    default:	log_write(0, LOG_PANIC, "%s:%d:%s: should not happen",
+	    default:	log_write(LOG_PANIC, "%s:%d:%s: should not happen",
 			    __FILE__, __LINE__, __FUNCTION__);
 	    }
 	  }
@@ -2167,7 +1283,7 @@ badseek:  err = errno;
       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");
+      DEBUG(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",
@@ -2189,7 +1305,7 @@ badseek:  err = errno;
 /* 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(
+  DEBUG(acl) debug_printf_indent(
       "Matched regex to malware [%s] [%s]\n", malware_re, malware_name);
   return OK;
   }
@@ -2253,7 +1369,7 @@ 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);
+receive_add_recipient(US"malware-victim@example.net", -1, rf_notify_unset,NULL);
 f.enable_dollar_recipients = TRUE;
 
 ret = malware_internal(US"*", TRUE, eml_filename, 0);
@@ -2279,20 +1395,6 @@ malware_init(void)
 if (!malware_default_re)
   malware_default_re = regex_must_compile(malware_regex_default, MCS_NOFLAGS, TRUE);
 
-#ifndef DISABLE_MAL_DRWEB
-if (!drweb_re)
-  drweb_re = regex_must_compile(drweb_re_str, MCS_NOFLAGS, TRUE);
-#endif
-#ifndef DISABLE_MAL_FSECURE
-if (!fsec_re)
-  fsec_re = regex_must_compile(fsec_re_str, MCS_NOFLAGS, TRUE);
-#endif
-#ifndef DISABLE_MAL_KAV
-if (!kav_re_sus)
-  kav_re_sus = regex_must_compile(kav_re_sus_str, MCS_NOFLAGS, TRUE);
-if (!kav_re_inf)
-  kav_re_inf = regex_must_compile(kav_re_inf_str, MCS_NOFLAGS, TRUE);
-#endif
 #ifndef DISABLE_MAL_AVAST
 if (!ava_re_clean)
   ava_re_clean = regex_must_compile(ava_re_clean_str, MCS_NOFLAGS, TRUE);
@@ -2301,12 +1403,6 @@ if (!ava_re_virus)
 if (!ava_re_error)
   ava_re_error = regex_must_compile(ava_re_error_str, MCS_NOFLAGS, TRUE);
 #endif
-#ifndef DISABLE_MAL_FFROT6D
-if (!fprot6d_re_error)
-  fprot6d_re_error = regex_must_compile(fprot6d_re_error_str, MCS_NOFLAGS, TRUE);
-if (!fprot6d_re_virus)
-  fprot6d_re_virus = regex_must_compile(fprot6d_re_virus_str, MCS_NOFLAGS, TRUE);
-#endif
 }
 
 
diff --git a/src/src/match.c b/src/src/match.c
index c843b2420..20d19e18a 100644
--- a/src/src/match.c
+++ b/src/src/match.c
@@ -41,7 +41,7 @@ is_tainted_metadata(const uschar * s)
 {
 /* Not enforcing for now, only logging; will enforce in a future release */
 if (is_tainted(s))
-  log_write(0, LOG_MAIN|LOG_PANIC,
+  log_write(LOG_MAIN|LOG_PANIC,
 	    "attempt to use tainted list metadata %s", s);
 return FALSE;
 }
@@ -286,7 +286,7 @@ li = search_findtype_partial(pattern, &partial, &affix, &affixlen,
   &starflags, &opts);
 *semicolon = ';';
 if (!li)
-  log_write_die(0, LOG_MAIN, "%s", search_error_message);
+  log_write_die(LOG_MAIN, "%s", search_error_message);
 
 /* Partial matching is not appropriate for certain lookups (e.g. when looking
 up user@domain for sender rejection). There's a flag to disable it. */
@@ -302,7 +302,7 @@ for; partial matching is all handled inside search_find(). Note that there is
 no search_close() because of the caching arrangements. */
 
 if (!(handle = search_open(filename, li, 0, NULL, NULL)))
-  log_write_die(0, LOG_MAIN, "%s", search_error_message);
+  log_write_die(LOG_MAIN, "%s", search_error_message);
 result = search_find(handle, filename, keyquery, partial, affix, affixlen,
   starflags, &expand_setup, opts);
 
@@ -402,7 +402,7 @@ if (Uskip_whitespace(&list) == '<')
   uschar c = *s == '\\' ? string_interpret_escape(&s) : *s;
   if (ispunct(c) || iscntrl(c))
     {
-    DEBUG(D_lists)
+    DEBUG(lists)
       {
       uschar s[2] = {0}; *s = c;
       debug_printf_indent("list separator: '%W'\n", s);
@@ -477,7 +477,7 @@ BOOL textonly_re;
 
 /* Save time by not scanning for the option name when we don't need it. */
 
-HDEBUG(D_any)
+HDEBUG(any)
   {
   const uschar * listname = readconf_find_option(listptr);
   if (*listname) ot = string_sprintf("%s in %s?", name, listname);
@@ -487,7 +487,7 @@ HDEBUG(D_any)
 
 if (!*listptr)
   {
-  HDEBUG(D_lists)
+  HDEBUG(lists)
     if (ot) debug_printf_indent("%s no (option unset)\n", ot);
     else    debug_printf_indent("%s not in empty list (option unset? cannot trace name)\n", name);
   return FAIL;
@@ -526,11 +526,11 @@ else
     {
     if (f.expand_string_forcedfail)
       {
-      HDEBUG(D_lists) debug_printf_indent("expansion of %q forced failure: "
+      HDEBUG(lists) debug_printf_indent("expansion of %q forced failure: "
         "assume not in this list\n", *listptr);
       return FAIL;
       }
-    log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand %q while checking "
+    log_write(LOG_MAIN|LOG_PANIC, "failed to expand %q while checking "
       "a list: %s", *listptr, expand_string_message);
     return DEFER;
     }
@@ -551,7 +551,7 @@ if (textonly_re) switch (type)
 /* For an unnamed list, use the expanded version in comments */
 #define LIST_LIMIT_PR 2048
 
-HDEBUG(D_any) if (!ot)
+HDEBUG(any) if (!ot)
   {	/* We failed to identify an option name, so give the list text */
   int n, m;
   gstring * g = string_fmt_append(NULL, "%s in \"%n%.*s%n\"",
@@ -561,7 +561,7 @@ HDEBUG(D_any) if (!ot)
   gstring_release_unused(g);
   ot = string_from_gstring(g);
   }
-HDEBUG(D_lists)
+HDEBUG(lists)
   {
   debug_printf_indent("%s\n", ot);
   expand_level++;
@@ -574,7 +574,7 @@ while ((sss = string_nextinlist(&list, &sep, NULL, 0)))
   {
   uschar * ss = sss;
 
-  HDEBUG(D_lists) debug_printf_indent("list element: %W\n", ss);
+  HDEBUG(lists) debug_printf_indent("list element: %W\n", ss);
 
   /* Address lists may contain +caseful, to restore caseful matching of the
   local part. We have to know the layout of the control block, unfortunately.
@@ -682,7 +682,7 @@ while ((sss = string_nextinlist(&list, &sep, NULL, 0)))
       const uschar * listname = readconf_find_option(listptr);
       if (!*listname)
         listname = string_sprintf("%q", *listptr);
-      log_write_die(0, LOG_MAIN, "%s",
+      log_write_die(LOG_MAIN, "%s",
         string_open_failed("%s when checking %s", sss, listname));
       goto cppcheck_silencing;
       }
@@ -727,7 +727,7 @@ while ((sss = string_nextinlist(&list, &sep, NULL, 0)))
         {
         case OK:
 	  (void)fclose(f);
-	  HDEBUG(D_lists) debug_printf_indent("%s %s (matched %q in %s)\n",
+	  HDEBUG(lists) debug_printf_indent("%s %s (matched %q in %s)\n",
 	    ot, yield == OK ? "yes" : "no", sss, filename);
 
 	  /* The "pattern" being matched came from the file; we use a stack-local.
@@ -742,14 +742,14 @@ while ((sss = string_nextinlist(&list, &sep, NULL, 0)))
 	    error = string_sprintf("DNS lookup of %s deferred", ss);
 	  if (ignore_defer)
 	    {
-	    HDEBUG(D_lists)
+	    HDEBUG(lists)
 	      debug_printf_indent("%s: item ignored by +ignore_defer\n", error);
 	    break;
 	    }
 	  (void)fclose(f);
 	  if (!include_defer)
 	    goto DEFER_RETURN;
-	  log_write(0, LOG_MAIN, "%s: accepted by +include_defer", error);
+	  log_write(LOG_MAIN, "%s: accepted by +include_defer", error);
 	  goto OK_RETURN;
 
         /* The ERROR return occurs when checking hosts, when either a forward
@@ -760,21 +760,21 @@ while ((sss = string_nextinlist(&list, &sep, NULL, 0)))
         case ERROR:
 	  if (ignore_unknown)
 	    {
-	    HDEBUG(D_lists) debug_printf_indent(
+	    HDEBUG(lists) debug_printf_indent(
 	      "%s: item ignored by +ignore_unknown\n", error);
 	    }
 	  else
 	    {
-	    HDEBUG(D_lists) debug_printf_indent("%s %s (%s)\n", ot,
+	    HDEBUG(lists) debug_printf_indent("%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);
+		log_write(LOG_MAIN, "list matching forced to fail: %s", error);
 	      goto FAIL_RETURN;
 	      }
-	    log_write(0, LOG_MAIN, "%s: accepted by +include_unknown", error);
+	    log_write(LOG_MAIN, "%s: accepted by +include_unknown", error);
 	    goto OK_RETURN;
 	    }
         }
@@ -801,13 +801,13 @@ while ((sss = string_nextinlist(&list, &sep, NULL, 0)))
     namedlist_block * nb;
     tree_node * t;
 
-    HDEBUG(D_lists)
+    HDEBUG(lists)
       { debug_printf_indent(" start sublist %s\n", ss+1); expand_level += 2; }
 
     if (is_tainted_metadata(ss)) goto BAD_TAINT;
     if (!(t = tree_search(*anchorptr, ss+1)))
       {
-      log_write(0, LOG_MAIN|LOG_PANIC, "unknown named%s list %q",
+      log_write(LOG_MAIN|LOG_PANIC, "unknown named%s list %q",
 	type == MCL_DOMAIN ?    " domain" :
 	type == MCL_HOST ?      " host" :
 	type == MCL_ADDRESS ?   " address" :
@@ -839,7 +839,7 @@ while ((sss = string_nextinlist(&list, &sep, NULL, 0)))
       {
       int res = match_check_list(&(nb->string), 0, anchorptr, &use_cache_bits,
 	      func, arg, type, name, valueptr);
-      HDEBUG(D_lists)
+      HDEBUG(lists)
 	{ expand_level -= 2; debug_printf_indent(" end sublist %s\n", ss+1); }
 
       switch (res)
@@ -879,7 +879,7 @@ while ((sss = string_nextinlist(&list, &sep, NULL, 0)))
 	  p->next = nb->cache_data;
 	  nb->cache_data = p;
 	  if (*valueptr)
-	    HDEBUG(D_lists) debug_printf_indent("data from lookup saved for "
+	    HDEBUG(lists) debug_printf_indent("data from lookup saved for "
 	      "cache for %s: key '%s' value '%s'\n", ss, p->key, *valueptr);
 	  }
 	}
@@ -891,7 +891,7 @@ while ((sss = string_nextinlist(&list, &sep, NULL, 0)))
 
     else
       {
-      HDEBUG(D_lists)
+      HDEBUG(lists)
 	{
 	expand_level -= 2;
 	debug_printf_indent("cached %s match for %s\n",
@@ -909,7 +909,7 @@ while ((sss = string_nextinlist(&list, &sep, NULL, 0)))
 	    *valueptr = p->data;
 	    break;
 	    }
-	HDEBUG(D_lists) debug_printf_indent("cached lookup data = %s\n", *valueptr);
+	HDEBUG(lists) debug_printf_indent("cached lookup data = %s\n", *valueptr);
 	}
       }
 
@@ -918,7 +918,7 @@ while ((sss = string_nextinlist(&list, &sep, NULL, 0)))
 
     if ((bits & (-bits)) == bits)    /* Only one of the two bits is set */
       {
-      HDEBUG(D_lists) debug_printf_indent("%s %s (matched %q%s)\n", ot,
+      HDEBUG(lists) debug_printf_indent("%s %s (matched %q%s)\n", ot,
 	yield == OK ? "yes" : "no", sss, cached);
       goto YIELD_RETURN;
       }
@@ -932,7 +932,7 @@ while ((sss = string_nextinlist(&list, &sep, NULL, 0)))
     switch ((func)(arg, ss, valueptr, &error))
       {
       case OK:
-	HDEBUG(D_lists) debug_printf_indent("%s %s (matched %q)\n", ot,
+	HDEBUG(lists) debug_printf_indent("%s %s (matched %q)\n", ot,
 	  yield == OK ? "yes" : "no", sss);
 	goto YIELD_RETURN;
 
@@ -941,13 +941,13 @@ while ((sss = string_nextinlist(&list, &sep, NULL, 0)))
 	  error = string_sprintf("DNS lookup of %q deferred", ss);
 	if (ignore_defer)
 	  {
-	  HDEBUG(D_lists)
+	  HDEBUG(lists)
 	    debug_printf_indent("%s: item ignored by +ignore_defer\n", error);
 	  break;
 	  }
 	if (include_defer)
 	  {
-	  log_write(0, LOG_MAIN, "%s: accepted by +include_defer", error);
+	  log_write(LOG_MAIN, "%s: accepted by +include_defer", error);
 	  return OK;
 	  }
 	if (!search_error_message) search_error_message = error;
@@ -961,20 +961,20 @@ while ((sss = string_nextinlist(&list, &sep, NULL, 0)))
       case ERROR:
 	if (ignore_unknown)
 	  {
-	  HDEBUG(D_lists) debug_printf_indent(
+	  HDEBUG(lists) debug_printf_indent(
 	    "%s: item ignored by +ignore_unknown\n", error);
 	  }
 	else
 	  {
-	  HDEBUG(D_lists) debug_printf_indent("%s %s (%s)\n", ot,
+	  HDEBUG(lists) debug_printf_indent("%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);
+	      log_write(LOG_MAIN, "list matching forced to fail: %s", error);
 	    return FAIL;
 	    }
-	  log_write(0, LOG_MAIN, "%s: accepted by +include_unknown", error);
+	  log_write(LOG_MAIN, "%s: accepted by +include_unknown", error);
 	  return OK;
 	  }
       }
@@ -984,9 +984,9 @@ while ((sss = string_nextinlist(&list, &sep, NULL, 0)))
 
 /* End of list reached: if the last item was negated yield OK, else FAIL. */
 
-HDEBUG(D_any)
+HDEBUG(any)
   {
-  HDEBUG(D_lists) expand_level--;
+  HDEBUG(lists) expand_level--;
   debug_printf_indent("%s %s (end of list)\n", ot, yield == OK ? "no":"yes");
   }
 return yield == OK ? FAIL : OK;
@@ -995,9 +995,9 @@ return yield == OK ? FAIL : OK;
 
 BAD_TAINT:
 DEFER_RETURN:
-  HDEBUG(D_any)
+  HDEBUG(any)
     {
-    HDEBUG(D_lists) expand_level--;
+    HDEBUG(lists) expand_level--;
     debug_printf_indent("%s list match deferred for %s\n", ot, sss);
     }
   return DEFER;
@@ -1010,7 +1010,7 @@ OK_RETURN:
   yield = OK;
 
 YIELD_RETURN:
-  HDEBUG(D_lists) expand_level--;
+  HDEBUG(lists) expand_level--;
   return yield;
 }
 
@@ -1121,7 +1121,7 @@ const uschar * s;
 uschar * pdomain, * sdomain;
 uschar * value = NULL;
 
-DEBUG(D_lists) debug_printf_indent("address match test: subject=%s pattern=%s\n",
+DEBUG(lists) debug_printf_indent("address match test: subject=%s pattern=%s\n",
   subject, pattern);
 
 /* Find the subject's domain */
@@ -1133,7 +1133,7 @@ empty. Otherwise, a subject with no domain is a serious configuration error. */
 
 if (!sdomain && *subject)
   {
-  log_write(0, LOG_MAIN|LOG_PANIC, "no @ found in the subject of an "
+  log_write(LOG_MAIN|LOG_PANIC, "no @ found in the subject of an "
     "address list match: subject=%q pattern=%q", subject, pattern);
   return FAIL;
   }
@@ -1160,7 +1160,7 @@ but write a panic log entry. However, *@ matching will be honoured. */
 if (*s == ';')
   {
   if (Ustrncmp(pattern, "partial-", 8) == 0)
-    log_write(0, LOG_MAIN|LOG_PANIC, "partial matching is not applicable to "
+    log_write(LOG_MAIN|LOG_PANIC, "partial matching is not applicable to "
       "whole-address lookups: ignored \"partial-\" in %q", pattern);
   return match_check_string(subject, pattern, -1, cb->flags, valueptr);
   }
@@ -1238,7 +1238,7 @@ if (pattern[0] == '@' && pattern[1] == '@')
   /* End of chain loop; panic if too many times */
 
   if (watchdog <= 0)
-    log_write_die(0, LOG_MAIN, "Loop detected in lookup of "
+    log_write_die(LOG_MAIN, "Loop detected in lookup of "
       "local part of %s in %s", subject, pattern);
 
   /* Otherwise the local part check has failed, so the whole match
diff --git a/src/src/mime.c b/src/src/mime.c
index 0197894da..5a09bd74a 100644
--- a/src/src/mime.c
+++ b/src/src/mime.c
@@ -15,7 +15,7 @@
 #include <sys/stat.h>
 
 FILE *mime_stream = NULL;
-uschar *mime_current_boundary = NULL;
+const uschar *mime_current_boundary = NULL;
 
 static mime_header mime_header_list[] = {
   /*	name			namelen		value */
@@ -424,9 +424,9 @@ Return:
  Allocated string with parameter value
 */
 static uschar *
-mime_param_val(uschar ** sp)
+mime_param_val(const uschar ** sp)
 {
-uschar * s = *sp;
+const uschar * s = *sp;
 gstring * val = NULL;
 
 /* debug_printf_indent("   considering paramval '%s'\n", s); */
@@ -445,8 +445,8 @@ while (*s && *s != ';')		/* ; terminates */
 return string_from_gstring(val);
 }
 
-static uschar *
-mime_next_semicolon(uschar * s)
+static const uschar *
+mime_next_semicolon(const uschar * s)
 {
 while (*s && *s != ';')		/* ; terminates */
   if (*s == '"')
@@ -488,19 +488,27 @@ return string_from_gstring(val);
 }
 
 
+#define MIME_MAX_DEPTH 64
+
 int
-mime_acl_check(uschar *acl, FILE *f, struct mime_boundary_context *context,
-    uschar **user_msgptr, uschar **log_msgptr)
+mime_acl_check(uschar * acl, FILE * f, struct mime_boundary_context * context,
+    uschar ** user_msgptr, uschar ** log_msgptr, unsigned depth)
 {
 int rc = OK;
 uschar * header = NULL;
 struct mime_boundary_context nested_context;
 
+if (depth >= MIME_MAX_DEPTH)
+  {
+  log_write(LOG_MAIN, "MIME acl condition fail: excessive nesting depth");
+  return FAIL;
+  }
+
 /* reserve a line buffer to work in.  Assume tainted data. */
 header = store_get(MIME_MAX_HEADER_SIZE+1, GET_TAINTED);
 
 /* Not actually used at the moment, but will be vital to fixing
- * some RFC 2046 nonconformance later... */
+some RFC 2046 nonconformance later... */
 nested_context.parent = context;
 
 /* loop through parts */
@@ -526,7 +534,7 @@ while(1)
     if (!fgets(CS header, MIME_MAX_HEADER_SIZE, f))
       {
       /* Hit EOF or read error. Ugh. */
-      DEBUG(D_acl) debug_printf_indent("MIME: Hit EOF ...\n");
+      DEBUG(acl) debug_printf_indent("MIME: Hit EOF ...\n");
       return rc;
       }
 
@@ -538,12 +546,12 @@ while(1)
       if (Ustrncmp((header+2+Ustrlen(context->boundary)), "--", 2) == 0)
 	{
 	/* END boundary found */
-	DEBUG(D_acl) debug_printf_indent("MIME: End boundary found %s\n",
+	DEBUG(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",
+      DEBUG(acl) debug_printf_indent("MIME: Next part with boundary %s\n",
 	context->boundary);
       break;
       }
@@ -557,14 +565,14 @@ while(1)
 	 mh < mime_header_list + mime_header_list_size;
 	 mh++) if (strncmpic(mh->name, header, mh->namelen) == 0)
       {
-      uschar * p = header + mh->namelen, * p1;
+      const uschar * p = header + mh->namelen, * p1;
 
       /* grab the value (normalize to lower case)
       and copy to its corresponding expansion variable */
 
       for (p1 = p; *p1 != ';' && *p1; p1++) ;
       *mh->value = string_copynlc(p, p1-p);
-      DEBUG(D_acl) debug_printf_indent("MIME: found %s header, value is '%s'\n",
+      DEBUG(acl) debug_printf_indent("MIME: found %s header, value is '%s'\n",
 	mh->name, *mh->value);
 
       if (*(p = p1)) p++;			/* jump past the ; */
@@ -579,7 +587,7 @@ while(1)
 
 	while (*p)
 	  {
-	  DEBUG(D_acl)
+	  DEBUG(acl)
 	    debug_printf_indent("MIME:   considering paramlist '%s'\n", p);
 
 	  /* look for interesting parameters */
@@ -595,13 +603,14 @@ while(1)
 	      if (*p == '*') p++;		/* step over sep chset mark */
 	      if (*p == '=')
 		{
-		uschar * p2;
+		const uschar * p2;
 		p++;				/* step over = */
 		p2 = mime_param_val(&p);	/* p now trailing ; or NUL */
 
 		if (p2 && *p2)			/* p2 is the dequoted value */
 		  {
-		  uschar * err_msg, * fname = p2;
+		  uschar * err_msg;
+		  const uschar * fname = p2;
 		  int slen;
 
 		  /* build up an un-decoded filename over successive
@@ -613,7 +622,7 @@ while(1)
 		    {
 		    if (!mime_filename_charset)
 		      {			/* try for RFC 2231 chset/lang */
-		      uschar * s = p2;
+		      const uschar * s = p2;
 
 		      /* look for a ' in the raw paramval */
 		      while(*s != '\'' && *s) s++;	/* s is 1st ' or NUL */
@@ -629,19 +638,19 @@ while(1)
 			}
 		      }
 
-		    DEBUG(D_acl)
+		    DEBUG(acl)
 		      debug_printf_indent("MIME:    charset %s fname '%s'\n",
 			mime_filename_charset ? mime_filename_charset : US"<NULL>",
 			fname);
 
 		    fname = rfc2231_to_2047(fname, mime_filename_charset,
 						  &slen);
-		    DEBUG(D_acl)
+		    DEBUG(acl)
 		      debug_printf_indent("MIME:    2047-name %s\n", fname);
 
 		    fname = rfc2047_decode(fname, FALSE, NULL, ' ',
 						  NULL, &err_msg);
-		    DEBUG(D_acl) debug_printf_indent(
+		    DEBUG(acl) debug_printf_indent(
 				    "MIME:    plain-name %s\n", fname);
 
 		    if (!fname || Ustrlen(fname) == slen)
@@ -674,7 +683,7 @@ while(1)
 		? rfc2047_decode(p3, check_rfc2047_length, NULL, 32, NULL,
 		    &dummy_errstr)
 		: NULL;
-	      DEBUG(D_acl) debug_printf_indent(
+	      DEBUG(acl) debug_printf_indent(
 		"MIME:  found %s parameter in %s header, value '%s'\n",
 		mp->name, mh->name, *mp->value);
 
@@ -696,7 +705,7 @@ while(1)
 	  if (decoding_failed)
 	    mime_filename = string_from_gstring(mime_fname_rfc2231);
 
-	  DEBUG(D_acl) debug_printf_indent(
+	  DEBUG(acl) debug_printf_indent(
 	    "MIME:  found %s parameter in %s header, value is '%s'\n",
 	    "filename", mh->name, mime_filename);
 	  }
@@ -737,7 +746,7 @@ while(1)
   if (  mime_content_type && nested_context.boundary 
      && Ustrncmp(mime_content_type,"multipart",9) == 0)
     {
-    DEBUG(D_acl)
+    DEBUG(acl)
       debug_printf_indent("MIME: Entering multipart recursion, boundary '%s'\n",
 	nested_context.boundary);
 
@@ -749,7 +758,8 @@ while(1)
       ? MBC_COVERLETTER_ALL
       : MBC_COVERLETTER_ONESHOT;
 
-    rc = mime_acl_check(acl, f, &nested_context, user_msgptr, log_msgptr);
+    rc = mime_acl_check(acl, f, &nested_context, user_msgptr, log_msgptr,
+			depth+1);
     if (rc != OK) break;
     }
   else if (  mime_content_type
@@ -783,7 +793,7 @@ while(1)
     mime_current_boundary = NULL;
     if (!mime_decoded_filename)		/* decoding failed */
       {
-      log_write(0, LOG_MAIN,
+      log_write(LOG_MAIN,
 	   "MIME acl condition warning - could not decode RFC822 MIME part to file.");
       rc = DEFER;
       goto out;
diff --git a/src/src/mime.h b/src/src/mime.h
index d436bcacb..611732490 100644
--- a/src/src/mime.h
+++ b/src/src/mime.h
@@ -20,21 +20,21 @@
 struct mime_boundary_context
 {
   struct mime_boundary_context *parent;
-  unsigned char *boundary;
-  int context;
+  const uschar *		boundary;
+  int				context;
 };
 
 typedef struct mime_header {
-  uschar *  name;
-  int       namelen;
-  uschar ** value;
+  const uschar *	name;
+  int			namelen;
+  const uschar **	value;
 } mime_header;
 
 
 typedef struct mime_parameter {
-  uschar *  name;
-  int       namelen;
-  uschar ** value;
+  const uschar *	name;
+  int			namelen;
+  const uschar **	value;
 } mime_parameter;
 
 /* MIME Anomaly list */
diff --git a/src/src/miscmods/Makefile b/src/src/miscmods/Makefile
index 1e46d4456..1076c6daf 100644
--- a/src/src/miscmods/Makefile
+++ b/src/src/miscmods/Makefile
@@ -14,6 +14,7 @@
 OBJ += dummy.o
 
 all:		miscmods.a $(MODS)
+		$(FE):
 
 miscmods.a:	$(OBJ)
 		@$(RM_COMMAND) -f miscmods.a
@@ -26,9 +27,9 @@ miscmods.a:	$(OBJ)
 		$(FE)$(CC) -c $(CFLAGS) $(INCLUDE) $*.c
 
 .c.so:;         @echo "$(CC) -shared $*.c"
-		$(FE)$(CC) $(SUPPORT_$*_INCLUDE) $(SUPPORT_$*_LIBS) \
-			-DDYNLOOKUP $(CFLAGS_DYNAMIC) $(CFLAGS) $(INCLUDE) \
-			$(DLFLAGS) $*.c -o $@
+		$(FE)$(CC) -DDYNLOOKUP $(CFLAGS_DYNAMIC) $(CFLAGS) \
+			$(SUPPORT_$*_INCLUDE) $(INCLUDE) $(DLFLAGS) $*.c \
+			$(SUPPORT_$*_LIBS) -o $@
 
 # Note that the sources from pdkim/ are linked into the build.../miscmods/ dir
 # by scripts/Makelinks.
@@ -36,25 +37,28 @@ arc.o	arc.so:			$(HDRS) pdkim.h arc.c
 dkim.o  dkim.so:		$(HDRS) dkim.h dkim.c dkim_transport.c \
 				crypt_ver.h pdkim.h pdkim_hash.h pdkim.c \
 				signing.h signing.c
-dmarc.o dmarc.so:		$(HDRS) spf.h pdkim.h dmarc.h dmarc.c
+dmarc.o dmarc.so:		$(HDRS) spf.h pdkim.h dmarc.h \
+				dmarc.c dmarc_common.c
+dmarc_native.o dmarc_native.so:	$(HDRS) spf.h pdkim.h dmarc.h \
+				dmarc_native.c dmarc_common.c
+dscp.o  dscp.so:		$(HDRS) dscp.c
 dummy.o:			dummy.c
-exim_filter.o exim_filter.so:	$(HDRS) exim_filter.c
-pam.o   pam.so:			$(HDRS) pam.c
-perl.o perl.so:         	$(HDRS) perl.c
-radius.o radius.so:		$(HDRS) radius.c
-sieve_filter.o sieve_filter.so:	$(HDRS) sieve_filter.c
-spf.o   spf.so:			$(HDRS) spf.h spf.c
-spf_perl.o spf_perl.so:		$(HDRS) spf.h spf_perl.c
+exim_filter.o	exim_filter.so:	$(HDRS) exim_filter.c
+pam.o   	pam.so:		$(HDRS) pam.c
+perl.o		perl.so:       	$(HDRS) perl.c
+proxy.o		proxy.so:	$(HDRS) proxy_api.h proxy.c
+radius.o	radius.so:	$(HDRS) radius.c
+sieve_filter.o	sieve_filter.so: $(HDRS) sieve_filter.c
+socks.o		socks.so:	$(HDRS) socks_api.h ../transports/smtp.h socks.c
+spf.o   	spf.so:		$(HDRS) spf.h spf.c
+spf_perl.o	spf_perl.so:	$(HDRS) spf.h spf_perl.c
+xclient.o	xclient.so:	$(HDRS) xclient_api.h xclient.c
 
 
 
 # We need a single .o because that's what scripts/Configure-Makefile
 # understands and fills in to $(OBJ).
 # Try desparately to get the Solaris cc/ld to build one.
-#dkim.o:
-#	@echo "$(CC) dkim.c dkim_transport.c pdkim.c signing.c"
-#	$(FE)$(CC) -r $(LDFLAGS_PARTIAL) -o $@ $(CFLAGS) $(INCLUDE) \
-#		dkim.c dkim_transport.c pdkim.c signing.c
 dkim.o:
 	@echo "$(CC) dkim.c dkim_transport.c pdkim.c signing.c"
 	$(FE)$(CC) -c $(CFLAGS) $(INCLUDE) dkim.c
@@ -68,10 +72,10 @@ dkim.o:
 # Similarly, we want a single .so for the dynamic-load module
 dkim.so:
 	@echo "$(CC) -shared dkim.c dkim_transport.c pdkim.c signing.c"
-	$(FE)$(CC) -DDYNLOOKUP $(CFLAGS_DYNAMIC) -o $@ \
-		$(SUPPORT_$*_INCLUDE) $(SUPPORT_$*_LIBS) \
-		$(CFLAGS) $(INCLUDE) $(TLS_INCLUDE) $(DLFLAGS) \
-		dkim.c dkim_transport.c pdkim.c signing.c
+	$(FE)$(CC) -DDYNLOOKUP $(CFLAGS_DYNAMIC) $(CFLAGS) \
+		$(SUPPORT_$*_INCLUDE) $(INCLUDE) $(TLS_INCLUDE) $(DLFLAGS) \
+		dkim.c dkim_transport.c pdkim.c signing.c $(SUPPORT_$*_LIBS)  \
+		-o $@
 
 # spf_perl is special; Configure-Makefile asks for it but we actually
 # build resulting in a file named spf.so because the module-loading
@@ -81,9 +85,24 @@ dkim.so:
 
 spf_perl.so:
 	@echo "$(CC) -shared $*.c"
-	$(FE)$(CC) $(SUPPORT_SPF_PERL_INCLUDE) $(SUPPORT_SPF_PERL_LIBS) \
-			-DDYNLOOKUP $(CFLAGS_DYNAMIC) $(CFLAGS) $(INCLUDE) \
-			$(DLFLAGS) spf_perl.c -o spf.so
+	$(FE)$(CC) -DDYNLOOKUP $(CFLAGS_DYNAMIC) $(CFLAGS) \
+		$(SUPPORT_SPF_PERL_INCLUDE) $(INCLUDE) $(DLFLAGS) \
+		spf_perl.c $(SUPPORT_SPF_PERL_LIBS) -o spf.so
+
+# DMARC
+dmarc.o dmarc_native.o:
+	@echo "$(CC) $*.c dmarc_common.c"
+	$(FE)$(CC) -c $(CFLAGS) $(INCLUDE) $*.c
+	$(FE)$(CC) -c $(CFLAGS) $(INCLUDE) dmarc_common.c
+	$(FE)mv $@ dmarc_tmp.o
+	$(FE)ld -r -o $@ $(LDFLAGS_PARTIAL) dmarc_tmp.o dmarc_common.o
+
+# dmarc_native is special in the same way as spf_perl
+dmarc.so dmarc_native.so:
+	@echo "$(CC) -shared $*.c dmarc_common.c"
+	$(FE)$(CC) -DDYNLOOKUP $(CFLAGS_DYNAMIC) $(CFLAGS) \
+		$(SUPPORT_$*_INCLUDE) $(INCLUDE) $(TLS_INCLUDE) $(DLFLAGS) \
+		$*.c dmarc_common.c $(SUPPORT_$*_LIBS) -o dmarc.so
 
 # Compile instructions for static perl.o for when EXIM_PERL is set
 # Dynamic is managed all via scripts/Configure-Makefile
diff --git a/src/src/miscmods/arc.c b/src/src/miscmods/arc.c
index 4767d45bf..662eddddf 100644
--- a/src/src/miscmods/arc.c
+++ b/src/src/miscmods/arc.c
@@ -149,7 +149,7 @@ arc_init(void * dummy)
 uschar * errstr = NULL;
 if ((arc_dkim_mod_info = misc_mod_find(US"dkim", &errstr)))
   return TRUE;
-log_write(0, LOG_MAIN, "arc: %s", errstr);
+log_write(LOG_MAIN, "arc: %s", errstr);
 return FALSE;
 }
 
@@ -158,6 +158,7 @@ arc_smtp_reset(void)
 {
 arc_state = arc_state_reason = NULL;
 arc_received_instance = 0;
+memset(&arc_verify_ctx, 0, sizeof(arc_verify_ctx));
 }
 
 /******************************************************************************/
@@ -199,14 +200,14 @@ for (pas = &ctx->arcset_chain, prev = NULL, next = ctx->arcset_chain;
   if (as->instance > i) break;
   if (as->instance == i)
     {
-    DEBUG(D_acl) debug_printf("ARC: existing instance %u\n", i);
+    DEBUG(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);
+DEBUG(acl) debug_printf("ARC: new instance %u\n", i);
 *pas = as = store_get(sizeof(arc_set), GET_UNTAINTED);
 memset(as, 0, sizeof(arc_set));
 as->next = next;
@@ -482,7 +483,7 @@ memset(al, 0, sizeof(arc_line));
 
 if ((e = arc_parse_line(al, h, off, l_ext)))
   {
-  DEBUG(D_acl) debug_printf("ARC: %s\n", e);
+  DEBUG(acl) debug_printf("ARC: %s\n", e);
   return string_sprintf("line parse: %s", e);
   }
 if (!(i = arc_instance_from_hdr(al)))	return US"instance find";
@@ -507,7 +508,7 @@ 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)
+  DEBUG(acl)
     {
     int len = h->slen;
     uschar * s;
@@ -518,7 +519,7 @@ if (strncmpic(ARC_HDR_AAR, h->text, ARC_HDRLEN_AAR) == 0)
   if ((e = arc_insert_hdr(ctx, h, ARC_HDRLEN_AAR, offsetof(arc_set, hdr_aar),
 	      is_signing ? le_instance_only : le_instance_plus_ip, NULL)))
     {
-    DEBUG(D_acl) debug_printf("inserting AAR: %s\n", e);
+    DEBUG(acl) debug_printf("inserting AAR: %s\n", e);
     return string_sprintf("inserting AAR: %s", e);
     }
   }
@@ -526,7 +527,7 @@ else if (strncmpic(ARC_HDR_AMS, h->text, ARC_HDRLEN_AMS) == 0)
   {
   arc_line * ams;
 
-  DEBUG(D_acl)
+  DEBUG(acl)
     {
     int len = h->slen;
     uschar * s;
@@ -537,7 +538,7 @@ else if (strncmpic(ARC_HDR_AMS, h->text, ARC_HDRLEN_AMS) == 0)
   if ((e = arc_insert_hdr(ctx, h, ARC_HDRLEN_AMS, offsetof(arc_set, hdr_ams),
 	      is_signing ? le_instance_only : le_all, &ams)))
     {
-    DEBUG(D_acl) debug_printf("inserting AMS: %s\n", e);
+    DEBUG(acl) debug_printf("inserting AMS: %s\n", e);
     return string_sprintf("inserting AMS: %s", e);
     }
 
@@ -550,7 +551,7 @@ else if (strncmpic(ARC_HDR_AMS, h->text, ARC_HDRLEN_AMS) == 0)
   }
 else if (strncmpic(ARC_HDR_AS, h->text, ARC_HDRLEN_AS) == 0)
   {
-  DEBUG(D_acl)
+  DEBUG(acl)
     {
     int len = h->slen;
     uschar * s;
@@ -561,7 +562,7 @@ else if (strncmpic(ARC_HDR_AS, h->text, ARC_HDRLEN_AS) == 0)
   if ((e = arc_insert_hdr(ctx, h, ARC_HDRLEN_AS, offsetof(arc_set, hdr_as),
 	    is_signing ? le_instance_only : le_all, NULL)))
     {
-    DEBUG(D_acl) debug_printf("inserting AS: %s\n", e);
+    DEBUG(acl) debug_printf("inserting AS: %s\n", e);
     return string_sprintf("inserting AS: %s", e);
     }
   }
@@ -585,7 +586,7 @@ header_line * h;
 hdr_rlist * r = NULL, * rprev = NULL;
 const uschar * e;
 
-DEBUG(D_acl) debug_printf("ARC: collecting arc sets\n");
+DEBUG(acl) debug_printf("ARC: collecting arc sets\n");
 for (h = header_list; h; h = h->next)
   {
   r = store_get(sizeof(hdr_rlist), GET_UNTAINTED);
@@ -667,7 +668,7 @@ int len;
 
 if (hm < 0 || !exim_sha_init(&hhash_ctx, hm))
   {
-  DEBUG(D_acl)
+  DEBUG(acl)
       debug_printf("ARC: hash setup error, possibly nonhandled hashtype\n");
   return;
   }
@@ -677,7 +678,7 @@ 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");
+DEBUG(acl) debug_printf("ARC: AMS header data for verification:\n");
 
 for (r = headers_rlist; r; r = r->prev)
   r->used = FALSE;
@@ -689,7 +690,7 @@ while ((hn = string_nextinlist(&headernames, &sep, NULL, 0)))
       {
       if (relaxed) s = arc_relax_header_n(s, r->h->slen, TRUE);
 
-      DEBUG(D_acl) debug_printf("%Z\n", s);
+      DEBUG(acl) debug_printf("%Z\n", s);
       exim_sha_update_string(&hhash_ctx, s);
       r->used = TRUE;
       break;
@@ -700,11 +701,11 @@ while ((hn = string_nextinlist(&headernames, &sep, NULL, 0)))
 s = ams->rawsig_no_b_val.data, len = ams->rawsig_no_b_val.len;
 if (relaxed)
   len = Ustrlen(s = arc_relax_header_n(s, len, FALSE));
-DEBUG(D_acl) debug_printf("%.*Z\n", len, s);
+DEBUG(acl) debug_printf("%.*Z\n", len, s);
 exim_sha_update(&hhash_ctx, s, len);
 
 exim_sha_finish(&hhash_ctx, hhash);
-DEBUG(D_acl)
+DEBUG(acl)
   { debug_printf("ARC: header hash: %.*H\n", hhash->len, hhash->data); }
 return;
 }
@@ -742,7 +743,7 @@ if (hashes)
     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=%b\n", hashes, &al->a);
+    DEBUG(acl) debug_printf("pubkey h=%s vs sig a=%b\n", hashes, &al->a);
     *errstr = US"no usable sig for this pubkey hash list";
     return NULL;
     }
@@ -821,27 +822,28 @@ typedef int (*fn_t)
 
 /* Get the public key from DNS */
 
-/*XXX dkim module */
 if (!(pubkey = arc_line_to_pubkey(al, &errstr)))
+  rc = ERROR;
+else
   {
-  *errstr_p = string_sprintf("%s (%s)", errstr, why);
-  return ERROR;
+  rc = (((fn_t *) arc_dkim_mod_info->functions)[DKIM_SIG_VERIFY])
+			    (sighash, hhash_computed, hm, pubkey, &errstr);
+  switch (rc)
+    {
+    case OK:
+      break;
+    case FAIL:
+      DEBUG(acl)
+	debug_printf("ARC i=%d %s verify %s\n", as->instance, why, errstr);
+      break;
+    case ERROR:
+      DEBUG(acl) debug_printf("ARC verify %s init: %s\n", why, errstr);
+      break;
+    }
   }
 
-rc = (((fn_t *) arc_dkim_mod_info->functions)[DKIM_SIG_VERIFY])
-			  (sighash, hhash_computed, hm, pubkey, &errstr);
-switch (rc)
-  {
-  case OK:
-    break;
-  case FAIL:
-    DEBUG(D_acl)
-      debug_printf("ARC i=%d %s verify %s\n", as->instance, why, errstr);
-    break;
-  case ERROR:
-    DEBUG(D_acl) debug_printf("ARC verify %s init: %s\n", why, errstr);
-    break;
-  }
+if (rc != OK)
+  *errstr_p = string_sprintf("%s (%s)", errstr, why);
 return rc;
 }
 
@@ -890,13 +892,11 @@ if (!(b = arc_ams_setup_vfy_bodyhash(ams)))
   return US"fail";
   }
 
-DEBUG(D_acl)
-  {
+DEBUG(acl)
   debug_printf("ARC i=%d AMS   Body bytes hashed: %lu\n"
 	       "              Body %b computed: %.*H\n",
 	       as->instance, b->signed_body_bytes,
 	       &ams->a_hash, b->bh.len, b->bh.data);
-  }
 
 /* We know the bh-tag blob is of a nul-term string, so safe as a string */
 
@@ -905,7 +905,7 @@ if (  !ams->bh.data
    || memcmp(sighash.data, b->bh.data, b->bh.len) != 0
    )
   {
-  DEBUG(D_acl)
+  DEBUG(acl)
     {
     debug_printf("ARC i=%d AMS Body hash from headers: ", as->instance);
     debug_printf("%.*H\n", sighash.len, sighash.data);
@@ -914,7 +914,7 @@ if (  !ams->bh.data
   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);
+DEBUG(acl) debug_printf("ARC i=%d AMS Body hash compared OK\n", as->instance);
 
 /* We know the b-tag blob is of a nul-term string, so safe as a string */
 arc_decode_base64(ams->b.data, &sighash);
@@ -923,7 +923,7 @@ arc_get_verify_hhash(ctx, ams, &hhash_computed);
 
 if ((hm = arc_dkim_hashname_blob_to_method(&ams->a_hash)) < 0)
   {
-  DEBUG(D_acl) debug_printf("ARC i=%d AMS verify bad a_hash\n", as->instance);
+  DEBUG(acl) debug_printf("ARC i=%d AMS verify bad a_hash\n", as->instance);
   return as->ams_verify_done = arc_state_reason = US"AMS sig nonverify";
   }
 
@@ -932,7 +932,7 @@ if (rc != OK)
   return as->ams_verify_done = arc_state_reason =
     rc == FAIL ? US"AMS sig nonverify" : errstr;
 
-DEBUG(D_acl) debug_printf("ARC i=%d AMS verify pass\n", as->instance);
+DEBUG(acl) debug_printf("ARC i=%d AMS verify pass\n", as->instance);
 as->ams_verify_passed = TRUE;
 return NULL;
 }
@@ -966,7 +966,7 @@ for(inst = as->instance; as; as = as->prev, inst--)
   else
     goto good;
 
-  DEBUG(D_acl) debug_printf("ARC chain fail at %s\n", arc_state_reason);
+  DEBUG(acl) debug_printf("ARC chain fail at %s\n", arc_state_reason);
   return US"fail";
 
   good:
@@ -984,7 +984,7 @@ for(inst = as->instance; as; as = as->prev, inst--)
 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);
+  DEBUG(acl) debug_printf("ARC chain fail %s\n", arc_state_reason);
   return US"fail";
   }
 
@@ -1021,7 +1021,7 @@ blob sighash;
 const uschar * errstr;
 int rc;
 
-DEBUG(D_acl) debug_printf("ARC: AS vfy i=%d\n", as->instance);
+DEBUG(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
@@ -1051,7 +1051,7 @@ hm = arc_dkim_hashname_blob_to_method(&hdr_as->a_hash);
 
 if (hm < 0 || !exim_sha_init(&hhash_ctx, hm))
   {
-  DEBUG(D_acl)
+  DEBUG(acl)
       debug_printf("ARC: hash setup error, possibly nonhandled hashtype\n");
   arc_state_reason = US"seal hash setup error";
   return US"fail";
@@ -1067,7 +1067,7 @@ if (hm < 0 || !exim_sha_init(&hhash_ctx, hm))
 Headers are CRLF-separated, but the last one is not crlf-terminated.
 */
 
-DEBUG(D_acl) debug_printf("ARC: AS header data for verification:\n");
+DEBUG(acl) debug_printf("ARC: AS header data for verification:\n");
 for (as2 = ctx->arcset_chain;
      as2 && as2->instance <= as->instance;
      as2 = as2->next)
@@ -1081,7 +1081,7 @@ for (as2 = ctx->arcset_chain;
     al->relaxed = s = arc_relax_header_n(al->complete->text,
 					    al->complete->slen, TRUE);
   len = Ustrlen(s);
-  DEBUG(D_acl) debug_printf("%Z\n", s);
+  DEBUG(acl) debug_printf("%Z\n", s);
   exim_sha_update(&hhash_ctx, s, len);
 
   al = as2->hdr_ams;
@@ -1089,7 +1089,7 @@ for (as2 = ctx->arcset_chain;
     al->relaxed = s = arc_relax_header_n(al->complete->text,
 					    al->complete->slen, TRUE);
   len = Ustrlen(s);
-  DEBUG(D_acl) debug_printf("%Z\n", s);
+  DEBUG(acl) debug_printf("%Z\n", s);
   exim_sha_update(&hhash_ctx, s, len);
 
   al = as2->hdr_as;
@@ -1100,7 +1100,7 @@ for (as2 = ctx->arcset_chain;
     al->relaxed = s = arc_relax_header_n(al->complete->text,
 					    al->complete->slen, TRUE);
   len = Ustrlen(s);
-  DEBUG(D_acl) debug_printf("%Z\n", s);
+  DEBUG(acl) debug_printf("%Z\n", s);
   exim_sha_update(&hhash_ctx, s, len);
   }
 
@@ -1109,7 +1109,7 @@ for (as2 = ctx->arcset_chain;
 */
 
 exim_sha_finish(&hhash_ctx, &hhash_computed);
-DEBUG(D_acl)
+DEBUG(acl)
   {
   debug_printf("ARC i=%d AS Header %b computed: ",
 		as->instance, &hdr_as->a_hash);
@@ -1142,7 +1142,7 @@ if (rc != OK)
   return US"fail";
   }
 
-DEBUG(D_acl) debug_printf("ARC: AS vfy i=%d pass\n", as->instance);
+DEBUG(acl) debug_printf("ARC: AS vfy i=%d pass\n", as->instance);
 return NULL;
 }
 
@@ -1157,7 +1157,7 @@ if (!as)
 
 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");
+DEBUG(acl) debug_printf("ARC: AS vfy overall pass\n");
 return NULL;
 }
 /******************************************************************************/
@@ -1258,7 +1258,7 @@ out:
   if (!(arc_state = res))
     return DEFER;
 
-  DEBUG(D_acl) debug_printf_indent("ARC verify result %s %s%s%s\n", arc_state,
+  DEBUG(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";
@@ -1390,7 +1390,7 @@ 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);
+DEBUG(transport) debug_printf("ARC: AAR '%.*s'\n", h->slen - 2, h->text);
 return g;
 }
 
@@ -1409,7 +1409,7 @@ const uschar * errstr;
 typedef const uschar * (*fn_t)
 			  (const blob *, hashmethod, const uschar *, blob *);
 
-DEBUG(D_transport)
+DEBUG(transport)
   {
   hctx hhash_ctx;
   debug_printf("ARC: %s header data for signing:\n", why);
@@ -1438,8 +1438,8 @@ errstr = (((fn_t *) arc_dkim_mod_info->functions)[DKIM_SIGN_DATA])
 						  (&hhash, hm, privkey, sig);
 if (errstr)
   {
-  log_write(0, LOG_MAIN, "ARC: %s signing: %s\n", why, errstr);
-  DEBUG(D_transport)
+  log_write(LOG_MAIN, "ARC: %s signing: %s\n", why, errstr);
+  DEBUG(transport)
     debug_printf("private key, or private-key file content, was: '%s'\n",
       privkey);
   return FALSE;
@@ -1565,7 +1565,7 @@ 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);
+DEBUG(transport) debug_printf("ARC: AMS '%.*s'\n", h->slen - 2, h->text);
 return g;
 }
 
@@ -1624,7 +1624,7 @@ blob sig;
       - all ARC set headers, set-number order, aar then ams then as,
         including self (but with an empty b= in self)
 */
-DEBUG(D_transport) debug_printf("ARC: building AS for status '%s'\n", status);
+DEBUG(transport) debug_printf("ARC: building AS for status '%s'\n", status);
 
 /* Construct the AS except for the signature */
 
@@ -1678,14 +1678,14 @@ if (!arc_sig_from_pseudoheader(hdata, hashtype, privkey, &sig, US"AS"))
 /* Lose the trailing semicolon */
 gstring_trim(arcset, 1);
 arcset = arc_sign_append_sig(arcset, &sig);
-DEBUG(D_transport) debug_printf("ARC: AS  '%.*s'\n", arcset->ptr - 2, arcset->s);
+DEBUG(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);
 
 badline:
-  DEBUG(D_transport)
+  DEBUG(transport)
     debug_printf("ARC: while building AS, missing %s in chain\n", badline_str);
   return NULL;
 }
@@ -1699,7 +1699,7 @@ arc_ams_setup_sign_bodyhash(void)
 blob canon = {.data = US"relaxed", .len = 7};	/*XXX hardwired */
 blob hash =  {.data = US"sha256",  .len = 6};	/*XXX hardwired */
 
-DEBUG(D_transport) debug_printf("ARC: requesting bodyhash\n");
+DEBUG(transport) debug_printf("ARC: requesting bodyhash\n");
 
 return arc_set_bodyhash(TRUE, &canon, &hash, -1);
 }
@@ -1837,7 +1837,7 @@ if ((opts = string_nextinlist(&signspec, &sep, NULL, 0)))
       }
   }
 
-DEBUG(D_transport) debug_printf("ARC: sign for %s\n", identity);
+DEBUG(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. */
@@ -1863,7 +1863,7 @@ if ((rheaders = arc_sign_scan_headers(&arc_sign_ctx, sigheaders)))
 
 if (!(arc_sign_find_ar(headers, identity, &ar)))
   {
-  log_write(0, LOG_MAIN, "ARC: no Authentication-Results header for signing");
+  log_write(LOG_MAIN, "ARC: no Authentication-Results header for signing");
   goto ret_sigheaders;
   }
 
@@ -1871,7 +1871,7 @@ if (!(arc_sign_find_ar(headers, identity, &ar)))
 feed from the DKIM module.  Use that to give the instance number for the ARC set we are
 about to build. */
 
-DEBUG(D_transport)
+DEBUG(transport)
   if (arc_sign_ctx.arcset_chain_last)
     debug_printf("ARC: existing chain highest instance: %d\n",
       arc_sign_ctx.arcset_chain_last->instance);
@@ -1931,11 +1931,11 @@ out:
 
 
 bad_bodyhash_ret:
-  log_write(0, LOG_MAIN, "ARC: bad message body-hash");
+  log_write(LOG_MAIN, "ARC: bad message body-hash");
   goto ret_sigheaders;
 
 bad_arg_ret:
-  log_write(0, LOG_MAIN,
+  log_write(LOG_MAIN,
 	    "ARC: bad signing-specification (%s) '%s'", s, orig_signspec);
 ret_sigheaders:
   g = sigheaders;
@@ -1962,7 +1962,7 @@ uschar * errstr;
 
 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");
+DEBUG(receive) debug_printf("ARC: spotted AMS header\n");
 /* Parse the AMS header */
 
 memset(&al, 0, sizeof(arc_line));
@@ -1970,13 +1970,13 @@ h.next = NULL;
 h.slen = len_string_from_gstring(g, &h.text);
 if ((errstr = arc_parse_line(&al, &h, ARC_HDRLEN_AMS, le_all)))
   {
-  DEBUG(D_acl) debug_printf("ARC: %s\n", errstr);
+  DEBUG(acl) debug_printf("ARC: %s\n", errstr);
   goto badline;
   }
 
 if (!al.a_hash.data)
   {
-  DEBUG(D_acl) debug_printf("ARC: no a_hash from '%.*s'\n", h.slen, h.text);
+  DEBUG(acl) debug_printf("ARC: no a_hash from '%.*s'\n", h.slen, h.text);
   goto badline;
   }
 
@@ -2063,7 +2063,7 @@ authres_arc(gstring * g)
 if (arc_state)
   {
   int start = 0;		/* Compiler quietening */
-  DEBUG(D_acl) start = gstring_length(g);
+  DEBUG(acl) start = gstring_length(g);
 
   g = string_append(g, 2, US";\n\tarc=", arc_state);
   if (arc_received_instance > 0)
@@ -2081,16 +2081,16 @@ if (arc_state)
     }
   else if (arc_state_reason)
     g = string_append(g, 3, US" (", arc_state_reason, US")");
-  DEBUG(D_acl) debug_printf_indent("ARC:\tauthres '%.*s'\n",
+  DEBUG(acl) debug_printf_indent("ARC:\tauthres '%.*s'\n",
 		  gstring_length(g) - start - 3, g->s + start + 3);
   }
 else
-  DEBUG(D_acl) debug_printf_indent("ARC:\tno authres\n");
+  DEBUG(acl) debug_printf_indent("ARC:\tno authres\n");
 return g;
 }
 
 
-#  ifdef SUPPORT_DMARC
+#  ifdef EXIM_HAVE_DMARC
 
 /* Module API: obtain ARC info for DMARC history.
 Arguments:
@@ -2144,7 +2144,7 @@ static void * arc_functions[] = {
   [ARC_STATE_IS_PASS] =	(void *) arc_is_pass,
   [ARC_SIGN_INIT] =	(void *) arc_sign_init,
   [ARC_SIGN] =		(void *) arc_sign,
-# ifdef SUPPORT_DMARC
+# ifdef EXIM_HAVE_DMARC
   [ARC_ARCSET_INFO] =	(void *) arc_arcset_string,
 # endif
 };
diff --git a/src/src/miscmods/dkim.c b/src/src/miscmods/dkim.c
index 4a2c50516..70123b323 100644
--- a/src/src/miscmods/dkim.c
+++ b/src/src/miscmods/dkim.c
@@ -81,7 +81,7 @@ static uschar *
 dkim_exim_query_dns_txt(const uschar * name)
 {
 dns_answer * dnsa = store_get_dns_answer();
-dns_scan dnss;
+dns_scan dnss = {0};
 rmark reset_point = store_mark();
 gstring * g = string_get_tainted(256, GET_TAINTED);
 
@@ -100,6 +100,8 @@ for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS);
       {
       uschar len = rr->data[rr_offset++];
 
+      if (rr_offset + len > rr->size)
+	goto bad;
       g = string_catn(g, US(rr->data + rr_offset), len);
       if (g->ptr >= PDKIM_DNS_TXT_MAX_RECLEN)
 	goto bad;
@@ -146,12 +148,12 @@ pdkim_pubkey * p;
 
 if (!dnstxt)
   {
-  DEBUG(D_acl) debug_printf_indent("pubkey dns lookup fail\n");
+  DEBUG(acl) debug_printf_indent("pubkey dns lookup fail\n");
   return NULL;
   }
 if (!(p = pdkim_parse_pubkey_record(dnstxt)))
   {
-  DEBUG(D_acl) debug_printf_indent("pubkey dns record format error\n");
+  DEBUG(acl) debug_printf_indent("pubkey dns record format error\n");
   return NULL;
   }
 *pubkey_p = &p->key;
@@ -260,7 +262,7 @@ 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,
+  log_write(LOG_MAIN,
 	     "DKIM: validation error: %.100s", dkim_collect_error);
   dkim_collect_input = 0;
   }
@@ -304,7 +306,7 @@ dkim_signatures = NULL;
 
 if (dkim_collect_error)
   {
-  log_write(0, LOG_MAIN,
+  log_write(LOG_MAIN,
       "DKIM: Error during validation, disabling signature verification: %.100s",
       dkim_collect_error);
   f.dkim_disable_verify = TRUE;
@@ -318,7 +320,7 @@ dkim_collect_input = 0;
 rc = pdkim_feed_finish(dkim_verify_ctx, (pdkim_signature **)&dkim_signatures,
 			&errstr);
 if (rc != PDKIM_OK && errstr && *errstr)
-  log_write(0, LOG_MAIN, "DKIM: validation error: %s", errstr);
+  log_write(LOG_MAIN, "DKIM: validation error: %s", errstr);
 
 /* Build a colon-separated list of signing domains (and identities, if present) in dkim_signers */
 
@@ -468,7 +470,7 @@ else
       break;
     }
 
-log_write(0, LOG_MAIN, "%Y", logmsg);
+log_write(LOG_MAIN, "%Y", logmsg);
 return;
 }
 
@@ -504,7 +506,7 @@ dkim_acl_call(uschar * id, gstring ** res_ptr,
   uschar ** user_msgptr, uschar ** log_msgptr)
 {
 int rc;
-DEBUG(D_receive)
+DEBUG(receive)
   debug_printf("calling acl_smtp_dkim for identity '%s' domain '%s' sel '%s'\n",
 	      id, dkim_signing_domain, dkim_signing_selector);
 
@@ -610,7 +612,7 @@ if (dkim_verify_signers && *dkim_verify_signers)
 
   if (!dkim_verify_signers_expanded)
     {
-    log_write(0, LOG_MAIN|LOG_PANIC,
+    log_write(LOG_MAIN|LOG_PANIC,
       "expansion of dkim_verify_signers option failed: %s",
       expand_string_message);
     return DEFER;
@@ -645,7 +647,7 @@ if (dkim_verify_signers && *dkim_verify_signers)
 
       if (seen_this_item)
 	{
-	DEBUG(D_receive)
+	DEBUG(receive)
 	  debug_printf("acl_smtp_dkim: skipping signer %s, "
 	    "already seen\n", item);
 	continue;
@@ -657,7 +659,7 @@ if (dkim_verify_signers && *dkim_verify_signers)
 
     if ((rc = dkim_exim_acl_run(item, &results, user_msgptr, log_msgptr)) != OK)
       {
-      DEBUG(D_receive)
+      DEBUG(receive)
 	debug_printf("acl_smtp_dkim: acl_check returned %d on %s, "
 	  "skipping remaining items\n", rc, item);
       break;
@@ -973,7 +975,7 @@ if (dkim_domain)
       pdkim_canon = PDKIM_CANON_SIMPLE;
     else
       {
-      log_write(0, LOG_MAIN,
+      log_write(LOG_MAIN,
 		 "DKIM: unknown canonicalization method '%s', defaulting to 'relaxed'.\n",
 		 dkim_canon_expanded);
       pdkim_canon = PDKIM_CANON_RELAXED;
@@ -1078,7 +1080,7 @@ 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");
+  DEBUG(transport) debug_printf("DKIM: no viable signatures to use\n");
   sigbuf = string_get(1);	/* return a zero-len string */
   }
 else
@@ -1108,7 +1110,7 @@ else
 
   if (!sig)
     {
-    DEBUG(D_transport) debug_printf("DKIM: no signatures to use\n");
+    DEBUG(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)
@@ -1122,7 +1124,7 @@ CLEANUP:
   return sigbuf;
 
 pk_bad:
-  log_write(0, LOG_MAIN|LOG_PANIC,
+  log_write(LOG_MAIN|LOG_PANIC,
 		"DKIM: signing failed: %.100s", pdkim_errstr(pdkim_rc));
 bad:
   sigbuf = NULL;
@@ -1131,13 +1133,13 @@ bad:
 expand_bad:
   *errstr = string_sprintf("failed to expand %s: %s",
               errwhen, expand_string_message);
-  log_write(0, LOG_MAIN | LOG_PANIC, "%s", *errstr);
+  log_write(LOG_MAIN | LOG_PANIC, "%s", *errstr);
   goto bad;
 }
 
 
 
-#ifdef SUPPORT_DMARC
+#ifdef EXIM_HAVE_DMARC
 
 /* Module API */
 
@@ -1216,7 +1218,7 @@ es_ctx sctx;
 const uschar * errstr;
 
 if ((errstr = exim_dkim_signing_init(privkey, &sctx)))
-  { DEBUG(D_transport) debug_printf("signing key setup: %s\n", errstr); }
+  { DEBUG(transport) debug_printf("signing key setup: %s\n", errstr); }
 else errstr = exim_dkim_sign(&sctx, hm, data, signature);
 
 return errstr;
@@ -1232,7 +1234,7 @@ authres_dkim(gstring * g)
 {
 int start = 0;		/* compiler quietening */
 
-DEBUG(D_acl) start = gstring_length(g);
+DEBUG(acl) start = gstring_length(g);
 
 for (pdkim_signature * sig = dkim_signatures; sig; sig = sig->next)
   {
@@ -1288,12 +1290,12 @@ for (pdkim_signature * sig = dkim_signatures; sig; sig = sig->next)
     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->identity) g = string_append(g, 2, US" header.i=", string_printing(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)
+DEBUG(acl)
   if (gstring_length(g) == start)
     debug_printf_indent("DKIM:\tno authres\n");
   else
@@ -1331,7 +1333,7 @@ static void * dkim_functions[] = {
   [DKIM_TRANSPORT_INIT] =	(void *) dkim_exim_sign_init,
   [DKIM_TRANSPORT_WRITE] =	(void *) dkim_transport_write_message,
 
-#ifdef SUPPORT_DMARC
+#ifdef EXIM_HAVE_DMARC
   [DKIM_SIGS_LIST] =		(void *) dkim_sigs_list,
 #endif
 #ifdef EXPERIMENTAL_ARC
diff --git a/src/src/miscmods/dkim_transport.c b/src/src/miscmods/dkim_transport.c
index 5bce81ad1..570513c78 100644
--- a/src/src/miscmods/dkim_transport.c
+++ b/src/src/miscmods/dkim_transport.c
@@ -30,7 +30,7 @@ if (dkim->dkim_strict)
       {
       /* Set errno to something halfway meaningful */
       *errp = EACCES;
-      log_write(0, LOG_MAIN, "DKIM: message could not be signed,"
+      log_write(LOG_MAIN, "DKIM: message could not be signed,"
 	" and dkim_strict is set. Deferring message delivery.");
       return FALSE;
       }
@@ -48,9 +48,9 @@ dkt_send_file(int out_fd, int in_fd, off_t off
   )
 {
 #ifdef OS_SENDFILE
-DEBUG(D_transport) debug_printf("send file fd=%d size=%u\n", out_fd, (unsigned)(size - off));
+DEBUG(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);
+DEBUG(transport) debug_printf("send file fd=%d\n", out_fd);
 #endif
 
 /*XXX should implement timeout, like transport_write_block_fd() ? */
@@ -163,7 +163,7 @@ int hsize;
 const uschar * errstr;
 BOOL rc;
 
-DEBUG(D_transport) debug_printf("dkim signing direct-mode\n");
+DEBUG(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) */
@@ -264,9 +264,9 @@ 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()));
+		    string_sprintf("-" PID_T_FMT "-K", getpid()));
 
-DEBUG(D_transport) debug_printf("dkim signing via file %s\n", dkim_spool_name);
+DEBUG(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)
   {
diff --git a/src/src/miscmods/dmarc.c b/src/src/miscmods/dmarc.c
index bc96607e0..dfd229b20 100644
--- a/src/src/miscmods/dmarc.c
+++ b/src/src/miscmods/dmarc.c
@@ -26,21 +26,13 @@
 #  include "dmarc.h"
 #  include "pdkim.h"
 
+extern void dmarc_send_forensic_report(const uschar **);
+extern uschar * dmarc_dns_lookup(const uschar *);
+extern void dmarc_write_history_file(const gstring *);
+
 OPENDMARC_LIB_T     dmarc_ctx;
-DMARC_POLICY_T     *dmarc_pctx = NULL;
-OPENDMARC_STATUS_T  libdm_status, action, dmarc_policy;
-OPENDMARC_STATUS_T  da, sa, action;
-BOOL dmarc_abort  = FALSE;
-uschar *dmarc_pass_fail = US"skipped";
-header_line *from_header   = NULL;
-
-static misc_module_info * dmarc_dkim_mod_info;
-static misc_module_info * dmarc_spf_mod_info;
-int dmarc_spf_ares_result  = ARES_RESULT_UNDEFINED;
-uschar *spf_sender_domain  = NULL;
-uschar *spf_human_readable = NULL;
-u_char *header_from_sender = NULL;
-int history_file_status    = DMARC_HIST_OK;
+DMARC_POLICY_T     *dmarc_pctx;
+OPENDMARC_STATUS_T  libdm_status;
 
 typedef struct dmarc_exim_p {
   uschar *name;
@@ -58,41 +50,24 @@ static dmarc_exim_p dmarc_policy_description[] = {
 
 
 /* $variables */
-BOOL	 dmarc_alignment_dkim	 = FALSE; /* Subtest result */
-BOOL	 dmarc_alignment_spf	 = FALSE; /* Subtest result */
-uschar * dmarc_domain_policy     = NULL; /* Declared policy of used domain */
-uschar * dmarc_status            = NULL; /* One word value */
-uschar * dmarc_status_text       = NULL; /* Human readable value */
-uschar * dmarc_used_domain       = NULL; /* Domain libopendmarc chose for DMARC policy lookup */
+extern BOOL	 dmarc_alignment_dkim;	   /* Subtest result */
+extern BOOL	 dmarc_alignment_spf;	   /* Subtest result */
+extern const uschar * dmarc_domain_policy; /* Declared policy of used domain */
+extern const uschar * dmarc_status;	   /* One word value */
+extern const uschar * dmarc_status_text;   /* Human readable value */
+extern uschar * dmarc_used_domain;
 
 /* options */
-uschar * dmarc_forensic_sender   = NULL; /* Set sender address for forensic reports */
-uschar * dmarc_history_file      = NULL; /* File to store dmarc results */
-uschar * dmarc_tld_file          = NULL; /* Mozilla TLDs text file */
-
+extern uschar * dmarc_forensic_sender;	/* Set sender address for forensic reports */
+extern uschar * dmarc_history_file;	/* File to store dmarc results */
+extern uschar * dmarc_tld_file;		/* Mozilla TLDs text file */
 
-/* One-time initialisation for dmarc.  Ensure the spf module is available. */
-
-static BOOL
-dmarc_init(void * dummy)
-{
-uschar * errstr;
-if (!(dmarc_spf_mod_info = misc_mod_find(US"spf", &errstr)))
-  {
-  log_write(0, LOG_MAIN|LOG_PANIC, "dmarc: %s", errstr);
-  return FALSE;
-  }
 
-if (!(dmarc_dkim_mod_info = misc_mod_find(US"dkim", &errstr)))
-  {
-  log_write(0, LOG_MAIN|LOG_PANIC, "dmarc: %s", errstr);
-  return FALSE;
-  }
+BOOL
+dmarc_local_init(void)
+{ return TRUE; }
 
-return TRUE;
-}
-
-static gstring *
+gstring *
 dmarc_version_report(gstring * g)
 {
 return string_fmt_append(g, "Library version: dmarc: Compile: %d.%d.%d.%d\n",
@@ -103,334 +78,71 @@ return string_fmt_append(g, "Library version: dmarc: Compile: %d.%d.%d.%d\n",
 }
 
 
-/* Accept an error_block struct, initialize if empty, parse to the
-end, and append the two strings passed to it.  Used for adding
-variable amounts of value:pair data to the forensic emails. */
-
-static error_block *
-add_to_eblock(error_block *eblock, uschar *t1, uschar *t2)
+void
+dmarc_local_msg_init(void)
 {
-error_block *eb = store_malloc(sizeof(error_block));
-if (!eblock)
-  eblock = eb;
-else
-  {
-  /* Find the end of the eblock struct and point it at eb */
-  error_block *tmp = eblock;
-  while(tmp->next)
-    tmp = tmp->next;
-  tmp->next = eb;
-  }
-eb->text1 = t1;
-eb->text2 = t2;
-eb->next  = NULL;
-return eblock;
-}
-
-/* dmarc_msg_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).
-However, we seem to only use it for one; we destroy some sort of context
-at the tail end of dmarc_process(). */
-
-static int
-dmarc_msg_init()
-{
-int *netmask   = NULL;   /* Ignored */
-uschar * s;
-
-/* Set some sane defaults.  Also clears previous results when
-multiple messages in one connection. */
-
 dmarc_pctx         = NULL;
-dmarc_status       = US"none";
-dmarc_abort        = FALSE;
-dmarc_pass_fail    = US"skipped";
-dmarc_used_domain  = US"";
-f.dmarc_has_been_checked = FALSE;
-header_from_sender = NULL;
-spf_sender_domain  = NULL;
-spf_human_readable = NULL;
-
-/* ACLs have "control=dmarc_disable_verify" */
-if (f.dmarc_disable_verify)
-  return OK;
 
 (void) memset(&dmarc_ctx, '\0', sizeof dmarc_ctx);
 dmarc_ctx.nscount = 0;
 libdm_status = opendmarc_policy_library_init(&dmarc_ctx);
 if (libdm_status != DMARC_PARSE_OKAY)
   {
-  log_write(0, LOG_MAIN|LOG_PANIC, "DMARC failure to init library: %s",
+  log_write(LOG_MAIN|LOG_PANIC, "DMARC failure to init library: %s",
 		       opendmarc_policy_status_to_str(libdm_status));
   dmarc_abort = TRUE;
   }
-GET_OPTION("dmarc_tld_file");
-if (!(s = dmarc_tld_file) || !(s = expand_string(s)) || !*s)
-  {
-  DEBUG(D_receive) debug_printf_indent("DMARC: no dmarc_tld_file\n");
-  dmarc_abort = TRUE;
-  }
 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': %s",
+  log_write(LOG_MAIN|LOG_PANIC, "DMARC failure to load tld list '%s': %s",
 		       dmarc_tld_file, strerror(errno));
   dmarc_abort = TRUE;
   }
-if (!sender_host_address)
-  {
-  DEBUG(D_receive) debug_printf_indent("DMARC: no sender_host_address\n");
-  dmarc_abort = TRUE;
-  }
+
 /* This catches locally originated email and startup errors above. */
 if (!dmarc_abort)
   {
-  int is_ipv6 = string_is_ip_address(sender_host_address, netmask) == 6;
+  int is_ipv6 = string_is_ip_address(sender_host_address, NULL) == 6;
   if (!(dmarc_pctx = opendmarc_policy_connect_init(sender_host_address, is_ipv6)))
     {
-    log_write(0, LOG_MAIN|LOG_PANIC,
+    log_write(LOG_MAIN|LOG_PANIC,
       "DMARC failure creating policy context: ip=%s", sender_host_address);
     dmarc_abort = TRUE;
     }
   }
-
-return OK;
-}
-
-
-static void
-dmarc_smtp_reset(void)
-{
-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;
-}
-
-
-/* dmarc_store_data stores the header data so that subsequent dmarc_process can
-access the data.
-Called after the entire message has been received, with the From: header. */
-
-static 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)
+dmarc_local_send_forensic_report(u_char ** ruf)
 {
-uschar * recipient;
-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 (   dmarc_policy == DMARC_POLICY_REJECT
+    && dmarc_action == DMARC_RESULT_REJECT
+   ||  dmarc_policy == DMARC_POLICY_QUARANTINE
+    && dmarc_action == DMARC_RESULT_QUARANTINE
+   ||  dmarc_policy == DMARC_POLICY_NONE
+    && dmarc_action == DMARC_RESULT_REJECT
+   ||  dmarc_policy == DMARC_POLICY_NONE
+    && dmarc_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 (int 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_indent("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);
-      }
-    }
-}
-
-
-/* Look up a DNS dmarc record for the given domain.  Return it or NULL */
-
-static uschar *
-dmarc_dns_lookup(uschar * dom)
-{
-dns_answer * dnsa = store_get_dns_answer();
-dns_scan dnss;
-uschar * res = NULL;
-
-expand_level++;
-
-if (dns_lookup(dnsa, string_sprintf("_dmarc.%s", dom), T_TXT, NULL)
-    == DNS_SUCCEED)
-  for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
-       rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
-    if (rr->type == T_TXT && rr->size > 3)
-      res = string_copyn_taint(US rr->data, rr->size, GET_TAINTED);
-
-expand_level--;
-store_free_dns_answer(dnsa);
-return res;
+    dmarc_send_forensic_report(CUSS ruf);
 }
 
 
-static int
-dmarc_write_history_file(const gstring * dkim_history_buffer)
-{
-int history_file_fd = 0, tmp_ans;
-u_char ** rua; /* aggregate report addressees */
-uschar * s;
-gstring * g;
-
-GET_OPTION("dmarc_history_file");
-if (!(s = dmarc_history_file) || !(s = expand_string(s)) || !*s)
-  {
-  DEBUG(D_receive) debug_printf_indent("DMARC history file not set\n");
-  return DMARC_HIST_DISABLED;
-  }
-if (!host_checking)
-  /* Ensure we use a modifiiable copy for the filename */
-  if ((history_file_fd =
-	log_open_as_exim(s == dmarc_history_file ? string_copy(s) : s)) < 0)
-    {
-    log_write(0, LOG_MAIN|LOG_PANIC,
-	      "failure to create DMARC history file: %s: %s",
-	      s, strerror(errno));
-    return DMARC_HIST_FILE_ERR;
-    }
 
-/* Generate the contents of the history file entry */
-
-g = string_fmt_append(NULL,
-  "job %s\nreporter %s\nreceived %ld\nipaddr %s\nfrom %s\nmfrom %s\n",
-  message_id, primary_hostname, time(NULL), sender_host_address,
-  header_from_sender, expand_string(US"$sender_address_domain"));
-
-if (dmarc_spf_ares_result != ARES_RESULT_UNDEFINED)
-  g = string_fmt_append(g, "spf %d\n", dmarc_spf_ares_result);
-
-if (dkim_history_buffer)
-  g = string_fmt_append(g, "%Y", dkim_history_buffer);
-
-g = string_fmt_append(g, "pdomain %s\npolicy %d\n",
-  dmarc_used_domain, dmarc_policy);
-
-if ((rua = opendmarc_policy_fetch_rua(dmarc_pctx, NULL, 0, 1)))
-  for (tmp_ans = 0; rua[tmp_ans]; tmp_ans++)
-    g = string_fmt_append(g, "rua %s\n", rua[tmp_ans]);
-else
-  g = string_catn(g, US"rua -\n", 6);
-
-opendmarc_policy_fetch_pct(dmarc_pctx, &tmp_ans);
-g = string_fmt_append(g, "pct %d\n", tmp_ans);
-
-opendmarc_policy_fetch_adkim(dmarc_pctx, &tmp_ans);
-g = string_fmt_append(g, "adkim %d\n", tmp_ans);
-
-opendmarc_policy_fetch_aspf(dmarc_pctx, &tmp_ans);
-g = string_fmt_append(g, "aspf %d\n", tmp_ans);
-
-opendmarc_policy_fetch_p(dmarc_pctx, &tmp_ans);
-g = string_fmt_append(g, "p %d\n", tmp_ans);
-
-opendmarc_policy_fetch_sp(dmarc_pctx, &tmp_ans);
-g = string_fmt_append(g, "sp %d\n", tmp_ans);
-
-g = string_fmt_append(g, "align_dkim %d\nalign_spf %d\naction %d\n",
-  da, sa, action);
-
-#if DMARC_API >= 100400
-# ifdef EXPERIMENTAL_ARC
-  {
-  const misc_module_info * mi = misc_mod_findonly(US"arc");
-  const uschar * t;
-  gstring * g2 = NULL;
-  typedef const uschar * (*fn_t)(gstring **);
-
-  if (mi && (t = (((fn_t *) mi->functions)[ARC_ARCSET_INFO]) (&g2)))
-    {
-    int i = Ustrcmp(t, "pass") == 0 ? ARES_RESULT_PASS
-	    : Ustrcmp(t, "fail") == 0 ? ARES_RESULT_FAIL
-	    : ARES_RESULT_UNKNOWN;
-
-    g = string_fmt_append(g, "arc %d\n"
-			     "arc_policy %d json[%#Y ]\n",
-			  i,
-			  i == ARES_RESULT_PASS ? DMARC_ARC_POLICY_RESULT_PASS
-			  : i == ARES_RESULT_FAIL ? DMARC_ARC_POLICY_RESULT_FAIL
-			  : DMARC_ARC_POLICY_RESULT_UNUSED,
-			  g2
-			  );
-    }
-  else
-    string_fmt_append(g, "arc %d\narc_policy %d json:[]\n",
-			ARES_RESULT_UNKNOWN, DMARC_ARC_POLICY_RESULT_UNUSED);
-  }
-
-# else
-g = string_fmt_append(g, "arc %d\narc_policy %d json:[]\n",
-		      ARES_RESULT_UNKNOWN, DMARC_ARC_POLICY_RESULT_UNUSED);
-# endif
-#endif
-
-/* Write the contents to the history file */
-DEBUG(D_receive)
-  {
-  debug_printf_indent("DMARC logging history data for opendmarc reporting%s\n",
-	     host_checking ? " (not really)" : "");
-  debug_printf_indent("DMARC history data for debugging:\n");
-  expand_level++;
-    debug_printf_indent("%Y", g);
-  expand_level--;
-  }
-
-if (!host_checking)
-  {
-  ssize_t written_len = write_to_fd_buf(history_file_fd,
-				g->s,
-				gstring_length(g));
-  if (written_len == 0)
-    {
-    log_write(0, LOG_MAIN|LOG_PANIC, "failure to write to DMARC history file: %s",
-			   dmarc_history_file);
-    return DMARC_HIST_WRITE_ERR;
-    }
-  (void)close(history_file_fd);
-  }
-return DMARC_HIST_OK;
-}
-
-
-/* dmarc_process adds the envelope sender address to the existing
+/*API: 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.
 Called for the first ACL dmarc= condition. */
 
-static int
+int
 dmarc_process(void)
 {
-int sr = SPF_RESULT_INVALID, origin;	/* used in SPF section */
 int dmarc_spf_result;			/* stores spf into dmarc conn ctx */
 int tmp_ans, c;
 uschar * rr;
@@ -444,82 +156,69 @@ if (f.dmarc_disable_verify)
   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
+If there is no from_header string, then it's likely this message
 is locally generated and relying on fixups to add it.  Just skip
 the entire DMARC system if we can't find a From: header....or if
 there was a previous error.  */
 
-if (!from_header)
+if (!header_from)
   {
-  DEBUG(D_receive) debug_printf_indent("DMARC: no From: header\n");
+  DEBUG(receive) debug_printf_indent("DMARC: no From: header\n");
   dmarc_abort = TRUE;
   }
 else if (!dmarc_abort)
   {
+  const uschar * end_addr, * s;
   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,
+  end_addr = parse_find_address_end(header_from, FALSE);
+  s = *end_addr
+      ? string_copyn(header_from, end_addr - header_from)
+      : header_from;
+  if ((dmarc_header_from_sender = parse_extract_address(s, &errormsg,
 			      &dummy, &dummy, &domain, FALSE)))
-    header_from_sender += domain;
-  *p = saveend;
+    dmarc_header_from_sender += domain;
 
   /* 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))
+  if (!dmarc_header_from_sender || !*dmarc_header_from_sender)
     dmarc_abort = TRUE;
   libdm_status = dmarc_abort
     ? DMARC_PARSE_OKAY
-    : opendmarc_policy_store_from_domain(dmarc_pctx, header_from_sender);
+    : opendmarc_policy_store_from_domain(dmarc_pctx, dmarc_header_from_sender);
   if (libdm_status != DMARC_PARSE_OKAY)
     {
-    log_write(0, LOG_MAIN|LOG_PANIC,
+    log_write(LOG_MAIN|LOG_PANIC,
 	      "failure to store header From: in DMARC: %s, header was '%s'",
-	      opendmarc_policy_status_to_str(libdm_status), from_header->text);
+	      opendmarc_policy_status_to_str(libdm_status), header_from);
     dmarc_abort = TRUE;
     }
   }
 
-/* Skip DMARC if connection is SMTP Auth. Temporarily, admin should
-instead do this in the ACLs.  */
+/* Skip DMARC if connection is SMTP Auth. Preferably this should not be
+hardwired; the admin should instead do this in the ACLs.  */
 
 if (!dmarc_abort && !sender_host_authenticated)
   {
-  uschar * dmarc_domain;
+  int sr = SPF_RESULT_INVALID, origin;
+  uschar * spf_human_readable = NULL, * spf_sender_domain;
   gstring * dkim_history_buffer = NULL;
-  typedef const pdkim_signature * (*sigs_fn_t)(void);
+  typedef const pdkim_signature * (*dkim_sigs_fn_t)(void);
+  typedef int (*spf_fn_t)(uschar **);
 
-  /* Use the envelope sender domain for this part of DMARC */
+  if (dmarc_spf_mod_info)
+    sr = ((spf_fn_t *) dmarc_spf_mod_info->functions)[SPF_GET_RESULTS]
+							(&spf_human_readable);
 
-  spf_sender_domain = expand_string(US"$sender_address_domain");
-
-    {
-    typedef int (*fn_t)(uschar **);
-    if (dmarc_spf_mod_info)
-      sr = ((fn_t *) dmarc_spf_mod_info->functions)[SPF_GET_RESULTS]
-							  (&spf_human_readable);
-    }
+  spf_sender_domain = expand_string(US"$spf_used_domain");
 
   if (sr == SPF_RESULT_INVALID)
     {
-    /* No spf data means null envelope sender so generate a domain name
-    from the sender_helo_name  */
-
-    if (!spf_sender_domain)
-      {
-      spf_sender_domain = sender_helo_name;
-      log_write(0, LOG_MAIN, "DMARC using synthesized SPF sender domain = %s\n",
-			     spf_sender_domain);
-      DEBUG(D_receive)
-	debug_printf_indent("DMARC using synthesized SPF sender domain = %s\n",
-	  spf_sender_domain);
-      }
+    DEBUG(receive) debug_printf_indent("DMARC: spf result 'invalid'\n");
+
     dmarc_spf_result = DMARC_POLICY_SPF_OUTCOME_NONE;
     dmarc_spf_ares_result = ARES_RESULT_UNKNOWN;
     origin = DMARC_POLICY_SPF_ORIGIN_HELO;
@@ -527,11 +226,12 @@ if (!dmarc_abort && !sender_host_authenticated)
     }
   else
     {
-    dmarc_spf_result = sr == SPF_RESULT_NEUTRAL  ? DMARC_POLICY_SPF_OUTCOME_NONE :
-		       sr == SPF_RESULT_PASS     ? DMARC_POLICY_SPF_OUTCOME_PASS :
-		       sr == SPF_RESULT_FAIL     ? DMARC_POLICY_SPF_OUTCOME_FAIL :
-		       sr == SPF_RESULT_SOFTFAIL ? DMARC_POLICY_SPF_OUTCOME_TMPFAIL :
-		       DMARC_POLICY_SPF_OUTCOME_NONE;
+    dmarc_spf_result =
+      sr == SPF_RESULT_NEUTRAL  ? DMARC_POLICY_SPF_OUTCOME_NONE :
+      sr == SPF_RESULT_PASS     ? DMARC_POLICY_SPF_OUTCOME_PASS :
+      sr == SPF_RESULT_FAIL     ? DMARC_POLICY_SPF_OUTCOME_FAIL :
+      sr == SPF_RESULT_SOFTFAIL ? DMARC_POLICY_SPF_OUTCOME_TMPFAIL :
+      DMARC_POLICY_SPF_OUTCOME_NONE;
     dmarc_spf_ares_result = sr == SPF_RESULT_NEUTRAL   ? ARES_RESULT_NEUTRAL :
 			    sr == SPF_RESULT_PASS      ? ARES_RESULT_PASS :
 			    sr == SPF_RESULT_FAIL      ? ARES_RESULT_FAIL :
@@ -541,17 +241,17 @@ if (!dmarc_abort && !sender_host_authenticated)
 			    sr == SPF_RESULT_PERMERROR ? ARES_RESULT_PERMERROR :
 			    ARES_RESULT_UNKNOWN;
     origin = DMARC_POLICY_SPF_ORIGIN_MAILFROM;
-    DEBUG(D_receive) debug_printf_indent("DMARC using SPF sender domain = %s\n",
+    DEBUG(receive) debug_printf_indent("DMARC using SPF sender domain = %s\n",
 					spf_sender_domain);
     }
-  if (strcmp( CCS spf_sender_domain, "") == 0)
+  if (!*spf_sender_domain)
     dmarc_abort = TRUE;
   if (!dmarc_abort)
     {
     libdm_status = opendmarc_policy_store_spf(dmarc_pctx, spf_sender_domain,
 				dmarc_spf_result, origin, spf_human_readable);
     if (libdm_status != DMARC_PARSE_OKAY)
-      log_write(0, LOG_MAIN|LOG_PANIC, "failure to store spf for DMARC: %s",
+      log_write(LOG_MAIN|LOG_PANIC, "failure to store spf for DMARC: %s",
 			   opendmarc_policy_status_to_str(libdm_status));
     }
 
@@ -559,7 +259,7 @@ if (!dmarc_abort && !sender_host_authenticated)
   the opendmarc context, further building the DMARC reply. */
 
   for(const pdkim_signature * sig =
-	      (((sigs_fn_t *)dmarc_dkim_mod_info->functions)[DKIM_SIGS_LIST])();
+	(((dkim_sigs_fn_t *)dmarc_dkim_mod_info->functions)[DKIM_SIGS_LIST])();
       sig; sig = sig->next)
     {
     int dkim_result, dkim_ares_result, vs, ves;
@@ -578,10 +278,10 @@ The EDITME provides a DMARC_API variable */
                                                sig->selector,
 #endif
                                                dkim_result, US"");
-    DEBUG(D_receive)
+    DEBUG(receive)
       debug_printf_indent("DMARC adding DKIM sender domain = %s\n", sig->domain);
     if (libdm_status != DMARC_PARSE_OKAY)
-      log_write(0, LOG_MAIN|LOG_PANIC,
+      log_write(LOG_MAIN|LOG_PANIC,
 		"failure to store dkim (%s) for DMARC: %s",
 		sig->domain, opendmarc_policy_status_to_str(libdm_status));
 
@@ -610,32 +310,32 @@ The EDITME provides a DMARC_API variable */
   our dns access path is used for debug tracing and for the testsuite
   diversion. */
 
-  libdm_status = (rr = dmarc_dns_lookup(header_from_sender))
-    ? opendmarc_policy_store_dmarc(dmarc_pctx, rr, header_from_sender, NULL)
+  libdm_status = (rr = dmarc_dns_lookup(dmarc_header_from_sender))
+    ? opendmarc_policy_store_dmarc(dmarc_pctx, rr, dmarc_header_from_sender, NULL)
     : DMARC_DNS_ERROR_NO_RECORD;
   switch (libdm_status)
     {
     case DMARC_DNS_ERROR_NXDOMAIN:
     case DMARC_DNS_ERROR_NO_RECORD:
-      DEBUG(D_receive)
-	debug_printf_indent("DMARC no record found for %s\n", header_from_sender);
+      DEBUG(receive)
+	debug_printf_indent("DMARC no record found for %s\n", dmarc_header_from_sender);
       has_dmarc_record = FALSE;
       break;
     case DMARC_PARSE_OKAY:
-      DEBUG(D_receive)
-	debug_printf_indent("DMARC record found for %s\n", header_from_sender);
+      DEBUG(receive)
+	debug_printf_indent("DMARC record found for %s\n", dmarc_header_from_sender);
       break;
     case DMARC_PARSE_ERROR_BAD_VALUE:
-      DEBUG(D_receive)
-	debug_printf_indent("DMARC record parse error for %s\n", header_from_sender);
+      DEBUG(receive)
+	debug_printf_indent("DMARC record parse error for %s\n", dmarc_header_from_sender);
       has_dmarc_record = FALSE;
       break;
     default:
       /* everything else, skip dmarc */
-      DEBUG(D_receive)
+      DEBUG(receive)
 	debug_printf_indent("DMARC skipping (%s), unsure what to do with %s",
 		      opendmarc_policy_status_to_str(libdm_status),
-		      from_header->text);
+		      header_from);
       has_dmarc_record = FALSE;
       break;
     }
@@ -645,92 +345,101 @@ The EDITME provides a DMARC_API variable */
   libdm_status = opendmarc_policy_fetch_p(dmarc_pctx, &tmp_ans);
   for (c = 0; dmarc_policy_description[c].name; c++)
     if (tmp_ans == dmarc_policy_description[c].value)
-      {
-      dmarc_domain_policy = string_sprintf("%s",dmarc_policy_description[c].name);
-      break;
-      }
+      { dmarc_domain_policy = dmarc_policy_description[c].name; break; }
 
   /* Can't use exim's string manipulation functions so allocate memory
   for libopendmarc using its max hostname length definition. */
 
-  dmarc_domain = store_get(DMARC_MAXHOSTNAMELEN, GET_TAINTED);
+  dmarc_used_domain = store_get(DMARC_MAXHOSTNAMELEN, GET_TAINTED);
   libdm_status = opendmarc_policy_fetch_utilized_domain(dmarc_pctx,
-    dmarc_domain, DMARC_MAXHOSTNAMELEN-1);
-  store_release_above(dmarc_domain + Ustrlen(dmarc_domain)+1);
-  dmarc_used_domain = dmarc_domain;
+    dmarc_used_domain, DMARC_MAXHOSTNAMELEN-1);
+  store_release_above(dmarc_used_domain + Ustrlen(dmarc_used_domain)+1);
 
   if (libdm_status != DMARC_PARSE_OKAY)
-    log_write(0, LOG_MAIN|LOG_PANIC,
+    log_write(LOG_MAIN|LOG_PANIC,
       "failure to read domainname used for DMARC lookup: %s",
       opendmarc_policy_status_to_str(libdm_status));
 
   dmarc_policy = libdm_status = opendmarc_get_policy_to_enforce(dmarc_pctx);
+
   switch(libdm_status)
     {
-    case DMARC_POLICY_ABSENT:     /* No DMARC record found */
+    case DMARC_POLICY_ABSENT:		/* No DMARC record found */
       dmarc_status = US"norecord";
       dmarc_pass_fail = US"none";
       dmarc_status_text = US"No DMARC record";
-      action = DMARC_RESULT_ACCEPT;
+      dmarc_action = DMARC_RESULT_ACCEPT;
       break;
-    case DMARC_FROM_DOMAIN_ABSENT:    /* No From: domain */
+    case DMARC_FROM_DOMAIN_ABSENT:	/* No From: domain */
       dmarc_status = US"nofrom";
       dmarc_pass_fail = US"temperror";
       dmarc_status_text = US"No From: domain found";
-      action = DMARC_RESULT_ACCEPT;
+      dmarc_action = DMARC_RESULT_ACCEPT;
       break;
-    case DMARC_POLICY_NONE:       /* Accept and report */
+    case DMARC_POLICY_NONE:		/* Accept and report */
       dmarc_status = US"none";
       dmarc_pass_fail = US"none";
       dmarc_status_text = US"None, Accept";
-      action = DMARC_RESULT_ACCEPT;
+      dmarc_action = DMARC_RESULT_ACCEPT;
       break;
-    case DMARC_POLICY_PASS:       /* Explicit accept */
+    case DMARC_POLICY_PASS:		/* Explicit accept */
       dmarc_status = US"accept";
       dmarc_pass_fail = US"pass";
       dmarc_status_text = US"Accept";
-      action = DMARC_RESULT_ACCEPT;
+      dmarc_action = DMARC_RESULT_ACCEPT;
       break;
-    case DMARC_POLICY_REJECT:       /* Explicit reject */
+    case DMARC_POLICY_REJECT:		/* Explicit reject */
       dmarc_status = US"reject";
       dmarc_pass_fail = US"fail";
       dmarc_status_text = US"Reject";
-      action = DMARC_RESULT_REJECT;
+      dmarc_action = DMARC_RESULT_REJECT;
       break;
-    case DMARC_POLICY_QUARANTINE:       /* Explicit quarantine */
+    case DMARC_POLICY_QUARANTINE:	/* Explicit quarantine */
       dmarc_status = US"quarantine";
       dmarc_pass_fail = US"fail";
       dmarc_status_text = US"Quarantine";
-      action = DMARC_RESULT_QUARANTINE;
+      dmarc_action = DMARC_RESULT_QUARANTINE;
       break;
     default:
       dmarc_status = US"temperror";
       dmarc_pass_fail = US"temperror";
       dmarc_status_text = US"Internal Policy Error";
-      action = DMARC_RESULT_TEMPFAIL;
+      dmarc_action = DMARC_RESULT_TEMPFAIL;
       break;
     }
 
-  libdm_status = opendmarc_policy_fetch_alignment(dmarc_pctx, &da, &sa);
+  libdm_status = opendmarc_policy_fetch_alignment(dmarc_pctx,
+		    &dmarc_dkim_alignment, &dmarc_spf_alignment);
   if (libdm_status != DMARC_PARSE_OKAY)
-    log_write(0, LOG_MAIN|LOG_PANIC, "failure to read DMARC alignment: %s",
+    log_write(LOG_MAIN|LOG_PANIC, "failure to read DMARC alignment: %s",
 			     opendmarc_policy_status_to_str(libdm_status));
 
   if (has_dmarc_record)
     {
-    dmarc_alignment_spf = sa == DMARC_POLICY_SPF_ALIGNMENT_PASS;
-    dmarc_alignment_dkim = da == DMARC_POLICY_DKIM_ALIGNMENT_PASS;
+    dmarc_alignment_spf =
+      dmarc_spf_alignment == DMARC_POLICY_SPF_ALIGNMENT_PASS;
+    dmarc_alignment_dkim =
+      dmarc_dkim_alignment == DMARC_POLICY_DKIM_ALIGNMENT_PASS;
 
-    log_write(0, LOG_MAIN, "DMARC results: spf_domain=%s dmarc_domain=%s "
+    DEBUG(receive)
+      debug_printf_indent("DMARC results: spf_domain=%s dmarc_domain=%s "
 			   "spf_align=%s dkim_align=%s enforcement='%s'",
 			   spf_sender_domain, dmarc_used_domain,
 			   dmarc_alignment_spf  ? "yes" : "no",
 			   dmarc_alignment_dkim ? "yes" : "no",
 			   dmarc_status_text);
-    history_file_status = dmarc_write_history_file(dkim_history_buffer);
+
+    dmarc_rua = USS opendmarc_policy_fetch_rua(dmarc_pctx, NULL, 0, 1);
+    opendmarc_policy_fetch_pct(dmarc_pctx, &dmarc_pct);
+    opendmarc_policy_fetch_adkim(dmarc_pctx, &dmarc_adkim);
+    opendmarc_policy_fetch_aspf(dmarc_pctx, &dmarc_aspf);
+    opendmarc_policy_fetch_p(dmarc_pctx, &dmarc_dom_policy);
+    opendmarc_policy_fetch_sp(dmarc_pctx, &dmarc_subdom_policy);
+    dmarc_write_history_file(dkim_history_buffer);
+
     /* Now get the forensic reporting addresses, if any */
     ruf = opendmarc_policy_fetch_ruf(dmarc_pctx, NULL, 0, 1);
-    dmarc_send_forensic_report(ruf);
+    dmarc_local_send_forensic_report(ruf);
     }
   }
 
@@ -743,94 +452,25 @@ if (!f.dmarc_disable_verify)
 return OK;
 }
 
-static uschar *
-dmarc_exim_expand_defaults(int what)
-{
-if (what == DMARC_VERIFY_STATUS)
-  return f.dmarc_disable_verify ?  US"off" : US"none";
-return US"";
-}
-
-static uschar *
-dmarc_exim_expand_query(int what)
+static const uschar *
+dmarc_exim_expand_defaults(void)
 {
-if (f.dmarc_disable_verify || !dmarc_pctx)
-  return dmarc_exim_expand_defaults(what);
-
-if (what == DMARC_VERIFY_STATUS)
-  return dmarc_status;
-return US"";
+return f.dmarc_disable_verify ?  US"off" : US"none";
 }
 
-
-static gstring *
-authres_dmarc(gstring * g)
+/*API
+Check result against list.  Return OK/FAIL/DEFER.
+*/
+int
+dmarc_result_inlist(const uschar * const * listp)
 {
-if (f.dmarc_has_been_checked)
-  {
-  int start = 0;		/* Compiler quietening */
-  DEBUG(D_acl) start = gstring_length(g);
-  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);
-  DEBUG(D_acl) debug_printf_indent("DMARC:\tauthres '%.*s'\n",
-		  gstring_length(g) - start - 3, g->s + start + 3);
-  }
-else
-  DEBUG(D_acl) debug_printf_indent("DMARC:\tno authres\n");
-return g;
+const uschar * res =
+  f.dmarc_disable_verify || !dmarc_pctx
+  ? dmarc_exim_expand_defaults()
+  : dmarc_status;
+return match_isinlist(res, listp, 0, NULL, NULL, MCL_STRING, TRUE, NULL);
 }
 
-/******************************************************************************/
-/* Module API */
-
-static optionlist dmarc_options[] = {
-  { "dmarc_forensic_sender",    opt_stringptr,      {&dmarc_forensic_sender} },
-  { "dmarc_history_file",       opt_stringptr,      {&dmarc_history_file} },
-  { "dmarc_tld_file",           opt_stringptr,      {&dmarc_tld_file} },
-};
-
-static void * dmarc_functions[] = {
-  [DMARC_PROCESS] =	(void *) dmarc_process,
-  [DMARC_EXPAND_QUERY] = (void *) dmarc_exim_expand_query,
-  [DMARC_STORE_DATA] =	(void *) dmarc_store_data,
-};
-
-/* dmarc_forensic_sender is provided for visibility of the the option setting
-by moan_send_message. We do not document it as a config-visible $variable.
-We could provide it via a function but there's little advantage. */
-
-static var_entry dmarc_variables[] = {
-  { "dmarc_alignment_dkim",	vtype_bool,		&dmarc_alignment_dkim },
-  { "dmarc_alignment_spf",	vtype_bool,		&dmarc_alignment_spf },
-  { "dmarc_domain_policy",	vtype_stringptr,	&dmarc_domain_policy },
-  { "dmarc_forensic_sender",	vtype_stringptr,	&dmarc_forensic_sender},
-  { "dmarc_status",		vtype_stringptr,	&dmarc_status },
-  { "dmarc_status_text",	vtype_stringptr,	&dmarc_status_text },
-  { "dmarc_used_domain",	vtype_stringptr,	&dmarc_used_domain },
-};
-
-misc_module_info dmarc_module_info =
-{
-  .name =		US"dmarc",
-# ifdef DYNLOOKUP
-  .dyn_magic =		MISC_MODULE_MAGIC,
-# endif
-  .init =		dmarc_init,
-  .lib_vers_report =	dmarc_version_report,
-  .smtp_reset =		dmarc_smtp_reset,
-  .msg_init =		dmarc_msg_init,
-  .authres =		authres_dmarc,
-
-  .options =		dmarc_options,
-  .options_count =	nelem(dmarc_options),
-
-  .functions =		dmarc_functions,
-  .functions_count =	nelem(dmarc_functions),
-
-  .variables =		dmarc_variables,
-  .variables_count =	nelem(dmarc_variables),
-};
 
 # endif /* SUPPORT_SPF */
 #endif /* SUPPORT_DMARC */
diff --git a/src/src/miscmods/dmarc.h b/src/src/miscmods/dmarc.h
index c1cafd0d1..fe3138cf2 100644
--- a/src/src/miscmods/dmarc.h
+++ b/src/src/miscmods/dmarc.h
@@ -2,8 +2,8 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Experimental DMARC support.
-   Copyright (c) The Exim Maintainers 2021 - 2023
+/* DMARC support.
+   Copyright (c) The Exim Maintainers 2021 - 2025
    Copyright (c) Todd Lyons <tlyons@exim.org> 2012 - 2014
    License: GPL */
 /* SPDX-License-Identifier: GPL-2.0-or-later */
@@ -11,14 +11,54 @@
 /* Portions Copyright (c) 2012, 2013, The Trusted Domain Project;
    All rights reserved, licensed for use per LICENSE.opendmarc. */
 
-#ifdef SUPPORT_DMARC
+#ifdef EXPERIMENTAL_DMARC_NATIVE
+# define EXIM_HAVE_DMARC EXPERIMENTAL_DMARC_NATIVE
+# define DMARC_SUPPORTS_ARC
+
+/* from opendmarc/dmarc.h */
+# define DMARC_MAXHOSTNAMELEN    256
+
+# define DMARC_POLICY_ABSENT     14
+# define DMARC_POLICY_PASS       15
+# define DMARC_POLICY_REJECT     16
+# define DMARC_POLICY_QUARANTINE 17
+# define DMARC_POLICY_NONE       18
+# define DMARC_USED_POLICY_IS_P  19
+# define DMARC_USED_POLICY_IS_SP 20
+
+# define DMARC_POLICY_SPF_ORIGIN_MAILFROM       1
+# define DMARC_POLICY_SPF_ORIGIN_HELO           2
+
+# define DMARC_POLICY_SPF_OUTCOME_NONE          0
+# define DMARC_POLICY_SPF_OUTCOME_PASS          1
+# define DMARC_POLICY_SPF_OUTCOME_FAIL          2
+# define DMARC_POLICY_SPF_OUTCOME_TMPFAIL       3
+# define DMARC_POLICY_SPF_ALIGNMENT_PASS        4
+# define DMARC_POLICY_SPF_ALIGNMENT_FAIL        5
+
+# define DMARC_POLICY_DKIM_OUTCOME_NONE         0
+# define DMARC_POLICY_DKIM_OUTCOME_PASS         1
+# define DMARC_POLICY_DKIM_OUTCOME_FAIL         2
+# define DMARC_POLICY_DKIM_OUTCOME_TMPFAIL      3
+# define DMARC_POLICY_DKIM_ALIGNMENT_PASS       4
+# define DMARC_POLICY_DKIM_ALIGNMENT_FAIL       5
+
+
+
+#elif defined(SUPPORT_DMARC)
+# define EXIM_HAVE_DMARC SUPPORT_DMARC
+# if DMARC_API >= 100400
+#  define DMARC_SUPPORTS_ARC
+# endif
 
 # include <opendmarc/dmarc.h>
 # ifdef SUPPORT_SPF
 #  include <spf2/spf.h>
 # endif /* SUPPORT_SPF */
 
-#define DMARC_VERIFY_STATUS    1
+#endif /* SUPPORT_DMARC */
+
+
 
 #define DMARC_HIST_OK          1
 #define DMARC_HIST_DISABLED    2
@@ -50,8 +90,54 @@
 #define ARES_RESULT_UNKNOWN 11
 #define ARES_RESULT_DISCARD 12
 
+# define DMARC_RECORD_A_UNSPECIFIED	('\0')		/* adkim and aspf */
+# define DMARC_RECORD_A_STRICT		('s')		/* adkim and aspf */
+# define DMARC_RECORD_A_RELAXED		('r')		/* adkim and aspf */
+# define DMARC_RECORD_P_UNSPECIFIED	('\0')		/* p and sp */
+# define DMARC_RECORD_P_NONE		('n')		/* p and sp */
+# define DMARC_RECORD_P_QUARANTINE	('q')		/* p and sp */
+# define DMARC_RECORD_P_REJECT		('r')		/* p and sp */
+
+#ifndef DMARC_POLICY_SPF_ALIGNMENT_PASS
+/* From opendmarc/dmarc.h */
+# define DMARC_POLICY_SPF_ALIGNMENT_PASS 4
+# define DMARC_POLICY_SPF_ALIGNMENT_FAIL 5
+#endif
+
+#ifndef DMARC_POLICY_DKIM_ALIGNMENT_PASS
+/* From opendmarc/dmarc.h */
+# define DMARC_POLICY_DKIM_ALIGNMENT_PASS 4
+# define DMARC_POLICY_DKIM_ALIGNMENT_FAIL 5
+#endif
+
 #define	DMARC_ARC_POLICY_RESULT_PASS	0
 #define	DMARC_ARC_POLICY_RESULT_UNUSED	1
 #define	DMARC_ARC_POLICY_RESULT_FAIL	2
 
-#endif /* SUPPORT_DMARC */
+
+
+/* These live in dmarc_common.c */
+extern BOOL dmarc_abort;
+extern uschar * dmarc_header_from_sender;
+extern uschar * dmarc_pass_fail;
+extern const misc_module_info * dmarc_dkim_mod_info;
+extern const misc_module_info * dmarc_spf_mod_info;
+extern int dmarc_spf_ares_result;
+extern uschar ** dmarc_rua;		/* aggregate report addressees */
+extern int dmarc_pct;
+extern int dmarc_adkim;
+extern int dmarc_aspf;
+extern int dmarc_policy;
+extern int dmarc_dom_policy;
+extern int dmarc_subdom_policy;
+extern int dmarc_spf_alignment;
+extern int dmarc_dkim_alignment;
+extern int dmarc_action;
+extern uschar * dmarc_used_domain;
+extern const uschar * dmarc_domain_policy;
+extern BOOL dmarc_alignment_spf;
+extern BOOL dmarc_alignment_dkim;
+
+extern const uschar * dmarc_status;
+extern const uschar * dmarc_status_text;
+
diff --git a/src/src/miscmods/dmarc_api.h b/src/src/miscmods/dmarc_api.h
index 2f10463fc..e99b45804 100644
--- a/src/src/miscmods/dmarc_api.h
+++ b/src/src/miscmods/dmarc_api.h
@@ -12,5 +12,4 @@
 /* Function table entry numbers */
 
 #define	DMARC_PROCESS		0
-#define DMARC_EXPAND_QUERY	1
-#define DMARC_STORE_DATA	2
+#define DMARC_RESULT_INLIST	1
diff --git a/src/src/miscmods/dmarc_common.c b/src/src/miscmods/dmarc_common.c
new file mode 100644
index 000000000..8d6f04764
--- /dev/null
+++ b/src/src/miscmods/dmarc_common.c
@@ -0,0 +1,528 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* DMARC support.
+   Copyright (c) The Exim Maintainers 2025
+   License: GPL */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "../exim.h"
+
+#ifdef EXIM_HAVE_DMARC
+
+extern BOOL		dmarc_local_init(void);
+extern void		dmarc_local_msg_init(void);
+extern gstring *	dmarc_version_report(gstring *);
+extern int		dmarc_process(void);
+extern int		dmarc_result_inlist(const uschar * const *);
+
+
+/* Other modules needed for services */
+const misc_module_info * dmarc_spf_mod_info;
+const misc_module_info * dmarc_dkim_mod_info;
+const misc_module_info * dmarc_arc_mod_info;
+
+/* Working data */
+BOOL		dmarc_abort;
+uschar *	dmarc_pass_fail;	/* for authres */
+uschar *	dmarc_header_from_sender;
+
+/* results */
+int		dmarc_spf_ares_result;
+uschar **	dmarc_rua;		/* aggregate report addressees */
+int		dmarc_pct;		/* percentage */
+int		dmarc_adkim;		/* dkim policy */
+int		dmarc_aspf;		/* spf policy */
+int		dmarc_policy;		/* policy to enforce */
+int		dmarc_dom_policy;	/* (the p tag, as numeric) */
+int		dmarc_subdom_policy;	/* (the sp tag, as numeric) */
+int		dmarc_spf_alignment;
+int		dmarc_dkim_alignment;
+int		dmarc_action;
+
+
+/* $variables */
+BOOL	 dmarc_alignment_dkim	 = FALSE; /* Subtest result */
+BOOL	 dmarc_alignment_spf	 = FALSE; /* Subtest result */
+const uschar * dmarc_domain_policy = NULL; /* Declared policy of used domain */
+const uschar * dmarc_status;		/* One word value */
+const uschar * dmarc_status_text   = NULL; /* Human readable value */
+uschar * dmarc_used_domain;		/* Domain libopendmarc chose for DMARC policy lookup */
+
+/* options */
+uschar * dmarc_forensic_sender   = NULL; /* Set sender address for forensic reports */
+uschar * dmarc_history_file      = NULL; /* File to store dmarc results */
+uschar * dmarc_tld_file          = NULL; /* Mozilla TLDs text file */
+
+
+/*API:
+One-time initialisation for dmarc.  Ensure the spf module is available.
+*/
+
+static BOOL
+dmarc_init(void * dummy)
+{
+uschar * errstr;
+if (!(dmarc_spf_mod_info = misc_mod_find(US"spf", &errstr)))
+  {
+  log_write(LOG_MAIN|LOG_PANIC, "dmarc: %s", errstr);
+  return FALSE;
+  }
+
+if (!(dmarc_dkim_mod_info = misc_mod_find(US"dkim", &errstr)))
+  {
+  log_write(LOG_MAIN|LOG_PANIC, "dmarc: %s", errstr);
+  return FALSE;
+  }
+
+dmarc_arc_mod_info = misc_mod_findonly(US"arc");
+return dmarc_local_init();
+}
+
+
+
+/*API: dmarc_msg_init could set 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).
+However, we seem to only use it for one; we destroy some sort of context
+at the tail end of dmarc_process(). */
+
+static int
+dmarc_msg_init(void)
+{
+/* Set some sane defaults.  Also clears previous results when
+multiple messages in one connection. */
+
+f.dmarc_has_been_checked = FALSE;
+dmarc_header_from_sender = NULL;
+dmarc_spf_ares_result  = ARES_RESULT_UNDEFINED;
+dmarc_status       = US"none";
+dmarc_abort        = FALSE;
+dmarc_pass_fail    = US"skipped";
+dmarc_used_domain  = US"";
+
+/* ACLs have "control=dmarc_disable_verify" */
+if (f.dmarc_disable_verify)
+  return OK;
+
+GET_OPTION("dmarc_tld_file");
+if (  !dmarc_tld_file
+   || !(dmarc_tld_file = expand_string(dmarc_tld_file))
+   || !*dmarc_tld_file)
+  {
+  DEBUG(receive) debug_printf_indent("DMARC: no dmarc_tld_file\n");
+  dmarc_abort = TRUE;
+  }
+else if (!sender_host_address)
+  {
+  DEBUG(receive) debug_printf_indent("DMARC: no sender_host_address\n");
+  dmarc_abort = TRUE;
+  }
+else
+  dmarc_local_msg_init();
+
+return OK;
+}
+
+
+/*API*/
+
+static void
+dmarc_smtp_reset(void)
+{
+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;
+}
+
+
+/* Accept an error_block struct, initialize if empty, parse to the
+end, and append the two strings passed to it.  Used for adding
+variable amounts of value:pair data to the forensic emails. */
+
+static error_block *
+add_to_eblock(error_block * eblock, const uschar * t1, const uschar * t2)
+{
+error_block * eb = store_malloc(sizeof(error_block));
+if (!eblock)
+  eblock = eb;
+else
+  {
+  /* Find the end of the eblock struct and point it at eb */
+  error_block * tmp = eblock;
+  while(tmp->next)
+    tmp = tmp->next;
+  tmp->next = eb;
+  }
+eb->text1 = t1;
+eb->text2 = t2;
+eb->next  = NULL;
+return eblock;
+}
+
+void
+dmarc_send_forensic_report(const uschar ** ruf)
+{
+error_block * eblock;
+
+/* Earlier ACL does not have *required* control=dmarc_enable_forensic */
+if (!f.dmarc_enable_forensic || !ruf)
+  return;
+
+eblock = add_to_eblock(NULL,
+	  string_sprintf("Subject: DMARC Forensic Report for %s from IP %s\n\n",
+			dmarc_used_domain, sender_host_address), NULL);
+eblock = add_to_eblock(eblock,
+	  US"A message claiming to be from you has failed the published DMARC\n"
+	  "policy for your domain.\n\n", NULL);
+
+eblock = add_to_eblock(eblock, US"Sender Domain", dmarc_header_from_sender);
+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",
+			       dmarc_alignment_spf ? US"yes" : US"no");
+eblock = add_to_eblock(eblock, US"DKIM Alignment",
+			       dmarc_alignment_dkim ? US"yes" : US"no");
+eblock = add_to_eblock(eblock, US"DMARC Results", dmarc_status_text);
+
+for (int c = 0; ruf[c]; c++)
+  {
+  uschar * recipient = string_copylc(ruf[c]);
+  if (Ustrncmp(recipient, "mailto:",7))
+    continue;
+  /* Move to first character past the colon */
+  recipient += 7;
+  DEBUG(receive)
+    debug_printf_indent("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, NULL, NULL))
+    log_write(LOG_MAIN|LOG_PANIC,
+      "failure to send DMARC forensic report to %s", recipient);
+  }
+}
+
+
+/* Look up a DNS dmarc record for the given domain.  Return it or NULL */
+
+const uschar *
+dmarc_dns_lookup(const uschar * dom)
+{
+dns_answer * dnsa = store_get_dns_answer();
+dns_scan dnss = {0};
+const uschar * res = NULL;
+
+expand_level++;
+
+/* RFC 7489 6.6.1 :- policy record is at a "_dmarc" sub of the domain */
+
+if (dns_lookup(dnsa, string_sprintf("_dmarc.%s", dom), T_TXT, NULL)
+    == DNS_SUCCEED)
+  {
+/*XXX we lose track of temporary DNS failures */
+
+  for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
+       rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
+    {
+    const uschar * rdata = rr->data;
+    int len = rdata[0];
+
+    if (len > 511) len = 127;
+    rdata++;
+
+/* RFC 7489 6.6.1 :- policy record is a TXT record */
+/* RFC 7489 6.6.3 step 2: ignore records not starting "v=DMARC1;"
+   (also noted in 6.3 for the v tag) */
+
+    if (  rr->type == T_TXT && len > 9
+       && Ustrncmp(rdata, "v=DMARC1;", 9) == 0)
+      if (!res)
+	res = string_copyn_taint(rdata, len, GET_TAINTED);	/*XXX*/
+      else
+/* RFC 7489 6.6.3 step 5: multiple records are treated as no record */
+	{
+	DEBUG(receive) debug_printf_indent("DMARC: multiple rr\n");
+	res = NULL;
+	break;
+	}
+    }
+  }
+else
+  DEBUG(receive) debug_printf_indent("DMARC: no ret\n");
+
+expand_level--;
+store_free_dns_answer(dnsa);
+DEBUG(receive) debug_printf_indent("DMARC: rr %q\n", res);
+return res;
+}
+
+
+
+const uschar *
+dmarc_lookup_regdom(const uschar * dom)
+{
+int expand_setup = -1, partial, affixlen, starflags;
+const uschar * affix, * opts, * res;
+const lookup_info * li;
+void * handle;
+static const uschar * cached_key = NULL, * cached_res = NULL;
+
+DEBUG(receive) debug_printf_indent("DMARC: lookup regdom for %q\n", dom);
+
+if (cached_key && Ustrcmp(dom, cached_key) == 0)
+  {
+  res = cached_res;
+  DEBUG(receive) debug_printf_indent(" DMARC: cached value %q\n", res);
+  return res;
+  }
+
+expand_level++;
+res = NULL;
+if (!(li = search_findtype_partial(US"regdom", &partial, &affix, &affixlen,
+				  &starflags, &opts)))
+  { DEBUG(receive) debug_printf_indent("DMARC: missing regdom lookup\n"); }
+
+else if (  (handle = search_open(dmarc_tld_file, li, 0, NULL, NULL))
+	&& (res = search_find(handle, dmarc_tld_file, dom, partial, affix,
+			      affixlen, starflags, &expand_setup, opts))
+	)
+/*XXX should we handle a defer return?  cf. f.search_find_defer */
+  {
+  cached_key = string_copy_perm(dom, FALSE);
+  cached_res = string_copy_perm(res, FALSE);
+  }
+
+expand_level--;
+return res;
+}
+
+const uschar *
+dmarc_get_dns_policy_record(const uschar ** used_dom_p)
+{
+const uschar * s;
+
+DEBUG(receive) debug_printf_indent("DMARC: lookup policy record for %s\n",
+				      dmarc_header_from_sender);
+
+/* RFC 7489 6.6.3 step 1: DNS domain matching the 5322.From */
+
+if ((s= dmarc_dns_lookup(*used_dom_p = dmarc_header_from_sender)))
+  return s;
+
+/* RFC 7489 6.6.3 step 3: if no record, use the Organizational Domain */
+
+if (!(s = dmarc_lookup_regdom(dmarc_header_from_sender)))
+  return NULL;
+
+/* RFC 7489 6.6.3 step 3: if the Organizational Domain differs */
+
+if (Ustrcmp(s, dmarc_header_from_sender) == 0)
+  return NULL;
+
+return dmarc_dns_lookup(*used_dom_p = s);
+}
+
+
+void
+dmarc_write_history_file(const gstring * dkim_history_buffer)
+{
+int history_file_fd = -1;
+uschar * s;
+gstring * g;
+
+GET_OPTION("dmarc_history_file");
+if (!(s = dmarc_history_file) || !(s = expand_string(s)) || !*s)
+  {
+  DEBUG(receive) debug_printf_indent("DMARC history file not set\n");
+  return;
+  }
+if (!host_checking)	/* -bh mode: nothing written except debug */
+  if ((history_file_fd = log_open_as_exim(s)) < 0)
+    {
+    log_write(LOG_MAIN|LOG_PANIC,
+	      "failure to create DMARC history file: %s: %s",
+	      s, strerror(errno));
+    return;
+    }
+
+/* Generate the contents of the history file entry */
+
+g = string_fmt_append(NULL,
+  "job %s\n"
+  "reporter %s\n"
+  "received %ld\n"
+  "ipaddr %s\n"
+  "from %s\n"
+  "mfrom %s\n",
+  message_id, primary_hostname, time(NULL), sender_host_address,
+  dmarc_header_from_sender, expand_string(US"$sender_address_domain"));
+
+if (dmarc_spf_ares_result != ARES_RESULT_UNDEFINED)
+  g = string_fmt_append(g, "spf %d\n", dmarc_spf_ares_result);
+
+if (dkim_history_buffer)
+  g = string_fmt_append(g, "%Y", dkim_history_buffer);
+
+g = string_fmt_append(g, "pdomain %s\n"
+			  "policy %d\n",
+  dmarc_used_domain, dmarc_policy);
+
+if (dmarc_rua)
+  for (uschar ** ss = dmarc_rua; *ss; ss++)
+    g = string_fmt_append(g, "rua %s\n", *ss);
+else
+  g = string_catn(g, US"rua -\n", 6);
+
+/* policy tag values */
+g = string_fmt_append(g, "pct %d\n"
+			  "adkim %d\n"
+			  "aspf %d\n"
+			  "p %d\n"
+			  "sp %d\n",
+  dmarc_pct, dmarc_adkim, dmarc_aspf, dmarc_dom_policy, dmarc_subdom_policy);
+
+g = string_fmt_append(g, "align_dkim %d\n"
+			  "align_spf %d\n"
+			  "action %d\n",
+  dmarc_dkim_alignment, dmarc_spf_alignment, dmarc_action);
+
+#ifdef DMARC_SUPPORTS_ARC
+  {
+# ifdef EXPERIMENTAL_ARC
+  const uschar * s;
+  gstring * g2 = NULL;
+  typedef const uschar * (*fn_t)(gstring **);
+
+  if (!dmarc_arc_mod_info)
+    dmarc_arc_mod_info = misc_mod_findonly(US"arc");
+
+  if (  dmarc_arc_mod_info
+     && (s = (((fn_t *) dmarc_arc_mod_info->functions)[ARC_ARCSET_INFO]) (&g2)))
+    {
+    int i = Ustrcmp(s, "pass") == 0 ? ARES_RESULT_PASS
+	    : Ustrcmp(s, "fail") == 0 ? ARES_RESULT_FAIL
+	    : ARES_RESULT_UNKNOWN;
+
+    g = string_fmt_append(g, "arc %d\n"
+			     "arc_policy %d json[%#Y ]\n",
+			  i,
+			  i == ARES_RESULT_PASS ? DMARC_ARC_POLICY_RESULT_PASS
+			  : i == ARES_RESULT_FAIL ? DMARC_ARC_POLICY_RESULT_FAIL
+			  : DMARC_ARC_POLICY_RESULT_UNUSED,
+			  g2
+			  );
+    }
+  else
+
+# endif
+    g = string_fmt_append(g, "arc %d\narc_policy %d json[ ]\n",
+		      ARES_RESULT_UNKNOWN, DMARC_ARC_POLICY_RESULT_UNUSED);
+  }
+#endif
+
+/* Write the contents to the history file */
+DEBUG(receive)
+  {
+  debug_printf_indent("DMARC history data for debugging:\n");
+  expand_level++;
+  debug_printf_indent("%Y", g);
+  expand_level--;
+  debug_printf_indent("DMARC logging history data for opendmarc reporting%s\n",
+	     host_checking ? " (not really)" : "");
+  }
+
+if (!host_checking)
+  {
+  ssize_t written_len = write_to_fd_buf(history_file_fd,
+				string_from_gstring(g), gstring_length(g));
+  if (written_len == 0)
+    {
+    log_write(LOG_MAIN|LOG_PANIC,
+	    "failure to write to DMARC history file: %s", dmarc_history_file);
+    (void)close(history_file_fd);
+    return;
+    }
+  (void)close(history_file_fd);
+  }
+return;
+}
+
+
+
+/*API*/
+static gstring *
+authres_dmarc(gstring * g)
+{
+if (f.dmarc_has_been_checked)
+  {
+  int start = 0;		/* Compiler quietening */
+  DEBUG(acl) start = gstring_length(g);
+  g = string_append(g, 2, US";\n\tdmarc=", dmarc_pass_fail);
+  if (dmarc_header_from_sender)
+    g = string_append(g, 2, US" header.from=", dmarc_header_from_sender);
+  DEBUG(acl) debug_printf_indent("DMARC:\tauthres '%.*s'\n",
+		  gstring_length(g) - start - 3, g->s + start + 3);
+  }
+else
+  DEBUG(acl) debug_printf_indent("DMARC:\tno authres\n");
+return g;
+}
+
+/******************************************************************************/
+/* Module API */
+
+static optionlist dmarc_options[] = {
+  { "dmarc_forensic_sender",    opt_stringptr,      {&dmarc_forensic_sender} },
+  { "dmarc_history_file",       opt_stringptr,      {&dmarc_history_file} },
+  { "dmarc_tld_file",           opt_stringptr,      {&dmarc_tld_file} },
+};
+
+static void * dmarc_functions[] = {
+  [DMARC_PROCESS] =	(void *) dmarc_process,
+  [DMARC_RESULT_INLIST] = (void *) dmarc_result_inlist,
+};
+
+/* dmarc_forensic_sender is provided for visibility of the the option setting
+by moan_send_message. We do not document it as a config-visible $variable.
+We could provide it via a function but there's little advantage. */
+
+static var_entry dmarc_variables[] = {
+  { "dmarc_alignment_dkim",	vtype_bool,		&dmarc_alignment_dkim },
+  { "dmarc_alignment_spf",	vtype_bool,		&dmarc_alignment_spf },
+  { "dmarc_domain_policy",	vtype_stringptr,	&dmarc_domain_policy },
+  { "dmarc_forensic_sender",	vtype_stringptr,	&dmarc_forensic_sender},
+  { "dmarc_status",		vtype_stringptr,	&dmarc_status },
+  { "dmarc_status_text",	vtype_stringptr,	&dmarc_status_text },
+  { "dmarc_used_domain",	vtype_stringptr,	&dmarc_used_domain },
+};
+
+misc_module_info dmarc_module_info =
+{
+  .name =		US"dmarc",
+# ifdef DYNLOOKUP
+  .dyn_magic =		MISC_MODULE_MAGIC,
+# endif
+  .init =		dmarc_init,
+  .lib_vers_report =	dmarc_version_report,
+  .smtp_reset =		dmarc_smtp_reset,
+  .msg_init =		dmarc_msg_init,
+  .authres =		authres_dmarc,
+
+  .options =		dmarc_options,
+  .options_count =	nelem(dmarc_options),
+
+  .functions =		dmarc_functions,
+  .functions_count =	nelem(dmarc_functions),
+
+  .variables =		dmarc_variables,
+  .variables_count =	nelem(dmarc_variables),
+};
+
+#endif	/*EXIM_HAVE_DMARC*/
+/* vi: aw ai sw=2
+ */
diff --git a/src/src/miscmods/dmarc_native.c b/src/src/miscmods/dmarc_native.c
new file mode 100644
index 000000000..7270f0228
--- /dev/null
+++ b/src/src/miscmods/dmarc_native.c
@@ -0,0 +1,699 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* DMARC support.
+   Copyright (c) The Exim Maintainers 2025
+   License: GPL */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "../exim.h"
+
+#ifdef SUPPORT_DMARC
+# error Build cannot support both libopendmarc and native DMARC modules
+#endif
+
+#ifdef EXPERIMENTAL_DMARC_NATIVE
+# ifndef EXIM_HAVE_SPF
+#  error SPF must also be enabled for DMARC
+# elif defined DISABLE_DKIM
+#  error DKIM must also be enabled for DMARC
+# elif !defined LOOKUP_PSL
+#  error PSL lookups must be enabled for DMARC
+# else
+
+#  include "../functions.h"
+#  include "pdkim.h"
+
+extern void		dmarc_send_forensic_report(const uschar **);
+extern const uschar *	dmarc_get_dns_policy_record(uschar **);
+extern void		dmarc_write_history_file(const gstring *);
+extern const uschar *	dmarc_lookup_regdom(const uschar *);
+
+
+static const pcre2_code * dmarc_regex_uri = NULL;
+static const pcre2_code * dmarc_regex_pct = NULL;
+static const pcre2_code * dmarc_regex_ri = NULL;
+static const pcre2_code * dmarc_regex_fo = NULL;
+
+BOOL
+dmarc_local_init(void)
+{
+if (!dmarc_regex_uri)
+  dmarc_regex_uri = regex_must_compile(US "^mailto:[^@]+@[^ !]+(?:[ !]|$)",
+							MCS_CACHEABLE, FALSE);
+if (!dmarc_regex_pct)
+  dmarc_regex_pct = regex_must_compile(US "^\\d{1,3}$",  MCS_CACHEABLE, FALSE);
+if (!dmarc_regex_ri)
+  dmarc_regex_ri =  regex_must_compile(US "^\\d{1,10}$", MCS_CACHEABLE, FALSE);
+if (!dmarc_regex_fo)
+  dmarc_regex_fo =  regex_must_compile(US "^[01ds]$",    MCS_CACHEABLE, FALSE);
+return TRUE;
+}
+
+
+#include "../version.h"
+
+gstring *
+dmarc_version_report(gstring * g)
+{
+return string_fmt_append(g, "Library version: dmarc: Exim %s builtin\n",
+			    EXIM_VERSION_STR);
+}
+
+
+int
+dmarc_local_msg_init()
+{
+return OK;
+}
+
+
+/* Convert to comma-sep list to NULL-terminated array of pointers */
+static uschar **
+dmarc_clist_to_array(const uschar * list)
+{
+int cnt = 0, sep = ',';
+const uschar * s = list;
+uschar * buf = store_get(2, list), ** rarray;
+
+while (string_nextinlist(&s, &sep, buf, 1)) cnt++;	/* count the elements */
+rarray = store_get((cnt+1) * sizeof(*rarray), list);
+for (cnt = 0; rarray[cnt] = string_nextinlist(&list, &sep, NULL, 0); ) cnt++;
+return rarray;
+}
+
+
+static void
+dmarc_maybe_send_forensic(const uschar * ruf)
+{
+/* Earlier ACL does not have *required* control=dmarc_enable_forensic */
+if (!f.dmarc_enable_forensic)
+  return;
+
+/* RFC 7489 6.3 - ruf is optional */
+if (!ruf)
+  return;
+
+if (   dmarc_policy == DMARC_POLICY_REJECT
+    && dmarc_action == DMARC_RESULT_REJECT
+   ||  dmarc_policy == DMARC_POLICY_QUARANTINE
+    && dmarc_action == DMARC_RESULT_QUARANTINE
+   ||  dmarc_policy == DMARC_POLICY_NONE
+    && dmarc_action == DMARC_RESULT_REJECT
+   ||  dmarc_policy == DMARC_POLICY_NONE
+    && dmarc_action == DMARC_RESULT_QUARANTINE
+   )
+  {
+/* RFC 7489 6.3 - ruf is a comma-sep list */
+  /* Convert to NULL-terminated array of pointers */
+  const uschar ** rarray = CUSS dmarc_clist_to_array(ruf);
+  dmarc_send_forensic_report(rarray);
+  }
+}
+
+
+/******************************************************************************/
+/* Policy record parsing */
+
+/* Value verification routines: return boolean "good" */
+
+static BOOL dmarc_vfy_vmode(const uschar * val)
+{ return (*val == 's' || *val == 'r') && val[1] == '\0'; }
+static BOOL dmarc_vfy_policy(const uschar * val)
+{ return Ustrcmp(val, "none") == 0
+	|| Ustrcmp(val, "quarantine") == 0
+	|| Ustrcmp(val, "reject") == 0; }
+static BOOL dmarc_vfy_fbl(const uschar * val)
+{
+/* For now, permit a list starting with (a plausible) mailto URI */
+return regex_match(dmarc_regex_uri, val, -1, NULL);
+}
+
+static BOOL dmarc_tag_vfy_adkim(const uschar * val)
+{ return dmarc_vfy_vmode(val); }
+static BOOL dmarc_tag_vfy_aspf(const uschar * val)
+{ return dmarc_vfy_vmode(val); }
+static BOOL dmarc_tag_vfy_fo(const uschar * val)
+{ return regex_match(dmarc_regex_fo, val, -1, NULL); }
+static BOOL dmarc_tag_vfy_p(const uschar * val)
+{ return dmarc_vfy_policy(val); }
+static BOOL dmarc_tag_vfy_pct(const uschar * val)
+{ return regex_match(dmarc_regex_pct, val, -1, NULL); }
+static BOOL dmarc_tag_vfy_rf(const uschar * val)
+{ return Ustrcmp(val, "afrf") == 0; }
+static BOOL dmarc_tag_vfy_ri(const uschar * val)
+{ return regex_match(dmarc_regex_ri, val, -1, NULL); }
+static BOOL dmarc_tag_vfy_rua(const uschar * val)
+{ return dmarc_vfy_fbl(val); }
+static BOOL dmarc_tag_vfy_ruf(const uschar * val)
+{ return dmarc_vfy_fbl(val); }
+static BOOL dmarc_tag_vfy_sp(const uschar * val)
+{ return dmarc_vfy_policy(val); }
+static BOOL dmarc_tag_vfy_v(const uschar * val)
+{ return Ustrcmp(val, "DMARC1") == 0; }
+
+typedef struct dmarc_policy_record {
+  const uschar * adkim;
+  const uschar * aspf;
+  const uschar * fo;
+  const uschar * p;
+  const uschar * pct;
+  const uschar * rf;
+  const uschar * ri;
+  const uschar * rua;
+  const uschar * ruf;
+  const uschar * sp;
+  const uschar * v;
+} dmarc_policy_record;
+
+typedef struct tag {
+  const uschar * name;
+  unsigned	 offset;
+  BOOL		(*verify)(const uschar *);
+} tag;
+#define TAG(field) {.name = US mac_expanded_string(field), \
+		    .offset = offsetof(dmarc_policy_record, field), \
+		    .verify = dmarc_tag_vfy_ ## field }
+tag policy_tags[] = {
+  TAG(adkim),
+  TAG(aspf),
+  TAG(fo),
+  TAG(p),
+  TAG(pct),
+  TAG(rf),
+  TAG(ri),
+  TAG(rua),
+  TAG(ruf),
+  TAG(sp),
+  TAG(v),
+};
+#undef TAG
+
+/* Handle one potential tag
+Return: boolean success; else parsing error
+*/
+static BOOL
+parse_tag(const uschar * tagrecord, dmarc_policy_record * prp)
+{
+const uschar * e = Ustrchr(tagrecord, '='), * s;
+
+/* RFC 6736 3.2 tagspec must have = */
+if (!e)
+  {
+  DEBUG(receive)
+    debug_printf_indent("DMARC: missing '=' for tag in %q\n", tagrecord);
+  return FALSE;
+  }
+
+/* RFC 6736 3.2 ignore whitespace between tag name and = */
+for (s = e; s > tagrecord && isspace(s[-1]); ) s--;
+
+/* RFC 6736 3.2 tag name at least 1 char */
+if (s == tagrecord)
+  {
+  DEBUG(receive)
+    debug_printf_indent("DMARC: missing tag name in %q\n", tagrecord);
+  return FALSE;
+  }
+
+/* search for tag name in our table of known ones */
+for (tag * ptp = policy_tags; ptp < policy_tags + nelem(policy_tags); ptp++)
+ {
+  if (  Ustrncmp(ptp->name, tagrecord, s - tagrecord) == 0
+     && Ustrlen(ptp->name) == s - tagrecord)
+
+    {			/* match; copy tag value to policy record struct */
+    const uschar ** vp = CUSS (US prp + ptp->offset);
+
+// debug_printf_indent("matched %q, off %u\n", tagrecord, ptp->offset);
+
+/* RFC 6736 3.2 ignore whitespace between = and value */
+    s = e + 1;
+    Uskip_whitespace(&s);
+
+    if (ptp->verify(s))
+      {
+      *vp = string_copy(s);
+      return TRUE;
+      }
+    DEBUG(receive)
+      debug_printf_indent("DMARC: bad value for tag %q: %q\n", ptp->name, s);
+    return FALSE;
+    }
+ }
+
+DEBUG(receive)
+  debug_printf_indent("DMARC: no recognised tag in %q\n", tagrecord);
+return FALSE;
+}
+
+static BOOL
+dmarc_local_parse_policy(const uschar * rr, dmarc_policy_record * prp)
+{
+/* RFC 6376 3.2 :- a taglist is a ;-sep list of tagspec */
+int sep = ';';
+
+/* RFC 6736 3.2 :- ignore whitespace preceding tag-name and after value */
+/* RFC 7489 6.3 :- syntax errors and unknown tags are ignored */
+
+for (uschar * tagspec; tagspec = string_nextinlist(&rr, &sep, NULL, 0); )
+  (void) parse_tag(tagspec, prp);
+
+return TRUE;
+}
+
+/******************************************************************************/
+
+static BOOL
+identifier_aligned(const uschar * a, const uschar * b, const uschar * mode)
+{
+BOOL res;
+
+/* RFC 7489 3.3.1 - In strict mode, only an exact match */
+
+if (*mode == 's')
+  res = Ustrcmp(a, b) == 0;
+
+/* RFC 7489 3.3.1 - In relaxed mode, the Organizational Domains of both */
+else
+  {
+  /* - if there is an exact match, the ODs will also match -
+  so check that first to save on regdom lookups. */
+
+  if (Ustrcmp(a, b) == 0)
+    res = TRUE;
+  else
+    {
+    a = dmarc_lookup_regdom(a);
+    b = dmarc_lookup_regdom(b);
+    res = a && b && Ustrcmp(a, b) == 0;
+    }
+  }
+DEBUG(receive)
+  if (res) debug_printf_indent("DMARC aligned(%s) %s %s\n", mode, a, b);
+return res;
+}
+
+
+/* API: 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.
+Called for the first ACL dmarc= condition. */
+
+int
+dmarc_process(void)
+{
+const uschar * rr;
+BOOL has_dmarc_record = TRUE;
+
+dmarc_alignment_spf = dmarc_alignment_dkim = FALSE;
+dmarc_dkim_alignment = DMARC_POLICY_DKIM_ALIGNMENT_FAIL;
+dmarc_spf_alignment =  DMARC_POLICY_SPF_ALIGNMENT_FAIL;
+
+/* ACLs have "control=dmarc_disable_verify" */
+if (f.dmarc_disable_verify || dmarc_abort)
+  return OK;
+
+DEBUG(receive) { debug_printf_indent("DMARC: process\n"); expand_level++; }
+
+/* Store the header From: sender domain for this part of DMARC.
+If there is no from_header string, then it's likely this message
+is locally generated and relying on fixups to add it.  Just skip
+the entire DMARC system if we can't find a From: header....or if
+there was a previous error.  */
+
+if (!header_from)
+  {
+  DEBUG(receive) debug_printf_indent("DMARC: no From: header\n");
+  dmarc_abort = TRUE;
+  }
+else
+  {
+/* RFC 7489 6.6.1 :- extract the domain from the 5322.From */
+  const uschar * end_addr, * s;
+  uschar * errormsg;
+  int dummy, domain;
+
+  f.parse_allow_group = TRUE;
+  end_addr = parse_find_address_end(header_from, FALSE);
+  s = *end_addr
+      ? string_copyn(header_from, end_addr - header_from)
+      : header_from;
+  if ((dmarc_header_from_sender = parse_extract_address(s, &errormsg,
+			      &dummy, &dummy, &domain, FALSE)))
+    dmarc_header_from_sender += domain;
+
+  /* Only use the domain if not empty.  Otherwise, skip out of DMARC. */
+
+  if (!dmarc_header_from_sender || !*dmarc_header_from_sender)
+    {
+    dmarc_status = US"nofrom";
+    dmarc_pass_fail = US"temperror";
+    dmarc_status_text = US"No From: domain found";
+    dmarc_action = DMARC_RESULT_ACCEPT;
+
+    dmarc_abort = TRUE;
+    }
+  }
+
+/* Skip DMARC if connection is SMTP Auth. Preferably this should not be
+hardwired; the admin should instead do this in the ACLs.  */
+
+if (!dmarc_abort && !sender_host_authenticated)
+  {
+/* RFC 7489 6.3 :- defaults for policy record tags */
+  dmarc_policy_record dmarc_parsed = {
+    .adkim =	US"r",
+    .aspf =	US"r",
+    .fo =	US"0",
+    .pct =	US"100",
+    .rf =	US"afrf",
+    .ri =	US"86400",
+    };
+
+  int sr = SPF_RESULT_INVALID /*, spf_origin*/;
+  uschar * spf_human_readable = NULL;
+  const uschar * spf_sender_domain = NULL;
+  unsigned dkim_sig_count = 0;
+  gstring * dkim_history_buffer = NULL;
+  typedef const pdkim_signature * (*sigs_fn_t)(void);
+
+/* RFC 7489 6.6.2 step 2: DMARC policy record from DNS */
+  DEBUG(receive)
+    {
+    debug_printf_indent("DMARC: get policy record\n");
+    expand_level++;
+    }
+
+  /* uses $dmarc_header_from_sender */
+  if (!(rr = dmarc_get_dns_policy_record(&dmarc_used_domain)))
+	/*XXX want to handle nxdomain,temprror etc. here */
+    {
+    DEBUG(receive) debug_printf_indent("DMARC: no record found for %s\n",
+					  dmarc_header_from_sender);
+    dmarc_policy = DMARC_POLICY_ABSENT;
+    dmarc_status = US"norecord";
+    dmarc_pass_fail = US"none";
+    dmarc_status_text = US"No DMARC record";
+    dmarc_action = DMARC_RESULT_ACCEPT;
+
+    has_dmarc_record = FALSE;
+    }
+
+  else if (!dmarc_local_parse_policy(rr, &dmarc_parsed))
+    {
+    DEBUG(receive) debug_printf_indent("DMARC: invalid record found for %s\n",
+					  dmarc_header_from_sender);
+    dmarc_policy = DMARC_POLICY_ABSENT;
+    dmarc_status = US"norecord";
+    dmarc_pass_fail = US"none";
+    dmarc_status_text = US"No DMARC record";
+    dmarc_action = DMARC_RESULT_ACCEPT;
+
+    has_dmarc_record = FALSE;
+    goto out;
+    }
+
+/* RFC 7489 6.6.3 step 6: p/sp checks */
+    if (  !dmarc_parsed.p
+       || !dmarc_tag_vfy_p(dmarc_parsed.p)
+       || dmarc_parsed.sp && !dmarc_tag_vfy_sp(dmarc_parsed.sp)
+       )
+
+/* RFC 7489 6.6.3 step 6: if a valid rua, continue with p=none */
+/*XXX "at least one syntactically valid reporting URI" */
+      if (dmarc_parsed.rua && dmarc_tag_vfy_rua(dmarc_parsed.rua))
+	{
+	DEBUG(receive)
+	  debug_printf_indent("DMARC: invalid p or sp; continue for rua\n");
+	dmarc_parsed.p = US"none";
+	}
+      else
+	{
+	DEBUG(receive)
+	  debug_printf_indent("DMARC: invalid p or sp, and no rua. Abort.\n");
+	dmarc_abort = TRUE;
+	goto out;
+	}
+
+/* RFC 7489 6.6.2 step 3: Perform DKIM signature verification checks */
+  DEBUG(receive)
+    {
+    expand_level--;
+    debug_printf_indent("DMARC: process dkim results\n");
+    expand_level++;
+    }
+
+  /* Now we cycle through the dkim signature results and put into
+  the opendmarc context, further building the DMARC reply. */
+
+  if (has_dmarc_record)
+    for(const pdkim_signature * sig =
+	      (((sigs_fn_t *)dmarc_dkim_mod_info->functions)[DKIM_SIGS_LIST])();
+	sig; sig = sig->next)
+    {
+    int dkim_result, dkim_ares_result, vs, ves;
+
+    dkim_sig_count++;
+    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;
+
+    DEBUG(receive)
+      debug_printf_indent("DMARC: adding DKIM sender domain = %s\n",
+			  sig->domain);
+
+    /* Update the history buffer */
+
+    dkim_ares_result =
+      vs == PDKIM_VERIFY_PASS    ? ARES_RESULT_PASS :
+      vs == PDKIM_VERIFY_FAIL    ? ARES_RESULT_FAIL :
+      vs == PDKIM_VERIFY_NONE    ? ARES_RESULT_NONE :
+      vs == PDKIM_VERIFY_INVALID ?
+       ves == PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE ? ARES_RESULT_PERMERROR :
+       ves == PDKIM_VERIFY_INVALID_BUFFER_SIZE        ? ARES_RESULT_PERMERROR :
+       ves == PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD   ? ARES_RESULT_PERMERROR :
+       ves == PDKIM_VERIFY_INVALID_PUBKEY_IMPORT      ? ARES_RESULT_PERMERROR :
+       ARES_RESULT_UNKNOWN :
+      ARES_RESULT_UNKNOWN;
+
+    dkim_history_buffer = string_fmt_append(dkim_history_buffer,
+      "dkim %s %s %d\n", sig->domain, sig->selector, dkim_ares_result);
+
+    /* Evaluate the sig vs. dmarc requirements */
+
+/* RFC 7489 3.1.1 if any DKIM signature ... verifies. */
+    if (  !dmarc_alignment_dkim
+       && dkim_result == DMARC_POLICY_DKIM_OUTCOME_PASS
+
+/* RFC 7489 3.1.1 dkim alignment: d= tag in dkim sig */
+/* RFC 7489 3.1.1 dkim alignment: 5322.From domain */
+/* RFC 7489 6.3   adkim: DKIM Identifier Alignment mode */
+
+       && (dmarc_alignment_dkim = identifier_aligned(sig->domain,
+				  dmarc_header_from_sender, dmarc_parsed.adkim))
+       )
+      dmarc_dkim_alignment = DMARC_POLICY_DKIM_ALIGNMENT_PASS;
+    }
+  DEBUG(receive) debug_printf_indent("DMARC: %u dkim sig%s\n",
+				dkim_sig_count, dkim_sig_count == 1 ? "" : "s");
+
+  DEBUG(receive)
+    {
+    expand_level--;
+    debug_printf_indent("DMARC: process spf results\n");
+    expand_level++;
+    }
+
+/* RFC 7489 6.6.2 step 4: Perform SPF validation checks */
+
+  if (has_dmarc_record)
+    {
+    int spf_result;
+    typedef int (*fn_t)(uschar **);
+
+    if (dmarc_spf_mod_info)
+      sr = ((fn_t *) dmarc_spf_mod_info->functions)[SPF_GET_RESULTS]
+							  (&spf_human_readable);
+
+    spf_sender_domain = expand_string(CUS"$spf_used_domain");
+
+    if (sr == SPF_RESULT_INVALID)
+      {
+      DEBUG(receive) debug_printf_indent("DMARC: spf result 'invalid'\n");
+
+      spf_result = DMARC_POLICY_SPF_OUTCOME_NONE;
+      dmarc_spf_ares_result = ARES_RESULT_UNKNOWN;
+      /* spf_origin = DMARC_POLICY_SPF_ORIGIN_HELO; not used, and we'd want to ask the spf impl really */
+      spf_human_readable = US"";
+      }
+    else
+      {
+      spf_result = sr == SPF_RESULT_NEUTRAL  ? DMARC_POLICY_SPF_OUTCOME_NONE :
+			 sr == SPF_RESULT_PASS     ? DMARC_POLICY_SPF_OUTCOME_PASS :
+			 sr == SPF_RESULT_FAIL     ? DMARC_POLICY_SPF_OUTCOME_FAIL :
+			 sr == SPF_RESULT_SOFTFAIL ? DMARC_POLICY_SPF_OUTCOME_TMPFAIL :
+			 DMARC_POLICY_SPF_OUTCOME_NONE;
+      dmarc_spf_ares_result = sr == SPF_RESULT_NEUTRAL   ? ARES_RESULT_NEUTRAL :
+			      sr == SPF_RESULT_PASS      ? ARES_RESULT_PASS :
+			      sr == SPF_RESULT_FAIL      ? ARES_RESULT_FAIL :
+			      sr == SPF_RESULT_SOFTFAIL  ? ARES_RESULT_SOFTFAIL :
+			      sr == SPF_RESULT_NONE      ? ARES_RESULT_NONE :
+			      sr == SPF_RESULT_TEMPERROR ? ARES_RESULT_TEMPERROR :
+			      sr == SPF_RESULT_PERMERROR ? ARES_RESULT_PERMERROR :
+			      ARES_RESULT_UNKNOWN;
+      /*XXX hmm, spf_origin never used? */
+      /* spf_origin = DMARC_POLICY_SPF_ORIGIN_MAILFROM; */
+      DEBUG(receive)
+	debug_printf_indent("DMARC: using SPF sender domain = %s\n",
+					  spf_sender_domain);
+      }
+    if (!spf_sender_domain || !*spf_sender_domain)
+      dmarc_abort = TRUE;
+    if (!dmarc_abort)
+      {
+  /* RFC 7489 3.1.1 spf alignment: the SPF-authenticated domain */
+  /* RFC 7489 3.1.1 spf alignment: 5322.From domain */
+  /* RFC 7489 6.3   aspf: SPF Identifier Alignment mode */
+
+      if (  spf_result == DMARC_POLICY_SPF_OUTCOME_PASS
+	 && (dmarc_alignment_spf = identifier_aligned(spf_sender_domain,
+				  dmarc_header_from_sender, dmarc_parsed.aspf))
+	 )
+	dmarc_spf_alignment = DMARC_POLICY_SPF_ALIGNMENT_PASS;
+      }
+    }
+
+  DEBUG(receive)
+    {
+    expand_level--;
+    debug_printf_indent("DMARC: finished spf\n");
+    }
+
+  /* Store the policy string in an expandable variable. */
+
+/* RFC 7489 is unclear how to obtain the policy-string that is to be used.
+The decription of tags p & sp in 6.3 uses the term "domain queried". I assume
+that is the portion of the DNS lookup key *after* the prepended "_dmarc."
+which returned the DMARC RR being used (so it could be the Organizational
+Domain, per 6.6.3 bullet 3, rather than the 5322.From domain).
+
+Given that assumption: if dom-used != 5322.From.dom and there is an sp,
+use the sp.  Otherwise use the p. */
+
+  dmarc_domain_policy = dmarc_parsed.sp
+			&& dmarc_used_domain != dmarc_header_from_sender
+		      ? dmarc_parsed.sp : dmarc_parsed.p;
+
+/* RFC 7489 6.6.2 step 5 - if either the spf or dkim shows alignment, pass */
+  if (dmarc_alignment_spf || dmarc_alignment_dkim)
+    {							/* Explicit accept */
+    dmarc_policy = DMARC_POLICY_PASS;
+    dmarc_status = US"accept";
+    dmarc_pass_fail = US"pass";
+    dmarc_status_text = US"Accept";
+    dmarc_action = DMARC_RESULT_ACCEPT;
+    }
+
+/* RFC 7489 6.6.2 step 6 - dispose of no-alignment per discovered policy */
+  else
+    {
+    dmarc_status = dmarc_domain_policy;
+    if (Ustrcmp(dmarc_domain_policy, "none") == 0)
+      {							/* Accept and report */
+      dmarc_policy = DMARC_POLICY_NONE;
+      dmarc_pass_fail = US"none";
+      dmarc_status_text = US"None, Accept";
+      dmarc_action = DMARC_RESULT_ACCEPT;
+      }
+    else if (Ustrcmp(dmarc_domain_policy, "quarantine") == 0)
+      {							/* Explicit quarantine*/
+      dmarc_policy = DMARC_POLICY_QUARANTINE;
+      dmarc_pass_fail = US"fail";
+      dmarc_status_text = US"Quarantine";
+      dmarc_action = DMARC_RESULT_QUARANTINE;
+      }
+    else if (Ustrcmp(dmarc_domain_policy, "reject") == 0)
+      {							/* Explicit reject */
+      dmarc_policy = DMARC_POLICY_REJECT;
+      dmarc_pass_fail = US"fail";
+      dmarc_status_text = US"Reject";
+      dmarc_action = DMARC_RESULT_REJECT;
+      }
+    else	/* should never happen; tag values were validated */
+      {		/* could use similar for dns tmpfail */
+      dmarc_status = dmarc_pass_fail = US"temperror";
+      dmarc_status_text = US"Internal Policy Error";
+      dmarc_action = DMARC_RESULT_TEMPFAIL;
+      }
+    }
+
+  if (has_dmarc_record && !dmarc_abort)
+    {
+    DEBUG(receive) 
+      debug_printf_indent("DMARC results: spf_domain=%s dmarc_domain=%s "
+			   "spf_align=%s dkim_align=%s enforcement='%s'",
+			   spf_sender_domain, dmarc_used_domain,
+			   dmarc_alignment_spf  ? "yes" : "no",
+			   dmarc_alignment_dkim ? "yes" : "no",
+			   dmarc_status_text);
+
+    /* History file, for later aggregate reporting. */
+
+    dmarc_pct = atoi(CCS dmarc_parsed.pct);
+
+    dmarc_adkim = dmarc_parsed.adkim
+		? *dmarc_parsed.adkim : DMARC_RECORD_A_UNSPECIFIED;
+    dmarc_aspf = dmarc_parsed.aspf 
+		? *dmarc_parsed.aspf : DMARC_RECORD_A_UNSPECIFIED;
+    dmarc_dom_policy = dmarc_parsed.p
+		? *dmarc_parsed.p : DMARC_RECORD_P_UNSPECIFIED;
+    dmarc_subdom_policy = dmarc_parsed.sp
+		? *dmarc_parsed.sp : DMARC_RECORD_P_UNSPECIFIED;
+
+/* RFC 7489 6.3 - rua is a comma-sep list */
+    dmarc_rua = dmarc_clist_to_array(dmarc_parsed.rua);
+
+    dmarc_write_history_file(dkim_history_buffer);
+
+    /* Forensic reporting */
+
+    dmarc_maybe_send_forensic(dmarc_parsed.ruf);
+    }
+  }
+
+out:
+  DEBUG(receive)
+    {
+    expand_level--;
+    debug_printf_indent("DMARC: finished process, status %q\n", dmarc_status);
+    }
+  return OK;
+}
+
+static const uschar *
+dmarc_exim_expand_defaults(void)
+{
+return f.dmarc_disable_verify ?  US"off" : US"none";
+}
+
+/*API
+Check result against list.  Return OK/FAIL/DEFER.
+*/
+int
+dmarc_result_inlist(const uschar * const * listp)
+{
+const uschar * res =
+  f.dmarc_disable_verify ? dmarc_exim_expand_defaults() : dmarc_status;
+return match_isinlist(res, listp, 0, NULL, NULL, MCL_STRING, TRUE, NULL);
+}
+
+
+# endif /* have SPF & DKIM */
+#endif /* EXPERIMENTAL_DMARC_NATIVE */
+/* vi: aw ai sw=2
+ */
diff --git a/src/src/miscmods/dscp.c b/src/src/miscmods/dscp.c
new file mode 100644
index 000000000..ee7c00128
--- /dev/null
+++ b/src/src/miscmods/dscp.c
@@ -0,0 +1,278 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* DSCP support for Exim
+   Copyright (c) The Exim Maintainers - 2025
+   License: GPL
+   SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include "../exim.h"
+#if defined SUPPORT_DSCP
+
+# include "../functions.h"
+
+/******************************************************************************/
+/* Utility functions */
+
+/*************************************************
+*    Lookup address family of potential socket   *
+*************************************************/
+
+/* Given a file-descriptor, check to see if it's a socket and, if so,
+return the address family; detects IPv4 vs IPv6.  If not a socket then
+return -1.
+
+The value 0 is typically AF_UNSPEC, which should not be seen on a connected
+fd.  If the return is -1, the errno will be from getsockname(); probably
+ENOTSOCK or ECONNRESET.
+
+Arguments:     socket-or-not fd
+Returns:       address family or -1
+*/
+
+static int
+ip_get_address_family(int fd)
+{
+struct sockaddr_storage ss;
+socklen_t sslen = sizeof(ss);
+
+if (getsockname(fd, (struct sockaddr *) &ss, &sslen) < 0)
+  return -1;
+
+return (int) ss.ss_family;
+}
+
+
+
+/*************************************************
+*       Lookup DSCP settings for a socket        *
+*************************************************/
+
+struct dscp_name_tableentry {
+  const uschar *name;
+  int value;
+};
+/* Keep both of these tables sorted! */
+static struct dscp_name_tableentry dscp_table[] = {
+#ifdef IPTOS_DSCP_AF11
+    { CUS"af11", IPTOS_DSCP_AF11 },
+    { CUS"af12", IPTOS_DSCP_AF12 },
+    { CUS"af13", IPTOS_DSCP_AF13 },
+    { CUS"af21", IPTOS_DSCP_AF21 },
+    { CUS"af22", IPTOS_DSCP_AF22 },
+    { CUS"af23", IPTOS_DSCP_AF23 },
+    { CUS"af31", IPTOS_DSCP_AF31 },
+    { CUS"af32", IPTOS_DSCP_AF32 },
+    { CUS"af33", IPTOS_DSCP_AF33 },
+    { CUS"af41", IPTOS_DSCP_AF41 },
+    { CUS"af42", IPTOS_DSCP_AF42 },
+    { CUS"af43", IPTOS_DSCP_AF43 },
+    { CUS"ef", IPTOS_DSCP_EF },
+#endif
+#ifdef IPTOS_LOWCOST
+    { CUS"lowcost", IPTOS_LOWCOST },
+#endif
+    { CUS"lowdelay", IPTOS_LOWDELAY },
+#ifdef IPTOS_MINCOST
+    { CUS"mincost", IPTOS_MINCOST },
+#endif
+    { CUS"reliability", IPTOS_RELIABILITY },
+    { CUS"throughput", IPTOS_THROUGHPUT }
+};
+static int dscp_table_size =
+  sizeof(dscp_table) / sizeof(struct dscp_name_tableentry);
+
+/* DSCP values change by protocol family, and so do the options used for
+setsockopt(); this utility does all the lookups.  It takes an unexpanded
+option string, expands it, strips off affix whitespace, then checks if it's
+a number.  If all of what's left is a number, then that's how the option will
+be parsed and success/failure is a range check.  If it's not all a number,
+then it must be a supported keyword.
+
+Arguments:
+  dscp_name   a string, so far unvalidated
+  af          address_family in use
+  level       setsockopt level to use
+  optname     setsockopt name to use
+  dscp_value  value for dscp_name
+
+Returns: TRUE if okay to setsockopt(), else FALSE
+
+*level and *optname may be set even if FALSE is returned
+*/
+
+static BOOL
+dscp_lookup(const uschar * dscp_name, int af,
+    int * level, int * optname, int * dscp_value)
+{
+uschar * dscp_lookup, * p;
+int first, last;
+long rawlong;
+
+if (af == AF_INET)
+  { *level = IPPROTO_IP; *optname = IP_TOS; }
+#if HAVE_IPV6 && defined(IPV6_TCLASS)
+else if (af == AF_INET6)
+  { *level = IPPROTO_IPV6; *optname = IPV6_TCLASS; }
+#endif
+else
+  {
+  DEBUG(transport)
+    debug_printf("Unhandled address family %d in dscp_lookup()\n", af);
+  return FALSE;
+  }
+if (!dscp_name)
+  {
+  DEBUG(transport)
+    debug_printf("[empty DSCP]\n");
+  return FALSE;
+  }
+dscp_lookup = expand_string_copy(dscp_name);
+if (!dscp_lookup || !*dscp_lookup)
+  return FALSE;
+
+p = dscp_lookup + Ustrlen(dscp_lookup) - 1;
+while (isspace(*p)) *p-- = '\0';
+while (isspace(*dscp_lookup) && dscp_lookup < p) dscp_lookup++;
+if (*dscp_lookup == '\0')
+  return FALSE;
+
+rawlong = Ustrtol(dscp_lookup, &p, 0);
+if (p != dscp_lookup && *p == '\0')
+  {
+  /* We have six bits available, which will end up shifted to fit in 0xFC mask.
+  RFC 2597 defines the values unshifted. */
+  if (rawlong < 0 || rawlong > 0x3F)
+    {
+    DEBUG(transport)
+      debug_printf("DSCP value %ld out of range, ignored.\n", rawlong);
+    return FALSE;
+    }
+  *dscp_value = rawlong << 2;
+  return TRUE;
+  }
+
+first = 0;
+last = dscp_table_size;
+while (last > first)
+  {
+  int middle = (first + last)/2;
+  int c = Ustrcmp(dscp_lookup, dscp_table[middle].name);
+  if (c == 0)
+    {
+    *dscp_value = dscp_table[middle].value;
+    return TRUE;
+    }
+  else if (c > 0)
+    first = middle + 1;
+  else
+    last = middle;
+  }
+return FALSE;
+}
+
+/******************************************************************************/
+
+/*API
+Set DSCP on stdin.  Called from ACL control.
+Return error message on fail, NULL on ok.
+*/
+
+static uschar *
+dscp_acl(const uschar * control, const uschar * opt)
+{
+int af, socklevel, optname, value;
+
+if (*opt != '/')
+  return string_sprintf("syntax error in \"control=%s\"", control);
+
+/* 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. */
+if (smtp_in_fd < 0) return US"no stdin";
+if ((af = ip_get_address_family(smtp_in_fd)) < 0)
+  {
+  HDEBUG(acl) debug_printf_indent(
+    "smtp input is probably not a socket [%s], not setting DSCP\n",
+    strerror(errno));
+  return NULL;
+  }
+if (!dscp_lookup(++opt, af, &socklevel, &optname, &value))
+  return string_sprintf("unrecognised DSCP value in \"control=%s\"", control);
+
+value = setsockopt(smtp_in_fd, socklevel, optname,
+		    &value, sizeof(value));
+HDEBUG(acl)
+  if (value < 0)
+    debug_printf_indent("failed to set input DSCP[%s]: %s\n",
+      opt, strerror(errno));
+  else
+    debug_printf_indent("set input DSCP to %q\n", opt);
+return NULL;
+}
+
+
+/*API
+Set DSCP on given socket; called from smtp transport.
+Can silently fail.
+*/
+
+static void
+dscp_transport(int sock, const uschar * dscp_str, int host_af)
+{
+int dscp_value, dscp_level, dscp_option;
+
+if (  dscp_str
+   && dscp_lookup(dscp_str, host_af, &dscp_level, &dscp_option, &dscp_value)
+   )
+  {
+  HDEBUG(transport|acl|v)
+    debug_printf_indent("DSCP %q=%x ", dscp_str, dscp_value);
+
+  if (setsockopt(sock, dscp_level, dscp_option, &dscp_value, sizeof(dscp_value)) < 0)
+    HDEBUG(transport|acl|v)
+      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_str, AF_INET, &dscp_level, &dscp_option, &dscp_value)
+     )
+    (void) setsockopt(sock, dscp_level, dscp_option, &dscp_value, sizeof(dscp_value));
+  }
+}
+
+
+/*API: output known DSCP names */
+static void
+dscp_keywords(FILE * stream)
+{
+for (int i = 0; i < dscp_table_size; ++i)
+  fprintf(stream, "%s\n", dscp_table[i].name);
+}
+
+/******************************************************************************/
+/* Module API */
+
+static void * dscp_functions[] = {
+  [DSCP_ACL] =		(void *) dscp_acl,
+  [DSCP_TRANSPORT] =	(void *) dscp_transport,
+  [DSCP_KEYWORDS] =	(void *) dscp_keywords,
+};
+
+misc_module_info dscp_module_info =
+{
+  .name =		US"dscp",
+# ifdef DYNLOOKUP
+  .dyn_magic =		MISC_MODULE_MAGIC,
+# endif
+  .functions =		dscp_functions,
+  .functions_count =	nelem(dscp_functions),
+};
+
+#endif /* SUPPORT_DSCP */
+/* vi: aw ai sw=2
+ */
diff --git a/src/src/miscmods/dscp_api.h b/src/src/miscmods/dscp_api.h
new file mode 100644
index 000000000..ce355319d
--- /dev/null
+++ b/src/src/miscmods/dscp_api.h
@@ -0,0 +1,16 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) The Exim Maintainers 2025 */
+/* See the file NOTICE for conditions of use and distribution. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/* API definitions for the arcmodule */
+
+
+/* Function table entry numbers */
+
+#define	DSCP_ACL		0
+#define	DSCP_TRANSPORT		1
+#define	DSCP_KEYWORDS		2
diff --git a/src/src/miscmods/exim_filter.c b/src/src/miscmods/exim_filter.c
index 08124b3f7..53e2cc366 100644
--- a/src/src/miscmods/exim_filter.c
+++ b/src/src/miscmods/exim_filter.c
@@ -59,7 +59,6 @@ static int  expect_endif;
 static int  had_else_endif;
 static int  log_fd;
 static int  log_mode;
-static int  output_indent;
 static BOOL filter_delivered;
 static BOOL finish_obeyed;
 static BOOL seen_force;
@@ -787,17 +786,6 @@ return nextsigchar(ptr, TRUE);
 
 
 
-/*************************************************
-*             Output the current indent          *
-*************************************************/
-
-static void
-indent(void)
-{
-DEBUG(D_filter) for (int i = 0; i < output_indent; i++) debug_printf(" ");
-}
-
-
 
 /*************************************************
 *          Condition printer: for debugging      *
@@ -1673,12 +1661,8 @@ switch (c->type)
 
       if (filter_thisaddress)
 	{
-	if ((filter_test != FTEST_NONE && debug_selector != 0) ||
-	    (debug_selector & D_filter) != 0)
-	  {
-	  indent();
+	if ((filter_test != FTEST_NONE && ANY_DEBUG) || IS_DEBUG(filter))
 	  debug_printf_indent("Extracted address %s\n", filter_thisaddress);
-	  }
 	yield = test_condition(c->right.c, FALSE);
 	}
 
@@ -1720,7 +1704,7 @@ switch (c->type)
 	break;
 
       case cond_contains:
-	yield = strstric_c(exp[0], exp[1], FALSE) != NULL;
+	yield = strstric(exp[0], exp[1], FALSE) != NULL;
 	break;
 
       case cond_CONTAINS:
@@ -1752,8 +1736,7 @@ switch (c->type)
 	const pcre2_code * re;
 	mcs_flags flags = textonly_re ? MCS_CACHEABLE : MCS_NOFLAGS;
 
-	if ((filter_test != FTEST_NONE && debug_selector != 0) ||
-	    (debug_selector & D_filter) != 0)
+	if ((filter_test != FTEST_NONE && ANY_DEBUG) || IS_DEBUG(filter))
 	  {
 	  debug_printf_indent("Match expanded arguments:\n");
 	  debug_printf_indent("  Subject = %s\n", exp[0]);
@@ -1788,10 +1771,8 @@ switch (c->type)
     break;
   }
 
-if ((filter_test != FTEST_NONE && debug_selector != 0) ||
-    (debug_selector & D_filter) != 0)
+if ((filter_test != FTEST_NONE && ANY_DEBUG) || IS_DEBUG(filter))
   {
-  indent();
   debug_printf_indent("%sondition is %s: ",
     toplevel ? "C" : "Sub-c",
     yield == c->testfor ? "true" : "false");
@@ -1940,7 +1921,6 @@ while (commands)
 
       if (filter_test != FTEST_NONE)
 	{
-	indent();
 	printf("%seliver message to: %s%s%s%s\n",
 	  commands->seen ? "D" : "Unseen d",
 	  expargs[0],
@@ -1953,12 +1933,13 @@ while (commands)
 
       else
 	{
-	DEBUG(D_filter) debug_printf_indent("Filter: %sdeliver message to: %s%s%s%s\n",
-	  commands->seen ? "" : "unseen ",
-	  expargs[0],
-	  commands->noerror ? " (noerror)" : "",
-	  s ? " errors_to " : "",
-	  s ? s : US"");
+	DEBUG(filter)
+	  debug_printf_indent("Filter: %sdeliver message to: %s%s%s%s\n",
+	    commands->seen ? "" : "unseen ",
+	    expargs[0],
+	    commands->noerror ? " (noerror)" : "",
+	    s ? " errors_to " : "",
+	    s ? s : US"");
 
 	/* Create the new address and add it to the chain, setting the
 	af_ignore_error flag if necessary, and the errors address, which can be
@@ -1980,7 +1961,6 @@ while (commands)
 
       if (filter_test != FTEST_NONE)
 	{
-	indent();
 	if (mode < 0)
 	  printf("%save message to: %s%s\n",
 	    commands->seen ? "S" : "Unseen s",
@@ -1999,7 +1979,7 @@ while (commands)
 	if (s[0] != '/' && filter_options & RDO_PREPEND_HOME &&
 	    deliver_home && *deliver_home)
 	  s = string_sprintf("%s/%s", deliver_home, s);
-	DEBUG(D_filter) debug_printf_indent("Filter: %ssave message to: %s%s\n",
+	DEBUG(filter) debug_printf_indent("Filter: %ssave message to: %s%s\n",
 	  commands->seen ? "" : "unseen ",
 	  s, commands->noerror ? " (noerror)" : "");
 
@@ -2021,14 +2001,13 @@ while (commands)
       s = string_copy(commands->args[0].u);
       if (filter_test != FTEST_NONE)
 	{
-	indent();
 	printf("%sipe message to: %s%s\n",
 	  commands->seen ? "P" : "Unseen p",
 	  s, commands->noerror? " (noerror)" : "");
 	}
       else /* Ensure pipe command starts with | */
 	{
-	DEBUG(D_filter) debug_printf_indent("Filter: %spipe message to: %s%s\n",
+	DEBUG(filter) debug_printf_indent("Filter: %spipe message to: %s%s\n",
 	  commands->seen ? "" : "unseen ", s,
 	  commands->noerror ? " (noerror)" : "");
 	if (s[0] != '|') s = string_sprintf("|%s", s);
@@ -2079,7 +2058,6 @@ while (commands)
       log_filename = expargs[0];
       if (filter_test != FTEST_NONE)
 	{
-	indent();
 	printf("%sogfile %s\n", commands->seen ? "Seen l" : "L", log_filename);
 	}
       break;
@@ -2089,7 +2067,6 @@ while (commands)
 
       if (filter_test != FTEST_NONE)
 	{
-	indent();
 	printf("%sogwrite \"%s\"\n", commands->seen ? "Seen l" : "L",
 	  string_printing(s));
 	}
@@ -2099,17 +2076,17 @@ while (commands)
 
       else if (filter_options & RDO_LOG)   /* Locked out */
 	{
-	DEBUG(D_filter)
+	DEBUG(filter)
 	  debug_printf_indent("filter log command aborted: euid=%ld\n",
-	  (long int)geteuid());
+			      (long int)geteuid());
 	*error_pointer = US"logwrite command forbidden";
 	return FF_ERROR;
 	}
       else if (filter_options & RDO_REALLOG)
 	{
 	int len;
-	DEBUG(D_filter) debug_printf_indent("writing filter log as euid %ld\n",
-	  (long int)geteuid());
+	DEBUG(filter) debug_printf_indent("writing filter log as euid %ld\n",
+					  (long int)geteuid());
 	if (log_fd < 0)
 	  {
 	  if (!log_filename)
@@ -2135,7 +2112,7 @@ while (commands)
 	  }
 	}
       else
-	DEBUG(D_filter)
+	DEBUG(filter)
 	  debug_printf_indent("skipping logwrite (verifying or testing)\n");
       break;
 
@@ -2210,21 +2187,19 @@ while (commands)
 
       if (filter_test != FTEST_NONE)
 	{
-	indent();
 	printf("%c%s text \"%s\"\n", toupper(ff_name[0]), ff_name+1, fmsg);
 	}
       else
-        DEBUG(D_filter) debug_printf_indent("Filter: %s %q\n", ff_name, fmsg);
+        DEBUG(filter) debug_printf_indent("Filter: %s %q\n", ff_name, fmsg);
       return ff_ret;
 
     case FINISH_COMMAND:
       if (filter_test != FTEST_NONE)
 	{
-	indent();
 	printf("%sinish\n", commands->seen ? "Seen f" : "F");
 	}
       else
-	DEBUG(D_filter) debug_printf_indent("Filter: %sfinish\n",
+	DEBUG(filter) debug_printf_indent("Filter: %sfinish\n",
 	  commands->seen ? " Seen " : "");
       finish_obeyed = TRUE;
       return filter_delivered ? FF_DELIVERED : FF_NOTDELIVERED;
@@ -2238,10 +2213,10 @@ while (commands)
 	  ok = FF_ERROR;
 	else
 	  {
-	  output_indent += 2;
+	  expand_level += 2;
 	  ok = interpret_commands(commands->args[condition_value ? 1:2].f,
 	    generated);
-	  output_indent -= 2;
+	  expand_level -= 2;
 	  }
 	filter_thisaddress = save_address;
 	if (finish_obeyed  ||  ok != FF_DELIVERED && ok != FF_NOTDELIVERED)
@@ -2261,7 +2236,7 @@ while (commands)
 	  if (filter_test != FTEST_NONE)
 	    printf("%s command ignored because return_path is empty\n",
 	      command_list[commands->command]);
-	  else DEBUG(D_filter)
+	  else DEBUG(filter)
 	    debug_printf_indent("%s command ignored because return_path "
 	    "is empty\n", command_list[commands->command]);
 	  break;
@@ -2349,7 +2324,6 @@ while (commands)
 	if (filter_test != FTEST_NONE)
 	  {
 	  const 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)" : "",
@@ -2360,7 +2334,7 @@ while (commands)
 	    if (arg)
 	      {
 	      int len = Ustrlen(mailargs[i]);
-	      int indent = debug_selector != 0 ? output_indent : 0;
+	      int indent = ANY_DEBUG ? expand_level : 0;
 	      while (len++ < 7 + indent) printf(" ");
 	      printf("%s: %s%s\n", mailargs[i], string_printing(arg),
 		(  commands->args[mailarg_index_expand].u
@@ -2389,7 +2363,7 @@ while (commands)
 	      break;
 	      }
 
-	  DEBUG(D_filter)
+	  DEBUG(filter)
 	    {
 	    debug_printf_indent("Filter: %smail to: %s%s%s\n",
 	      commands->seen ? "seen " : "",
@@ -2402,10 +2376,13 @@ while (commands)
 	      if (arg)
 		{
 		int len = Ustrlen(mailargs[i]);
-		while (len++ < 15) debug_printf_indent(" ");
-		debug_printf_indent("%s: %s%s\n", mailargs[i], string_printing(arg),
+		if (len > 14) len = 14;
+		expand_level += len;
+		debug_printf_indent("%s: %s%s\n", mailargs[i],
+		  string_printing(arg),
 		  (commands->args[mailarg_index_expand].u != NULL &&
 		    Ustrcmp(mailargs[i], "file") == 0)? " (expanded)" : "");
+		expand_level -= len;
 		}
 	      }
 	    }
@@ -2419,15 +2396,14 @@ while (commands)
 	  tt = to;
 	  while (*tt)
 	    {
-	    uschar * ss = parse_find_address_end(tt, FALSE), * errmess;
-	    const uschar * recipient;
+	    const uschar * ss = parse_find_address_end(tt, FALSE);
+	    const uschar * ttt, * recipient;
+	    uschar * errmess;
 	    int start, end, domain;
-	    int temp = *ss;
 
-	    *ss = 0;
-	    recipient = parse_extract_address(tt, &errmess,
+	    ttt = *ss ? string_copyn(tt, ss - tt) : tt;
+	    recipient = parse_extract_address(ttt, &errmess,
 					      &start, &end, &domain, FALSE);
-	    *ss = temp;
 
 	    /* Ignore empty addresses and errors; an error will occur later if
 	    there's something really bad. */
@@ -2501,10 +2477,10 @@ while (commands)
 	break;
 
     case TESTPRINT_COMMAND:
-	if (filter_test != FTEST_NONE || (debug_selector & D_filter) != 0)
+	if (filter_test != FTEST_NONE || IS_DEBUG(filter))
 	  {
 	  const uschar * t = string_printing(expargs[0]);
-	  if (filter_test == FTEST_NONE)
+	  DEBUG(filter)
 	    debug_printf_indent("Filter: testprint: %s\n", t);
 	  else
 	    printf("Testprint: %s\n", t);
@@ -2550,14 +2526,13 @@ const uschar *save_headers_charset = headers_charset;
 filter_cmd *commands = NULL;
 filter_cmd **lastcmdptr = &commands;
 
-DEBUG(D_route) debug_printf("Filter: start of processing\n");
+DEBUG(route) debug_printf_indent("Filter: start of processing\n");
 acl_level++;
 
 /* Initialize "not in an if command", set the global flag that is always TRUE
 while filtering, and zero the variables. */
 
 expect_endif = 0;
-output_indent = 0;
 f.filter_running = TRUE;
 for (i = 0; i < FILTER_VARIABLE_COUNT; i++) filter_n[i] = 0;
 
@@ -2585,7 +2560,7 @@ ptr = nextsigchar(ptr, TRUE);
 if (read_command_list(&ptr, &lastcmdptr, FALSE))
   yield = interpret_commands(commands, generated);
 
-if (filter_test != FTEST_NONE || (debug_selector & D_filter) != 0)
+if (filter_test != FTEST_NONE || IS_DEBUG(filter))
   {
   uschar *s = US"";
   switch(yield)
@@ -2631,7 +2606,7 @@ f.filter_running = FALSE;
 headers_charset = save_headers_charset;
 
 acl_level--;
-DEBUG(D_route) debug_printf("Filter: end of processing\n");
+DEBUG(route) debug_printf_indent("Filter: end of processing\n");
 return yield;
 }
 
diff --git a/src/src/miscmods/pam.c b/src/src/miscmods/pam.c
index 842282ba5..9a44b07b8 100644
--- a/src/src/miscmods/pam.c
+++ b/src/src/miscmods/pam.c
@@ -68,7 +68,7 @@ static int
 pam_converse (int num_msg, PAM_CONVERSE_ARG2_TYPE **msg,
   struct pam_response **resp, void *appdata_ptr)
 {
-int sep = 0;
+int sep = ':';
 struct pam_response *reply;
 
 /* It seems that PAM frees reply[] */
@@ -131,7 +131,7 @@ Returns:   OK if authentication succeeded
 static int
 auth_call_pam(const uschar * s, uschar ** errptr)
 {
-pam_handle_t *pamh = NULL;
+pam_handle_t * pamh = NULL;
 struct pam_conv pamc;
 int pam_error;
 int sep = ':';	/* Do not permit change-of-separator */
@@ -160,7 +160,7 @@ if (user == NULL || user[0] == 0) return FAIL;
 
 /* Start off PAM interaction */
 
-DEBUG(D_auth) debug_printf("Running PAM authentication for user %q\n", user);
+DEBUG(auth) debug_printf("Running PAM authentication for user %q\n", user);
 
 pam_error = pam_start ("exim", CS user, &pamc, &pamh);
 
@@ -184,12 +184,12 @@ pam_end(pamh, PAM_SUCCESS);
 
 if (pam_error == PAM_SUCCESS)
   {
-  DEBUG(D_auth) debug_printf("PAM success\n");
+  DEBUG(auth) debug_printf("PAM success\n");
   return OK;
   }
 
 *errptr = US pam_strerror(pamh, pam_error);
-DEBUG(D_auth) debug_printf("PAM error: %s\n", *errptr);
+DEBUG(auth) debug_printf("PAM error: %s\n", *errptr);
 
 if (pam_error == PAM_USER_UNKNOWN ||
     pam_error == PAM_AUTH_ERR ||
diff --git a/src/src/miscmods/pdkim/pdkim.c b/src/src/miscmods/pdkim/pdkim.c
index a43ea9bfb..d39af02f2 100644
--- a/src/src/miscmods/pdkim/pdkim.c
+++ b/src/src/miscmods/pdkim/pdkim.c
@@ -473,13 +473,39 @@ for (uschar * p = raw_hdr; ; p++)
   if (where == PDKIM_HDR_LIMBO)
     {
     /* In limbo, just wait for a tag-char to appear */
-    if (!(c >= 'a' && c <= 'z'))
+    if (!c || isspace(c))
       goto NEXT_CHAR;
+    if (!(c >= 'a' && c <= 'z'))
+      {
+      DEBUG(acl) debug_printf_indent("bad char %W starting tag name\n", p);
+      return NULL;
+      }
 
     where = PDKIM_HDR_TAG;
     }
 
   if (where == PDKIM_HDR_TAG)
+    {
+    if (c == ' ' || c == '\t')			/* Permit FWS after tag name */
+      {
+      while (c = *++p)
+	if (c != ' ' && c != '\t') switch (c)
+	  {
+	  case '\r':
+	    if (*++p != '\n' || (c = *++p) != ' ' && c != '\t')
+	      {
+	      DEBUG(acl) debug_printf_indent("bad FWS after tag name\n");
+	      return NULL;
+	      }
+	    break;
+	  case '=': case ';':
+	    goto DONE_FWS;
+	  default:
+	    DEBUG(acl) debug_printf_indent("space in tag name\n");
+	    return NULL;
+	  }
+      DONE_FWS: ;
+      }
     if (c == '=')
       {
       if (Ustrcmp(string_from_gstring(cur_tag), "b") == 0)
@@ -490,8 +516,9 @@ for (uschar * p = raw_hdr; ; p++)
       where = PDKIM_HDR_VALUE;
       goto NEXT_CHAR;
       }
-    else if (!isspace(c))
+    else
       cur_tag = string_catn(cur_tag, p, 1);
+    }
 
   if (where == PDKIM_HDR_VALUE)
     {
@@ -510,7 +537,7 @@ for (uschar * p = raw_hdr; ; p++)
 	(void) string_from_gstring(cur_val);
 	pdkim_strtrim(cur_val);
 
-	DEBUG(D_acl) debug_printf(" %s=%s\n", cur_tag->s, cur_val->s);
+	DEBUG(acl) debug_printf(" %s=%s\n", cur_tag->s, cur_val->s);
 
 	switch (*cur_tag->s)
 	  {
@@ -580,7 +607,7 @@ for rsafp signatures.  But later discussion is dropping those. */
 	  }
 	}
 	else
-bad_tag:  DEBUG(D_acl) debug_printf(" Unknown tag encountered: %Y\n", cur_tag);
+bad_tag:  DEBUG(acl) debug_printf(" Unknown tag encountered: %Y\n", cur_tag);
 
       cur_tag = cur_val = NULL;
       in_b_val = FALSE;
@@ -606,7 +633,7 @@ if (sig->keytype < 0 || sig->hashtype < 0)	/* Cannot verify this signature */
 while (--q > sig->rawsig_no_b_val  && (*q == '\r' || *q == '\n'))
   *q = '\0';
 
-DEBUG(D_acl)
+DEBUG(acl)
   {
   debug_printf(
 	  "DKIM >> Raw signature w/o b= tag value >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
@@ -644,7 +671,7 @@ for (const uschar * ele = raw_record, * tspec, * end, * val; *ele; ele = end)
     {
     int taglen = val++ - tspec;
 
-    DEBUG(D_acl) debug_printf(" %.*s=%s\n", taglen, tspec, val);
+    DEBUG(acl) debug_printf(" %.*s=%s\n", taglen, tspec, val);
     while (taglen > 1 && isspace(tspec[taglen-1]))
       taglen--;			/* Ignore whitespace before = */
     Uskip_whitespace(&val);	/* Ignore whitespace after = */
@@ -655,7 +682,7 @@ for (const uschar * ele = raw_record, * tspec, * end, * val; *ele; ele = end)
 	gstring_trim(g, 1);
       if (!(val = string_from_gstring(g)))
 	{
-	DEBUG(D_acl)
+	DEBUG(acl)
 	  debug_printf(" Missing value for tag '%.*s'\n", taglen, tspec);
 	return NULL;
 	}
@@ -677,7 +704,7 @@ for (const uschar * ele = raw_record, * tspec, * end, * val; *ele; ele = end)
       }
     else
 bad_tag:
-       DEBUG(D_acl) debug_printf(" Unknown tag encountered\n");
+       DEBUG(acl) debug_printf(" Unknown tag encountered\n");
     }
   }
 
@@ -686,7 +713,7 @@ 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");
+  DEBUG(acl) debug_printf(" Bad v= field\n");
   return NULL;
   }
 
@@ -698,7 +725,7 @@ if (!pub->srvtype    ) pub->srvtype     = US"*";
 if (pub->key.data)
   return pub;
 
-DEBUG(D_acl) debug_printf(" Missing p= field\n");
+DEBUG(acl) debug_printf(" Missing p= field\n");
 return NULL;
 }
 
@@ -768,7 +795,7 @@ if (left > 0)
   {
   exim_sha_update(&b->body_hash_ctx, CUS canon_data->data, left);
   b->signed_body_bytes += left;
-  DEBUG(D_acl) debug_printf("%.*Z\n", left, canon_data->data);
+  DEBUG(acl) debug_printf("%.*Z\n", left, canon_data->data);
   }
 
 return relaxed_data;
@@ -782,7 +809,7 @@ pdkim_finish_bodyhash(pdkim_ctx * ctx)
 {
 for (pdkim_bodyhash * b = ctx->bodyhash; b; b = b->next)     /* Finish hashes */
   {
-  DEBUG(D_acl) debug_printf("DKIM: finish bodyhash %s/%s/%ld len %ld\n",
+  DEBUG(acl) debug_printf("DKIM: finish bodyhash %s/%s/%ld len %ld\n",
       pdkim_hashes[b->hashtype].dkim_hashname, pdkim_canons[b->canon_method],
       b->bodylength, b->signed_body_bytes);
   exim_sha_finish(&b->body_hash_ctx, &b->bh);
@@ -793,7 +820,7 @@ for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next)
   {
   pdkim_bodyhash * b = sig->calc_body_hash;
 
-  DEBUG(D_acl)
+  DEBUG(acl)
     {
     debug_printf("DKIM [%s]%s Body bytes (%s) hashed: %lu\n"
 		 "DKIM [%s]%s Body %s computed: ",
@@ -818,11 +845,11 @@ for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next)
     if (sig->bodyhash.data && sig->bodyhash.len == b->bh.len
        && memcmp(b->bh.data, sig->bodyhash.data, b->bh.len) == 0)
       {
-      DEBUG(D_acl) debug_printf("DKIM [%s] Body hash compared OK\n", sig->domain);
+      DEBUG(acl) debug_printf("DKIM [%s] Body hash compared OK\n", sig->domain);
       }
     else
       {
-      DEBUG(D_acl)
+      DEBUG(acl)
         {
 	debug_printf("DKIM [%s] Body hash signature from headers: ", sig->domain);
 	debug_printf("%.*H\n", sig->bodyhash.len, sig->bodyhash.data);
@@ -975,7 +1002,7 @@ if (ctx->flags & PDKIM_MODE_SIGN)
 else
   {
 #ifdef notdef
-  DEBUG(D_acl) debug_printf("DKIM >> raw hdr: %.*Z\n",
+  DEBUG(acl) debug_printf("DKIM >> raw hdr: %.*Z\n",
 			    ctx->cur_head->ptr, CUS g->s);
 #endif
   if (strncasecmp(CCS g->s,
@@ -987,7 +1014,7 @@ else
     required tags here, but prefer to create the internal sig and expicitly
     fail verification of it later. */
 
-    DEBUG(D_acl) debug_printf(
+    DEBUG(acl) debug_printf(
 	"DKIM >> Found sig, trying to parse >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
 
     sig = pdkim_parse_sig_header(ctx, g->s);
@@ -1000,10 +1027,12 @@ else
       last_sig->next = sig;
       }
 
+    /* Check for too many signatures */
+
     if (dkim_collect_input && --dkim_collect_input == 0)
       {
       ctx->headers = pdkim_prepend_stringlist(ctx->headers, g->s);
-      g->s[g->ptr = 0] = '\0';
+      gstring_reset(g);
       return PDKIM_ERR_EXCESS_SIGS;
       }
     }
@@ -1013,7 +1042,7 @@ else
   }
 
 BAIL:
-g->s[g->ptr = 0] = '\0';	/* leave buffer for reuse */
+gstring_reset(g);	/* leave buffer for reuse */
 return PDKIM_OK;
 }
 
@@ -1072,7 +1101,7 @@ else for (unsigned p = 0; p < len; p++)
 	  return rc;
 
 	ctx->flags = (ctx->flags & ~(PDKIM_SEEN_LF|PDKIM_SEEN_CR)) | PDKIM_PAST_HDRS;
-	DEBUG(D_acl) debug_printf(
+	DEBUG(acl) debug_printf(
 	    "DKIM >> Body data for hash, canonicalized >>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
 	continue;
 	}
@@ -1321,7 +1350,7 @@ check_bare_ed25519_pubkey(pdkim_pubkey * p)
 int excess = p->key.len - 32;
 if (excess > 0)
   {
-  DEBUG(D_acl)
+  DEBUG(acl)
     debug_printf("DKIM: unexpected pubkey len %lu\n", (unsigned long) p->key.len);
   p->key.data += excess; p->key.len = 32;
   }
@@ -1348,7 +1377,7 @@ if (  !(dns_txt_reply = ctx->dns_txt_callback(dns_txt_name))
   return NULL;
   }
 
-DEBUG(D_acl)
+DEBUG(acl)
   {
   debug_printf(
     "DKIM >> Parsing public key record >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"
@@ -1365,7 +1394,7 @@ if (  !(p = pdkim_parse_pubkey_record(CUS dns_txt_reply))
   sig->verify_status =      PDKIM_VERIFY_INVALID;
   sig->verify_ext_status =  PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD;
 
-  DEBUG(D_acl)
+  DEBUG(acl)
     {
     if (p)
       debug_printf(" Invalid public key service type '%s'\n", p->srvtype);
@@ -1377,7 +1406,7 @@ if (  !(p = pdkim_parse_pubkey_record(CUS dns_txt_reply))
   return NULL;
   }
 
-DEBUG(D_acl) debug_printf(
+DEBUG(acl) debug_printf(
       "DKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
 
 /* Import public key */
@@ -1390,7 +1419,7 @@ instead.  Assume writing on the sig is ok in that case. */
 if (sig->keytype < 0)
   if ((sig->keytype = pdkim_keyname_to_keytype(p->keytype)) < 0)
     {
-    DEBUG(D_acl) debug_printf("verify_init: unhandled keytype %s\n", p->keytype);
+    DEBUG(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;
@@ -1403,7 +1432,7 @@ if ((*errstr = exim_dkim_verify_init(&p->key,
 	    sig->keytype == KEYTYPE_ED25519 ? KEYFMT_ED25519_BARE : KEYFMT_DER,
 	    vctx, &sig->keybits)))
   {
-  DEBUG(D_acl) debug_printf("verify_init: %s\n", *errstr);
+  DEBUG(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;
@@ -1428,7 +1457,7 @@ int sep;
 if (!siglist) return NULL;
 
 /* first select in order of hashtypes */
-DEBUG(D_acl) debug_printf("DKIM: dkim_verify_hashes   '%s'\n", dkim_verify_hashes);
+DEBUG(acl) debug_printf("DKIM: dkim_verify_hashes   '%s'\n", dkim_verify_hashes);
 for (prefs = dkim_verify_hashes, sep = 0, yield = NULL, ss = &yield;
      ele = string_nextinlist(&prefs, &sep, NULL, 0); )
   {
@@ -1446,7 +1475,7 @@ for (prefs = dkim_verify_hashes, sep = 0, yield = NULL, ss = &yield;
 
 /* then in order of keytypes */
 siglist = yield;
-DEBUG(D_acl) debug_printf("DKIM: dkim_verify_keytypes '%s'\n", dkim_verify_keytypes);
+DEBUG(acl) debug_printf("DKIM: dkim_verify_keytypes '%s'\n", dkim_verify_keytypes);
 for (prefs = dkim_verify_keytypes, sep = 0, yield = NULL, ss = &yield;
      ele = string_nextinlist(&prefs, &sep, NULL, 0); )
   {
@@ -1462,7 +1491,7 @@ for (prefs = dkim_verify_keytypes, sep = 0, yield = NULL, ss = &yield;
     }
   }
 
-DEBUG(D_acl) for (pdkim_signature * s = yield; s; s = s->next)
+DEBUG(acl) for (pdkim_signature * s = yield; s; s = s->next)
   debug_printf(" retain d=%s s=%s a=%s\n",
     s->domain, s->selector, dkim_sig_to_a_tag(s));
 return yield;
@@ -1493,7 +1522,7 @@ if (ctx->cur_header && ctx->cur_header->ptr > 0)
   if (rnl) store_free(rnl);
   }
 else
-  DEBUG(D_acl) debug_printf(
+  DEBUG(acl) debug_printf(
       "DKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
 
 /* Build (and/or evaluate) body hash.  Do this even if no DKIM sigs, in case we
@@ -1508,7 +1537,7 @@ if (!(ctx->flags & PDKIM_MODE_SIGN))
 
 if (!ctx->sig)
   {
-  DEBUG(D_acl) debug_printf("DKIM: no signatures\n");
+  DEBUG(acl) debug_printf("DKIM: no signatures\n");
   *return_signatures = NULL;
   return PDKIM_OK;
   }
@@ -1524,7 +1553,7 @@ for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next)
   if (  !(ctx->flags & PDKIM_MODE_SIGN)
      && sig->verify_status == PDKIM_VERIFY_FAIL)
     {
-    DEBUG(D_acl)
+    DEBUG(acl)
        debug_printf("DKIM: [%s] abandoning this signature\n", sig->domain);
     continue;
     }
@@ -1552,18 +1581,18 @@ for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next)
 
   if (!exim_sha_init(&hhash_ctx, pdkim_hashes[sig->hashtype].exim_hashmethod))
     {
-    log_write(0, LOG_MAIN|LOG_PANIC,
+    log_write(LOG_MAIN|LOG_PANIC,
       "DKIM: hash setup error, possibly nonhandled hashtype");
     break;
     }
 
   if (ctx->flags & PDKIM_MODE_SIGN)
-    DEBUG(D_acl) debug_printf(
+    DEBUG(acl) debug_printf(
 	"DKIM >> Headers to be signed:                            >>>>>>>>>>>>\n"
 	" %s\n",
 	sig->sign_headers);
 
-  DEBUG(D_acl) debug_printf(
+  DEBUG(acl) debug_printf(
       "DKIM >> Header data for hash, canonicalized (%-7s), in sequence >>\n",
 	pdkim_canons[sig->canon_headers]);
 
@@ -1586,7 +1615,7 @@ for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next)
 
     if ((*err = exim_dkim_signing_init(CUS sig->privkey, &sctx)))
       {
-      log_write(0, LOG_MAIN|LOG_PANIC, "signing_init: %s", *err);
+      log_write(LOG_MAIN|LOG_PANIC, "signing_init: %s", *err);
       return PDKIM_ERR_RSA_PRIVKEY;
       }
     sig->keytype = sctx.keytype;
@@ -1611,7 +1640,7 @@ for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next)
 	/*XXX we could avoid doing this for all but the GnuTLS/RSA case */
 	hdata = exim_dkim_data_append(hdata, rh);
 
-	DEBUG(D_acl) debug_printf("%Z\n", rh);
+	DEBUG(acl) debug_printf("%Z\n", rh);
 	}
       }
 
@@ -1668,7 +1697,7 @@ for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next)
 	    /* Feed header to the hash algorithm */
 	    exim_sha_update_string(&hhash_ctx, CUS rh);
 
-	    DEBUG(D_acl) debug_printf("%Z\n", rh);
+	    DEBUG(acl) debug_printf("%Z\n", rh);
 	    hdrs->tag = 1;
 	    break;
 	    }
@@ -1681,10 +1710,10 @@ for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next)
       }
     }
 
-  DEBUG(D_acl) debug_printf(
+  DEBUG(acl) debug_printf(
 	    "DKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
 
-  DEBUG(D_acl)
+  DEBUG(acl)
     {
     debug_printf(
 	    "DKIM >> Signed DKIM-Signature header, pre-canonicalized >>>>>>>>>>>>>\n");
@@ -1697,7 +1726,7 @@ for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next)
   if (sig->canon_headers == PDKIM_CANON_RELAXED)
     sig_hdr = pdkim_relax_header(sig_hdr, FALSE);
 
-  DEBUG(D_acl)
+  DEBUG(acl)
     {
     debug_printf("DKIM >> Signed DKIM-Signature header, canonicalized (%-7s) >>>>>>>\n",
 	    pdkim_canons[sig->canon_headers]);
@@ -1710,7 +1739,7 @@ for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next)
   exim_sha_update_string(&hhash_ctx, CUS sig_hdr);
   exim_sha_finish(&hhash_ctx, &hhash);
 
-  DEBUG(D_acl)
+  DEBUG(acl)
     {
     debug_printf("DKIM [%s] Header %s computed: ",
       sig->domain, pdkim_hashes[sig->hashtype].dkim_hashname);
@@ -1746,11 +1775,11 @@ for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next)
 
     if ((*err = exim_dkim_sign(&sctx, hm, &hhash, &sig->sighash)))
       {
-      log_write(0, LOG_MAIN|LOG_PANIC, "signing: %s", *err);
+      log_write(LOG_MAIN|LOG_PANIC, "signing: %s", *err);
       return PDKIM_ERR_RSA_SIGNING;
       }
 
-    DEBUG(D_acl)
+    DEBUG(acl)
       {
       debug_printf( "DKIM [%s] b computed: ", sig->domain);
       debug_printf("%.*H\n", sig->sighash.len, sig->sighash.data);
@@ -1779,7 +1808,7 @@ for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next)
       sig->verify_status     = PDKIM_VERIFY_INVALID;
       sig->verify_ext_status = PDKIM_VERIFY_INVALID_SIGNATURE_ERROR;
 
-      DEBUG(D_acl) debug_printf(
+      DEBUG(acl) debug_printf(
 	  " Error in DKIM-Signature header: tags missing or invalid (%s)\n"
 	  "DKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n",
 	  !(sig->domain && *sig->domain) ? "d="
@@ -1799,13 +1828,13 @@ for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next)
       sig->verify_status     = PDKIM_VERIFY_INVALID;
       sig->verify_ext_status = PDKIM_VERIFY_INVALID_DKIM_VERSION;
 
-      DEBUG(D_acl) debug_printf(
+      DEBUG(acl) debug_printf(
           " Error in DKIM-Signature header: unsupported DKIM version\n"
           "DKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
       goto NEXT_VERIFY;
       }
 
-    DEBUG(D_acl)
+    DEBUG(acl)
       {
       debug_printf( "DKIM [%s] b from mail: ", sig->domain);
       debug_printf("%.*H\n", sig->sighash.len, sig->sighash.data);
@@ -1813,7 +1842,7 @@ for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next)
 
     if (!(sig->pubkey = pdkim_key_from_dns(ctx, sig, &vctx, err)))
       {
-      log_write(0, LOG_MAIN, "DKIM: %s%s %s%s [failed key import]",
+      log_write(LOG_MAIN, "DKIM: %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;
@@ -1830,7 +1859,7 @@ for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next)
 	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",
+	DEBUG(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);
@@ -1852,7 +1881,7 @@ for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next)
 
     if ((*err = exim_dkim_verify(&vctx, hm, &hhash, &sig->sighash)))
       {
-      DEBUG(D_acl)
+      DEBUG(acl)
 	debug_printf("headers verify: %s\n", **err ? *err : US"fail");
       sig->verify_status =      PDKIM_VERIFY_FAIL;
       sig->verify_ext_status =  PDKIM_VERIFY_FAIL_MESSAGE;
@@ -1865,7 +1894,7 @@ for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next)
 				    dkim_verify_min_keysizes);
       if (ss &&  (minbits = atoi(CCS ss)) > sig->keybits)
 	{
-	DEBUG(D_acl) debug_printf("Key too short: Actual: %s %u  Minima '%s'\n",
+	DEBUG(acl) debug_printf("Key too short: Actual: %s %u  Minima '%s'\n",
 	  pdkim_keytypes[sig->keytype], sig->keybits, dkim_verify_min_keysizes);
 	sig->verify_status =      PDKIM_VERIFY_FAIL;
 	sig->verify_ext_status =  PDKIM_VERIFY_INVALID_PUBKEY_KEYSIZE;
@@ -1889,7 +1918,7 @@ for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next)
       }
 
 NEXT_VERIFY:
-    DEBUG(D_acl)
+    DEBUG(acl)
       {
       debug_printf("DKIM [%s] %s signature status: %s",
 	      sig->domain, dkim_sig_to_a_tag(sig),
@@ -1966,12 +1995,12 @@ for (hashtype = 0; hashtype < nelem(pdkim_hashes); hashtype++)
   { sig->hashtype = hashtype; break; }
 if (hashtype >= nelem(pdkim_hashes))
   {
-  log_write(0, LOG_MAIN|LOG_PANIC,
+  log_write(LOG_MAIN|LOG_PANIC,
     "DKIM: unrecognised hashname '%s'", hashname);
   return NULL;
   }
 
-DEBUG(D_acl)
+DEBUG(acl)
   {
   pdkim_signature s = *sig;
   ev_ctx vctx;
@@ -2029,7 +2058,7 @@ if (hashtype == -1 || canon_method == -1) return NULL;
 
 if (!ctx)
   {
-  DEBUG(D_receive) debug_printf("pdkim_set_bodyhash: null context\n");
+  DEBUG(receive) debug_printf("pdkim_set_bodyhash: null context\n");
   return NULL;
   }
 
@@ -2038,12 +2067,12 @@ for (b = ctx->bodyhash; b; b = b->next)
      && canon_method == b->canon_method
      && bodylength == b->bodylength)
     {
-    DEBUG(D_receive) debug_printf("DKIM: using existing bodyhash %s/%s/%ld\n",
+    DEBUG(receive) debug_printf("DKIM: using existing bodyhash %s/%s/%ld\n",
       pdkim_hashes[hashtype].dkim_hashname, pdkim_canons[canon_method], bodylength);
     return b;
     }
 
-DEBUG(D_receive) debug_printf("DKIM: new bodyhash %s/%s/%ld\n",
+DEBUG(receive) debug_printf("DKIM: new bodyhash %s/%s/%ld\n",
     pdkim_hashes[hashtype].dkim_hashname, pdkim_canons[canon_method], bodylength);
 b = store_get(sizeof(pdkim_bodyhash), GET_UNTAINTED);
 b->next = ctx->bodyhash;
@@ -2053,7 +2082,7 @@ 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(acl)
     debug_printf("DKIM: hash init error, possibly nonhandled hashtype\n");
   return NULL;
   }
@@ -2093,7 +2122,7 @@ memset(ctx, 0, sizeof(pdkim_ctx));
 ctx->flags = dot_stuffed ? PDKIM_MODE_SIGN | PDKIM_DOT_TERM : PDKIM_MODE_SIGN;
 /* The line buffer is for message data, hence tainted */
 ctx->linebuf = store_get(PDKIM_MAX_BODY_LINE_LEN, GET_TAINTED);
-DEBUG(D_acl) ctx->dns_txt_callback = dns_txt_callback;
+DEBUG(acl) ctx->dns_txt_callback = dns_txt_callback;
 }
 
 
diff --git a/src/src/miscmods/pdkim/pdkim.h b/src/src/miscmods/pdkim/pdkim.h
index 454343b1b..ec9bbcd7b 100644
--- a/src/src/miscmods/pdkim/pdkim.h
+++ b/src/src/miscmods/pdkim/pdkim.h
@@ -39,6 +39,7 @@
                              "Resent-Sender:Resent-To:Resent-Cc:"\
                              "Resent-Message-ID:In-Reply-To:References:"\
                              "List-Id:List-Help:List-Unsubscribe:"\
+			     "List-Unsubscribe-Post:"\
                              "List-Subscribe:List-Post:List-Owner:List-Archive"
 
 #define PDKIM_OVERSIGN_HEADERS "+From:+Sender:+Reply-To:+Subject:+Date:"\
@@ -48,6 +49,7 @@
                              "+Resent-Sender:+Resent-To:+Resent-Cc:"\
                              "+Resent-Message-ID:+In-Reply-To:+References:"\
                              "+List-Id:+List-Help:+List-Unsubscribe:"\
+			     "+List-Unsubscribe-Post:"\
                              "+List-Subscribe:+List-Post:+List-Owner:+List-Archive"
 
 /* -------------------------------------------------------------------------- */
diff --git a/src/src/miscmods/pdkim/signing.c b/src/src/miscmods/pdkim/signing.c
index 464e117d6..6aba5a56f 100644
--- a/src/src/miscmods/pdkim/signing.c
+++ b/src/src/miscmods/pdkim/signing.c
@@ -58,10 +58,10 @@ 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);
+  DEBUG(tls) debug_printf("GnuTLS<%d> empty debug message\n", level);
   return;
   }
-DEBUG(D_tls) debug_printf("GnuTLS<%d>: %s%s", level, message,
+DEBUG(tls) debug_printf("GnuTLS<%d>: %s%s", level, message,
     message[len-1] == '\n' ? "" : "\n");
 }
 #endif
@@ -72,7 +72,7 @@ void
 exim_dkim_signers_init(void)
 {
 #if EXIM_GNUTLS_LIBRARY_LOG_LEVEL >= 0
-DEBUG(D_tls)
+DEBUG(tls)
   {
   gnutls_global_set_log_function(exim_gnutls_logger_cb);
   /* arbitrarily chosen level; bump upto 9 for more */
@@ -457,7 +457,7 @@ if (  (s1 = as_mpi(&der, &sign_ctx->n))
   return s1;
 
 #ifdef extreme_debug
-DEBUG(D_acl) debug_printf_indent("rsa_signing_init:\n");
+DEBUG(acl) debug_printf_indent("rsa_signing_init:\n");
   {
   uschar * s;
   gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->n);
@@ -544,7 +544,7 @@ if (  !(s_sig = gcry_sexp_find_token(s_sig, "s", 0))
 m_sig = gcry_sexp_nth_mpi(s_sig, 1, GCRYMPI_FMT_USG);
 
 #ifdef extreme_debug
-DEBUG(D_acl)
+DEBUG(acl)
   {
   uschar * s;
   gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, m_sig);
@@ -605,7 +605,7 @@ 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";
+DEBUG(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;
@@ -613,26 +613,26 @@ pubkey->data += alen; pubkey->len -= alen;
 
 /* bitstring: limit range to size of bitstring;
 move over header + content wrapper */
-DEBUG(D_acl) stage = US"BS";
+DEBUG(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";
+DEBUG(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";
+DEBUG(acl) stage = US"MPI";
 nbits = pubkey->len;
 if ((errstr = as_mpi(pubkey, &verify_ctx->n))) return errstr;
 nbits = (nbits - pubkey->len) * 8;
 if ((errstr = as_mpi(pubkey, &verify_ctx->e))) return errstr;
 
 #ifdef extreme_debug
-DEBUG(D_acl) debug_printf_indent("rsa_verify_init:\n");
+DEBUG(acl) debug_printf_indent("rsa_verify_init:\n");
 	{
 	uschar * s;
 	gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, verify_ctx->n);
@@ -646,7 +646,7 @@ if (bits) *bits = nbits;
 return NULL;
 
 asn_err:
-DEBUG(D_acl) return string_sprintf("%s: %s", stage, asn1_strerror(rc));
+DEBUG(acl) return string_sprintf("%s: %s", stage, asn1_strerror(rc));
 	     return US asn1_strerror(rc);
 }
 
@@ -691,7 +691,7 @@ if (  (stage = US"pkey sexp build",
        gerr = gcry_pk_verify(s_sig, s_hash, s_pkey))
    )
   {
-  DEBUG(D_acl) debug_printf_indent("verify: error in stage '%s'\n", stage);
+  DEBUG(acl) debug_printf_indent("verify: error in stage '%s'\n", stage);
   return gerr == GCRY_ERR_BAD_SIGNATURE ? US"" : US gcry_strerror(gerr);
   }
 
@@ -825,7 +825,8 @@ 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);
+      ret = string_sprintf("deccoding pubkey DER:%s",
+				ERR_reason_error_string(ERR_get_error()));
     break;
 #ifdef SIGN_HAVE_ED25519
   case KEYFMT_ED25519_BARE:
@@ -896,7 +897,7 @@ else
       { EVP_PKEY_CTX_free(ctx); return NULL; }
     EVP_PKEY_CTX_free(ctx);
 
-    DEBUG(D_tls)
+    DEBUG(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));
diff --git a/src/src/miscmods/perl.c b/src/src/miscmods/perl.c
index 2220c7dc9..64f9ffb00 100644
--- a/src/src/miscmods/perl.c
+++ b/src/src/miscmods/perl.c
@@ -77,6 +77,7 @@ if (SvTRUE(ERRSV))
   STRLEN len;
   s = US SvPV(ERRSV, len);
   s = string_copyn(s, (unsigned)len);
+  debug_printf_indent("adding perl codeblock: %s\n", s);
   }
 
 setlocale(LC_ALL, "C");		/* In case it got changed */
@@ -128,10 +129,11 @@ const uschar * s;
 if (items != 1)
   croak("Usage: Exim::log_write(string)");
 s = US SvPV(ST(0), len);
-log_write(0, LOG_MAIN, "%.*s", (int)len, s);
+log_write(LOG_MAIN, "%.*s", (int)len, s);
 }
 
-/* Do a DNS lookup using Exim's facilities.  Returns a scalar with the response packet. */
+/* Do a DNS lookup using Exim's facilities.
+Returns a scalar with the response packet; undef for DNS_FAIL or DNS_AGAIN. */
 
 XS(xs_dns_lookup)
 {
@@ -154,8 +156,12 @@ debug_printf_indent(" dnsa answer %p len %d\n", dnsa->answer, dnsa->answerlen);
 */
 
 ST(0) = sv_newmortal();
-sv_setpvn(ST(0), CCS dnsa->answer,
-		  (STRLEN) (dns_res == DNS_NODATA ? 0 : dnsa->answerlen));
+if (dns_res != DNS_SUCCEED)
+  sv_setsv(ST(0), &PL_sv_undef);
+else if (dnsa->answerlen == -1 && !fake_dnsa_len_for_fail(dnsa, rrtype_int))
+  sv_setsv(ST(0), &PL_sv_undef);
+else
+  sv_setpvn(ST(0), CCS dnsa->answer, (STRLEN) dnsa->answerlen);
 XSRETURN(1);	/* ? needed because there are 2 arg, but 1 res? */
 
 store_free_dns_answer(dnsa);
@@ -196,19 +202,26 @@ perl_parse(interp_perl, xs_init, argc, argv, 0);
 perl_run(interp_perl);
 
 /*********************************************************************/
+errstr = exim_perl_add_codeblock(US
+
 /* These lines by PH added to make "warn" output go to the Exim log; I
 hope this doesn't break anything. */
 
-errstr = exim_perl_add_codeblock(US
   "$SIG{__WARN__} = sub { my($s) = $_[0];"
   "$s =~ s/\\n$//;"
   "Exim::log_write($s) };"
 
-/* These lines added by JGH to route DNS queries via Exim's facilities */
+/* These lines added by JGH to route DNS queries via Exim's facilities.
+If a port was specified, we punt.
+*/
 
   "package Net::DNS::Resolver;"
   "sub send {"
-        "my ( $self, $dom, $rrtype_str ) = @_;"
+	"my $self = shift;"
+
+	"return $self->SUPER::send(@_) if ($self->{'port'} != 53);"
+
+        "my ( $dom, $rrtype_str ) = @_;"
 	"my $rr = {"
 	"\"A\"     => 1,"
 	"\"NS\"    => 2,"
@@ -222,10 +235,16 @@ errstr = exim_perl_add_codeblock(US
 	"\"TLSA\"  => 52,"
 	"\"SPF\"   => 99,"
 	"};"
-	"my $rrtype = $rr->{$rrtype_str};"		/*XXX only one rrtype per query...*/
+	/*XXX only one rrtype per query...*/
+	"my $rrtype = $rr->{$rrtype_str};"
         "my $dnsa = Exim::dns_lookup($dom, $rrtype);"
-	"my $res = Net::DNS::Packet->decode( \\$dnsa );"
-		/* "Exim::debug_write( $res->string . '\n' );" */
+
+	"my $res;"
+	"$res = new Net::DNS::Packet(\\$dnsa) if (defined($dnsa));"
+
+	/*XXX dumb, but at least clears the undef on errorstring */
+	"$self->errorstring(defined($dnsa) ? 'ok' : 'timeout');"
+
 	"return $res;"
   "}"
   "package MAIN;"
@@ -256,7 +275,7 @@ yield string */
 
 static gstring *
 call_perl_cat(gstring * yield, uschar ** errstrp,
-  uschar * name, const uschar ** arg)
+  const uschar * name, const uschar ** arg)
 {
 dSP;
 SV * sv;
@@ -275,7 +294,7 @@ SAVETMPS;
 PUSHMARK(SP);
 while (*arg) XPUSHs(newSVpv(CCS (*arg++), 0));
 PUTBACK;
-items = perl_call_pv(CS name, G_SCALAR|G_EVAL);
+items = perl_call_pv(CS string_copy(name), G_SCALAR|G_EVAL);
 items = items;	/* stupid compiler quietening */
 SPAGAIN;
 sv = POPs;
diff --git a/src/src/proxy.c b/src/src/miscmods/proxy.c
similarity index 85%
rename from src/src/proxy.c
rename to src/src/miscmods/proxy.c
index 922bf7264..b9e52c506 100644
--- a/src/src/proxy.c
+++ b/src/src/miscmods/proxy.c
@@ -11,9 +11,17 @@
 *            Proxy-Protocol support             *
 ************************************************/
 
-#include "exim.h"
+#include "../exim.h"
 
 #ifdef SUPPORT_PROXY
+
+
+static void
+command_timeout_handler(int sig)
+{
+had_command_timeout = sig;
+}
+
 /*************************************************
 *       Check if host is required proxy host     *
 *************************************************/
@@ -25,15 +33,16 @@ Arguments: none
 Returns:   boolean for Proxy Protocol needed
 */
 
-BOOL
+static BOOL
 proxy_protocol_host(void)
 {
 if (  sender_host_address
    && verify_check_this_host(CUSS &hosts_proxy, NULL, NULL,
                            sender_host_address, NULL) == OK)
   {
-  DEBUG(D_receive)
+  DEBUG(receive)
     debug_printf("Detected proxy protocol configured host\n");
+  /* having this set when we could still fail is ugly */
   proxy_session = TRUE;
   }
 return proxy_session;
@@ -90,7 +99,7 @@ while (capacity > 0)
     { ret = read(fd, to, 1); }
   while (ret == -1 && errno == EINTR && !had_command_timeout);
 
-  if (ret == -1)
+  if (ret <= 0)
     return -1;
   have++;
   if (last)
@@ -114,6 +123,7 @@ debug_printf("PROXY<<%3.*H\n", (int)(end - start), buf + start);
 }
 
 
+/*API*/
 /*************************************************
 *         Setup host for proxy protocol          *
 *************************************************/
@@ -123,11 +133,11 @@ so exit with an error if do not find the exact required pieces. This
 includes an incorrect number of spaces separating args.
 
 Arguments: none
-Returns:   Boolean success
+Returns:   TRUE iff success
 */
 
-void
-proxy_protocol_setup(void)
+static BOOL
+proxy_protocol(void)
 {
 union {
   struct {
@@ -201,6 +211,10 @@ const char v2sig[12] = "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A";
 uschar * iptype;  /* To display debug info */
 BOOL yield = FALSE;
 
+if (!proxy_protocol_host())
+  return TRUE;
+
+os_non_restarting_signal(SIGALRM, command_timeout_handler);
 ALARM(proxy_protocol_timeout);
 
 do
@@ -212,9 +226,9 @@ do
   ret = read(smtp_in_fd, &hdr, PROXY_INITIAL_READ);
   } while (ret == -1 && errno == EINTR && !had_command_timeout);
 
-if (ret == -1)
+if (ret <= 0)
   goto proxyfail;
-DEBUG(D_receive) proxy_debug(US &hdr, 0, ret);
+DEBUG(receive) proxy_debug(US &hdr, 0, ret);
 
 /* For v2, handle reading the length, and then the rest. */
 if ((ret == PROXY_INITIAL_READ) && (memcmp(&hdr.v2, v2sig, sizeof(v2sig)) == 0))
@@ -222,16 +236,16 @@ if ((ret == PROXY_INITIAL_READ) && (memcmp(&hdr.v2, v2sig, sizeof(v2sig)) == 0))
   int retmore;
   uint8_t ver;
 
-  DEBUG(D_receive) debug_printf("v2\n");
+  DEBUG(receive) debug_printf("v2\n");
 
   /* First get the length fields. */
   do
     {
     retmore = read(smtp_in_fd, US &hdr + ret, PROXY_V2_HEADER_SIZE - PROXY_INITIAL_READ);
     } while (retmore == -1 && errno == EINTR && !had_command_timeout);
-  if (retmore == -1)
+  if (retmore <= 0)
     goto proxyfail;
-  DEBUG(D_receive) proxy_debug(US &hdr, ret, ret + retmore);
+  DEBUG(receive) proxy_debug(US &hdr, ret, ret + retmore);
 
   ret += retmore;
 
@@ -244,13 +258,13 @@ if ((ret == PROXY_INITIAL_READ) && (memcmp(&hdr.v2, v2sig, sizeof(v2sig)) == 0))
 
   if (ver != 0x02)
     {
-    DEBUG(D_receive) debug_printf("Invalid Proxy Protocol version: %d\n", ver);
+    DEBUG(receive) debug_printf("Invalid Proxy Protocol version: %d\n", ver);
     goto proxyfail;
     }
 
   /* The v2 header will always be 16 bytes per the spec. */
   size = 16 + ntohs(hdr.v2.len);
-  DEBUG(D_receive) debug_printf("Detected PROXYv2 header, size %d (limit %d)\n",
+  DEBUG(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
@@ -258,7 +272,7 @@ if ((ret == PROXY_INITIAL_READ) && (memcmp(&hdr.v2, v2sig, sizeof(v2sig)) == 0))
   get the rest. */
   if (size > sizeof(hdr))
     {
-    DEBUG(D_receive) debug_printf("PROXYv2 header size unreasonably large; security attack?\n");
+    DEBUG(receive) debug_printf("PROXYv2 header size unreasonably large; security attack?\n");
     goto proxyfail;
     }
 
@@ -268,11 +282,11 @@ if ((ret == PROXY_INITIAL_READ) && (memcmp(&hdr.v2, v2sig, sizeof(v2sig)) == 0))
       {
       retmore = read(smtp_in_fd, US &hdr + ret, size-ret);
       } while (retmore == -1 && errno == EINTR && !had_command_timeout);
-    if (retmore == -1)
+    if (retmore <= 0)
       goto proxyfail;
-    DEBUG(D_receive) proxy_debug(US &hdr, ret, ret + retmore);
+    DEBUG(receive) proxy_debug(US &hdr, ret, ret + retmore);
     ret += retmore;
-    DEBUG(D_receive) debug_printf("PROXYv2: have %d/%d required octets\n", ret, size);
+    DEBUG(receive) debug_printf("PROXYv2: have %d/%d required octets\n", ret, size);
     } while (ret < size);
 
   } /* end scope for getting rest of data for v2 */
@@ -302,7 +316,7 @@ if (ret >= 16 && memcmp(&hdr.v2, v2sig, 12) == 0)
           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(receive) debug_printf("Invalid %s source IP\n", iptype);
             goto proxyfail;
             }
           proxy_local_address = sender_host_address;
@@ -315,7 +329,7 @@ if (ret >= 16 && memcmp(&hdr.v2, v2sig, 12) == 0)
           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(receive) debug_printf("Invalid %s dest port\n", iptype);
             goto proxyfail;
             }
           proxy_external_address = string_copy(US tmpip);
@@ -334,7 +348,7 @@ if (ret >= 16 && memcmp(&hdr.v2, v2sig, 12) == 0)
           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(receive) debug_printf("Invalid %s source IP\n", iptype);
             goto proxyfail;
             }
           proxy_local_address = sender_host_address;
@@ -347,7 +361,7 @@ if (ret >= 16 && memcmp(&hdr.v2, v2sig, 12) == 0)
           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(receive) debug_printf("Invalid %s dest port\n", iptype);
             goto proxyfail;
             }
           proxy_external_address = string_copy(US tmpip6);
@@ -355,7 +369,7 @@ if (ret >= 16 && memcmp(&hdr.v2, v2sig, 12) == 0)
           proxy_external_port  = tmpport;
           goto done;
         default:
-          DEBUG(D_receive)
+          DEBUG(receive)
             debug_printf("Unsupported PROXYv2 connection type: 0x%02x\n",
                          hdr.v2.fam);
           goto proxyfail;
@@ -367,7 +381,7 @@ if (ret >= 16 && memcmp(&hdr.v2, v2sig, 12) == 0)
       iptype = US"local";
       break;
     default:
-      DEBUG(D_receive)
+      DEBUG(receive)
         debug_printf("Unsupported PROXYv2 command: 0x%x\n", cmd);
       goto proxyfail;
     }
@@ -392,19 +406,19 @@ else if (ret >= 8 && memcmp(hdr.v1.line, "PROXY", 5) == 0)
 
   if (!end || (end == US &hdr + ret) || end[1] != '\n')
     {
-    DEBUG(D_receive) debug_printf("Partial or invalid PROXY header\n");
+    DEBUG(receive) debug_printf("Partial or invalid PROXY header\n");
     goto proxyfail;
     }
   *end = '\0'; /* Terminate the string */
   size = end + 2 - p; /* Skip header + CRLF */
-  DEBUG(D_receive) debug_printf("Detected PROXYv1 header\n");
-  DEBUG(D_receive) debug_printf("Bytes read not within PROXY header: %d\n", ret - size);
+  DEBUG(receive) debug_printf("Detected PROXYv1 header\n");
+  DEBUG(receive) debug_printf("Bytes read not within PROXY header: %d\n", ret - size);
   /* Step through the string looking for the required fields. Ensure
   strict adherence to required formatting, exit for any error. */
   p += 5;
   if (!isspace(*p++))
     {
-    DEBUG(D_receive) debug_printf("Missing space after PROXY command\n");
+    DEBUG(receive) debug_printf("Missing space after PROXY command\n");
     goto proxyfail;
     }
   if (!Ustrncmp(p, CCS"TCP4", 4))
@@ -418,27 +432,27 @@ else if (ret >= 8 && memcmp(hdr.v1.line, "PROXY", 5) == 0)
     }
   else
     {
-    DEBUG(D_receive) debug_printf("Invalid TCP type\n");
+    DEBUG(receive) debug_printf("Invalid TCP type\n");
     goto proxyfail;
     }
 
   p += Ustrlen(iptype);
   if (!isspace(*p++))
     {
-    DEBUG(D_receive) debug_printf("Missing space after TCP4/6 command\n");
+    DEBUG(receive) debug_printf("Missing space after TCP4/6 command\n");
     goto proxyfail;
     }
   /* Find the end of the arg */
   if ((sp = Ustrchr(p, ' ')) == NULL)
     {
-    DEBUG(D_receive)
+    DEBUG(receive)
       debug_printf("Did not find proxied src %s\n", iptype);
     goto proxyfail;
     }
   *sp = '\0';
   if(!string_is_ip_address(p, NULL))
     {
-    DEBUG(D_receive)
+    DEBUG(receive)
       debug_printf("Proxied src arg is not an %s address\n", iptype);
     goto proxyfail;
     }
@@ -447,14 +461,14 @@ else if (ret >= 8 && memcmp(hdr.v1.line, "PROXY", 5) == 0)
   p = sp + 1;
   if ((sp = Ustrchr(p, ' ')) == NULL)
     {
-    DEBUG(D_receive)
+    DEBUG(receive)
       debug_printf("Did not find proxy dest %s\n", iptype);
     goto proxyfail;
     }
   *sp = '\0';
   if(!string_is_ip_address(p, NULL))
     {
-    DEBUG(D_receive)
+    DEBUG(receive)
       debug_printf("Proxy dest arg is not an %s address\n", iptype);
     goto proxyfail;
     }
@@ -462,14 +476,14 @@ else if (ret >= 8 && memcmp(hdr.v1.line, "PROXY", 5) == 0)
   p = sp + 1;
   if ((sp = Ustrchr(p, ' ')) == NULL)
     {
-    DEBUG(D_receive) debug_printf("Did not find proxied src port\n");
+    DEBUG(receive) debug_printf("Did not find proxied src port\n");
     goto proxyfail;
     }
   *sp = '\0';
   tmp_port = strtol(CCS p, &endc, 10);
   if (*endc || tmp_port == 0)
     {
-    DEBUG(D_receive)
+    DEBUG(receive)
       debug_printf("Proxied src port '%s' not an integer\n", p);
     goto proxyfail;
     }
@@ -478,13 +492,13 @@ else if (ret >= 8 && memcmp(hdr.v1.line, "PROXY", 5) == 0)
   p = sp + 1;
   if ((sp = Ustrchr(p, '\0')) == NULL)
     {
-    DEBUG(D_receive) debug_printf("Did not find proxy dest port\n");
+    DEBUG(receive) debug_printf("Did not find proxy dest port\n");
     goto proxyfail;
     }
   tmp_port = strtol(CCS p, &endc, 10);
   if (*endc || tmp_port == 0)
     {
-    DEBUG(D_receive)
+    DEBUG(receive)
       debug_printf("Proxy dest port '%s' not an integer\n", p);
     goto proxyfail;
     }
@@ -494,13 +508,13 @@ else if (ret >= 8 && memcmp(hdr.v1.line, "PROXY", 5) == 0)
 else
   {
   /* Wrong protocol */
-  DEBUG(D_receive) debug_printf("Invalid proxy protocol version negotiation\n");
+  DEBUG(receive) debug_printf("Invalid proxy protocol version negotiation\n");
   (void) swallow_until_crlf(smtp_in_fd, US &hdr, ret, sizeof(hdr)-ret);
   goto proxyfail;
   }
 
 done:
-  DEBUG(D_receive)
+  DEBUG(receive)
     debug_printf("Valid %s sender from Proxy Protocol header\n", iptype);
   yield = proxy_session;
 
@@ -508,7 +522,9 @@ done:
 should cause a synchronization failure */
 
 proxyfail:
-  DEBUG(D_receive) if (had_command_timeout)
+
+  ALARM(0);
+  DEBUG(receive) if (had_command_timeout)
     debug_printf("Timeout while reading proxy header\n");
 
   if (yield)
@@ -518,15 +534,29 @@ proxyfail:
     host_build_sender_fullhost();
     }
   else
-    {
-    f.proxy_session_failed = TRUE;
-    DEBUG(D_receive)
+    DEBUG(receive)
       debug_printf("Failure to extract proxied host, only QUIT allowed\n");
-    }
 
-ALARM(0);
-return;
+return yield;
 }
+
+
+/******************************************************************************/
+/* Module API */
+
+static void * proxy_functions[] = {
+  [PROXY_PROTO_START] =	(void *) proxy_protocol,
+};
+
+misc_module_info proxy_module_info =
+{
+  .name =		US"proxy",
+# ifdef DYNLOOKUP
+  .dyn_magic =		MISC_MODULE_MAGIC,
+# endif
+  .functions =		proxy_functions,
+  .functions_count =	nelem(proxy_functions),
+};
 #endif	/*SUPPORT_PROXY*/
 
 /* vi: aw ai sw=2
diff --git a/src/src/miscmods/proxy_api.h b/src/src/miscmods/proxy_api.h
new file mode 100644
index 000000000..4cd57908c
--- /dev/null
+++ b/src/src/miscmods/proxy_api.h
@@ -0,0 +1,14 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) The Exim Maintainers 2025 */
+/* See the file NOTICE for conditions of use and distribution. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/* API definitions for the proxy-protocol module */
+
+
+/* Function table entry numbers */
+
+#define	PROXY_PROTO_START	0
diff --git a/src/src/miscmods/radius.c b/src/src/miscmods/radius.c
index 3f8232756..3c512def5 100644
--- a/src/src/miscmods/radius.c
+++ b/src/src/miscmods/radius.c
@@ -59,8 +59,8 @@ using its original API. At release 0.4.0 the API changed. */
 more data strings.
 
 Arguments:
-  s        a colon-separated list of strings
-  errptr   where to point an error message
+  radius_args	a colon-separated list of strings
+  errptr	where to point an error message
 
 Returns:   OK if authentication succeeded
            FAIL if authentication failed
@@ -68,12 +68,10 @@ Returns:   OK if authentication succeeded
 */
 
 static int
-auth_call_radius(const uschar *s, uschar **errptr)
+auth_call_radius(const uschar * radius_args, uschar ** errptr)
 {
-uschar *user;
-const uschar *radius_args = s;
-int result;
-int sep = ':';
+uschar * user, * pwd;
+int sep = ':', result;
 
 #ifdef RADIUS_LIB_RADLIB
   struct rad_handle *h;
@@ -89,9 +87,10 @@ int sep = ':';
 
 
 if (!(user = string_nextinlist(&radius_args, &sep, NULL, 0))) user = US"";
+pwd = string_nextinlist(&radius_args, &sep, NULL, 0);
 
-DEBUG(D_auth) debug_printf("Running RADIUS authentication for user %q "
-               "and %q\n", user, radius_args);
+DEBUG(auth) debug_printf("Running RADIUS authentication for user %q "
+               "and %q\n", user, pwd);
 
 *errptr = NULL;
 
@@ -112,7 +111,7 @@ else if (rc_read_dictionary(rc_conf_str("dictionary")) != 0)
 else if (!rc_avpair_add(&send, PW_USER_NAME, user, 0))
   *errptr = US"RADIUS: add user name failed";
 
-else if (!rc_avpair_add(&send, PW_USER_PASSWORD, CS radius_args, 0))
+else if (!rc_avpair_add(&send, PW_USER_PASSWORD, pwd, 0))
   *errptr = US"RADIUS: add password failed");
 
 else if (!rc_avpair_add(&send, PW_SERVICE_TYPE, &service, 0))
@@ -140,7 +139,7 @@ else if (!rc_avpair_add(h, &send, PW_SERVICE_TYPE, &service, 0, 0))
 
 if (*errptr)
   {
-  DEBUG(D_auth) debug_printf("%s\n", *errptr);
+  DEBUG(auth) debug_printf("%s\n", *errptr);
   return ERROR;
   }
 
@@ -150,7 +149,7 @@ result = rc_auth(0, send, &received, msg);
 result = rc_auth(h, 0, send, &received, msg);
 #endif
 
-DEBUG(D_auth) debug_printf("RADIUS code returned %d\n", result);
+DEBUG(auth) debug_printf("RADIUS code returned %d\n", result);
 
 switch (result)
   {
@@ -212,7 +211,7 @@ else
       break;
     }
 
-if (*errptr) DEBUG(D_auth) debug_printf("%s\n", *errptr);
+if (*errptr) DEBUG(auth) debug_printf("%s\n", *errptr);
 rad_close(h);
 return result;
 
diff --git a/src/src/miscmods/sieve_filter.c b/src/src/miscmods/sieve_filter.c
index 38b408f1b..b6425f440 100644
--- a/src/src/miscmods/sieve_filter.c
+++ b/src/src/miscmods/sieve_filter.c
@@ -291,20 +291,17 @@ if (address->ptr > 0)
   uschar * error;
   const uschar * ss = parse_extract_address(address->s, &error,
 					    &start, &end, &domain, FALSE);
-  if (!ss)
-    {
-    filter->errmsg = string_sprintf("malformed address %q (%s)",
-      address->s, error);
-    return -1;
-    }
-  else
+  if (ss)
     return 1;
+
+  filter->errmsg = string_sprintf("malformed address %q (%s)",
+      address->s, error);
   }
 else
-  {
   filter->errmsg = CUS "empty address";
-  return -1;
-  }
+
+DEBUG(filter) debug_printf_indent("%s\n", filter->errmsg);
+return -1;
 }
 
 
@@ -339,7 +336,11 @@ for (t = s = str->s, e = s + str->ptr; s < e; )
             | (isdigit(s[2]) ? s[2]-'0' : tolower(s[2])-'a'+10);
       s += 3;
       }
-    else return FALSE;
+    else
+      {
+      DEBUG(filter) debug_printf_indent("uri decode: bad encoding\n");
+      return FALSE;
+      }
     }
   else
     *t++ = *s++;
@@ -403,7 +404,7 @@ if (*uri && *uri != '?')
       if (!uri_decode(to))
         {
         filter->errmsg = US"Invalid URI encoding";
-        return -1;
+        goto bad;
         }
       new = store_get(sizeof(string_item), GET_UNTAINTED);
       new->text = string_from_gstring(to);
@@ -413,7 +414,7 @@ if (*uri && *uri != '?')
     else
       {
       filter->errmsg = US"Missing addr-spec in URI";
-      return -1;
+      goto bad;
       }
     if (*uri == '%') uri += 3;
     else break;
@@ -432,14 +433,14 @@ if (*uri == '?')
       if (!uri_decode(hname))
         {
         filter->errmsg = US"Invalid URI encoding";
-        return -1;
+        goto bad;
         }
       }
     /* match = */
     if (*uri++ != '=')
       {
       filter->errmsg = US"Missing equal after hname";
-      return -1;
+      goto bad;
       }
 
     /* match hvalue */
@@ -451,7 +452,7 @@ if (*uri == '?')
       if (!uri_decode(hvalue))
         {
         filter->errmsg = US"Invalid URI encoding";
-        return -1;
+        goto bad;
         }
       }
     if (hname->ptr == 2 && strcmpic(hname->s, US"to") == 0)
@@ -492,9 +493,13 @@ if (*uri == '?')
 if (*uri)
   {
   filter->errmsg = US"Syntactically invalid URI";
-  return -1;
+  goto bad;
   }
 return 1;
+
+bad:
+  DEBUG(filter) debug_printf_indent("%s\n", filter->errmsg);
+  return -1;
 }
 #endif
 
@@ -649,7 +654,11 @@ while (n < nend)
       case '\\':
         {
         ++npart;
-        if (npart == nend) return -1;
+        if (npart == nend)
+	  {
+	  DEBUG(filter) debug_printf_indent("glob pattern error\n");
+	  return -1;
+	  }
         /* FALLTHROUGH */
         }
       default:
@@ -772,8 +781,7 @@ compare(struct Sieve * filter, const gstring * needle, const gstring * haystack,
 {
 int r = 0;
 
-if (   (filter_test != FTEST_NONE && debug_selector != 0)
-   || (debug_selector & D_filter) != 0)
+if ((filter_test != FTEST_NONE && ANY_DEBUG) || IS_DEBUG(filter))
   {
   debug_printf_indent("String comparison (match ");
   switch (mt)
@@ -790,8 +798,10 @@ if (   (filter_test != FTEST_NONE && debug_selector != 0)
     case COMP_ASCII_NUMERIC: debug_printf_indent("i;ascii-numeric"); break;
     }
   debug_printf_indent("\"):\n");
-  debug_printf_indent("  Search = %s (%d chars)\n", needle->s, needle->ptr);
-  debug_printf_indent("  Inside = %s (%d chars)\n", haystack->s, haystack->ptr);
+  debug_printf_indent("  Search = %.*W (%d chars)\n",
+				  needle->ptr, needle->s, needle->ptr);
+  debug_printf_indent("  Inside = %.*W (%d chars)\n",
+				  haystack->ptr, haystack->s, haystack->ptr);
   }
 switch (mt)
   {
@@ -859,8 +869,7 @@ switch (mt)
       }
     break;
   }
-if ((filter_test != FTEST_NONE && debug_selector != 0) ||
-  (debug_selector & D_filter) != 0)
+if ((filter_test != FTEST_NONE && ANY_DEBUG) || IS_DEBUG(filter))
   debug_printf_indent("  Result %s\n", r?"true":"false");
 return r;
 }
@@ -976,15 +985,14 @@ for (new_addr = *generated; new_addr; new_addr = new_addr->next)
 	)
      )
     {
-    if (  filter_test != FTEST_NONE && debug_selector != 0
-       || (debug_selector & D_filter) != 0)
+    if ((filter_test != FTEST_NONE && ANY_DEBUG) || IS_DEBUG(filter))
       debug_printf_indent("Repeated %s `%s' ignored.\n",
 			  file ? "fileinto" : "redirect", addr);
 
     return;
     }
 
-if ((filter_test != FTEST_NONE && debug_selector != 0) || (debug_selector & D_filter) != 0)
+if ((filter_test != FTEST_NONE && ANY_DEBUG) || IS_DEBUG(filter))
   debug_printf_indent("%s `%s'\n", file ? "fileinto" : "redirect", addr);
 
 new_addr = deliver_make_addr(addr, TRUE);
@@ -1022,9 +1030,10 @@ expand_header(gstring * value, const gstring * header)
 {
 uschar *s, *r, *t;
 uschar *errmsg;
+const uschar * cs;
 
 value->ptr = 0;
-value->s = (uschar*)0;
+value->s = NULL;
 
 t = r = s = expand_string(string_sprintf("$rheader_%s", quote(header)));
 if (!t) return;
@@ -1037,7 +1046,10 @@ while (*r)
 
 while (t>s && (*(t-1) == ' ' || *(t-1) == '\t')) --t;
 *t = '\0';
-value->s = rfc2047_decode(s, check_rfc2047_length, US"utf-8", '\0', &value->ptr, &errmsg);
+cs = rfc2047_decode(s, check_rfc2047_length, US"utf-8", '\0', &value->ptr, &errmsg);
+
+/* possible embedded NUL, so not string_copyn() */
+memcpy((value->s = store_get(value->ptr, cs)), cs, (size_t)value->ptr);
 }
 
 
@@ -1079,6 +1091,7 @@ while (*filter->pc)
   else ++filter->pc;
   }
 filter->errmsg = CUS "missing end of comment";
+DEBUG(filter) debug_printf_indent("%s\n", filter->errmsg);
 return -1;
 }
 
@@ -1112,6 +1125,7 @@ while (*filter->pc)
     ++filter->pc;
 
 filter->errmsg = CUS "missing end of comment";
+DEBUG(filter) debug_printf_indent("%s\n", filter->errmsg);
 return -1;
 }
 
@@ -1205,7 +1219,10 @@ do
   if (*src == ' ' || *src == '\t' || *src == '\n')
     while (*src == ' ' || *src == '\t' || *src == '\n') ++src;
   else
+    {
+    DEBUG(filter) debug_printf_indent("hex decode: bad syntax\n");
     return -1;
+    }
   }
 while (src < end);
 return decoded;
@@ -1255,8 +1272,16 @@ do
   for (c = 0, d = 0;
        d < 7 && src < end && isxdigit(n = tolower(*src));
        c = (c<<4)|(n>= '0' && n<= '9' ? n-'0' : 10+(n-'a')), ++d, ++src) ;
-  if (src == hex_seq) return -1;
-  if (d == 7 || (!((c >= 0 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0x10ffff)))) return -2;
+  if (src == hex_seq)
+    {
+    DEBUG(filter) debug_printf_indent("unicode decode: bad syntax\n");
+    return -1;
+    }
+  if (d == 7 || (!((c >= 0 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0x10ffff))))
+    {
+    DEBUG(filter) debug_printf_indent("unicode decode: char not in range\n");
+    return -2;
+    }
   if (c<128)
     {
     if (dst) *dst++ = c;
@@ -1457,7 +1482,7 @@ if (*filter->pc == '"') /* quoted string */
       }
     }
   filter->errmsg = CUS "missing end of string";
-  return -1;
+  goto bad;
   }
 else if (Ustrncmp(filter->pc, CUS "text:", 5) == 0) /* multiline string */
   {
@@ -1484,7 +1509,7 @@ else if (Ustrncmp(filter->pc, CUS "text:", 5) == 0) /* multiline string */
   else
     {
     filter->errmsg = CUS "syntax error";
-    return -1;
+    goto bad;
     }
   while (*filter->pc)
     {
@@ -1539,9 +1564,13 @@ else if (Ustrncmp(filter->pc, CUS "text:", 5) == 0) /* multiline string */
       }
     }
   filter->errmsg = CUS "missing end of multi line string";
-  return -1;
+  goto bad;
   }
 else return 0;
+
+bad:
+  DEBUG(filter) debug_printf_indent("%s\n", filter->errmsg);
+  return -1;
 }
 
 
@@ -1608,7 +1637,7 @@ if (*filter->pc>= '0' && *filter->pc<= '9')
   if (errno == ERANGE)
     {
     filter->errmsg = CUstrerror(ERANGE);
-    return -1;
+    goto bad;
     }
   filter->pc = e;
   u = 1;
@@ -1618,17 +1647,18 @@ if (*filter->pc>= '0' && *filter->pc<= '9')
   if (d>(ULONG_MAX/u))
     {
     filter->errmsg = CUstrerror(ERANGE);
-    return -1;
+    goto bad;
     }
   d *= u;
   *data = d;
   return 1;
   }
-else
-  {
-  filter->errmsg = CUS "missing number";
+
+filter->errmsg = CUS "missing number";
+
+bad:
+  DEBUG(filter) debug_printf_indent("%s\n", filter->errmsg);
   return -1;
-  }
 }
 
 
@@ -1681,6 +1711,7 @@ if (*filter->pc == '[') /* string list */
       else
         {
         filter->errmsg = CUS "missing string";
+	DEBUG(filter) debug_printf_indent("%s\n", filter->errmsg);
         goto error;
         }
       }
@@ -1701,13 +1732,13 @@ if (*filter->pc == '[') /* string list */
   else
     {
     filter->errmsg = CUS "missing closing bracket";
+    DEBUG(filter) debug_printf_indent("%s\n", filter->errmsg);
     goto error;
     }
   }
 else /* single string */
   {
-  if (!(d = store_get(sizeof(gstring)*2, GET_UNTAINTED)))
-    return -1;
+  d = store_get(sizeof(gstring)*2, GET_UNTAINTED);
 
   m = parse_string(filter, &d[0]);
   if (m == -1)
@@ -1728,6 +1759,7 @@ else /* single string */
   }
 error:
 filter->errmsg = CUS "missing string list";
+DEBUG(filter) debug_printf_indent("%s\n", filter->errmsg);
 return -1;
 }
 
@@ -1759,7 +1791,7 @@ if (parse_identifier(filter, CUS ":user") == 1)
   if (!filter->require_subaddress)
     {
     filter->errmsg = CUS "missing previous require \"subaddress\";";
-    return -1;
+    goto bad;
     }
   *a = ADDRPART_USER;
   return 1;
@@ -1769,7 +1801,7 @@ else if (parse_identifier(filter, CUS ":detail") == 1)
   if (!filter->require_subaddress)
     {
     filter->errmsg = CUS "missing previous require \"subaddress\";";
-    return -1;
+    goto bad;
     }
   *a = ADDRPART_DETAIL;
   return 1;
@@ -1792,6 +1824,10 @@ else if (parse_identifier(filter, CUS ":all") == 1)
   return 1;
   }
 else return 0;
+
+bad:
+  DEBUG(filter) debug_printf_indent("%s\n", filter->errmsg);
+  return -1;
 }
 
 
@@ -1926,7 +1962,9 @@ if (*filter->pc == '(')
     switch (parse_test(filter, &cond, exec))
       {
       case -1: return -1;
-      case 0: filter->errmsg = CUS "missing test"; return -1;
+      case 0: filter->errmsg = CUS "missing test";
+	      DEBUG(filter) debug_printf_indent("%s\n", filter->errmsg);
+	      return -1;
       default: ++*n; if (cond) ++*num_true; break;
       }
     if (parse_white(filter) == -1) return -1;
@@ -1941,6 +1979,7 @@ if (*filter->pc == '(')
   else
     {
     filter->errmsg = CUS "missing closing paren";
+    DEBUG(filter) debug_printf_indent("%s\n", filter->errmsg);
     return -1;
     }
   }
@@ -1966,7 +2005,7 @@ Returns:      1                success
 static int
 parse_test(struct Sieve *filter, int *cond, int exec)
 {
-if (parse_white(filter) == -1) return -1;
+if (parse_white(filter) == -1) goto bad;
 if (parse_identifier(filter, CUS "address"))
   {
   /*
@@ -1985,58 +2024,58 @@ if (parse_identifier(filter, CUS "address"))
 
   for (;;)
     {
-    if (parse_white(filter) == -1) return -1;
+    if (parse_white(filter) == -1) goto bad;
     if ((m = parse_addresspart(filter, &addressPart)) != 0)
       {
-      if (m == -1) return -1;
+      if (m == -1) goto bad;
       if (ap)
         {
         filter->errmsg = CUS "address part already specified";
-        return -1;
+        goto bad;
         }
       else ap = 1;
       }
     else if ((m = parse_comparator(filter, &comparator)) != 0)
       {
-      if (m == -1) return -1;
+      if (m == -1) goto bad;
       if (co)
         {
         filter->errmsg = CUS "comparator already specified";
-        return -1;
+        goto bad;
         }
       else co = 1;
       }
     else if ((m = parse_matchtype(filter, &matchType)) != 0)
       {
-      if (m == -1) return -1;
+      if (m == -1) goto bad;
       if (mt)
         {
         filter->errmsg = CUS "match type already specified";
-        return -1;
+        goto bad;
         }
       else mt = 1;
       }
     else break;
     }
   if (parse_white(filter) == -1)
-    return -1;
+    goto bad;
   if ((m = parse_stringlist(filter, &hdr)) != 1)
     {
     if (m == 0) filter->errmsg = CUS "header string list expected";
-    return -1;
+    goto bad;
     }
   if (parse_white(filter) == -1)
-    return -1;
+    goto bad;
   if ((m = parse_stringlist(filter, &key)) != 1)
     {
     if (m == 0) filter->errmsg = CUS "key string list expected";
-    return -1;
+    goto bad;
     }
   *cond = 0;
   for (gstring * h = hdr; h->ptr != -1 && !*cond; ++h)
     {
     const uschar * header_value = NULL;
-    uschar * extracted_addr, * end_addr;
+    uschar * extracted_addr;
 
     if (  !eq_asciicase(h, &str_from, FALSE)
        && !eq_asciicase(h, &str_to, FALSE)
@@ -2048,7 +2087,7 @@ if (parse_identifier(filter, CUS "address"))
        )
       {
       filter->errmsg = CUS "invalid header field";
-      return -1;
+      goto bad;
       }
     if (exec)
       {
@@ -2056,20 +2095,20 @@ if (parse_identifier(filter, CUS "address"))
       if (!(header_value = expand_string(string_sprintf("$rheader_%s", quote(h)))))
         {
         filter->errmsg = CUS "header string expansion failed";
-        return -1;
+        goto bad;
         }
       f.parse_allow_group = TRUE;
       while (*header_value && !*cond)
         {
-        uschar *error;
+        uschar * part = NULL, * error;
+	const uschar * end_addr, * ss;
         int start, end, domain;
-        int saveend;
-        uschar *part = NULL;
 
         end_addr = parse_find_address_end(header_value, FALSE);
-        saveend = *end_addr;
-        *end_addr = 0;
-        extracted_addr = parse_extract_address(header_value, &error, &start, &end, &domain, FALSE);
+	ss = *end_addr
+	  ? header_value : string_copyn(header_value, end_addr - header_value);
+        extracted_addr = parse_extract_address(ss, &error,
+						&start, &end, &domain, FALSE);
 
         if (extracted_addr) switch (addressPart)
           {
@@ -2084,19 +2123,18 @@ if (parse_identifier(filter, CUS "address"))
 #endif
           }
 
-        *end_addr = saveend;
         if (part && extracted_addr)
 	  {
 	  gstring partStr = {.s = part, .ptr = Ustrlen(part), .size = Ustrlen(part)+1};
           for (gstring * k = key; k->ptr != - 1; ++k)
             {
 	    *cond = compare(filter, k, &partStr, comparator, matchType);
-	    if (*cond == -1) return -1;
+	    if (*cond == -1) goto bad;
 	    if (*cond) break;
             }
 	  }
 
-        if (saveend == 0) break;
+        if (*end_addr) break;
         header_value = end_addr + 1;
         }
       f.parse_allow_group = FALSE;
@@ -2115,8 +2153,8 @@ else if (parse_identifier(filter, CUS "allof"))
 
   switch (parse_testlist(filter, &n, &num_true, exec))
     {
-    case -1: return -1;
-    case 0: filter->errmsg = CUS "missing test list"; return -1;
+    case -1: goto bad;
+    case 0: filter->errmsg = CUS "missing test list"; goto bad;
     default: *cond = (n == num_true); return 1;
     }
   }
@@ -2130,8 +2168,8 @@ else if (parse_identifier(filter, CUS "anyof"))
 
   switch (parse_testlist(filter, &n, &num_true, exec))
     {
-    case -1: return -1;
-    case 0: filter->errmsg = CUS "missing test list"; return -1;
+    case -1: goto bad;
+    case 0: filter->errmsg = CUS "missing test list"; goto bad;
     default: *cond = (num_true>0); return 1;
     }
   }
@@ -2145,11 +2183,11 @@ else if (parse_identifier(filter, CUS "exists"))
   int m;
 
   if (parse_white(filter) == -1)
-    return -1;
+    goto bad;
   if ((m = parse_stringlist(filter, &hdr)) != 1)
     {
     if (m == 0) filter->errmsg = CUS "header string list expected";
-    return -1;
+    goto bad;
     }
   if (exec)
     {
@@ -2161,7 +2199,7 @@ else if (parse_identifier(filter, CUS "exists"))
       if (!header_def)
         {
         filter->errmsg = CUS "header string expansion failed";
-        return -1;
+        goto bad;
         }
       if (Ustrcmp(header_def,"false") == 0) *cond = 0;
       }
@@ -2193,42 +2231,42 @@ else if (parse_identifier(filter, CUS "header"))
   for (;;)
     {
     if (parse_white(filter) == -1)
-      return -1;
+      goto bad;
     if ((m = parse_comparator(filter, &comparator)) != 0)
       {
-      if (m == -1) return -1;
+      if (m == -1) goto bad;
       if (co)
         {
         filter->errmsg = CUS "comparator already specified";
-        return -1;
+        goto bad;
         }
       else co = 1;
       }
     else if ((m = parse_matchtype(filter, &matchType)) != 0)
       {
-      if (m == -1) return -1;
+      if (m == -1) goto bad;
       if (mt)
         {
         filter->errmsg = CUS "match type already specified";
-        return -1;
+        goto bad;
         }
       else mt = 1;
       }
     else break;
     }
   if (parse_white(filter) == -1)
-    return -1;
+    goto bad;
   if ((m = parse_stringlist(filter, &hdr)) != 1)
     {
     if (m == 0) filter->errmsg = CUS "header string list expected";
-    return -1;
+    goto bad;
     }
   if (parse_white(filter) == -1)
-    return -1;
+    goto bad;
   if ((m = parse_stringlist(filter, &key)) != 1)
     {
     if (m == 0) filter->errmsg = CUS "key string list expected";
-    return -1;
+    goto bad;
     }
   *cond = 0;
   for (gstring * h = hdr; h->ptr != -1 && !*cond; ++h)
@@ -2236,7 +2274,7 @@ else if (parse_identifier(filter, CUS "header"))
     if (!is_header(h))
       {
       filter->errmsg = CUS "invalid header field";
-      return -1;
+      goto bad;
       }
     if (exec)
       {
@@ -2249,13 +2287,13 @@ else if (parse_identifier(filter, CUS "header"))
       if (!header_value.s || !header_def)
         {
         filter->errmsg = CUS "header string expansion failed";
-        return -1;
+        goto bad;
         }
       for (gstring * k = key; k->ptr != -1; ++k)
         if (Ustrcmp(header_def,"true") == 0)
           {
           *cond = compare(filter, k, &header_value, comparator, matchType);
-          if (*cond == -1) return -1;
+          if (*cond == -1) goto bad;
           if (*cond) break;
           }
       }
@@ -2264,11 +2302,11 @@ else if (parse_identifier(filter, CUS "header"))
   }
 else if (parse_identifier(filter, CUS "not"))
   {
-  if (parse_white(filter) == -1) return -1;
+  if (parse_white(filter) == -1) goto bad;
   switch (parse_test(filter, cond, exec))
     {
-    case -1: return -1;
-    case 0: filter->errmsg = CUS "missing test"; return -1;
+    case -1: goto bad;
+    case 0: filter->errmsg = CUS "missing test"; goto bad;
     default: *cond = !*cond; return 1;
     }
   }
@@ -2282,16 +2320,16 @@ else if (parse_identifier(filter, CUS "size"))
   unsigned long limit;
   int overNotUnder;
 
-  if (parse_white(filter) == -1) return -1;
+  if (parse_white(filter) == -1) goto bad;
   if (parse_identifier(filter, CUS ":over")) overNotUnder = 1;
   else if (parse_identifier(filter, CUS ":under")) overNotUnder = 0;
   else
     {
     filter->errmsg = CUS "missing :over or :under";
-    return -1;
+    goto bad;
     }
-  if (parse_white(filter) == -1) return -1;
-  if (parse_number(filter, &limit) == -1) return -1;
+  if (parse_white(filter) == -1) goto bad;
+  if (parse_number(filter, &limit) == -1) goto bad;
   *cond = (overNotUnder ? (message_size>limit) : (message_size<limit));
   return 1;
   }
@@ -2322,56 +2360,56 @@ else if (parse_identifier(filter, CUS "envelope"))
   if (!filter->require_envelope)
     {
     filter->errmsg = CUS "missing previous require \"envelope\";";
-    return -1;
+    goto bad;
     }
   for (;;)
     {
-    if (parse_white(filter) == -1) return -1;
+    if (parse_white(filter) == -1) goto bad;
     if ((m = parse_comparator(filter, &comparator)) != 0)
       {
-      if (m == -1) return -1;
+      if (m == -1) goto bad;
       if (co)
         {
         filter->errmsg = CUS "comparator already specified";
-        return -1;
+        goto bad;
         }
       else co = 1;
       }
     else if ((m = parse_addresspart(filter, &addressPart)) != 0)
       {
-      if (m == -1) return -1;
+      if (m == -1) goto bad;
       if (ap)
         {
         filter->errmsg = CUS "address part already specified";
-        return -1;
+        goto bad;
         }
       else ap = 1;
       }
     else if ((m = parse_matchtype(filter, &matchType)) != 0)
       {
-      if (m == -1) return -1;
+      if (m == -1) goto bad;
       if (mt)
         {
         filter->errmsg = CUS "match type already specified";
-        return -1;
+        goto bad;
         }
       else mt = 1;
       }
     else break;
     }
   if (parse_white(filter) == -1)
-    return -1;
+    goto bad;
   if ((m = parse_stringlist(filter, &env)) != 1)
     {
     if (m == 0) filter->errmsg = CUS "envelope string list expected";
-    return -1;
+    goto bad;
     }
   if (parse_white(filter) == -1)
-    return -1;
+    goto bad;
   if ((m = parse_stringlist(filter, &key)) != 1)
     {
     if (m == 0) filter->errmsg = CUS "key string list expected";
-    return -1;
+    goto bad;
     }
   *cond = 0;
   for (gstring * e = env; e->ptr != -1 && !*cond; ++e)
@@ -2427,21 +2465,21 @@ else if (parse_identifier(filter, CUS "envelope"))
     else
       {
       filter->errmsg = CUS "invalid envelope string";
-      return -1;
+      goto bad;
       }
     if (exec && envelopeExpr)
       {
       if (!(envelope = expand_string(US envelopeExpr)))
         {
         filter->errmsg = CUS "header string expansion failed";
-        return -1;
+        goto bad;
         }
       for (gstring * k = key; k->ptr != -1; ++k)
         {
         gstring envelopeStr = {.s = envelope, .ptr = Ustrlen(envelope), .size = Ustrlen(envelope)+1};
 
         *cond = compare(filter, k, &envelopeStr, comparator, matchType);
-        if (*cond == -1) return -1;
+        if (*cond == -1) goto bad;
         if (*cond) break;
         }
       }
@@ -2462,14 +2500,14 @@ else if (parse_identifier(filter, CUS "valid_notify_method"))
   if (!filter->require_enotify)
     {
     filter->errmsg = CUS "missing previous require \"enotify\";";
-    return -1;
+    goto bad;
     }
   if (parse_white(filter) == -1)
-    return -1;
+    goto bad;
   if ((m = parse_stringlist(filter, &uris)) != 1)
     {
     if (m == 0) filter->errmsg = CUS "URI string list expected";
-    return -1;
+    goto bad;
     }
   if (exec)
     {
@@ -2506,28 +2544,28 @@ else if (parse_identifier(filter, CUS "notify_method_capability"))
   if (!filter->require_enotify)
     {
     filter->errmsg = CUS "missing previous require \"enotify\";";
-    return -1;
+    goto bad;
     }
   for (;;)
     {
-    if (parse_white(filter) == -1) return -1;
+    if (parse_white(filter) == -1) goto bad;
     if ((m = parse_comparator(filter, &comparator)) != 0)
       {
-      if (m == -1) return -1;
+      if (m == -1) goto bad;
       if (co)
         {
         filter->errmsg = CUS "comparator already specified";
-        return -1;
+        goto bad;
         }
       else co = 1;
       }
     else if ((m = parse_matchtype(filter, &matchType)) != 0)
       {
-      if (m == -1) return -1;
+      if (m == -1) goto bad;
       if (mt)
         {
         filter->errmsg = CUS "match type already specified";
-        return -1;
+        goto bad;
         }
       else mt = 1;
       }
@@ -2536,21 +2574,21 @@ else if (parse_identifier(filter, CUS "notify_method_capability"))
     if ((m = parse_string(filter, &uri)) != 1)
       {
       if (m == 0) filter->errmsg = CUS "missing notification URI string";
-      return -1;
+      goto bad;
       }
     if (parse_white(filter) == -1)
-      return -1;
+      goto bad;
     if ((m = parse_string(filter, &capa)) != 1)
       {
       if (m == 0) filter->errmsg = CUS "missing notification capability string";
-      return -1;
+      goto bad;
       }
     if (parse_white(filter) == -1)
-      return -1;
+      goto bad;
     if ((m = parse_stringlist(filter, &keys)) != 1)
       {
       if (m == 0) filter->errmsg = CUS "missing key string list";
-      return -1;
+      goto bad;
       }
     if (exec)
       {
@@ -2565,7 +2603,7 @@ else if (parse_identifier(filter, CUS "notify_method_capability"))
           for (gstring * k = keys; k->ptr != -1; ++k)
             {
             *cond = compare(filter, k, &str_maybe, comparator, matchType);
-            if (*cond == -1) return -1;
+            if (*cond == -1) goto bad;
             if (*cond) break;
             }
       }
@@ -2573,6 +2611,10 @@ else if (parse_identifier(filter, CUS "notify_method_capability"))
   }
 #endif
 else return 0;
+
+bad:
+  DEBUG(filter) debug_printf_indent("%s\n", filter->errmsg);
+  return -1;
 }
 
 
@@ -2609,6 +2651,7 @@ if (*filter->pc == '{')
     return 1;
     }
   filter->errmsg = CUS "expecting command or closing brace";
+  DEBUG(filter) debug_printf_indent("%s\n", filter->errmsg);
   return -1;
   }
 return 0;
@@ -2638,6 +2681,7 @@ if (*filter->pc == ';')
   return 1;
   }
 filter->errmsg = CUS "missing semicolon";
+DEBUG(filter) debug_printf_indent("%s\n", filter->errmsg);
 return -1;
 }
 
@@ -2679,10 +2723,9 @@ while (*filter->pc)
     if (m == 0)
       {
       filter->errmsg = CUS "missing test";
-      return -1;
+      goto bad;
       }
-    if ((filter_test != FTEST_NONE && debug_selector != 0) ||
-        (debug_selector & D_filter) != 0)
+    if ((filter_test != FTEST_NONE && ANY_DEBUG) || IS_DEBUG(filter))
       {
       if (exec) debug_printf_indent("if %s\n", cond?"true":"false");
       }
@@ -2692,7 +2735,7 @@ while (*filter->pc)
     if (m == 0)
       {
       filter->errmsg = CUS "missing block";
-      return -1;
+      goto bad;
       }
     unsuccessful = !cond;
     for (;;) /* elsif test block */
@@ -2709,10 +2752,9 @@ while (*filter->pc)
         if (m == 0)
           {
           filter->errmsg = CUS "missing test";
-          return -1;
+	  goto bad;
           }
-        if ((filter_test != FTEST_NONE && debug_selector != 0) ||
-            (debug_selector & D_filter) != 0)
+	if ((filter_test != FTEST_NONE && ANY_DEBUG) || IS_DEBUG(filter))
           {
           if (exec) debug_printf_indent("elsif %s\n", cond?"true":"false");
           }
@@ -2722,7 +2764,7 @@ while (*filter->pc)
         if (m == 0)
           {
           filter->errmsg = CUS "missing block";
-          return -1;
+	  goto bad;
           }
         if (exec && unsuccessful && cond)
 	  unsuccessful = 0;
@@ -2740,7 +2782,7 @@ while (*filter->pc)
       if (m == 0)
         {
         filter->errmsg = CUS "missing block";
-        return -1;
+	goto bad;
         }
       }
     }
@@ -2806,7 +2848,7 @@ while (*filter->pc)
         if (!filter->require_copy)
           {
           filter->errmsg = CUS "missing previous require \"copy\";";
-          return -1;
+	  goto bad;
           }
 	copy = 1;
         }
@@ -2818,12 +2860,12 @@ while (*filter->pc)
       {
       if (m == 0)
 	filter->errmsg = CUS "missing redirect recipient string";
-      return -1;
+      goto bad;
       }
     if (strchr(CCS recipient.s, '@') == NULL)
       {
       filter->errmsg = CUS "unqualified recipient address";
-      return -1;
+      goto bad;
       }
     if (exec)
       {
@@ -2850,7 +2892,7 @@ while (*filter->pc)
     if (!filter->require_fileinto)
       {
       filter->errmsg = CUS "missing previous require \"fileinto\";";
-      return -1;
+      goto bad;
       }
     for (;;)
       {
@@ -2861,7 +2903,7 @@ while (*filter->pc)
         if (!filter->require_copy)
           {
           filter->errmsg = CUS "missing previous require \"copy\";";
-          return -1;
+          goto bad;
           }
           copy = 1;
         }
@@ -2872,7 +2914,7 @@ while (*filter->pc)
     if ((m = parse_string(filter, &folder)) != 1)
       {
       if (m == 0) filter->errmsg = CUS "missing fileinto folder string";
-      return -1;
+      goto bad;
       }
     m = 0; s = folder.s;
     if (folder.ptr == 0)
@@ -2887,7 +2929,7 @@ while (*filter->pc)
     if (m)
       {
       filter->errmsg = CUS "invalid folder";
-      return -1;
+      goto bad;
       }
     if (exec)
       {
@@ -2925,14 +2967,14 @@ while (*filter->pc)
     if (!filter->require_enotify)
       {
       filter->errmsg = CUS "missing previous require \"enotify\";";
-      return -1;
+      goto bad;
       }
     envelope_from = sender_address && sender_address[0]
      ? expand_string(US"$local_part_prefix$local_part$local_part_suffix@$domain") : US "";
     if (!envelope_from)
       {
       filter->errmsg = CUS "expansion failure for envelope from";
-      return -1;
+      goto bad;
       }
     for (;;)
       {
@@ -2945,7 +2987,7 @@ while (*filter->pc)
         if ((m = parse_string(filter, &from)) != 1)
           {
           if (m == 0) filter->errmsg = CUS "from string expected";
-          return -1;
+          goto bad;
           }
         }
       else if (parse_identifier(filter, CUS ":importance") == 1)
@@ -2956,12 +2998,12 @@ while (*filter->pc)
           {
           if (m == 0)
 	    filter->errmsg = CUS "importance string expected";
-          return -1;
+          goto bad;
           }
         if (importance.ptr != 1 || importance.s[0] < '1' || importance.s[0] > '3')
           {
           filter->errmsg = CUS "invalid importance";
-          return -1;
+          goto bad;
           }
         }
       else if (parse_identifier(filter, CUS ":options") == 1)
@@ -2977,7 +3019,7 @@ while (*filter->pc)
           {
           if (m == 0)
 	    filter->errmsg = CUS "message string expected";
-          return -1;
+          goto bad;
           }
         }
       else break;
@@ -2988,7 +3030,7 @@ while (*filter->pc)
       {
       if (m == 0)
 	filter->errmsg = CUS "missing method string";
-      return -1;
+      goto bad;
       }
     if (parse_semicolon(filter) == -1)
       return -1;
@@ -3005,7 +3047,7 @@ while (*filter->pc)
       if (!auto_submitted_value.s || !auto_submitted_def)
         {
         filter->errmsg = CUS "header string expansion failed";
-        return -1;
+        goto bad;
         }
         if (Ustrcmp(auto_submitted_def,"true") != 0 || Ustrcmp(auto_submitted_value.s,"no") == 0)
         {
@@ -3031,7 +3073,8 @@ while (*filter->pc)
   #ifndef COMPILE_SYNTAX_CHECKER
           if (filter_test == FTEST_NONE)
             {
-            int pid, fd;
+            pid_t pid;
+            int fd;
 
             if ((pid = child_open_exim2(&fd, envelope_from, envelope_from,
 			US"sieve-notify")) >= 1)
@@ -3060,16 +3103,16 @@ while (*filter->pc)
               (void)child_close(pid, 0);
               }
             }
-          if ((filter_test != FTEST_NONE && debug_selector != 0) || debug_selector & D_filter)
+	  if ((filter_test != FTEST_NONE && ANY_DEBUG) || IS_DEBUG(filter))
             debug_printf_indent("Notification to `%s': '%s'.\n", method.s, message.ptr != -1 ? message.s : CUS "");
 #endif
           }
         else
-          if ((filter_test != FTEST_NONE && debug_selector != 0) || debug_selector & D_filter)
+	  if ((filter_test != FTEST_NONE && ANY_DEBUG) || IS_DEBUG(filter))
             debug_printf_indent("Repeated notification to `%s' ignored.\n", method.s);
         }
       else
-        if ((filter_test != FTEST_NONE && debug_selector != 0) || debug_selector & D_filter)
+	if ((filter_test != FTEST_NONE && ANY_DEBUG) || IS_DEBUG(filter))
           debug_printf_indent("Ignoring notification, triggering message contains Auto-submitted: field.\n");
       }
     }
@@ -3100,14 +3143,14 @@ while (*filter->pc)
     if (!filter->require_vacation)
       {
       filter->errmsg = CUS "missing previous require \"vacation\";";
-      return -1;
+      goto bad;
       }
     if (exec)
       {
       if (filter->vacation_ran)
         {
         filter->errmsg = CUS "trying to execute vacation more than once";
-        return -1;
+        goto bad;
         }
       filter->vacation_ran = TRUE;
       }
@@ -3144,7 +3187,7 @@ while (*filter->pc)
           {
           if (m == 0)
 	    filter->errmsg = CUS "subject string expected";
-          return -1;
+          goto bad;
           }
         }
       else if (parse_identifier(filter, CUS ":from") == 1)
@@ -3155,7 +3198,7 @@ while (*filter->pc)
           {
           if (m == 0)
 	    filter->errmsg = CUS "from string expected";
-          return -1;
+          goto bad;
           }
         if (check_mail_address(filter, &from) != 1)
           return -1;
@@ -3168,7 +3211,7 @@ while (*filter->pc)
           {
           if (m == 0)
 	    filter->errmsg = CUS "addresses string list expected";
-          return -1;
+          goto bad;
           }
         for (gstring * a = addresses; a->ptr != -1; ++a)
           {
@@ -3191,7 +3234,7 @@ while (*filter->pc)
           {
           if (m == 0)
 	    filter->errmsg = CUS "handle string expected";
-          return -1;
+          goto bad;
           }
         }
       else break;
@@ -3202,7 +3245,7 @@ while (*filter->pc)
       {
       if (m == 0)
 	filter->errmsg = CUS "missing reason string";
-      return -1;
+      goto bad;
       }
     if (reason_is_mime)
       {
@@ -3213,7 +3256,7 @@ while (*filter->pc)
       if (s<end)
         {
         filter->errmsg = CUS "MIME reason string contains 8bit text";
-        return -1;
+        goto bad;
         }
       }
     if (parse_semicolon(filter) == -1) return -1;
@@ -3229,7 +3272,7 @@ while (*filter->pc)
       if (!(mi = misc_mod_find(US"exim_filter", NULL)))
         {
         filter->errmsg = CUS "test for 'personal': module not available";
-        return -1;
+        goto bad;
         }
       if ((((fn_t *) mi->functions)[EXIM_FILTER_PERSONAL])(aliases, TRUE))
         {
@@ -3262,7 +3305,7 @@ while (*filter->pc)
         for (int 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 && ANY_DEBUG) || IS_DEBUG(filter))
           debug_printf_indent("Sieve: mail was personal, vacation file basename: %s\n", hexdigest);
 
         if (filter_test == FTEST_NONE)
@@ -3341,7 +3384,7 @@ while (*filter->pc)
             }
           }
         }
-        else if ((filter_test != FTEST_NONE && debug_selector != 0) || (debug_selector & D_filter) != 0)
+	else if ((filter_test != FTEST_NONE && ANY_DEBUG) || IS_DEBUG(filter))
           debug_printf_indent("Sieve: mail was not personal, vacation would ignore it\n");
       }
     }
@@ -3349,6 +3392,10 @@ while (*filter->pc)
 #endif
   }
 return 1;
+
+bad:
+  DEBUG(filter) debug_printf_indent("%s\n", filter->errmsg);
+  return -1;
 }
 
 
@@ -3409,7 +3456,7 @@ if (exec && filter->vacation_directory && filter_test == FTEST_NONE)
      && errno != ENOENT)
     {
     filter->errmsg = CUS "unable to open vacation directory";
-    return -1;
+    goto bad;
     }
 
   if (oncelogdir)
@@ -3440,7 +3487,7 @@ while (parse_identifier(filter, CUS "require"))
   if ((m = parse_stringlist(filter, &cap)) != 1)
     {
     if (m == 0) filter->errmsg = CUS "capability string list expected";
-    return -1;
+    goto bad;
     }
   for (gstring * check = cap; check->s; ++check)
     {
@@ -3458,7 +3505,7 @@ while (parse_identifier(filter, CUS "require"))
       if (!filter->enotify_mailto_owner)
         {
         filter->errmsg = CUS "enotify disabled";
-        return -1;
+        goto bad;
         }
         filter->require_enotify = 1;
       }
@@ -3472,7 +3519,7 @@ while (parse_identifier(filter, CUS "require"))
       if (filter_test == FTEST_NONE && !filter->vacation_directory)
         {
         filter->errmsg = CUS "vacation disabled";
-        return -1;
+        goto bad;
         }
       filter->require_vacation = TRUE;
       }
@@ -3485,7 +3532,7 @@ while (parse_identifier(filter, CUS "require"))
     else
       {
       filter->errmsg = CUS "unknown capability";
-      return -1;
+      goto bad;
       }
     }
     if (parse_semicolon(filter) == -1) return -1;
@@ -3494,9 +3541,13 @@ while (parse_identifier(filter, CUS "require"))
   if (*filter->pc)
     {
     filter->errmsg = CUS "syntax error";
-    return -1;
+    goto bad;
     }
   return 1;
+
+bad:
+  DEBUG(filter) debug_printf_indent("%s\n", filter->errmsg);
+  return -1;
 }
 
 
@@ -3534,7 +3585,7 @@ struct Sieve sieve;
 int r;
 uschar * msg;
 
-DEBUG(D_route) debug_printf_indent("Sieve: start of processing\n");
+DEBUG(route) debug_printf_indent("Sieve: start of processing\n");
 expand_level++;
 sieve.filter = filter;
 
@@ -3609,7 +3660,7 @@ if (filter_test != FTEST_NONE) printf("%s\n", (const char*) msg);
 #endif
 
 expand_level--;
-DEBUG(D_route) debug_printf_indent("Sieve: end of processing\n");
+DEBUG(route) debug_printf_indent("Sieve: end of processing\n");
 return r;
 }
 
@@ -3643,3 +3694,5 @@ misc_module_info sieve_filter_module_info =
 };
 
 /* End of sieve_filter.c */
+/* vi: aw ai sw=2
+*/
diff --git a/src/src/transports/smtp_socks.c b/src/src/miscmods/socks.c
similarity index 85%
rename from src/src/transports/smtp_socks.c
rename to src/src/miscmods/socks.c
index 436674e9b..c97cee1d6 100644
--- a/src/src/transports/smtp_socks.c
+++ b/src/src/miscmods/socks.c
@@ -10,15 +10,10 @@
 /* SOCKS version 5 proxy, client-mode */
 
 #include "../exim.h"
-#include "smtp.h"
+#include "../transports/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
@@ -106,20 +101,20 @@ int len, i, j;
 switch(method)
   {
   default:
-    log_write(0, LOG_MAIN|LOG_PANIC,
+    log_write(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",
+    HDEBUG(transport|acl|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%n", AUTH_NAME_VER,
       i, sob->auth_name, j, sob->auth_pwd, &len);
 
-    HDEBUG(D_transport|D_acl|D_v)
+    HDEBUG(transport|acl|v)
       debug_printf_indent("  SOCKS>>%3.*H\n", len, s);
     if (send(fd, s, len, 0) < 0)
       return FAIL;
@@ -128,14 +123,14 @@ switch(method)
 #endif
     if (!fd_ready(fd, tmo) || read(fd, s, 2) != 2)
       return FAIL;
-    HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SOCKS<<%3.2H\n", s);
+    HDEBUG(transport|acl_v) debug_printf_indent("  SOCKS<<%3.2H\n", s);
     if (s[0] == AUTH_NAME_VER && s[1] == 0)
       {
-      HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  socks auth OK\n");
+      HDEBUG(transport|acl|v) debug_printf_indent("  socks auth OK\n");
       return OK;
       }
 
-    log_write(0, LOG_MAIN|LOG_PANIC, "socks auth failed");
+    log_write(LOG_MAIN|LOG_PANIC, "socks auth failed");
     errno = EPROTO;
     return FAIL;
   }
@@ -180,25 +175,25 @@ for (rnd = random_number(weights), i = 0; i < nproxies; i++)
       return i;
   }
 
-log_write(0, LOG_MAIN|LOG_PANIC,
+log_write(LOG_MAIN|LOG_PANIC,
   "%s unknown error (memory/cpu corruption?)", __FUNCTION__);
 return -1;
 }
 
 
 
-/* Make a connection via a socks proxy
+/*API: Make a connection via a socks proxy
 
 Arguments:
  sc		details for making connection: host, af, interface, transport
  early_data	data to send down the smtp channel (once proxied)
 
 Return value:
- 0 on success; -1 on failure, with errno set
+ connected file descriptor on success; -1 on failure, with errno set
 */
 
-int
-socks_sock_connect(smtp_connect_args * sc, const blob * early_data)
+static int
+socks_sock_connect(const smtp_connect_args * sc, const blob * early_data)
 {
 transport_instance * tb = sc->tblock;
 smtp_transport_options_block * ob = tb->drinst.options_block;
@@ -258,11 +253,13 @@ for(;;)
   {
   int idx;
   host_item proxy;
+
+  /* Large (64k if DANE supported) */
   smtp_connect_args proxy_sc = {.sock = -1};
 
   if ((idx = socks_get_proxy(proxies, nproxies)) < 0)
     {
-    HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  no proxies left\n");
+    HDEBUG(transport|acl|v) debug_printf_indent("  no proxies left\n");
     errno = EBUSY;
     return -1;
     }
@@ -286,13 +283,13 @@ for(;;)
     break;
     }
 
-  log_write(0, LOG_MAIN, "%s: %s", __FUNCTION__, strerror(errno));
+  log_write(LOG_MAIN, "%s: %s", __FUNCTION__, strerror(errno));
   sob->is_failed = TRUE;
   }
 
 /* Do the socks protocol stuff */
 
-HDEBUG(D_transport|D_acl|D_v)
+HDEBUG(transport|acl|v)
   debug_printf_indent("  SOCKS>> 05 01 %02x\n", sob->auth_type);
 
 /* expect method response */
@@ -305,7 +302,7 @@ if (  !fd_ready(fd, tmo)
    || read(fd, buf, 2) != 2
    )
   goto rcv_err;
-HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SOCKS<<%3.2H\n", buf);
+HDEBUG(transport|acl|v) debug_printf_indent("  SOCKS<<%3.2H\n", buf);
 if (  buf[0] != 5
    || socks_auth(fd, buf[1], sob, tmo) != OK
    )
@@ -339,7 +336,7 @@ if (  buf[0] != 5
  }
 
 state = US"connect";
-HDEBUG(D_transport|D_acl|D_v)
+HDEBUG(transport|acl|v)
   debug_printf_indent("  SOCKS>>%3.*H\n", (int)size, buf);
 if (send(fd, buf, size, 0) < 0)
   goto snd_err;
@@ -351,7 +348,7 @@ if (  !fd_ready(fd, tmo)
    || (size = read(fd, buf, size)) < 2
    )
   goto rcv_err;
-HDEBUG(D_transport|D_acl|D_v)
+HDEBUG(transport|acl|v)
   debug_printf_indent("  SOCKS<<%3.*H\n", (int)size, buf);
 
 if (  buf[0] != 5
@@ -364,14 +361,14 @@ proxy_external_address = string_copy(
 proxy_external_port = ntohs(*((uint16_t *)(buf + (buf[3] == 4 ? 20 : 8))));
 proxy_session = TRUE;
 
-HDEBUG(D_transport|D_acl|D_v)
+HDEBUG(transport|acl|v)
   debug_printf_indent("  proxy farside: [%s]:%d\n", proxy_external_address, proxy_external_port);
 
 if (early_data && early_data->data && early_data->len)
   if (send(fd, early_data->data, early_data->len, 0) < 0)
     {
     int save_errno = errno;
-    HDEBUG(D_transport|D_acl|D_v)
+    HDEBUG(transport|acl|v)
       {
       debug_printf_indent("failed: %s", CUstrerror(save_errno));
       if (save_errno == ETIMEDOUT)
@@ -386,25 +383,45 @@ if (early_data && early_data->data && early_data->len)
 return fd;
 
 snd_err:
-  HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  proxy snd_err %s: %s\n", state, strerror(errno));
+  HDEBUG(transport|acl|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)
+  HDEBUG(transport|acl|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));
+  HDEBUG(transport|acl|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;
 }
 
+/******************************************************************************/
+/* Module API */
+
+static void * socks_functions[] = {
+  [SOCKS_CONNECT] =	(void *) socks_sock_connect,
+};
+
+misc_module_info socks_module_info =
+{
+  .name =		US"socks",
+# ifdef DYNLOOKUP
+  .dyn_magic =		MISC_MODULE_MAGIC,
+# endif
+
+  .functions =		socks_functions,
+  .functions_count =	nelem(socks_functions),
+};
+
 #endif	/* entire file */
 /* vi: aw ai sw=2
 */
diff --git a/src/src/miscmods/socks_api.h b/src/src/miscmods/socks_api.h
new file mode 100644
index 000000000..6a083d3cb
--- /dev/null
+++ b/src/src/miscmods/socks_api.h
@@ -0,0 +1,14 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) The Exim Maintainers 2025 */
+/* See the file NOTICE for conditions of use and distribution. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/* API definitions for the socks module */
+
+
+/* Function table entry numbers */
+
+#define	SOCKS_CONNECT	0
diff --git a/src/src/miscmods/spf.c b/src/src/miscmods/spf.c
index 9d215f516..f0520581e 100644
--- a/src/src/miscmods/spf.c
+++ b/src/src/miscmods/spf.c
@@ -42,7 +42,8 @@ uschar * spf_smtp_comment       = NULL;
 uschar * spf_smtp_comment_template
                     /* Used to be: "Please%_see%_http://www.open-spf.org/Why?id=%{S}&ip=%{C}&receiver=%{R}" */
 				= US"Please%_see%_http://www.open-spf.org/Why";
-BOOL    spf_result_guessed     = FALSE;
+BOOL    spf_result_guessed	= FALSE;
+const uschar * spf_used_domain	= NULL;
 
 
 
@@ -67,7 +68,7 @@ SPF_dns_exim_lookup(SPF_dns_server_t *spf_dns_server,
   const char *domain, ns_type rr_type, int should_cache)
 {
 dns_answer * dnsa = store_get_dns_answer();
-dns_scan dnss;
+dns_scan dnss = {0};
 SPF_dns_rr_t * spfrr;
 unsigned found = 0;
 
@@ -84,18 +85,18 @@ SPF_dns_rr_t srr = {
   .source = spf_dns_server
 };
 
-DEBUG(D_receive) debug_printf("SPF_dns_exim_lookup '%s'\n", domain);
+DEBUG(receive)
+  { debug_printf_indent("SPF_dns_exim_lookup '%s'\n", domain); expand_level++; }
 
 /* Shortcircuit SPF RR lookups by returning NO_DATA.  They were obsoleted by
 RFC 6686/7208 years ago. see bug #1294 */
 
 if (rr_type == T_SPF)
   {
-  HDEBUG(D_host_lookup) debug_printf("faking NO_DATA for SPF RR(99) lookup\n");
+  HDEBUG(host_lookup)
+    debug_printf_indent("faking NO_DATA for SPF RR(99) lookup\n");
   srr.herrno = NO_DATA;
-  SPF_dns_rr_dup(&spfrr, &srr);
-  store_free_dns_answer(dnsa);
-  return spfrr;
+  goto out;
   }
 
 switch (dns_lookup(dnsa, US domain, rr_type, NULL))
@@ -115,11 +116,7 @@ switch (dns_lookup(dnsa, US domain, rr_type, NULL))
   }
 
 if (found == 0)
-  {
-  SPF_dns_rr_dup(&spfrr, &srr);
-  store_free_dns_answer(dnsa);
-  return spfrr;
-  }
+  goto out;
 
 srr.rr = store_malloc(sizeof(SPF_dns_rr_data_t) * found);
 
@@ -138,9 +135,15 @@ for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
 	s += 2;	/* skip the MX precedence field */
       case T_PTR:
 	{
+	/* We lose taint-tracking here, really just assuming the data
+	given to the spf library will never leak back out.  Not sure if
+	the lib assumes it can free this (it does for srr.rr) - meaning we
+	cannot use pool store. The use of malloc also for T_TXT implies so. */
+
 	uschar * buf = store_malloc(256);
-	(void)dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, s,
-	  (DN_EXPAND_ARG4_TYPE)buf, 256);
+	if (dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, s,
+	    (DN_EXPAND_ARG4_TYPE)buf, 256) < 0)
+	  continue;
 	s = buf;
 	break;
 	}
@@ -153,8 +156,8 @@ for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
 	if (rr->size < 1+6) continue;		/* min for version str */
 	if (strncmpic(rr->data+1, US SPF_VER_STR, 6) != 0)
 	  {
-	  HDEBUG(D_host_lookup) debug_printf("not an spf record: %.*s\n",
-	    (int) s[0], s+1);
+	  HDEBUG(host_lookup) debug_printf_indent("not an spf record: %.*s\n",
+						    (int) s[0], s+1);
 	  continue;
 	  }
 
@@ -168,9 +171,11 @@ for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
 	  }
 	if (!g)
 	  continue;
-	gstring_release_unused(g);
+	/* Again, we lose taint-tracking here */
 	s = string_copy_malloc(string_from_gstring(g));
-	DEBUG(D_receive) debug_printf("SPF_dns_exim_lookup '%s'\n", s);
+	gstring_reset(g);
+	gstring_release_unused(g);
+	DEBUG(receive) debug_printf_indent("SPF_dns_exim_lookup '%s'\n", s);
 	break;
 	}
 
@@ -178,8 +183,9 @@ for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
       case T_AAAA:
       default:
 	{
-	uschar * buf = store_malloc(dnsa->answerlen + 1);
-	s = memcpy(buf, s, dnsa->answerlen + 1);
+	uschar * buf = store_malloc(rr->size + 1);
+	/* Again, we lose taint-tracking here */
+	s = memcpy(buf, s, rr->size + 1);
 	break;
 	}
       }
@@ -192,10 +198,13 @@ empty ANSWER section. */
 if (!(srr.num_rr = found))
   srr.herrno = NO_DATA;
 
-/* spfrr->rr must have been malloc()d for this */
-SPF_dns_rr_dup(&spfrr, &srr);
-store_free_dns_answer(dnsa);
-return spfrr;
+out:
+  /* spfrr->rr must have been malloc()d for this */
+  SPF_dns_rr_dup(&spfrr, &srr);
+
+  DEBUG(receive) expand_level--;
+  store_free_dns_answer(dnsa);
+  return spfrr;
 }
 
 
@@ -205,7 +214,7 @@ SPF_dns_exim_new(int debug)
 {
 SPF_dns_server_t * spf_dns_server = store_malloc(sizeof(SPF_dns_server_t));
 
-/* DEBUG(D_receive) debug_printf("SPF_dns_exim_new\n"); */
+/* DEBUG(receive) debug_printf_indent("SPF_dns_exim_new\n"); */
 
 memset(spf_dns_server, 0, sizeof(SPF_dns_server_t));
 spf_dns_server->destroy      = NULL;
@@ -244,7 +253,7 @@ SPF_dns_server_t * dc;
 int debug = 0;
 const uschar *s;
 
-DEBUG(D_receive) debug = 1;
+DEBUG(receive) debug = 1;
 
 /* We insert our own DNS access layer rather than letting the spf library
 do it, so that our dns access path is used for debug tracing and for the
@@ -252,17 +261,17 @@ testsuite. */
 
 if (!(dc = SPF_dns_exim_new(debug)))
   {
-  DEBUG(D_receive) debug_printf("spf: SPF_dns_exim_new() failed\n");
+  DEBUG(receive) debug_printf_indent("SPF_dns_exim_new() failed\n");
   return FALSE;
   }
 if (!(dc = SPF_dns_cache_new(dc, NULL, debug, 8)))
   {
-  DEBUG(D_receive) debug_printf("spf: SPF_dns_cache_new() failed\n");
+  DEBUG(receive) debug_printf_indent("SPF_dns_cache_new() failed\n");
   return FALSE;
   }
 if (!(spf_server = SPF_server_new_dns(dc, debug)))
   {
-  DEBUG(D_receive) debug_printf("spf: SPF_server_new() failed.\n");
+  DEBUG(receive) debug_printf_indent("SPF_server_new() failed.\n");
   return FALSE;
   }
 
@@ -273,11 +282,11 @@ but is broken now (May 18th, 2020) */
 
 GET_OPTION("spf_smtp_comment_template");
 if (!(s = expand_string(spf_smtp_comment_template)))
-  log_write_die(0, LOG_MAIN, "expansion of spf_smtp_comment_template failed");
+  log_write_die(LOG_MAIN, "expansion of spf_smtp_comment_template failed");
 
 SPF_server_set_explanation(spf_server, CCS s, &spf_response);
 if (SPF_response_errcode(spf_response) != SPF_E_SUCCESS)
-  log_write_die(0, LOG_MAIN, "%s", SPF_strerror(SPF_response_errcode(spf_response)));
+  log_write_die(LOG_MAIN, "%s", SPF_strerror(SPF_response_errcode(spf_response)));
 
 return TRUE;
 }
@@ -294,8 +303,8 @@ static int
 spf_conn_init(const uschar * spf_helo_domain, const uschar * spf_remote_addr,
   const uschar ** errstr)
 {
-DEBUG(D_receive)
-  debug_printf("spf_conn_init: %s %s\n", spf_helo_domain, spf_remote_addr);
+DEBUG(receive) debug_printf_indent("spf_conn_init: %s %s\n",
+				      spf_helo_domain, spf_remote_addr);
 
 if (!spf_server && !spf_init(NULL))
   {
@@ -305,8 +314,8 @@ if (!spf_server && !spf_init(NULL))
 
 if (SPF_server_set_rec_dom(spf_server, CS primary_hostname))
   {
-  DEBUG(D_receive) debug_printf("spf: SPF_server_set_rec_dom(%q) failed.\n",
-    primary_hostname);
+  DEBUG(receive) debug_printf_indent("SPF_server_set_rec_dom(%q) failed.\n",
+					primary_hostname);
   spf_server = NULL;
   *errstr = US"spf: setting host name";
   return FAIL;
@@ -318,8 +327,8 @@ if (  SPF_request_set_ipv4_str(spf_request, CCS spf_remote_addr)
    && SPF_request_set_ipv6_str(spf_request, CCS spf_remote_addr)
    )
   {
-  DEBUG(D_receive)
-    debug_printf("spf: SPF_request_set_ipv4_str() and "
+  DEBUG(receive)
+    debug_printf_indent("SPF_request_set_ipv4_str() and "
       "SPF_request_set_ipv6_str() failed [%s]\n", spf_remote_addr);
   spf_server = NULL;
   spf_request = NULL;
@@ -329,8 +338,8 @@ if (  SPF_request_set_ipv4_str(spf_request, CCS spf_remote_addr)
 
 if (SPF_request_set_helo_dom(spf_request, CCS spf_helo_domain))
   {
-  DEBUG(D_receive) debug_printf("spf: SPF_set_helo_dom(%q) failed.\n",
-    spf_helo_domain);
+  DEBUG(receive) debug_printf_indent("SPF_set_helo_dom(%q) failed.\n",
+				      spf_helo_domain);
   spf_server = NULL;
   spf_request = NULL;
   *errstr = US"spf: setting helo string";
@@ -352,14 +361,14 @@ static void
 spf_response_debug(SPF_response_t * spf_response)
 {
 if (SPF_response_messages(spf_response) == 0)
-  debug_printf(" (no errors)\n");
+  debug_printf_indent(" (no errors)\n");
 else for (int i = 0; i < SPF_response_messages(spf_response); i++)
   {
   SPF_error_t * err = SPF_response_message(spf_response, i);
-  debug_printf( "%s_msg = (%d) %s\n",
-		  (SPF_error_errorp(err) ? "warn" : "err"),
-		  SPF_error_code(err),
-		  SPF_error_message(err));
+  debug_printf_indent("%s_msg = (%d) %s\n",
+		      SPF_error_errorp(err) ? "warn" : "err",
+		      SPF_error_code(err),
+		      SPF_error_message(err));
   }
 }
 
@@ -374,12 +383,9 @@ static int
 spf_process(const uschar ** listptr, const uschar * spf_envelope_sender,
   int action)
 {
-int sep = 0;
-const uschar *list = *listptr;
-uschar *spf_result_id;
-int rc = SPF_RESULT_PERMERROR;
+int rc = SPF_RESULT_PERMERROR, ret;
 
-DEBUG(D_receive) debug_printf("spf_process\n");
+DEBUG(receive) { debug_printf_indent("SPF: process\n"); expand_level++; }
 
 if (!(spf_server && spf_request))
   /* no global context, assume temp error and skip to evaluation */
@@ -405,31 +411,31 @@ else
   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);
+  spf_used_domain	 = sender_address && *sender_address
+			  ? expand_string(US"$sender_address_domain")
+			  : sender_helo_name;
 
   rc = SPF_response_result(spf_response);
 
-  DEBUG(D_acl) spf_response_debug(spf_response);
+  DEBUG(acl) spf_response_debug(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);
+DEBUG(acl)
+  debug_printf_indent("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);
+  ret = spf_process(listptr, spf_envelope_sender, SPF_PROCESS_FALLBACK);
 
-while ((spf_result_id = string_nextinlist(&list, &sep, NULL, 0)))
+else
   {
-  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;
+  const uschar * list = *listptr;
+  ret = match_isinlist(spf_result_id_list[rc].name, &list,
+                    0, NULL, NULL, MCL_STRING, TRUE, NULL);
   }
 
-/* no match */
-return FAIL;
+DEBUG(receive) expand_level--;
+return ret;
 }
 
 
@@ -441,7 +447,7 @@ uschar * s;
 if (spf_result)
   {
   int start = 0;		/* Compiler quietening */
-  DEBUG(D_acl) start = gstring_length(g);
+  DEBUG(acl) start = gstring_length(g);
 
   g = string_append(g, 2, US";\n\tspf=", spf_result);
   if (spf_result_guessed)
@@ -457,11 +463,11 @@ if (spf_result)
       ? string_append(g, 2, US" smtp.helo=", s)
       : string_cat(g, US" smtp.mailfrom=<>");
     }
-  DEBUG(D_acl) debug_printf("SPF:\tauthres '%.*s'\n",
+  DEBUG(acl) debug_printf_indent("SPF:\tauthres '%.*s'\n",
 		  gstring_length(g) - start - 3, g->s + start + 3);
   }
 else
-  DEBUG(D_acl) debug_printf("SPF:\tno authres\n");
+  DEBUG(acl) debug_printf_indent("SPF:\tno authres\n");
 return g;
 }
 
@@ -478,7 +484,7 @@ if (spf_response)
   s = US spf_response->header_comment;
   }
 *human_readable_p = s ? string_copy(s) : US"";
-DEBUG(D_acl) debug_printf("SPF: %d '%s'\n", res, s);
+DEBUG(acl) debug_printf_indent(" SPF: %d '%s'\n", res, s);
 return res;
 }
 
@@ -492,7 +498,7 @@ SPF_dns_server_t * dc;
 SPF_server_t * spf_server = NULL;
 int debug = 0;
 
-DEBUG(D_lookup) debug = 1;
+DEBUG(lookup) debug = 1;
 
 if ((dc = SPF_dns_exim_new(debug)))
   if ((dc = SPF_dns_cache_new(dc, NULL, debug, 8)))
@@ -562,7 +568,7 @@ if (SPF_request_set_env_from(spf_request, CS keystring))
 SPF_request_query_mailfrom(spf_request, &spf_response);
 *result = string_copy(US SPF_strresult(SPF_response_result(spf_response)));
 
-DEBUG(D_lookup) spf_response_debug(spf_response);
+DEBUG(lookup) spf_response_debug(spf_response);
 
 SPF_response_free(spf_response);
 SPF_request_free(spf_request);
@@ -594,6 +600,7 @@ static var_entry spf_variables[] = {
   { "spf_result",		vtype_stringptr,	&spf_result },
   { "spf_result_guessed",	vtype_bool,		&spf_result_guessed },
   { "spf_smtp_comment",		vtype_stringptr,	&spf_smtp_comment },
+  { "spf_used_domain",		vtype_stringptr,	&spf_used_domain },
 };
 
 misc_module_info spf_module_info =
diff --git a/src/src/miscmods/spf_perl.c b/src/src/miscmods/spf_perl.c
index d491ae64e..081963eb6 100644
--- a/src/src/miscmods/spf_perl.c
+++ b/src/src/miscmods/spf_perl.c
@@ -35,9 +35,6 @@ static spf_result_id spf_result_id_list[] = {
   { US"permerror",	7 }  /* RFC 4408 defined */
 };
 
-const uschar * conn_helo = NULL;
-const uschar * conn_addr = NULL;
-
 uschar * spf_guess              = US"v=spf1 a/24 mx/24 ptr ?all";
 uschar * spf_header_comment     = NULL;
 uschar * spf_received           = NULL;
@@ -46,7 +43,8 @@ uschar * spf_smtp_comment       = NULL;
 uschar * spf_smtp_comment_template
                     /* Used to be: "Please%_see%_http://www.open-spf.org/Why?id=%{S}&ip=%{C}&receiver=%{R}" */
 				= US"Please%_see%_http://www.open-spf.org/Why";
-BOOL    spf_result_guessed     = FALSE;
+BOOL    spf_result_guessed	= FALSE;
+const uschar * spf_used_domain	= NULL;
 
 
 static const misc_module_info * spf_perl_mi = NULL;
@@ -61,9 +59,10 @@ static const uschar spf_pl[] =
   "use Mail::SPF;"
   "sub my_spf_req {"
     "my ($mfrom, $conn_addr, $conn_helo) = @_;"
+    "my ($id, $sc) = ($mfrom ne '') ? ($mfrom, 'mfrom') : ($conn_helo, 'helo');"
     "my $request = Mail::SPF::Request->new("
-      "scope       => 'mfrom',"
-      "identity    => $mfrom,"
+      "scope       => $sc,"
+      "identity    => $id,"
       "ip_address  => $conn_addr,"
       "helo_identity => $conn_helo"
       ");"
@@ -80,21 +79,19 @@ spf lookup routine to it.  Safely does nothing if called again.
 */
 
 static const misc_module_info *
-setup_spf_perl_mi(void)
+setup_spf_perl_mi(uschar ** errstr)
 {
 typedef uschar * (*fn_t)(const uschar *);
 
 if (!spf_perl_mi)
   {
   if (!(spf_perl_mi = perl_startup(opt_perl_startup ? opt_perl_startup : US"")))
-    /* errstr = string_sprintf("spf: %s", expand_string_message); */
+    {
+    *errstr = string_sprintf("spf: %s", expand_string_message);
     return NULL;
+    }
 
-  /*XXX could return an error string here:
-  if ((errstr = (((fn_t *) spf_perl_mi->functions)[PERL_ADDBLOCK]) (spf_pl)))
-  */
-
-  if ((((fn_t *) spf_perl_mi->functions)[PERL_ADDBLOCK]) (spf_pl))
+  if ((*errstr = (((fn_t *) spf_perl_mi->functions)[PERL_ADDBLOCK]) (spf_pl)))
     return spf_perl_mi = NULL;
   }
 return spf_perl_mi;
@@ -109,11 +106,11 @@ uschar * errstr;
 gstring * g;
 typedef gstring * (*fn_t)(gstring *, uschar **, uschar *, const uschar **);
 
-DEBUG(D_acl) debug_printf_indent("calling Mail::SPF\n");
+DEBUG(acl) debug_printf_indent("calling Mail::SPF\n");
 expand_level++;
 if (!(g = (((fn_t *) spf_perl_mi->functions)[PERL_CAT])
 			  (NULL, &errstr, US"my_spf_req", argv)))
-  DEBUG(D_acl) debug_printf_indent("SPF err %q\n", errstr);
+  DEBUG(acl) debug_printf_indent("SPF err %q\n", errstr);
 expand_level--;
 return g;
 }
@@ -121,43 +118,13 @@ return g;
 /******************************************************************************/
 
 
-#ifdef notdef
 /*API*/
 static gstring *
 spf_lib_version_report(gstring * g)
 {
 /*XXX Does Mail::SPF have a version? MetaCPAN says yes, but does not
 document a method that returns it. */
-return g;
-}
-#endif
-
-
-
-/*API*/
-/* Set 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).
-
-We delay doing perl startup until spf processing time, as ACL might
-never need us on any given connection.
-
-Return: OK/FAIL
-*/
-
-static int
-spf_conn_init(const uschar * spf_helo_domain, const uschar * spf_remote_addr,
-  const uschar ** errstr)
-{
-DEBUG(D_receive) debug_printf_indent("spf_conn_init: helo:%s addr:%s\n",
-			      spf_helo_domain, spf_remote_addr);
-
-/* Copy the args to globals */
-
-conn_helo = spf_helo_domain;
-conn_addr = spf_remote_addr;
-
-return OK;
+return string_cat(g, US"Library_version: SPF: perl Mail::SPF\n");
 }
 
 
@@ -188,33 +155,38 @@ static int
 spf_process(const uschar ** listptr, const uschar * spf_envelope_sender,
   int action)
 {
-int res = FAIL, sep;
+int res, sep;
+uschar * errstr;
 const uschar * arglist = *listptr;
 
 expand_level++;
-DEBUG(D_acl)
+DEBUG(acl)
   debug_printf_indent("%s: mfrom:<%s>\n", __FUNCTION__, spf_envelope_sender);
 
-if (!setup_spf_perl_mi())
+if (!setup_spf_perl_mi(&errstr))
+  {
+  expand_level--;
   return FAIL;
+  }
 
-if (!(conn_helo && conn_addr))
+if (!(sender_helo_name && sender_host_address))
   spf_result = US"permerror";
 
 else
   {
-  const uschar * argv[4] = {spf_envelope_sender, conn_addr, conn_helo, NULL};
+  const uschar * argv[4] = {spf_envelope_sender, sender_host_address,
+			    sender_helo_name, NULL};
   gstring * g;
   uschar * res_list, * s;
 
   if (!(g = call_my_spf_req(argv)))
-    goto out;
+    { res = FAIL; goto out; }
 
   res_list = US string_from_gstring(g);
   sep = '\n';
 
   spf_result = string_nextinlist(CUSS &res_list, &sep, NULL, 0);
-  DEBUG(D_acl) debug_printf_indent("SPF result is %s\n", spf_received);
+  DEBUG(acl) debug_printf_indent("MAIL::SPF result is %q\n", spf_received);
 
   spf_received = res_list;		/* remainder of the returned string */
 
@@ -225,20 +197,14 @@ else
     uschar * t = Ustrchr(s, ')');	/* grab a (comment) if there is one */
     if (t) spf_header_comment = string_copyn(s+1, t-s-1);
     }
-  }
 
-sep = 0;
-for (uschar * ele; ele = string_nextinlist(&arglist, &sep, NULL, 0); )
-  {
-  BOOL negate, result;
-
-  if ((negate = *ele == '!'))
-    ele++;
-
-  result = Ustrcmp(ele, spf_result) == 0;
-  if (negate != result) { res = OK; break; }
+  spf_used_domain	 = sender_address && *sender_address
+			  ? expand_string(US"$sender_address_domain")
+			  : sender_helo_name;
   }
-/* if the loop ran out of list, no match */
+
+res = match_isinlist(spf_result, &arglist,
+		    0, NULL, NULL, MCL_STRING, TRUE, NULL);
 
 out:
   expand_level--;
@@ -255,7 +221,7 @@ uschar * s;
 if (spf_result)
   {
   int start = 0;		/* Compiler quietening */
-  DEBUG(D_acl) start = gstring_length(g);
+  DEBUG(acl) start = gstring_length(g);
 
   g = string_append(g, 2, US";\n\tspf=", spf_result);
   if (spf_result_guessed)
@@ -271,11 +237,11 @@ if (spf_result)
       ? string_append(g, 2, US" smtp.helo=", s)
       : string_cat(g, US" smtp.mailfrom=<>");
     }
-  DEBUG(D_acl) debug_printf_indent("SPF:\tauthres '%.*s'\n",
+  DEBUG(acl) debug_printf_indent("SPF:\tauthres '%.*s'\n",
 		  gstring_length(g) - start - 3, g->s + start + 3);
   }
 else
-  DEBUG(D_acl) debug_printf_indent("SPF:\tno authres\n");
+  DEBUG(acl) debug_printf_indent("SPF:\tno authres\n");
 return g;
 }
 
@@ -300,7 +266,7 @@ if (spf_result)
   }
 
 *human_readable_p = s ? string_copy(s) : US"";
-DEBUG(D_acl) debug_printf_indent("SPF: %d '%s'\n", res, s);
+DEBUG(acl) debug_printf_indent(" SPF: %d '%s'\n", res, s);
 return res;
 }
 
@@ -329,18 +295,19 @@ spf_lookup_find(void * handle, const uschar * filename,
   const uschar * keystring, int key_len, uschar ** result, uschar ** errmsg,
   uint * do_cache, const uschar * opts)
 {
+uschar * errstr;
 int res = FAIL;
 
-expand_level++;
-DEBUG(D_acl) debug_printf_indent("%s: mfrom:<%s> ip %q\n", __FUNCTION__,
+DEBUG(acl) debug_printf_indent("%s: mfrom:<%s> ip %q\n", __FUNCTION__,
 				  keystring, filename);
+expand_level++;
 
-if (setup_spf_perl_mi())
+if (setup_spf_perl_mi(&errstr))
   if (!filename)
     *result = US"permerror";
   else
     {
-    const uschar * argv[4] = {keystring, filename, conn_helo, NULL};
+    const uschar * argv[4] = {keystring, filename, sender_helo_name, NULL};
     gstring * g;
 
     if ((g = call_my_spf_req(argv)))
@@ -348,11 +315,12 @@ if (setup_spf_perl_mi())
       uschar * res_list = US string_from_gstring(g);
       int sep = '\n';
       *result = string_nextinlist(CUSS &res_list, &sep, NULL, 0);
-      DEBUG(D_acl) debug_printf_indent("SPF result is %s\n", *result);
+      DEBUG(acl) debug_printf_indent("MAIL::SPF result is %q\n", *result);
       res = OK;
       }
     }
 
+expand_level--;
 return res;
 }
 
@@ -381,6 +349,7 @@ static var_entry spf_variables[] = {
   { "spf_result",		vtype_stringptr,	&spf_result },
   { "spf_result_guessed",	vtype_bool,		&spf_result_guessed },
   { "spf_smtp_comment",		vtype_stringptr,	&spf_smtp_comment },
+  { "spf_used_domain",		vtype_stringptr,	&spf_used_domain },
 };
 
 misc_module_info spf_module_info =
@@ -389,8 +358,8 @@ misc_module_info spf_module_info =
 # ifdef DYNLOOKUP
   .dyn_magic =		MISC_MODULE_MAGIC,
 # endif
-  /* .lib_vers_report =	spf_lib_version_report, */
-  .conn_init =		spf_conn_init,
+  .lib_vers_report =	spf_lib_version_report,
+  .conn_init =		NULL,
   .smtp_reset =		spf_smtp_reset,
   .authres =		authres_spf,
 
diff --git a/src/src/xclient.c b/src/src/miscmods/xclient.c
similarity index 69%
rename from src/src/xclient.c
rename to src/src/miscmods/xclient.c
index 817469308..842c3051d 100644
--- a/src/src/xclient.c
+++ b/src/src/miscmods/xclient.c
@@ -6,7 +6,7 @@
 /* See the file NOTICE for conditions of use and distribution. */
 /* SPDX-License-Identifier: GPL-2.0-or-later */
 
-#include "exim.h"
+#include "../exim.h"
 
 #ifdef EXPERIMENTAL_XCLIENT
 
@@ -88,16 +88,18 @@ Return: NULL on success, or error message
 # define XCLIENT_UNAVAIL     US"[UNAVAILABLE]"
 # define XCLIENT_TEMPUNAVAIL US"[TEMPUNAVAIL]"
 
-uschar *
-xclient_smtp_command(uschar * s, int * resp, BOOL * flagp)
+static uschar *
+xclient_smtp_command(const uschar * s, int * resp, BOOL * flagp)
 {
-uschar * word = s;
+const uschar * word = s;
+uschar * errmsg = NULL;
 enum {
   XCLIENT_READ_COMMAND = 0,
   XCLIENT_READ_VALUE,
   XCLIENT_SKIP_SPACES
 } state = XCLIENT_SKIP_SPACES;
 enum xclient_cmd_e cmd;
+static BOOL xc_proxy_session = FALSE;
 
 if (  !*flagp
    && verify_check_host(&hosts_require_helo) == OK)
@@ -107,13 +109,11 @@ if (  !*flagp
   return US"no HELO/EHLO given";
   }
 
-/* If already in a proxy session, do not re-check permission.
-Strictly we should avoid doing this for a Proxy-Protocol
-session to avoid mixups. */
+/* If already in a proxy session, do not re-check permission. */
 
-if(!proxy_session && verify_check_host(&hosts_xclient) == FAIL)
+if (!xc_proxy_session && verify_check_host(&hosts_xclient) == FAIL)
   {
-  *resp = 550;
+  *resp = 501;
   *flagp= TRUE;
   return US"XCLIENT command used when not advertised";
   }
@@ -127,7 +127,7 @@ if (sender_address)
 
 if (!*word)
   {
-  s = US"XCLIENT must have at least one operand";
+  errmsg = US"XCLIENT must have at least one operand";
   goto fatal_501;
   }
 
@@ -143,12 +143,12 @@ for (state = XCLIENT_SKIP_SPACES; *s; )
       len = s - word;
       if (!*s)
 	{
-	s = string_sprintf("XCLIENT: missing value for parameter '%.*s'",
+	errmsg = string_sprintf("XCLIENT: missing value for parameter '%.*s'",
 			  len, word);
 	goto fatal_501;
 	}
 
-      DEBUG(D_transport) debug_printf(" XCLIENT: cmd %.*s\n", len, word);
+      DEBUG(transport) debug_printf(" XCLIENT: cmd %.*s\n", len, word);
       cmd = XCLIENT_CMD_UNKNOWN;
       for (struct xclient_cmd * x = xclient_cmds + 1;
 	   x < xclient_cmds + nelem(xclient_cmds); x++)
@@ -159,7 +159,7 @@ for (state = XCLIENT_SKIP_SPACES; *s; )
 	  }
       if (cmd == XCLIENT_CMD_UNKNOWN)
 	{
-	s = string_sprintf("XCLIENT: unrecognised parameter '%.*s'",
+	errmsg = string_sprintf("XCLIENT: unrecognised parameter '%.*s'",
 			  len, word);
 	goto fatal_501;
 	}
@@ -177,9 +177,9 @@ for (state = XCLIENT_SKIP_SPACES; *s; )
       Uskip_nonwhite(&s);
       len = s - word;
 
-      DEBUG(D_transport) debug_printf(" XCLIENT: \tvalue %.*s\n", len, word);
+      DEBUG(transport) debug_printf(" XCLIENT: \tvalue %.*s\n", len, word);
       if (len == 0)
-	{ s = US"XCLIENT: zero-length value for param"; goto fatal_501; }
+	{ errmsg = US"XCLIENT: zero-length value for param"; goto fatal_501; }
 
       if (  len == 13
 	 && (  strncmpic(word, XCLIENT_UNAVAIL, 13) == 0
@@ -189,7 +189,7 @@ for (state = XCLIENT_SKIP_SPACES; *s; )
 
       else if ((len = xclient_xtextdecode(word, s, &val)) == -1)
 	{
-	s = string_sprintf("failed xtext decode for XCLIENT: '%.*s'", len, word);
+	errmsg = string_sprintf("failed xtext decode for XCLIENT: '%.*s'", len, word);
 	goto fatal_501;
 	}
 
@@ -222,10 +222,7 @@ for (state = XCLIENT_SKIP_SPACES; *s; )
 	    authentication_failed = FALSE;
 	    }
 	  else
-	    {
-	    authenticated_id = NULL;
-	    sender_host_authenticated = NULL;
-	    }
+	    authenticated_id = sender_host_authenticated = NULL;
 	  break;
 
 # ifdef XCLIENT_V1
@@ -234,13 +231,13 @@ for (state = XCLIENT_SKIP_SPACES; *s; )
 	  break;
 	case XCLIENT_CMD_PROTO:
 	  if (!val)
-	    { store_pool = old_pool; s = US"missing proto for XCLIENT"; goto fatal_501; }
+	    { store_pool = old_pool; errmsg = US"missing proto for XCLIENT"; goto fatal_501; }
 	  else if (len == 4 && strncmpic(val, US"SMTP", 4) == 0)
 	    *esmtpflag = FALSE;	/* function arg */
 	  else if (len == 5 && strncmpic(val, US"ESMTP", 5) == 0)
 	    *esmtpflag = TRUE;
 	  else
-	    { store_pool = old_pool; s = US"bad proto for XCLIENT"; goto fatal_501; }
+	    { store_pool = old_pool; errmsg = US"bad proto for XCLIENT"; goto fatal_501; }
 	  break;
 # endif
 	default: break;	/* stupid compiler silencing */
@@ -256,33 +253,35 @@ for (state = XCLIENT_SKIP_SPACES; *s; )
       break;
 
     default:
-      s = US"unhandled XCLIENT parameter type";
+      errmsg = US"unhandled XCLIENT parameter type";
       goto fatal_501;
     }
 
 if (!proxy_local_address)
-  { s = US"missing ADDR for XCLIENT"; goto fatal_501; }
+  { errmsg = US"missing ADDR for XCLIENT"; goto fatal_501; }
 if (!proxy_local_port)
-  { s = US"missing PORT for XCLIENT"; goto fatal_501; }
+  { errmsg = US"missing PORT for XCLIENT"; goto fatal_501; }
 if (state != XCLIENT_SKIP_SPACES)
-  { s = US"bad state parsing XCLIENT parameters"; goto fatal_501; }
+  { errmsg = US"bad state parsing XCLIENT parameters"; goto fatal_501; }
 
 host_build_sender_fullhost();
-proxy_session = TRUE;
+proxy_session = TRUE;		/* global */
+xc_proxy_session = TRUE;	/* local */
 *resp = 220;
 return NULL;
 
 fatal_501:
   *flagp= TRUE;
   *resp = 501;
-  return s;
+  return errmsg;
 }
 
 # undef XCLIENT_UNAVAIL
 # undef XCLIENT_TEMPUNAVAIL
 
 
-gstring *
+/*API*/
+static gstring *
 xclient_smtp_advertise_str(gstring * g)
 {
 g = string_catn(g, US"-XCLIENT ", 8);
@@ -295,8 +294,60 @@ return string_catn(g, US"\r\n", 2);
 }
 
 
+/*API
+Return: true if the cmd was good and the protocol started
+*/
+
+static int
+xclient_protocol_start(const uschar * smtp_cmd_data, int * donep,
+  BOOL helo_seen)
+{
+BOOL fatal = helo_seen;
+uschar * errmsg;
+int resp;
+
+if (!(errmsg = xclient_smtp_command(smtp_cmd_data, &resp, &fatal)))
+  {
+  /*XXX unclear in spec. if this needs to be an ESMTP banner,
+  nor whether we get the original client's HELO after (or a proxy fake).
+  We require that we do; the following HELO/EHLO handling will set
+  sender_helo_name as normal. */
+
+  smtp_printf("%d XCLIENT success\r\n", SP_NO_MORE, resp);
+  return TRUE;
+  }
+else if (fatal)
+  *donep = synprot_error(FALSE, resp, NULL, errmsg);
+else
+  {
+  smtp_printf("%d %s\r\n", SP_NO_MORE, resp, errmsg);
+  log_write(LOG_MAIN|LOG_REJECT, "rejected XCLIENT from %s: %s",
+    host_and_ident(FALSE), errmsg);
+  }
+  
+return FALSE;
+}
+
+/******************************************************************************/
+/* Module API */
+
+static void * xclient_functions[] = {
+  [XCLIENT_PROTO_ADVERTISE] =	(void *) xclient_smtp_advertise_str,
+  [XCLIENT_PROTO_START] =	(void *) xclient_protocol_start,
+};
+
+misc_module_info xclient_module_info =
+{
+  .name =		US"xclient",
+# ifdef DYNLOOKUP
+  .dyn_magic =		MISC_MODULE_MAGIC,
+# endif
+  .functions =		xclient_functions,
+  .functions_count =	nelem(xclient_functions),
+};
 #endif	/*EXPERIMENTAL_XCLIENT*/
 
 /* vi: aw ai sw=2
 */
 /* End of xclient.c */
+
diff --git a/src/src/miscmods/xclient_api.h b/src/src/miscmods/xclient_api.h
new file mode 100644
index 000000000..e59296395
--- /dev/null
+++ b/src/src/miscmods/xclient_api.h
@@ -0,0 +1,15 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) The Exim Maintainers 2025 */
+/* See the file NOTICE for conditions of use and distribution. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/* API definitions for the proxy-protocol module */
+
+
+/* Function table entry numbers */
+
+#define	XCLIENT_PROTO_ADVERTISE	0
+#define	XCLIENT_PROTO_START	1
diff --git a/src/src/moan.c b/src/src/moan.c
index efac33681..6827bbb23 100644
--- a/src/src/moan.c
+++ b/src/src/moan.c
@@ -34,7 +34,7 @@ uschar * s;
 GET_OPTION("dsn_from");
 if (!(s = expand_string(dsn_from)))
   {
-  log_write(0, LOG_MAIN|LOG_PANIC,
+  log_write(LOG_MAIN|LOG_PANIC,
     "Failed to expand dsn_from (using default): %s", expand_string_message);
   s = expand_string(US DEFAULT_DSN_FROM);
   }
@@ -159,14 +159,15 @@ Returns:         TRUE if message successfully sent
 */
 
 BOOL
-moan_send_message(const uschar * recipient, int ident, error_block * eblock,
-  header_line * headers, FILE * message_file, const uschar * firstline)
+moan_send_message(const uschar * recipient, int ident,
+  const error_block * eblock, const header_line * headers,
+  FILE * message_file, const uschar * firstline)
 {
 int written = 0, fd, status, count = 0, size_limit = bounce_return_size_limit;
 FILE * fp;
-int pid;
+pid_t pid;
 
-#ifdef SUPPORT_DMARC
+#ifdef EXIM_HAVE_DMARC
 uschar * s, * s2;
 
 /* For DMARC if there is a specific sender set, expand the variable for the
@@ -193,11 +194,12 @@ pid = child_open_exim(&fd, US"moan_send_message");
 
 if (pid < 0)
   {
-  DEBUG(D_any) debug_printf("Failed to create child to send message: %s\n",
+  DEBUG(any) debug_printf("Failed to create child to send message: %s\n",
     strerror(errno));
   return FALSE;
   }
-else DEBUG(D_any) debug_printf("Child process %d for sending message\n", pid);
+else DEBUG(any)
+  debug_printf("Child process " PID_T_FMT " for sending message\n", pid);
 
 /* Creation of child succeeded */
 
@@ -205,7 +207,7 @@ 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 SUPPORT_DMARC
+#ifdef EXIM_HAVE_DMARC
 if (s)
   fprintf(fp, "From: %s\n", s);
 else
@@ -323,23 +325,16 @@ switch(ident)
   fprintf(fp, "\n");
   break;
 
-#ifdef SUPPORT_DMARC
+#ifdef EXIM_HAVE_DMARC
   case ERRMESS_DMARC_FORENSIC:
     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);
-    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;
+    for (; eblock; eblock = eblock->next)
+      if (eblock->text2)
+	fprintf(fp, "  %s: %s\n", eblock->text1, eblock->text2);
+      else
+	fprintf(fp, "%s", eblock->text1);
+    break;
 #endif
 
   default:
@@ -389,7 +384,7 @@ if (bounce_return_message)
 
   while (headers)
     {
-    if (headers->text != NULL) fprintf(fp, "%s", CS headers->text);
+    if (headers->text) fprintf(fp, "%s", CS headers->text);
     headers = headers->next;
     }
 
@@ -434,13 +429,13 @@ if (bounce_return_message)
       fputs(CS buf, fp);
       }
     }
-#ifdef SUPPORT_DMARC
+#ifdef EXIM_HAVE_DMARC
   /* Overkill, but use exact test in case future code gets inserted */
-  else if (bounce_return_body && message_file == NULL)
+  else if (bounce_return_body && !message_file)
     {
     /*XXX limit line length here? */
     /* This doesn't print newlines, disable until can parse and fix
-     * output to be legible.  */
+    output to be legible.  */
     fprintf(fp, "%s", expand_string(US"$message_body"));
     }
 #endif
@@ -454,10 +449,10 @@ if (status != 0)
   {
   uschar *msg = US"Child mail process returned status";
   if (status == -257)
-    log_write(0, LOG_MAIN, "%s %d: errno=%d: %s", msg, status, errno,
+    log_write(LOG_MAIN, "%s %d: errno=%d: %s", msg, status, errno,
       strerror(errno));
   else
-    log_write(0, LOG_MAIN, "%s %d", msg, status);
+    log_write(LOG_MAIN, "%s %d", msg, status);
   return FALSE;
   }
 
@@ -494,8 +489,8 @@ Returns:        FALSE if there is no sender_address to send to;
 */
 
 BOOL
-moan_to_sender(int ident, error_block *eblock, header_line *headers,
-  FILE *message_file, BOOL check_sender)
+moan_to_sender(int ident, const error_block * eblock,
+  const header_line * headers, FILE * message_file, BOOL check_sender)
 {
 uschar *firstline = NULL;
 uschar *msg = US"Error while reading message with no usable sender address";
@@ -531,13 +526,13 @@ switch(ident)
   case ERRMESS_BADARGADDRESS:
   case ERRMESS_BADNOADDRESS:
   case ERRMESS_BADADDRESS:
-  log_write(0, LOG_MAIN, "%s: at least one malformed recipient address: "
+  log_write(LOG_MAIN, "%s: at least one malformed recipient address: "
     "%s - %s", msg, eblock->text1, eblock->text2);
   break;
 
   case ERRMESS_IGADDRESS:
   case ERRMESS_NOADDRESS:
-  log_write(0, LOG_MAIN, "%s: no recipient addresses", msg);
+  log_write(LOG_MAIN, "%s: no recipient addresses", msg);
   break;
 
   /* This error has already been logged. */
@@ -545,35 +540,35 @@ switch(ident)
   break;
 
   case ERRMESS_VLONGHEADER:
-  log_write(0, LOG_MAIN, "%s: excessively long message header section read "
+  log_write(LOG_MAIN, "%s: excessively long message header section read "
     "(more than %d characters)", msg, header_maxsize);
   break;
 
   case ERRMESS_VLONGHDRLINE:
-  log_write(0, LOG_MAIN, "%s: excessively long message header line read "
+  log_write(LOG_MAIN, "%s: excessively long message header line read "
     "(more than %d characters)", msg, header_line_maxsize);
   break;
 
   case ERRMESS_TOOBIG:
-  log_write(0, LOG_MAIN, "%s: message too big (limit set to %d)", msg,
+  log_write(LOG_MAIN, "%s: message too big (limit set to %d)", msg,
     thismessage_size_limit);
   break;
 
   case ERRMESS_TOOMANYRECIP:
-  log_write(0, LOG_MAIN, "%s: too many recipients (max set to %d)", msg,
+  log_write(LOG_MAIN, "%s: too many recipients (max set to %d)", msg,
     recipients_max_expanded);
   break;
 
   case ERRMESS_LOCAL_SCAN:
-  log_write(0, LOG_MAIN, "%s: rejected by local_scan: %s", msg, eblock->text1);
+  log_write(LOG_MAIN, "%s: rejected by local_scan: %s", msg, eblock->text1);
   break;
 
   case ERRMESS_LOCAL_ACL:
-  log_write(0, LOG_MAIN, "%s: rejected by non-SMTP ACL: %s", msg, eblock->text1);
+  log_write(LOG_MAIN, "%s: rejected by non-SMTP ACL: %s", msg, eblock->text1);
   break;
 
   default:
-  log_write(0, LOG_MAIN|LOG_PANIC, "%s: unknown error number %d", msg,
+  log_write(LOG_MAIN|LOG_PANIC, "%s: unknown error number %d", msg,
     ident);
   break;
   }
@@ -601,16 +596,17 @@ Returns:        nothing
 */
 
 void
-moan_tell_someone(const uschar * who, address_item * addr,
+moan_tell_someone(const uschar * who, const address_item * addr,
   const uschar * subject, const char * format, ...)
 {
 FILE * f;
 va_list ap;
-int fd, pid = child_open_exim(&fd, US"moan_tell_someone");
+int fd;
+pid_t pid = child_open_exim(&fd, US"moan_tell_someone");
 
 if (pid < 0)
   {
-  DEBUG(D_any) debug_printf("Failed to create child to send message: %s\n",
+  DEBUG(any) debug_printf("Failed to create child to send message: %s\n",
     strerror(errno));
   return;
   }
@@ -672,9 +668,9 @@ void
 moan_smtp_batch(const uschar * cmd_buffer, const char * format, ...)
 {
 va_list ap;
-int yield = (receive_messagecount > 0)? 1 : 2;
+int yield = receive_messagecount > 0 ? EXIT_FAILURE : 2;
 
-DEBUG(D_any) debug_printf("Handling error in batched SMTP input\n");
+DEBUG(any) debug_printf("Handling error in batched SMTP input\n");
 
 /* On stdout, write stuff that a program could parse fairly easily. */
 
@@ -777,13 +773,13 @@ while ((item = string_nextinlist(&listptr, &sep, NULL, 0)))
     yield = expand_string_copy(newaddress);
     deliver_domain = deliver_localpart = NULL;
     if (yield == NULL)
-      log_write(0, LOG_MAIN|LOG_PANIC, "Failed to expand %s when processing "
+      log_write(LOG_MAIN|LOG_PANIC, "Failed to expand %s when processing "
         "errors_copy: %s", newaddress, expand_string_message);
     break;
     }
   }
 
-DEBUG(D_any) debug_printf("errors_copy check returned %s\n",
+DEBUG(any) debug_printf("errors_copy check returned %s\n",
   (yield == NULL)? US"NULL" : yield);
 
 expand_nmax = -1;
@@ -814,26 +810,27 @@ Returns:            FALSE if string expansion failed; TRUE otherwise
 */
 
 BOOL
-moan_skipped_syntax_errors(uschar *rname, error_block *eblock,
-  uschar *syntax_errors_to, BOOL some, uschar *custom)
+moan_skipped_syntax_errors(const uschar * rname, const error_block * eblock,
+  const uschar * syntax_errors_to, BOOL some, const uschar * custom)
 {
-int pid, fd;
+pid_t pid;
+int fd;
 const uschar * s;
 FILE * f;
 
-for (error_block * e = eblock; e; e = e->next)
+for (const error_block * e = eblock; e; e = e->next)
   if (e->text2)
-    log_write(0, LOG_MAIN, "%s router: skipped error: %s in %q",
+    log_write(LOG_MAIN, "%s router: skipped error: %s in %q",
       rname, e->text1, e->text2);
   else
-    log_write(0, LOG_MAIN, "%s router: skipped error: %s", rname,
+    log_write(LOG_MAIN, "%s router: skipped error: %s", rname,
       e->text1);
 
 if (!syntax_errors_to) return TRUE;
 
 if (!(s = expand_string(syntax_errors_to)))
   {
-  log_write(0, LOG_MAIN, "%s router failed to expand %s: %s", rname,
+  log_write(LOG_MAIN, "%s router failed to expand %s: %s", rname,
     syntax_errors_to, expand_string_message);
   return FALSE;
   }
@@ -844,7 +841,7 @@ pid = child_open_exim(&fd, US"moan_skipped_syntax_errors");
 
 if (pid < 0)
   {
-  DEBUG(D_any) debug_printf("Failed to create child to send message: %s\n",
+  DEBUG(any) debug_printf("Failed to create child to send message: %s\n",
     strerror(errno));
   return TRUE;
   }
@@ -860,7 +857,7 @@ if (custom)
   {
   if (!(s = expand_string(custom)))
     {
-    log_write(0, LOG_MAIN, "%s router failed to expand %s: %s", rname,
+    log_write(LOG_MAIN, "%s router failed to expand %s: %s", rname,
       custom, expand_string_message);
     return FALSE;
     }
@@ -870,11 +867,10 @@ if (custom)
 fprintf(f, "The %s router encountered the following error(s):\n\n",
   rname);
 
-for (error_block * e = eblock; e; e = e->next)
+for (const error_block * e = eblock; e; e = e->next)
   {
   fprintf(f, "  %s", e->text1);
-  if (e->text2 != NULL)
-    fprintf(f, " in the address\n  \"%s\"", e->text2);
+  if (e->text2) fprintf(f, " in the address\n  \"%s\"", e->text2);
   fprintf(f, "\n\n");
   }
 
@@ -890,3 +886,5 @@ return TRUE;
 }
 
 /* End of moan.c */
+/* vi: aw ai sw=2
+*/
diff --git a/src/src/os.c b/src/src/os.c
index 4a054ac50..42860de04 100644
--- a/src/src/os.c
+++ b/src/src/os.c
@@ -11,9 +11,6 @@
 # include <signal.h>
 # include <stdio.h>
 # include <time.h>
-#else
-# define IS_DEBUG(x) (debug_selector & (x))
-# define DEBUG(x) if (IS_DEBUG(x))
 #endif
 
 #ifndef CS
@@ -495,7 +492,7 @@ ip_address_item *last = NULL;
 ip_address_item  *next;
 
 if (getifaddrs(&ifalist) != 0)
-  log_write_die(0, LOG_PANIC_DIE, "Unable to call getifaddrs: %d %s",
+  log_write_die(LOG_PANIC_DIE, "Unable to call getifaddrs: %d %s",
     errno, strerror(errno));
 
 for (struct ifaddrs * ifa = ifalist; ifa; ifa = ifa->ifa_next)
@@ -528,7 +525,7 @@ for (struct ifaddrs * ifa = ifalist; ifa; ifa = ifa->ifa_next)
     last = next;
     }
 
-  DEBUG(D_interface) debug_printf("Actual local interface address is %s (%s)\n",
+  DEBUG(interface) debug_printf_indent("Actual local interface address is %s (%s)\n",
     last->address, ifa->ifa_name);
   }
 
@@ -634,13 +631,13 @@ what we want to know. */
 if ((vs = socket(FAMILY, SOCK_DGRAM, 0)) < 0)
   {
 #if HAVE_IPV6
-  DEBUG(D_interface)
+  DEBUG(interface)
     debug_printf("Unable to create IPv6 socket to find interface addresses:\n  "
       "error %d %s\nTrying for an IPv4 socket\n", errno, strerror(errno));
   vs = socket(AF_INET, SOCK_DGRAM, 0);
   if (vs < 0)
 #endif
-  log_write_die(0, LOG_PANIC_DIE, "Unable to create IPv4 socket to find interface "
+  log_write_die(LOG_PANIC_DIE, "Unable to create IPv4 socket to find interface "
     "addresses: %d %s", errno, strerror(errno));
   }
 
@@ -656,7 +653,7 @@ ifc.V_ifc_flags = 0;
 #endif
 
 if (ioctl(vs, V_GIFCONF, CS &ifc) < 0)
-  log_write_die(0, LOG_PANIC_DIE, "Unable to get interface configuration: %d %s",
+  log_write_die(LOG_PANIC_DIE, "Unable to get interface configuration: %d %s",
     errno, strerror(errno));
 
 /* If the buffer is big enough, the ioctl sets the value of ifc.V_ifc_len to
@@ -667,7 +664,7 @@ number of interfaces, even though they don't all fit in the buffer. */
 if (ifc.V_ifc_len > sizeof(buf))
   {
   ifc.V_ifc_len = sizeof(buf);
-  DEBUG(D_interface)
+  DEBUG(interface)
     debug_printf("more than %d interfaces found: remainder not used\n"
       "(set MAX_INTERFACES in Local/Makefile and rebuild if you want more)\n",
       MAX_INTERFACES);
@@ -700,7 +697,7 @@ for (char * cp = buf; cp < buf + ifc.V_ifc_len; cp += len)
           ifreq.ifr_addr.sa_len : sizeof(ifreq.ifr_addr)) +
          sizeof(ifreq.V_ifr_name);
   if (len > sizeof(addrbuf))
-    log_write_die(0, LOG_PANIC_DIE, "Address for %s interface is absurdly long",
+    log_write_die(LOG_PANIC_DIE, "Address for %s interface is absurdly long",
         ifreq.V_ifr_name);
 
   #endif
@@ -724,7 +721,7 @@ for (char * cp = buf; cp < buf + ifc.V_ifc_len; cp += len)
     {
     continue;
     /*************
-    log_write(0, LOG_PANIC_DIE, "Unable to get flags for %s interface: %d %s",
+    log_write(LOG_PANIC_DIE, "Unable to get flags for %s interface: %d %s",
       ifreq.V_ifr_name, errno, strerror(errno));
     *************/
     }
@@ -737,7 +734,7 @@ for (char * cp = buf; cp < buf + ifc.V_ifc_len; cp += len)
 
   #ifndef SIOCGIFCONF_GIVES_ADDR
   if (ioctl(vs, V_GIFADDR, CS &ifreq) < 0)
-    log_write_die(0, LOG_PANIC_DIE, "Unable to get IP address for %s interface: "
+    log_write_die(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;
 
@@ -761,7 +758,7 @@ for (char * cp = buf; cp < buf + ifc.V_ifc_len; cp += len)
     last = next;
     }
 
-  DEBUG(D_interface) debug_printf("Actual local interface address is %s (%s)\n",
+  DEBUG(interface) debug_printf_indent("Actual local interface address is %s (%s)\n",
     last->address, ifreq.V_ifr_name);
   }
 
@@ -794,7 +791,7 @@ yield->next->port = 0;
 yield->next->next = NULL;
 #endif
 
-DEBUG(D_interface) debug_printf("Unable to find local interface addresses "
+DEBUG(interface) debug_printf("Unable to find local interface addresses "
   "on this OS: returning loopback address(es)\n");
 return yield;
 }
diff --git a/src/src/parse.c b/src/src/parse.c
index d840beb6d..3900564e9 100644
--- a/src/src/parse.c
+++ b/src/src/parse.c
@@ -68,8 +68,8 @@ Returns:   pointer past the end of the address
            (i.e. points to null or comma)
 */
 
-uschar *
-parse_find_address_end(const uschar * s, BOOL nl_ends)
+const uschar *
+parse_find_address_end_gen(const uschar * s, BOOL nl_ends)
 {
 BOOL source_routing = *s == '@';
 int no_term = source_routing ? 1 : 0;
@@ -129,7 +129,7 @@ while (*s && (*s != ',' || no_term > 0) && (*s != '\n' || !nl_ends))
     }
   }
 
-return US s;
+return s;
 }
 
 
@@ -792,7 +792,7 @@ else if (!*t)
 
 else
   {
-  t += Ustrlen((const uschar *)t);
+  t += Ustrlen(t);
   *t++ = *s++;
   *domain = t - yield;
   s = read_domain(s, t, TRUE, errorptr);
@@ -806,7 +806,6 @@ move it back past white space if necessary. */
 
 PARSE_SUCCEEDED:
 if (*s)
-  {
   if (f.parse_found_group && *s == ';')
     {
     f.parse_found_group = FALSE;
@@ -818,7 +817,7 @@ if (*s)
       s, (int)(s - US mailbox), mailbox);
     goto PARSE_FAILED;
     }
-  }
+
 *start = startptr - US mailbox;      /* Return offsets */
 while (isspace(endptr[-1])) endptr--;
 *end = endptr - US mailbox;
@@ -882,13 +881,10 @@ const uschar *
 parse_quote_2047(const uschar * string, int len, const uschar * charset,
   BOOL fold)
 {
-int hlen, line_off;
-BOOL coded = FALSE;
-BOOL first_byte = FALSE;
-gstring * g =
-  string_fmt_append(NULL, "=?%s?Q?%n", charset ? charset : US"iso-8859-1", &hlen);
-
-line_off = hlen;
+int line_off = 0, hlen;
+BOOL coded = FALSE, first_byte = FALSE;
+gstring * g = string_fmt_append(NULL, "=?%s?Q?%n",
+				charset ? charset : US"iso-8859-1", &hlen);
 
 for (const uschar * s = string; len > 0; s++, len--)
   {
@@ -898,7 +894,7 @@ for (const uschar * s = string; len > 0; s++, len--)
     {
     g = fold ? string_catn(g, US"?=\n ", 4) : string_catn(g, US"?= ", 3);
     line_off = g->ptr;
-    g = string_catn(g, g->s, hlen);
+    g = string_catn(g, g->s, hlen);		/* dup the leader */
     }
 
   if (  ch < 33 || ch > 126
@@ -1252,7 +1248,7 @@ parse_forward_list(const uschar *s, int options, address_item **anchor,
 {
 int count = 0;
 
-DEBUG(D_route) debug_printf("parse_forward_list: %s\n", s);
+DEBUG(route) debug_printf("parse_forward_list: %s\n", s);
 
 for (;;)
   {
@@ -1332,7 +1328,7 @@ for (;;)
 
   len = ss - s;
 
-  DEBUG(D_route) debug_printf("extract item: %.*s\n", len, s);
+  DEBUG(route) debug_printf("extract item: %.*s\n", len, s);
 
   /* Handle special addresses if permitted. If the address is :unknown:
   ignore it - this is for backward compatibility with old alias files. You
diff --git a/src/src/priv.c b/src/src/priv.c
index 2bc0c213d..2c1044bc0 100644
--- a/src/src/priv.c
+++ b/src/src/priv.c
@@ -27,7 +27,7 @@ void
 priv_drop_temp(const uid_t temp_uid, const gid_t temp_gid)
 {
 if (priv_state != PRIV_RESTORED)
-  log_write_die(0, LOG_PANIC_DIE,
+  log_write_die(LOG_PANIC_DIE,
 		"priv_drop_temp: unexpected priv_state %d != %d",
 		priv_state, PRIV_RESTORED);
 
@@ -39,19 +39,19 @@ if (priv_euid == root_uid)
   priv_egid = getegid();
   priv_ngroups = getgroups(nelem(priv_groups), priv_groups);
   if (priv_ngroups < 0)
-    log_write_die(0, LOG_PANIC_DIE, "getgroups: %s", strerror(errno));
+    log_write_die(LOG_PANIC_DIE, "getgroups: %s", strerror(errno));
 
   if (priv_ngroups > 0 && setgroups(1, &temp_gid) != 0)
-    log_write_die(0, LOG_PANIC_DIE, "setgroups: %s", strerror(errno));
+    log_write_die(LOG_PANIC_DIE, "setgroups: %s", strerror(errno));
   if (setegid(temp_gid) != 0)
-    log_write_die(0, LOG_PANIC_DIE, "setegid(%d): %s", temp_gid, strerror(errno));
+    log_write_die(LOG_PANIC_DIE, "setegid(%d): %s", temp_gid, strerror(errno));
   if (seteuid(temp_uid) != 0)
-    log_write_die(0, LOG_PANIC_DIE, "seteuid(%d): %s", temp_uid, strerror(errno));
+    log_write_die(LOG_PANIC_DIE, "seteuid(%d): %s", temp_uid, strerror(errno));
 
   if (geteuid() != temp_uid)
-    log_write_die(0, LOG_PANIC_DIE, "getdeuid() != %d", temp_uid);
+    log_write_die(LOG_PANIC_DIE, "getdeuid() != %d", temp_uid);
   if (getegid() != temp_gid)
-    log_write_die(0, LOG_PANIC_DIE, "getegid() != %d", temp_gid);
+    log_write_die(LOG_PANIC_DIE, "getegid() != %d", temp_gid);
   }
 
 priv_state = PRIV_DROPPED;
@@ -63,22 +63,22 @@ void
 priv_restore(void)
 {
 if (priv_state != PRIV_DROPPED)
-  log_write_die(0, LOG_PANIC_DIE, "priv_restore: unexpected priv_state %d != %d", priv_state, PRIV_DROPPED);
+  log_write_die(LOG_PANIC_DIE, "priv_restore: unexpected priv_state %d != %d", priv_state, PRIV_DROPPED);
 priv_state = PRIV_RESTORING;
 
 if (priv_euid == root_uid)
   {
   if (seteuid(priv_euid) != 0)
-    log_write_die(0, LOG_PANIC_DIE, "seteuid(%d): %s", priv_euid, strerror(errno));
+    log_write_die(LOG_PANIC_DIE, "seteuid(%d): %s", priv_euid, strerror(errno));
   if (setegid(priv_egid) != 0)
-    log_write_die(0, LOG_PANIC_DIE, "setegid(%d): %s", priv_egid, strerror(errno));
+    log_write_die(LOG_PANIC_DIE, "setegid(%d): %s", priv_egid, strerror(errno));
   if (priv_ngroups > 0 && setgroups(priv_ngroups, priv_groups) != 0)
-    log_write_die(0, LOG_PANIC_DIE, "setgroups: %s", strerror(errno));
+    log_write_die(LOG_PANIC_DIE, "setgroups: %s", strerror(errno));
 
   if (geteuid() != priv_euid)
-    log_write_die(0, LOG_PANIC_DIE, "getdeuid() != %d", priv_euid);
+    log_write_die(LOG_PANIC_DIE, "getdeuid() != %d", priv_euid);
   if (getegid() != priv_egid)
-    log_write_die(0, LOG_PANIC_DIE, "getdegid() != %d", priv_egid);
+    log_write_die(LOG_PANIC_DIE, "getdegid() != %d", priv_egid);
   }
 
 priv_state = PRIV_RESTORED;
diff --git a/src/src/queue.c b/src/src/queue.c
index b3d972f41..949c232f7 100644
--- a/src/src/queue.c
+++ b/src/src/queue.c
@@ -39,14 +39,15 @@ static BOOL queue_tls_init = FALSE;
 queue_get_spool_list() below.
 
 Arguments:
-  a            points to an ordered list of queue_filename items
-  b            points to another ordered list
+  a		points to an ordered list of queue_filename items
+  b		points to another ordered list
+  order		older/newer first
 
-Returns:       a pointer to a merged ordered list
+Returns:	a pointer to a merged ordered list
 */
 
 static queue_filename *
-merge_queue_lists(queue_filename * a, queue_filename * b)
+merge_queue_lists(queue_filename * a, queue_filename * b, s_order_t order)
 {
 queue_filename * first = NULL, ** append = &first;
 
@@ -60,7 +61,7 @@ while (a && b)
     d = Ustrcmp(a->text + (a_old ? 6+1+6+1 : MESSAGE_ID_TIME_LEN + 1 + MESSAGE_ID_PID_LEN + 1),
 		b->text + (b_old ? 6+1+6+1 : MESSAGE_ID_TIME_LEN + 1 + MESSAGE_ID_PID_LEN + 1));
     }
-  if (d < 0)
+  if ((d < 0) == (order == SLIST_OLDER_FIRST))
     {
     *append = a;
     append= &a->next;
@@ -100,7 +101,7 @@ therein. Single-character sub-directories are handled as follows:
   identifying character of the subdirectory, if any. The subdirs vector is
   still required as an argument.
 
-If the randomize argument is TRUE, messages are returned in "randomized" order.
+If random order requested messages are returned in "randomized" order.
 Actually, the order is anything but random, but the algorithm is cheap, and the
 point is simply to ensure that the same order doesn't occur every time, in case
 a particular message is causing a remote MTA to barf - we would like to try
@@ -110,7 +111,7 @@ If the randomize argument is FALSE, sort the list according to the file name.
 This should give the order in which the messages arrived. It is normally used
 only for presentation to humans, in which case the (possibly expensive) sort
 that it does is not part of the normal operational code. However, if
-queue_run_in_order is set, sorting has to take place for queue runs as well.
+running ordered, sorting has to take place for queue runs as well.
 When randomize is FALSE, the first argument is normally -1, so all messages are
 included.
 
@@ -118,7 +119,7 @@ Arguments:
   subdiroffset   sub-directory character offset, or 0 or -1 (see above)
   subdirs        vector to store list of subdirchars
   subcount       pointer to int in which to store count of subdirs
-  randomize      TRUE if the order of the list is to be unpredictable
+  order		 dontcare/oldest/random/newest-first
   pcount	 If not NULL, fill in with count of files and do not return list
 
 Returns:         pointer to a chain of queue name items
@@ -126,7 +127,7 @@ Returns:         pointer to a chain of queue name items
 
 static queue_filename *
 queue_get_spool_list(int subdiroffset, uschar * subdirs, int * subcount,
-  BOOL randomize, unsigned * pcount)
+  s_order_t order, unsigned * pcount)
 {
 int i, flags = 0, resetflags = -1, subptr;
 queue_filename * yield = NULL, * last = NULL;
@@ -140,7 +141,7 @@ not randomizing, initialize the sublists for the bottom-up merge sort. */
 
 if (pcount)
   *pcount = 0;
-else if (randomize)
+else if (order == SLIST_RANDOM)
   resetflags = time(NULL) & 0xFFFF;
 else
    for (i = 0; i < LOG2_MAXNODES; i++)
@@ -174,7 +175,7 @@ for (; i <= *subcount; i++)
   {
   int count = 0;
   int subdirchar = subdirs[i];      /* 0 for main directory */
-  DIR *dd;
+  DIR * dd;
 
   if (subdirchar != 0)
     {
@@ -182,7 +183,7 @@ for (; i <= *subcount; i++)
     buffer[subptr+1] = subdirchar;
     }
 
-  DEBUG(D_queue_run) debug_printf("looking in %s\n", buffer);
+  DEBUG(queue_run) debug_printf("looking in %s\n", buffer);
   if (!(dd = exim_opendir(buffer)))
     continue;
 
@@ -221,13 +222,14 @@ for (; i <= *subcount; i++)
 	Ustrcpy(next->text, name);
 	next->dir_uschar = subdirchar;
 
-	/* Handle the creation of a randomized list. The first item becomes both
-	the top and bottom of the list. Subsequent items are inserted either at
-	the top or the bottom, randomly. This is, I argue, faster than doing a
-	sort by allocating a random number to each item, and it also saves having
-	to store the number with each item. */
+	if (order == SLIST_RANDOM)
+	  {
+	  /* Handle the creation of a randomized list. The first item becomes
+	  both the top and bottom of the list. Subsequent items are inserted
+	  either at the top or the bottom, randomly. This is, I argue, faster
+	  than doing a sort by allocating a random number to each item, and it
+	  also saves having to store the number with each item. */
 
-	if (randomize)
 	  if (!yield)
 	    {
 	    next->next = NULL;
@@ -237,7 +239,7 @@ for (; i <= *subcount; i++)
 	    {
 	    if (flags == 0)
 	      flags = resetflags;
-	    if ((flags & 1) == 0)
+	    if (!(flags & 1))
 	      {
 	      next->next = yield;
 	      yield = next;
@@ -250,16 +252,16 @@ for (; i <= *subcount; i++)
 	      }
 	    flags = flags >> 1;
 	    }
-
-	/* Otherwise do a bottom-up merge sort based on the name. */
-
+	  }
 	else
 	  {
+	  /* Do a bottom-up merge sort based on the name. */
+
 	  next->next = NULL;
 	  for (int j = 0; j < LOG2_MAXNODES; j++)
 	    if (root[j])
 	      {
-	      next = merge_queue_lists(next, root[j]);
+	      next = merge_queue_lists(next, root[j], order);
 	      root[j] = j == LOG2_MAXNODES - 1 ? next : NULL;
 	      }
 	    else
@@ -305,9 +307,9 @@ for (; i <= *subcount; i++)
 /* When using a bottom-up merge sort, do the final merging of the sublists.
 Then pass back the final list of file items. */
 
-if (!pcount && !randomize)
+if (!pcount && order != SLIST_RANDOM)
   for (i = 0; i < LOG2_MAXNODES; ++i)
-    yield = merge_queue_lists(yield, root[i]);
+    yield = merge_queue_lists(yield, root[i], order);
 
 return yield;
 }
@@ -351,7 +353,8 @@ Returns:     nothing
 */
 
 void
-queue_run(qrunner * q, const uschar * start_id, const uschar * stop_id, BOOL recurse)
+queue_run(qrunner * q, const uschar * start_id, const uschar * stop_id,
+  BOOL recurse)
 {
 BOOL force_delivery = q->queue_run_force
   || deliver_selectstring || deliver_selectstring_sender;
@@ -362,6 +365,7 @@ int subcount = 0;
 uschar subdirs[64];
 pid_t qpid[4] = {0};	/* Parallelism factor for q2stage 1st phase */
 BOOL single_id = FALSE, msg_handled = FALSE;
+s_order_t order;
 
 #ifdef MEASURE_TIMING
 report_time_since(&timestamp_startup, US"queue_run start");
@@ -387,6 +391,21 @@ f.queue_smtp = q->queue_2stage;
 queue_run_pid = getpid();
 f.queue_running = TRUE;
 
+GET_OPTION("queue_run_order");
+order = SLIST_RANDOM;			/* default */
+if (queue_run_order)
+  {
+  if (Ustrcmp(queue_run_order, "oldest") == 0)
+    order = SLIST_OLDER_FIRST;
+  else if (Ustrcmp(queue_run_order, "newest") == 0)
+    order = SLIST_NEWER_FIRST;
+  }
+else
+  {
+  GET_OPTION("queue_run_in_order");	/* Obsolete option, 4.100 onwards */
+  if (queue_run_in_order) order = SLIST_OLDER_FIRST;
+  }
+
 /* Log the true start of a queue run, and fancy options */
 
 if (!recurse)
@@ -401,7 +420,7 @@ if (!recurse)
   *p = '\0';
 
   p = big_buffer;
-  p += sprintf(CS p, "pid=%d", (int)queue_run_pid);
+  p += sprintf(CS p, "pid=" PID_T_FMT, queue_run_pid);
 
   if (*extras)
     p += sprintf(CS p, " -q%s", extras);
@@ -417,14 +436,13 @@ if (!recurse)
       deliver_selectstring_sender);
 
   log_detail = string_copy(big_buffer);
-  if (q->name)
-    log_write(L_queue_run, LOG_MAIN, "Start %s'%s' queue run: %s",
-      atrn_mode ? "ODMR " : "",
-      q->name, log_detail);
-  else
-    log_write(L_queue_run, LOG_MAIN, "Start %squeue run: %s",
-      atrn_mode ? "ODMR " : "",
-      log_detail);
+  if (LOGGING(queue_run))
+    if (q->name)
+      log_write(LOG_MAIN, "Start %s'%s' queue run: %s",
+	atrn_mode ? "ODMR " : "", q->name, log_detail);
+    else
+      log_write(LOG_MAIN, "Start %squeue run: %s",
+	atrn_mode ? "ODMR " : "", log_detail);
 
   single_id = start_id && stop_id && !q->queue_2stage
 	      && Ustrcmp(start_id, stop_id) == 0;
@@ -451,7 +469,7 @@ if (!queue_tls_init)
 /* If the spool is split into subdirectories, we want to process it one
 directory at a time, so as to spread out the directory scanning and the
 delivering when there are lots of messages involved, except when
-queue_run_in_order is set.
+running the queue in (some) order.
 
 In the random order case, this loop runs once for the main directory (handling
 any messages therein), and then repeats for any subdirectories that were found.
@@ -460,16 +478,16 @@ 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
 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 ordered queue runs)
+it scans all directories and makes a single message list. */
 
-for (int i = queue_run_in_order ? -1 : 0;
-     i <= (queue_run_in_order ? -1 : subcount);
+for (int i = order == SLIST_RANDOM ? 0 : -1;
+     i <= (order == SLIST_RANDOM ? subcount : -1);
      i++)
   {
   rmark reset_point1 = store_mark();
 
-  DEBUG(D_queue_run)
+  DEBUG(queue_run)
     {
     if (i == 0)
       debug_printf("queue running main directory\n");
@@ -480,7 +498,7 @@ for (int i = queue_run_in_order ? -1 : 0;
     }
 
   for (queue_filename * fq = queue_get_spool_list(i, subdirs, &subcount,
-					     !queue_run_in_order, NULL);
+					     order, NULL);
        fq; fq = fq->next)
     {
     pid_t pid;
@@ -495,22 +513,23 @@ for (int i = queue_run_in_order ? -1 : 0;
     if (!q->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,
-          (double)load_average/1000.0,
-          (double)deliver_queue_load_max/1000.0);
+	if (LOGGING(queue_run))
+	  log_write(LOG_MAIN, "Abandon queue run: %s (load %.2f, max %.2f)",
+	    log_detail,
+	    (double)load_average/1000.0,
+	    (double)deliver_queue_load_max/1000.0);
         i = subcount;                 /* Don't process other directories */
         break;
         }
       else
-        DEBUG(D_load) debug_printf("load average = %.2f max = %.2f\n",
+        DEBUG(load) debug_printf("load average = %.2f max = %.2f\n",
           (double)load_average/1000.0,
           (double)deliver_queue_load_max/1000.0);
 
     /* If initial of a 2-phase run (and not under the test-harness)
     maintain a set of child procs to get disk parallelism */
 
-    if (q->queue_2stage && !queue_run_in_order)
+    if (q->queue_2stage && order == SLIST_RANDOM)
       {
       int j;
       if (qpid[
@@ -519,10 +538,14 @@ for (int i = queue_run_in_order ? -1 : 0;
 #endif
 	      nelem(qpid) - 1])
 	{		/* The child table is maxed out; wait for the oldest */
-	DEBUG(D_queue_run)
+			/*XXX It might be nicer to wait for the first one
+			that happens to complete. */
+	set_process_info("running queue (ph 1): parallel limit %u",
+			nelem(qpid));
+	DEBUG(queue_run)
 	  debug_printf("q2stage waiting for child %d\n", (int)qpid[0]);
 	waitpid(qpid[0], NULL, 0);
-	DEBUG(D_queue_run)
+	DEBUG(queue_run)
 	  debug_printf("q2stage reaped child %d\n", (int)qpid[0]);
 #ifndef MEASURE_TIMING
 	if (f.running_in_test_harness) j = 0; else
@@ -577,7 +600,7 @@ for (int i = queue_run_in_order ? -1 : 0;
 
       if (f.deliver_freeze && !q->deliver_force_thaw)
         {
-        log_write(L_skip_delivery, LOG_MAIN, "Message is frozen");
+        if (LOGGING(skip_delivery)) log_write(LOG_MAIN, "Message is frozen");
         wanted = FALSE;
         }
 
@@ -585,7 +608,7 @@ for (int i = queue_run_in_order ? -1 : 0;
 
       else if (q->queue_run_first_delivery && !f.deliver_firsttime)
         {
-        DEBUG(D_queue_run) debug_printf("%s: not first delivery\n", fq->text);
+        DEBUG(queue_run) debug_printf("%s: not first delivery\n", fq->text);
         wanted = FALSE;
         }
 
@@ -598,11 +621,11 @@ for (int i = queue_run_in_order ? -1 : 0;
       else if (  deliver_selectstring_sender
 	      && !(f.deliver_selectstring_sender_regex
 		  ? regex_match(selectstring_regex_sender, sender_address, -1, NULL)
-		  : (strstric_c(sender_address, deliver_selectstring_sender, FALSE)
+		  : (strstric(sender_address, deliver_selectstring_sender, FALSE)
 		      != NULL)
 	      )   )
         {
-        DEBUG(D_queue_run) debug_printf("%s: sender address did not match %s\n",
+        DEBUG(queue_run) debug_printf("%s: sender address did not match %s\n",
           fq->text, deliver_selectstring_sender);
         wanted = FALSE;
         }
@@ -617,7 +640,7 @@ for (int i = queue_run_in_order ? -1 : 0;
           const uschar * address = recipients_list[i].address;
           if (  (f.deliver_selectstring_regex
 		? regex_match(selectstring_regex, address, -1, NULL)
-                : (strstric_c(address, deliver_selectstring, FALSE) != NULL)
+                : (strstric(address, deliver_selectstring, FALSE) != NULL)
 		)
              && tree_search(tree_nonrecipients, address) == NULL
 	     )
@@ -626,12 +649,12 @@ for (int i = queue_run_in_order ? -1 : 0;
 
         if (i >= recipients_count)
           {
-          DEBUG(D_queue_run)
+          DEBUG(queue_run)
             debug_printf("%s: no recipient address matched %s\n",
               fq->text, deliver_selectstring);
           wanted = FALSE;
           }
-	else DEBUG(D_acl) if (atrn_domains)
+	else DEBUG(acl) if (atrn_domains)
 	  debug_printf_indent("%s matches ATRN\n", fq->text);
         }
 
@@ -652,8 +675,8 @@ for (int i = queue_run_in_order ? -1 : 0;
     pretty cheap. */
 
     if (pipe(pfd) < 0)
-      log_write_die(0, LOG_MAIN, "failed to create pipe in queue "
-        "runner process %d: %s", queue_run_pid, strerror(errno));
+      log_write_die(LOG_MAIN, "failed to create pipe in queue "
+        "runner process " PID_T_FMT ": %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
@@ -697,8 +720,8 @@ single_item_retry:
 		? EXIT_FAILURE : EXIT_SUCCESS);
       }
     if (pid < 0)
-      log_write_die(0, LOG_MAIN, "fork of delivery process from "
-        "queue runner %d failed\n", queue_run_pid);
+      log_write_die(LOG_MAIN, "fork of delivery process from "
+        "queue runner " PID_T_FMT " failed\n", queue_run_pid);
 
     /* Close the writing end of the synchronizing pipe in this process,
     then wait for the first level process to terminate. */
@@ -708,7 +731,7 @@ single_item_retry:
     for (int ret; (ret = wait (&status)) != pid; )
       if (ret == -1)
 	{
-	DEBUG(D_any) debug_printf("%s %d: wait: %s\n", __FUNCTION__, __LINE__,
+	DEBUG(any) debug_printf("%s %d: wait: %s\n", __FUNCTION__, __LINE__,
 				  strerror(errno));
 	status = 0;
 	break;
@@ -722,7 +745,7 @@ single_item_retry:
     /* If the process crashed, tell somebody */
 
     else if (status & 0x00ff)
-      log_write(0, LOG_MAIN|LOG_PANIC,
+      log_write(LOG_MAIN|LOG_PANIC,
         "queue run: process %d crashed with signal %d while delivering %s",
         (int)pid, status & 0x00ff, fq->text);
 
@@ -732,9 +755,9 @@ single_item_retry:
     if (status & 0xff00 && single_id)
       {
       single_id = FALSE;
-      DEBUG(D_queue_run) debug_printf("qrun single-item pause before retry\n");
+      DEBUG(queue_run) debug_printf("qrun single-item pause before retry\n");
       millisleep(500);
-      DEBUG(D_queue_run) debug_printf("qrun single-item retry after pause\n");
+      DEBUG(queue_run) debug_printf("qrun single-item retry after pause\n");
       goto single_item_retry;
       }
 
@@ -745,7 +768,7 @@ single_item_retry:
 
     set_process_info("running queue: waiting for children of %d", pid);
     if ((status = read(pfd[pipe_read], buffer, sizeof(buffer))) != 0)
-      log_write(0, LOG_MAIN|LOG_PANIC, status > 0 ?
+      log_write(LOG_MAIN|LOG_PANIC, status > 0 ?
 	"queue run: unexpected data on pipe" : "queue run: error on pipe: %s",
 	strerror(errno));
     (void)close(pfd[pipe_read]);
@@ -753,7 +776,7 @@ single_item_retry:
 
     /* If initial of a 2-phase run, we are a child - so just exit */
 
-    if (q->queue_2stage && !queue_run_in_order)
+    if (q->queue_2stage && order == SLIST_RANDOM)
       exim_exit(EXIT_SUCCESS);
 
     /* If we are in the test harness, and this is not the first of a 2-stage
@@ -770,7 +793,7 @@ single_item_retry:
 
   go_around:
     /* If initial of a 2-phase run, we are a child - so just exit */
-    if (q->queue_2stage && !queue_run_in_order)
+    if (q->queue_2stage && order == SLIST_RANDOM)
       exim_exit(EXIT_SUCCESS);
     }                                  /* End loop for list of messages */
 
@@ -780,7 +803,7 @@ single_item_retry:
   /* If this was the first time through for random order processing, and
   sub-directories have been found, randomize their order if necessary. */
 
-  if (i == 0 && subcount > 1 && !queue_run_in_order)
+  if (i == 0 && subcount > 1 && order == SLIST_RANDOM)
     for (int j = 1; j <= subcount; j++)
       {
       int r;
@@ -799,21 +822,27 @@ turned off. */
 
 if (q->queue_2stage)
   {
+  unsigned active;
+  for (active = 0; active < nelem(qpid); active++)
+    if (!qpid[active]) { --active; break; }
 
-  /* wait for last children */
-  for (int i = 0; i < nelem(qpid); i++)
+  /* wait for all first-stage children */
+  for (unsigned i = 0; i < nelem(qpid); i++)
     if (qpid[i])
       {
-      DEBUG(D_queue_run) debug_printf("q2stage reaped child %d\n", (int)qpid[i]);
+      set_process_info("running queue (ph 1): wait-all, child %u/%u",
+		      i+1, active);
       waitpid(qpid[i], NULL, 0);
+      DEBUG(queue_run)
+	debug_printf("q2stage reaped child " PID_T_FMT "\n", qpid[i]);
       }
-    else break;
+    else break;		/* should be no holes in table, so we're done */
 
 #ifdef MEASURE_TIMING
   report_time_since(&timestamp_startup, US"queue_run phase 1 done");
 #endif
   q->queue_2stage = f.queue_2stage = FALSE;
-  DEBUG(D_queue_run) debug_printf("queue_run phase 2 start\n");
+  DEBUG(queue_run) debug_printf("queue_run phase 2 start\n");
   queue_run(q, start_id, stop_id, TRUE);
   }
 
@@ -821,11 +850,11 @@ if (q->queue_2stage)
 
 if (!recurse)
   {
-  if (q->name)
-    log_write(L_queue_run, LOG_MAIN, "End '%s' queue run: %s",
-      q->name, log_detail);
-  else
-    log_write(L_queue_run, LOG_MAIN, "End queue run: %s", log_detail);
+  if (LOGGING(queue_run))
+    if (q->name)
+      log_write(LOG_MAIN, "End '%s' queue run: %s", q->name, log_detail);
+    else
+      log_write(LOG_MAIN, "End queue run: %s", log_detail);
 
   /* If no ATRN messages were sent, try to close the channel semi-cleanly.
   XXX is this the best place to be doing this? We really ought to be
@@ -833,7 +862,7 @@ if (!recurse)
 
   if (atrn_domains && !msg_handled)
     {
-    DEBUG(D_any) debug_printf("ATRN: no messages; sending QUIT\n");
+    DEBUG(any) debug_printf("ATRN: no messages; sending QUIT\n");
     (void) send(0, "QUIT\r\n", 6, 0);
     }
   }
@@ -844,7 +873,7 @@ if (!recurse)
 void
 single_queue_run(qrunner * q, const uschar * start_id, const uschar * stop_id)
 {
-DEBUG(D_queue_run) debug_printf("Single queue run%s%s%s%s\n",
+DEBUG(queue_run) debug_printf("Single queue run%s%s%s%s\n",
   start_id ? US" starting at " : US"",
   start_id ? start_id: US"",
   stop_id ?  US" stopping at " : US"",
@@ -882,7 +911,7 @@ tls_support orig_tls_in = tls_in;
 for (queue_filename * fq = queue_get_spool_list(-1,	/* entire queue */
 				      subdirs,		/* for holding sublist*/
 				      &subcount,	/* for subcount */
-				      FALSE,		/* not random */
+				      SLIST_OLDER_FIRST, /* not random */
 				      NULL);
      fq && yield != OK; fq = fq->next)
   {
@@ -904,7 +933,7 @@ for (queue_filename * fq = queue_get_spool_list(-1,	/* entire queue */
 	 && match_isinlist(s+1, &domains, 0, &domainlist_anchor, NULL,
 			  MCL_DOMAIN + MCL_NOEXPAND, TRUE, NULL) == OK)
 	{
-	DEBUG(D_all)
+	DEBUG(all)
 	  debug_printf_indent("found a matching message: '%s'\n", r->address);
 	yield = OK;
 	}
@@ -943,7 +972,7 @@ uschar subdirs[64];
 (void) queue_get_spool_list(-1,		/* entire queue */
 			subdirs,        /* for holding sub list */
 			&subcount,      /* for subcount */
-			FALSE,		/* not random */
+			SLIST_ORDER_UNDEFINED,
 			&count);	/* just get the count */
 return count;
 }
@@ -1043,7 +1072,7 @@ else
           -1,				/* entire queue */
           subdirs,			/* for holding sub list */
           &subcount,			/* for subcount */
-          option >= QL_UNSORTED,	/* randomize if required */
+          option >= QL_UNSORTED ? SLIST_RANDOM : SLIST_OLDER_FIRST,
 	  NULL);			/* don't just count */
 
 option &= ~QL_UNSORTED;
@@ -1357,7 +1386,7 @@ switch(action)
     if (spool_write_header(id, SW_MODIFYING, &errmsg) >= 0)
       {
       printf("is now frozen\n");
-      log_write(0, LOG_MAIN, "frozen by %s", username);
+      log_write(LOG_MAIN, "frozen by %s", username);
       }
     else
       {
@@ -1381,7 +1410,7 @@ switch(action)
     if (spool_write_header(id, SW_MODIFYING, &errmsg) >= 0)
       {
       printf("is no longer frozen\n");
-      log_write(0, LOG_MAIN, "unfrozen by %s", username);
+      log_write(LOG_MAIN, "unfrozen by %s", username);
       }
     else
       {
@@ -1408,7 +1437,7 @@ switch(action)
       {
       uschar * fname = spool_fname(US"msglog", message_subdir, id, US"");
 
-      DEBUG(D_any) debug_printf(" removing %s", fname);
+      DEBUG(any) debug_printf(" removing %s", fname);
       if (Uunlink(fname) < 0)
 	{
 	if (errno != ENOENT)
@@ -1416,12 +1445,12 @@ switch(action)
 	  yield = FALSE;
 	  printf("Error while removing %s: %s\n", fname, strerror(errno));
 	  }
-	else DEBUG(D_any) debug_printf(" (no file)\n");
+	else DEBUG(any) debug_printf(" (no file)\n");
 	}
       else
 	{
 	removed = TRUE;
-	DEBUG(D_any) debug_printf(" (ok)\n");
+	DEBUG(any) debug_printf(" (ok)\n");
 	}
 
       for (int i = 0; i < 3; i++)
@@ -1429,7 +1458,7 @@ switch(action)
 	suffix[1] = (US"DHJ")[i];
 	fname = spool_fname(US"input", message_subdir, id, suffix);
 
-	DEBUG(D_any) debug_printf(" removing %s", fname);
+	DEBUG(any) debug_printf(" removing %s", fname);
 	if (Uunlink(fname) < 0)
 	  {
 	  if (errno != ENOENT)
@@ -1437,12 +1466,12 @@ switch(action)
 	    yield = FALSE;
 	    printf("Error while removing %s: %s\n", fname, strerror(errno));
 	    }
-	  else DEBUG(D_any) debug_printf(" (no file)\n");
+	  else DEBUG(any) debug_printf(" (no file)\n");
 	  }
 	else
 	  {
 	  removed = TRUE;
-	  DEBUG(D_any) debug_printf(" (done)\n");
+	  DEBUG(any) debug_printf(" (done)\n");
 	  }
 	}
       }
@@ -1468,7 +1497,7 @@ switch(action)
 	  int start, end, dom;
 
 	  if (!parse_extract_address(addr, &err, &start, &end, &dom, TRUE))
-	    log_write(0, LOG_MAIN|LOG_PANIC,
+	    log_write(LOG_MAIN|LOG_PANIC,
 	      "failed to parse address '%.100s'\n: %s", addr, err);
 	  else
 	    {
@@ -1487,8 +1516,8 @@ switch(action)
 	}
       (void) event_raise(event_action, US"msg:complete", NULL, NULL);
 #endif
-      log_write(0, LOG_MAIN, "removed by %s", username);
-      log_write(0, LOG_MAIN, "Completed");
+      log_write(LOG_MAIN, "removed by %s", username);
+      log_write(LOG_MAIN, "Completed");
       }
     break;
     }
@@ -1510,7 +1539,7 @@ switch(action)
     {
     printf("has been modified\n");
     for (int i = 0; i < recipients_count; i++)
-      log_write(0, LOG_MAIN, "address <%s> marked delivered by %s",
+      log_write(LOG_MAIN, "address <%s> marked delivered by %s",
         recipients_list[i].address, username);
     }
   else
@@ -1574,8 +1603,8 @@ switch(action)
 #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",
+        receive_add_recipient(recipient, -1, rf_notify_unset, NULL);
+        log_write(LOG_MAIN, "recipient <%s> added by %s",
           recipient, username);
         }
       else if (action == MSG_MARK_DELIVERED)
@@ -1592,7 +1621,7 @@ switch(action)
         else
           {
           tree_add_nonrecipient(recipients_list[i].address);
-          log_write(0, LOG_MAIN, "address <%s> marked delivered by %s",
+          log_write(LOG_MAIN, "address <%s> marked delivered by %s",
             recipient, username);
           }
         }
@@ -1602,7 +1631,7 @@ switch(action)
 	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",
+        log_write(LOG_MAIN, "sender address changed to <%s> by %s",
           recipient, username);
         }
       }
@@ -1659,14 +1688,14 @@ if (s)
       if (Ustat(ss, &statbuf) == 0)
 	{
 	f.queue_smtp = TRUE;
-	DEBUG(D_receive) debug_printf("queue_smtp set because %s exists\n", ss);
+	DEBUG(receive) debug_printf("queue_smtp set because %s exists\n", ss);
 	}
       }
     else
       if (Ustat(ss, &statbuf) == 0)
 	{
 	queue_only = TRUE;
-	DEBUG(D_receive) debug_printf("queue_only set because %s exists\n", ss);
+	DEBUG(receive) debug_printf("queue_only set because %s exists\n", ss);
 	}
 }
 
@@ -1683,7 +1712,14 @@ int bsize = 1 + MESSAGE_ID_LENGTH + 1 + Ustrlen(queue_name) + 1;
 uschar * buf = store_get(bsize, GET_UNTAINTED);
 int fd;
 
-DEBUG(D_queue_run) debug_printf("%s: %s\n", __FUNCTION__, msgid);
+DEBUG(queue_run) debug_printf("%s: %s\n", __FUNCTION__, msgid);
+
+if (  deliver_queue_load_max >= 0
+   && os_getloadavg() > deliver_queue_load_max)
+  {
+  DEBUG(queue_run) debug_printf(" - avoided due to load-avg\n");
+  return;
+  }
 
 buf[0] = NOTIFY_MSG_QRUN;
 memcpy(buf+1, msgid, MESSAGE_ID_LENGTH+1);
@@ -1695,11 +1731,11 @@ if ((fd = socket(AF_UNIX, SOCK_DGRAM, 0)) >= 0)
   ssize_t len = daemon_notifier_sockname(&sa_un);
 
   if (sendto(fd, buf, bsize, 0, (struct sockaddr *)&sa_un, (socklen_t)len) < 0)
-    DEBUG(D_queue_run)
+    DEBUG(queue_run)
       debug_printf("%s: sendto %s\n", __FUNCTION__, strerror(errno));
   close(fd);
   }
-else DEBUG(D_queue_run) debug_printf(" socket: %s\n", strerror(errno));
+else DEBUG(queue_run) debug_printf(" socket: %s\n", strerror(errno));
 }
 #endif
 
diff --git a/src/src/rda.c b/src/src/rda.c
index dde001114..c23f34c89 100644
--- a/src/src/rda.c
+++ b/src/src/rda.c
@@ -122,7 +122,7 @@ if (saved_errno == ENOENT)
   saved_errno = errno;
   ALARM_CLR(0);
 
-  DEBUG(D_route) debug_printf("stat(%s)=%d\n", s, rc);
+  DEBUG(route) debug_printf_indent("stat(%s)=%d\n", s, rc);
   }
 
 if (sigalrm_seen || rc != 0)
@@ -133,7 +133,7 @@ if (sigalrm_seen || rc != 0)
   }
 
 *error = string_sprintf("%s does not exist", filename);
-DEBUG(D_route) debug_printf("%s\n", *error);
+DEBUG(route) debug_printf_indent("%s\n", *error);
 return FILE_NOT_EXIST;
 }
 
@@ -200,7 +200,7 @@ directory test. */
 if (!(fwd = Ufopen(filename, "rb"))) switch(errno)
   {
   case ENOENT:          /* File does not exist */
-    DEBUG(D_route) debug_printf("%s does not exist\n%schecking parent directory\n",
+    DEBUG(route) debug_printf_indent("%s does not exist\n%schecking parent directory\n",
       filename, options & RDO_ENOTDIR ? "ignore_enotdir set => skip " : "");
     *yield =
 	options & RDO_ENOTDIR || rda_exists(filename, error) == FILE_NOT_EXIST
@@ -209,14 +209,14 @@ if (!(fwd = Ufopen(filename, "rb"))) switch(errno)
 
   case ENOTDIR:         /* Something on the path isn't a directory */
     if (!(options & RDO_ENOTDIR)) goto DEFAULT_ERROR;
-    DEBUG(D_route) debug_printf("non-directory on path %s: file assumed not to "
+    DEBUG(route) debug_printf_indent("non-directory on path %s: file assumed not to "
       "exist\n", filename);
     *yield = FF_NONEXIST;
     return NULL;
 
   case EACCES:           /* Permission denied */
     if (!(options & RDO_EACCES)) goto DEFAULT_ERROR;
-    DEBUG(D_route) debug_printf("permission denied for %s: file assumed not to "
+    DEBUG(route) debug_printf_indent("permission denied for %s: file assumed not to "
       "exist\n", filename);
     *yield = FF_NONEXIST;
     return NULL;
@@ -295,7 +295,7 @@ if (fread(filebuf, 1, statbuf.st_size, fwd) != statbuf.st_size)
   }
 filebuf[statbuf.st_size] = 0;
 
-DEBUG(D_route) debug_printf(OFF_T_FMT " %sbytes read from %s\n",
+DEBUG(route) debug_printf_indent(OFF_T_FMT " %sbytes read from %s\n",
   statbuf.st_size, is_tainted(filename) ? "(tainted) " : "", filename);
 
 (void)fclose(fwd);
@@ -343,14 +343,15 @@ rda_extract(const redirect_block * rdata, int options,
   error_block ** eblockp, int * filtertype)
 {
 const uschar * data;
+int yield = 0;
 
-if (rdata->isfile)
-  {
-  int yield = 0;
+expand_level++;
+
+if (!rdata->isfile)
+  data = rdata->string;
+else
   if (!(data = rda_get_file_contents(rdata, options, error, &yield)))
-    return yield;
-  }
-else data = rdata->string;
+    goto out;
 
 *filtertype = f.system_filtering ? FILTER_EXIM : rda_is_filter(data);
 
@@ -362,10 +363,9 @@ expand_forbid that the expander inspects. */
 
 if (*filtertype != FILTER_FORWARD)
   {
-  int frc;
   int old_expand_forbid = expand_forbid;
 
-  DEBUG(D_route) debug_printf("data is %s filter program\n",
+  DEBUG(route) debug_printf_indent("data is %s filter program\n",
     *filtertype == FILTER_EXIM ? "an Exim" : "a Sieve");
 
   /* RDO_FILTER is an "allow" bit */
@@ -373,7 +373,7 @@ if (*filtertype != FILTER_FORWARD)
   if (!(options & RDO_FILTER))
     {
     *error = US"filtering not enabled";
-    return FF_ERROR;
+    goto ff_error;
     }
 
   expand_forbid =
@@ -389,14 +389,14 @@ if (*filtertype != FILTER_FORWARD)
     if (options & RDO_EXIM_FILTER)
       {
       *error = US"Exim filtering not enabled";
-      return FF_ERROR;
+      goto ff_error;
       }
     if (!(mi = misc_mod_find(US"exim_filter", NULL)))
       {
       *error = US"Exim-filtering not available";
-      return FF_ERROR;
+      goto ff_error;
       }
-    frc = (((fn_t *) mi->functions)[EXIM_INTERPRET])
+    yield = (((fn_t *) mi->functions)[EXIM_INTERPRET])
 				      (data, options, generated, error);
     }
   else
@@ -408,32 +408,40 @@ if (*filtertype != FILTER_FORWARD)
     if (options & RDO_SIEVE_FILTER)
       {
       *error = US"Sieve filtering not enabled";
-      return FF_ERROR;
+      goto ff_error;
       }
     if (!(mi = misc_mod_find(US"sieve_filter", NULL)))
       {
       *error = US"Sieve filtering not available";
-      return FF_ERROR;
+      goto ff_error;
       }
-    frc = (((fn_t *) mi->functions)[SIEVE_INTERPRET])
+    yield = (((fn_t *) mi->functions)[SIEVE_INTERPRET])
 				      (data, options, sieve, generated, error);
     }
 
   expand_forbid = old_expand_forbid;
-  return frc;
+  goto out;
   }
 
 /* Not a filter script */
 
-DEBUG(D_route) debug_printf("file is not a filter file\n");
+DEBUG(route) debug_printf_indent("file is not a filter file\n");
 
-return parse_forward_list(data,
+yield = parse_forward_list(data,
   options,                           /* specials that are allowed */
   generated,                         /* where to hang them */
   error,                             /* for errors */
   deliver_domain,                    /* to qualify \name */
   include_directory,                 /* restrain to directory */
   eblockp);                          /* for skipped syntax errors */
+
+out:
+  expand_level--;
+  return yield;
+
+ff_error:
+  yield = FF_ERROR;
+  goto out;
 }
 
 
@@ -554,8 +562,8 @@ Returns:        values from extraction function, or FF_NONEXIST:
 int
 rda_interpret(redirect_block * rdata, int options,
   const uschar * include_directory, const sieve_block * sieve,
-  const ugid_block * ugid, address_item ** generated,
-  uschar ** error, error_block ** eblockp, int * filtertype, const uschar * rname)
+  const ugid_block * ugid, address_item ** generated, uschar ** error,
+  error_block ** eblockp, int * filtertype, const uschar * rname)
 {
 int fd, rc, pfd[2];
 int yield, status;
@@ -565,7 +573,7 @@ uschar *data;
 uschar *readerror = US"";
 void (*oldsignal)(int);
 
-DEBUG(D_route) debug_printf("rda_interpret (%s): '%s'\n",
+DEBUG(route) debug_printf_indent("rda_interpret (%s): '%s'\n",
   rdata->isfile ? "file" : "string", string_printing(rdata->string));
 
 /* Do the expansions of the file name or data first, while still privileged. */
@@ -579,8 +587,8 @@ if (!(data = expand_string(rdata->string)))
   }
 rdata->string = data;
 
-DEBUG(D_route)
-  debug_printf("expanded: '%s'%s\n", data, is_tainted(data) ? " (tainted)":"");
+DEBUG(route)
+  debug_printf_indent("expanded: '%s'%s\n", data, is_tainted(data) ? " (tainted)":"");
 
 if (rdata->isfile && data[0] != '/')
   {
@@ -613,7 +621,7 @@ we have to create the subprocess to do everything as the given user. The
 results of processing are passed back via a pipe. */
 
 if (pipe(pfd) != 0)
-  log_write_die(0, LOG_MAIN, "creation of pipe for filter or "
+  log_write_die(LOG_MAIN, "creation of pipe for filter or "
     ":include: failed for %s: %s", rname, strerror(errno));
 
 /* Ensure that SIGCHLD is set to SIG_DFL before forking, so that the child
@@ -644,9 +652,9 @@ if ((pid = exim_fork(US"router-interpret")) == 0)
 
   if (ugid->uid != root_uid && ugid->uid != exim_uid)
     {
-    DEBUG(D_rewrite) debug_printf("turned off address rewrite logging (not "
+    DEBUG(rewrite) debug_printf_indent("turned off address rewrite logging (not "
       "root or exim in this process)\n");
-    BIT_CLEAR(log_selector, log_selector_size, Li_address_rewrite);
+    logging_modify_channels(US"-address_rewrite");
     }
 
   /* Now do the business */
@@ -785,14 +793,14 @@ out:
   exim_underbar_exit(EXIT_SUCCESS);
 
 bad:
-  DEBUG(D_rewrite) debug_printf("rda_interpret: failed write to pipe\n");
+  DEBUG(rewrite) debug_printf_indent("rda_interpret: failed write to pipe\n");
   goto out;
   }
 
 /* Back in the main process: panic if the fork did not succeed. */
 
 if (pid < 0)
-  log_write_die(0, LOG_MAIN, "fork failed for %s", rname);
+  log_write_die(LOG_MAIN, "fork failed for %s", rname);
 
 /* Read the pipe to get the data from the filter/forward. Our copy of the
 writing end must be closed first, as otherwise read() won't return zero on an
@@ -959,12 +967,13 @@ WAIT_EXIT:
 while ((rc = wait(&status)) != pid)
   if (rc < 0 && errno == ECHILD)      /* Process has vanished */
     {
-    log_write(0, LOG_MAIN, "redirection process %d vanished unexpectedly", pid);
+    log_write(LOG_MAIN,
+      "redirection process " PID_T_FMT " vanished unexpectedly", pid);
     goto FINAL_EXIT;
     }
 
-DEBUG(D_route)
-  debug_printf("rda_interpret: subprocess yield=%d error=%s\n", yield, *error);
+DEBUG(route)
+  debug_printf_indent("rda_interpret: subprocess yield=%d error=%s\n", yield, *error);
 
 if (had_disaster)
   {
@@ -973,10 +982,10 @@ if (had_disaster)
     status, readerror,
     *error ? US": error=" : US"",
     *error ? *error : US"");
-  log_write(0, LOG_MAIN|LOG_PANIC, "%s", *error);
+  log_write(LOG_MAIN|LOG_PANIC, "%s", *error);
   }
 else if (status != 0)
-  log_write(0, LOG_MAIN|LOG_PANIC, "internal problem in %s: unexpected status "
+  log_write(LOG_MAIN|LOG_PANIC, "internal problem in %s: unexpected status "
     "%04x from redirect subprocess (but data correctly received)", rname,
     status);
 
@@ -1005,3 +1014,5 @@ goto WAIT_EXIT;
 }
 
 /* End of rda.c */
+/* vi: aw ai sw=2
+*/
diff --git a/src/src/readconf.c b/src/src/readconf.c
index cec89f9bb..ea47000d4 100644
--- a/src/src/readconf.c
+++ b/src/src/readconf.c
@@ -47,7 +47,7 @@ static optionlist optionlist_config[] = {
   { "acl_smtp_data_prdr",       opt_stringptr,   {&acl_smtp_data_prdr} },
 #endif
 #ifndef DISABLE_DKIM
-  { "acl_smtp_dkim",            opt_module,	 {US"dkim"} },
+  { "acl_smtp_dkim",            opt_misc_module, {US"dkim"} },
 #endif
   { "acl_smtp_etrn",            opt_stringptr,   {&acl_smtp_etrn} },
   { "acl_smtp_expn",            opt_stringptr,   {&acl_smtp_expn} },
@@ -79,9 +79,7 @@ static optionlist optionlist_config[] = {
   { "av_scanner",               opt_stringptr,   {&av_scanner} },
 #endif
   { "bi_command",               opt_stringptr,   {&bi_command} },
-#ifdef EXPERIMENTAL_BRIGHTMAIL
-  { "bmi_config_file",          opt_stringptr,   {&bmi_config_file} },
-#endif
+  { "bounce_charset",           opt_stringptr,   {&bounce_charset} },
   { "bounce_message_file",      opt_stringptr,   {&bounce_message_file} },
   { "bounce_message_text",      opt_stringptr,   {&bounce_message_text} },
   { "bounce_return_body",       opt_bool,        {&bounce_return_body} },
@@ -101,6 +99,7 @@ static optionlist optionlist_config[] = {
   { "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_modules_load",	opt_stringptr,   {&daemon_modules_load} },
   { "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} },
@@ -121,16 +120,16 @@ static optionlist optionlist_config[] = {
 #endif
   { "disable_ipv6",             opt_bool,        {&disable_ipv6} },
 #ifndef DISABLE_DKIM
-  { "dkim_verify_hashes",       opt_module,	 {US"dkim"} },
-  { "dkim_verify_keytypes",     opt_module,	 {US"dkim"} },
-  { "dkim_verify_min_keysizes", opt_module,	 {US"dkim"} },
-  { "dkim_verify_minimal",      opt_module,	 {US"dkim"} },
-  { "dkim_verify_signers",      opt_module,	 {US"dkim"} },
+  { "dkim_verify_hashes",       opt_misc_module, {US"dkim"} },
+  { "dkim_verify_keytypes",     opt_misc_module, {US"dkim"} },
+  { "dkim_verify_min_keysizes", opt_misc_module, {US"dkim"} },
+  { "dkim_verify_minimal",      opt_misc_module, {US"dkim"} },
+  { "dkim_verify_signers",      opt_misc_module, {US"dkim"} },
 #endif
-#ifdef SUPPORT_DMARC
-  { "dmarc_forensic_sender",    opt_module,	 {US"dmarc"} },
-  { "dmarc_history_file",       opt_module,	 {US"dmarc"} },
-  { "dmarc_tld_file",           opt_module,	 {US"dmarc"} },
+#ifdef EXIM_HAVE_DMARC
+  { "dmarc_forensic_sender",    opt_misc_module, {US"dmarc"} },
+  { "dmarc_history_file",       opt_misc_module, {US"dmarc"} },
+  { "dmarc_tld_file",           opt_misc_module, {US"dmarc"} },
 #endif
   { "dns_again_means_nonexist", opt_stringptr,   {&dns_again_means_nonexist} },
   { "dns_check_names_pattern",  opt_stringptr,   {&check_dns_names_pattern} },
@@ -143,9 +142,6 @@ static optionlist optionlist_config[] = {
   { "dns_retry",                opt_int,         {&dns_retry} },
   { "dns_trust_aa",             opt_stringptr,   {&dns_trust_aa} },
   { "dns_use_edns0",            opt_int,         {&dns_use_edns0} },
- /* This option is now a no-op, retained for compatibility */
-  { "drop_cr",                  opt_bool,        {&drop_cr} },
-/*********************************************************/
   { "dsn_advertise_hosts",      opt_stringptr,   {&dsn_advertise_hosts} },
   { "dsn_from",                 opt_stringptr,   {&dsn_from} },
   { "envelope_to_remove",       opt_bool,        {&envelope_to_remove} },
@@ -191,9 +187,6 @@ static optionlist optionlist_config[] = {
   { "hosts_treat_as_local",     opt_stringptr,   {&hosts_treat_as_local} },
 #ifdef EXPERIMENTAL_XCLIENT
   { "hosts_xclient",		opt_stringptr,	 {&hosts_xclient} },
-#endif
-#ifdef LOOKUP_IBASE
-  { "ibase_servers",            opt_stringptr,   {&ibase_servers} },
 #endif
   { "ignore_bounce_errors_after", opt_time,      {&ignore_bounce_errors_after} },
   { "ignore_fromline_hosts",    opt_stringptr,   {&ignore_fromline_hosts} },
@@ -201,15 +194,15 @@ static optionlist optionlist_config[] = {
   { "keep_environment",         opt_stringptr,   {&keep_environment} },
   { "keep_malformed",           opt_time,        {&keep_malformed} },
 #ifdef LOOKUP_LDAP
-  { "ldap_ca_cert_dir",         opt_stringptr,   {&eldap_ca_cert_dir} },
-  { "ldap_ca_cert_file",        opt_stringptr,   {&eldap_ca_cert_file} },
-  { "ldap_cert_file",           opt_stringptr,   {&eldap_cert_file} },
-  { "ldap_cert_key",            opt_stringptr,   {&eldap_cert_key} },
-  { "ldap_cipher_suite",        opt_stringptr,   {&eldap_cipher_suite} },
-  { "ldap_default_servers",     opt_stringptr,   {&eldap_default_servers} },
-  { "ldap_require_cert",        opt_stringptr,   {&eldap_require_cert} },
-  { "ldap_start_tls",           opt_bool,        {&eldap_start_tls} },
-  { "ldap_version",             opt_int,         {&eldap_version} },
+  { "ldap_ca_cert_dir",         opt_lookup_module, {US"ldap"} },
+  { "ldap_ca_cert_file",        opt_lookup_module, {US"ldap"} },
+  { "ldap_cert_file",           opt_lookup_module, {US"ldap"} },
+  { "ldap_cert_key",            opt_lookup_module, {US"ldap"} },
+  { "ldap_cipher_suite",        opt_lookup_module, {US"ldap"} },
+  { "ldap_default_servers",     opt_lookup_module, {US"ldap"} },
+  { "ldap_require_cert",        opt_lookup_module, {US"ldap"} },
+  { "ldap_start_tls",           opt_lookup_module, {US"ldap"} },
+  { "ldap_version",             opt_lookup_module, {US"ldap"} },
 #endif
 #ifndef DISABLE_ESMTP_LIMITS
   { "limits_advertise_hosts", opt_stringptr, {&limits_advertise_hosts} },
@@ -240,7 +233,7 @@ static optionlist optionlist_config[] = {
 #endif
   { "mua_wrapper",              opt_bool,        {&mua_wrapper} },
 #ifdef LOOKUP_MYSQL
-  { "mysql_servers",            opt_stringptr,   {&mysql_servers} },
+  { "mysql_servers",            opt_lookup_module, {US"mysql"} },
 #endif
   { "never_users",              opt_uidlist,     {&never_users} },
   { "notifier_socket",          opt_stringptr,   {&notifier_socket} },
@@ -248,7 +241,7 @@ static optionlist optionlist_config[] = {
   { "openssl_options",          opt_stringptr,   {&openssl_options} },
 #endif
 #ifdef LOOKUP_ORACLE
-  { "oracle_servers",           opt_stringptr,   {&oracle_servers} },
+  { "oracle_servers",           opt_lookup_module, {US"oracle"} },
 #endif
   { "panic_coredump",           opt_bool,        {&panic_coredump} },
   { "percent_hack_domains",     opt_stringptr,   {&percent_hack_domains} },
@@ -258,7 +251,7 @@ static optionlist optionlist_config[] = {
   { "perl_taintmode",           opt_bool,        {&opt_perl_taintmode} },
 #endif
 #ifdef LOOKUP_PGSQL
-  { "pgsql_servers",            opt_stringptr,   {&pgsql_servers} },
+  { "pgsql_servers",            opt_lookup_module, {US"pgsql"} },
 #endif
   { "pid_file_path",            opt_stringptr,   {&pid_file_path} },
   { "pipelining_advertise_hosts", opt_stringptr, {&pipelining_advertise_hosts} },
@@ -291,6 +284,7 @@ static optionlist optionlist_config[] = {
   { "queue_only_override",      opt_bool,        {&queue_only_override} },
   { "queue_run_in_order",       opt_bool,        {&queue_run_in_order} },
   { "queue_run_max",            opt_stringptr,   {&queue_run_max} },
+  { "queue_run_order",          opt_stringptr,   {&queue_run_order} },
   { "queue_smtp_domains",       opt_stringptr,   {&queue_smtp_domains} },
   { "receive_timeout",          opt_time,        {&receive_timeout} },
   { "received_header_text",     opt_stringptr,   {&received_header_text} },
@@ -299,7 +293,7 @@ static optionlist optionlist_config[] = {
   { "recipients_max",           opt_stringptr,   {&recipients_max} },
   { "recipients_max_reject",    opt_bool,        {&recipients_max_reject} },
 #ifdef LOOKUP_REDIS
-  { "redis_servers",            opt_stringptr,   {&redis_servers} },
+  { "redis_servers",            opt_lookup_module, {US"redis"} },
 #endif
   { "remote_max_parallel",      opt_int,         {&remote_max_parallel} },
   { "remote_sort_domains",      opt_stringptr,   {&remote_sort_domains} },
@@ -345,15 +339,15 @@ static optionlist optionlist_config[] = {
   { "spamd_address",            opt_stringptr,   {&spamd_address} },
 #endif
 #ifdef EXIM_HAVE_SPF
-  { "spf_guess",                opt_module,	 {US"spf"} },
-  { "spf_smtp_comment_template",opt_module,	 {US"spf"} },
+  { "spf_guess",                opt_misc_module, {US"spf"} },
+  { "spf_smtp_comment_template",opt_misc_module, {US"spf"} },
 #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_dbfile",            opt_stringptr,   {&sqlite_dbfile} },
-  { "sqlite_lock_timeout",      opt_int,         {&sqlite_lock_timeout} },
+  { "sqlite_dbfile",            opt_lookup_module, {US"sqlite"} },
+  { "sqlite_lock_timeout",      opt_lookup_module, {US"sqlite"} },
 #endif
   { "strict_acl_vars",          opt_bool,        {&strict_acl_vars} },
   { "strip_excess_angle_brackets", opt_bool,     {&strip_excess_angle_brackets} },
@@ -423,6 +417,7 @@ add_driver_info(driver_info ** drlist_p, const driver_info * newent,
 {
 #ifdef MACRO_PREDEF
 driver_info * listent = malloc(size);
+if (!listent) return;
 #else
 driver_info * listent = store_get(size, newent);
 #endif
@@ -515,9 +510,9 @@ options_logging(void)
 {
 uschar buf[EXIM_DRIVERNAME_MAX];
 
-for (bit_table * bp = log_options; bp < log_options + log_options_count; bp++)
+for (bit_table * p = log_channels; p < log_channels + log_chan_count; p++)
   {
-  spf(buf, sizeof(buf), US"_LOG_%T", bp->name);
+  spf(buf, sizeof(buf), US"_LOG_%T", p->name);
   builtin_macro_create(buf);
   }
 }
@@ -794,7 +789,7 @@ macro_create(const uschar * name, const uschar * val, BOOL command_line)
 {
 macro_item * m = store_get(sizeof(macro_item), GET_UNTAINTED);
 
-EARLY_DEBUG(D_any, "%s: '%s' '%s'\n", __FUNCTION__, name, val);
+EARLY_DEBUG(macro, "%s: '%s' '%s'\n", __FUNCTION__, name, val);
 m->next = NULL;
 m->command_line = command_line;
 m->namelen = Ustrlen(name);
@@ -837,7 +832,7 @@ while (isalnum(*s) || *s == '_')
   {
   if (namelen >= sizeof(name) - 1)
     {
-    log_write(0, LOG_PANIC|LOG_CONFIG_IN,
+    log_write(LOG_PANIC|LOG_CONFIG_IN,
       "macro name too long (maximum is " SIZE_T_FMT " characters)", sizeof(name) - 1);
     return FALSE;
     }
@@ -848,7 +843,7 @@ name[namelen] = 0;
 Uskip_whitespace(&s);
 if (*s++ != '=')
   {
-  log_write(0, LOG_PANIC|LOG_CONFIG_IN,
+  log_write(LOG_PANIC|LOG_CONFIG_IN,
     "malformed macro definition %q", line);
   return FALSE;
   }
@@ -875,7 +870,7 @@ for (m = macros; m; m = m->next)
     {
     if (!m->command_line && !redef)
       {
-      log_write(0, LOG_CONFIG|LOG_PANIC, "macro %q is already "
+      log_write(LOG_CONFIG|LOG_PANIC, "macro %q is already "
        "defined (use \"==\" if you want to redefine it)", name);
       return FALSE;
       }
@@ -884,7 +879,7 @@ for (m = macros; m; m = m->next)
 
   if (m->namelen < namelen && Ustrstr(name, m->name) != NULL)
     {
-    log_write(0, LOG_CONFIG|LOG_PANIC, "%q cannot be defined as "
+    log_write(LOG_CONFIG|LOG_PANIC, "%q cannot be defined as "
       "a macro because previously defined macro %q is a substring",
       name, m->name);
     return FALSE;
@@ -894,7 +889,7 @@ for (m = macros; m; m = m->next)
   macro is permitted (there is even an example).
   *
   * if (m->namelen > namelen && Ustrstr(m->name, name) != NULL)
-  *   log_write_die(0, LOG_CONFIG|"%q cannot be defined as "
+  *   log_write_die(LOG_CONFIG|"%q cannot be defined as "
   *     "a macro because it is a substring of previously defined macro %q",
   *     name, m->name);
   */
@@ -914,7 +909,7 @@ if (redef)
     }
   else
     {
-    log_write(0, LOG_CONFIG|LOG_PANIC, "can't redefine an undefined macro "
+    log_write(LOG_CONFIG|LOG_PANIC, "can't redefine an undefined macro "
       "%q", name);
     return FALSE;
     }
@@ -987,8 +982,13 @@ if (*s) for (macro_item * m = *s == '_' ? macros : macros_user; m; m = m->next)
     {
     int moveby;
 
-    EARLY_DEBUG(D_any, "%s: matched '%s' in '%.*s'\n", __FUNCTION__,
-      m->name, (int) Ustrlen(ss)-1, ss);
+    DEBUG(macro)
+      if (f.expansion_test)
+	printf("macro '%s' -> '%s'\n", m->name, m->replacement);
+      else
+	EARLY_DEBUG(macro, "%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)
@@ -1083,7 +1083,7 @@ for (;;)
     /* EOF at top level */
 
     if (cstate_stack_ptr >= 0)
-      log_write_die(0, LOG_CONFIG_IN,
+      log_write_die(LOG_CONFIG_IN,
         "Unexpected end of configuration file: .endif missing");
 
     if (len != 0) break;        /* EOF after continuation */
@@ -1152,7 +1152,7 @@ for (;;)
       if (c->pushpop > 0)
         {
         if (cstate_stack_ptr >= CSTATE_STACK_SIZE - 1)
-          log_write_die(0, LOG_CONFIG_IN,
+          log_write_die(LOG_CONFIG_IN,
             ".%s nested too deeply", c->name);
         cstate_stack[++cstate_stack_ptr] = cstate;
         cstate = next_cstate[cstate][macro_found ? c->action1 : c->action2];
@@ -1164,7 +1164,7 @@ for (;;)
       else
         {
         if (cstate_stack_ptr < 0)
-          log_write_die(0, LOG_CONFIG_IN,
+          log_write_die(LOG_CONFIG_IN,
             ".%s without matching .ifdef", c->name);
         cstate = (c->pushpop < 0)? cstate_stack[cstate_stack_ptr--] :
           next_cstate[cstate][macro_found ? c->action1 : c->action2];
@@ -1213,7 +1213,7 @@ for (;;)
     we need to check the permissions/ownership of the containing folder */
     if (*ss != '/')
       if (include_if_exists)
-	log_write_die(0, LOG_CONFIG_IN,
+	log_write_die(LOG_CONFIG_IN,
 			".include specifies a non-absolute path %q", ss);
       else
 	ss = string_sprintf("%s/%s", config_directory, ss);
@@ -1231,7 +1231,7 @@ for (;;)
     save->lineno = config_lineno;
 
     if (!(config_file = Ufopen(ss, "rb")))
-      log_write_die(0, LOG_CONFIG_IN, "failed to open included "
+      log_write_die(LOG_CONFIG_IN, "failed to open included "
         "configuration file %s", ss);
 
     config_filename = string_copy(ss);
@@ -1294,12 +1294,6 @@ if (strncmpic(s, US"begin ", 6) == 0)
   return NULL;
   }
 
-#ifdef LOOKUP_MODULE_DIR
-/* Check for any required module load operations */
-
-//mod_load_check(s);
-#endif
-
 /* Return the first non-blank character. */
 
 return s;
@@ -1342,7 +1336,7 @@ if (isalpha(Uskip_whitespace(&s)))
 
 name[p] = 0;
 if (broken) {
-  log_write_die(0, LOG_CONFIG_IN,
+  log_write_die(LOG_CONFIG_IN,
             "exim item name too long (>%d), unable to use %q (truncated)",
             len, name);
 }
@@ -1515,7 +1509,7 @@ optionlist *ol;
 uschar name2[EXIM_DRIVERNAME_MAX];
 sprintf(CS name2, "*set_%.50s", name);
 if (!(ol = find_option(name2, oltop, last)))
-  log_write_die(0, LOG_MAIN,
+  log_write_die(LOG_MAIN,
     "Exim internal error: missing set flag for %s", name);
 return data_block
   ? (BOOL *)(US data_block + ol->v.offset) : (BOOL *)ol->v.value;
@@ -1543,7 +1537,7 @@ extra_chars_error(const uschar *s, const uschar *t1, const uschar *t2, const usc
 {
 uschar *comment = US"";
 if (*s == '#') comment = US" (# is comment only at line start)";
-log_write_die(0, LOG_CONFIG_IN,
+log_write_die(LOG_CONFIG_IN,
   "extra characters follow %s%s%s%s", t1, t2, t3, comment);
 }
 
@@ -1584,7 +1578,7 @@ next->key = string_dequote(&p);
 
 Uskip_whitespace(&p);
 if (!*p)
-  log_write_die(0, LOG_CONFIG_IN,
+  log_write_die(LOG_CONFIG_IN,
     "missing rewrite replacement string");
 
 next->flags = 0;
@@ -1615,12 +1609,12 @@ while (*p) switch (*p++)
   case 'S':
   next->flags |= rewrite_smtp;
   if (next->key[0] != '^' && Ustrncmp(next->key, "\\N^", 3) != 0)
-    log_write_die(0, LOG_CONFIG_IN,
+    log_write_die(LOG_CONFIG_IN,
       "rewrite rule has the S flag but is not a regular expression");
   break;
 
   default:
-  log_write_die(0, LOG_CONFIG_IN,
+  log_write_die(LOG_CONFIG_IN,
     "unknown rewrite flag character '%c' "
     "(could be missing quotes round replacement item)", p[-1]);
   break;
@@ -1692,7 +1686,7 @@ ss = s;
 yield = string_dequote(&s);
 
 if (s == ss+1 || s[-1] != '\"')
-  log_write_die(0, LOG_CONFIG_IN,
+  log_write_die(LOG_CONFIG_IN,
     "missing quote at end of string value for %s", name);
 
 if (*s != 0) extra_chars_error(s, US"string value for ", name, US"");
@@ -1721,7 +1715,7 @@ else
   /* "smtp_receive_timeout",     opt_time,        &smtp_receive_timeout */
   smtp_receive_timeout = readconf_readtime(str, 0, FALSE);
   if (smtp_receive_timeout < 0)
-    log_write_die(0, LOG_CONFIG_IN, "invalid time value for %s",
+    log_write_die(LOG_CONFIG_IN, "invalid time value for %s",
       name);
   }
 }
@@ -1766,13 +1760,10 @@ Returns:        TRUE if an option was read successfully,
 */
 
 static BOOL
-readconf_handle_option(uschar *buffer, optionlist *oltop, int last,
-  void *data_block, uschar *unknown_txt)
+readconf_handle_option(uschar * buffer, optionlist * oltop, int last,
+  void * data_block, uschar * unknown_txt)
 {
-int ptr;
-int offset = 0;
-int count, type, value;
-int issecure = 0;
+int ptr, offset = 0, count, type, value, issecure = 0;
 uid_t uid;
 gid_t gid;
 BOOL boolvalue = TRUE;
@@ -1797,7 +1788,7 @@ ptr = 0;
 with a letter. */
 
 if (!isalpha( Uskip_whitespace(&s) ))
-  log_write_die(0, LOG_CONFIG_IN, "option setting expected: %s", s);
+  log_write_die(LOG_CONFIG_IN, "option setting expected: %s", s);
 
 /* Read the name of the option, and skip any subsequent white space. If
 it turns out that what we read was "hide", set the flag indicating that
@@ -1837,11 +1828,11 @@ is set twice, is a disaster. */
 if (!(ol = find_option(name + offset, oltop, last)))
   {
   if (!unknown_txt) return FALSE;
-  log_write_die(0, LOG_CONFIG_IN, CS unknown_txt, name);
+  log_write_die(LOG_CONFIG_IN, CS unknown_txt, name);
   }
 
 if ((ol->type & opt_set)  && !(ol->type & (opt_rep_con | opt_rep_str)))
-  log_write_die(0, LOG_CONFIG_IN,
+  log_write_die(LOG_CONFIG_IN,
     "%q option set for the second time", name);
 
 ol->type |= opt_set | issecure;
@@ -1853,13 +1844,13 @@ applies only to boolean values. */
 if (type < opt_bool || type > opt_bool_last)
   {
   if (offset != 0)
-    log_write_die(0, LOG_CONFIG_IN,
+    log_write_die(LOG_CONFIG_IN,
       "negation prefix applied to a non-boolean option");
   if (!*s)
-    log_write_die(0, LOG_CONFIG_IN,
+    log_write_die(LOG_CONFIG_IN,
       "unexpected end of line (data missing) after %s", name);
   if (*s != '=')
-    log_write_die(0, LOG_CONFIG_IN, "missing \"=\" after %s", name);
+    log_write_die(LOG_CONFIG_IN, "missing \"=\" after %s", name);
   }
 
 /* If a boolean wasn't preceded by "no[t]_" it can be followed by = and
@@ -2000,7 +1991,7 @@ switch (type)
 	  ol3 = find_option(name2, oltop, last);
 
 	  if (!ol2 || !ol3)
-	    log_write_die(0, LOG_CONFIG_IN,
+	    log_write_die(LOG_CONFIG_IN,
 	      "rewrite rules not available for driver");
 
 	  if (data_block)
@@ -2023,7 +2014,7 @@ switch (type)
 	    }
 
 	  if ((*flagptr & (rewrite_all_envelope | rewrite_smtp)) != 0)
-	    log_write_die(0, LOG_CONFIG_IN, "rewrite rule specifies a "
+	    log_write_die(LOG_CONFIG_IN, "rewrite rule specifies a "
 	      "non-header rewrite - not allowed at transport time -");
 	  }
 	break;
@@ -2058,7 +2049,7 @@ switch (type)
 
       case opt_uid:
 	if (!route_finduser(sptr, &pw, &uid))
-	  log_write_die(0, LOG_CONFIG_IN, "user %s was not found", sptr);
+	  log_write_die(LOG_CONFIG_IN, "user %s was not found", sptr);
 	if (data_block)
 	  *(uid_t *)(US data_block + ol->v.offset) = uid;
 	else
@@ -2119,7 +2110,7 @@ switch (type)
 
       case opt_gid:
 	if (!route_findgroup(sptr, &gid))
-	  log_write_die(0, LOG_CONFIG_IN, "group %s was not found", sptr);
+	  log_write_die(LOG_CONFIG_IN, "group %s was not found", sptr);
 	if (data_block)
 	  *((gid_t *)(US data_block + ol->v.offset)) = gid;
 	else
@@ -2141,7 +2132,7 @@ switch (type)
 	const uschar *op = expand_string (sptr);
 
 	if (op == NULL)
-	  log_write_die(0, LOG_CONFIG_IN, "failed to expand %s: %s",
+	  log_write_die(LOG_CONFIG_IN, "failed to expand %s: %s",
 	    name, expand_string_message);
 
 	p = op;
@@ -2162,7 +2153,7 @@ switch (type)
 	  /* If p is tainted we trap.  Not sure that can happen */
 	  (void)string_nextinlist(&p, &sep, big_buffer, BIG_BUFFER_SIZE);
 	  if (!route_finduser(big_buffer, NULL, &uid))
-	    log_write_die(0, LOG_CONFIG_IN, "user %s was not found",
+	    log_write_die(LOG_CONFIG_IN, "user %s was not found",
 	      big_buffer);
 	  list[ptr++] = uid;
 	  }
@@ -2183,7 +2174,7 @@ switch (type)
 	const uschar *op = expand_string (sptr);
 
 	if (!op)
-	  log_write_die(0, LOG_CONFIG_IN, "failed to expand %s: %s",
+	  log_write_die(LOG_CONFIG_IN, "failed to expand %s: %s",
 	    name, expand_string_message);
 
 	p = op;
@@ -2204,7 +2195,7 @@ switch (type)
 	  /* If p is tainted we trap.  Not sure that can happen */
 	  (void)string_nextinlist(&p, &sep, big_buffer, BIG_BUFFER_SIZE);
 	  if (!route_findgroup(big_buffer, &gid))
-	    log_write_die(0, LOG_CONFIG_IN, "group %s was not found",
+	    log_write_die(LOG_CONFIG_IN, "group %s was not found",
 	      big_buffer);
 	  list[ptr++] = gid;
 	  }
@@ -2257,7 +2248,7 @@ switch (type)
 	boolvalue = TRUE;
       else if (strcmpic(name2, US"false") == 0 || strcmpic(name2, US"no") == 0)
 	boolvalue = FALSE;
-      else log_write_die(0, LOG_CONFIG_IN,
+      else log_write_die(LOG_CONFIG_IN,
 	"%q is not a valid value for the %q option", name2, name);
       if (*s != 0) extra_chars_error(s, string_sprintf("%q ", name2),
 	US"for boolean option ", name);
@@ -2326,7 +2317,7 @@ switch (type)
       lvalue = strtol(CS s, CSS &endptr, intbase);
 
       if (endptr == s)
-	log_write_die(0, LOG_CONFIG_IN, "%sinteger expected for %s",
+	log_write_die(LOG_CONFIG_IN, "%sinteger expected for %s",
 	  inttype, name);
 
       if (errno != ERANGE && *endptr)
@@ -2350,7 +2341,7 @@ switch (type)
 	}
 
       if (errno == ERANGE || lvalue > INT_MAX || lvalue < INT_MIN)
-	log_write_die(0, LOG_CONFIG_IN,
+	log_write_die(LOG_CONFIG_IN,
 	  "absolute value of integer %q is too large (overflow)", s);
 
       if (Uskip_whitespace(&endptr))
@@ -2374,7 +2365,7 @@ switch (type)
     int_eximarith_t lvalue = strtol(CS s, CSS &endptr, intbase);
 
     if (endptr == s)
-      log_write_die(0, LOG_CONFIG_IN, "%sinteger expected for %s",
+      log_write_die(LOG_CONFIG_IN, "%sinteger expected for %s",
         inttype, name);
 
     if (errno != ERANGE && *endptr)
@@ -2398,7 +2389,7 @@ switch (type)
 	lvalue = (lvalue + 512)/1024;
       }
 
-    if (errno == ERANGE) log_write_die(0, LOG_CONFIG_IN,
+    if (errno == ERANGE) log_write_die(LOG_CONFIG_IN,
       "absolute value of integer %q is too large (overflow)", s);
 
     if (Uskip_whitespace(&endptr))
@@ -2415,15 +2406,15 @@ switch (type)
 
   case opt_fixed:
     if (sscanf(CS s, "%d%n", &value, &count) != 1)
-      log_write_die(0, LOG_CONFIG_IN,
+      log_write_die(LOG_CONFIG_IN,
 	"fixed-point number expected for %s", name);
 
-    if (value < 0) log_write_die(0, LOG_CONFIG_IN,
+    if (value < 0) log_write_die(LOG_CONFIG_IN,
       "integer %q is too large (overflow)", s);
 
     value *= 1000;
 
-    if (value < 0) log_write_die(0, LOG_CONFIG_IN,
+    if (value < 0) log_write_die(LOG_CONFIG_IN,
       "integer %q is too large (overflow)", s);
 
     /* We get a coverity error here for using count, as it derived
@@ -2457,7 +2448,7 @@ switch (type)
   case opt_time:
     value = readconf_readtime(s, 0, FALSE);
     if (value < 0)
-      log_write_die(0, LOG_CONFIG_IN, "invalid time value for %s",
+      log_write_die(LOG_CONFIG_IN, "invalid time value for %s",
 	name);
     if (data_block)
       *((int *)(US data_block + ol->v.offset)) = value;
@@ -2488,10 +2479,10 @@ switch (type)
         }
       value = readconf_readtime(s, terminator, FALSE);
       if (value < 0)
-        log_write_die(0, LOG_CONFIG_IN, "invalid time value for %s",
+        log_write_die(LOG_CONFIG_IN, "invalid time value for %s",
           name);
       if (count > 1 && value <= list[count])
-        log_write_die(0, LOG_CONFIG_IN,
+        log_write_die(LOG_CONFIG_IN,
           "time value out of order for %s", name);
       list[count+1] = value;
       if (snext == NULL) break;
@@ -2500,7 +2491,7 @@ switch (type)
       }
 
     if (count > list[0] - 2)
-      log_write_die(0, LOG_CONFIG_IN, "too many time values for %s",
+      log_write_die(LOG_CONFIG_IN, "too many time values for %s",
         name);
     if (count > 0 && list[2] == 0) count = 0;
     list[1] = count;
@@ -2511,12 +2502,25 @@ switch (type)
     ol->v.fn(name, s, 0);
     break;
 
-  case opt_module:
+  case opt_lookup_module:
     {
     uschar * errstr;
-    misc_module_info * mi = misc_mod_find(US ol->v.value, &errstr);
+    const lookup_info * li = lookup_find(US ol->v.value, &errstr);
+    if (!li)
+      log_write_die(LOG_CONFIG_IN,
+	"failed to find %s module for %s: %s", US ol->v.value, name, errstr);
+
+    oltop = li->options;
+    last = li->options_count;
+    goto sublist;
+    }
+
+  case opt_misc_module:
+    {
+    uschar * errstr;
+    const misc_module_info * mi = misc_mod_find(US ol->v.value, &errstr);
     if (!mi)
-      log_write_die(0, LOG_CONFIG_IN,
+      log_write_die(LOG_CONFIG_IN,
 	"failed to find %s module for %s: %s", US ol->v.value, name, errstr);
 
     oltop = mi->options;
@@ -3174,10 +3178,10 @@ if (Ustrncmp(s, "_cache", 6) == 0)
   }
 
 if (!isspace(*s))
-  log_write_die(0, LOG_CONFIG_IN, "unrecognized configuration line");
+  log_write_die(LOG_CONFIG_IN, "unrecognized configuration line");
 
 if (*numberp >= max)
- log_write_die(0, LOG_CONFIG_IN, "too many named %ss (max is %d)\n",
+ log_write_die(LOG_CONFIG_IN, "too many named %ss (max is %d)\n",
    tname, max);
 
 Uskip_whitespace(&s);
@@ -3189,7 +3193,7 @@ t->name[s-ss] = 0;
 Uskip_whitespace(&s);
 
 if (!tree_insertnode(anchorp, t))
-  log_write_die(0, LOG_CONFIG_IN,
+  log_write_die(LOG_CONFIG_IN,
     "duplicate name %q for a named %s", t->name, tname);
 
 t->data.ptr = nb;
@@ -3197,7 +3201,7 @@ nb->number = *numberp;
 *numberp += 1;
 nb->hide = hide;
 
-if (*s++ != '=') log_write_die(0, LOG_CONFIG_IN,
+if (*s++ != '=') log_write_die(LOG_CONFIG_IN,
   "missing '=' after %q", t->name);
 Uskip_whitespace(&s);
 nb->string = read_string(s, t->name);
@@ -3246,7 +3250,7 @@ if (sscanf(CS s, "%d, %15[0123456789smhdw.], %lf, %15s", threshold, bstring,
   *limit = readconf_readtime(lstring, 0, TRUE);
   if (*base >= 0 && *limit >= 0) return;
   }
-log_write_die(0, LOG_MAIN, "malformed ratelimit data: %s", s);
+log_write_die(LOG_MAIN, "malformed ratelimit data: %s", s);
 }
 
 
@@ -3392,10 +3396,10 @@ if (config_file)
   }
 else
   if (!filename)
-    log_write_die(0, LOG_MAIN, "non-existent configuration file(s): "
+    log_write_die(LOG_MAIN, "non-existent configuration file(s): "
       "%s", config_main_filelist);
   else
-    log_write_die(0, LOG_MAIN, "%s",
+    log_write_die(LOG_MAIN, "%s",
       string_open_failed("configuration file %s", filename));
 
 /* Now, once we found and opened our configuration file, we change the directory
@@ -3413,7 +3417,7 @@ privileges and the file isn't /dev/null (which *should* be 0666). */
 if (f.trusted_config && Ustrcmp(filename, US"/dev/null"))
   {
   if (fstat(fileno(config_file), &statbuf) != 0)
-    log_write_die(0, LOG_MAIN, "failed to stat configuration file %s",
+    log_write_die(LOG_MAIN, "failed to stat configuration file %s",
       big_buffer);
 
   if (    statbuf.st_uid != root_uid		/* owner not root */
@@ -3429,7 +3433,7 @@ if (f.trusted_config && Ustrcmp(filename, US"/dev/null"))
      ||						/* or */
        (statbuf.st_mode & 2) != 0		/* world writeable  */
      )
-    log_write_die(0, LOG_MAIN, "Exim configuration file %s has the "
+    log_write_die(LOG_MAIN, "Exim configuration file %s has the "
       "wrong owner, group, or mode", big_buffer);
 
   /* Do a dummy store-allocation of a size related to the (toplevel) file size.
@@ -3457,7 +3461,7 @@ while ((s = get_config_line()))
   uschar * t;
 
   if (config_lineno == 1 && Ustrstr(s, "\xef\xbb\xbf") == s)
-    log_write_die(0, LOG_CONFIG_IN,
+    log_write_die(LOG_CONFIG_IN,
       "found unexpected BOM (Byte Order Mark)");
 
   if (isupper(*s))
@@ -3493,7 +3497,7 @@ while ((s = get_config_line()))
 /* If local_sender_retain is set, local_from_check must be unset. */
 
 if (local_sender_retain && local_from_check)
-  log_write_die(0, LOG_MAIN, "both local_from_check and "
+  log_write_die(LOG_MAIN, "both local_from_check and "
     "local_sender_retain are set; this combination is not allowed");
 
 /* If the timezone string is empty, set it to NULL, implying no TZ variable
@@ -3526,7 +3530,7 @@ if (!primary_hostname)
   struct utsname uts;
 
   if (uname(&uts) < 0)
-    log_write_die(0, LOG_MAIN, "uname() failed to yield host name");
+    log_write_die(LOG_MAIN, "uname() failed to yield host name");
   hostname = US uts.nodename;
 
   if (Ustrchr(hostname, '.') == NULL)
@@ -3575,17 +3579,17 @@ got set above. Of course, writing to the log may not work if log_file_path is
 not set, but it will at least get to syslog or somewhere, with any luck. */
 
 if (!*spool_directory)
-  log_write_die(0, LOG_MAIN, "spool_directory undefined: cannot "
+  log_write_die(LOG_MAIN, "spool_directory undefined: cannot "
     "proceed");
 
 /* Expand the spool directory name; it may, for example, contain the primary
 host name. Same comment about failure. */
 
-DEBUG(D_any) if (Ustrchr(spool_directory, '$'))
+DEBUG(any) if (Ustrchr(spool_directory, '$'))
   debug_printf("Expanding spool_directory option\n");
 
 if (!(s = expand_string(spool_directory)))
-  log_write_die(0, LOG_MAIN, "failed to expand spool_directory "
+  log_write_die(LOG_MAIN, "failed to expand spool_directory "
     "%q: %s", spool_directory, expand_string_message);
 spool_directory = s;
 
@@ -3598,7 +3602,7 @@ if (*log_file_path)
   const uschar *ss, *sss;
   int sep = ':';                       /* Fixed for log file path */
   if (!(s = expand_string(log_file_path)))
-    log_write_die(0, LOG_MAIN, "failed to expand log_file_path "
+    log_write_die(LOG_MAIN, "failed to expand log_file_path "
       "%q: %s", log_file_path, expand_string_message);
 
   ss = s;
@@ -3608,12 +3612,12 @@ if (*log_file_path)
     uschar *t;
     if (sss[0] == 0 || Ustrcmp(sss, "syslog") == 0) continue;
     if (!(t = Ustrstr(sss, "%s")))
-      log_write_die(0, LOG_MAIN, "log_file_path %q does not "
+      log_write_die(LOG_MAIN, "log_file_path %q does not "
         "contain \"%%s\"", sss);
     *t = 'X';
     if ((t = Ustrchr(sss, '%')))
       if ((t[1] != 'D' && t[1] != 'M') || Ustrchr(t+2, '%') != NULL)
-        log_write_die(0, LOG_MAIN, "log_file_path %q contains "
+        log_write_die(LOG_MAIN, "log_file_path %q contains "
           "unexpected \"%%\" character", s);
     }
 
@@ -3641,7 +3645,7 @@ if (syslog_facility_str)
       }
 
   if (i >= syslog_list_size)
-    log_write_die(0, LOG_CONFIG,
+    log_write_die(LOG_CONFIG,
       "failed to interpret syslog_facility %q", syslog_facility_str);
   }
 
@@ -3651,7 +3655,7 @@ if (*pid_file_path)
   {
   const uschar * t = expand_string(pid_file_path);
   if (!t)
-    log_write_die(0, LOG_MAIN, "failed to expand pid_file_path "
+    log_write_die(LOG_MAIN, "failed to expand pid_file_path "
       "%q: %s", pid_file_path, expand_string_message);
   pid_file_path = t;
   }
@@ -3690,7 +3694,7 @@ if (system_filter_uid_set && !system_filter_gid_set)
   {
   struct passwd *pw = getpwuid(system_filter_uid);
   if (!pw)
-    log_write_die(0, LOG_MAIN, "Failed to look up uid %ld",
+    log_write_die(LOG_MAIN, "Failed to look up uid %ld",
       (long int)system_filter_uid);
   system_filter_gid = pw->pw_gid;
   system_filter_gid_set = TRUE;
@@ -3707,11 +3711,11 @@ if (errors_reply_to)
     &start, &end, &domain, FALSE);
 
   if (!recipient)
-    log_write_die(0, LOG_CONFIG,
+    log_write_die(LOG_CONFIG,
       "error in errors_reply_to (%s): %s", errors_reply_to, errmess);
 
   if (!domain)
-    log_write_die(0, LOG_CONFIG,
+    log_write_die(LOG_CONFIG,
       "errors_reply_to (%s) does not contain a domain", errors_reply_to);
   }
 
@@ -3719,7 +3723,7 @@ if (errors_reply_to)
 smtp_accept_max must also be set. */
 
 if (smtp_accept_max == 0 && (smtp_accept_queue > 0 || smtp_accept_max_per_host))
-  log_write_die(0, LOG_CONFIG,
+  log_write_die(LOG_CONFIG,
     "smtp_accept_max must be set if smtp_accept_queue or "
     "smtp_accept_max_per_host is set");
 
@@ -3734,15 +3738,15 @@ if (host_number_string)
   uschar *s = expand_string(host_number_string);
 
   if (!s)
-    log_write_die(0, LOG_MAIN,
+    log_write_die(LOG_MAIN,
         "failed to expand localhost_number %q: %s",
         host_number_string, expand_string_message);
   n = Ustrtol(s, &end, 0);
   if (Uskip_whitespace(&end))
-    log_write_die(0, LOG_CONFIG,
+    log_write_die(LOG_CONFIG,
       "localhost_number value is not a number: %s", s);
   if (n > LOCALHOST_MAX)
-    log_write_die(0, LOG_CONFIG,
+    log_write_die(LOG_CONFIG,
       "localhost_number is greater than the maximum allowed value (%d)",
         LOCALHOST_MAX);
   host_number = n;
@@ -3752,7 +3756,7 @@ if (host_number_string)
 /* If tls_verify_hosts is set, tls_verify_certificates must also be set */
 
 if ((tls_verify_hosts || tls_try_verify_hosts) && !tls_verify_certificates)
-  log_write_die(0, LOG_CONFIG,
+  log_write_die(LOG_CONFIG,
     "tls_%sverify_hosts is set, but tls_verify_certificates is not set",
     tls_verify_hosts ? "" : "try_");
 
@@ -3760,26 +3764,26 @@ if ((tls_verify_hosts || tls_try_verify_hosts) && !tls_verify_certificates)
 used by so many clients, and what Exim used to use always, that it makes
 sense to just min-clamp this max-clamp at that. */
 if (tls_dh_max_bits < 1024)
-  log_write_die(0, LOG_CONFIG,
+  log_write_die(LOG_CONFIG,
       "tls_dh_max_bits is too small, must be at least 1024 for interop");
 
 /* If openssl_options is set, validate it */
 if (openssl_options)
   {
 # ifdef USE_GNUTLS
-  log_write_die(0, LOG_CONFIG,
+  log_write_die(LOG_CONFIG,
     "openssl_options is set but we're using GnuTLS");
 # else
   long dummy;
   if (!tls_openssl_options_parse(openssl_options, &dummy))
-    log_write_die(0, LOG_CONFIG,
+    log_write_die(LOG_CONFIG,
       "openssl_options parse error: %s", openssl_options);
 # endif
   }
 #endif	/*DISABLE_TLS*/
 
 if (!nowarn && !keep_environment && environ && *environ)
-  log_write(0, LOG_MAIN,
+  log_write(LOG_MAIN,
       "Warning: purging the environment.\n"
       " Suggested action: use keep_environment.");
 }
@@ -3823,17 +3827,16 @@ for (di = *info_anchor; di; di = di->next)
 #ifdef LOOKUP_MODULE_DIR
 /* Potentially a loadable module. Look for a file with the right name. */
 
-if (!(dd = exim_opendir(CUS LOOKUP_MODULE_DIR)))
-  {
-  log_write(0, LOG_MAIN|LOG_PANIC,
-	    "Couldn't open %s: not loading driver modules\n", LOOKUP_MODULE_DIR);
-  }
+if (!(dd = open_module_dir()))
+  log_write(LOG_MAIN|LOG_PANIC,
+	  "Couldn't open %s: not loading driver modules\n", LOOKUP_MODULE_DIR);
 else
   {
-  uschar * fname = string_sprintf("%s_%s." DYNLIB_FN_EXT, d->driver_name, class), * sname;
+  uschar * fname = string_sprintf("%s_%s." DYNLIB_FN_EXT,
+				  d->driver_name, class);
   const char * errormsg;
 
-  DEBUG(D_any) debug_printf("Loading %s %s driver from %s\n",
+  DEBUG(any) debug_printf("Loading %q %s driver from %s\n",
 			    d->driver_name, class, LOOKUP_MODULE_DIR);
 
   for(struct dirent * ent; ent = readdir(dd); ) if (Ustrcmp(ent->d_name, fname) == 0)
@@ -3848,7 +3851,7 @@ else
     if (!dl)
       {
       errormsg = dlerror();
-      log_write(0, LOG_MAIN|LOG_PANIC, "Error loading %s %s driver: %s\n",
+      log_write(LOG_MAIN|LOG_PANIC, "Error loading %s %s driver: %s\n",
 		d->driver_name, class, errormsg);
       break;
       }
@@ -3857,8 +3860,8 @@ else
     di = (driver_info *) dlsym(dl, CS string_sprintf("_%s_info", class));
     if ((errormsg = dlerror()))
       {
-      log_write(0, LOG_MAIN|LOG_PANIC,
-		"%s does not appear to be a %s module (%s)\n", fname, class, errormsg);
+      log_write(LOG_MAIN|LOG_PANIC,
+	"%s does not appear to be a %s module (%s)\n", fname, class, errormsg);
       dlclose(dl);
       break;
       }
@@ -3869,22 +3872,21 @@ else
 	store_pool = POOL_PERM;
 	add_driver_info(info_anchor, di, size_of_info);
 	store_pool = old_pool;
-	DEBUG(D_any) debug_printf("Loaded %s %s\n", d->driver_name, class);
-	closedir(dd);
+	DEBUG(any)
+	  debug_printf("Loaded module %q (%s)\n", d->driver_name, class);
 	goto found;
 	}
 
-    log_write(0, LOG_MAIN|LOG_PANIC,
+    log_write(LOG_MAIN|LOG_PANIC,
 	      "%s module %s is not compatible with this version of Exim\n",
 	      class, d->driver_name);
     dlclose(dl);
     break;
     }
-  closedir(dd);
   }
 #endif	/* LOOKUP_MODULE_DIR */
 
-log_write_die(0, LOG_CONFIG_IN,
+log_write_die(LOG_CONFIG_IN,
   "%s %s: cannot find %s driver %q", class, d->name, class, d->driver_name);
 
 found:
@@ -3907,7 +3909,7 @@ driver_init_fini(driver_instance * d, const uschar * class)
 driver_info * di = d->info;
 
 if (!d->driver_name)
-  log_write_die(0, LOG_CONFIG,
+  log_write_die(LOG_CONFIG,
     "no driver defined for %s %q", class, d->name);
 (di->init)(d);
 }
@@ -3997,7 +3999,7 @@ while ((buffer = get_config_line()))
 
     for (d = *anchor; d; d = d->next)
       if (Ustrcmp(name, d->name) == 0)
-        log_write_die(0, LOG_CONFIG,
+        log_write_die(LOG_CONFIG,
           "there are two %ss called %q", class, name);
 
     /* Set up a new driver instance data block on the chain, with
@@ -4027,7 +4029,7 @@ while ((buffer = get_config_line()))
   current driver yet. */
 
   if (!d)
-    log_write_die(0, LOG_CONFIG_IN, "%s name missing", class);
+    log_write_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
@@ -4053,7 +4055,7 @@ while ((buffer = get_config_line()))
 
   /* The option is not generic and the driver name has not yet been given. */
 
-  else log_write_die(0, LOG_CONFIG_IN, "option %q unknown "
+  else log_write_die(LOG_CONFIG_IN, "option %q unknown "
     "(\"driver\" must be specified before any private options)", name);
   }
 
@@ -4097,13 +4099,13 @@ for (optionlist * ol = di->options; ol < di->options + count; ol++)
       {
       if (ss <= value || (ss[-1] != '$' && ss[-1] != '{') ||
 	isalnum(ss[Ustrlen(s)])) continue;
-      DEBUG(D_transport) debug_printf("driver %s: %q option depends on %s\n",
+      DEBUG(transport) debug_printf("driver %s: %q option depends on %s\n",
 	d->name, ol->name, s);
       return TRUE;
       }
     }
 
-DEBUG(D_transport) debug_printf("driver %s does not depend on %s\n", d->name, s);
+DEBUG(transport) debug_printf("driver %s does not depend on %s\n", d->name, s);
 return FALSE;
 }
 
@@ -4177,7 +4179,7 @@ else if (len == 7 && strncmpic(pp, US"timeout", len) == 0)
 
     if (i >= nelem(extras))
       if (strncmpic(x, US"DNS", xlen) == 0)
-        log_write(0, LOG_MAIN|LOG_PANIC, "\"timeout_dns\" is no longer "
+        log_write(LOG_MAIN|LOG_PANIC, "\"timeout_dns\" is no longer "
           "available in retry rules (it has never worked) - treated as "
           "\"timeout\"");
       else
@@ -4267,14 +4269,14 @@ retry_arg(const uschar ** paddr, int type)
 {
 const uschar * p = *paddr, * pp;
 
-if (*p++ != ',') log_write_die(0, LOG_CONFIG_IN, "comma expected");
+if (*p++ != ',') log_write_die(LOG_CONFIG_IN, "comma expected");
 
 Uskip_whitespace(&p);
 pp = p;
 while (isalnum(*p) || (type == 1 && *p == '.')) p++;
 
 if (*p && !isspace(*p) && *p != ',' && *p != ';')
-  log_write_die(0, LOG_CONFIG_IN, "comma or semicolon expected");
+  log_write_die(LOG_CONFIG_IN, "comma or semicolon expected");
 
 *paddr = p;
 switch (type)
@@ -4315,14 +4317,14 @@ while ((p = get_config_line()))
   Uskip_whitespace(&p);
   pp = p;
   while (mac_isgraph(*p)) p++;
-  if (p - pp <= 0) log_write_die(0, LOG_CONFIG_IN,
+  if (p - pp <= 0) log_write_die(LOG_CONFIG_IN,
     "missing error type in retry rule");
 
   /* Test error names for things we understand. */
 
   if ((error = readconf_retry_error(pp, p, &next->basic_errno,
        &next->more_errno)))
-    log_write_die(0, LOG_CONFIG_IN, "%s", error);
+    log_write_die(LOG_CONFIG_IN, "%s", error);
 
   /* There may be an optional address list of senders to be used as another
   constraint on the rule. This was added later, so the syntax is a bit of a
@@ -4334,7 +4336,7 @@ while ((p = get_config_line()))
     {
     p += 7;
     Uskip_whitespace(&p);
-    if (*p++ != '=') log_write_die(0, LOG_CONFIG_IN,
+    if (*p++ != '=') log_write_die(LOG_CONFIG_IN,
       "\"=\" expected after \"senders\" in retry rule");
     Uskip_whitespace(&p);
     next->senders = string_dequote(&p);
@@ -4368,13 +4370,13 @@ while ((p = get_config_line()))
 	break;
 
       default:
-	log_write_die(0, LOG_CONFIG_IN, "unknown retry rule letter");
+	log_write_die(LOG_CONFIG_IN, "unknown retry rule letter");
 	break;
       }
 
     if (rule->timeout <= 0 || rule->p1 <= 0 ||
           (rule->rule != 'F' && rule->p2 < 1000))
-      log_write_die(0, LOG_CONFIG_IN,
+      log_write_die(LOG_CONFIG_IN,
         "bad parameters for retry rule");
 
     if (Uskip_whitespace(&p) == ';')
@@ -4383,7 +4385,7 @@ while ((p = get_config_line()))
       Uskip_whitespace(&p);
       }
     else if (*p)
-      log_write_die(0, LOG_CONFIG_IN, "semicolon expected");
+      log_write_die(LOG_CONFIG_IN, "semicolon expected");
     }
   }
 }
@@ -4416,9 +4418,9 @@ store_pool = POOL_PERM;
   }
 store_pool = old_pool;
 
-/* Read the config file "authenticators" section, creating an auth instance list.
-For any yet-undiscovered driver, check for a loadable module and add it to
-those available. */
+/* Read the config file "authenticators" section, creating an auth instance
+list.  For any yet-undiscovered driver, check for a loadable module and add it
+to those available. */
 
 readconf_driver_init((driver_instance **)&auths,      /* chain anchor */
   (driver_info **)&auths_available,  /* available drivers */
@@ -4432,14 +4434,14 @@ readconf_driver_init((driver_instance **)&auths,      /* chain anchor */
 for (auth_instance * au = auths; au; au = au->drinst.next)
   {
   if (!au->public_name)
-    log_write_die(0, LOG_CONFIG, "no public name specified for "
+    log_write_die(LOG_CONFIG, "no public name specified for "
       "the %s authenticator", au->drinst.name);
 
   for (auth_instance * bu = au->drinst.next; bu; bu = bu->drinst.next)
     if (strcmpic(au->public_name, bu->public_name) == 0)
       if (  au->client && bu->client
 	 || au->server && bu->server)
-        log_write_die(0, LOG_CONFIG, "two %s authenticators "
+        log_write_die(LOG_CONFIG, "two %s authenticators "
           "(%s and %s) have the same public name (%s)",
           au->client && bu->client ? US"client" : US"server",
 	  au->drinst.name, bu->drinst.name, au->public_name);
@@ -4517,18 +4519,18 @@ while(acl_line)
     }
 
   if (*p != ':' || name[0] == 0)
-    log_write_die(0, LOG_CONFIG_IN, "missing or malformed ACL name");
+    log_write_die(LOG_CONFIG_IN, "missing or malformed ACL name");
 
   node = store_get_perm(sizeof(tree_node) + Ustrlen(name), name);
   Ustrcpy(node->name, name);
   if (!tree_insertnode(&acl_anchor, node))
-    log_write_die(0, LOG_CONFIG_IN,
+    log_write_die(LOG_CONFIG_IN,
       "there are two ACLs called %q", name);
 
   node->data.ptr = acl_read(acl_callback, &error);
 
   if (node->data.ptr == NULL && error != NULL)
-    log_write_die(0, LOG_CONFIG_IN, "error in ACL: %s", error);
+    log_write_die(LOG_CONFIG_IN, "error in ACL: %s", error);
   }
 }
 
@@ -4550,7 +4552,7 @@ static void
 local_scan_init(void)
 {
 #ifndef LOCAL_SCAN_HAS_OPTIONS
-log_write_die(0, LOG_CONFIG_IN, "local_scan() options not supported: "
+log_write_die(LOG_CONFIG_IN, "local_scan() options not supported: "
   "(LOCAL_SCAN_HAS_OPTIONS not defined in Local/Makefile)");
 #else
 
@@ -4597,7 +4599,7 @@ readconf_rest(void)
 {
 int had = 0;
 
-while(next_section[0] != 0)
+while(*next_section)
   {
   int bit;
   int first = 0;
@@ -4605,7 +4607,8 @@ while(next_section[0] != 0)
   int mid = last/2;
   int n = Ustrlen(next_section);
 
-  EARLY_DEBUG(D_any, "%s: %s\n", __FUNCTION__, next_section);
+  EARLY_DEBUG(start, "%s: %s\n", __FUNCTION__, next_section);
+  expand_level++;
   if (tolower(next_section[n-1]) != 's') Ustrcpy(next_section+n, US"s");
 
   for (;;)
@@ -4614,14 +4617,14 @@ while(next_section[0] != 0)
     if (c == 0) break;
     if (c > 0) first = mid + 1; else last = mid;
     if (first >= last)
-      log_write_die(0, LOG_CONFIG_IN,
+      log_write_die(LOG_CONFIG_IN,
         "\"%.*s\" is not a known configuration section name", n, next_section);
     mid = (last + first)/2;
     }
 
   bit = 1 << mid;
   if (((had ^= bit) & bit) == 0)
-    log_write_die(0, LOG_CONFIG_IN,
+    log_write_die(LOG_CONFIG_IN,
       "\"%.*s\" section is repeated in the configuration file", n,
         next_section);
 
@@ -4635,6 +4638,7 @@ while(next_section[0] != 0)
     case 5: route_init(); break;
     case 6: transport_init(); break;
     }
+  expand_level--;
   }
 
 (void)fclose(config_file);
diff --git a/src/src/receive.c b/src/src/receive.c
index 9d8bfa89c..ed8e1a2cf 100644
--- a/src/src/receive.c
+++ b/src/src/receive.c
@@ -16,7 +16,7 @@
 extern int dcc_ok;
 #endif
 
-#ifdef SUPPORT_DMARC
+#ifdef EXIM_HAVE_DMARC
 # include "miscmods/dmarc.h"
 #endif
 
@@ -49,17 +49,17 @@ uschar stdin_buf[4096];
 uschar * stdin_inptr = stdin_buf;
 uschar * stdin_inend = stdin_buf;
 
-static BOOL
-stdin_refill(void)
+BOOL
+stdin_refill(const in_processing * inp, unsigned lim)
 {
-size_t rc = fread(stdin_buf, 1, sizeof(stdin_buf), stdin);
+size_t rc = fread(stdin_buf, 1, MIN(sizeof(stdin_buf), lim), stdin);
 if (rc == 0)
   {
   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");
+    if (LOGGING(lost_incoming_connection))
+      log_write(LOG_MAIN, "timed out while reading local message");
     receive_bomb_out(US"data-timeout", NULL);   /* Does not return */
     }
   if (had_data_sigint)
@@ -68,7 +68,7 @@ if (rc == 0)
       {
       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",
+      log_write(LOG_MAIN, "%s received while reading local message",
 	had_data_sigint == SIGTERM ? "SIGTERM" : "SIGINT");
       }
     receive_bomb_out(US"signal-exit", NULL);    /* Does not return */
@@ -81,43 +81,71 @@ return TRUE;
 }
 
 int
-stdin_getc(unsigned lim)
+stdin_getc(const in_processing * inp, unsigned lim)
 {
 if (stdin_inptr >= stdin_inend)
-  if (!stdin_refill())
+  if (!stdin_refill(inp, lim))
       return EOF;
 return *stdin_inptr++;
 }
 
 
 BOOL
-stdin_hasc(void)
+stdin_hasc(const in_processing * inp)
 {
 return stdin_inptr < stdin_inend;
 }
 
+/* Get many bytes, refilling buffer if needed. Can return NULL on EOF/errror. */
+
+const uschar *
+stdin_getbuf(const in_processing * inp, unsigned * len)
+{
+unsigned size;
+const uschar * buf;
+
+if (!stdin_hasc(inp) && !stdin_refill(inp, *len))
+  { *len = 0; return NULL; }
+
+if ((size = stdin_inend - stdin_inptr) > *len) size = *len;
+buf = stdin_inptr;
+stdin_inptr += size;
+*len = size;
+return buf;
+}
+
 int
-stdin_ungetc(int c)
+stdin_ungetc(const in_processing * inp, int c)
 {
 if (stdin_inptr <= stdin_buf)
-  log_write_die(0, LOG_MAIN, "buffer underflow in stdin_ungetc");
+  log_write_die(LOG_MAIN, "buffer underflow in stdin_ungetc");
 
 *--stdin_inptr = c;
 return c;
 }
 
 int
-stdin_feof(void)
+stdin_feof(const in_processing * inp)
 {
-return stdin_hasc() ? FALSE : feof(stdin);
+return stdin_hasc(inp) ? FALSE : feof(stdin);
 }
 
 int
-stdin_ferror(void)
+stdin_ferror(const in_processing * inp)
 {
 return ferror(stdin);
 }
 
+in_processing stdin_template = {
+  .in_layer_name =	US"stdin",
+  .in_getc =	stdin_getc,
+  .in_hasc =	stdin_hasc,
+  .in_getbuf =	stdin_getbuf,
+  .in_ungetc =	stdin_ungetc,
+  .in_feof =	stdin_feof,
+  .in_ferror =	stdin_ferror
+};
+
 
 
 
@@ -245,7 +273,7 @@ if (STATVFS(CS path, &statbuf) != 0)
     }
   else
     {
-    log_write(0, LOG_MAIN|LOG_PANIC, "cannot accept message: failed to stat "
+    log_write(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);
@@ -296,7 +324,7 @@ if (check_spool_space > 0 || msg_size > 0 || check_spool_inodes > 0)
   {
   int_eximarith_t space = receive_statvfs(TRUE, &inodes);
 
-  DEBUG(D_receive)
+  DEBUG(receive)
     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);
@@ -304,7 +332,7 @@ if (check_spool_space > 0 || msg_size > 0 || check_spool_inodes > 0)
   if (  space >= 0 && space + msg_size / 1024 < check_spool_space
      || inodes >= 0 && inodes < check_spool_inodes)
     {
-    log_write(0, LOG_MAIN, "spool directory space check failed: space="
+    log_write(LOG_MAIN, "spool directory space check failed: space="
       PR_EXIM_ARITH " inodes=%d", space, inodes);
     return FALSE;
     }
@@ -314,7 +342,7 @@ if (check_log_space > 0 || check_log_inodes > 0)
   {
   int_eximarith_t space = receive_statvfs(FALSE, &inodes);
 
-  DEBUG(D_receive)
+  DEBUG(receive)
     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);
@@ -322,7 +350,7 @@ if (check_log_space > 0 || check_log_inodes > 0)
   if (  space >= 0 && space < check_log_space
      || inodes >= 0 && inodes < check_log_inodes)
     {
-    log_write(0, LOG_MAIN, "log directory space check failed: space=" PR_EXIM_ARITH
+    log_write(LOG_MAIN, "log directory space check failed: space=" PR_EXIM_ARITH
       " inodes=%d", space, inodes);
     return FALSE;
     }
@@ -509,13 +537,18 @@ format.
 Arguments:
   recipient   the next address to add to recipients_list
   pno         parent number for fixed aliases; -1 otherwise
+  dsn_flags   classes of notify
+  dsn_orcpt   original recipient
 
 Returns:      nothing
 */
 
 void
-receive_add_recipient(const uschar * recipient, int pno)
+receive_add_recipient(const uschar * recipient, int pno, int dsn_flags,
+  const uschar * dsn_orcpt)
 {
+recipient_item * rp;
+
 if (recipients_count >= recipients_list_max)
   {
   const recipient_item * oldlist = recipients_list;
@@ -523,7 +556,7 @@ if (recipients_count >= recipients_list_max)
 
   const int safe_recipients_limit = INT_MAX / 2 / sizeof(recipient_item);
   if (recipients_list_max < 0 || recipients_list_max >= safe_recipients_limit)
-    log_write_die(0, LOG_MAIN, "Too many recipients: %d", recipients_list_max);
+    log_write_die(LOG_MAIN, "Too many recipients: %d", recipients_list_max);
 
   recipients_list_max = recipients_list_max ? 2*recipients_list_max : 50;
   recipients_list = store_get(recipients_list_max * sizeof(recipient_item), GET_UNTAINTED);
@@ -531,16 +564,15 @@ if (recipients_count >= recipients_list_max)
     memcpy(recipients_list, oldlist, oldmax * sizeof(recipient_item));
   }
 
-recipients_list[recipients_count].address = recipient;
-recipients_list[recipients_count].pno = pno;
-#ifdef EXPERIMENTAL_BRIGHTMAIL
-recipients_list[recipients_count].bmi_optin = bmi_current_optin;
-/* reset optin string pointer for next recipient */
-bmi_current_optin = NULL;
-#endif
-recipients_list[recipients_count].orcpt = NULL;
-recipients_list[recipients_count].dsn_flags = 0;
-recipients_list[recipients_count++].errors_to = NULL;
+rp = recipients_list + recipients_count++;
+rp->address = recipient;
+rp->pno = pno;
+rp->orcpt = dsn_orcpt;
+rp->dsn_flags = dsn_flags;
+rp->errors_to = NULL;
+
+/* DEBUG(receive) debug_printf("DSN: orcpt: %s  flags: %d\n",
+  rp->orcpt, rp->dsn_flags); */
 }
 
 
@@ -591,7 +623,7 @@ Returns:      TRUE if it did remove something; FALSE otherwise
 BOOL
 receive_remove_recipient(const uschar * recipient)
 {
-DEBUG(D_receive) debug_printf("receive_remove_recipient(%q) called\n",
+DEBUG(receive) debug_printf("receive_remove_recipient(%q) called\n",
   recipient);
 for (int count = 0; count < recipients_count; count++)
   if (Ustrcmp(recipients_list[count].address, recipient) == 0)
@@ -679,7 +711,7 @@ if (!f.dot_ends)
   int last_ch = '\n';
 
   for ( ;
-       log_close_chk(), (ch = (receive_getc)(GETC_BUFFER_UNLIMITED)) >= 0;
+       log_close_chk(), (ch = receive_getc(GETC_BUFFER_UNLIMITED)) >= 0;
        last_ch = ch)
     {
     if (ch == 0) body_zerocount++;
@@ -722,7 +754,8 @@ if (!f.dot_ends)
 
 ch_state = 1;
 
-while (log_close_chk(), (ch = (receive_getc)(GETC_BUFFER_UNLIMITED)) >= 0)
+while (log_close_chk(),
+       (ch = receive_getc(GETC_BUFFER_UNLIMITED)) >= 0)
   {
   if (ch == 0) body_zerocount++;
   switch (ch_state)
@@ -821,7 +854,7 @@ SMTP, in which case the CRs are optional, but...
 
 FUDGE: It seems that sites on the net send out messages with just LF
 terminators, despite the warnings in the RFCs, and other MTAs handle this. So
-we make the CRs optional in all cases.
+we make the CRs optional in all cases [no longer; see below].
 
 July 2003: Bare CRs cause trouble. We now treat them as line terminators as
 well, so that there are no CRs in spooled messages. However, the message
@@ -846,7 +879,7 @@ enum { s_linestart, s_normal, s_had_cr, s_had_nl_dot, s_had_dot_cr } ch_state =
 	      s_linestart;
 int linelength = 0, ch;
 
-while ((ch = (receive_getc)(GETC_BUFFER_UNLIMITED)) >= 0)
+while ((ch = receive_getc(GETC_BUFFER_UNLIMITED)) >= 0)
   {
   if (ch == 0) body_zerocount++;
   switch (ch_state)
@@ -988,40 +1021,50 @@ BOOL fix_nl = FALSE;
 
 for(;;)
   {
-  switch ((ch = bdat_getc(GETC_BUFFER_UNLIMITED)))
+  switch ((ch = receive_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.
+      /* Nothing to get from the sender anymore according to the BDAT commands.
+      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.
+      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 (fgetc(fout) == '\n')		return END_DOT; /* good exit */
 	}
 
+      /* The last char in the file was not \n */
+
       if (linelength == -1)    /* \r already seen (see below) */
         {
-        DEBUG(D_receive) debug_printf("Add missing LF\n");
-        bdat_ungetc('\n');
-        continue;
+        DEBUG(receive) debug_printf("Add missing LF\n");
+	ch = '\n';
         }
-      DEBUG(D_receive) debug_printf("Add missing CRLF\n");
-      bdat_ungetc('\r');      /* not even \r was seen */
-      fix_nl = TRUE;
+      else
+	{				/* not even \r was seen */
+	DEBUG(receive) debug_printf("Add missing CRLF\n");
+	fix_nl = TRUE;
+	ch = '\r';
+	}
+
+      /* For an EOD return, the bdat layer was popped. But we're not done yet
+      because we have to unget, so re-push it. */
 
+      bdat_push_receive_functions();
+      receive_ungetc(ch);
       continue;
+
     case '\0':  body_zerocount++; break;
     }
   switch (ch_state)
@@ -1042,7 +1085,7 @@ for(;;)
       else if (ch == '\r')
 	{
 	ch_state = CR_SEEN;
-       if (fix_nl) bdat_ungetc('\n');
+	if (fix_nl) receive_ungetc('\n');
 	continue;			/* don't write CR */
 	}
       break;
@@ -1092,7 +1135,7 @@ int ch;
 
 /* Remember that this message uses wireformat. */
 
-DEBUG(D_receive) debug_printf("CHUNKING: %s\n",
+DEBUG(receive) debug_printf("CHUNKING: %s\n",
 	fout ? "writing spoolfile in wire format" : "flushing input");
 f.spool_file_wireformat = TRUE;
 
@@ -1101,13 +1144,13 @@ for (;;)
   if (chunking_data_left > 0)
     {
     unsigned len = MAX(chunking_data_left, thismessage_size_limit - message_size + 1);
-    const uschar * buf = bdat_getbuf(&len);
+    const uschar * buf = receive_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))
+  else switch (ch = receive_getc(GETC_BUFFER_UNLIMITED))
     {
     case EOF: return END_EOF;
     case EOD: return END_DOT;
@@ -1170,8 +1213,9 @@ Returns:   the SMTP response
 static uschar *
 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);
+if (LOGGING(lost_incoming_connection) || LOGGING(smtp_connection))
+  log_write(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";
 }
@@ -1202,7 +1246,7 @@ static void
 give_local_error(int errcode, uschar *text1, uschar *text2, int error_rc,
   FILE *f, header_line *hptr)
 {
-DEBUG(D_all) debug_printf("%s%s\n", text2, text1);
+DEBUG(all) debug_printf("%s%s\n", text2, text1);
 
 if (error_handling == ERRORS_SENDER)
   {
@@ -1256,7 +1300,7 @@ switch(where)
     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"
+    log_write(LOG_MAIN|LOG_PANIC, "Header modification in data ACLs"
 			" will not take effect on cutthrough deliveries");
     return;
     }
@@ -1264,7 +1308,7 @@ switch(where)
 
 if (acl_removed_headers)
   {
-  DEBUG(D_receive|D_acl) debug_printf_indent(">>Headers removed by %s ACL:\n", acl_name);
+  DEBUG(receive|acl) debug_printf_indent(">>Headers removed by %s ACL:\n", acl_name);
 
   for (header_line * h = header_list; h; h = h->next) if (h->type != htype_old)
     {
@@ -1283,15 +1327,15 @@ if (acl_removed_headers)
 	 )
 	{
 	h->type = htype_old;
-	DEBUG(D_receive|D_acl) debug_printf_indent("  %s", h->text);
+	DEBUG(receive|acl) debug_printf_indent("  %s", h->text);
 	}
     }
   acl_removed_headers = NULL;
-  DEBUG(D_receive|D_acl) debug_printf_indent(">>\n");
+  DEBUG(receive|acl) debug_printf_indent(">>\n");
   }
 
 if (!acl_added_headers) return;
-DEBUG(D_receive|D_acl) debug_printf_indent(">>Headers added by %s ACL:\n", acl_name);
+DEBUG(receive|acl) debug_printf_indent(">>Headers added by %s ACL:\n", acl_name);
 
 for (header_line * h = acl_added_headers, * next; h; h = next)
   {
@@ -1302,7 +1346,7 @@ for (header_line * h = acl_added_headers, * next; h; h = next)
     case htype_add_top:
       h->next = header_list;
       header_list = h;
-      DEBUG(D_receive|D_acl) debug_printf_indent("  (at top)");
+      DEBUG(receive|acl) debug_printf_indent("  (at top)");
       break;
 
     case htype_add_rec:
@@ -1317,7 +1361,7 @@ for (header_line * h = acl_added_headers, * next; h; h = next)
 	}
       h->next = last_received->next;
       last_received->next = h;
-      DEBUG(D_receive|D_acl) debug_printf_indent("  (after Received:)");
+      DEBUG(receive|acl) debug_printf_indent("  (after Received:)");
       break;
 
     case htype_add_rfc:
@@ -1332,13 +1376,13 @@ for (header_line * h = acl_added_headers, * next; h; h = next)
 	 of all headers. Our current header must follow it. */
       h->next = last_received->next;
       last_received->next = h;
-      DEBUG(D_receive|D_acl) debug_printf_indent("  (before any non-Received: or Resent-*: header)");
+      DEBUG(receive|acl) debug_printf_indent("  (before any non-Received: or Resent-*: header)");
       break;
 
     default:
       h->next = NULL;
       header_last->next = h;
-      DEBUG(D_receive|D_acl) debug_printf_indent("  ");
+      DEBUG(receive|acl) debug_printf_indent("  ");
       break;
     }
 
@@ -1353,11 +1397,11 @@ for (header_line * h = acl_added_headers, * next; h; h = next)
   h->type = header_checkname(h, FALSE);
   if (h->type >= 'a') h->type = htype_other;
 
-  DEBUG(D_receive|D_acl) debug_printf("%s", h->text);
+  DEBUG(receive|acl) debug_printf("%s", h->text);
   }
 
 acl_added_headers = NULL;
-DEBUG(D_receive|D_acl) debug_printf_indent(">>\n");
+DEBUG(receive|acl) debug_printf_indent(">>\n");
 }
 
 
@@ -1366,6 +1410,36 @@ DEBUG(D_receive|D_acl) debug_printf_indent(">>\n");
 *       Add host information for log line        *
 *************************************************/
 
+gstring *
+add_spf_info_for_log(gstring * g)
+{
+#ifdef EXIM_HAVE_SPF
+if (LOGGING(spf_verbose))
+  {
+  const uschar * s = expand_string(CUS"$spf_result");
+  if (*s) g = string_append(g, 2, US" SPF=", s);
+  }
+else if (LOGGING(spf) && Ustrcmp(expand_string(CUS"$spf_result"), "pass") == 0)
+  g = string_catn(g, US" SPF", 4);
+#endif
+return g;
+}
+
+gstring *
+add_dmarc_info_for_log(gstring * g)
+{
+#ifdef EXIM_HAVE_DMARC
+if (LOGGING(dmarc_verbose))
+  {
+  const uschar * s = expand_string(CUS"$dmarc_status");
+  if (*s) g = string_append(g, 2, US" DMARC=", s);
+  }
+else if (LOGGING(dmarc) && Ustrcmp(expand_string(CUS"$dmarc_status"), "accept") == 0)
+  g = string_catn(g, US" DMARC", 6);
+#endif
+return g;
+}
+
 /* Called for acceptance and rejecting log lines. This adds information about
 the calling host to a string that is being built dynamically.
 
@@ -1412,6 +1486,8 @@ if (LOGGING(pipelining) && f.smtp_in_pipelining_advertised)
   if (!f.smtp_in_pipelining_used)
     g = string_catn(g, US"-", 1);
   }
+
+g = add_spf_info_for_log(g);
 return g;
 }
 
@@ -1455,11 +1531,11 @@ for (header_line * my_headerlist = header_list; my_headerlist;
      && strncmpic(my_headerlist->text, US"Content-Type:", 13) == 0
      )
     {
-    DEBUG(D_receive) debug_printf("Found Content-Type: header - executing acl_smtp_mime.\n");
+    DEBUG(receive) debug_printf("Found Content-Type: header - executing acl_smtp_mime.\n");
     goto DO_MIME_ACL;
     }
 
-DEBUG(D_receive) debug_printf("No Content-Type: header - presumably not a MIME message.\n");
+DEBUG(receive) debug_printf("No Content-Type: header - presumably not a MIME message.\n");
 return TRUE;
 
 DO_MIME_ACL:
@@ -1467,7 +1543,7 @@ DO_MIME_ACL:
 /* make sure the eml mbox file is spooled up */
 if (!(mbox_file = spool_mbox(&mbox_size, NULL, &mbox_filename)))
   {								/* error while spooling */
-  log_write(0, LOG_MAIN|LOG_PANIC,
+  log_write(LOG_MAIN|LOG_PANIC,
          "acl_smtp_mime: error while creating mbox spool file, message temporarily rejected.");
   Uunlink(spool_name);
   unspool_mbox();
@@ -1484,7 +1560,7 @@ mime_is_rfc822 = 0;
 
 MIME_ACL_CHECK:
 mime_part_count = -1;
-rc = mime_acl_check(acl, mbox_file, NULL, &user_msg, &log_msg);
+rc = mime_acl_check(acl, mbox_file, NULL, &user_msg, &log_msg, 0);
 (void)fclose(mbox_file);
 
 if (rfc822_file_path)
@@ -1493,7 +1569,7 @@ if (rfc822_file_path)
 
   if (unlink(CS rfc822_file_path) == -1)
     {
-    log_write(0, LOG_PANIC,
+    log_write(LOG_PANIC,
          "acl_smtp_mime: can't unlink RFC822 spool file, skipping.");
     goto END_MIME_ACL;
     }
@@ -1512,7 +1588,7 @@ if (rc == OK)
     if (strncmpic(US entry->d_name, US"__rfc822_", 9) == 0)
       {
       rfc822_file_path = string_sprintf("%s/%s", scandir, entry->d_name);
-      DEBUG(D_receive)
+      DEBUG(receive)
 	debug_printf("RFC822 attachment detected: running MIME ACL for '%s'\n",
 	  rfc822_file_path);
       break;
@@ -1528,7 +1604,7 @@ if (rc == OK)
       mime_part_count_buffer = mime_part_count;
       goto MIME_ACL_CHECK;
       }
-    log_write(0, LOG_PANIC,
+    log_write(LOG_PANIC,
        "acl_smtp_mime: can't open RFC822 spool file, skipping.");
     unlink(CS rfc822_file_path);
     }
@@ -1583,7 +1659,7 @@ if (!received)
   {
   if(spool_name[0] != 0)
     Uunlink(spool_name);           /* Lose the data file */
-  log_write_die(0, LOG_MAIN, "Expansion of %q "
+  log_write_die(LOG_MAIN, "Expansion of %q "
     "(received_header_text) failed: %s", string_printing(received_header_text),
       expand_string_message);
   }
@@ -1606,7 +1682,7 @@ else
 
 received_header->slen = Ustrlen(received_header->text);
 
-DEBUG(D_receive) debug_printf(">>Generated Received: header line\n%c %s",
+DEBUG(receive) debug_printf(">>Generated Received: header line\n%c %s",
   received_header->type, received_header->text);
 }
 
@@ -1693,6 +1769,7 @@ terminated by CRLF is treated in the same way as a bare CR.
 Arguments:
   extract_recip  TRUE if recipients are to be extracted from the message's
                    headers
+  rcpt_dsn_flags notify classes, used only if extract_recip
 
 Returns:  TRUE   there are more messages to be read (SMTP input)
           FALSE  there are no more messages to be read (non-SMTP input
@@ -1703,49 +1780,38 @@ whether the headers (which is all that is read) were terminated by '.' or
 not. */
 
 BOOL
-receive_msg(BOOL extract_recip)
+receive_msg(BOOL extract_recip, int rcpt_dsn_flags)
 {
-int  rc = FAIL;
-int  msg_size = 0;
-int  process_info_len = Ustrlen(process_info);
+int  rc = FAIL, msg_size = 0, process_info_len = Ustrlen(process_info);
+int  header_size = 256, had_zero = 0, prevlines_length = 0, ptr = 0;
+
 int  error_rc = error_handling == ERRORS_SENDER
 	? errors_sender_rc : EXIT_FAILURE;
-int  header_size = 256;
-int  had_zero = 0;
-int  prevlines_length = 0;
 const int id_resolution = BASE_62 == 62 && !host_number_string ? 1
   : BASE_62 != 62 && host_number_string ? 4
   : 2;
 
-int ptr = 0;
-
-BOOL contains_resent_headers = FALSE;
-BOOL extracted_ignored = FALSE;
-BOOL first_line_ended_crlf = TRUE_UNSET;
-BOOL smtp_yield = TRUE;
-BOOL yield = FALSE;
+BOOL contains_resent_headers = FALSE, extracted_ignored = FALSE;
+BOOL first_line_ended_crlf = TRUE_UNSET, smtp_yield = TRUE, yield = FALSE;
 
 BOOL resents_exist = FALSE;
-uschar *resent_prefix = US"";
-uschar *blackholed_by = NULL;
-uschar *blackhole_log_msg = US"";
+uschar * resent_prefix = US"", * blackholed_by = NULL;
+uschar * blackhole_log_msg = US"";
 enum {NOT_TRIED, TMP_REJ, PERM_REJ, ACCEPTED} cutthrough_done = NOT_TRIED;
 
 flock_t lock_data;
-error_block *bad_addresses = NULL;
+error_block * bad_addresses = NULL;
 
-uschar *frozen_by = NULL;
-uschar *queued_by = NULL;
+uschar * frozen_by = NULL, * queued_by = NULL;
 
-uschar *errmsg;
+uschar * errmsg;
 rmark rcvd_log_reset_point;
 gstring * g;
 struct stat statbuf;
 
 /* Final message to give to SMTP caller, and messages from ACLs */
 
-uschar *smtp_reply = NULL;
-uschar *user_msg, *log_msg;
+uschar * smtp_reply = NULL,  * user_msg, * log_msg;
 
 /* Working header pointers */
 
@@ -1759,9 +1825,6 @@ BOOL date_header_exists = FALSE;
 /* Pointers to receive the addresses of headers whose contents we need. */
 
 header_line * from_header = NULL;
-#ifdef SUPPORT_DMARC
-header_line * dmarc_from_header = NULL;
-#endif
 header_line * subject_header = NULL, * msgid_header = NULL, * received_header;
 BOOL msgid_header_newly_created = FALSE;
 
@@ -1784,6 +1847,7 @@ search_tidyup();
 /* Extracting the recipient list from an input file is incompatible with
 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(TRUE, US"not smtp input");
 
@@ -1791,7 +1855,8 @@ if (extract_recip || !smtp_input)
 header. Temporarily mark it as "old", i.e. not to be used. We keep header_last
 pointing to the end of the chain to make adding headers simple. */
 
-received_header = header_list = header_last = store_get(sizeof(header_line), GET_UNTAINTED);
+received_header = header_list = header_last =
+  store_get(sizeof(header_line), GET_UNTAINTED);
 header_list->next = NULL;
 header_list->type = htype_old;
 header_list->text = NULL;
@@ -1818,11 +1883,7 @@ received_count = 1;            /* For the one we will add */
 
 if (thismessage_size_limit <= 0) thismessage_size_limit = INT_MAX;
 
-/* While reading the message, the following counts are computed. */
-
-message_linecount = body_linecount = body_zerocount =
-  max_received_linelength = 0;
-
+header_from = NULL;
 #ifdef WITH_CONTENT_SCAN
 /* reset non-per-part mime variables */
 mime_is_coverletter    = 0;
@@ -1833,6 +1894,11 @@ mime_part_count        = -1;
 if (misc_mod_msg_init() != OK)
   goto CONN_GONE;
 
+/* While reading the message, the following counts are computed. */
+
+message_linecount = body_linecount = body_zerocount =
+  max_received_linelength = 0;
+
 /* In SMTP sessions we may receive several messages in one connection. Before
 each subsequent one, we wait for the clock to tick at the level of message-id
 granularity.
@@ -1907,7 +1973,7 @@ next->text. */
 
 for (;;)
   {
-  int ch = (receive_getc)(GETC_BUFFER_UNLIMITED);
+  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. */
@@ -1991,7 +2057,7 @@ for (;;)
   if (f.dot_ends && ptr == 0 && ch == '.')
     {
     /* leading dot while in headers-read mode */
-    if ((ch = (receive_getc)(GETC_BUFFER_UNLIMITED)) < 0)
+    if ((ch = receive_getc(GETC_BUFFER_UNLIMITED)) < 0)
       goto CONN_GONE;
     if (ch == '\n' && first_line_ended_crlf == TRUE /* and not TRUE_UNSET */ )
     		/* dot, LF  but we are in CRLF mode.  Attack? */
@@ -1999,7 +2065,7 @@ for (;;)
 
     else if (ch == '\r')
       {
-      if ((ch = (receive_getc)(GETC_BUFFER_UNLIMITED)) < 0)
+      if ((ch = receive_getc(GETC_BUFFER_UNLIMITED)) < 0)
 	goto CONN_GONE;
       if (ch != '\n')
         {
@@ -2031,7 +2097,7 @@ for (;;)
 
   if (ch == '\r')
     {
-    if ((ch = (receive_getc)(GETC_BUFFER_UNLIMITED)) < 0)
+    if ((ch = receive_getc(GETC_BUFFER_UNLIMITED)) < 0)
       goto CONN_GONE;
     if (ch == '\n')
       {
@@ -2043,7 +2109,7 @@ for (;;)
     /* Otherwise, put back the character after CR, and turn the bare CR
     into LF SP. */
 
-    if (ch >= 0) (receive_ungetc)(ch);
+    if (ch >= 0) receive_ungetc(ch);
     next->text[ptr++] = '\n';
     message_size++;
     ch = ' ';
@@ -2069,7 +2135,7 @@ OVERSIZE:
     header_last->next = next;
     header_last = next;
 
-    log_write(0, LOG_MAIN, "ridiculously long message header received from "
+    log_write(LOG_MAIN, "ridiculously long message header received from "
       "%s (more than %d characters): message abandoned",
       f.sender_host_unknown ? sender_ident : sender_fullhost, header_maxsize);
 
@@ -2129,7 +2195,7 @@ OVERSIZE:
 
   if (ch >= 0)
     {
-    int nextch = (receive_getc)(GETC_BUFFER_UNLIMITED);
+    int nextch = receive_getc(GETC_BUFFER_UNLIMITED);
     if (nextch == ' ' || nextch == '\t')
       {
       next->text[ptr++] = nextch;
@@ -2138,7 +2204,7 @@ OVERSIZE:
       continue;                      /* Iterate the loop */
       }
     else if (nextch >= 0)	/* not EOF, ERR etc */
-      (receive_ungetc)(nextch);   /* For next time */
+      receive_ungetc(nextch);		/* For next time */
     else ch = nextch;                   /* Cause main loop to exit at end */
     }
 
@@ -2146,7 +2212,7 @@ OVERSIZE:
   beyond it. If it turns out to be a real header, internal binary zeros will
   be squashed later. */
 
-  next->text[ptr] = 0;
+  next->text[ptr] = '\0';
   next->slen = ptr;
   store_release_above(next->text + ptr + 1);
 
@@ -2202,7 +2268,7 @@ OVERSIZE:
       const uschar * uucp_sender;
       GET_OPTION("uucp_from_sender");
       if (!(uucp_sender = expand_string(uucp_from_sender)))
-        log_write(0, LOG_MAIN|LOG_PANIC,
+        log_write(LOG_MAIN|LOG_PANIC,
           "expansion of %q failed after matching "
           "\"From \" line: %s", uucp_from_sender, expand_string_message);
       else
@@ -2249,7 +2315,7 @@ OVERSIZE:
 
     if (isspace(*p))
       {
-      DEBUG(D_receive) debug_printf("WARNING: bad header line "
+      DEBUG(receive) debug_printf("WARNING: bad header line "
 	      " (starts with whitespace).  Assuming first line of body\n");
 #ifndef DISABLE_DKIM
       f.dkim_disable_verify = TRUE;	/* This could be a DKIM-bypass attack */
@@ -2259,7 +2325,7 @@ OVERSIZE:
     while (mac_isgraph(*p) && *p != ':') p++;
     if (Uskip_whitespace(&p) != ':')
       {
-      DEBUG(D_receive) debug_printf("WARNING: bad header line"
+      DEBUG(receive) debug_printf("WARNING: bad header line"
 	      " (no colon).  Assuming first line of body\n");
 #ifndef DISABLE_DKIM
       f.dkim_disable_verify = TRUE;
@@ -2307,7 +2373,7 @@ OVERSIZE:
 
     if (header_line_maxsize > 0 && next->slen > header_line_maxsize)
       {
-      log_write(0, LOG_MAIN, "overlong message header line received from "
+      log_write(LOG_MAIN, "overlong message header line received from "
         "%s (more than %d characters): message abandoned",
         f.sender_host_unknown ? sender_ident : sender_fullhost,
         header_line_maxsize);
@@ -2340,13 +2406,14 @@ OVERSIZE:
 
   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"");
+    if (LOGGING(size_reject))
+      log_write(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", SP_NO_MORE);
-    bdat_flush_data();
+    bdat_flush_data(rx_prc);
     smtp_reply = US"";
     goto TIDYUP;                             /* Skip to end of function */
     }
@@ -2379,7 +2446,7 @@ we are going to generate a bit later on. If next != NULL, it contains the first
 data line - which terminated the headers before reaching a blank line (not the
 normal case). */
 
-DEBUG(D_receive)
+DEBUG(receive)
   {
   debug_printf(">>Headers received:\n");
   acl_level++;
@@ -2396,7 +2463,7 @@ skipped if already at EOF.
 In CHUNKING mode, a protocol error makes us give up on the message. */
 
 if (smtp_input)
-  if ((receive_feof)())
+  if (receive_feof())
     {
     smtp_reply = handle_lost_connection(US" (after header)");
     smtp_yield = FALSE;
@@ -2459,9 +2526,7 @@ for (header_line * h = header_list->next; h; h = h->next)
 
     case htype_from:
       h->type = htype_from;
-#ifdef SUPPORT_DMARC
-      if (!is_resent) dmarc_from_header = h;
-#endif
+      if (!is_resent) header_from = h->text;
       if (!resents_exist || is_resent)
 	{
 	from_header = h;
@@ -2480,7 +2545,7 @@ for (header_line * h = header_list->next; h; h = h->next)
 	      originator_login, qualify_domain_sender);
 	    from_header = header_last;
 	    h->type = htype_old;
-	    DEBUG(D_receive|D_rewrite)
+	    DEBUG(receive|rewrite)
 	      debug_printf("rewrote \"%s:\" header using gecos\n", name);
 	   }
 	  }
@@ -2710,12 +2775,10 @@ if (extract_recip)
         no recipients left. */
 
         else if (recipient)
-          {
           if (tree_search(tree_nonrecipients, recipient) == NULL)
-            receive_add_recipient(recipient, -1);
+            receive_add_recipient(recipient, -1, rcpt_dsn_flags, NULL);
           else
             extracted_ignored = TRUE;
-          }
 
         /* Move on past this address */
 
@@ -2843,7 +2906,7 @@ if (  !msgid_header
     if (!new_id_domain)
       {
       if (!f.expand_string_forcedfail)
-        log_write(0, LOG_MAIN|LOG_PANIC,
+        log_write(LOG_MAIN|LOG_PANIC,
           "expansion of %q (message_id_header_domain) "
           "failed: %s", message_id_domain, expand_string_message);
       }
@@ -2867,7 +2930,7 @@ if (  !msgid_header
     if (!new_id_text)
       {
       if (!f.expand_string_forcedfail)
-        log_write(0, LOG_MAIN|LOG_PANIC,
+        log_write(LOG_MAIN|LOG_PANIC,
           "expansion of %q (message_id_header_text) "
           "failed: %s", message_id_text, expand_string_message);
       }
@@ -2913,13 +2976,13 @@ if (LOGGING(received_recipients))
 recipients will get here only if the conditions were right (allow_unqualified_
 recipient is TRUE). */
 
-DEBUG(D_rewrite)
+DEBUG(rewrite)
   { debug_printf_indent("qualify & rewrite recipients list\n"); acl_level++; }
 for (int i = 0; i < recipients_count; i++)
   recipients_list[i].address =	/* deconst ok as src was not cont */
     US rewrite_address(recipients_list[i].address, TRUE, TRUE,
       global_rewrite_rules, rewrite_existflags);
-DEBUG(D_rewrite) acl_level--;
+DEBUG(rewrite) acl_level--;
 
 /* If there is no From: header, generate one for local (without
 suppress_local_fixups) or submission_mode messages. If there is no sender
@@ -3081,8 +3144,9 @@ if (  from_header
     if (!sender_address_unrewritten)
       sender_address_unrewritten = sender_address;
     sender_address = generated_sender_address;
-    if (Ustrcmp(sender_address_unrewritten, generated_sender_address) != 0)
-      log_write(L_address_rewrite, LOG_MAIN,
+    if (  LOGGING(address_rewrite)
+       && (Ustrcmp(sender_address_unrewritten, generated_sender_address) != 0))
+      log_write(LOG_MAIN,
         "%q from env-from rewritten as %q by submission mode",
         sender_address_unrewritten, generated_sender_address);
     }
@@ -3091,17 +3155,17 @@ if (  from_header
 /* 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. */
 
-DEBUG(D_rewrite)
+DEBUG(rewrite)
   { debug_printf("rewrite rules on sender address\n"); acl_level++; }
 if (global_rewrite_rules && !sender_address_unrewritten && *sender_address)
   {
   /* deconst ok as src was not const */
   sender_address = US rewrite_address(sender_address, FALSE, TRUE,
     global_rewrite_rules, rewrite_existflags);
-  DEBUG(D_receive|D_rewrite)
+  DEBUG(receive|rewrite)
     debug_printf("rewritten sender = %s\n", sender_address);
   }
-DEBUG(D_rewrite) acl_level--;
+DEBUG(rewrite) acl_level--;
 
 
 /* The headers must be run through rewrite_header(), because it ensures that
@@ -3118,13 +3182,13 @@ 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. */
 
-DEBUG(D_rewrite)
+DEBUG(rewrite)
   { debug_printf("qualify and rewrite headers\n"); acl_level++; }
 for (header_line * h = header_list->next, * newh; h; h = h->next)
   if ((newh = rewrite_header(h, NULL, NULL, global_rewrite_rules,
 			      rewrite_existflags, TRUE)))
     h = newh;
-DEBUG(D_rewrite) acl_level--;
+DEBUG(rewrite) acl_level--;
 
 
 /* An RFC 822 (sic) message is not legal unless it has at least one of "to",
@@ -3155,7 +3219,7 @@ search_tidyup();    /* Free any cached resources */
 /* Show the complete set of headers if debugging. Note that the first one (the
 new Received:) has not yet been set. */
 
-DEBUG(D_receive)
+DEBUG(receive)
   {
   debug_printf(">>Headers after rewriting and local additions:\n");
   acl_level++;
@@ -3194,7 +3258,7 @@ if (cutthrough.cctx.sock >= 0 && cutthrough.delivery)
     {
     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: "
+    log_write(LOG_MAIN|LOG_REJECT, "rejected from <%s>%s%s%s%s: "
       "Too many \"Received\" headers",
       sender_address,
       sender_fullhost ? "H=" : "", sender_fullhost ? sender_fullhost : US"",
@@ -3213,7 +3277,7 @@ to access it both via a file descriptor and a stdio stream. Try to make the
 directory if it isn't there. */
 
 spool_name = spool_fname(US"input", message_subdir, message_id, US"-D");
-DEBUG(D_receive) debug_printf("Data file name: %s\n", spool_name);
+DEBUG(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)
   {
@@ -3225,7 +3289,7 @@ if ((data_fd = Uopen(spool_name, O_RDWR|O_CREAT|O_EXCL, SPOOL_MODE)) < 0)
     data_fd = Uopen(spool_name, O_RDWR|O_CREAT|O_EXCL, SPOOL_MODE);
     }
   if (data_fd < 0)
-    log_write_die(0, LOG_MAIN, "Failed to create spool file %s: %s",
+    log_write_die(LOG_MAIN, "Failed to create spool file %s: %s",
       spool_name, strerror(errno));
   }
 
@@ -3233,7 +3297,7 @@ if ((data_fd = Uopen(spool_name, O_RDWR|O_CREAT|O_EXCL, SPOOL_MODE)) < 0)
 because the group setting doesn't always get set automatically. */
 
 if (0 != exim_fchown(data_fd, exim_uid, exim_gid, spool_name))
-  log_write_die(0, LOG_MAIN,
+  log_write_die(LOG_MAIN,
     "Failed setting ownership on spool file %s: %s",
     spool_name, strerror(errno));
 (void)fchmod(data_fd, SPOOL_MODE);
@@ -3250,7 +3314,7 @@ lock_data.l_start = 0;
 lock_data.l_len = spool_data_start_offset(message_id);
 
 if (fcntl(data_fd, F_SETLK, &lock_data) < 0)
-  log_write_die(0, LOG_MAIN, "Cannot lock %s (%d): %s", spool_name,
+  log_write_die(LOG_MAIN, "Cannot lock %s (%d): %s", spool_name,
     errno, strerror(errno));
 
 /* We have an open, locked data file. Write the message id to it to make it
@@ -3273,7 +3337,7 @@ if (next)
 (indicated by '.'), or might have encountered an error while writing the
 message id or "next" line. */
 
-if (!ferror(spool_data_file) && !(receive_feof)() && message_ended != END_DOT)
+if (!ferror(spool_data_file) && !receive_feof() && message_ended != END_DOT)
   {
   if (smtp_input)
     {
@@ -3313,15 +3377,16 @@ if (!ferror(spool_data_file) && !(receive_feof)() && message_ended != END_DOT)
       cancel_cutthrough_connection(TRUE, US"mail too big");
       if (smtp_input) receive_swallow_smtp();  /* Swallow incoming SMTP */
 
-      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 (LOGGING(size_reject))
+	log_write(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)
 	{
@@ -3363,16 +3428,16 @@ 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(spool_data_file) == EOF || ferror(spool_data_file) ||
-    EXIMfsync(fileno(spool_data_file)) < 0 || (receive_ferror)())
+    EXIMfsync(fileno(spool_data_file)) < 0 || receive_ferror())
   {
   uschar *msg_errno = US strerror(errno);
-  BOOL input_error = (receive_ferror)() != 0;
+  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 ? sender_fullhost : sender_ident);
 
-  log_write(0, LOG_MAIN, "Message abandoned: %s", msg);
+  log_write(LOG_MAIN, "Message abandoned: %s", msg);
   Uunlink(spool_name);                /* Lose the data file */
   cancel_cutthrough_connection(TRUE, US"error writing spoolfile");
 
@@ -3400,7 +3465,7 @@ if (fflush(spool_data_file) == EOF || ferror(spool_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);
+DEBUG(receive) debug_printf("Data file written for message %s\n", message_id);
 gettimeofday(&received_time_complete, NULL);
 
 
@@ -3417,7 +3482,7 @@ syntactically good recipient address.) */
 
 if (extract_recip && (bad_addresses || recipients_count == 0))
   {
-  DEBUG(D_receive)
+  DEBUG(receive)
     {
     if (recipients_count == 0) debug_printf("*** No recipients\n");
     if (bad_addresses)
@@ -3428,7 +3493,7 @@ if (extract_recip && (bad_addresses || recipients_count == 0))
       }
     }
 
-  log_write(0, LOG_MAIN|LOG_PANIC, "%s found in headers",
+  log_write(LOG_MAIN|LOG_PANIC, "%s found in headers",
     bad_addresses ? "bad addresses" : "no recipients");
 
   fseek(spool_data_file, (long int)spool_data_start_offset(message_id), SEEK_SET);
@@ -3589,17 +3654,6 @@ else
       }
 #endif /* WITH_CONTENT_SCAN */
 
-#ifdef SUPPORT_DMARC
-    {
-    misc_module_info * mi = misc_mod_findonly(US"dmarc");
-    if (mi)
-      {
-      typedef int (*fn_t)(header_line *);
-      (((fn_t *) mi->functions)[DMARC_STORE_DATA]) (dmarc_from_header);
-      }
-    }
-#endif
-
 #ifndef DISABLE_PRDR
     if (prdr_requested && recipients_count > 1)
       {
@@ -3616,7 +3670,7 @@ else
 	  const uschar * addr = recipients_list[c].address;
 	  uschar * msg= US"PRDR R=<%s> %s";
 	  uschar * code;
-	  DEBUG(D_receive)
+	  DEBUG(receive)
 	    debug_printf("PRDR processing recipient %s (%d of %d)\n",
 			 addr, c+1, recipients_count);
 	  rc = acl_check(ACL_WHERE_PRDR, addr,
@@ -3648,9 +3702,9 @@ else
 	      }
 	    smtp_user_msg(code, msg);
 	    }
-	  if (log_msg)       log_write(0, LOG_MAIN, "PRDR %s %s", addr, log_msg);
-	  else if (user_msg) log_write(0, LOG_MAIN, "PRDR %s %s", addr, user_msg);
-	  else               log_write(0, LOG_MAIN, "%s", CS msg);
+	  if (log_msg)       log_write(LOG_MAIN, "PRDR %s %s", addr, log_msg);
+	  else if (user_msg) log_write(LOG_MAIN, "PRDR %s %s", addr, user_msg);
+	  else               log_write(LOG_MAIN, "%s", CS msg);
 
 	  if (rc != OK) { receive_remove_recipient(addr); c--; }
 	  }
@@ -3748,7 +3802,7 @@ else
         nowhere. The default is main and reject logs. */
 
         if (log_reject_target)
-          log_write(0, log_reject_target, "F=<%s> rejected by non-SMTP ACL: %s",
+          log_write(log_reject_target, "F=<%s> rejected by non-SMTP ACL: %s",
             sender_address, log_msg);
 
         if (!user_msg) user_msg = US"local configuration problem";
@@ -3802,7 +3856,7 @@ if (sigsetjmp(local_scan_env, 1) == 0)
   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",
+  DEBUG(receive) debug_printf("calling local_scan(); timeout=%d\n",
     local_scan_timeout);
   local_scan_data = NULL;
 
@@ -3816,7 +3870,7 @@ if (sigsetjmp(local_scan_env, 1) == 0)
   f.enable_dollar_recipients = FALSE;
 
   store_pool = POOL_MAIN;   /* In case changed */
-  DEBUG(D_receive) debug_printf("local_scan() returned %d %s\n", rc,
+  DEBUG(receive) debug_printf("local_scan() returned %d %s\n", rc,
     local_scan_data);
 
   os_non_restarting_signal(SIGSEGV, SIG_DFL);
@@ -3828,7 +3882,7 @@ else
   {
   if (had_local_scan_crash)
     {
-    log_write(0, LOG_MAIN|LOG_REJECT, "local_scan() function crashed with "
+    log_write(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");
@@ -3836,7 +3890,7 @@ else
     }
   if (had_local_scan_timeout)
     {
-    log_write(0, LOG_MAIN|LOG_REJECT, "local_scan() function timed out - "
+    log_write(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 */
@@ -3896,8 +3950,7 @@ multiline SMTP responses. */
 
 else
   {
-  uschar *istemp = US"";
-  uschar *smtp_code;
+  uschar * istemp = US"", * smtp_code;
   gstring * g;
 
   errmsg = local_scan_data;
@@ -3906,12 +3959,12 @@ else
   switch(rc)
     {
     default:
-      log_write(0, LOG_MAIN, "invalid return %d from local_scan(). Temporary "
-	"rejection given", rc);
+      log_write(LOG_MAIN,
+	"invalid return %d from local_scan(). Temporary rejection given", rc);
       goto TEMPREJECT;
 
     case LOCAL_SCAN_REJECT_NOLOGHDR:
-      BIT_CLEAR(log_selector, log_selector_size, Li_rejected_header);
+      logging_modify_channels(US"-rejected_header");
       /* Fall through */
 
     case LOCAL_SCAN_REJECT:
@@ -3920,7 +3973,7 @@ else
       break;
 
     case LOCAL_SCAN_TEMPREJECT_NOLOGHDR:
-      BIT_CLEAR(log_selector, log_selector_size, Li_rejected_header);
+      logging_modify_channels(US"-rejected_header");
       /* Fall through */
 
     case LOCAL_SCAN_TEMPREJECT:
@@ -3934,7 +3987,7 @@ else
   g = string_append(NULL, 2, US"F=", *sender_address ? sender_address : US"<>");
   g = add_host_info_for_log(g);
 
-  log_write(0, LOG_MAIN|LOG_REJECT, "%Y %srejected by local_scan(): %.256s",
+  log_write(LOG_MAIN|LOG_REJECT, "%Y %srejected by local_scan(): %.256s",
     g, istemp, string_printing(errmsg));
 
   if (smtp_input)
@@ -3971,7 +4024,7 @@ if (fake_response != OK)
   for (recipient_item * r = recipients_list;
        r < recipients_list + recipients_count; r++)
     {
-    DEBUG(D_receive) if (r->dsn_flags & (rf_notify_success | rf_notify_delay))
+    DEBUG(receive) if (r->dsn_flags & (rf_notify_success | rf_notify_delay))
       debug_printf("DSN: clearing flags due to fake-response for message\n");
     r->dsn_flags = r->dsn_flags & ~(rf_notify_success | rf_notify_delay)
 		    | rf_notify_never;
@@ -3982,14 +4035,6 @@ if (fake_response != OK)
 
 f.deliver_firsttime = TRUE;
 
-#ifdef EXPERIMENTAL_BRIGHTMAIL
-if (bmi_run == 1)
-  { /* rewind data file */
-  lseek(data_fd, (long int)spool_data_start_offset(message_id), SEEK_SET);
-  bmi_verdicts = bmi_process_message(header_list, data_fd);
-  }
-#endif
-
 /* 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. */
@@ -4026,7 +4071,7 @@ if (host_checking || blackholed_by)
 else
   if ((msg_size = spool_write_header(message_id, SW_RECEIVING, &errmsg)) < 0)
     {
-    log_write(0, LOG_MAIN, "Message abandoned: %s", errmsg);
+    log_write(LOG_MAIN, "Message abandoned: %s", errmsg);
     Uunlink(spool_name);           /* Lose the data file */
 
     if (smtp_input)
@@ -4059,7 +4104,7 @@ if (  fflush(spool_data_file)
    )
   {
   errmsg = string_sprintf("Spool write error: %s", strerror(errno));
-  log_write(0, LOG_MAIN, "%s\n", errmsg);
+  log_write(LOG_MAIN, "%s\n", errmsg);
   Uunlink(spool_name);           /* Lose the data file */
 
   if (smtp_input)
@@ -4101,9 +4146,11 @@ if (message_reference)
   g = string_append(g, 2, US" R=", message_reference);
 
 g = add_host_info_for_log(g);
-
-#ifndef DISABLE_TLS
 g = add_tls_info_for_log(g);
+
+#ifdef SUPPORT_PROXY
+if (proxy_session && LOGGING(proxy))
+  g = string_append(g, 2, US" PRX=", proxy_local_address);
 #endif
 
 if (sender_host_authenticated)
@@ -4122,11 +4169,6 @@ if (prdr_requested)
   g = string_catn(g, US" PRDR", 5);
 #endif
 
-#ifdef SUPPORT_PROXY
-if (proxy_session && LOGGING(proxy))
-  g = string_append(g, 2, US" PRX=", proxy_local_address);
-#endif
-
 if (chunking_state > CHUNKING_OFFERED)
   g = string_catn(g, US" K", 2);
 
@@ -4139,6 +4181,9 @@ g = string_fmt_append(g, " S=%d", msg_size);
 if (LOGGING(8bitmime))
   g = string_fmt_append(g, " M8S=%d", body_8bitmime);
 
+if (LOGGING(dsn))
+  g = add_dsn_info_for_log(g, ACL_WHERE_DATA);
+
 #ifndef DISABLE_DKIM
 if (LOGGING(dkim))
   {
@@ -4158,6 +4203,8 @@ if (LOGGING(dkim))
   }
 #endif
 
+g = add_dmarc_info_for_log(g);
+
 if (LOGGING(receive_time))
   {
   struct timeval diff = received_time_complete;
@@ -4239,14 +4286,14 @@ if (message_logs && !blackholed_by)
     }
 
   if (fd < 0)
-    log_write(0, LOG_MAIN|LOG_PANIC, "Couldn't open message log %s: %s",
+    log_write(LOG_MAIN|LOG_PANIC, "Couldn't open message log %s: %s",
       m_name, strerror(errno));
   else
     {
     FILE *message_log = fdopen(fd, "a");
     if (!message_log)
       {
-      log_write(0, LOG_MAIN|LOG_PANIC, "Couldn't fdopen message log %s: %s",
+      log_write(LOG_MAIN|LOG_PANIC, "Couldn't fdopen message log %s: %s",
         m_name, strerror(errno));
       (void)close(fd);
       }
@@ -4295,8 +4342,8 @@ if (  smtp_input && sender_host_address && !f.sender_host_notsocket
   {
   if (poll_one_fd(smtp_in_fd, POLLIN, 0) != 0)
     {
-    int c = (receive_getc)(GETC_BUFFER_UNLIMITED);
-    if (c >= 0) (receive_ungetc)(c);
+    int c = receive_getc(GETC_BUFFER_UNLIMITED);
+    if (c >= 0) receive_ungetc(c);
     else
       {
       smtp_notquit_exit(US"connection-lost", NULL, NULL);
@@ -4306,7 +4353,7 @@ if (  smtp_input && sender_host_address && !f.sender_host_notsocket
       gstring_reset(g);
       g = string_cat(g, US"SMTP connection lost after final dot");
       g = add_host_info_for_log(g);
-      log_write(0, LOG_MAIN, "%Y", g);
+      log_write(LOG_MAIN, "%Y", g);
 
       /* Delete the files for this aborted message. */
 
@@ -4370,18 +4417,18 @@ if(!smtp_reply || prdr_requested)
 if(!smtp_reply)
 #endif
   {
-  log_write(0, LOG_MAIN |
+  log_write(LOG_MAIN |
 		(LOGGING(received_recipients) ? LOG_RECIPIENTS : 0) |
 		(LOGGING(received_sender) ? LOG_SENDER : 0),
 	    "%Y", g);
 
   /* Log any control actions taken by an ACL or local_scan(). */
 
-  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);
+  if (f.deliver_freeze) log_write(LOG_MAIN, "frozen by %s", frozen_by);
+  if (f.queue_only_policy && LOGGING(delay_delivery))
+    log_write(LOG_MAIN, "no immediate delivery: queued%s%s by %s",
+		    *queue_name ? " in " : "", *queue_name ? CS queue_name : "",
+		    queued_by);
   }
 f.receive_call_bombout = FALSE;
 
@@ -4434,7 +4481,7 @@ NOT_ACCEPTED:
   message_id[0] = 0;			/* Indicate no message accepted */
 
 TIDYUP:
-DEBUG(D_receive) debug_printf("%s: tidyup\n", __FUNCTION__);
+DEBUG(receive) debug_printf("%s: tidyup\n", __FUNCTION__);
 
 process_info[process_info_len] = 0;			/* Remove message id */
 if (spool_data_file && cutthrough_done == NOT_TRIED)
@@ -4442,11 +4489,11 @@ if (spool_data_file && cutthrough_done == NOT_TRIED)
   if (fclose(spool_data_file))				/* Frees the lock */
     {
     log_msg = string_sprintf("spoolfile error on close: %s", strerror(errno));
-    log_write(0, LOG_MAIN|LOG_PANIC |
+    log_write(LOG_MAIN|LOG_PANIC |
 		  (LOGGING(received_recipients) ? LOG_RECIPIENTS : 0) |
 		  (LOGGING(received_sender) ? LOG_SENDER : 0),
 	      "%s", log_msg);
-    log_write(0, LOG_MAIN |
+    log_write(LOG_MAIN |
 		  (LOGGING(received_recipients) ? LOG_RECIPIENTS : 0) |
 		  (LOGGING(received_sender) ? LOG_SENDER : 0),
 	      "rescind the above message-accept");
@@ -4455,8 +4502,10 @@ if (spool_data_file && cutthrough_done == NOT_TRIED)
     Uunlink(spool_fname(US"input", message_subdir, message_id, US"-H"));
     Uunlink(spool_fname(US"msglog", message_subdir, message_id, US""));
 
-    /* Claim a data ACL temp-reject, just to get reject logging and response */
-    if (smtp_input) smtp_handle_acl_fail(ACL_WHERE_DATA, rc, NULL, log_msg);
+    /* Claim a data ACL temp-reject, to get reject logging and SMTP response */
+
+    if (smtp_input)
+      smtp_handle_acl_fail(ACL_WHERE_DATA, DEFER, NULL, log_msg);
     smtp_reply = US"";		/* Indicate reply already sent */
 
     message_id[0] = 0;		/* no message accepted */
@@ -4534,7 +4583,7 @@ if (smtp_input)
     switch (cutthrough_done)
       {
       case ACCEPTED:
-	log_write(0, LOG_MAIN, "Completed");/* Delivery was done */
+	log_write(LOG_MAIN, "Completed");/* Delivery was done */
       case PERM_REJ:
 							 /* Delete spool files */
 	Uunlink(spool_name);
@@ -4586,8 +4635,8 @@ if (blackholed_by)
     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");
+  log_write(LOG_MAIN, "=> blackhole %s%s", detail, blackhole_log_msg);
+  log_write(LOG_MAIN, "Completed");
   message_id[0] = 0;
   }
 
@@ -4603,5 +4652,5 @@ return yield;  /* TRUE if more messages (SMTP only) */
 }
 
 /* End of receive.c */
-/* vi: se aw ai sw=2
+/* vi: aw ai sw=2
 */
diff --git a/src/src/regex.c b/src/src/regex.c
index ff52d349c..2e5c41275 100644
--- a/src/src/regex.c
+++ b/src/src/regex.c
@@ -25,7 +25,7 @@ typedef struct pcre_list {
 } pcre_list;
 
 extern FILE *mime_stream;
-extern uschar *mime_current_boundary;
+extern const uschar *mime_current_boundary;
 
 
 static pcre_list *
@@ -46,7 +46,7 @@ while ((regex_string = string_nextinlist(&list, &sep, NULL, 0)))
 
     if (!re)
       {
-      log_write(0, LOG_MAIN, "regex acl condition warning - %s, skipped", errstr);
+      log_write(LOG_MAIN, "regex acl condition warning - %s, skipped", errstr);
       continue;
       }
 
@@ -113,7 +113,7 @@ for (int i = 0; i < REGEX_VARS; i++) regex_vars[i] = NULL;
 
 
 int
-regex(const uschar ** listptr, BOOL cacheable)
+exim_regex(const uschar ** listptr, BOOL cacheable)
 {
 unsigned long mbox_size;
 FILE * mbox_file;
@@ -127,7 +127,7 @@ 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,
+    log_write(LOG_MAIN|LOG_PANIC,
 	   "regex acl condition: error while creating mbox spool file");
     return DEFER;
     }
@@ -136,7 +136,7 @@ else
   {
   if ((f_pos = ftell(mime_stream)) < 0)
     {
-    log_write(0, LOG_MAIN|LOG_PANIC,
+    log_write(LOG_MAIN|LOG_PANIC,
 	   "regex acl condition: mime_stream: %s", strerror(errno));
     return DEFER;
     }
@@ -177,7 +177,7 @@ else
   clearerr(mime_stream);
   if (fseek(mime_stream, f_pos, SEEK_SET) == -1)
     {
-    log_write(0, LOG_MAIN|LOG_PANIC,
+    log_write(LOG_MAIN|LOG_PANIC,
 	   "regex acl condition: mime_stream: %s", strerror(errno));
     clearerr(mime_stream);
     }
@@ -205,7 +205,7 @@ if (!mime_decoded_filename)
   mime_decode(&empty);
   if (!mime_decoded_filename)
     {				/* decoding failed */
-    log_write(0, LOG_MAIN,
+    log_write(LOG_MAIN,
        "mime_regex acl condition warning - could not decode MIME part to file");
     return DEFER;
     }
@@ -214,7 +214,7 @@ if (!mime_decoded_filename)
 /* open file */
 if (!(f = fopen(CS mime_decoded_filename, "rb")))
   {
-  log_write(0, LOG_MAIN,
+  log_write(LOG_MAIN,
        "mime_regex acl condition warning - can't open '%s' for reading",
        mime_decoded_filename);
   return DEFER;
diff --git a/src/src/regex_cache.c b/src/src/regex_cache.c
index b92556d92..3be169a93 100644
--- a/src/src/regex_cache.c
+++ b/src/src/regex_cache.c
@@ -52,7 +52,7 @@ int rlen = sizeof(re_req) + klen;
 re_req * req;
 int fd, old_pool = store_pool;
 
-DEBUG(D_expand|D_lists)
+DEBUG(regex)
   debug_printf_indent("sending RE '%s' to daemon\n", key);
 
 store_pool = POOL_MAIN;
@@ -68,11 +68,11 @@ if ((fd = socket(AF_UNIX, SOCK_DGRAM, 0)) >= 0)
   ssize_t len = daemon_notifier_sockname(&sa_un);
 
   if (sendto(fd, req, rlen, 0, (struct sockaddr *)&sa_un, (socklen_t)len) < 0)
-    DEBUG(D_queue_run)
+    DEBUG(regex)
       debug_printf("%s: sendto %s\n", __FUNCTION__, strerror(errno));
   close(fd);
   }
-else DEBUG(D_queue_run) debug_printf(" socket: %s\n", strerror(errno));
+else DEBUG(regex) debug_printf(" socket: %s\n", strerror(errno));
 }
 
 
@@ -81,7 +81,7 @@ regex_from_cache(const uschar * key, BOOL caseless)
 {
 tree_node * node  =
   tree_search(caseless ? regex_caseless_cache : regex_cache, key);
-DEBUG(D_expand|D_lists)
+DEBUG(regex)
   debug_printf_indent("compiled %sRE '%s' %sfound in local cache\n",
 		      caseless ? "caseless " : "", key, node ? "" : "not ");
 
@@ -99,8 +99,8 @@ Ustrcpy(node->name, key);
 node->data.ptr = (void *)cre;
 
 if (!tree_insertnode(caseless ? &regex_caseless_cache : &regex_cache, node))
-  { DEBUG(D_expand|D_lists) debug_printf_indent("duplicate key!\n"); }
-else DEBUG(D_expand|D_lists)
+  { DEBUG(regex) debug_printf_indent("duplicate key!\n"); }
+else DEBUG(regex)
   debug_printf_indent("compiled RE '%s' saved in local cache\n", key);
 
 /* Additionally, if not re-execed and not the daemon, tell the daemon of the RE
@@ -142,7 +142,7 @@ size_t offset;
 const pcre2_code * yield;
 int old_pool = store_pool, err;
 
-/* Optionall, check the cache and return if found */
+/* Optionally, check the cache and return if found */
 
 if (  flags & MCS_CACHEABLE
    && (yield = regex_from_cache(pattern, caseless)))
@@ -156,7 +156,7 @@ if (!(yield = pcre2_compile((PCRE2_SPTR)pattern, PCRE2_ZERO_TERMINATED,
   {
   uschar errbuf[128];
   pcre2_get_error_message(err, errbuf, sizeof(errbuf));
-  log_write_die(0, LOG_MAIN, "regular expression error: "
+  log_write_die(LOG_MAIN, "regular expression error: "
     "%s at offset %ld while compiling %s", errbuf, (long)offset, pattern);
   }
 
@@ -191,37 +191,38 @@ const pcre2_code *
 regex_compile(const uschar * pattern, mcs_flags flags, uschar ** errstr,
   pcre2_compile_context * cctx)
 {
-const uschar * key = pattern;
 BOOL caseless = !!(flags & MCS_CASELESS);
-int err;
-PCRE2_SIZE offset;
 const pcre2_code * yield;
-int old_pool = store_pool;
 
+expand_level++;
 /* Optionally, check the cache and return if found */
 
-if (  flags & MCS_CACHEABLE
-   && (yield = regex_from_cache(key, caseless)))
-  return yield;
-
-DEBUG(D_expand|D_lists) debug_printf_indent("compiling %sRE '%s'\n",
-				caseless ? "caseless " : "", pattern);
-
-store_pool = POOL_PERM;
-if (!(yield = pcre2_compile((PCRE2_SPTR)pattern, PCRE2_ZERO_TERMINATED,
-		caseless ? PCRE_COPT|PCRE2_CASELESS : PCRE_COPT,
-		&err, &offset, cctx)))
+if (  !(flags & MCS_CACHEABLE)
+   || !(yield = regex_from_cache(pattern, caseless)))
   {
-  uschar errbuf[128];
-  pcre2_get_error_message(err, errbuf, sizeof(errbuf));
+  int old_pool = store_pool, err;
+  PCRE2_SIZE offset;
+
+  DEBUG(regex) debug_printf_indent("compiling %sRE '%s'\n",
+				  caseless ? "caseless " : "", pattern);
+  store_pool = POOL_PERM;
+  if (!(yield = pcre2_compile((PCRE2_SPTR)pattern, PCRE2_ZERO_TERMINATED,
+		  caseless ? PCRE_COPT|PCRE2_CASELESS : PCRE_COPT,
+		  &err, &offset, cctx)))
+    {
+    uschar errbuf[128];
+    pcre2_get_error_message(err, errbuf, sizeof(errbuf));
+    store_pool = old_pool;
+    *errstr = string_sprintf("regular expression error in "
+	      "%q: %s at offset %ld", pattern, errbuf, (long)offset);
+    DEBUG(regex) debug_printf_indent("'%s'\n%*s\n", pattern, (int)offset, "^");
+    }
+  else if (flags & MCS_CACHEABLE)
+    regex_to_cache(pattern, caseless, yield);
   store_pool = old_pool;
-  *errstr = string_sprintf("regular expression error in "
-	    "%q: %s at offset %ld", pattern, errbuf, (long)offset);
   }
-else if (flags & MCS_CACHEABLE)
-  regex_to_cache(key, caseless, yield);
-store_pool = old_pool;
 
+expand_level--;
 return yield;
 }
 
@@ -246,6 +247,6 @@ else if ((cre = regex_compile(req->re,
 	    &errstr, pcre_gen_cmp_ctx)))
   regex_cachesize++;
 
-DEBUG(D_any) if (!cre) debug_printf("%s\n", errstr);
+DEBUG(any) if (!cre) debug_printf("%s\n", errstr);
 return;
 }
diff --git a/src/src/retry.c b/src/src/retry.c
index 24b2fa66a..55522f7ff 100644
--- a/src/src/retry.c
+++ b/src/src/retry.c
@@ -37,7 +37,7 @@ retry_ultimate_address_timeout(const uschar * retry_key, const uschar *domain,
 BOOL address_timeout;
 retry_config * retry;
 
-DEBUG(D_retry)
+DEBUG(retry)
   {
   debug_printf("retry time not reached: checking ultimate address timeout\n");
   debug_printf("  now=" TIME_T_FMT " first_failed=" TIME_T_FMT
@@ -53,19 +53,19 @@ if (retry && retry->rules)
   {
   retry_rule *last_rule;
   for (last_rule = retry->rules; last_rule->next; last_rule = last_rule->next) ;
-  DEBUG(D_retry)
+  DEBUG(retry)
     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
   {
-  DEBUG(D_retry)
+  DEBUG(retry)
     debug_printf("no retry rule found: assume timed out\n");
   address_timeout = TRUE;
   }
 
-DEBUG(D_retry)
+DEBUG(retry)
   if (address_timeout)
     debug_printf("on queue longer than maximum retry for address - "
       "allowing delivery\n");
@@ -164,7 +164,7 @@ dbdata_retry * host_retry_record, * message_retry_record;
 if (host->status != hstatus_unknown) return FALSE;
 host->status = hstatus_usable;
 
-DEBUG(D_transport|D_retry)
+DEBUG(transport|retry)
   {
   debug_printf_indent("checking retry status of %s\n", host->name);
   acl_level++;
@@ -186,7 +186,7 @@ the retry database when it is updated). */
 
 if ((node = tree_search(tree_unusable, host_key)))
   {
-  DEBUG(D_transport|D_retry)
+  DEBUG(transport|retry)
     debug_printf_indent("found in tree of unusables\n");
   host->status = node->data.val > 255
     ? hstatus_unusable_expired : hstatus_unusable;
@@ -201,16 +201,16 @@ if (!continue_retry_db)
   dbm_file = dbfn_open(US"retry", O_RDONLY, &dbblock, FALSE, TRUE);
 else if (continue_retry_db != (open_db *)-1)
   {
-  DEBUG(D_hints_lookup)
+  DEBUG(hints_lookup)
     debug_printf_indent(" using cached retry hintsdb handle\n");
   dbm_file = continue_retry_db;
   }
-else DEBUG(D_hints_lookup)
+else DEBUG(hints_lookup)
     debug_printf_indent(" using cached retry hintsdb nonpresence\n");
 
 if (!dbm_file)
   {
-  DEBUG(D_deliver|D_retry|D_hints_lookup)
+  DEBUG(deliver|retry|hints_lookup)
     debug_printf_indent("no retry data available\n");
   goto out;
   }
@@ -219,28 +219,28 @@ message_retry_record = dbfn_read(dbm_file, message_key);
 if (!continue_retry_db)
   dbfn_close(dbm_file);
 else
-  DEBUG(D_hints_lookup) debug_printf_indent("retaining retry hintsdb handle\n");
+  DEBUG(hints_lookup) debug_printf_indent("retaining retry hintsdb handle\n");
 
 /* Ignore the data if it is too old - too long since it was written */
 
 if (!host_retry_record)
   {
-  DEBUG(D_transport|D_retry) debug_printf_indent("no host retry record\n");
+  DEBUG(transport|retry) debug_printf_indent("no host retry record\n");
   }
-else if (now - host_retry_record->time_stamp > retry_data_expire)
+else if (now - host_retry_record->gen.time_stamp > retry_data_expire)
   {
   host_retry_record = NULL;
-  DEBUG(D_transport|D_retry) debug_printf_indent("host retry record too old\n");
+  DEBUG(transport|retry) debug_printf_indent("host retry record too old\n");
   }
 
 if (!message_retry_record)
   {
-  DEBUG(D_transport|D_retry) debug_printf_indent("no message retry record\n");
+  DEBUG(transport|retry) debug_printf_indent("no message retry record\n");
   }
-else if (now - message_retry_record->time_stamp > retry_data_expire)
+else if (now - message_retry_record->gen.time_stamp > retry_data_expire)
   {
   message_retry_record = NULL;
-  DEBUG(D_transport|D_retry)
+  DEBUG(transport|retry)
     debug_printf_indent("message retry record too old\n");
   }
 
@@ -299,7 +299,7 @@ if (message_retry_record)
   }
 
 out:
-DEBUG(D_transport|D_retry) acl_level--;
+DEBUG(transport|retry) acl_level--;
 return yield;
 }
 
@@ -348,7 +348,7 @@ rti->message = host
   : addr->message;
 rti->flags = flags;
 
-DEBUG(D_transport|D_retry)
+DEBUG(transport|retry)
   {
   int letter = rti->more_errno & 255;
   debug_printf("added retry %sitem for %s: errno=%d more_errno=",
@@ -426,12 +426,12 @@ if (alternate)    alternate = string_sprintf("*@%s", alternate);
 
 /* Scan the configured retry items. */
 
-DEBUG(D_retry) acl_level++;
+DEBUG(retry) acl_level++;
 for (yield = retries; yield; yield = yield->next)
   {
   const uschar * plist = yield->pattern, * slist = yield->senders;
 
-  DEBUG(D_retry)
+  DEBUG(retry)
     {
     acl_level--;
     debug_printf_indent("Check retry rule (%s:%d) '%s'\n",
@@ -543,7 +543,7 @@ for (yield = retries; yield; yield = yield->next)
      )  )
     break;
   }
-DEBUG(D_retry) acl_level--;
+DEBUG(retry) acl_level--;
 
 return yield;
 }
@@ -582,7 +582,7 @@ retry_update(address_item ** addr_defer, address_item ** addr_failed,
 open_db dbblock, * dbm_file = NULL;
 time_t now = time(NULL);
 
-DEBUG(D_retry) { debug_printf_indent("Processing retry items\n"); acl_level++; }
+DEBUG(retry) { debug_printf_indent("Processing retry items\n"); acl_level++; }
 
 /* Three-times loop to handle succeeded, failed, and deferred addresses.
 Deferred addresses must be handled after failed ones, because some may be moved
@@ -595,7 +595,7 @@ for (int i = 0; i < 3; i++)
   address_item ** paddr = i==0 ? addr_succeed : i==1 ? addr_failed : addr_defer;
   address_item ** saved_paddr = NULL;
 
-  DEBUG(D_retry)
+  DEBUG(retry)
     {
     debug_printf_indent("%s addresses:\n",
       i == 0 ? "Succeeded" : i == 1 ? "Failed" : "Deferred");
@@ -620,7 +620,7 @@ for (int i = 0; i < 3; i++)
       {
       int update_count = 0, timedout_count = 0;
 
-      DEBUG(D_retry)
+      DEBUG(retry)
 	{
 	debug_printf_indent("%s%s\n", addr->address,
 			    addr->retries ? "" : ": no retry items");
@@ -646,7 +646,7 @@ for (int i = 0; i < 3; i++)
         if (!dbm_file)
 	  if (continue_retry_db && continue_retry_db != (open_db *)-1)
 	    {
-	    DEBUG(D_hints_lookup)
+	    DEBUG(hints_lookup)
 	      debug_printf_indent("using cached retry hintsdb handle\n");
 	    dbm_file = continue_retry_db;
 	    }
@@ -654,7 +654,7 @@ for (int i = 0; i < 3; i++)
 		    ? dbfn_open(US"retry", O_RDWR|O_CREAT, &dbblock, TRUE, TRUE)
 		    : dbfn_open_multi(US"retry", O_RDWR|O_CREAT, &dbblock)))
 	    {
-	    DEBUG(D_deliver|D_retry|D_hints_lookup)
+	    DEBUG(deliver|retry|hints_lookup)
 	      debug_printf_indent("retry db not available for updating\n");
 	    return;
 	    }
@@ -676,7 +676,7 @@ for (int i = 0; i < 3; i++)
         if (rti->flags & rf_delete)
           {
           (void)dbfn_delete(dbm_file, rti->key);
-          DEBUG(D_retry)
+          DEBUG(retry)
             debug_printf_indent("deleted retry information for %s\n", rti->key);
           continue;
           }
@@ -697,7 +697,7 @@ for (int i = 0; i < 3; i++)
              rti->flags & rf_host ? addr->domain : NULL,
              rti->basic_errno, rti->more_errno)))
           {
-          DEBUG(D_retry) debug_printf_indent("No configured retry item for %s%s%s\n",
+          DEBUG(retry) debug_printf_indent("No configured retry item for %s%s%s\n",
             rti->key,
             rti->flags & rf_host ? US" or " : US"",
             rti->flags & rf_host ? addr->domain : US"");
@@ -705,7 +705,7 @@ for (int i = 0; i < 3; i++)
           continue;
           }
 
-        DEBUG(D_retry)
+        DEBUG(retry)
           if (rti->flags & rf_host)
             debug_printf_indent("retry for %s (%s) = %s %d %d\n", rti->key,
               addr->domain, retry->pattern, retry->basic_errno,
@@ -726,7 +726,7 @@ for (int i = 0; i < 3; i++)
         message_length = Ustrlen(message);
         if (message_length > EXIM_DB_RLIMIT)
 	  {
-	  DEBUG(D_retry)
+	  DEBUG(retry)
 	    debug_printf_indent("truncating message from %u to %u bytes\n",
 				message_length, EXIM_DB_RLIMIT);
 	  message_length = EXIM_DB_RLIMIT;
@@ -744,7 +744,7 @@ for (int i = 0; i < 3; i++)
         retry_record = dbfn_read_with_length(dbm_file, rti->key,
 					      &message_space);
         if (  retry_record
-	   && now - retry_record->time_stamp > retry_data_expire)
+	   && now - retry_record->gen.time_stamp > retry_data_expire)
           retry_record = NULL;
 
         if (!retry_record)
@@ -763,7 +763,7 @@ for (int i = 0; i < 3; i++)
         /* Compute how long this destination has been failing */
 
         failing_interval = now - retry_record->first_failed;
-        DEBUG(D_retry) debug_printf_indent("failing_interval=%d message_age=%d\n",
+        DEBUG(retry) debug_printf_indent("failing_interval=%d message_age=%d\n",
           failing_interval, message_age);
 
         /* For a non-host error, if the message has been on the queue longer
@@ -845,7 +845,7 @@ for (int i = 0; i < 3; i++)
 	    ;
           if (now - received_time.tv_sec > last_rule->timeout)
             {
-            DEBUG(D_retry) debug_printf_indent("on queue longer than maximum retry\n");
+            DEBUG(retry) debug_printf_indent("on queue longer than maximum retry\n");
             timedout_count++;
             rule = NULL;
             }
@@ -911,7 +911,7 @@ for (int i = 0; i < 3; i++)
         Ustrncpy(retry_record->text, message, message_length);
         retry_record->text[message_length] = 0;	/* nul-term string in db */
 
-        DEBUG(D_retry)
+        DEBUG(retry)
           {
           int letter = retry_record->more_errno & 255;
           debug_printf_indent("Writing retry data for %s\n", rti->key);
@@ -929,12 +929,12 @@ for (int i = 0; i < 3; i++)
 
         if (dbfn_write(dbm_file, rti->key, retry_record,
 		      sizeof(dbdata_retry) + message_length) != 0)
-	  DEBUG(D_retry) debug_printf_indent("retry record write failed\n");
+	  DEBUG(retry) debug_printf_indent("retry record write failed\n");
 
 	if (!exim_lockfile_needed())
 	  dbfn_transaction_commit(dbm_file);
         }                            /* Loop for each retry item */
-      DEBUG(D_retry) acl_level--;
+      DEBUG(retry) acl_level--;
 
       /* If all the non-delete retry items are timed out, the address is
       timed out, provided that we didn't skip any hosts because their retry
@@ -943,11 +943,11 @@ for (int i = 0; i < 3; i++)
       if (update_count > 0 && update_count == timedout_count)
         if (!testflag(endaddr, af_retry_skipped))
           {
-          DEBUG(D_retry) debug_printf_indent("timed out: all retries expired\n");
+          DEBUG(retry) debug_printf_indent("timed out: all retries expired\n");
           timed_out = TRUE;
           }
         else
-          DEBUG(D_retry)
+          DEBUG(retry)
             debug_printf_indent("timed out but some hosts were skipped\n");
       }     /* Loop for an address and its parents */
 
@@ -982,7 +982,7 @@ for (int i = 0; i < 3; i++)
           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",
+          log_write(LOG_MAIN, "** %s%s%s%s: retry timeout exceeded",
             addr->address,
             addr->parent ? US" <" : US"",
             addr->parent ? addr->parent->address : US"",
@@ -1009,7 +1009,7 @@ for (int i = 0; i < 3; i++)
 
     paddr = &(endaddr->next);         /* Advance to next address */
     }                                 /* Loop for all addresses  */
-  DEBUG(D_retry) acl_level--;
+  DEBUG(retry) acl_level--;
   }                                   /* Loop for succeed, fail, defer */
 
 /* Close and unlock the database */
@@ -1020,10 +1020,10 @@ if (dbm_file)
 	dbfn_close(dbm_file);
       else
 	dbfn_close_multi(dbm_file);
-  else DEBUG(D_hints_lookup)
+  else DEBUG(hints_lookup)
     debug_printf_indent("retaining retry hintsdb handle\n");
 
-DEBUG(D_retry)
+DEBUG(retry)
   { acl_level--; debug_printf_indent("end of retry processing\n"); }
 }
 
diff --git a/src/src/rewrite.c b/src/src/rewrite.c
index 97c811f41..734418b1f 100644
--- a/src/src/rewrite.c
+++ b/src/src/rewrite.c
@@ -135,7 +135,7 @@ for (rewrite_rule * rule = rewrite_rules;
     if (!key)
       {
       if (!f.expand_string_forcedfail)
-        log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand %q while "
+        log_write(LOG_MAIN|LOG_PANIC, "failed to expand %q while "
           "checking for SMTP rewriting: %s", rule->key, expand_string_message);
       continue;
       }
@@ -199,7 +199,7 @@ for (rewrite_rule * rule = rewrite_rules;
 
     expand_string_message = expand_hide_passwords(expand_string_message);
 
-    log_write(0, LOG_MAIN|LOG_PANIC, "Expansion of %s failed while rewriting: "
+    log_write(LOG_MAIN|LOG_PANIC, "Expansion of %s failed while rewriting: "
       "%s", rule->replacement, expand_string_message);
     break;
     }
@@ -212,7 +212,7 @@ for (rewrite_rule * rule = rewrite_rules;
 
   if (!newparsed)
     {
-    log_write(0, LOG_MAIN|LOG_PANIC, "Rewrite of %s yielded unparseable "
+    log_write(LOG_MAIN|LOG_PANIC, "Rewrite of %s yielded unparseable "
       "address: %s in address %s", subject, error, new);
     break;   /* Give up on this address */
     }
@@ -233,7 +233,7 @@ for (rewrite_rule * rule = rewrite_rules;
       }
     else
       {
-      log_write(0, LOG_MAIN|LOG_PANIC, "Rewrite of %s yielded unqualified "
+      log_write(LOG_MAIN|LOG_PANIC, "Rewrite of %s yielded unqualified "
         "address %q", subject, new);
       break;   /* Give up on this address */
       }
@@ -241,7 +241,7 @@ for (rewrite_rule * rule = rewrite_rules;
 
   /* We have a validly rewritten address */
 
-  if (LOGGING(address_rewrite) || debug_selector & D_rewrite)
+  if (LOGGING(address_rewrite) || IS_DEBUG(rewrite))
     {
     const uschar * where = CUS"?";
 
@@ -249,8 +249,8 @@ for (rewrite_rule * rule = rewrite_rules;
       if (flag == where_list[i].bit)
         { where = where_list[i].string; break; }
 
-    log_write(L_address_rewrite,
-           LOG_MAIN, "\"%s\" from %s rewritten %s \"%s\" by rule %d",
+    if (LOGGING(address_rewrite))
+      log_write(LOG_MAIN, "\"%s\" from %s rewritten %s \"%s\" by rule %d",
            yield, where,
 	   rule->flags & rewrite_whole || !Ustrchr(new, '<')
 	     ?  "as" : "with (address part of)",
@@ -347,7 +347,7 @@ for (rewrite_rule * rule = rewrite_rules;
   if (rule->flags & rewrite_repeat)
     {
     if (count++ < 10) goto REPEAT_RULE;
-    log_write(0, LOG_MAIN|LOG_PANIC, "rewrite rule repeat ignored after 10 "
+    log_write(LOG_MAIN|LOG_PANIC, "rewrite rule repeat ignored after 10 "
       "times");
     }
   }
@@ -448,7 +448,7 @@ uschar * s = Ustrchr(h->text, ':') + 1;
 
 Uskip_whitespace(&s);
 
-DEBUG(D_rewrite)	/* The header text includes the trailing newline */
+DEBUG(rewrite)	/* The header text includes the trailing newline */
   debug_printf_indent("rewrite_one_header: type=%c:\n  %s", h->type, h->text);
 
 f.parse_allow_group = TRUE;     /* Allow group syntax */
@@ -502,7 +502,7 @@ while (*s)
     this one and carry on. */
 
     if (Ustrcmp(errmess, "empty address") != 0)
-      log_write(0, LOG_MAIN, "qualify/rewrite: %s", errmess);
+      log_write(LOG_MAIN, "qualify/rewrite: %s", errmess);
 
     loop_reset_point = store_reset(loop_reset_point);
     continue;
@@ -523,7 +523,7 @@ while (*s)
     if (domain <= 0 || strcmpic(recipient+domain, routed_old) != 0) continue;
     recipient[domain-1] = 0;
     new = string_sprintf("%s@%s", recipient, routed_new);
-    DEBUG(D_rewrite)
+    DEBUG(rewrite)
       {
       recipient[domain-1] = '@';
       debug_printf("%s rewritten by router as %s\n", recipient, new);
@@ -653,7 +653,7 @@ while (*s)
 
     Ustrcat(newt, s);
 
-    DEBUG(D_rewrite) debug_printf("newlen=%d newtype=%c newtext:\n%s",
+    DEBUG(rewrite) debug_printf("newlen=%d newtype=%c newtext:\n%s",
       slen, type, newtstart);
 
     /* Compute the length of the rest of the header line before we possibly
@@ -676,7 +676,7 @@ while (*s)
     /* Set up for scanning the rest of the header */
 
     s = newh->text + remlen;
-    DEBUG(D_rewrite) debug_printf("remainder: %s", *s ? s : US"\n");
+    DEBUG(rewrite) debug_printf("remainder: %s", *s ? s : US"\n");
     }
   }
 
diff --git a/src/src/rfc2047.c b/src/src/rfc2047.c
index 7d3e42cb7..2fb051ebc 100644
--- a/src/src/rfc2047.c
+++ b/src/src/rfc2047.c
@@ -43,31 +43,31 @@ Returns:      the length of the decoded string, or -1 on failure
 */
 
 static int
-rfc2047_qpdecode(uschar *string, uschar **ptrptr)
+rfc2047_qpdecode(const uschar * string, uschar ** ptrptr)
 {
-int len = 0;
-uschar *ptr;
+int len, ch;
+uschar * ptr;
 
 ptr = *ptrptr = store_get(Ustrlen(string) + 1, string);  /* No longer than this */
 
-while (*string != 0)
+for (len = 0; ch = *string++; len++)
   {
-  int ch = *string++;
-
-  if (ch == '_') *ptr++ = ' ';
+  if (ch == '_')
+    *ptr++ = ' ';
   else if (ch == '=')
     {
-    int a = *string;
-    int b = (a == 0)? 0 : string[1];
-    if (!isxdigit(a) || !isxdigit(b)) return -1;  /* Bad QP string */
+    int a = *string, b = a == 0 ? 0 : string[1];
+
+    if (!isxdigit(a) || !isxdigit(b))
+      return -1;  /* Bad QP string */
     *ptr++ = ((Ustrchr(hex_digits, tolower(a)) - hex_digits) << 4) +
                Ustrchr(hex_digits, tolower(b)) - hex_digits;
     string += 2;
     }
-  else if (ch == ' ' || ch == '\t') return -1;    /* Whitespace is illegal */
-  else *ptr++ = ch;
-
-  len++;
+  else if (ch == ' ' || ch == '\t')
+    return -1;    /* Whitespace is illegal */
+  else
+    *ptr++ = ch;
   }
 
 *ptr = 0;
@@ -95,20 +95,23 @@ Arguments:
 Returns:     address of =? or NULL if not present
 */
 
-static uschar *
-decode_mimeword(uschar *string, BOOL lencheck, uschar **q1ptr, uschar **q2ptr,
-  uschar **endptr, size_t *dlenptr, uschar **dptrptr)
+static const uschar *
+decode_mimeword(const uschar * string, BOOL lencheck,
+  const uschar ** q1ptr, const uschar ** q2ptr,
+  const uschar ** endptr, size_t * dlenptr, uschar ** dptrptr)
 {
-uschar *mimeword;
+const uschar * mimeword;
 for (;; string = mimeword + 2)
   {
-  int encoding;
-  int dlen = -1;
+  int dlen = -1, encoding;
+  const uschar * s;
 
-  if ((mimeword = Ustrstr(string, "=?"))  == NULL ||
-      (*q1ptr = Ustrchr(mimeword+2, '?')) == NULL ||
-      (*q2ptr = Ustrchr(*q1ptr+1, '?')) == NULL ||
-      (*endptr = Ustrstr(*q2ptr+1, "?=")) == NULL) return NULL;
+  if (  (mimeword = Ustrstr(string, "=?"))  == NULL
+     || (*q1ptr = Ustrchr(mimeword+2, '?')) == NULL
+     || (*q2ptr = Ustrchr(*q1ptr+1, '?')) == NULL
+     || (*endptr = Ustrstr(*q2ptr+1, "?=")) == NULL
+     )
+    return NULL;
 
   /* We have found =?xxx?xxx?xxx?= in the string. Optionally check the
   length, and that the second field is just one character long. If not,
@@ -120,12 +123,12 @@ for (;; string = mimeword + 2)
   /* Get the encoding letter, and decode the data string. */
 
   encoding = toupper((*q1ptr)[1]);
-  **endptr = 0;
+  s = *q2ptr+1;
+  s = string_copyn(s, *endptr - s);		/* decode fns need nul-term */
   if (encoding == 'B')
-    dlen = b64decode(*q2ptr+1, dptrptr, *q2ptr+1);
+    dlen = b64decode(s, dptrptr, s);
   else if (encoding == 'Q')
-    dlen = rfc2047_qpdecode(*q2ptr+1, dptrptr);
-  **endptr = '?';   /* restore */
+    dlen = rfc2047_qpdecode(s, dptrptr);
 
   /* If the decoding succeeded, we are done. Set the length of the decoded
   string, and pass back the initial pointer. Otherwise, the loop continues. */
@@ -186,15 +189,15 @@ Returns:         the decoded, converted string, or NULL on error; if there are
                    no MIME words in the string, the original string is returned
 */
 
-uschar *
-rfc2047_decode2(uschar *string, BOOL lencheck, const uschar *target,
-  int zeroval, int *lenptr, int *sizeptr, uschar **error)
+const uschar *
+rfc2047_decode2(const uschar * string, BOOL lencheck, const uschar * target,
+  int zeroval, int * lenptr, int * sizeptr, uschar ** error)
 {
 int size = Ustrlen(string);
 size_t dlen;
 uschar * dptr;
 gstring * yield;
-uschar * mimeword, * q1, * q2, * endword;
+const uschar * mimeword, * q1, * q2, * endword;
 
 *error = NULL;
 mimeword = decode_mimeword(string, lencheck, &q1, &q2, &endword, &dlen, &dptr);
@@ -221,7 +224,6 @@ while (mimeword)
 
   if (mimeword != string)
     yield = string_catn(yield, string, mimeword - string);
-/*XXX that might have to convert an untainted string to a tainted one */
 
   /* Do a charset translation if required. This is supported only on hosts
   that have the iconv() function. Translation errors set error, but carry on,
@@ -231,13 +233,17 @@ while (mimeword)
   robust. */
 
 #if HAVE_ICONV
-  *q1 = 0;
-  if (target && strcmpic(target, mimeword+2) != 0)
-    if ((icd = iconv_open(CS target, CS(mimeword+2))) == (iconv_t)-1)
+  if (target)
+    {
+    const uschar * src_chset = mimeword + 2;
+    src_chset = string_copyn(src_chset, q1 - src_chset); /* need nul-term str */
+
+    if (  strcmpic(target, src_chset) != 0
+       && (icd = iconv_open(CCS target, CCS src_chset)) == (iconv_t)-1)
       *error = string_sprintf("iconv_open(%q, %q) failed: %s%s",
-        target, mimeword+2, strerror(errno),
-        (errno == EINVAL)? " (maybe unsupported conversion)" : "");
-  *q1 = '?';
+	target, src_chset, strerror(errno),
+	errno == EINVAL ? " (maybe unsupported conversion)" : "");
+    }
 #endif
 
   while (dlen > 0)
@@ -273,7 +279,7 @@ while (mimeword)
         }
       else
         {
-        DEBUG(D_any) debug_printf("iconv error translating \"%.*s\" to %s: "
+        DEBUG(any) debug_printf("iconv error translating \"%.*s\" to %s: "
         "%s\n", (int)(endword + 2 - mimeword), mimeword, target, strerror(errno));
         }
       }
@@ -313,7 +319,7 @@ while (mimeword)
   mimeword = decode_mimeword(string, lencheck, &q1, &q2, &endword, &dlen, &dptr);
   if (mimeword)
     {
-    uschar * s = string;
+    const uschar * s = string;
     Uskip_whitespace(&s);
     if (s == mimeword) string = s;
     }
@@ -333,9 +339,9 @@ return string_from_gstring(yield);
 /* This is the stub that provides the original interface without the sizeptr
 argument. */
 
-uschar *
-rfc2047_decode(uschar *string, BOOL lencheck, const uschar *target, int zeroval,
-  int *lenptr, uschar **error)
+const uschar *
+rfc2047_decode(const uschar *string, BOOL lencheck, const uschar *target,
+  int zeroval, int *lenptr, uschar **error)
 {
 return rfc2047_decode2(string, lencheck, target, zeroval, lenptr, NULL, error);
 }
diff --git a/src/src/route.c b/src/src/route.c
index 534198a85..ea87c2618 100644
--- a/src/src/route.c
+++ b/src/src/route.c
@@ -35,16 +35,6 @@ optionlist optionlist_routers[] = {
                  LOFF(address_data) },
   { "address_test",       opt_bool|opt_public,
                  LOFF(address_test) },
-#ifdef EXPERIMENTAL_BRIGHTMAIL
-  { "bmi_deliver_alternate",   opt_bool | opt_public,
-                 LOFF(bmi_deliver_alternate) },
-  { "bmi_deliver_default",   opt_bool | opt_public,
-                 LOFF(bmi_deliver_default) },
-  { "bmi_dont_deliver",   opt_bool | opt_public,
-                 LOFF(bmi_dont_deliver) },
-  { "bmi_rule",           opt_stringptr|opt_public,
-                 LOFF(bmi_rule) },
-#endif
   { "cannot_route_message", opt_stringptr | opt_public,
                  LOFF(cannot_route_message) },
   { "caseful_local_part", opt_bool | opt_public,
@@ -247,11 +237,11 @@ for (rr = routers; rr; rr = rr->drinst.next)
   }
 
 if (!rr)
-  log_write_die(0, LOG_CONFIG,
+  log_write_die(LOG_CONFIG,
     "new_router %q not found for %q router", name, r->drinst.name);
 
 if (after && !afterthis)
-  log_write_die(0, LOG_CONFIG,
+  log_write_die(LOG_CONFIG,
     "new_router %q does not follow %q router", name, r->drinst.name);
 }
 
@@ -308,11 +298,11 @@ for (router_instance * r = routers; r; r = r->drinst.next)
   /* Check for transport or no transport on certain routers */
 
   if (ri->ri_flags & ri_yestransport && !r->transport_name && !r->verify_only)
-    log_write_die(0, LOG_CONFIG, "%s router:\n  "
+    log_write_die(LOG_CONFIG, "%s router:\n  "
       "a transport is required for this router", r->drinst.name);
 
   if (ri->ri_flags & ri_notransport && r->transport_name)
-    log_write_die(0, LOG_CONFIG, "%s router:\n  "
+    log_write_die(LOG_CONFIG, "%s router:\n  "
       "a transport must not be defined for this router", r->drinst.name);
 
   /* The "self" option needs to be decoded into a code value and possibly a
@@ -337,7 +327,7 @@ for (router_instance * r = routers; r; r = r->drinst.next)
     r->self_code = self_reroute;
     }
 
-  else log_write_die(0, LOG_CONFIG_FOR, "%s router:\n  "
+  else log_write_die(LOG_CONFIG_FOR, "%s router:\n  "
       "%s is not valid for the self option", r->drinst.name, s);
 
   /* If any router has check_local_user set, default retry_use_local_part
@@ -367,7 +357,7 @@ for (router_instance * r = routers; r; r = r->drinst.next)
     set_router(r, r->pass_router_name, &(r->pass_router), TRUE);
 
 #ifdef notdef
-  DEBUG(D_route) debug_printf_indent("DSN: %s %s\n", r->name,
+  DEBUG(route) debug_printf_indent("DSN: %s %s\n", r->name,
 	r->dsn_lasthop ? "lasthop set" : "propagating DSN");
 #endif
   }
@@ -401,8 +391,9 @@ for (router_instance * r = routers; r; r = r->drinst.next)
 *************************************************/
 
 /* This function is handed a local part and a list of possible prefixes; if any
-one matches, return the prefix length. A prefix beginning with '*' is a
-wildcard.
+one matches, return the prefix length.
+A prefix beginning with '*' is a greedy wildcard (longest match), one beginning
+with '~' is a nongreedy wildcard.
 
 Arguments:
   local_part    the local part to check
@@ -417,16 +408,13 @@ route_check_prefix(const uschar * local_part, const uschar * prefixes,
   unsigned * vp)
 {
 int sep = 0;
-uschar *prefix;
-const uschar *listptr = prefixes;
-
-while ((prefix = string_nextinlist(&listptr, &sep, NULL, 0)))
+for (const uschar * prefix; prefix = string_nextinlist(&prefixes,&sep,NULL,0); )
   {
-  int plen = Ustrlen(prefix);
-  if (prefix[0] == '*')
+  unsigned plen = Ustrlen(prefix);
+  if (*prefix == '*')
     {
-    prefix++;
-    for (const uschar * p = local_part + Ustrlen(local_part) - (--plen);
+    prefix++; plen--;
+    for (const uschar * p = local_part + Ustrlen(local_part) - plen;
          p >= local_part; p--)
       if (strncmpic(prefix, p, plen) == 0)
 	{
@@ -435,6 +423,16 @@ while ((prefix = string_nextinlist(&listptr, &sep, NULL, 0)))
 	return plen + vlen;
 	}
     }
+  else if (*prefix == '~')
+    {
+    const uschar * p = strstric(local_part, ++prefix, FALSE);
+    if (p)
+      {
+      unsigned vlen = p - local_part;
+      if (vp) *vp = vlen;
+      return plen-1 + vlen;
+      }
+    }
   else
     if (strncmpic(prefix, local_part, plen) == 0)
       {
@@ -453,8 +451,9 @@ return 0;
 *************************************************/
 
 /* This function is handed a local part and a list of possible suffixes;
-if any one matches, return the suffix length. A suffix ending with '*'
-is a wildcard.
+if any one matches, return the suffix length.
+A suffix ending with '*' is a greedy wildcard (longest match), one ending
+with '~' is a nongreedy wildcard.
 
 Arguments:
   local_part    the local part to check
@@ -469,31 +468,47 @@ route_check_suffix(const uschar * local_part, const uschar * suffixes,
   unsigned * vp)
 {
 int sep = 0, alen = Ustrlen(local_part);
-const uschar * listptr = suffixes, * suffix;
+unsigned suffix_len, vlen;
 
-while ((suffix = string_nextinlist(&listptr, &sep, NULL, 0)))
+for (uschar * suffix; suffix = string_nextinlist(&suffixes, &sep, NULL, 0); )
   {
   int slen = Ustrlen(suffix);
-  if (suffix[slen-1] == '*')
+  uschar c = suffix[slen-1];
+  if (c == '*')
+    {
+    for (const uschar * p = local_part, * pend = p + alen - (--slen) + 1;
+	 p < pend; p++)
+      if (strncmpic(suffix, p, slen) == 0)
+	{
+	suffix_len = alen - (p - local_part);
+	vlen = suffix_len - slen;
+	goto out;
+	}
+    }
+  else if (c == '~')
     {
-    const uschar * pend = local_part + alen - (--slen) + 1;
-    for (const uschar * p = local_part; p < pend; p++)
+    for (const uschar * p = local_part + alen - (--slen) + 1;
+	 p >= local_part; p--)
       if (strncmpic(suffix, p, slen) == 0)
 	{
-	int tlen = alen - (p - local_part);
-	if (vp) *vp = tlen - slen;
-	return tlen;
+	suffix_len = alen - (p - local_part);
+	vlen = suffix_len - slen;
+	goto out;
 	}
     }
   else
     if (alen > slen && strncmpic(suffix, local_part + alen - slen, slen) == 0)
       {
-      if (vp) *vp = 0;
-      return slen;
+      suffix_len = slen; vlen = 0;
+      goto out;
       }
   }
 
 return 0;
+
+out:
+  if (vp) *vp = vlen;
+  return suffix_len;
 }
 
 
@@ -532,7 +547,7 @@ route_check_dls(const uschar * rname, const uschar * type, const uschar * list,
 {
 if (!list) return OK;   /* Empty list always succeeds */
 
-DEBUG(D_route) debug_printf_indent("checking %s\n", type);
+DEBUG(route) debug_printf_indent("checking %s\n", type);
 
 /* The domain and local part use the same matching function, whereas sender
 has its own code. */
@@ -549,13 +564,13 @@ switch(domloc
 
   case FAIL:
     *perror = string_sprintf("%s router skipped: %s mismatch", rname, type);
-    DEBUG(D_route) debug_printf_indent("%s\n", *perror);
+    DEBUG(route) debug_printf_indent("%s\n", *perror);
     return SKIP;
 
   default:      /* Paranoia, and keeps compilers happy */
   case DEFER:
     *perror = string_sprintf("%s check lookup or other defer", type);
-    DEBUG(D_route) debug_printf_indent("%s\n", *perror);
+    DEBUG(route) debug_printf_indent("%s\n", *perror);
     return DEFER;
   }
 }
@@ -598,7 +613,7 @@ route_check_access(const uschar * path, uid_t uid, gid_t gid, int bits)
 struct stat statbuf;
 uschar * rp = US realpath(CCS path, CS big_buffer);
 
-DEBUG(D_route) debug_printf_indent("route_check_access(%s,%d,%d,%o)\n", path,
+DEBUG(route) debug_printf_indent("route_check_access(%s,%d,%d,%o)\n", path,
   (int)uid, (int)gid, bits);
 
 if (!rp) return FALSE;
@@ -606,7 +621,7 @@ if (!rp) return FALSE;
 for (uschar * sp = rp + 1, * slash; slash = Ustrchr(sp, '/'); sp = slash + 1)
   {
   *slash = '\0';
-  DEBUG(D_route) debug_printf_indent("stat %s\n", rp);
+  DEBUG(route) debug_printf_indent("stat %s\n", rp);
   if (Ustat(rp, &statbuf) < 0) return FALSE;
   if ((statbuf.st_mode &
        (statbuf.st_uid == uid ? 0100 : statbuf.st_gid == gid ? 0010 : 001)
@@ -620,7 +635,7 @@ for (uschar * sp = rp + 1, * slash; slash = Ustrchr(sp, '/'); sp = slash + 1)
 
 /* Down to the final component */
 
-DEBUG(D_route) debug_printf_indent("stat %s\n", rp);
+DEBUG(route) debug_printf_indent("stat %s\n", rp);
 
 if (Ustat(rp, &statbuf) < 0) return FALSE;
 
@@ -633,7 +648,7 @@ if ((statbuf.st_mode & bits) != bits)
   return FALSE;
   }
 
-DEBUG(D_route) debug_printf_indent("route_check_access() succeeded\n");
+DEBUG(route) debug_printf_indent("route_check_access() succeeded\n");
 return TRUE;
 }
 
@@ -677,7 +692,7 @@ uschar *check;
 
 if (!s) return OK;
 
-DEBUG(D_route|D_expand) debug_printf_indent("checking require_files\n");
+DEBUG(route|expand) debug_printf_indent("checking require_files\n");
 
 listptr = s;
 while ((check = string_nextinlist(&listptr, &sep, NULL, 0)))
@@ -743,7 +758,7 @@ while ((check = string_nextinlist(&listptr, &sep, NULL, 0)))
 
     /* Note that we have values set, and proceed to next item */
 
-    DEBUG(D_route)
+    DEBUG(route)
       debug_printf_indent("check subsequent files for access by %s\n", ss);
     ugid_set = TRUE;
     continue;
@@ -774,7 +789,7 @@ while ((check = string_nextinlist(&listptr, &sep, NULL, 0)))
 
   rc = Ustat(ss, &statbuf);
 
-  DEBUG(D_route)
+  DEBUG(route)
     {
     debug_printf_indent("file check: %s\n", check);
     if (ss != check) debug_printf_indent("expanded file: %s\n", ss);
@@ -793,7 +808,7 @@ while ((check = string_nextinlist(&listptr, &sep, NULL, 0)))
     pid_t pid;
     void (*oldsignal)(int);
 
-    DEBUG(D_route) debug_printf_indent("root is denied access: forking to check "
+    DEBUG(route) debug_printf_indent("root is denied access: forking to check "
       "in subprocess\n");
 
     /* Before forking, ensure that SIGCHLD is set to SIG_DFL before forking, so
@@ -809,7 +824,7 @@ while ((check = string_nextinlist(&listptr, &sep, NULL, 0)))
 
     if (pid < 0)
       {
-      DEBUG(D_route)
+      DEBUG(route)
        debug_printf_indent("require_files: fork failed: %s\n", strerror(errno));
       errno = EACCES;
       goto HANDLE_ERROR;
@@ -825,7 +840,7 @@ while ((check = string_nextinlist(&listptr, &sep, NULL, 0)))
         string_sprintf("require_files check, file=%s", ss));
       if (route_check_access(ss, uid, gid, 4))
 	exim_underbar_exit(EXIT_SUCCESS);
-      DEBUG(D_route) debug_printf_indent("route_check_access() failed\n");
+      DEBUG(route) debug_printf_indent("route_check_access() failed\n");
       exim_underbar_exit(EXIT_FAILURE);
       }
 
@@ -850,7 +865,7 @@ while ((check = string_nextinlist(&listptr, &sep, NULL, 0)))
 
   if (rc == 0 && ugid_set && !route_check_access(ss, uid, gid, 4))
     {
-    DEBUG(D_route) debug_printf_indent("route_check_access() failed\n");
+    DEBUG(route) debug_printf_indent("route_check_access() failed\n");
     rc = -1;
     }
 
@@ -862,12 +877,12 @@ while ((check = string_nextinlist(&listptr, &sep, NULL, 0)))
   HANDLE_ERROR:
   if (rc < 0)
     {
-    DEBUG(D_route) debug_printf_indent("errno = %d\n", errno);
+    DEBUG(route) debug_printf_indent("errno = %d\n", errno);
     if (errno == EACCES)
       {
       if (eacces_code == 1)
         {
-        DEBUG(D_route) debug_printf_indent("EACCES => ENOENT\n");
+        DEBUG(route) debug_printf_indent("EACCES => ENOENT\n");
         errno = ENOENT;   /* Treat as non-existent */
         }
       }
@@ -889,7 +904,7 @@ return OK;
 /* Come here on any of the errors that return DEFER. */
 
 RETURN_DEFER:
-DEBUG(D_route) debug_printf_indent("%s\n", *perror);
+DEBUG(route) debug_printf_indent("%s\n", *perror);
 return DEFER;
 }
 
@@ -942,7 +957,7 @@ f.search_find_defer = FALSE;
 
 if ((verify == v_none || verify == v_expn) && r->verify_only)
   {
-  DEBUG(D_route) debug_printf_indent("%s router skipped: verify_only set\n", rname);
+  DEBUG(route) debug_printf_indent("%s router skipped: verify_only set\n", rname);
   return SKIP;
   }
 
@@ -950,7 +965,7 @@ if ((verify == v_none || verify == v_expn) && r->verify_only)
 
 if (f.address_test_mode && !r->address_test)
   {
-  DEBUG(D_route) debug_printf_indent("%s router skipped: address_test is unset\n",
+  DEBUG(route) debug_printf_indent("%s router skipped: address_test is unset\n",
     rname);
   return SKIP;
   }
@@ -961,7 +976,7 @@ set. */
 if ((verify == v_sender && !r->verify_sender) ||
     (verify == v_recipient && !r->verify_recipient))
   {
-  DEBUG(D_route) debug_printf_indent("%s router skipped: verify %d %d %d\n",
+  DEBUG(route) debug_printf_indent("%s router skipped: verify %d %d %d\n",
     rname, verify, r->verify_sender, r->verify_recipient);
   return SKIP;
   }
@@ -970,7 +985,7 @@ if ((verify == v_sender && !r->verify_sender) ||
 
 if (verify == v_expn && !r->expn)
   {
-  DEBUG(D_route) debug_printf_indent("%s router skipped: no_expn set\n", rname);
+  DEBUG(route) debug_printf_indent("%s router skipped: no_expn set\n", rname);
   return SKIP;
   }
 
@@ -1014,10 +1029,10 @@ local_user_{uid,gid} and local_part_data.  */
 
 if (r->check_local_user)
   {
-  DEBUG(D_route) debug_printf_indent("checking for local user\n");
+  DEBUG(route) debug_printf_indent("checking for local user\n");
   if (!route_finduser(addr->local_part, pw, NULL))
     {
-    DEBUG(D_route) debug_printf_indent("%s router skipped: %s is not a local user\n",
+    DEBUG(route) debug_printf_indent("%s router skipped: %s is not a local user\n",
       rname, addr->local_part);
     return SKIP;
     }
@@ -1070,7 +1085,7 @@ debug_print_string(r->debug_string);
 
 if ((rc = check_files(r->require_files, perror)) != OK)
   {
-  DEBUG(D_route) debug_printf_indent("%s router %s: file check\n", rname,
+  DEBUG(route) debug_printf_indent("%s router %s: file check\n", rname,
     rc == SKIP ? "skipped" : "deferred");
   return rc;
   }
@@ -1079,64 +1094,22 @@ if ((rc = check_files(r->require_files, perror)) != OK)
 
 if (r->condition)
   {
-  DEBUG(D_route|D_expand)
+  DEBUG(route|expand)
     debug_printf_indent("checking \"condition\" \"%.80s\"...\n", r->condition);
   if (!expand_check_condition(r->condition, rname, US"router"))
     {
     if (f.search_find_defer)
       {
       *perror = US"condition check lookup defer";
-      DEBUG(D_route) debug_printf_indent("%s\n", *perror);
+      DEBUG(route) debug_printf_indent("%s\n", *perror);
       return DEFER;
       }
-    DEBUG(D_route)
+    DEBUG(route)
       debug_printf_indent("%s router skipped: condition failure\n", rname);
     return SKIP;
     }
   }
 
-#ifdef EXPERIMENTAL_BRIGHTMAIL
-/* check if a specific Brightmail AntiSpam rule fired on the message */
-if (r->bmi_rule)
-  {
-  DEBUG(D_route) debug_printf_indent("checking bmi_rule\n");
-  if (bmi_check_rule(bmi_base64_verdict, r->bmi_rule) == 0)
-    {    /* none of the rules fired */
-    DEBUG(D_route)
-      debug_printf_indent("%s router skipped: none of bmi_rule rules fired\n", rname);
-    return SKIP;
-    }
-  }
-
-/* check if message should not be delivered */
-if (r->bmi_dont_deliver && bmi_deliver == 1)
-  {
-  DEBUG(D_route)
-    debug_printf_indent("%s router skipped: bmi_dont_deliver is FALSE\n", rname);
-  return SKIP;
-  }
-
-/* check if message should go to an alternate location */
-if (  r->bmi_deliver_alternate
-   && (bmi_deliver == 0 || !bmi_alt_location)
-   )
-  {
-  DEBUG(D_route)
-    debug_printf_indent("%s router skipped: bmi_deliver_alternate is FALSE\n", rname);
-  return SKIP;
-  }
-
-/* check if message should go to default location */
-if (  r->bmi_deliver_default
-   && (bmi_deliver == 0 || bmi_alt_location)
-   )
-  {
-  DEBUG(D_route)
-    debug_printf_indent("%s router skipped: bmi_deliver_default is FALSE\n", rname);
-  return SKIP;
-  }
-#endif
-
 /* All the checks passed. */
 
 return OK;
@@ -1184,7 +1157,7 @@ route_finduser(const uschar *s, struct passwd **pw, uid_t *return_uid)
 {
 BOOL cache_set = (Ustrcmp(lastname, s) == 0);
 
-DEBUG(D_uid) debug_printf_indent("seeking password data for user %q: %s\n", s,
+DEBUG(uid) debug_printf_indent("seeking password data for user %q: %s\n", s,
   cache_set ? "using cached result" : "cache not available");
 
 if (!cache_set)
@@ -1203,7 +1176,7 @@ if (!cache_set)
 
   if (max_username_length > 0 && Ustrlen(lastname) > max_username_length)
     {
-    DEBUG(D_uid) debug_printf_indent("forced failure of finduser(): string "
+    DEBUG(uid) debug_printf_indent("forced failure of finduser(): string "
       "length of %s is greater than %d\n", lastname, max_username_length);
     lastpw = NULL;
     }
@@ -1232,17 +1205,17 @@ if (!cache_set)
     lastpw = &pwcache;
     }
 
-  else DEBUG(D_uid) if (errno != 0)
+  else DEBUG(uid) if (errno != 0)
     debug_printf_indent("getpwnam(%s) failed: %s\n", s, strerror(errno));
   }
 
 if (!lastpw)
   {
-  DEBUG(D_uid) debug_printf_indent("getpwnam() returned NULL (user not found)\n");
+  DEBUG(uid) debug_printf_indent("getpwnam() returned NULL (user not found)\n");
   return FALSE;
   }
 
-DEBUG(D_uid) debug_printf_indent("getpwnam() succeeded uid=%d gid=%d\n",
+DEBUG(uid) debug_printf_indent("getpwnam() succeeded uid=%d gid=%d\n",
     lastpw->pw_uid, lastpw->pw_gid);
 
 if (return_uid) *return_uid = lastpw->pw_uid;
@@ -1326,7 +1299,7 @@ if (!user)
   {
   *errmsg = string_sprintf("Failed to expand user string %q for the "
     "%s %s: %s", string, driver_name, driver_type, expand_string_message);
-  log_write(0, LOG_MAIN|LOG_PANIC, "%s", *errmsg);
+  log_write(LOG_MAIN|LOG_PANIC, "%s", *errmsg);
   return FALSE;
   }
 
@@ -1334,7 +1307,7 @@ if (route_finduser(user, pw, uid)) return TRUE;
 
 *errmsg = string_sprintf("Failed to find user %q from expanded string "
   "%q for the %s %s", user, string, driver_name, driver_type);
-log_write(0, LOG_MAIN|LOG_PANIC, "%s", *errmsg);
+log_write(LOG_MAIN|LOG_PANIC, "%s", *errmsg);
 return FALSE;
 }
 
@@ -1368,7 +1341,7 @@ if (!group)
   {
   *errmsg = string_sprintf("Failed to expand group string %q for the "
     "%s %s: %s", string, driver_name, driver_type, expand_string_message);
-  log_write(0, LOG_MAIN|LOG_PANIC, "%s", *errmsg);
+  log_write(LOG_MAIN|LOG_PANIC, "%s", *errmsg);
   return FALSE;
   }
 
@@ -1376,7 +1349,7 @@ if (!route_findgroup(group, gid))
   {
   *errmsg = string_sprintf("Failed to find group %q from expanded string "
     "%q for the %s %s", group, string, driver_name, driver_type);
-  log_write(0, LOG_MAIN|LOG_PANIC, "%s", *errmsg);
+  log_write(LOG_MAIN|LOG_PANIC, "%s", *errmsg);
   yield = FALSE;
   }
 
@@ -1465,7 +1438,7 @@ new->start_router = addr->router->drinst.next;
 new->next = *addr_new;
 *addr_new = new;
 
-DEBUG(D_route) debug_printf_indent("\"unseen\" set: replicated %s\n", addr->address);
+DEBUG(route) debug_printf_indent("\"unseen\" set: replicated %s\n", addr->address);
 
 /* Make a new unique field, to distinguish from the normal one. */
 
@@ -1478,7 +1451,7 @@ are handled in the normal way. */
 
 if (addr->transport && tree_search(tree_nonrecipients, addr->unique))
   {
-  DEBUG(D_route)
+  DEBUG(route)
     debug_printf_indent("\"unseen\" delivery previously done - discarded\n");
   parent->child_count--;
   if (*paddr_remote == addr) *paddr_remote = addr->next;
@@ -1517,7 +1490,7 @@ for (uschar * ele; (ele = string_nextinlist(&varlist, &sep, NULL, 0)); )
 
   if (!name || name[0] != 'r' || name[1] != '_' || !name[2])
     {
-    log_write(0, LOG_MAIN|LOG_PANIC,
+    log_write(LOG_MAIN|LOG_PANIC,
 	"bad router variable name '%s' in router '%s'\n", name, drname);
     return FAIL;
     }
@@ -1530,17 +1503,17 @@ for (uschar * ele; (ele = string_nextinlist(&varlist, &sep, NULL, 0)); )
       {
       int yield;
       BOOL more;
-      DEBUG(D_route) debug_printf_indent("forced failure in expansion of %q "
+      DEBUG(route) debug_printf_indent("forced failure in expansion of %q "
 	  "(router variable): decline action taken\n", ele);
 
       /* Expand "more" if necessary; DEFER => an expansion failed */
 
-      if ((yield = exp_bool(addr, US"router", drname, D_route,
+      if ((yield = exp_bool(addr, US"router", drname, IS_DEBUG(route),
 		US"more", r->more, r->expand_more, &more)) != OK) return yield;
 
       if (more) return PASS;
 
-      DEBUG(D_route)
+      DEBUG(route)
 	debug_printf_indent("\"more\"=false: skipping remaining routers\n");
       router_name = NULL;
       r = NULL;
@@ -1552,7 +1525,7 @@ for (uschar * ele; (ele = string_nextinlist(&varlist, &sep, NULL, 0)); )
 	"in %s router: %s", ele, drname, expand_string_message);
       /* Caller will replace that for logging, if a DB lookup, to avoid exposing
       passwords */
-      DEBUG(D_route) debug_printf_indent("%s\n", addr->message);
+      DEBUG(route) debug_printf_indent("%s\n", addr->message);
       return f.search_find_defer ? DEFER : FAIL;
       }
 
@@ -1563,7 +1536,7 @@ for (uschar * ele; (ele = string_nextinlist(&varlist, &sep, NULL, 0)); )
     (void)tree_insertnode(root, node);
     }
   node->data.ptr = US val;
-  DEBUG(D_route) debug_printf_indent("set r_%s%s = '%s'%s\n",
+  DEBUG(route) debug_printf_indent("set r_%s%s = '%s'%s\n",
 		    name, is_tainted(name)?" (tainted)":"",
 		    val, is_tainted(val)?" (tainted)":"");
 
@@ -1611,7 +1584,7 @@ router_instance * r, * nextr;
 const uschar * old_domain = addr->domain;
 const uschar * rname_l;
 
-HDEBUG(D_route)
+HDEBUG(route)
   {
   debug_printf_indent(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
   debug_printf_indent("routing %s\n", addr->address);
@@ -1630,7 +1603,7 @@ for (r = addr->start_router ? addr->start_router : routers; r; r = nextr)
   int loopcount = 0, rc;
 
   rname_l = r->drinst.name;
-  DEBUG(D_route)
+  DEBUG(route)
     {
     expand_level--;
     debug_printf_indent("--------> %s router <--------\n", rname_l);
@@ -1684,7 +1657,7 @@ for (r = addr->start_router ? addr->start_router : routers; r; r = nextr)
 
       if (break_loop)
         {
-        DEBUG(D_route) debug_printf_indent("%s router skipped: previously routed %s\n",
+        DEBUG(route) debug_printf_indent("%s router skipped: previously routed %s\n",
           rname_l, parent->address);
         loop_detected = TRUE;
         break;
@@ -1695,7 +1668,7 @@ for (r = addr->start_router ? addr->start_router : routers; r; r = nextr)
 
     if (loopcount++ > 100)
       {
-      log_write(0, LOG_MAIN|LOG_PANIC, "routing loop for %s", addr->address);
+      log_write(LOG_MAIN|LOG_PANIC, "routing loop for %s", addr->address);
       yield = DEFER;
       goto ROUTERS_LOOP_EXIT;
       }
@@ -1710,7 +1683,7 @@ for (r = addr->start_router ? addr->start_router : routers; r; r = nextr)
   addr->local_part = r->caseful_local_part
     ? addr->cc_local_part : addr->lc_local_part;
 
-  DEBUG(D_route) debug_printf_indent("local_part=%s domain=%s\n", addr->local_part,
+  DEBUG(route) debug_printf_indent("local_part=%s domain=%s\n", addr->local_part,
     addr->domain);
 
   /* Handle any configured prefix by replacing the local_part address,
@@ -1736,11 +1709,11 @@ for (r = addr->start_router ? addr->start_router : routers; r; r = nextr)
       else
 	addr->prefix = string_copyn_taint(addr->local_part, plen, GET_UNTAINTED);
       addr->local_part += plen;
-      DEBUG(D_route) debug_printf_indent("stripped prefix %s\n", addr->prefix);
+      DEBUG(route) debug_printf_indent("stripped prefix %s\n", addr->prefix);
       }
     else if (!r->prefix_optional)
       {
-      DEBUG(D_route)
+      DEBUG(route)
 	debug_printf_indent("%s router skipped: prefix mismatch\n", rname_l);
       continue;
       }
@@ -1760,11 +1733,11 @@ for (r = addr->start_router ? addr->start_router : routers; r; r = nextr)
 	: string_copy_taint(addr->local_part + lplen, GET_UNTAINTED);
       addr->suffix_v = addr->suffix + Ustrlen(addr->suffix) - vlen;
       addr->local_part = string_copyn(addr->local_part, lplen);
-      DEBUG(D_route) debug_printf_indent("stripped suffix %s\n", addr->suffix);
+      DEBUG(route) debug_printf_indent("stripped suffix %s\n", addr->suffix);
       }
     else if (!r->suffix_optional)
       {
-      DEBUG(D_route)
+      DEBUG(route)
 	debug_printf_indent("%s router skipped: suffix mismatch\n", rname_l);
       continue;
       }
@@ -1816,23 +1789,23 @@ for (r = addr->start_router ? addr->start_router : routers; r; r = nextr)
 
   if (r->address_data)
     {
-    DEBUG(D_route|D_expand) debug_printf_indent("processing address_data\n");
+    DEBUG(route|expand) debug_printf_indent("processing address_data\n");
     if (!(deliver_address_data = expand_string(r->address_data)))
       {
       if (f.expand_string_forcedfail)
         {
-        DEBUG(D_route) debug_printf_indent("forced failure in expansion of %q "
+        DEBUG(route) debug_printf_indent("forced failure in expansion of %q "
             "(address_data): decline action taken\n", r->address_data);
 
         /* Expand "more" if necessary; DEFER => an expansion failed */
 
-        yield = exp_bool(addr, US"router", rname_l, D_route,
+        yield = exp_bool(addr, US"router", rname_l, IS_DEBUG(route),
 			US"more", r->more, r->expand_more, &more);
         if (yield != OK) goto ROUTERS_LOOP_EXIT;
 
         if (!more)
           {
-          DEBUG(D_route)
+          DEBUG(route)
             debug_printf_indent("\"more\"=false: skipping remaining routers\n");
 	  driver_srcfile = router_name = NULL; driver_srcline = 0;
           r = NULL;
@@ -1876,12 +1849,12 @@ for (r = addr->start_router ? addr->start_router : routers; r; r = nextr)
   if (r->dsn_lasthop && !(addr->dsn_flags & rf_dsnlasthop))
     {
     addr->dsn_flags |= rf_dsnlasthop;
-    HDEBUG(D_route) debug_printf_indent("DSN: last hop for %s\n", addr->address);
+    HDEBUG(route) debug_printf_indent("DSN: last hop for %s\n", addr->address);
     }
 
   /* Run the router, and handle the consequences. */
 
-  HDEBUG(D_route) debug_printf_indent("calling %s router\n", rname_l);
+  HDEBUG(route) debug_printf_indent("calling %s router\n", rname_l);
 
     {
     router_info * ri = r->drinst.info;
@@ -1893,7 +1866,7 @@ for (r = addr->start_router ? addr->start_router : routers; r; r = nextr)
 
   if (yield == FAIL)
     {
-    HDEBUG(D_route) debug_printf_indent("%s router forced address failure\n", rname_l);
+    HDEBUG(route) debug_printf_indent("%s router forced address failure\n", rname_l);
     goto ROUTERS_LOOP_EXIT;
     }
 
@@ -1917,7 +1890,7 @@ for (r = addr->start_router ? addr->start_router : routers; r; r = nextr)
 
   if (yield != PASS && yield != DECLINE) break;
 
-  HDEBUG(D_route)
+  HDEBUG(route)
     {
     debug_printf_indent("%s router %s for %s\n", rname_l,
       yield == PASS ? "passed" : "declined", addr->address);
@@ -1937,13 +1910,13 @@ for (r = addr->start_router ? addr->start_router : routers; r; r = nextr)
     {
     /* Expand "more" if necessary */
 
-    yield = exp_bool(addr, US"router", rname_l, D_route,
+    yield = exp_bool(addr, US"router", rname_l, IS_DEBUG(route),
 		       	US"more", r->more, r->expand_more, &more);
     if (yield != OK) goto ROUTERS_LOOP_EXIT;
 
     if (!more)
       {
-      HDEBUG(D_route)
+      HDEBUG(route)
         debug_printf_indent("\"more\" is false: skipping remaining routers\n");
       r = NULL;
       break;
@@ -1960,7 +1933,8 @@ running a router go direct to ROUTE_EXIT from code above. */
 
 if (!r)
   {
-  HDEBUG(D_route) debug_printf_indent("no more routers\n");
+  HDEBUG(route) debug_printf_indent("no more routers\n");
+  addr->basic_errno = ERRNO_NOROUTER;
   if (!addr->message)
     {
     uschar * message = US"Unrouteable address";
@@ -1974,7 +1948,7 @@ if (!r)
 	  message = s;
 	else
 	  if (!f.expand_string_forcedfail)
-	    log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
+	    log_write(LOG_MAIN|LOG_PANIC, "failed to expand "
 	      "cannot_route_message in %s router: %s",
 	      addr->router->drinst.name,
 	      expand_string_message);
@@ -1989,7 +1963,7 @@ if (!r)
 
 if (yield == DEFER)
   {
-  HDEBUG(D_route) debug_printf_indent("%s router: defer for %s\n  message: %s\n",
+  HDEBUG(route) debug_printf_indent("%s router: defer for %s\n  message: %s\n",
       rname_l, addr->address, addr->message ? addr->message : US"<none>");
   goto ROUTE_EXIT;
   }
@@ -1999,7 +1973,7 @@ if (yield == DISCARD) goto ROUTE_EXIT;
 /* The yield must be either OK or REROUTED. */
 
 if (yield != OK && yield != REROUTED)
-  log_write_die(0, LOG_MAIN, "%s router returned unknown value %d",
+  log_write_die(LOG_MAIN, "%s router returned unknown value %d",
     rname_l, yield);
 
 /* If the yield was REROUTED, the router put a child address on the new chain
@@ -2007,7 +1981,7 @@ as a result of a domain change of some sort (widening, typically). */
 
 if (yield == REROUTED)
   {
-  HDEBUG(D_route) debug_printf_indent("re-routed to %s\n", addr->address);
+  HDEBUG(route) debug_printf_indent("re-routed to %s\n", addr->address);
   yield = OK;
   goto ROUTE_EXIT;
   }
@@ -2046,7 +2020,7 @@ if (r->translate_ip_address)
       goto ROUTE_EXIT;
       }
 
-    DEBUG(D_route) debug_printf_indent("%s [%s] translated to %s\n",
+    DEBUG(route) debug_printf_indent("%s [%s] translated to %s\n",
       h->name, h->address, newaddress);
     if (string_is_ip_address(newaddress, NULL) != 0)
       {
@@ -2079,15 +2053,15 @@ if (r->translate_ip_address)
 /* See if this is an unseen routing; first expand the option if necessary.
 DEFER can be given if the expansion fails */
 
-if ((yield = exp_bool(addr, US"router", rname_l, D_route,
+if ((yield = exp_bool(addr, US"router", rname_l, IS_DEBUG(route),
       US"unseen", r->unseen, r->expand_unseen, &unseen)) != OK) goto ROUTE_EXIT;
 
 /* Debugging output recording a successful routing */
 
-HDEBUG(D_route) debug_printf_indent("routed by %s router%s\n", rname_l,
+HDEBUG(route) debug_printf_indent("routed by %s router%s\n", rname_l,
     unseen ? " (unseen)" : "");
 
-DEBUG(D_route)
+DEBUG(route)
   {
   debug_printf_indent("  envelope to:\t%s\n", addr->address);
   debug_printf_indent("  transport:\t%s\n", addr->transport
diff --git a/src/src/routers/Makefile b/src/src/routers/Makefile
index 6c1dc6e9a..32515fcf8 100644
--- a/src/src/routers/Makefile
+++ b/src/src/routers/Makefile
@@ -12,7 +12,8 @@
 
 # MAGIC-TAG-MODS-OBJ-RULES-GO-HERE
 
-all:             routers.a $(MODS)
+all:	routers.a $(MODS)
+	$(FE):
 
 routers.a:       $(OBJ)
 		 @$(RM_COMMAND) -f routers.a
@@ -25,7 +26,8 @@ routers.a:       $(OBJ)
 		 $(FE)$(CC) -c $(CFLAGS) $(INCLUDE) $*.c
 
 .c.so:;          @echo "$(CC) -shared $*.c"
-		 $(FE)$(CC) -DDYNLOOKUP $(CFLAGS_DYNAMIC) $(CFLAGS) $(INCLUDE) $(DLFLAGS) $*.c -o $@
+		 $(FE)$(CC) -DDYNLOOKUP $(CFLAGS_DYNAMIC) $(CFLAGS) $(INCLUDE) \
+			$(DLFLAGS) $*.c -o $@
 
 
 
diff --git a/src/src/routers/accept.c b/src/src/routers/accept.c
index 09992b85f..c5846d897 100644
--- a/src/src/routers/accept.c
+++ b/src/src/routers/accept.c
@@ -112,7 +112,7 @@ const uschar * errors_to;
 uschar * remove_headers;
 header_line * extra_headers;
 
-DEBUG(D_route) debug_printf_indent("%s router called for %s\n  domain = %s\n",
+DEBUG(route) debug_printf_indent("%s router called for %s\n  domain = %s\n",
   rblock->drinst.name, addr->address, addr->domain);
 
 /* Set up the errors address, if any. */
@@ -156,7 +156,7 @@ router_info accept_router_info =
   .options_block =	&accept_router_option_defaults,
   .options_len =	sizeof(accept_router_options_block),
   .init =		accept_router_init,
-# ifdef DYNLOOKUP
+# if ROUTER_ACCEPT==2
   .dyn_magic =		ROUTER_MAGIC,
 # endif
   },
diff --git a/src/src/routers/dnslookup.c b/src/src/routers/dnslookup.c
index 97e273dca..b9d531528 100644
--- a/src/src/routers/dnslookup.c
+++ b/src/src/routers/dnslookup.c
@@ -152,7 +152,7 @@ const uschar * pre_widen = addr->domain, * post_widen = NULL;
 const uschar * fully_qualified_name, * listptr;
 uschar widen_buffer[256];
 
-DEBUG(D_route)
+DEBUG(route)
   debug_printf_indent("%s router called for %s\n  domain = %s\n",
     rblock->drinst.name, addr->address, addr->domain);
 
@@ -229,14 +229,14 @@ for (;;)
     /* not expanded so should never be tainted */
     widen = string_nextinlist(&listptr, &widen_sep, widen_buffer,
       sizeof(widen_buffer));
-    DEBUG(D_route) debug_printf("%s router widened %s to %s\n",
+    DEBUG(route) debug_printf("%s router widened %s to %s\n",
       rblock->drinst.name, addr->domain, h.name);
     }
   else if (post_widen)
     {
     h.name = post_widen;
     post_widen = NULL;
-    DEBUG(D_route) debug_printf("%s router trying %s after widening failed\n",
+    DEBUG(route) debug_printf("%s router trying %s after widening failed\n",
       rblock->drinst.name, h.name);
     }
   else return DECLINE;
@@ -279,7 +279,7 @@ for (;;)
     if (ob->search_parents) flags |= HOST_FIND_SEARCH_PARENTS;
     }
 
-  DEBUG(D_route) debug_printf_indent("main lookup for domain\n");
+  DEBUG(route) debug_printf_indent("main lookup for domain\n");
    {
     expand_level++;
     rc = host_find_bydns(&h, CUS rblock->ignore_target_hosts, flags,
@@ -301,13 +301,14 @@ for (;;)
           &domainlist_anchor, addr->domain_cache, MCL_DOMAIN, TRUE, NULL))
       {
       case DEFER:
-      addr->message = US"lookup defer for mx_domains";
-      return DEFER;
+	addr->basic_errno = ERRNO_DNSDEFER;
+	addr->message = US"lookup defer for mx_domains";
+	return DEFER;
 
       case OK:
-      DEBUG(D_route) debug_printf("%s router rejected %s: no MX record(s)\n",
-        rblock->drinst.name, fully_qualified_name);
-      continue;
+	DEBUG(route) debug_printf("%s router rejected %s: no MX record(s)\n",
+	  rblock->drinst.name, fully_qualified_name);
+	continue;
       }
 
   /* Deferral returns forthwith, and anything other than failure breaks the
@@ -315,6 +316,7 @@ for (;;)
 
   if (rc == HOST_FIND_SECURITY)
     {
+    addr->basic_errno = ERRNO_DNSDEFER;
     addr->message = US"host lookup done insecurely";
     return DEFER;
     }
@@ -322,10 +324,11 @@ for (;;)
     {
     if (rblock->pass_on_timeout)
       {
-      DEBUG(D_route) debug_printf("%s router timed out, and pass_on_timeout is set\n",
+      DEBUG(route) debug_printf("%s router timed out, and pass_on_timeout is set\n",
         rblock->drinst.name);
       return PASS;
       }
+    addr->basic_errno = ERRNO_DNSDEFER;
     addr->message = US"host lookup did not complete";
     return DEFER;
     }
@@ -338,12 +341,14 @@ for (;;)
 	  &domainlist_anchor, addr->domain_cache, MCL_DOMAIN, TRUE, NULL))
       {
       case DEFER:
+	addr->basic_errno = ERRNO_DNSDEFER;
 	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",
+	DEBUG(route) debug_printf("%s router: matched fail_defer_domains\n",
 	  rblock->drinst.name);
+	addr->basic_errno = ERRNO_MXDEFER;
 	addr->message = US"missing MX, or all MXs point to missing A records,"
 	  " and defer requested";
 	return DEFER;
@@ -435,7 +440,7 @@ if (rc == HOST_FOUND_LOCAL)
 
 else if (ob->check_secondary_mx && !testflag(addr, af_local_host_removed))
   {
-  DEBUG(D_route) debug_printf("check_secondary_mx set and local host not secondary\n");
+  DEBUG(route) debug_printf("check_secondary_mx set and local host not secondary\n");
   return DECLINE;
   }
 
@@ -484,7 +489,7 @@ router_info dnslookup_router_info =
   .options_block =	&dnslookup_router_option_defaults,
   .options_len =	sizeof(dnslookup_router_options_block),
   .init =		dnslookup_router_init,
-# ifdef DYNLOOKUP
+# if ROUTER_DNSLOOKUP==2
   .dyn_magic =		ROUTER_MAGIC,
 # endif
   },
diff --git a/src/src/routers/ipliteral.c b/src/src/routers/ipliteral.c
index 9052f2d48..0adcc96f7 100644
--- a/src/src/routers/ipliteral.c
+++ b/src/src/routers/ipliteral.c
@@ -118,7 +118,7 @@ const uschar * ip;
 int len = Ustrlen(domain);
 int rc, ipv;
 
-DEBUG(D_route) debug_printf_indent("%s router called for %s: domain = %s\n",
+DEBUG(route) debug_printf_indent("%s router called for %s: domain = %s\n",
   rblock->drinst.name, addr->address, addr->domain);
 
 /* Check that the domain is an IP address enclosed in square brackets. Remember
@@ -141,7 +141,7 @@ but if it is set, it should probably work. */
 if (verify_check_this_host(CUSS&rblock->ignore_target_hosts,
        	NULL, domain, ip, NULL) == OK)
   {
-  DEBUG(D_route)
+  DEBUG(route)
       debug_printf("%s is in ignore_target_hosts\n", ip);
   addr->message = US"IP literal host explicitly ignored";
   return DECLINE;
@@ -218,7 +218,7 @@ router_info ipliteral_router_info =
   .options_block =	&ipliteral_router_option_defaults,
   .options_len =	sizeof(ipliteral_router_options_block),
   .init =		ipliteral_router_init,
-# ifdef DYNLOOKUP
+# if ROUTER_IPLITERAL==2
   .dyn_magic =		ROUTER_MAGIC,
 # endif
   },
diff --git a/src/src/routers/iplookup.c b/src/src/routers/iplookup.c
index 1f3945bc8..6e91d3b7d 100644
--- a/src/src/routers/iplookup.c
+++ b/src/src/routers/iplookup.c
@@ -96,11 +96,11 @@ iplookup_router_options_block * ob =
 /* A port and a host list must be given */
 
 if (ob->port < 0)
-  log_write_die(0, LOG_CONFIG_FOR, "%s router:\n  "
+  log_write_die(LOG_CONFIG_FOR, "%s router:\n  "
     "a port must be specified", r->name);
 
 if (!ob->hosts)
-  log_write_die(0, LOG_CONFIG_FOR, "%s router:\n  "
+  log_write_die(LOG_CONFIG_FOR, "%s router:\n  "
     "a host list must be specified", r->name);
 
 /* Translate protocol name into value */
@@ -109,7 +109,7 @@ if (ob->protocol_name)
   {
   if (Ustrcmp(ob->protocol_name, "udp") == 0) ob->protocol = ip_udp;
   else if (Ustrcmp(ob->protocol_name, "tcp") == 0) ob->protocol = ip_tcp;
-  else log_write_die(0, LOG_CONFIG_FOR, "%s router:\n  "
+  else log_write_die(LOG_CONFIG_FOR, "%s router:\n  "
     "protocol not specified as udp or tcp", r->name);
   }
 
@@ -173,7 +173,7 @@ const pcre2_code *re = ob->re_response_pattern;
 int count, query_len, rc;
 int sep = 0;
 
-DEBUG(D_route) debug_printf_indent("%s router called for %s: domain = %s\n",
+DEBUG(route) debug_printf_indent("%s router called for %s: domain = %s\n",
   rblock->drinst.name, addr->address, addr->domain);
 
 reply = store_get(256, GET_TAINTED);
@@ -194,7 +194,7 @@ else
     }
 
 query_len = Ustrlen(query);
-DEBUG(D_route) debug_printf("%s router query is %q\n", rblock->drinst.name,
+DEBUG(route) debug_printf("%s router query is %q\n", rblock->drinst.name,
   string_printing(query));
 
 /* Now connect to the required port for each of the hosts in turn, until a
@@ -208,7 +208,7 @@ while ((hostname = string_nextinlist(&listptr, &sep, host_buffer,
   {
   host_item *h;
 
-  DEBUG(D_route) debug_printf("calling host %s\n", hostname);
+  DEBUG(route) debug_printf("calling host %s\n", hostname);
 
   host->name = hostname;
   host->address = NULL;
@@ -260,7 +260,7 @@ while ((hostname = string_nextinlist(&listptr, &sep, host_buffer,
 		ob->protocol == ip_udp ? NULL : &tcp_fastopen_nodata) < 0)
       {
       close(query_cctx.sock);
-      DEBUG(D_route)
+      DEBUG(route)
         debug_printf("connection to %s failed: %s\n", h->address,
           strerror(errno));
       continue;
@@ -270,7 +270,7 @@ while ((hostname = string_nextinlist(&listptr, &sep, host_buffer,
 
     if (send(query_cctx.sock, query, query_len, 0) < 0)
       {
-      DEBUG(D_route) debug_printf("send to %s failed\n", h->address);
+      DEBUG(route) debug_printf("send to %s failed\n", h->address);
       (void)close(query_cctx.sock);
       continue;
       }
@@ -282,7 +282,7 @@ while ((hostname = string_nextinlist(&listptr, &sep, host_buffer,
     (void)close(query_cctx.sock);
     if (count <= 0)
       {
-      DEBUG(D_route) debug_printf("%s from %s\n", (errno == ETIMEDOUT)?
+      DEBUG(route) debug_printf("%s from %s\n", (errno == ETIMEDOUT)?
         "timed out" : "recv failed", h->address);
       *reply = 0;
       continue;
@@ -291,7 +291,7 @@ while ((hostname = string_nextinlist(&listptr, &sep, host_buffer,
     /* Success; break the loop */
 
     reply[count] = 0;
-    DEBUG(D_route) debug_printf("%s router received %q from %s\n",
+    DEBUG(route) debug_printf("%s router received %q from %s\n",
       rblock->drinst.name, string_printing(reply), h->address);
     break;
     }
@@ -310,7 +310,7 @@ defer otherwise. */
 
 if (!hostname)
   {
-  DEBUG(D_route) debug_printf("%s router failed to get anything\n", rblock->drinst.name);
+  DEBUG(route) debug_printf("%s router failed to get anything\n", rblock->drinst.name);
   if (ob->optional) return PASS;
   addr->message = string_sprintf("%s router: failed to communicate with any "
     "host", rblock->drinst.name);
@@ -326,7 +326,7 @@ if (re != NULL)
   {
   if (!regex_match_and_setup(re, reply, 0, -1))
     {
-    DEBUG(D_route) debug_printf("%s router: %s failed to match response %s\n",
+    DEBUG(route) debug_printf("%s router: %s failed to match response %s\n",
       rblock->drinst.name, ob->response_pattern, reply);
     return DECLINE;
     }
@@ -351,7 +351,7 @@ else
     while (isspace(reply[nn])) nn++;
     if (Ustrcmp(query + query_len/2 + 1, reply+nn) != 0)
       {
-      DEBUG(D_route) debug_printf("%s router: failed to match identification "
+      DEBUG(route) debug_printf("%s router: failed to match identification "
         "in response %s\n", rblock->drinst.name, reply);
       return DECLINE;
       }
@@ -382,7 +382,7 @@ else
 
 if (!(domain = Ustrchr(reroute, '@')))
   {
-  log_write(0, LOG_MAIN, "%s router: reroute string %s is not of the form "
+  log_write(LOG_MAIN, "%s router: reroute string %s is not of the form "
     "user@domain", rblock->drinst.name, reroute);
   addr->message = string_sprintf("%s router: reroute string %s is not of the "
     "form user@domain", rblock->drinst.name, reroute);
@@ -398,7 +398,7 @@ new_addr->parent = addr;
 new_addr->prop = addr->prop;
 
 if (addr->child_count == USHRT_MAX)
-  log_write_die(0, LOG_MAIN, "%s router generated more than %d "
+  log_write_die(LOG_MAIN, "%s router generated more than %d "
     "child addresses for <%s>", rblock->drinst.name, USHRT_MAX, addr->address);
 addr->child_count++;
 new_addr->next = *addr_new;
@@ -433,7 +433,7 @@ router_info iplookup_router_info =
   .options_block =	&iplookup_router_option_defaults,
   .options_len =	sizeof(iplookup_router_options_block),
   .init =		iplookup_router_init,
-# ifdef DYNLOOKUP
+# if ROUTER_IPLOOKUP==2
   .dyn_magic =		ROUTER_MAGIC,
 # endif
   },
diff --git a/src/src/routers/manualroute.c b/src/src/routers/manualroute.c
index 5a4c72bb0..4e335c8e4 100644
--- a/src/src/routers/manualroute.c
+++ b/src/src/routers/manualroute.c
@@ -106,7 +106,7 @@ for (int i = 0; i < hff_count; i++)
     break;
     }
 if (ob->hff_code < 0)
-  log_write_die(0, LOG_CONFIG_FOR, "%s router:\n  "
+  log_write_die(LOG_CONFIG_FOR, "%s router:\n  "
     "unrecognized setting for host_find_failed option", rblock->name);
 
 for (int i = 1; i < hff_count; i++)   /* NB starts at 1 to skip "ignore" */
@@ -116,14 +116,14 @@ for (int i = 1; i < hff_count; i++)   /* NB starts at 1 to skip "ignore" */
     break;
     }
 if (ob->hai_code < 0)
-  log_write_die(0, LOG_CONFIG_FOR, "%s router:\n  "
+  log_write_die(LOG_CONFIG_FOR, "%s router:\n  "
     "unrecognized setting for host_all_ignored option", rblock->name);
 
 /* One of route_list or route_data must be specified */
 
 if (  !ob->route_list && !ob->route_data
    || ob->route_list && ob->route_data)
-  log_write_die(0, LOG_CONFIG_FOR, "%s router:\n  "
+  log_write_die(LOG_CONFIG_FOR, "%s router:\n  "
     "route_list or route_data (but not both) must be specified",
     rblock->name);
 }
@@ -248,7 +248,7 @@ manualroute_router_options_block * ob =
 transport_instance * transport = NULL;
 BOOL individual_transport_set = FALSE, randomize;
 
-DEBUG(D_route) debug_printf_indent("%s router called for %s\n  domain = %s\n",
+DEBUG(route) debug_printf_indent("%s router called for %s\n  domain = %s\n",
   rblock->drinst.name, addr->address, addr->domain);
 
 /* The initialization check ensures that either route_list or route_data is
@@ -263,7 +263,7 @@ if (ob->route_list)
     {
     int rc;
 
-    DEBUG(D_route) debug_printf("route_item = %s\n", route_item);
+    DEBUG(route) debug_printf_indent("route_item = %s\n", route_item);
     if (!parse_route_item(route_item, &domain, &hostlist, &options))
       continue;     /* Ignore blank items */
 
@@ -276,10 +276,7 @@ if (ob->route_list)
     /* If there was a problem doing the check, defer */
 
     if (rc == DEFER)
-      {
-      addr->message = US"lookup defer in route_list";
-      return DEFER;
-      }
+      { addr->message = US"lookup defer in route_list"; return DEFER; }
     }
 
   if (!route_item) return DECLINE;  /* No pattern in the list matched */
@@ -301,7 +298,7 @@ else
 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_indent("original list of hosts = '%s' options = '%s'\n",
+DEBUG(route) debug_printf_indent("original list of hosts = '%s' options = '%s'\n",
   hostlist, options);
 
 newhostlist = expand_string_copy(hostlist);
@@ -320,12 +317,12 @@ if (!newhostlist)
   }
 else hostlist = newhostlist;
 
-DEBUG(D_route) debug_printf_indent("expanded list of hosts = '%s' options = '%s'\n",
+DEBUG(route) debug_printf_indent("expanded list of hosts = '%s' options = '%s'\n",
   hostlist, options);
 
 /* Get the hosts_randomize router option, expanding if needed */
 
-if (exp_bool(addr, US"router", rblock->drinst.name, D_route,
+if (exp_bool(addr, US"router", rblock->drinst.name, IS_DEBUG(route),
 	  US"hosts_randomize", ob->hosts_randomize, ob->expand_hosts_randomize,
 	  &randomize) != OK)
   return DEFER;
@@ -364,7 +361,7 @@ while (*options)
     if (!t)
       {
       s = string_sprintf("unknown routing option or transport name %q", s);
-      log_write(0, LOG_MAIN, "Error in %s router: %s", rblock->drinst.name, s);
+      log_write(LOG_MAIN, "Error in %s router: %s", rblock->drinst.name, s);
       addr->message = string_sprintf("error in router: %s", s);
       return DEFER;
       }
@@ -441,7 +438,7 @@ if (!hostlist[0])
   if (verify != v_none) goto ROUTED;
   addr->message = string_sprintf("error in %s router: no host(s) specified "
     "for domain %s", rblock->drinst.name, addr->domain);
-  log_write(0, LOG_MAIN, "%s", addr->message);
+  log_write(LOG_MAIN, "%s", addr->message);
   return DEFER;
   }
 
@@ -460,7 +457,7 @@ is controlled by host_all_ignored. */
 if (!addr->host_list)
   {
   int i;
-  DEBUG(D_route) debug_printf("host_find_failed ignored every host\n");
+  DEBUG(route) debug_printf("host_find_failed ignored every host\n");
   if (ob->hai_code == hff_decline) return DECLINE;
   if (ob->hai_code == hff_pass) return PASS;
 
@@ -484,7 +481,7 @@ dealt with above. However, we don't need one if verifying only. */
 
 if (!transport && verify == v_none)
     {
-    log_write(0, LOG_MAIN, "Error in %s router: no transport defined",
+    log_write(LOG_MAIN, "Error in %s router: no transport defined",
       rblock->drinst.name);
     addr->message = US"error in router: transport missing";
     return DEFER;
@@ -517,7 +514,7 @@ router_info manualroute_router_info =
   .options_block =	&manualroute_router_option_defaults,
   .options_len =	sizeof(manualroute_router_options_block),
   .init =		manualroute_router_init,
-# ifdef DYNLOOKUP
+# if ROUTER_MANUALROUTE==2
   .dyn_magic =		ROUTER_MAGIC,
 # endif
   },
diff --git a/src/src/routers/queryprogram.c b/src/src/routers/queryprogram.c
index 92a2524ab..e13abaa45 100644
--- a/src/src/routers/queryprogram.c
+++ b/src/src/routers/queryprogram.c
@@ -90,13 +90,13 @@ queryprogram_router_options_block *ob =
 /* A command must be given */
 
 if (!ob->command)
-  log_write_die(0, LOG_CONFIG_FOR, "%s router:\n  "
+  log_write_die(LOG_CONFIG_FOR, "%s router:\n  "
     "a command specification is required", rblock->name);
 
 /* A uid/gid must be supplied */
 
 if (!ob->cmd_uid_set && !ob->expand_cmd_uid)
-  log_write_die(0, LOG_CONFIG_FOR, "%s router:\n  "
+  log_write_die(LOG_CONFIG_FOR, "%s router:\n  "
     "command_user must be specified", rblock->name);
 }
 
@@ -141,11 +141,11 @@ while (generated != NULL)
   *addr_new = next;
 
   if (addr->child_count == USHRT_MAX)
-    log_write_die(0, LOG_MAIN, "%s router generated more than %d "
+    log_write_die(LOG_MAIN, "%s router generated more than %d "
       "child addresses for <%s>", rblock->drinst.name, USHRT_MAX, addr->address);
   addr->child_count++;
 
-  DEBUG(D_route)
+  DEBUG(route)
     debug_printf("%s router generated %s\n", rblock->drinst.name, next->address);
   }
 }
@@ -226,7 +226,7 @@ gid_t gid = ob->cmd_gid;
 uid_t *puid = &uid;
 gid_t *pgid = &gid;
 
-DEBUG(D_route) debug_printf_indent("%s router called for %s: domain = %s\n",
+DEBUG(route) debug_printf_indent("%s router called for %s: domain = %s\n",
   rblock->drinst.name, addr->address, addr->domain);
 
 ugid.uid_set = ugid.gid_set = FALSE;
@@ -271,14 +271,14 @@ if (!ob->cmd_gid_set)
     return DEFER;
     }
 
-DEBUG(D_route) debug_printf("requires uid=%ld gid=%ld current_directory=%s\n",
+DEBUG(route) debug_printf("requires uid=%ld gid=%ld current_directory=%s\n",
   (long int)uid, (long int)gid, current_directory);
 
 /* If we are not running as root, we will not be able to change uid/gid. */
 
 if (curr_uid != root_uid && (uid != curr_uid || gid != curr_gid))
   {
-  DEBUG(D_route)
+  DEBUG(route)
     {
     debug_printf("not running as root: cannot change uid/gid\n");
     debug_printf("subprocess will run with uid=%ld gid=%ld\n",
@@ -360,7 +360,7 @@ the result. */
 while (len > 0 && isspace(buffer[len-1])) len--;
 buffer[len] = 0;
 
-DEBUG(D_route) debug_printf("command wrote: %s\n", buffer);
+DEBUG(route) debug_printf("command wrote: %s\n", buffer);
 
 rword = buffer;
 Uskip_whitespace(&rword);
@@ -456,7 +456,7 @@ if (strcmpic(rword, US"accept") != 0)
   else if (strcmpic(rword, US"defer") != 0)
     {
     addr->message = string_sprintf("bad command yield: %s %s", rword, rdata);
-    log_write(0, LOG_PANIC, "%s router: %s", rblock->drinst.name, addr->message);
+    log_write(LOG_PANIC, "%s router: %s", rblock->drinst.name, addr->message);
     }
   return DEFER;
   }
@@ -479,7 +479,7 @@ if ((s = expand_getkeyed(US"transport", rdata)) && *s)
     {
     addr->message = string_sprintf("unknown transport name %s yielded by "
       "command", s);
-    log_write(0, LOG_PANIC, "%s router: %s", rblock->drinst.name, addr->message);
+    log_write(LOG_PANIC, "%s router: %s", rblock->drinst.name, addr->message);
     return DEFER;
     }
   addr->transport = transport;
@@ -511,7 +511,7 @@ if ((s = expand_getkeyed(US"hosts", rdata)) && *s)
     else
       {
       addr->message = string_sprintf("bad lookup type %q yielded by command", ss);
-      log_write(0, LOG_PANIC, "%s router: %s", rblock->drinst.name, addr->message);
+      log_write(LOG_PANIC, "%s router: %s", rblock->drinst.name, addr->message);
       return DEFER;
       }
     }
@@ -549,7 +549,7 @@ router_info queryprogram_router_info =
   .options_block =    &queryprogram_router_option_defaults,
   .options_len =      sizeof(queryprogram_router_options_block),
   .init =             queryprogram_router_init,
-# ifdef DYNLOOKUP
+# if ROUTER_QUERYPROGRAM==2
   .dyn_magic =	      ROUTER_MAGIC,
 # endif
   },
diff --git a/src/src/routers/redirect.c b/src/src/routers/redirect.c
index 6d0574c14..6a8fee9ec 100644
--- a/src/src/routers/redirect.c
+++ b/src/src/routers/redirect.c
@@ -151,7 +151,7 @@ redirect_router_options_block * ob =
 /* Either file or data must be set, but not both */
 
 if ((ob->file == NULL) == (ob->data == NULL))
-  log_write_die(0, LOG_CONFIG_FOR, "%s router:\n  "
+  log_write_die(LOG_CONFIG_FOR, "%s router:\n  "
     "%sone of \"file\" or \"data\" must be specified",
     r->name, ob->file ? "only " : "");
 
@@ -164,11 +164,11 @@ if (ob->one_time)
   {
   ob->forbid_pipe = ob->forbid_file = ob->forbid_filter_reply = TRUE;
   if (rblock->extra_headers || rblock->remove_headers)
-    log_write_die(0, LOG_CONFIG_FOR, "%s router:\n  "
+    log_write_die(LOG_CONFIG_FOR, "%s router:\n  "
       "\"headers_add\" and \"headers_remove\" are not permitted with "
       "\"one_time\"", r->name);
   if (rblock->unseen || rblock->expand_unseen)
-    log_write_die(0, LOG_CONFIG_FOR, "%s router:\n  "
+    log_write_die(LOG_CONFIG_FOR, "%s router:\n  "
       "\"unseen\" may not be used with \"one_time\"", r->name);
   }
 
@@ -188,7 +188,7 @@ if (ob->check_group == TRUE_UNSET)
 /* If explicit qualify domain set, the preserve option is locked out */
 
 if (ob->qualify_domain && ob->qualify_preserve_domain)
-  log_write_die(0, LOG_CONFIG_FOR, "%s router:\n  "
+  log_write_die(LOG_CONFIG_FOR, "%s router:\n  "
     "only one of \"qualify_domain\" or \"qualify_preserve_domain\" must be set",
     r->name);
 
@@ -198,7 +198,7 @@ if (!rblock->check_local_user &&
     !rblock->uid_set &&
     rblock->expand_uid == NULL &&
     (ob->bit_options & RDO_FILTER) != 0)
-  log_write_die(0, LOG_CONFIG_FOR, "%s router:\n  "
+  log_write_die(LOG_CONFIG_FOR, "%s router:\n  "
     "\"user\" or \"check_local_user\" must be set with \"allow_filter\"",
     r->name);
 }
@@ -289,7 +289,7 @@ while (generated)
   next->parent = addr;
   next->start_router = rblock->redirect_router;
   if (addr->child_count == USHRT_MAX)
-    log_write_die(0, LOG_MAIN, "%s router generated more than %d "
+    log_write_die(LOG_MAIN, "%s router generated more than %d "
       "child addresses for <%s>", rblock->drinst.name, USHRT_MAX, addr->address);
   addr->child_count++;
 
@@ -317,7 +317,7 @@ while (generated)
            : strcmpic(next->address, parent->address)
           ) == 0)
         {
-        DEBUG(D_route) debug_printf("generated parent replaced by child\n");
+        DEBUG(route) debug_printf("generated parent replaced by child\n");
         next->address = string_copy(addr->address);
         break;
         }
@@ -413,7 +413,7 @@ while (generated)
         || (sender_address && string_is_utf8(sender_address));
 #endif
 
-  DEBUG(D_route)
+  DEBUG(route)
     {
     debug_printf("%s router generated %s\n  %serrors_to=%s transport=%s\n",
       rblock->drinst.name,
@@ -482,19 +482,16 @@ int redirect_router_entry(
 {
 redirect_router_options_block * ob =
   (redirect_router_options_block *)(rblock->drinst.options_block);
-address_item *generated = NULL;
-const uschar *save_qualify_domain_recipient = qualify_domain_recipient;
-uschar *discarded = US"discarded";
+address_item * generated = NULL;
+const uschar * save_qualify_domain_recipient = qualify_domain_recipient;
+uschar * discarded = US"discarded";
 address_item_propagated addr_prop;
-error_block *eblock = NULL;
+error_block * eblock = NULL;
 ugid_block ugid;
 redirect_block redirect;
 sieve_block sieve;
-int filtertype = FILTER_UNSET;
-int yield = OK;
-int options = ob->bit_options;
-int frc = 0;
-int xrc = 0;
+int filtertype = FILTER_UNSET, yield = OK, options = ob->bit_options;
+int frc = 0, xrc = 0;
 
 /* Initialize the data to be propagated to the children */
 
@@ -592,7 +589,7 @@ switch (frc)
     return DECLINE;
 
   case FF_BLACKHOLE:
-    DEBUG(D_route) debug_printf("address :blackhole:d\n");
+    DEBUG(route) debug_printf("address :blackhole:d\n");
     generated = NULL;
     discarded = US":blackhole:";
     frc = FF_DELIVERED;
@@ -604,10 +601,12 @@ switch (frc)
     verifying. Remove any SMTP code if it is not allowed. */
 
   case FF_DEFER:
+    addr->basic_errno = ERRNO_ROUTERDEFER;
     yield = DEFER;
     goto SORT_MESSAGE;
 
   case FF_FAIL:
+    addr->basic_errno = ERRNO_ROUTERFAIL;
     if ((xrc = sort_errors_and_headers(rblock, addr, verify, &addr_prop)) != OK)
       return xrc;
     add_generated(rblock, addr_new, addr, generated, &addr_prop, &ugid, pw);
@@ -622,7 +621,7 @@ switch (frc)
       if (  ob->forbid_smtp_code
 	 && regex_match(regex_smtp_code, addr->message, -1, &matched))
 	{
-	DEBUG(D_route) debug_printf("SMTP code at start of error message "
+	DEBUG(route) debug_printf("SMTP code at start of error message "
 	  "is ignored because forbid_smtp_code is set\n");
 	addr->message += Ustrlen(matched);
 	}
@@ -739,7 +738,7 @@ if (frc == FF_DELIVERED)
   {
   if (generated == NULL && verify == v_none && !f.address_test_mode)
     {
-    log_write(0, LOG_MAIN, "=> %s <%s> R=%s", discarded, addr->address,
+    log_write(LOG_MAIN, "=> %s <%s> R=%s", discarded, addr->address,
       rblock->drinst.name);
     yield = DISCARD;
     }
@@ -770,7 +769,7 @@ else
 
   next->prop = addr_prop;
 
-  DEBUG(D_route) debug_printf("%s router autogenerated %s\n%s%s%s",
+  DEBUG(route) debug_printf("%s router autogenerated %s\n%s%s%s",
     rblock->drinst.name,
     next->address,
     (addr_prop.errors_address != NULL)? "  errors to " : "",
@@ -804,7 +803,7 @@ router_info redirect_router_info =
   .options_block =	&redirect_router_option_defaults,
   .options_len =	sizeof(redirect_router_options_block),
   .init =		redirect_router_init,
-# ifdef DYNLOOKUP
+# if ROUTER_REDIRECT==2
   .dyn_magic =		ROUTER_MAGIC,
 # endif
   },
diff --git a/src/src/routers/rf_change_domain.c b/src/src/routers/rf_change_domain.c
index 61c156b3e..342e8dc63 100644
--- a/src/src/routers/rf_change_domain.c
+++ b/src/src/routers/rf_change_domain.c
@@ -42,7 +42,7 @@ const uschar * at = Ustrrchr(addr->address, '@');
 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(route) debug_printf("domain changed to %s\n", domain);
 
 /* The current address item is made into the parent, and a new address is set
 up in the old space. */
@@ -68,12 +68,11 @@ addr->next = *addr_new;
 
 if (rewrite)
   {
-  DEBUG(D_route|D_rewrite) debug_printf("rewriting header lines\n");
-  for (header_line * h = header_list; h != NULL; h = h->next)
+  DEBUG(route|rewrite) debug_printf("rewriting header lines\n");
+  for (header_line * h = header_list; h; h = h->next)
     {
-    header_line *newh =
-      rewrite_header(h, parent->domain, domain,
-        global_rewrite_rules, rewrite_existflags, TRUE);
+    header_line * newh = rewrite_header(h, parent->domain, domain,
+				global_rewrite_rules, rewrite_existflags, TRUE);
     if (newh)
       {
       h = newh;
diff --git a/src/src/routers/rf_expand_data.c b/src/src/routers/rf_expand_data.c
index def615a71..2b757fb85 100644
--- a/src/src/routers/rf_expand_data.c
+++ b/src/src/routers/rf_expand_data.c
@@ -35,7 +35,7 @@ uschar *yield = expand_string(s);
 if (yield) return yield;
 if (f.expand_string_forcedfail)
   {
-  DEBUG(D_route) debug_printf("forced failure for expansion of %q\n", s);
+  DEBUG(route) debug_printf("forced failure for expansion of %q\n", s);
   *prc = DECLINE;
   }
 else
diff --git a/src/src/routers/rf_get_errors_address.c b/src/src/routers/rf_get_errors_address.c
index 664c5c932..6f0017af6 100644
--- a/src/src/routers/rf_get_errors_address.c
+++ b/src/src/routers/rf_get_errors_address.c
@@ -48,7 +48,7 @@ if (!(s = expand_string(rblock->errors_to)))
   {
   if (f.expand_string_forcedfail)
     {
-    DEBUG(D_route)
+    DEBUG(route)
       debug_printf("forced expansion failure - ignoring errors_to\n");
     return OK;
     }
@@ -77,7 +77,7 @@ associated with an address. */
 if (verify != v_none)
   {
   *errors_to = s;
-  DEBUG(D_route)
+  DEBUG(route)
     debug_printf("skipped verify errors_to address: already verifying\n");
   }
 else
@@ -107,13 +107,13 @@ else
   vopt_is_recipient, as otherwise sender_address may be altered because
   verify_address() thinks it is dealing with *the* sender of the message. */
 
-  DEBUG(D_route|D_verify)
+  DEBUG(route|verify)
     debug_printf("------ Verifying errors address %s ------\n", s);
   if (verify_address(snew, -1,
       vopt_is_recipient /* vopt_fake_sender is the alternative */
       | vopt_qualify, -1, -1, -1, NULL, NULL, NULL) == OK)
     *errors_to = snew->address;
-  DEBUG(D_route|D_verify)
+  DEBUG(route|verify)
     debug_printf("------ End verifying errors address %s ------\n", s);
 
   f.address_test_mode = save_address_test_mode;
diff --git a/src/src/routers/rf_get_transport.c b/src/src/routers/rf_get_transport.c
index 5d23d534e..9d28ca765 100644
--- a/src/src/routers/rf_get_transport.c
+++ b/src/src/routers/rf_get_transport.c
@@ -71,7 +71,7 @@ if (expandable)
     }
   if (is_tainted(ss))
     {
-    log_write(0, LOG_MAIN|LOG_PANIC,
+    log_write(LOG_MAIN|LOG_PANIC,
       "attempt to use tainted value '%s' from '%s' for transport", ss, tpname);
     addr->basic_errno = ERRNO_BADTRANSPORT;
     /* Avoid leaking info to an attacker */
@@ -85,7 +85,7 @@ else
 for (transport_instance * tp = transports; tp; tp = tp->drinst.next)
   if (Ustrcmp(tp->drinst.name, ss) == 0)
     {
-    DEBUG(D_route) debug_printf_indent("set transport '%s'\n", ss);
+    DEBUG(route) debug_printf_indent("set transport '%s'\n", ss);
     *tpptr = tp;
     return TRUE;
     }
diff --git a/src/src/routers/rf_lookup_hostlist.c b/src/src/routers/rf_lookup_hostlist.c
index 0a3a71ba9..38e5b2875 100644
--- a/src/src/routers/rf_lookup_hostlist.c
+++ b/src/src/routers/rf_lookup_hostlist.c
@@ -71,8 +71,9 @@ for (host_item * prev = NULL, * h = addr->host_list, *next_h; h; h = next_h)
   next_h = h->next;
   if (h->address) { prev = h; continue; }
 
-  DEBUG(D_route|D_host_lookup)
+  DEBUG(route|host_lookup)
     debug_printf_indent("finding IP address for %s\n", h->name);
+  expand_level++;
 
   /* Handle any port setting that may be on the name; it will be removed
   from the end of the name. */
@@ -98,7 +99,7 @@ for (host_item * prev = NULL, * h = addr->host_list, *next_h; h; h = next_h)
       ? HOST_FIND_BY_MX | HOST_FIND_IPV4_FIRST
       : HOST_FIND_BY_MX;
 
-    DEBUG(D_route|D_host_lookup)
+    DEBUG(route|host_lookup)
       debug_printf("doing DNS MX lookup for %s\n", h->name);
 
     mx = MX_NONE;
@@ -119,7 +120,7 @@ for (host_item * prev = NULL, * h = addr->host_list, *next_h; h; h = next_h)
 
   else if (lookup_type & LK_BYNAME || string_is_ip_address(h->name, NULL) != 0)
     {
-    DEBUG(D_route|D_host_lookup) debug_printf_indent("calling host_find_byname\n");
+    DEBUG(route|host_lookup) debug_printf_indent("calling host_find_byname\n");
     rc = host_find_byname(h, ignore_target_hosts, HOST_FIND_QUALIFY_SINGLE,
       &canonical_name, TRUE);
     }
@@ -137,7 +138,7 @@ for (host_item * prev = NULL, * h = addr->host_list, *next_h; h; h = next_h)
       ? 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(route|host_lookup) debug_printf("doing DNS lookup\n");
     switch (rc = host_find_bydns(h, ignore_target_hosts, whichrrs, NULL,
 	NULL, NULL,
 	&rblock->dnssec,			/* domains for request/require */
@@ -149,7 +150,7 @@ for (host_item * prev = NULL, * h = addr->host_list, *next_h; h; h = next_h)
       case HOST_FIND_FAILED:
 	if (lookup_type & LK_DEFAULT)
 	  {
-	  DEBUG(D_route|D_host_lookup)
+	  DEBUG(route|host_lookup)
 	    debug_printf("DNS lookup failed: trying %s\n",
 	      f.running_in_test_harness
 	      ? "host_fake_gethostbyname" : "getipnodebyname");
@@ -164,15 +165,17 @@ for (host_item * prev = NULL, * h = addr->host_list, *next_h; h; h = next_h)
 
   if (rc == HOST_FIND_SECURITY)
     {
+    expand_level--;
     addr->message = string_sprintf("host lookup for %s done insecurely" , h->name);
     addr->basic_errno = ERRNO_DNSDEFER;
     return DEFER;
     }
   if (rc == HOST_FIND_AGAIN)
     {
+    expand_level--;
     if (rblock->pass_on_timeout)
       {
-      DEBUG(D_route)
+      DEBUG(route)
         debug_printf("%s router timed out and pass_on_timeout set\n",
           rblock->drinst.name);
       return PASS;
@@ -187,6 +190,7 @@ for (host_item * prev = NULL, * h = addr->host_list, *next_h; h; h = next_h)
 
   if (rc == HOST_FIND_FAILED)
     {
+    expand_level--;
     if (hff_code == hff_ignore)
       {
       if (prev == NULL) addr->host_list = next_h; else prev->next = next_h;
@@ -231,13 +235,14 @@ for (host_item * prev = NULL, * h = addr->host_list, *next_h; h; h = next_h)
     {
     if (prev)
       {
-      DEBUG(D_route)
+      DEBUG(route)
         {
         debug_printf("Removed from host list:\n");
         for (; h; h = h->next) debug_printf("  %s\n", h->name);
         }
       prev->next = NULL;
       setflag(addr, af_local_host_removed);
+      expand_level--;
       break;
       }
     rc = rf_self_action(addr, h, rblock->self_code, rblock->self_rewrite,
@@ -245,6 +250,7 @@ for (host_item * prev = NULL, * h = addr->host_list, *next_h; h; h = next_h)
     if (rc != OK)
       {
       addr->host_list = NULL;   /* Kill the host list for */
+      expand_level--;
       return rc;                /* anything other than "send" */
       }
     self_send = TRUE;
@@ -255,6 +261,7 @@ for (host_item * prev = NULL, * h = addr->host_list, *next_h; h; h = next_h)
 
   prev = h;
   while (prev->next != next_h) prev = prev->next;
+  expand_level--;
   }
 
 return OK;
diff --git a/src/src/routers/rf_queue_add.c b/src/src/routers/rf_queue_add.c
index aa4645dcf..fb1c1b785 100644
--- a/src/src/routers/rf_queue_add.c
+++ b/src/src/routers/rf_queue_add.c
@@ -116,7 +116,7 @@ remote_delivery_count++;
 
 donelocal:
 
-DEBUG(D_route)
+DEBUG(route)
   {
   debug_printf_indent("queued for %s transport: local_part = %s\ndomain = %s\n"
     "  errors_to=%s\n",
diff --git a/src/src/routers/rf_self_action.c b/src/src/routers/rf_self_action.c
index 75fbaa400..5912d933e 100644
--- a/src/src/routers/rf_self_action.c
+++ b/src/src/routers/rf_self_action.c
@@ -77,44 +77,46 @@ switch (code)
 
     if (!message_id[0])
       if (sender_fullhost)
-	log_write(0, LOG_MAIN, "%s: %s (while verifying <%s> from host %s)",
+	log_write(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,
+	log_write(LOG_MAIN, "%s: %s (while routing <%s>)", msg,
 	  addr->domain, addr->address);
     else
-      log_write(0, LOG_MAIN, "%s: %s", msg, addr->domain);
+      log_write(LOG_MAIN, "%s: %s", msg, addr->domain);
 
     addr->message = msg;
     addr->special_action = SPECIAL_FREEZE;
     return DEFER;
 
   case self_defer:
+    addr->basic_errno = ERRNO_ROUTERDEFER;
     addr->message = msg;
     return DEFER;
 
   case self_reroute:
-    DEBUG(D_route)
-      debug_printf("%s: %s: domain changed to %s\n", msg, addr->domain, new);
+    DEBUG(route)
+      debug_printf_indent("%s: %s: domain changed to %s\n", msg, addr->domain, new);
     rf_change_domain(addr, new, rewrite, addr_new);
     return REROUTED;
 
   case self_send:
-    DEBUG(D_route)
-      debug_printf("%s: %s: configured to try delivery anyway\n", msg, addr->domain);
+    DEBUG(route)
+      debug_printf_indent("%s: %s: configured to try delivery anyway\n", msg, addr->domain);
     return OK;
 
   case self_pass:    /* This is soft failure; pass to next router */
-    DEBUG(D_route)
-      debug_printf("%s: %s: passed to next router (self = pass)\n", msg, addr->domain);
+    DEBUG(route)
+      debug_printf_indent("%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:
-    DEBUG(D_route)
-      debug_printf("%s: %s: address failed (self = fail)\n", msg, addr->domain);
+    DEBUG(route)
+      debug_printf_indent("%s: %s: address failed (self = fail)\n", msg, addr->domain);
     addr->message = msg;
+    addr->basic_errno = ERRNO_HOST_IS_LOCAL;
     setflag(addr, af_pass_message);
     return FAIL;
   }
diff --git a/src/src/search.c b/src/src/search.c
index e09f3ab4e..8217aea0b 100644
--- a/src/src/search.c
+++ b/src/src/search.c
@@ -54,8 +54,8 @@ static rmark search_reset_point = NULL;
 *      Validate a plain lookup type name         *
 *************************************************/
 
-static const lookup_info *
-internal_search_findtype(const uschar * name)
+const lookup_info *
+lookup_findonly(const uschar * name)
 {
 tree_node * t = tree_search(lookups_tree, name);
 return t ? t->data.ptr : NULL;
@@ -81,19 +81,19 @@ const lookup_info * li;
 
 if (name[len])
   name = string_copyn(name, len);
-if ((li = internal_search_findtype(name)))
+if ((li = lookup_findonly(name)))
   return li;
 
 #ifdef LOOKUP_MODULE_DIR
-    DEBUG(D_lookup)
+    DEBUG(lookup)
       debug_printf_indent("searchtype %s not initially found\n", name);
 
     if (lookup_one_mod_load(name, NULL))
-      if ((li = internal_search_findtype(name)))
+      if ((li = lookup_findonly(name)))
 	return li;
       else
-	{ DEBUG(D_lookup) debug_printf_indent("find retry failed\n"); }
-    else DEBUG(D_lookup)
+	{ DEBUG(lookup) debug_printf_indent("find retry failed\n"); }
+    else DEBUG(lookup)
       debug_printf_indent("scan modules dir for %s failed\n", name);
 #endif
 
@@ -320,7 +320,7 @@ search_tidyup(void)
 {
 int old_pool = store_pool;
 
-DEBUG(D_lookup) debug_printf_indent("search_tidyup called\n");
+DEBUG(lookup) debug_printf_indent("search_tidyup called\n");
 expand_level++;
 
 /* Close individually each cached open file. */
@@ -406,7 +406,7 @@ int old_pool = store_pool;
 
 if (filename && is_tainted(filename))
   {
-  log_write(0, LOG_MAIN|LOG_PANIC,
+  log_write(LOG_MAIN|LOG_PANIC,
     "Tainted filename for search: '%s'", filename);
   return NULL;
   }
@@ -416,7 +416,7 @@ if (filename && is_tainted(filename))
 store_pool = POOL_SEARCH;
 if (!search_reset_point) search_reset_point = store_mark();
 
-DEBUG(D_lookup) debug_printf_indent("search_open: %s %q\n", li->name,
+DEBUG(lookup) debug_printf_indent("search_open: %s %q\n", li->name,
   filename ? filename : US"NULL");
 
 /* See if we already have this open for this type of search, and if so,
@@ -431,12 +431,12 @@ if ((t = tree_search(search_tree, keybuffer)))
   {
   if ((c = (search_cache *)t->data.ptr)->handle)
     {
-    DEBUG(D_lookup)
+    DEBUG(lookup)
       if (c->handle != (void *)1) debug_printf_indent("  cached open\n");
     store_pool = old_pool;
     return t;
     }
-  DEBUG(D_lookup) debug_printf_indent("  cached closed\n");
+  DEBUG(lookup) debug_printf_indent("  cached closed\n");
   }
 
 /* Otherwise, we need to open the file or database - each search type has its
@@ -447,12 +447,12 @@ recently used one. */
 
 if (li->type == lookup_absfile && open_filecount >= lookup_open_max)
   if (!open_bot)
-    log_write(0, LOG_MAIN|LOG_PANIC, "too many lookups open, but can't find "
+    log_write(LOG_MAIN|LOG_PANIC, "too many lookups open, but can't find "
       "one to close");
   else
     {
     search_cache * c = (search_cache *)(open_bot->data.ptr);
-    DEBUG(D_lookup) debug_printf_indent("Too many lookup files open\n  closing %s\n",
+    DEBUG(lookup) debug_printf_indent("Too many lookup files open\n  closing %s\n",
       open_bot->name);
     if ((open_bot = c->up))
       ((search_cache *)(open_bot->data.ptr))->down = NULL;
@@ -550,7 +550,7 @@ the callers don't have to test for NULL, set an empty string. */
 search_error_message = US"";
 f.search_find_defer = FALSE;
 
-DEBUG(D_lookup) debug_printf_indent("internal_search_find: file=%q\n  "
+DEBUG(lookup) debug_printf_indent("internal_search_find: file=%q\n  "
   "type=%s key=%q opts=%s%s%s\n", filename,
   li->name, keystring, opts ? "\"" : "", opts, opts ? "\"" : "");
 
@@ -573,7 +573,7 @@ if (  (t = tree_search(c->item_cache, keystring))
    )
   { /* Data was in the cache already; set the pointer from the tree node */
   data = e->data.ptr;
-  DEBUG(D_lookup) debug_printf_indent("cached data used for lookup of %s%s%s\n",
+  DEBUG(lookup) debug_printf_indent("cached data used for lookup of %s%s%s\n",
     keystring,
     filename ? US"\n  in " : US"", filename ? filename : US"");
   }
@@ -582,7 +582,7 @@ else
   uint do_cache = cache & CACHE_WR ? UINT_MAX : 0;
   int keylength = Ustrlen(keystring);
 
-  DEBUG(D_lookup)
+  DEBUG(lookup)
     {
     if (t)
       debug_printf_indent("cached data found but %s; ",
@@ -635,11 +635,10 @@ else
     /* If we're called from a transport, no privs to open the paniclog;
     the logging punts to using stderr - and that seems to stop the debug
     stream. */
-    log_write(0,
-      transport_name ? LOG_MAIN : LOG_MAIN|LOG_PANIC,
+    log_write(transport_name ? LOG_MAIN : LOG_MAIN|LOG_PANIC,
       "tainted search query is not properly quoted%s: %s", loc, ks);
 
-    DEBUG(D_lookup)
+    DEBUG(lookup)
       {
       const uschar * quoter_name;
       int q = quoter_for_address(ks, &quoter_name);
@@ -669,7 +668,7 @@ else
 
   else if (do_cache)
     {
-    DEBUG(D_lookup) debug_printf_indent("%s cache entry\n",
+    DEBUG(lookup) debug_printf_indent("%s cache entry\n",
       t ? "replacing old" : "creating new");
     if (!t)	/* No existing entry.  Create new one. */
       {
@@ -694,15 +693,15 @@ cannot release the store at this stage. */
 
   else if (cache & CACHE_WR)
     {
-    DEBUG(D_lookup) debug_printf_indent("lookup forced cache cleanup\n");
+    DEBUG(lookup) debug_printf_indent("lookup forced cache cleanup\n");
     c->item_cache = NULL; 	/* forget all lookups on this connection */
     }
-  else DEBUG(D_lookup)
+  else DEBUG(lookup)
     debug_printf_indent("no_wr option: no cache invalidate\n");
   }
 
 out:
-DEBUG(D_lookup)
+DEBUG(lookup)
   {
   if (data)
     debug_printf_indent("lookup yielded: %W\n", data);
@@ -757,7 +756,7 @@ BOOL set_null_wild = FALSE, ret_key = FALSE;
 unsigned cache = CACHE_RD | CACHE_WR;
 uschar * yield;
 
-DEBUG(D_lookup)
+DEBUG(lookup)
   {
   if (partial < 0) affixlen = 99;   /* So that "NULL" prints */
   debug_printf_indent("search_find: file=%q\n  key=%q "
@@ -821,7 +820,7 @@ if (open_top != (tree_node *)handle)
     }
   }
 
-DEBUG(D_lookup)
+DEBUG(lookup)
   {
   debug_printf_indent("LRU list:\n");
   for (tree_node * t = open_top; t; )
@@ -862,7 +861,7 @@ else if (partial >= 0)
 	  is_tainted(keystring) || is_tainted(affix) ? GET_TAINTED : GET_UNTAINTED);
     Ustrncpy(keystring2, affix, affixlen);
     Ustrcpy(keystring2 + affixlen, keystring);
-    DEBUG(D_lookup) debug_printf_indent("trying partial match %s\n", keystring2);
+    DEBUG(lookup) debug_printf_indent("trying partial match %s\n", keystring2);
     yield = internal_search_find(handle, filename, CUS keystring2, cache, opts);
     if (f.search_find_defer) return NULL;
     }
@@ -900,7 +899,7 @@ else if (partial >= 0)
         if (affixlen > 0) Ustrncpy(keystring3, affix, affixlen);
         }
 
-      DEBUG(D_lookup) debug_printf_indent("trying partial match %s\n", keystring3);
+      DEBUG(lookup) debug_printf_indent("trying partial match %s\n", keystring3);
       yield = internal_search_find(handle, filename, CUS keystring3,
 		cache, opts);
       if (f.search_find_defer) return NULL;
@@ -945,7 +944,7 @@ if (!yield  &&  starflags & SEARCH_STARAT)
     savechar = *--atat;
     *atat = '*';
 
-    DEBUG(D_lookup) debug_printf_indent("trying default match %s\n", atat);
+    DEBUG(lookup) debug_printf_indent("trying default match %s\n", atat);
     yield = internal_search_find(handle, filename, atat, cache, opts);
     *atat = savechar;
     if (f.search_find_defer) return NULL;
@@ -968,7 +967,7 @@ and the second is empty. */
 
 if (!yield  &&  starflags & (SEARCH_STAR|SEARCH_STARAT))
   {
-  DEBUG(D_lookup) debug_printf_indent("trying to match *\n");
+  DEBUG(lookup) debug_printf_indent("trying to match *\n");
   yield = internal_search_find(handle, filename, US"*", cache, opts);
   if (yield && expand_setup && *expand_setup >= 0)
     {
@@ -1008,7 +1007,7 @@ it have been validated by the lookup. */
 if (yield && ret_key)
   {
   yield = string_copy_taint(keystring, GET_UNTAINTED);
-  DEBUG(D_lookup)
+  DEBUG(lookup)
     debug_printf_indent("lookup yield replace by key: %s\n", yield);
   }
 
diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c
index 1f354e013..ec4df3aa2 100644
--- a/src/src/smtp_in.c
+++ b/src/src/smtp_in.c
@@ -28,7 +28,8 @@ we need room to handle large base64-encoded AUTHs for GSSAPI.
 
 /* Size of buffer for reading SMTP incoming packets */
 
-#define IN_BUFFER_SIZE		8192
+#define IN_UNGET_MAX		2
+#define IN_BUFFER_SIZE		IN_UNGET_MAX + 8192
 
 /* Buffer for SMTP responses */
 
@@ -160,6 +161,9 @@ static uschar *smtp_data_buffer;
 static uschar *smtp_cmd_data;
 static uschar *smtp_resp_buffer;
 
+static uschar * rcpt_orcpt;
+static int rcpt_dsn_flags;
+
 /* We need to know the position of RSET, HELO, EHLO, AUTH, and STARTTLS. Their
 final fields of all except AUTH are forced TRUE at the start of a new message
 setup, to allow one of each between messages that is not counted as a nonmail
@@ -345,8 +349,8 @@ one incoming SMTP call, we just use a single buffer and flags. There is no need
 to implement a complicated private FILE-like structure.*/
 
 static uschar *smtp_inbuffer;
-static uschar *smtp_inptr;
-static uschar *smtp_inend;
+static uschar *smtp_inptr;		/* read point */
+static uschar *smtp_inend;		/* write point */
 static int     smtp_had_eof;
 static int     smtp_had_error;
 
@@ -385,8 +389,9 @@ if (recipients_count > 0)
   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);
+if (LOGGING(smtp_incomplete_transaction))
+  log_write(LOG_MAIN|LOG_SENDER|LOG_RECIPIENTS,
+    "%s incomplete transaction (%s)", host_and_ident(TRUE), what);
 }
 
 
@@ -394,16 +399,18 @@ log_write(L_smtp_incomplete_transaction, LOG_MAIN|LOG_SENDER|LOG_RECIPIENTS,
 static void
 log_close_event(const uschar * reason)
 {
-log_write(L_smtp_connection, LOG_MAIN, "%s D=%s closed %s",
-  smtp_get_connection_info(), string_timesince(&smtp_connection_start), reason);
+if (LOGGING(smtp_connection))
+  log_write(LOG_MAIN, "%s D=%s closed %s",
+    smtp_get_connection_info(), string_timesince(&smtp_connection_start),
+    reason);
 }
 
 
 void
 smtp_command_timeout_exit(void)
 {
-log_write(L_lost_incoming_connection,
-	  LOG_MAIN, "SMTP command timeout on%s connection from %s D=%s",
+if (LOGGING(lost_incoming_connection))
+  log_write(LOG_MAIN, "SMTP command timeout on%s connection from %s D=%s",
 	  tls_in.active.sock >= 0 ? " TLS" : "", host_and_ident(FALSE),
 	  string_timesince(&smtp_connection_start));
 if (smtp_batched_input)
@@ -428,10 +435,11 @@ exim_exit(EXIT_FAILURE);
 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> D=%s",
-  sender_fullhost ? sender_fullhost : US"local process", sender_address,
-  string_timesince(&smtp_connection_start));
+if (LOGGING(lost_incoming_connection))
+  log_write(LOG_MAIN,
+    "SMTP data timeout (message abandoned) on connection from %s F=<%s> D=%s",
+    sender_fullhost ? sender_fullhost : US"local process", sender_address,
+    string_timesince(&smtp_connection_start));
 receive_bomb_out(US"data-timeout", US"SMTP incoming data timeout");
 /* Does not return */
 }
@@ -457,10 +465,10 @@ 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. */
 
 if (!(smtp_inbuffer = US malloc(IN_BUFFER_SIZE)))
-  log_write_die(0, LOG_MAIN, "malloc() failed for SMTP input buffer");
+  log_write_die(LOG_MAIN, "malloc() failed for SMTP input buffer");
 smtp_inbuffer[IN_BUFFER_SIZE-1] = '\0';
 
-smtp_inptr = smtp_inend = smtp_inbuffer;
+smtp_inptr = smtp_inend = smtp_inbuffer + IN_UNGET_MAX;
 smtp_had_eof = smtp_had_error = 0;
 }
 
@@ -488,8 +496,9 @@ Return false for error or EOF.
 */
 
 static BOOL
-smtp_refill(unsigned lim)
+smtp_refill(const in_processing * inp, unsigned lim)
 {
+uschar * s;
 int rc, save_errno;
 
 if (smtp_out_fd < 0 || smtp_in_fd < 0) return FALSE;
@@ -498,9 +507,11 @@ smtp_fflush(SFF_UNCORK);
 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. */
+Take care to not touch the safety NUL at the end of the buffer.
+Leave two unused bytes at the start of the buffer for ungetc operations. */
 
-rc = read(smtp_in_fd, smtp_inbuffer, MIN(IN_BUFFER_SIZE-1, lim));
+s = smtp_inbuffer + IN_UNGET_MAX;
+rc = read(smtp_in_fd, s, MIN(IN_BUFFER_SIZE-IN_UNGET_MAX-1, lim));
 save_errno = errno;
 if (smtp_receive_timeout > 0) ALARM_CLR(0);
 if (rc <= 0)
@@ -523,10 +534,10 @@ if (rc <= 0)
   return FALSE;
   }
 #ifndef DISABLE_DKIM
-smtp_verify_feed(smtp_inbuffer, rc);
+smtp_verify_feed(s, rc);
 #endif
-smtp_inend = smtp_inbuffer + rc;
-smtp_inptr = smtp_inbuffer;
+smtp_inend = s + rc;
+smtp_inptr = s;
 return TRUE;
 }
 
@@ -534,7 +545,7 @@ return TRUE;
 /* Check if there is buffered data */
 
 BOOL
-smtp_hasc(void)
+smtp_hasc(const in_processing * inp)
 {
 return smtp_inptr < smtp_inend;
 }
@@ -551,21 +562,21 @@ Returns:    the next character or EOF
 */
 
 int
-smtp_getc(unsigned lim)
+smtp_getc(const in_processing * inp, unsigned lim)
 {
-if (!smtp_hasc() && !smtp_refill(lim)) return EOF;
+if (!smtp_hasc(inp) && !smtp_refill(inp, lim)) return EOF;
 return *smtp_inptr++;
 }
 
 /* Get many bytes, refilling buffer if needed. Can return NULL on EOF/errror. */
 
-uschar *
-smtp_getbuf(unsigned * len)
+const uschar *
+smtp_getbuf(const in_processing * inp, unsigned * len)
 {
 unsigned size;
 uschar * buf;
 
-if (!smtp_hasc() && !smtp_refill(*len))
+if (!smtp_hasc(inp) && !smtp_refill(inp, *len))
   { *len = 0; return NULL; }
 
 if ((size = smtp_inend - smtp_inptr) > *len) size = *len;
@@ -579,7 +590,7 @@ return buf;
 Called, unless TLS, just before starting to read message headers. */
 
 void
-smtp_get_cache(unsigned lim)
+smtp_get_cache(const in_processing * inp, unsigned lim)
 {
 #ifndef DISABLE_DKIM
 int n = smtp_inend - smtp_inptr;
@@ -592,7 +603,9 @@ if (n > 0)
 
 
 /* SMTP version of ungetc()
-Puts a character back in the input buffer. Only ever called once.
+Puts a character back in the input buffer. Usually only ever called once,
+with a char just gotten - but the bdat layer, for certain error situations
+can call a sequence of two.
 
 Arguments:
   ch           the character
@@ -601,10 +614,10 @@ Returns:       the character
 */
 
 int
-smtp_ungetc(int ch)
+smtp_ungetc(const in_processing * inp, int ch)
 {
 if (smtp_inptr <= smtp_inbuffer)	/* NB: NOT smtp_hasc() ! */
-  log_write_die(0, LOG_MAIN, "buffer underflow in smtp_ungetc");
+  log_write_die(LOG_MAIN, "buffer underflow in smtp_ungetc");
 
 *--smtp_inptr = ch;
 return ch;
@@ -619,7 +632,7 @@ Returns:       non-zero if the eof flag is set
 */
 
 int
-smtp_feof(void)
+smtp_feof(const in_processing * inp)
 {
 return smtp_had_eof;
 }
@@ -634,12 +647,24 @@ Returns:       non-zero if the error flag is set
 */
 
 int
-smtp_ferror(void)
+smtp_ferror(const in_processing * inp)
 {
 errno = smtp_had_error;
 return smtp_had_error;
 }
 
+in_processing smtp_template = {
+  .in_layer_name =	US"smtp",
+  .in_getc =	smtp_getc,
+  .in_getbuf =	smtp_getbuf,
+  .in_getcache =smtp_get_cache,
+  .in_hasc =	smtp_hasc,
+  .in_ungetc =	smtp_ungetc,
+  .in_feof =	smtp_feof,
+  .in_ferror =	smtp_ferror
+};
+
+
 
 /* Check if a getc will block or not */
 /*XXX should convert from select() to poll() */
@@ -662,10 +687,10 @@ if (rc <= 0) return FALSE;	/* Not ready to read */
 if (eof_ok) return TRUE;	/* A read will not block */
 
 /* Check for actual data */
-rc = smtp_getc(GETC_BUFFER_UNLIMITED);
+rc = smtp_getc(rx_prc, GETC_BUFFER_UNLIMITED);
 if (rc < 0) return FALSE;      /* End of file or error */
 
-smtp_ungetc(rc);
+smtp_ungetc(rx_prc, rc);
 return TRUE;
 }
 
@@ -723,17 +748,10 @@ return wouldblock_reading(eof_ok);
 static inline void
 smtp_rcv_cleartext(void)
 {
-receive_getc = smtp_getc;
-receive_getbuf = smtp_getbuf;
-receive_get_cache = smtp_get_cache;
-receive_hasc = smtp_hasc;
-receive_ungetc = smtp_ungetc;
-receive_feof = smtp_feof;
-receive_ferror = smtp_ferror;
+rx_prc = &smtp_template;
 }
 
 /* Forward declarations */
-static inline void bdat_push_receive_functions(void);
 static inline void bdat_pop_receive_functions(void);
 
 
@@ -749,12 +767,13 @@ 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
+Arguments:	lim		(ignored)
+Returns:	The next character or ERR, EOD or EOF.
+		For all of those last three status codes, the bdat layer has been popped.
 */
 
 int
-bdat_getc(unsigned lim)
+bdat_getc(const in_processing * inp, unsigned lim)
 {
 uschar * user_msg = NULL, * log_msg;
 int rc;
@@ -773,7 +792,7 @@ for(;;)
   {
 
   if (chunking_data_left > 0)
-    return lwr_receive_getc(chunking_data_left--);
+    return inp_getc(inp->in_lower, chunking_data_left--);
 
   bdat_pop_receive_functions();
 #ifndef DISABLE_DKIM
@@ -786,20 +805,20 @@ for(;;)
   if (!f.smtp_in_pipelining_advertised && !check_sync(WBR_DATA_ONLY))
     {
     unsigned nchars = 32;
-    uschar * buf = receive_getbuf(&nchars);		/* destructive read */
+    const uschar * buf = receive_getbuf(&nchars);	/* destructive read */
 
     incomplete_transaction_log(US"sync failure");
     if (buf)
-      log_write(0, LOG_MAIN|LOG_REJECT, "SMTP protocol synchronization error "
+      log_write(LOG_MAIN|LOG_REJECT, "SMTP protocol synchronization error "
 	"(next input sent too soon: pipelining was not advertised): "
 	"rejected %q %s next input=%q%s",
 	smtp_cmd_buffer, host_and_ident(TRUE),
 	string_printing(string_copyn(buf, nchars)),
 	smtp_inend - smtp_inptr > 0 ? "..." : "");
     else
-      log_write(0, LOG_MAIN|LOG_REJECT, "Error or EOF on input from %s",
+      log_write(LOG_MAIN|LOG_REJECT, "Error or EOF on input from %s",
 	host_and_ident(TRUE));
-    (void) synprot_error(L_smtp_protocol_error, 554, NULL,
+    (void) synprot_error(TRUE, 554, NULL,
       US"SMTP synchronization error");
     goto repeat_until_rset;
     }
@@ -817,7 +836,7 @@ for(;;)
 
   smtp_printf("250 %u byte chunk received\r\n", SP_NO_MORE, chunking_datasize);
   chunking_state = CHUNKING_OFFERED;
-  DEBUG(D_receive)
+  DEBUG(receive)
     debug_printf("chunking state '%s'\n", chunking_states[chunking_state]);
 
   /* Expect another BDAT cmd from input. RFC 3030 says nothing about
@@ -827,7 +846,7 @@ next_cmd:
   switch(smtp_read_command(TRUE, 1))
     {
     default:
-      (void) synprot_error(L_smtp_protocol_error, 503, NULL,
+      (void) synprot_error(TRUE, 503, NULL,
 	US"only BDAT permissible after non-LAST BDAT");
 
   repeat_until_rset:
@@ -836,7 +855,7 @@ next_cmd:
 	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,
+	default:	if (synprot_error(TRUE, 503, NULL,
 					  US"only RSET accepted now") > 0)
 			  return ERR;
 			goto repeat_until_rset;
@@ -863,14 +882,14 @@ next_cmd:
 
       if (sscanf(CS smtp_cmd_data, "%u %n", &chunking_datasize, &n) < 1)
 	{
-	(void) synprot_error(L_smtp_protocol_error, 501, NULL,
+	(void) synprot_error(TRUE, 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 '%s', %d bytes\n",
+      DEBUG(receive) debug_printf("chunking state '%s', %d bytes\n",
 			chunking_states[chunking_state], chunking_data_left);
 
       if (chunking_datasize == 0)
@@ -878,7 +897,7 @@ next_cmd:
 	  return EOD;
 	else
 	  {
-	  (void) synprot_error(L_smtp_protocol_error, 504, NULL,
+	  (void) synprot_error(TRUE, 504, NULL,
 	    US"zero size for BDAT command");
 	  goto repeat_until_rset;
 	  }
@@ -894,121 +913,149 @@ next_cmd:
 }
 
 BOOL
-bdat_hasc(void)
+bdat_hasc(const in_processing * inp)
 {
 if (chunking_data_left > 0)
-  return lwr_receive_hasc();
+  return inp_hasc(inp->in_lower);
 return TRUE;
 }
 
-uschar *
-bdat_getbuf(unsigned * len)
+int
+bdat_ungetc(const in_processing * inp, int ch)
 {
-uschar * buf;
+chunking_data_left++;
+return inp_ungetc(inp->in_lower, ch);
+}
+
+const uschar *
+bdat_getbuf(const in_processing * inp, unsigned * len)
+{
+const 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 */
+buf = inp_getbuf(inp->in_lower, len);	/* Either smtp_getbuf or tls_getbuf */
 chunking_data_left -= *len;
 return buf;
 }
 
 void
-bdat_flush_data(void)
+bdat_get_cache(const in_processing * inp, unsigned lim)
+{
+inp_get_cache(inp->in_lower, lim);
+}
+int
+bdat_feof(const in_processing * inp)
+{
+return inp_feof(inp->in_lower);
+}
+
+void
+bdat_flush_data(const in_processing * inp)
 {
 while (chunking_data_left)
   {
   unsigned n = chunking_data_left;
-  if (!bdat_getbuf(&n)) break;
+  if (!bdat_getbuf(inp, &n)) break;
   }
 
 bdat_pop_receive_functions();
 chunking_state = CHUNKING_OFFERED;
-DEBUG(D_receive)
+DEBUG(receive)
   debug_printf("chunking state '%s'\n", chunking_states[chunking_state]);
 }
 
+static in_processing bdat_template = {
+  .in_layer_name =	US"bdat",
+  .in_getc =	bdat_getc,
+  .in_getbuf =	bdat_getbuf,
+  .in_getcache =bdat_get_cache,
+  .in_hasc =	bdat_hasc,
+  .in_ungetc =	bdat_ungetc,
+  .in_feof =	bdat_feof,
+};
+
+/* We're only expecting one bdat layer at a time, but we do bounce it in and
+out a lot.  So use a static struct rather than allocating. */
+static in_processing bdat_proc;
 
-static inline void
+void
 bdat_push_receive_functions(void)
 {
-/* push the current receive_* function on the "stack", and
-replace them by bdat_getc(), which in turn will use the lwr_receive_*
+/* Only legitimate over smtp or tls layer. */
+
+if (ANY_DEBUG || f.running_in_test_harness)
+  if (  Ustrcmp(rx_prc->in_layer_name, "smtp") != 0
+     && Ustrcmp(rx_prc->in_layer_name, "tls") != 0)
+    log_write(LOG_PANIC_DIE,
+	      "%s: bad substrate %s", __FUNCTION__, rx_prc->in_layer_name);
+
+/* Push the current receive_* function on the "stack", and
+replace them by bdat_getc(), which in turn will use the next lower layer
 functions to do the dirty work. */
-if (!lwr_receive_getc)
-  {
-  lwr_receive_getc = receive_getc;
-  lwr_receive_getbuf = receive_getbuf;
-  lwr_receive_hasc = receive_hasc;
-  lwr_receive_ungetc = receive_ungetc;
-  }
-else
-  {
-  DEBUG(D_receive) debug_printf("chunking double-push receive functions\n");
-  }
 
-receive_getc = bdat_getc;
-receive_getbuf = bdat_getbuf;
-receive_hasc = bdat_hasc;
-receive_ungetc = bdat_ungetc;
+bdat_proc = bdat_template;
+bdat_proc.in_lower = rx_prc;
+rx_prc = &bdat_proc;
+
+DEBUG(receive) debug_print_processing_stack();
 }
 
 static inline void
 bdat_pop_receive_functions(void)
 {
-if (!lwr_receive_getc)
+const in_processing * lwr = rx_prc->in_lower;
+
+if (!lwr)
+  log_write(LOG_PANIC_DIE, "%s: bad substrate <none>", __FUNCTION__);
+
+if (ANY_DEBUG || f.running_in_test_harness)
   {
-  DEBUG(D_receive) debug_printf("chunking double-pop receive functions\n");
-  return;
+  if (Ustrcmp(rx_prc->in_layer_name, "bdat") != 0)
+    log_write(LOG_PANIC_DIE,
+	      "%s: bad current layer %s", __FUNCTION__, rx_prc->in_layer_name);
+  if (  Ustrcmp(lwr->in_layer_name, "smtp") != 0
+     && Ustrcmp(lwr->in_layer_name, "tls") != 0)
+    log_write(LOG_PANIC_DIE,
+	      "%s: bad substrate %s", __FUNCTION__, lwr->in_layer_name);
   }
-receive_getc = lwr_receive_getc;
-receive_getbuf = lwr_receive_getbuf;
-receive_hasc = lwr_receive_hasc;
-receive_ungetc = lwr_receive_ungetc;
-
-lwr_receive_getc = NULL;
-lwr_receive_getbuf = NULL;
-lwr_receive_hasc = NULL;
-lwr_receive_ungetc = NULL;
-}
 
-int
-bdat_ungetc(int ch)
-{
-chunking_data_left++;
-bdat_push_receive_functions();  /* we're not done yet, calling push is safe, because it checks the state before pushing anything */
-return lwr_receive_ungetc(ch);
+rx_prc = lwr;
+DEBUG(receive) debug_print_processing_stack();
 }
 
 
 
 #ifndef DISABLE_TLS
-/* The TLS layer has received a Close notification alert, meaning no
-more encrypted data will be received.
+/* The TLS layer is done with receiving.
 
 To preserve layering of the receive processing if a BDAT chunk is still
 in progress, pop the bdat layer, reset to plaintext processing then
 re-push the bdat layer.
-Then close our writing side of the TLS channel.
 
 Any chunk in progress will likely error out anyway; let the existing
 processing handle it.
 */
 
 void
-tls_close_notify(void)
+tls_receive_done(void)
 {
 if (chunking_state > CHUNKING_OFFERED)
-  {
   bdat_pop_receive_functions();
-  smtp_rcv_cleartext();
+
+rx_prc = tls_pop_receive_functions(rx_prc);
+DEBUG(receive) debug_print_processing_stack();
+
+tls_in.active.tls_ctx = NULL;
+tls_in.sni = NULL;
+/* Leave bits, peercert, cipher, peerdn, certificate_verified set, for logging */
+
+if (chunking_state > CHUNKING_OFFERED)
   bdat_push_receive_functions();
-  }
-tls_close(NULL, TLS_NO_SHUTDOWN);
 }
-#endif
+#endif	/*DISABLE_TLS*/
 
 /******************************************************************************/
 
@@ -1064,7 +1111,7 @@ that we'll never expand it. */
 yield = !! string_vformat(&gs, SVFMT_TAINT_NOCHK, format, ap);
 string_from_gstring(&gs);
 
-DEBUG(D_receive) for (const uschar * t, * s = gs.s;
+DEBUG(receive) for (const uschar * t, * s = gs.s;
 		      s && (t = Ustrchr(s, '\r'));
 		      s = t + 2)				/* \r\n */
     debug_printf("%s %.*s\n",
@@ -1073,7 +1120,7 @@ DEBUG(D_receive) for (const uschar * t, * s = gs.s;
 
 if (!yield)
   {
-  log_write(0, LOG_MAIN|LOG_PANIC, "string too large in smtp_printf()");
+  log_write(LOG_MAIN|LOG_PANIC, "string too large in smtp_printf()");
   smtp_closedown(US"Unexpected error");
   exim_exit(EXIT_FAILURE);
   }
@@ -1169,7 +1216,7 @@ smtp_fflush(BOOL uncork)
 {
 if (smtp_out_fd <= 0) return 0;
 
-DEBUG(D_receive) debug_printf("SMTP>- %Vflush%V\n", "<", ">");
+DEBUG(receive) debug_printf("SMTP>- %Vflush%V\n", "<", ">");
 
 #ifndef DISABLE_TLS
 if (tls_in.active.sock >= 0)
@@ -1299,7 +1346,7 @@ os_non_restarting_signal(SIGALRM, command_timeout_handler);
 
 /* Read up to end of line */
 
-while ((c = (receive_getc)(buffer_lim)) != '\n')
+while ((c = receive_getc(buffer_lim)) != '\n')
   {
   /* If hit end of file, return pseudo EOF command. Whether we have a
   part-line already read doesn't matter, since this is an error state. */
@@ -1308,7 +1355,7 @@ while ((c = (receive_getc)(buffer_lim)) != '\n')
     {
     os_non_restarting_signal(SIGALRM, sigalrm_handler);
     /* c could be EOF, ERR, or a good (positive) value overflowing the buffer */
-    DEBUG(D_receive)
+    DEBUG(receive)
       if (c < 0)
 	debug_printf("SMTP(%s)<<\n", c == EOF ? "closed" : "error");
       else
@@ -1334,7 +1381,7 @@ string. */
 while (ptr > 0 && isspace(smtp_cmd_buffer[ptr-1])) ptr--;
 smtp_cmd_buffer[ptr] = 0;
 
-DEBUG(D_receive) debug_printf("SMTP<< %s\n", smtp_cmd_buffer);
+DEBUG(receive) debug_printf("SMTP<< %s\n", smtp_cmd_buffer);
 
 /* NULLs are not allowed in SMTP commands */
 
@@ -1347,8 +1394,8 @@ if required. */
 for (smtp_cmd_list * p = cmd_list; p < cmd_list + nelem(cmd_list); p++)
   {
 #ifdef SUPPORT_PROXY
-  /* Only allow QUIT command if Proxy Protocol parsing failed */
-  if (proxy_session && f.proxy_session_failed && p->cmd != QUIT_CMD)
+  /* If Proxy Protocol parsing failed, only allow QUIT command */
+  if (f.quit_cmd_only && p->cmd != QUIT_CMD)
     continue;
 #endif
   if (  p->len
@@ -1402,8 +1449,8 @@ for (smtp_cmd_list * p = cmd_list; p < cmd_list + nelem(cmd_list); p++)
   }
 
 #ifdef SUPPORT_PROXY
-/* Only allow QUIT command if Proxy Protocol parsing failed */
-if (proxy_session && f.proxy_session_failed)
+/* If Proxy Protocol parsing failed, only allow QUIT command */
+if (f.quit_cmd_only)
   return PROXY_FAIL_IGNORE_CMD;
 #endif
 
@@ -1462,6 +1509,11 @@ for (;;) switch(smtp_read_command(FALSE, GETC_BUFFER_UNLIMITED))
     smtp_printf("250 Reset OK\r\n", SP_NO_MORE);
     break;
 
+  case BADARG_CMD:
+    if (synprot_error(FALSE, 501, NULL,
+      US"unexpected argument data") > 0) return;
+    break;
+
   default:
     smtp_printf("421 %s\r\n", SP_NO_MORE, message);
     break;
@@ -1522,7 +1574,6 @@ return string_from_gstring(g);
 
 
 
-#ifndef DISABLE_TLS
 /* Append TLS-related information to a log line
 
 Arguments:
@@ -1533,13 +1584,14 @@ Returns:	Allocated string or NULL
 gstring *
 add_tls_info_for_log(gstring * g)
 {
+#ifndef DISABLE_TLS
 if (LOGGING(tls_cipher) && tls_in.cipher)
   {
   g = string_append(g, 2, US" X=", tls_in.cipher);
-#ifndef DISABLE_TLS_RESUME
+# ifndef DISABLE_TLS_RESUME
   if (LOGGING(tls_resumption) && tls_in.resumption & RESUME_USED)
     g = string_catn(g, US"*", 1);
-#endif
+# endif
   }
 if (LOGGING(tls_certificate_verified) && tls_in.peercert)
   g = string_append(g, 2, US" CV=", tls_in.certificate_verified? "yes":"no");
@@ -1548,8 +1600,44 @@ if (LOGGING(tls_peerdn) && tls_in.peerdn)
 if (LOGGING(tls_sni) && tls_in.sni)
   g = string_append(g, 2, US" SNI=", string_printing2(tls_in.sni, SP_TAB|SP_SPACE));
 return g;
-}
 #endif
+}
+
+/* Append DSN information to a log line
+
+Arguments:
+  g		String under construction: allocated string to extend, or NULL
+  where		ACL cause of call
+
+Returns:	Allocated string or NULL
+*/
+gstring *
+add_dsn_info_for_log(gstring * g, int where)
+{
+BOOL has_orcpt = FALSE, has_notify = FALSE;
+
+if (where == ACL_WHERE_RCPT)
+  {
+  if (rcpt_orcpt) has_orcpt = TRUE;
+  if (rcpt_dsn_flags) has_notify = TRUE;
+  }
+else if (recipients_list) for (int i = 0; i < recipients_count; i++)
+  {
+  has_orcpt |= !!recipients_list[i].orcpt;
+  has_notify |= !!recipients_list[i].dsn_flags;
+  }
+
+if (LOGGING(dsn) && (dsn_ret || dsn_envid || has_orcpt || has_notify))
+  {
+  g = string_catn(g, US" DSN=", 5);
+  if (dsn_ret) g = string_catn(g, US"ret,", 4);
+  if (dsn_envid) g = string_catn(g, US"envid,", 6);
+  if (has_orcpt) g = string_catn(g, US"orcpt,", 6);
+  if (has_notify) g = string_catn(g, US"notify,", 7);
+  gstring_trim(g, 1);
+  }
+return g;
+}
 
 
 
@@ -1586,7 +1674,6 @@ Returns:     nothing
 void
 smtp_log_no_mail(void)
 {
-uschar * s;
 gstring * g = NULL;
 
 if (smtp_mailcmd_count > 0 || !LOGGING(smtp_no_mail))
@@ -1598,17 +1685,12 @@ if (sender_host_authenticated)
   if (authenticated_id) g = string_append(g, 2, US":", authenticated_id);
   }
 
-#ifndef DISABLE_TLS
 g = add_tls_info_for_log(g);
-#endif
-
 g = s_connhad_log(g);
 
-if (!(s = string_from_gstring(g))) s = US"";
-
-log_write(0, LOG_MAIN, "no MAIL in %sSMTP connection from %s D=%s%s",
+log_write(LOG_MAIN, "no MAIL in %sSMTP connection from %s D=%s%#Y",
   f.tcp_in_fastopen ? f.tcp_in_fastopen_data ? US"TFO* " : US"TFO " : US"",
-  host_and_ident(FALSE), string_timesince(&smtp_connection_start), s);
+  host_and_ident(FALSE), string_timesince(&smtp_connection_start), g);
 }
 
 
@@ -1781,6 +1863,8 @@ Returns:    a replacement reset point
 rmark
 smtp_reset(rmark reset_point)
 {
+deliver_set_expansions(NULL);
+
 recipients_list = NULL;
 rcpt_count = rcpt_defer_count = rcpt_fail_count =
   raw_recipients_count = recipients_count = recipients_list_max = 0;
@@ -1805,11 +1889,8 @@ 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;
-deliver_localpart_data = deliver_domain_data =
 recipient_data = sender_data = NULL;			/* Can be set by ACL */
 recipient_verify_failure = NULL;
-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 */
@@ -1819,14 +1900,10 @@ memset(sender_address_cache, 0, sizeof(sender_address_cache));
 memset(sender_domain_cache, 0, sizeof(sender_domain_cache));
 
 authenticated_sender = NULL;
-#ifdef EXPERIMENTAL_BRIGHTMAIL
- bmi_run = 0;
- bmi_verdicts = NULL;
-#endif
+dnslist_domain = dnslist_matched = NULL;
 
 dsn_ret = 0;
 dsn_envid = NULL;
-deliver_host = deliver_host_address = NULL;	/* Can be set by ACL */
 #ifndef DISABLE_TLS
  memset(&tls_out, 0, sizeof(tls_out));		/* Can be set by callout */
  tls_out.active.sock = -1;
@@ -1840,6 +1917,9 @@ deliver_host = deliver_host_address = NULL;	/* Can be set by ACL */
 #ifdef SUPPORT_SRS
  srs_recipient = NULL;
 #endif
+#ifdef HAVE_LOCAL_SCAN
+local_scan_data = NULL;
+#endif
 #ifdef WITH_CONTENT_SCAN
  regex_vars_clear();
  malware_name = NULL;
@@ -1910,7 +1990,7 @@ like HELO and RSET count as whole transactions. */
 
 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");
 reset_point = smtp_reset(reset_point);		/* Reset for start of message */
@@ -1991,13 +2071,15 @@ while (done <= 0)
 	  {
 	  /* deconst ok as sender_address was not const */
 	  sender_address = US rewrite_address_qualify(sender_address, FALSE);
-	  DEBUG(D_receive) debug_printf("unqualified address %s accepted "
+	  DEBUG(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");
+
+      /*XXX Note: we do not handle DSN options on MAIL */
       break;
 
 
@@ -2048,7 +2130,7 @@ while (done <= 0)
       if (!recipient_domain)
 	if (f.allow_unqualified_recipient)
 	  {
-	  DEBUG(D_receive) debug_printf("unqualified address %s accepted\n",
+	  DEBUG(receive) debug_printf("unqualified address %s accepted\n",
 	    recipient);
 	  /* deconst ok as recipient was not const */
 	  recipient = US rewrite_address_qualify(recipient, TRUE);
@@ -2058,7 +2140,8 @@ while (done <= 0)
 	  moan_smtp_batch(smtp_cmd_buffer,
 	    "501 recipient address must contain a domain");
 
-      receive_add_recipient(recipient, -1);
+      /*XXX Note: we do not handle DSN options on RCPT */
+      receive_add_recipient(recipient, -1, rf_notify_unset, NULL);
       break;
 
 
@@ -2136,7 +2219,7 @@ const 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);
+log_write(LOG_MAIN, "TLS error on %s %s", conn_info, errstr);
 return FALSE;
 }
 #endif
@@ -2161,12 +2244,12 @@ if (getsockopt(smtp_out_fd, IPPROTO_TCP, TCP_FASTOPEN, &is_fastopen, &len) == 0)
   {
   if (is_fastopen)
     {
-    DEBUG(D_receive)
+    DEBUG(receive)
       debug_printf("TFO mode connection (TCP_FASTOPEN getsockopt)\n");
     f.tcp_in_fastopen = TRUE;
     }
   }
-else DEBUG(D_receive)
+else DEBUG(receive)
   debug_printf("TCP_FASTOPEN getsockopt: %s\n", strerror(errno));
 
 # elif defined(TCP_INFO)
@@ -2178,14 +2261,14 @@ if (getsockopt(smtp_out_fd, IPPROTO_TCP, TCP_INFO, &tinfo, &len) == 0)
 #  ifdef TCPI_OPT_SYN_DATA	/* FreeBSD 11,12 do not seem to have this yet */
   if (tinfo.tcpi_options & TCPI_OPT_SYN_DATA)
     {
-    DEBUG(D_receive)
+    DEBUG(receive)
       debug_printf("TFO mode connection (ACKd data-on-SYN)\n");
     f.tcp_in_fastopen_data = f.tcp_in_fastopen = TRUE;
     }
 #   ifdef TCPI_OPT_TFO_CHILD
   else if (tinfo.tcpi_options & TCPI_OPT_TFO_CHILD)
     {
-    DEBUG(D_receive)
+    DEBUG(receive)
       debug_printf("TFO mode connection (SYN with TFO option)\n");
     f.tcp_in_fastopen = TRUE;
     }
@@ -2201,12 +2284,12 @@ if (getsockopt(smtp_out_fd, IPPROTO_TCP, TCP_INFO, &tinfo, &len) == 0)
 
     if (tinfo.tcpi_state == TCP_SYN_RECV)	/* Not seen on FreeBSD 12.1 */
     {
-    DEBUG(D_receive)
+    DEBUG(receive)
       debug_printf("TFO mode connection (state TCP_SYN_RECV)\n");
     f.tcp_in_fastopen = TRUE;
     }
   }
-else DEBUG(D_receive)
+else DEBUG(receive)
   debug_printf("TCP_INFO getsockopt: %s\n", strerror(errno));
 # endif	/* TCP_INFO */
 }
@@ -2216,22 +2299,13 @@ else DEBUG(D_receive)
 static void
 log_connect_tls_drop(const uschar * what, const uschar * log_msg)
 {
-if (log_reject_target)
-  {
-#ifdef DISABLE_TLS
-  uschar * tls = NULL;
-#else
-  gstring * g = add_tls_info_for_log(NULL);
-  uschar * tls = string_from_gstring(g);
-#endif
-  log_write(L_connection_reject,
-    log_reject_target, "%s%s%s dropped by %s%s%s",
+if (log_reject_target && LOGGING(connection_reject))
+  log_write(log_reject_target, "%s%s%#Y dropped by %s%s%s",
     LOGGING(dnssec) && sender_host_dnssec ? US" DS" : US"",
     host_and_ident(TRUE),
-    tls ? tls : US"",
+    add_tls_info_for_log(NULL),
     what,
     log_msg ? US": " : US"", log_msg);
-  }
 }
 
 
@@ -2282,7 +2356,7 @@ int optcount, sprint_len;
 struct in_addr addr;
 uschar * optstart = US OPTSTART;
 
-DEBUG(D_receive) debug_printf("IP options exist\n");
+DEBUG(receive) debug_printf("IP options exist\n");
 
 p = Ustpcpy(big_buffer, "IP options on incoming call:");
 
@@ -2352,11 +2426,11 @@ bad_srr:    break;
     }
 
 *p = '\0';
-log_write(0, LOG_MAIN, "%s", big_buffer);
+log_write(LOG_MAIN, "%s", big_buffer);
 
 /* Refuse any call with IP options. This is what tcpwrappers 7.5 does. */
 
-log_write(0, LOG_MAIN|LOG_REJECT,
+log_write(LOG_MAIN|LOG_REJECT,
   "connection from %s refused (IP options)", host_and_ident(FALSE));
 
 smtp_printf("554 SMTP service not available\r\n", SP_NO_MORE);
@@ -2457,26 +2531,15 @@ else
 call the local functions instead of the standard C ones. */
 
 smtp_buf_init();
+smtp_rcv_cleartext();
 
 #ifndef DISABLE_TLS
 if (atrn_mode && tls_in.active.sock >= 0)
   {
-  receive_getc = tls_getc;
-  receive_getbuf = tls_getbuf;
-  receive_get_cache = tls_get_cache;
-  receive_hasc = tls_hasc;
-  receive_ungetc = tls_ungetc;
-  receive_feof = tls_feof;
-  receive_ferror = tls_ferror;
+  rx_prc = tls_push_receive_functions(rx_prc);
+  DEBUG(receive) debug_print_processing_stack();
   }
-else
 #endif
-  smtp_rcv_cleartext();
-
-lwr_receive_getc = NULL;
-lwr_receive_getbuf = NULL;
-lwr_receive_hasc = NULL;
-lwr_receive_ungetc = NULL;
 
 /* Set up the message size limit; this may be host-specific */
 
@@ -2485,10 +2548,10 @@ thismessage_size_limit = expand_string_integer(message_size_limit, TRUE);
 if (expand_string_message)
   {
   if (thismessage_size_limit == -1)
-    log_write(0, LOG_MAIN|LOG_PANIC, "unable to expand message_size_limit: "
+    log_write(LOG_MAIN|LOG_PANIC, "unable to expand message_size_limit: "
       "%s", expand_string_message);
   else
-    log_write(0, LOG_MAIN|LOG_PANIC, "invalid message_size_limit: "
+    log_write(LOG_MAIN|LOG_PANIC, "invalid message_size_limit: "
       "%s", expand_string_message);
   smtp_closedown(US"Temporary local problem - please try later");
   return FALSE;
@@ -2554,7 +2617,7 @@ if (!f.sender_host_unknown)
     of writing. So for that error, carry on - we just can't do an IP options
     check. */
 
-    DEBUG(D_receive) debug_printf("checking for IP options\n");
+    DEBUG(receive) debug_printf("checking for IP options\n");
 
     if (  smtp_out_fd < 0
        || getsockopt(smtp_out_fd, IPPROTO_IP, IP_OPTIONS, US ipopt,
@@ -2562,7 +2625,7 @@ if (!f.sender_host_unknown)
       {
       if (errno != ENOPROTOOPT)
         {
-        log_write(0, LOG_MAIN, "getsockopt() failed from %s: %s",
+        log_write(LOG_MAIN, "getsockopt() failed from %s: %s",
           host_and_ident(FALSE), strerror(errno));
         smtp_printf("451 SMTP service not available\r\n", SP_NO_MORE);
         return FALSE;
@@ -2579,7 +2642,7 @@ if (!f.sender_host_unknown)
 
     /* Length of options = 0 => there are no options */
 
-    else DEBUG(D_receive) debug_printf("no IP options found\n");
+    else DEBUG(receive) debug_printf("no IP options found\n");
     }
 #endif  /* HAVE_IPV6 && !defined(NO_IP_OPTIONS) */
 
@@ -2615,7 +2678,7 @@ if (!f.sender_host_unknown)
        || !(*exp)
        || (smtp_receive_timeout = readconf_readtime(exp, 0, FALSE)) < 0
        )
-      log_write(0, LOG_MAIN|LOG_PANIC,
+      log_write(LOG_MAIN|LOG_PANIC,
 	"bad value for smtp_receive_timeout: '%s'", exp ? exp : US"");
     }
 
@@ -2623,8 +2686,9 @@ if (!f.sender_host_unknown)
 
   if (verify_check_host(&host_reject_connection) == OK)
     {
-    log_write(L_connection_reject, LOG_MAIN|LOG_REJECT, "refused connection "
-      "from %s (host_reject_connection)", host_and_ident(FALSE));
+    if (LOGGING(connection_reject))
+      log_write(LOG_MAIN|LOG_REJECT, "refused connection "
+	"from %s (host_reject_connection)", host_and_ident(FALSE));
 #ifndef DISABLE_TLS
     if (!tls_in.on_connect)
 #endif
@@ -2640,11 +2704,12 @@ if (!f.sender_host_unknown)
     {
     if ((rc = verify_check_host(&smtp_reserve_hosts)) != OK)
       {
-      log_write(L_connection_reject,
-        LOG_MAIN, "temporarily refused connection from %s: not in "
-        "reserve list: connected=%d max=%d reserve=%d%s",
-        host_and_ident(FALSE), smtp_accept_count - 1, smtp_accept_max,
-        smtp_accept_reserve, (rc == DEFER)? " (lookup deferred)" : "");
+      if (LOGGING(connection_reject))
+	log_write(LOG_MAIN,
+	  "temporarily refused connection from %s: not in "
+	  "reserve list: connected=%d max=%d reserve=%d%s",
+	  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", SP_NO_MORE, smtp_active_hostname);
       return FALSE;
@@ -2663,10 +2728,11 @@ if (!f.sender_host_unknown)
        !reserved_host &&
        verify_check_host(&smtp_reserve_hosts) != OK)
     {
-    log_write(L_connection_reject,
-      LOG_MAIN, "temporarily refused connection from %s: not in "
-      "reserve list and load average = %.2f", host_and_ident(FALSE),
-      (double)load_average/1000.0);
+    if (LOGGING(connection_reject))
+      log_write(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", SP_NO_MORE,
       smtp_active_hostname);
     return FALSE;
@@ -2714,11 +2780,19 @@ proxy_session = FALSE;
 /* If valid Proxy Protocol source is connecting, set up session.
 Failure will not allow any SMTP function other than QUIT. */
 
-f.proxy_session_failed = FALSE;
-if (proxy_protocol_host())
+f.quit_cmd_only = FALSE;
+if (hosts_proxy)
   {
-  os_non_restarting_signal(SIGALRM, command_timeout_handler);
-  proxy_protocol_setup();
+  misc_module_info * mi;
+  typedef BOOL (*fn_t) (void);
+
+  if (!(mi = misc_mod_find(US"proxy", NULL)))
+    {
+    smtp_closedown(US"Temporary local problem - please try later");
+    return FALSE;
+    }
+  if (!((fn_t *) mi->functions)[PROXY_PROTO_START] ())
+    f.quit_cmd_only = TRUE;
   }
 #endif
 
@@ -2765,7 +2839,7 @@ else
   GET_OPTION("smtp_banner");
   if (!(s = expand_string(smtp_banner)))
     {
-    log_write(0, f.expand_string_forcedfail ? LOG_MAIN : LOG_MAIN|LOG_PANIC_DIE,
+    log_write(f.expand_string_forcedfail ? LOG_MAIN : LOG_MAIN|LOG_PANIC_DIE,
       "Expansion of %q (smtp_banner) failed: %s",
       smtp_banner, expand_string_message);
     /* for force-fail */
@@ -2841,7 +2915,7 @@ if (gstring_length(ss) == 0)			/* banner already sent */
   if (f.running_in_test_harness)		/* make visible to testsuite */
     {
     for (int i = 20; i && check_sync(WBR_DATA_OR_EOF); i--) millisleep(5);
-    log_write(0, LOG_MAIN, "Ci=%s SMTP peer appears to have %s our banner",
+    log_write(LOG_MAIN, "Ci=%s SMTP peer appears to have %s our banner",
 		connection_id, check_sync(WBR_DATA_OR_EOF) ? "NOT SEEN" : "seen");
     }
   }
@@ -2851,10 +2925,6 @@ else						/* not already sent */
   this synchronisation check is disabled. */
 
 #ifndef DISABLE_PIPE_CONNECT
-  fl.pipe_connect_acceptable =
-       sender_host_address
-    && verify_check_host(&pipe_connect_advertise_hosts) == OK;
-
   if (!check_sync(WBR_DATA_ONLY))
     if (fl.pipe_connect_acceptable)
       f.smtp_in_early_pipe_used = TRUE;
@@ -2864,15 +2934,15 @@ else						/* not already sent */
 #endif
       {
       unsigned nchars = 128;
-      uschar * buf = receive_getbuf(&nchars);		/* destructive read */
+      const uschar * buf = receive_getbuf(&nchars);	/* destructive read */
 
       if (buf)
-	log_write(0, LOG_MAIN|LOG_REJECT, "SMTP protocol "
+	log_write(LOG_MAIN|LOG_REJECT, "SMTP protocol "
 	  "synchronization error (input sent without waiting for greeting): "
 	  "rejected connection from %s input=%q", host_and_ident(TRUE),
 	  string_printing(string_copyn(buf, nchars)));
       else
-	log_write(0, LOG_MAIN|LOG_REJECT, "Error or EOF on input from %s",
+	log_write(LOG_MAIN|LOG_REJECT, "Error or EOF on input from %s",
 	  host_and_ident(TRUE));
       smtp_printf("554 SMTP synchronization error\r\n", SP_NO_MORE);
       return FALSE;
@@ -2913,7 +2983,7 @@ to do so. Then transmit the error response. The return value depends on the
 number of syntax and protocol errors in this SMTP session.
 
 Arguments:
-  type      error type, given as a log flag bit
+  type      error type: TRUE for protocol, FALSE for syntax
   code      response code; <= 0 means don't send a response
   data      data to reflect in the response (can be NULL)
   errmess   the error message
@@ -2925,25 +2995,27 @@ These values fit in with the values of the "done" variable in the main
 processing loop in smtp_setup_msg(). */
 
 int
-synprot_error(int type, int code, uschar *data, uschar *errmess)
+synprot_error(BOOL type, int code, uschar *data, uschar *errmess)
 {
 int yield = -1;
+BOOL syntax_error = !type;
 
 #ifndef DISABLE_EVENT
 event_raise(event_action,
-  L_smtp_syntax_error ? US"smtp:fail:syntax" : US"smtp:fail:protocol",
+  syntax_error ? US"smtp:fail:syntax" : US"smtp:fail:protocol",
   errmess, NULL);
 #endif
 
-log_write(type, LOG_MAIN, "SMTP %s error in %q %s %s",
-  type == L_smtp_syntax_error ? "syntax" : "protocol",
-  string_printing(smtp_cmd_buffer), host_and_ident(TRUE), errmess);
+if (syntax_error ? LOGGING(smtp_syntax_error) : LOGGING(smtp_protocol_error))
+  log_write(LOG_MAIN, "SMTP %s error in %q %s %s",
+    syntax_error ? "syntax" : "protocol",
+    string_printing(smtp_cmd_buffer), host_and_ident(TRUE), errmess);
 
 GET_OPTION("smtp_max_synprot_errors");
 if (++synprot_error_count > smtp_max_synprot_errors)
   {
   yield = 1;
-  log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many "
+  log_write(LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many "
     "syntax or protocol errors (last command was %q, %Y)",
     host_and_ident(FALSE), string_printing(smtp_cmd_buffer),
     s_connhad_log(NULL)
@@ -2959,7 +3031,7 @@ if (code > 0)
     {
     smtp_notquit_exit(US"bad-command-synprot", string_sprintf("%d", code),
 		      US"Too many syntax or protocol errors");
-    DEBUG(D_any) debug_printf_indent("SMTP(close)>>\n");
+    DEBUG(any) debug_printf_indent("SMTP(close)>>\n");
 #ifndef DISABLE_TLS
     tls_close(NULL, TLS_SHUTDOWN_WAIT);
 #endif
@@ -3089,7 +3161,7 @@ if (!msg || !*msg || !regex_match(regex_smtp_code, *msg, -1, &match))
 len = Ustrlen(match);
 if (check_valid && (*msg)[0] != (*code)[0])
   {
-  log_write(0, LOG_MAIN|LOG_PANIC, "configured error code starts with "
+  log_write(LOG_MAIN|LOG_PANIC, "configured error code starts with "
     "incorrect digit (expected %c) in %q", (*code)[0], *msg);
   if (log_msg && *log_msg == *msg)
     *log_msg = string_sprintf("%s %s", *code, *log_msg + len);
@@ -3147,10 +3219,7 @@ smtp_handle_acl_fail(int where, int rc, uschar * user_msg, uschar * log_msg)
 {
 BOOL drop = rc == FAIL_DROP;
 int codelen = 3;
-uschar *smtp_code;
-uschar *lognl;
-uschar *sender_info = US"";
-uschar *what;
+uschar * smtp_code, * lognl, * sender_info = US"", * what;
 
 if (drop) rc = FAIL;
 
@@ -3198,7 +3267,7 @@ switch (where)
 #ifdef WITH_CONTENT_SCAN
   case ACL_WHERE_MIME:
 #endif
-    sender_info = string_sprintf("F=<%s>%s%s%s%s ",
+    sender_info = string_sprintf(" F=<%s>%s%s%s%s",
       sender_address_unrewritten ? sender_address_unrewritten : sender_address,
       sender_host_authenticated ? US" A="                                    : US"",
       sender_host_authenticated ? sender_host_authenticated                  : US"",
@@ -3222,7 +3291,7 @@ if (sender_verified_failed &&
   setflag(sender_verified_failed, af_sverify_told);
 
   if (rc != FAIL || LOGGING(sender_verify_fail))
-    log_write(0, LOG_MAIN|LOG_REJECT, "%s sender verify %s for <%s>%s",
+    log_write(LOG_MAIN|LOG_REJECT, "%s sender verify %s for <%s>%s",
       host_and_ident(TRUE),
       ((sender_verified_failed->special_action & 255) == DEFER)? "defer":"fail",
       sender_verified_failed->address,
@@ -3295,24 +3364,18 @@ smtp_fflush(SFF_UNCORK);
 the connection is not forcibly to be dropped, return 0. Otherwise, log why it
 is closing if required and return 2.  */
 
-if (log_reject_target)
-  {
-#ifndef DISABLE_TLS
-  gstring * g = add_tls_info_for_log(NULL);
-  uschar * tls = string_from_gstring(g);
-  if (!tls) tls = US"";
-#else
-  uschar * tls = US"";
-#endif
-  log_write(where == ACL_WHERE_CONNECT ? L_connection_reject : 0,
-    log_reject_target, "%s%s%s %s%srejected %s%s",
+if (  log_reject_target
+   && (where != ACL_WHERE_CONNECT || LOGGING(connection_reject)))
+  log_write(log_reject_target, "%s%s%#Y%s%#Y%#Y%#Y %srejected %s%s",
     LOGGING(dnssec) && sender_host_dnssec ? US" DS" : US"",
     host_and_ident(TRUE),
-    tls,
+    add_tls_info_for_log(NULL),
     sender_info,
+    add_spf_info_for_log(NULL),
+    add_dmarc_info_for_log(NULL),
+    add_dsn_info_for_log(NULL, where),
     rc == FAIL ? US"" : US"temporarily ",
     what, log_msg);
-  }
 
 if (!drop) return 0;
 
@@ -3326,13 +3389,13 @@ smtp_notquit_exit(US"acl-drop", NULL, NULL);
 
 /* An overenthusiastic fail2ban/iptables implimentation has been seen to result
 in the TCP conn staying open, and retrying, despite this process exiting. A
-malicious client could possibly do the same, tying up server netowrking
+malicious client could possibly do the same, tying up server networking
 resources. Close the socket explicitly to try to avoid that (there's a note in
 the Linux socket(7) manpage, SO_LINGER para, to the effect that exit() without
 close() results in the socket always lingering). */
 
 (void) poll_one_fd(smtp_in_fd, POLLIN, 200);
-DEBUG(D_any) debug_printf_indent("SMTP(close)>>\n");
+DEBUG(any) debug_printf_indent("SMTP(close)>>\n");
 smtp_inout_close();
 
 return 2;
@@ -3381,7 +3444,7 @@ we get when the line goes on to drop when CHUNKING. */
 if (smtp_notquit_reason)
   {
 #ifdef notdef
-  log_write(0, LOG_PANIC,
+  log_write(LOG_PANIC,
     "smtp_notquit_exit() called more than once (%s, prev: %s)",
     reason, smtp_notquit_reason);
 #endif
@@ -3395,7 +3458,7 @@ GET_OPTION("acl_smtp_notquit");
 if (acl_smtp_notquit && reason)
   if (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_write(LOG_MAIN|LOG_PANIC, "ACL for not-QUIT returned ERROR: %s",
       log_msg);
 
 /* Write an SMTP response if we are expected to give one. As the default
@@ -3446,19 +3509,19 @@ smtp_verify_helo(void)
 {
 BOOL yield = TRUE;
 
-HDEBUG(D_receive) debug_printf("verifying EHLO/HELO argument %q\n",
+HDEBUG(receive) debug_printf("verifying EHLO/HELO argument %q\n",
   sender_helo_name);
 
 if (sender_helo_name == NULL)
   {
-  HDEBUG(D_receive) debug_printf("no EHLO/HELO command was issued\n");
+  HDEBUG(receive) debug_printf("no EHLO/HELO command was issued\n");
   }
 
 /* Deal with the case of -bs without an IP address */
 
 else if (sender_host_address == NULL)
   {
-  HDEBUG(D_receive) debug_printf("no client IP address: assume success\n");
+  HDEBUG(receive) debug_printf("no client IP address: assume success\n");
   f.helo_verified = TRUE;
   }
 
@@ -3478,7 +3541,7 @@ else if (sender_helo_name[0] == '[')
     }
 #endif
 
-  HDEBUG(D_receive)
+  HDEBUG(receive)
     { if (f.helo_verified) debug_printf("matched host address\n"); }
   }
 
@@ -3497,7 +3560,7 @@ else
     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");
+      HDEBUG(receive) debug_printf("matched host name\n");
       }
     else
       {
@@ -3509,7 +3572,7 @@ else
 	  break;
 	  }
 
-      HDEBUG(D_receive) if (f.helo_verified)
+      HDEBUG(receive) if (f.helo_verified)
           debug_printf("matched alias %s\n", *(--aliases));
       }
 
@@ -3523,7 +3586,7 @@ else
     dnssec_domains d =
       {.request = US"*", .require = US""};
 
-    HDEBUG(D_receive) debug_printf("getting IP address for %s\n",
+    HDEBUG(receive) debug_printf("getting IP address for %s\n",
       sender_helo_name);
     rc = host_find_bydns(&h, NULL, HOST_FIND_BY_A | HOST_FIND_BY_AAAA,
 			  NULL, NULL, NULL, &d, NULL, NULL);
@@ -3533,7 +3596,7 @@ else
           {
           f.helo_verified = TRUE;
 	  if (h.dnssec_used == DS_YES) sender_helo_dnssec = TRUE;
-          HDEBUG(D_receive)
+          HDEBUG(receive)
             debug_printf("IP address for %s matches calling address\n"
 	      "Forward DNS security status: %sverified\n",
               sender_helo_name, sender_helo_dnssec ? "" : "un");
@@ -3705,7 +3768,7 @@ qualify_recipient(uschar ** recipient, uschar * smtp_cmd_data, uschar * tag)
 if (f.allow_unqualified_recipient || strcmpic(*recipient, US"postmaster") == 0)
   {
   int rd = Ustrlen(recipient) + 1;
-  DEBUG(D_receive) debug_printf("unqualified address %s accepted\n",
+  DEBUG(receive) debug_printf("unqualified address %s accepted\n",
     *recipient);
   /* deconst ok as *recipient was not const */
   *recipient = US rewrite_address_qualify(*recipient, TRUE);
@@ -3713,9 +3776,9 @@ if (f.allow_unqualified_recipient || strcmpic(*recipient, US"postmaster") == 0)
   }
 smtp_printf("501 %s: recipient address must contain a domain\r\n", SP_NO_MORE,
   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);
+if (LOGGING(smtp_syntax_error))
+  log_write(LOG_MAIN|LOG_REJECT, "unqualified %s rejected: <%s> %s%s",
+    tag, *recipient, host_and_ident(TRUE), host_lookup_msg);
 return 0;
 }
 
@@ -3732,7 +3795,7 @@ GET_OPTION("acl_smtp_quit");
 if (  acl_smtp_quit
    && acl_check(ACL_WHERE_QUIT, NULL, acl_smtp_quit, user_msgp, log_msgp)
 	== ERROR)
-    log_write(0, LOG_MAIN|LOG_PANIC, "ACL for QUIT returned ERROR: %s",
+    log_write(LOG_MAIN|LOG_PANIC, "ACL for QUIT returned ERROR: %s",
       *log_msgp);
 
 #ifdef EXIM_TCP_CORK
@@ -3818,7 +3881,7 @@ if (verify_check_host(&wellknown_advertise_hosts) != FAIL)
   }
 
 smtp_printf("554 not permitted\r\n", SP_NO_MORE);
-log_write(0, LOG_MAIN|LOG_REJECT, "rejected %q from %s",
+log_write(LOG_MAIN|LOG_REJECT, "rejected %q from %s",
 	      smtp_cmd_buffer, sender_helo_name, host_and_ident(FALSE));
 return 0;
 }
@@ -3829,10 +3892,190 @@ static int
 expand_mailmax(const uschar * s)
 {
 if (!(s = expand_string(s)))
-  log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand smtp_accept_max_per_connection");
+  log_write(LOG_MAIN|LOG_PANIC, "failed to expand smtp_accept_max_per_connection");
 return *s ? Uatoi(s) : 0;
 }
 
+
+
+static int
+etrn_handle(uschar ** user_msgp, uschar ** log_msgp)
+{
+int rc;
+uschar * etrn_command, * etrn_serialize_key = NULL;
+const uschar ** argv;
+void (*oldsignal)(int);
+pid_t pid;
+
+if (sender_address)
+  return synprot_error(TRUE, 503, NULL,
+    US"ETRN is not permitted inside a transaction");
+
+if (LOGGING(etrn))
+  log_write(LOG_MAIN, "ETRN %s received from %s", smtp_cmd_argument,
+    host_and_ident(FALSE));
+
+GET_OPTION("acl_smtp_etrn");
+if ((rc = acl_check(ACL_WHERE_ETRN, NULL, acl_smtp_etrn,
+	    user_msgp, log_msgp)) != OK)
+  return smtp_handle_acl_fail(ACL_WHERE_ETRN, rc, *user_msgp, *log_msgp);
+
+/* Compute the serialization key for this command. We used (all the way
+back to 4.00) to include the given string as part of the key, but this
+opens a security hole for hintsdb types that use a command-string for
+operations. So, use a hash of the string. All ETRN with the same command
+hash are serialized */
+
+if (smtp_etrn_serialize)
+  {
+  md5 hash;
+  uschar * digest = store_get(16, GET_TAINTED);
+
+  md5_start(&hash);
+  md5_end(&hash, smtp_cmd_argument, Ustrlen(smtp_cmd_argument), digest);
+
+  etrn_serialize_key = string_sprintf("etrn-%.16H", digest);
+  }
+
+/* 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. */
+
+GET_OPTION("smtp_etrn_command");
+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,
+		  TSUC_EXPAND_ARGS, 0, NULL, US"ETRN processing", &error);
+  deliver_domain = NULL;
+  if (!rc)
+    {
+    log_write(LOG_MAIN|LOG_PANIC, "failed to set up ETRN command: %s",
+      error);
+    smtp_printf("458 Internal failure\r\n", SP_NO_MORE);
+    return 0;
+    }
+  }
+
+/* Else set up to call Exim with the -R option. */
+
+else
+  {
+  if (*smtp_cmd_data++ != '#')
+    return synprot_error(FALSE, 501, NULL,
+      US"argument must begin with #");
+
+  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);
+  }
+
+/* If we are host-testing, don't actually do anything. */
+
+if (host_checking)
+  {
+  HDEBUG(any)
+    {
+    debug_printf("ETRN command is: %s\n", etrn_command);
+    debug_printf("ETRN command execution skipped\n");
+    }
+  if (*user_msgp)
+    smtp_user_msg(US"250", *user_msgp);
+  else
+    smtp_printf("250 OK\r\n", SP_NO_MORE);
+  return 0;
+  }
+
+
+/* If ETRN queue runs are to be serialized, check the database to
+ensure one isn't already running. */
+
+if (smtp_etrn_serialize && !enq_start(etrn_serialize_key, 1))
+  {
+  smtp_printf("458 Already processing %s\r\n", SP_NO_MORE, smtp_cmd_data);
+  return 0;
+  }
+
+/* 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 = exim_fork(US"etrn-command")) == 0)
+  {
+  smtp_input = FALSE;       /* This process is not associated with the */
+  smtp_inout_close();	  /* SMTP call any more. */
+
+  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 = exim_fork(US"etrn-serialised-command")) == 0)
+    {
+    DEBUG(exec) debug_print_argv(argv);
+    exim_nullstd();                   /* Ensure std{in,out,err} exist */
+    /* argv[0] should be untainted, from child_exec_exim() */
+    execv(CS argv[0], (char *const *)argv);
+    log_write_die(LOG_MAIN, "exec of %q (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(LOG_MAIN|LOG_PANIC, "2nd fork for serialized ETRN "
+      "failed: %s", strerror(errno));
+  else
+    {
+    int status;
+    DEBUG(any)
+      debug_printf("waiting for serialized ETRN process " PID_T_FMT "\n", pid);
+    (void)wait(&status);
+    DEBUG(any)
+      debug_printf("serialized ETRN process " PID_T_FMT " ended\n", pid);
+    }
+
+  if (smtp_etrn_serialize) enq_end(etrn_serialize_key);
+  exim_underbar_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(LOG_MAIN|LOG_PANIC, "fork of process for ETRN failed: %s",
+    strerror(errno));
+  smtp_printf("458 Unable to fork process\r\n", SP_NO_MORE);
+  if (smtp_etrn_serialize) enq_end(etrn_serialize_key);
+  }
+else
+  if (*user_msgp)
+    smtp_user_msg(US"250", *user_msgp);
+  else
+    smtp_printf("250 OK\r\n", SP_NO_MORE);
+
+signal(SIGCHLD, oldsignal);
+return 0;
+}
+
+
 /*************************************************
 *       Initialize for SMTP incoming message     *
 *************************************************/
@@ -3864,9 +4107,13 @@ smtp_setup_msg(void)
 int done = 0;
 BOOL toomany = FALSE, discarded = FALSE, last_was_rej_mail = FALSE,
     last_was_rcpt = FALSE;
+#ifdef EXPERIMENTAL_XCLIENT
+static misc_module_info * xclient_mi = NULL;
+#endif
 rmark reset_point = store_mark();
 
-DEBUG(D_receive) debug_printf("smtp_setup_msg entered\n");
+DEBUG(receive)
+  { debug_printf("smtp_setup_msg entered\n"); debug_print_processing_stack(); }
 
 /* Reset for start of new message. We allow one RSET not to be counted as a
 nonmail command, for those MTAs that insist on sending it between every
@@ -3886,14 +4133,6 @@ cmd_list[CL_EHLO].is_mail_cmd = TRUE;
 cmd_list[CL_STLS].is_mail_cmd = TRUE;
 #endif
 
-if (lwr_receive_getc && !atrn_mode)
-  {
-  /* This should have already happened, but if we've gotten confused,
-  force a reset here. */
-  DEBUG(D_receive) debug_printf("WARNING: smtp_setup_msg had to restore receive functions to lowers\n");
-  bdat_pop_receive_functions();
-  }
-
 /* Set the local signal handler for SIGTERM - it tries to end off tidily */
 
 had_command_sigterm = 0;
@@ -3914,17 +4153,12 @@ value. The values are 2 larger than the required yield of the function. */
 
 while (done <= 0)
   {
-  const uschar ** argv;
-  uschar * etrn_command, * etrn_serialize_key, * errmess;
-  uschar * log_msg, * smtp_code;
+  uschar * errmess, * log_msg, * smtp_code;
   uschar * user_msg = NULL, * recipient = NULL, * hello = NULL;
-  uschar * s, * ss;
+  uschar * s;
   BOOL was_rej_mail = FALSE, was_rcpt = FALSE;
-  void (*oldsignal)(int);
-  pid_t pid;
   int start, end, sender_domain, recipient_domain;
-  int rc, c, dsn_flags;
-  uschar * orcpt = NULL;
+  int rc, c;
   gstring * g;
 
 #ifdef AUTH_TLS
@@ -3948,19 +4182,20 @@ while (done <= 0)
 	  done = smtp_handle_acl_fail(ACL_WHERE_AUTH, rc, user_msg, log_msg);
 	else
 	  {
-	  smtp_cmd_data = NULL;
+	  uschar * dummy_errmsg;
 
-	  if (smtp_in_auth(au, &s, &ss) == OK)
-	    { DEBUG(D_auth) debug_printf("tls auth succeeded\n"); }
+	  smtp_cmd_data = NULL;
+	  if (smtp_in_auth(au, &s, &dummy_errmsg) == OK)
+	    { DEBUG(auth) debug_printf("tls auth succeeded\n"); }
 	  else
 	    {
-	    DEBUG(D_auth) debug_printf("tls auth not succeeded\n");
+	    DEBUG(auth) debug_printf("tls auth not succeeded\n");
 #ifndef DISABLE_EVENT
 	     {
 	      uschar * save_name = sender_host_authenticated, * logmsg;
 	      sender_host_authenticated = au->drinst.name;
 	      if ((logmsg = event_raise(event_action, US"auth:fail", s, NULL)))
-		log_write(0, LOG_MAIN, "%s", logmsg);
+		log_write(LOG_MAIN, "%s", logmsg);
 	      sender_host_authenticated = save_name;
 	     }
 #endif
@@ -4000,19 +4235,19 @@ while (done <= 0)
 
       if (!fl.auth_advertised && !f.allow_auth_unadvertised)
 	{
-	done = synprot_error(L_smtp_protocol_error, 503, NULL,
+	done = synprot_error(TRUE, 503, NULL,
 	  US"AUTH command used when not advertised");
 	break;
 	}
       if (sender_host_authenticated)
 	{
-	done = synprot_error(L_smtp_protocol_error, 503, NULL,
+	done = synprot_error(TRUE, 503, NULL,
 	  US"already authenticated");
 	break;
 	}
       if (sender_address)
 	{
-	done = synprot_error(L_smtp_protocol_error, 503, NULL,
+	done = synprot_error(TRUE, 503, NULL,
 	  US"not permitted in mail transaction");
 	break;
 	}
@@ -4035,7 +4270,7 @@ while (done <= 0)
       for (; (c = *smtp_cmd_data) && !isspace(c); smtp_cmd_data++)
 	if (!isalnum(c) && c != '-' && c != '_')
 	  {
-	  done = synprot_error(L_smtp_syntax_error, 501, NULL,
+	  done = synprot_error(FALSE, 501, NULL,
 	    US"invalid character in authentication mechanism name");
 	  goto COMMAND_LOOP;
 	  }
@@ -4078,14 +4313,14 @@ while (done <= 0)
 	     }
 #endif
 	    if (logmsg)
-	      log_write(0, LOG_MAIN|LOG_REJECT, "%s", logmsg);
+	      log_write(LOG_MAIN|LOG_REJECT, "%s", logmsg);
 	    else
-	      log_write(0, LOG_MAIN|LOG_REJECT, "%s authenticator failed for %s: %s",
+	      log_write(LOG_MAIN|LOG_REJECT, "%s authenticator failed for %s: %s",
 		au->drinst.name, host_and_ident(TRUE), errmsg);
 	    }
 	  }
 	else
-	  done = synprot_error(L_smtp_protocol_error, 504, NULL,
+	  done = synprot_error(TRUE, 504, NULL,
 	    string_sprintf("%s authentication mechanism not supported", s));
 	}
 
@@ -4129,7 +4364,7 @@ while (done <= 0)
 	{
 	smtp_printf("501 Syntactically invalid %s argument(s)\r\n", SP_NO_MORE, hello);
 
-	log_write(0, LOG_MAIN|LOG_REJECT, "rejected %s from %s: syntactically "
+	log_write(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));
@@ -4137,7 +4372,7 @@ while (done <= 0)
 	GET_OPTION("smtp_max_synprot_errors");
 	if (++synprot_error_count > smtp_max_synprot_errors)
 	  {
-	  log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many "
+	  log_write(LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many "
 	    "syntax or protocol errors (last command was %q, %Y)",
 	    host_and_ident(FALSE), string_printing(smtp_cmd_buffer),
 	    s_connhad_log(NULL)
@@ -4179,11 +4414,11 @@ while (done <= 0)
 	  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_verify_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. */
+	make some people happy to be able to do it. If helo_verify_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_verify_required || fl.helo_verify)
@@ -4195,13 +4430,13 @@ while (done <= 0)
 	      {
 	      smtp_printf("%d %s argument does not match calling host\r\n", SP_NO_MORE,
 		tempfail? 451 : 550, hello);
-	      log_write(0, LOG_MAIN|LOG_REJECT, "%srejected \"%s %s\" from %s",
+	      log_write(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 "
+	    HDEBUG(all) debug_printf("%s verification failed but host is in "
 	      "helo_try_verify_hosts\n", hello);
 	    }
 	  }
@@ -4214,7 +4449,7 @@ while (done <= 0)
       if (misc_mod_conn_init(sender_helo_name, sender_host_address, &errstr)
 	  != OK)
 	{
-	DEBUG(D_receive)
+	DEBUG(receive)
 	  debug_printf("A module conn-init routine failed: %s\n", errstr);
 	done = 1;
 	break;
@@ -4288,7 +4523,7 @@ while (done <= 0)
 	s = string_sprintf("%.*s%s", codelen, smtp_code, user_msg);
 	if ((t= strpbrk(CS s, "\r\n")) != NULL)
 	  {
-	  log_write(0, LOG_MAIN|LOG_PANIC, "EHLO/HELO response must not contain "
+	  log_write(LOG_MAIN|LOG_PANIC, "EHLO/HELO response must not contain "
 	    "newlines: message truncated: %s", string_printing(s));
 	  *t = '\0';
 	  }
@@ -4431,7 +4666,7 @@ while (done <= 0)
 	    au->advertised = FALSE;
 	    if (au->server)
 	      {
-	      DEBUG(D_auth+D_expand) debug_printf_indent(
+	      DEBUG(auth|expand) debug_printf_indent(
 		"Evaluating advertise_condition for %s %s authenticator\n",
 		au->drinst.name, au->public_name);
 	      if (  !au->advertise_condition
@@ -4486,8 +4721,16 @@ while (done <= 0)
 #ifdef EXPERIMENTAL_XCLIENT
 	if (proxy_session || verify_check_host(&hosts_xclient) != FAIL)
 	  {
+	  typedef gstring * (*fn_t) (gstring *);
+
+	  if (  !xclient_mi
+	     && !(xclient_mi = misc_mod_find(US"xclient", NULL)))
+	    {
+	    smtp_closedown(US"Temporary local problem - please try later");
+	    return FALSE;
+	    }
 	  g = string_catn(g, smtp_code, 3);
-	  g = xclient_smtp_advertise_str(g);
+	  g = ((fn_t *) xclient_mi->functions)[XCLIENT_PROTO_ADVERTISE] (g);
 	  }
 #endif
 #ifndef DISABLE_PRDR
@@ -4569,37 +4812,21 @@ while (done <= 0)
 
 #ifdef EXPERIMENTAL_XCLIENT
     case XCLIENT_CMD:
-      {
-      BOOL fatal = fl.helo_seen;
-      uschar * errmsg;
-      int resp;
-
       HAD(SCH_XCLIENT);
       smtp_mailcmd_count++;
 
-      if ((errmsg = xclient_smtp_command(smtp_cmd_data, &resp, &fatal)))
-	if (fatal)
-	  done = synprot_error(L_smtp_syntax_error, resp, NULL, errmsg);
-	else
-	  {
-	  smtp_printf("%d %s\r\n", SP_NO_MORE, resp, errmsg);
-	  log_write(0, LOG_MAIN|LOG_REJECT, "rejected XCLIENT from %s: %s",
-	    host_and_ident(FALSE), errmsg);
-	  }
+      if (!xclient_mi)
+	done = synprot_error(FALSE, 501, NULL,
+	  US"XCLIENT command used when not advertised");
       else
 	{
-	fl.helo_seen = FALSE;			/* Require another EHLO */
-	smtp_code = string_sprintf("%d", resp);
-
-	/*XXX unclear in spec. if this needs to be an ESMTP banner,
-	nor whether we get the original client's HELO after (or a proxy fake).
-	We require that we do; the following HELO/EHLO handling will set
-	sender_helo_name as normal. */
-
-	smtp_printf("%s XCLIENT success\r\n", SP_NO_MORE, smtp_code);
+	typedef BOOL (*fn_t) (const uschar *, int *, BOOL);
+	BOOL started = ((fn_t *) xclient_mi->functions)[XCLIENT_PROTO_START]
+					  (smtp_cmd_data, &done, fl.helo_seen);
+	if (started)
+	  fl.helo_seen = FALSE;			/* Require another EHLO */
 	}
       break; /* XCLIENT */
-      }
 #endif
 
 
@@ -4620,9 +4847,9 @@ while (done <= 0)
 	if (  fl.helo_verify_required
 	   || verify_check_host(&hosts_require_helo) == OK)
 	  {
-	  log_write(0, LOG_MAIN|LOG_REJECT, "rejected MAIL from %s: no "
+	  log_write(LOG_MAIN|LOG_REJECT, "rejected MAIL from %s: no "
 	    "HELO/EHLO given", host_and_ident(FALSE));
-	  done = synprot_error(L_smtp_protocol_error, 503, NULL,
+	  done = synprot_error(TRUE, 503, NULL,
 		      US"HELO or EHLO required");
 	  break;
 	  }
@@ -4631,14 +4858,14 @@ while (done <= 0)
 
       if (sender_address)
 	{
-	done = synprot_error(L_smtp_protocol_error, 503, NULL,
+	done = synprot_error(TRUE, 503, NULL,
 	  US"sender already given");
 	break;
 	}
 
       if (!*smtp_cmd_data)
 	{
-	done = synprot_error(L_smtp_protocol_error, 501, NULL,
+	done = synprot_error(TRUE, 501, NULL,
 	  US"MAIL must have an address operand");
 	break;
 	}
@@ -4649,7 +4876,7 @@ while (done <= 0)
       if (smtp_mailcmd_max > 0 && smtp_mailcmd_count > smtp_mailcmd_max)
 	{
 	smtp_printf("421 too many messages in this connection\r\n", SP_NO_MORE);
-	log_write(0, LOG_MAIN|LOG_REJECT, "rejected MAIL command %s: too many "
+	log_write(LOG_MAIN|LOG_REJECT, "rejected MAIL command %s: too many "
 	  "messages in one connection", host_and_ident(TRUE));
 	break;
 	}
@@ -4715,19 +4942,18 @@ while (done <= 0)
 	      else
 		{
 		body_8bitmime = 0;
-		done = synprot_error(L_smtp_syntax_error, 501, NULL,
+		done = synprot_error(FALSE, 501, NULL,
 		  US"invalid data for BODY");
 		goto COMMAND_LOOP;
 		}
-	      DEBUG(D_receive) debug_printf("8BITMIME: %d\n", body_8bitmime);
+	      DEBUG(receive) debug_printf("8BITMIME: %d\n", body_8bitmime);
 	      break;
 	    }
 	    arg_error = TRUE;
 	    break;
 
 	  /* 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. */
+	  will have caused "DSN" to be given in the EHLO response). */
 
 	  case ENV_MAIL_OPT_RET:
 	    if (fl.dsn_advertised)
@@ -4735,7 +4961,7 @@ while (done <= 0)
 	      /* Check if RET has already been set */
 	      if (dsn_ret > 0)
 		{
-		done = synprot_error(L_smtp_syntax_error, 501, NULL,
+		done = synprot_error(FALSE, 501, NULL,
 		  US"RET can be specified once only");
 		goto COMMAND_LOOP;
 		}
@@ -4744,11 +4970,11 @@ while (done <= 0)
 		: strcmpic(value, US"FULL") == 0
 		? dsn_ret_full
 		: 0;
-	      DEBUG(D_receive) debug_printf("DSN_RET: %d\n", dsn_ret);
+	      DEBUG(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,
+		done = synprot_error(FALSE, 501, NULL,
 		  US"Value for RET is invalid");
 		goto COMMAND_LOOP;
 		}
@@ -4760,12 +4986,12 @@ while (done <= 0)
 	      /* Check if the dsn envid has been already set */
 	      if (dsn_envid)
 		{
-		done = synprot_error(L_smtp_syntax_error, 501, NULL,
+		done = synprot_error(FALSE, 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);
+	      DEBUG(receive) debug_printf("DSN_ENVID: %s\n", dsn_envid);
 	      }
 	    break;
 
@@ -4787,7 +5013,7 @@ while (done <= 0)
 		/* Put back terminator overrides for error message */
 		value[-1] = '=';
 		name[-1] = ' ';
-		done = synprot_error(L_smtp_syntax_error, 501, NULL,
+		done = synprot_error(FALSE, 501, NULL,
 		  US"invalid data for AUTH");
 		goto COMMAND_LOOP;
 		}
@@ -4807,14 +5033,14 @@ while (done <= 0)
 	      switch (rc)
 		{
 		case OK:
-		  if (authenticated_by == NULL ||
-		      authenticated_by->mail_auth_condition == NULL ||
-		      expand_check_condition(authenticated_by->mail_auth_condition,
+		  if (!authenticated_by
+		     || !authenticated_by->mail_auth_condition
+		     || expand_check_condition(authenticated_by->mail_auth_condition,
 			  authenticated_by->drinst.name, US"authenticator"))
 		    break;     /* Accept the AUTH */
 
 		  ignore_msg = US"server_mail_auth_condition failed";
-		  if (authenticated_id != NULL)
+		  if (authenticated_id)
 		    ignore_msg = string_sprintf("%s: authenticated ID=%q",
 		      ignore_msg, authenticated_id);
 
@@ -4822,7 +5048,7 @@ while (done <= 0)
 
 		case FAIL:
 		  authenticated_sender = NULL;
-		  log_write(0, LOG_MAIN, "ignoring AUTH=%s from %s (%s)",
+		  log_write(LOG_MAIN, "ignoring AUTH=%s from %s (%s)",
 		    value, host_and_ident(TRUE), ignore_msg);
 		  break;
 
@@ -4850,12 +5076,12 @@ while (done <= 0)
 	  case ENV_MAIL_OPT_UTF8:
 	    if (!fl.smtputf8_advertised)
 	      {
-	      done = synprot_error(L_smtp_syntax_error, 501, NULL,
+	      done = synprot_error(FALSE, 501, NULL,
 		US"SMTPUTF8 used when not advertised");
 	      goto COMMAND_LOOP;
 	      }
 
-	    DEBUG(D_receive) debug_printf("smtputf8 requested\n");
+	    DEBUG(receive) debug_printf("smtputf8 requested\n");
 	    message_smtputf8 = allow_utf8_domains = TRUE;
 	    if (Ustrncmp(received_protocol, US"utf8", 4) != 0)
 	      {
@@ -4890,7 +5116,7 @@ while (done <= 0)
       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",
+	DEBUG(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;
@@ -4913,7 +5139,7 @@ while (done <= 0)
 
       if (!raw_sender)
 	{
-	done = synprot_error(L_smtp_syntax_error, 501, smtp_cmd_data, errmess);
+	done = synprot_error(FALSE, 501, smtp_cmd_data, errmess);
 	break;
 	}
 
@@ -4926,13 +5152,11 @@ while (done <= 0)
       if (thismessage_size_limit > 0 && message_size > thismessage_size_limit)
 	{
 	smtp_printf("552 Message size exceeds maximum permitted\r\n", SP_NO_MORE);
-	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,
+	if (LOGGING(size_reject))
+	  log_write(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;
@@ -4967,19 +5191,17 @@ while (done <= 0)
 	  sender_domain = Ustrlen(sender_address) + 1;
 	  /* deconst ok as sender_address was not const */
 	  sender_address = US rewrite_address_qualify(sender_address, FALSE);
-	  DEBUG(D_receive) debug_printf("unqualified address %s accepted\n",
+	  DEBUG(receive) debug_printf("unqualified address %s accepted\n",
 	    raw_sender);
 	  }
 	else
 	  {
 	  smtp_printf("501 %s: sender address must contain a domain\r\n", SP_NO_MORE,
 	    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);
+	  if (LOGGING(smtp_syntax_error))
+	    log_write(LOG_MAIN|LOG_REJECT,
+	      "unqualified sender rejected: <%s> %s%s",
+	      raw_sender, host_and_ident(TRUE), host_lookup_msg);
 	  sender_address = NULL;
 	  break;
 	  }
@@ -5037,10 +5259,10 @@ while (done <= 0)
 
     case RCPT_CMD:
       HAD(SCH_RCPT);
-      /* We got really to many recipients. A check against configured
+      /* We got badly too many recipients. A check against configured
       limits is done later */
       if (rcpt_count < 0 || rcpt_count >= INT_MAX/2)
-        log_write_die(0, LOG_MAIN, "Too many recipients: %d", rcpt_count);
+        log_write_die(LOG_MAIN, "Too many recipients: %d", rcpt_count);
       rcpt_count++;
       was_rcpt = fl.rcpt_in_progress = TRUE;
 
@@ -5058,7 +5280,7 @@ while (done <= 0)
 	  }
 	else
 	  {
-	  done = synprot_error(L_smtp_protocol_error, 503, NULL,
+	  done = synprot_error(TRUE, 503, NULL,
 	    US"sender not yet given");
 	  was_rcpt = FALSE;             /* Not a valid RCPT */
 	  }
@@ -5070,19 +5292,19 @@ while (done <= 0)
 
       if (!smtp_cmd_data[0])
 	{
-	done = synprot_error(L_smtp_syntax_error, 501, NULL,
+	done = synprot_error(FALSE, 501, NULL,
 	  US"RCPT must have an address operand");
 	rcpt_fail_count++;
 	break;
 	}
 
-      /* Set the DSN flags orcpt and dsn_flags from the session*/
-      orcpt = NULL;
-      dsn_flags = 0;
+      /* Set the DSN flags orcpt and dsn_flags from the session */
+      rcpt_orcpt = NULL;
+      rcpt_dsn_flags = 0;
 
       if (fl.esmtp) for(;;)
 	{
-	uschar *name, *value;
+	uschar * name, * value;
 
 	if (!extract_option(&name, &value))
 	  break;
@@ -5090,60 +5312,60 @@ while (done <= 0)
 	if (fl.dsn_advertised && strcmpic(name, US"ORCPT") == 0)
 	  {
 	  /* Check whether orcpt has been already set */
-	  if (orcpt)
+	  if (rcpt_orcpt)
 	    {
-	    done = synprot_error(L_smtp_syntax_error, 501, NULL,
+	    done = synprot_error(FALSE, 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);
+	  rcpt_orcpt = string_copy(value);
+	  DEBUG(receive) debug_printf("DSN orcpt: %s\n", rcpt_orcpt);
 	  }
 
 	else if (fl.dsn_advertised && strcmpic(name, US"NOTIFY") == 0)
 	  {
 	  /* Check if the notify flags have been already set */
-	  if (dsn_flags > 0)
+	  if (rcpt_dsn_flags > 0)
 	    {
-	    done = synprot_error(L_smtp_syntax_error, 501, NULL,
+	    done = synprot_error(FALSE, 501, NULL,
 		US"NOTIFY can be specified once only");
 	    goto COMMAND_LOOP;
 	    }
 	  if (strcmpic(value, US"NEVER") == 0)
-	    dsn_flags |= rf_notify_never;
+	    rcpt_dsn_flags |= rf_notify_never;
 	  else
 	    {
-	    uschar *p = value;
-	    while (*p != 0)
+	    uschar * p = value;
+	    while (*p)
 	      {
 	      uschar *pp = p;
-	      while (*pp != 0 && *pp != ',') pp++;
+	      while (*pp && *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;
+		DEBUG(receive) debug_printf("DSN: Setting notify success\n");
+		rcpt_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;
+		DEBUG(receive) debug_printf("DSN: Setting notify failure\n");
+		rcpt_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;
+		DEBUG(receive) debug_printf("DSN: Setting notify delay\n");
+		rcpt_dsn_flags |= rf_notify_delay;
 		}
 	      else
 		{
 		/* Catch any strange values */
-		done = synprot_error(L_smtp_syntax_error, 501, NULL,
+		done = synprot_error(FALSE, 501, NULL,
 		  US"Invalid value for NOTIFY parameter");
 		goto COMMAND_LOOP;
 		}
 	      p = pp;
 	      }
-	      DEBUG(D_receive) debug_printf("DSN Flags: %x\n", dsn_flags);
+	      DEBUG(receive) debug_printf("DSN Flags: %x\n", rcpt_dsn_flags);
 	    }
 	  }
 
@@ -5152,7 +5374,7 @@ while (done <= 0)
 
 	else
 	  {
-	  DEBUG(D_receive) debug_printf("Invalid RCPT option: %s : %s\n", name, value);
+	  DEBUG(receive) debug_printf("Invalid RCPT option: %s : %s\n", name, value);
 	  name[-1] = ' ';
 	  value[-1] = '=';
 	  break;
@@ -5171,7 +5393,7 @@ while (done <= 0)
       if (!(recipient = parse_extract_address(recipient, &errmess, &start, &end,
 	&recipient_domain, FALSE)))
 	{
-	done = synprot_error(L_smtp_syntax_error, 501, smtp_cmd_data, errmess);
+	done = synprot_error(FALSE, 501, smtp_cmd_data, errmess);
 	rcpt_fail_count++;
 	break;
 	}
@@ -5205,7 +5427,7 @@ while (done <= 0)
 	  rcpt_fail_count++;
 	  smtp_printf("552 too many recipients\r\n", SP_NO_MORE);
 	  if (!toomany)
-	    log_write(0, LOG_MAIN|LOG_REJECT, "too many recipients: message "
+	    log_write(LOG_MAIN|LOG_REJECT, "too many recipients: message "
 	      "rejected: sender=<%s> %s", sender_address, host_and_ident(TRUE));
 	  }
 	else
@@ -5213,7 +5435,7 @@ while (done <= 0)
 	  rcpt_defer_count++;
 	  smtp_printf("452 too many recipients\r\n", SP_NO_MORE);
 	  if (!toomany)
-	    log_write(0, LOG_MAIN|LOG_REJECT, "too many recipients: excess "
+	    log_write(LOG_MAIN|LOG_REJECT, "too many recipients: excess "
 	      "temporarily rejected: sender=<%s> %s", sender_address,
 	      host_and_ident(TRUE));
 	  }
@@ -5228,7 +5450,7 @@ while (done <= 0)
       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",
+	DEBUG(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;
@@ -5262,15 +5484,7 @@ while (done <= 0)
 	  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); */
+	receive_add_recipient(recipient, -1, rcpt_dsn_flags, rcpt_orcpt);
 	}
 
       /* The recipient was discarded */
@@ -5283,7 +5497,7 @@ while (done <= 0)
 	  smtp_printf("250 Accepted\r\n", SP_NO_MORE);
 	rcpt_fail_count++;
 	discarded = TRUE;
-	log_write(0, LOG_MAIN|LOG_REJECT, "%s F=<%s> RCPT %s: "
+	log_write(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",
@@ -5327,7 +5541,7 @@ while (done <= 0)
       HAD(SCH_BDAT);
       if (chunking_state != CHUNKING_OFFERED)
 	{
-	done = synprot_error(L_smtp_protocol_error, 503, NULL,
+	done = synprot_error(TRUE, 503, NULL,
 	  US"BDAT command used when CHUNKING not advertised");
 	break;
 	}
@@ -5336,14 +5550,14 @@ while (done <= 0)
 
       if (sscanf(CS smtp_cmd_data, "%u %n", &chunking_datasize, &n) < 1)
 	{
-	done = synprot_error(L_smtp_protocol_error, 501, NULL,
+	done = synprot_error(TRUE, 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 '%s', %d bytes\n",
+      DEBUG(receive) debug_printf("chunking state '%s', %d bytes\n",
 			chunking_states[chunking_state], chunking_data_left);
 
       f.bdat_readers_wanted = TRUE; /* FIXME: redundant vs chunking_state? */
@@ -5378,7 +5592,7 @@ while (done <= 0)
 	  smtp_printf("503 Valid RCPT command must precede %s\r\n", SP_NO_MORE,
 	    smtp_names[smtp_connection_had[SMTP_HBUFF_PREV(smtp_ch_index)]]);
 	else
-	  done = synprot_error(L_smtp_protocol_error, 503, NULL,
+	  done = synprot_error(TRUE, 503, NULL,
 	    smtp_connection_had[SMTP_HBUFF_PREV(smtp_ch_index)] == SCH_DATA
 	    ? US"valid RCPT command must precede DATA"
 	    : US"valid RCPT command must precede BDAT");
@@ -5386,7 +5600,7 @@ while (done <= 0)
 	if (chunking_state > CHUNKING_OFFERED)
 	  {
 	  bdat_push_receive_functions();
-	  bdat_flush_data();
+	  bdat_flush_data(rx_prc);
 	  }
 	break;
 	}
@@ -5400,7 +5614,7 @@ while (done <= 0)
 	if (chunking_state > CHUNKING_OFFERED)
 	  {
 	  bdat_push_receive_functions();
-	  bdat_flush_data();
+	  bdat_flush_data(rx_prc);
 	  }
 	break;
 	}
@@ -5498,7 +5712,7 @@ while (done <= 0)
 	    s = addr->user_message
 	      ? 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",
+	    log_write(LOG_MAIN, "VRFY failed for %s %s",
 	      smtp_cmd_argument, host_and_ident(TRUE));
 	    break;
 	  }
@@ -5534,7 +5748,7 @@ while (done <= 0)
       HAD(SCH_STARTTLS);
       if (!fl.tls_advertised)
 	{
-	done = synprot_error(L_smtp_protocol_error, 503, NULL,
+	done = synprot_error(TRUE, 503, NULL,
 	  US"STARTTLS command used when not advertised");
 	break;
 	}
@@ -5572,7 +5786,7 @@ while (done <= 0)
 
       if (receive_hasc())
 	{
-	DEBUG(D_any)
+	DEBUG(any)
 	  debug_printf("Non-empty input buffer after STARTTLS; naive attack?\n");
 	if (tls_in.active.sock < 0)
 	  smtp_inend = smtp_inptr = smtp_inbuffer;
@@ -5625,7 +5839,7 @@ while (done <= 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");
+	DEBUG(tls) debug_printf("TLS active\n");
 	break;     /* Successful STARTTLS */
 	}
       else
@@ -5646,7 +5860,7 @@ while (done <= 0)
       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");
+      DEBUG(tls) debug_printf("TLS failed to start\n");
       while (done <= 0) switch(smtp_read_command(FALSE, GETC_BUFFER_UNLIMITED))
 	{
 	case EOF_CMD:
@@ -5732,7 +5946,7 @@ while (done <= 0)
 
     case EOF_CMD:
       {
-      uschar * errstr = smtp_ferror()
+      uschar * errstr = smtp_ferror(rx_prc)
 	? string_sprintf(" (error: %s)", strerror(errno)) : US"";
 
       incomplete_transaction_log(US"connection lost");
@@ -5744,20 +5958,22 @@ while (done <= 0)
       */
 
       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), errstr,
-	  string_timesince(&smtp_connection_start)
-	  );
-
-      else
-	log_write(L_smtp_connection, LOG_MAIN, "%s %slost%s D=%s",
+	{
+	if (LOGGING(lost_incoming_connection))
+	  log_write(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), errstr,
+	    string_timesince(&smtp_connection_start)
+	    );
+	  }
+      else if (LOGGING(smtp_connection))
+	log_write(LOG_MAIN, "%s %slost%s D=%s",
 	  smtp_get_connection_info(),
 	  f.tcp_in_fastopen && !f.tcp_in_fastopen_logged ? US"TFO " : US"",
 	  errstr,
@@ -5775,181 +5991,11 @@ while (done <= 0)
 
     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;
-	}
-
-      log_write(L_etrn, LOG_MAIN, "ETRN %s received from %s", smtp_cmd_argument,
-	host_and_ident(FALSE));
-
-      GET_OPTION("acl_smtp_etrn");
-      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;
-	}
-
-      /* Compute the serialization key for this command. We used (all the way
-      back to 4.00) to include the given string as part of the key, but this
-      opens a security hole for hintsdb types that use a command-string for
-      operations. All ETRN with the same command hash are serialized */
-
-      md5 hash;
-      uschar *digest = store_get(16, GET_TAINTED);
-
-      md5_start(&hash);
-      md5_end(&hash, smtp_cmd_argument, Ustrlen(smtp_cmd_argument), digest);
-
-      etrn_serialize_key = string_sprintf("etrn-" /* don't we have a function doing exactly this? */
-          "%02x%02x%02x%02x" "%02x%02x%02x%02x"   /* we have, since 2024-09-xx we can use %.16H */
-          "%02x%02x%02x%02x" "%02x%02x%02x%02x",
-          digest[0], digest[1], digest[2], digest[3], digest[4], digest[5], digest[6], digest[7],
-          digest[8], digest[9], digest[10], digest[11], digest[12], digest[13], digest[14], digest[15]);
-
-      /* 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. */
-
-      GET_OPTION("smtp_etrn_command");
-      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,
-			TSUC_EXPAND_ARGS, 0, NULL, US"ETRN processing", &error);
-	deliver_domain = NULL;
-	if (!rc)
-	  {
-	  log_write(0, LOG_MAIN|LOG_PANIC, "failed to set up ETRN command: %s",
-	    error);
-	  smtp_printf("458 Internal failure\r\n", SP_NO_MORE);
-	  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 = CUSS child_exec_exim(CEE_RETURN_ARGV, TRUE, NULL, TRUE,
-	  *queue_name ? 4 : 2,
-	  US"-R", smtp_cmd_data,
-	  US"-MCG", queue_name);
-	}
-
-      /* If we are host-testing, don't actually do anything. */
-
-      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", SP_NO_MORE);
-	  else smtp_user_msg(US"250", user_msg);
-	break;
-	}
-
-
-      /* If ETRN queue runs are to be serialized, check the database to
-      ensure one isn't already running. */
-
-      if (smtp_etrn_serialize && !enq_start(etrn_serialize_key, 1))
-	{
-	smtp_printf("458 Already processing %s\r\n", SP_NO_MORE, smtp_cmd_data);
-	break;
-	}
-
-      /* 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 = exim_fork(US"etrn-command")) == 0)
-	{
-	smtp_input = FALSE;       /* This process is not associated with the */
-	smtp_inout_close();	  /* SMTP call any more. */
-
-	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 = exim_fork(US"etrn-serialised-command")) == 0)
-	  {
-	  DEBUG(D_exec) debug_print_argv(argv);
-	  exim_nullstd();                   /* Ensure std{in,out,err} exist */
-	  /* argv[0] should be untainted, from child_exec_exim() */
-	  execv(CS argv[0], (char *const *)argv);
-	  log_write_die(0, LOG_MAIN, "exec of %q (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);
-	exim_underbar_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", SP_NO_MORE);
-	if (smtp_etrn_serialize) enq_end(etrn_serialize_key);
-	}
-      else
-	if (!user_msg)
-	  smtp_printf("250 OK\r\n", SP_NO_MORE);
-	else
-	  smtp_user_msg(US"250", user_msg);
-
-      signal(SIGCHLD, oldsignal);
+      done = etrn_handle(&user_msg, &log_msg);
       break;
 
-
     case BADARG_CMD:
-      done = synprot_error(L_smtp_syntax_error, 501, NULL,
+      done = synprot_error(FALSE, 501, NULL,
 	US"unexpected argument data");
       break;
 
@@ -5957,7 +6003,7 @@ while (done <= 0)
     /* This currently happens only for NULLs, but could be extended. */
 
     case BADCHAR_CMD:
-      done = synprot_error(L_smtp_syntax_error, 0, NULL,       /* Just logs */
+      done = synprot_error(FALSE, 0, NULL,       /* Just logs */
 	US"NUL character(s) present (shown as '?')");
       smtp_printf("501 NUL characters are not allowed in SMTP commands\r\n",
 		  SP_NO_MORE);
@@ -5968,13 +6014,13 @@ while (done <= 0)
     SYNC_FAILURE:
       {
 	unsigned nchars = 150;
-	uschar * buf = receive_getbuf(&nchars);		/* destructive read */
+	const uschar * buf = receive_getbuf(&nchars);	/* destructive read */
 
 	incomplete_transaction_log(US"sync failure");
 	if (buf)
 	  {
-	  buf[nchars] = '\0';
-	  log_write(0, LOG_MAIN|LOG_REJECT,
+	  buf = string_copyn(buf, nchars);
+	  log_write(LOG_MAIN|LOG_REJECT,
 	    "SMTP protocol synchronization error "
 	    "(next input sent too soon: pipelining was%s advertised): "
 	    "rejected %q %s next input=%q (%u bytes)",
@@ -5983,7 +6029,7 @@ while (done <= 0)
 	    string_printing(buf), nchars);
 	  }
 	else
-	  log_write(0, LOG_MAIN|LOG_REJECT, "Error or EOF on input from %s",
+	  log_write(LOG_MAIN|LOG_REJECT, "Error or EOF on input from %s",
 	    host_and_ident(TRUE));
 	smtp_notquit_exit(US"synchronization-error", US"554",
 	  US"SMTP synchronization error");
@@ -5996,7 +6042,7 @@ while (done <= 0)
       s = smtp_cmd_buffer;
       Uskip_nonwhite(&s);
       incomplete_transaction_log(US"too many non-mail commands");
-      log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many "
+      log_write(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");
@@ -6013,20 +6059,21 @@ while (done <= 0)
       GET_OPTION("smtp_max_unknown_commands");
       if (unknown_command_count++ >= smtp_max_unknown_commands)
 	{
-	log_write(L_smtp_syntax_error, LOG_MAIN,
-	  "SMTP syntax error in %q %s %s",
-	  string_printing(smtp_cmd_buffer), host_and_ident(TRUE),
-	  US"unrecognized command");
+	if (LOGGING(smtp_syntax_error))
+	  log_write(LOG_MAIN,
+	    "SMTP syntax error in %q %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 "
+	log_write(LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many "
 	  "unrecognized commands (last was %q)", host_and_ident(FALSE),
 	  string_printing(smtp_cmd_buffer));
 	}
       else
-	done = synprot_error(L_smtp_syntax_error, 500, NULL,
+	done = synprot_error(FALSE, 500, NULL,
 	  US"unrecognized command");
       break;
     }
diff --git a/src/src/smtp_out.c b/src/src/smtp_out.c
index 465acb5ed..78f20e705 100644
--- a/src/src/smtp_out.c
+++ b/src/src/smtp_out.c
@@ -57,7 +57,7 @@ if (!(expint = expand_string(istring)))
 
 if (is_tainted(expint))
   {
-  log_write(0, LOG_MAIN|LOG_PANIC,
+  log_write(LOG_MAIN|LOG_PANIC,
     "attempt to use tainted value '%s' from '%s' for interface",
     expint, istring);
   addr->transport_return = PANIC;
@@ -180,7 +180,7 @@ if (done_once) return;
 	 && tinfo.__tcpi_unacked > 0
 	 )
 	{
-	DEBUG(D_transport|D_v)
+	DEBUG(transport|v)
 	 debug_printf("TCP_FASTOPEN tcpi_unacked %d\n", tinfo.__tcpi_unacked);
 	tcp_out_fastopen = TFO_USED_NODATA;
 	}
@@ -227,7 +227,7 @@ if (done_once) return;
 	 && tinfo.tcpi_unacked > 1
 	 )
 	{
-	DEBUG(D_transport|D_v)
+	DEBUG(transport|v)
 	  debug_printf("TCP_FASTOPEN tcpi_unacked %d\n", tinfo.tcpi_unacked);
 	tcp_out_fastopen = TFO_USED_NODATA;
 	}
@@ -248,12 +248,12 @@ if (done_once) return;
 	{
 	if (tinfo.tcpi_options & TCPI_OPT_SYN_DATA)
 	  {
-	  DEBUG(D_transport|D_v) debug_printf("TFO: data was acked\n");
+	  DEBUG(transport|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");
+	  DEBUG(transport|v) debug_printf("TFO: had to retransmit\n");
 	  tcp_out_fastopen = TFO_NOT_USED;
 	  }
 	done_once = TRUE;
@@ -276,10 +276,7 @@ Update those with the state.  Return the fd, or -1 with errno set.
 int
 smtp_boundsock(smtp_connect_args * sc)
 {
-transport_instance * tb = sc->tblock;
-smtp_transport_options_block * ob = tb->drinst.options_block;
-const uschar * dscp = ob->dscp;
-int sock, dscp_value, dscp_level, dscp_option;
+int sock;
 
 if ((sock = ip_socket(SOCK_STREAM, sc->host_af)) < 0)
   return -1;
@@ -287,26 +284,25 @@ if ((sock = ip_socket(SOCK_STREAM, sc->host_af)) < 0)
 /* Set TCP_NODELAY; Exim does its own buffering. */
 
 if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, US &on, sizeof(on)))
-  HDEBUG(D_transport|D_acl|D_v)
+  HDEBUG(transport|acl|v)
     debug_printf_indent("failed to set NODELAY: %s ", strerror(errno));
 
+#ifdef SUPPORT_DSCP
 /* 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. */
-
-GET_OPTION("dscp");
-if (dscp && dscp_lookup(dscp, sc->host_af, &dscp_level, &dscp_option, &dscp_value))
   {
-  HDEBUG(D_transport|D_acl|D_v)
-    debug_printf_indent("DSCP %q=%x ", dscp, dscp_value);
-  if (setsockopt(sock, dscp_level, dscp_option, &dscp_value, sizeof(dscp_value)) < 0)
-    HDEBUG(D_transport|D_acl|D_v)
-      debug_printf_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 (sc->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));
+  transport_instance * tb = sc->tblock;
+  smtp_transport_options_block * ob = tb->drinst.options_block;
+  GET_OPTION("dscp");
+  if (ob->dscp)
+    {
+    misc_module_info * mi = misc_mod_find(US"dscp", NULL);
+    typedef void (*fn_t)(int, const uschar *, int);
+    if (mi)
+      ((fn_t *) mi->functions)[DSCP_TRANSPORT] (sock, ob->dscp, sc->host_af);
+    }
   }
+#endif
 
 /* Bind to a specific interface if requested. Caller must ensure the interface
 is the same type (IPv4 or IPv6) as the outgoing address. */
@@ -320,7 +316,7 @@ if (sc->interface)
      || getsockname(sock, (struct sockaddr *) &interface_sock, &size) < 0
      )
     {
-    HDEBUG(D_transport|D_acl|D_v)
+    HDEBUG(transport|acl|v)
       debug_printf_indent("unable to bind outgoing SMTP call to %s: %s\n", sc->interface,
 	strerror(errno));
     close(sock);
@@ -384,7 +380,7 @@ if (!save_errno)
 # ifdef TCP_FASTOPEN_CONNECT
     else
       {						/* expecting client data */
-      DEBUG(D_transport|D_acl|D_v) debug_printf(" set up lazy-connect\n");
+      DEBUG(transport|acl|v) debug_printf(" set up lazy-connect\n");
       setsockopt(sock, IPPROTO_TCP, TCP_FASTOPEN_CONNECT, US &on, sizeof(on));
       /* fastopen_blob = NULL;		 lazy TFO, triggered by data write */
       tcp_out_fastopen = TFO_ATTEMPTED_DATA;
@@ -399,7 +395,7 @@ if (!save_errno)
   else if (early_data && !fastopen_blob && early_data->data && early_data->len)
     {
     /* We had some early-data to send, but couldn't do TFO */
-    HDEBUG(D_transport|D_acl|D_v)
+    HDEBUG(transport|acl|v)
       debug_printf("sending %ld nonTFO early-data\n", (long)early_data->len);
 
 #ifdef TCP_QUICKACK_notdef
@@ -424,12 +420,12 @@ if (!save_errno)
 
   /* Both bind() and connect() succeeded, and any early-data */
 
-  HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("connected\n");
+  HDEBUG(transport|acl|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
     {
-    log_write(0, LOG_MAIN | ((errno == ECONNRESET)? 0 : LOG_PANIC),
+    log_write(LOG_MAIN | ((errno == ECONNRESET)? 0 : LOG_PANIC),
       "getsockname() failed: %s", strerror(errno));
     close(sock);
     return -1;
@@ -444,7 +440,7 @@ if (!save_errno)
 
 /* Either bind() or connect() failed */
 
-HDEBUG(D_transport|D_acl|D_v)
+HDEBUG(transport|acl|v)
   {
   debug_printf_indent(" sock_connect failed: %s", CUstrerror(save_errno));
   if (save_errno == ETIMEDOUT)
@@ -466,7 +462,7 @@ smtp_port_for_connect(host_item * host, int tpt_port)
 if (host->port == PORT_NONE)
   host->port = tpt_port;    /* Set the port actually used */
 
-else HDEBUG(D_transport|D_acl|D_v) if (tpt_port != host->port)
+else HDEBUG(transport|acl|v) if (tpt_port != host->port)
   debug_printf_indent("Transport port=%d replaced by host-specific port=%d\n",
 		      tpt_port, host->port);
 }
@@ -498,7 +494,7 @@ smtp_transport_options_block * ob = sc->ob;
 
 callout_address = string_sprintf("[%s]:%d", sc->host->address, sc->host->port);
 
-HDEBUG(D_transport|D_acl|D_v)
+HDEBUG(transport|acl|v)
   {
   gstring * g = sc->interface
     ? string_fmt_append(NULL, " from %s", sc->interface)
@@ -518,12 +514,16 @@ if (ob->socks_proxy)
   {
   if (!(ob->socks_proxy = expand_string(ob->socks_proxy)))
     {
-    log_write(0, LOG_MAIN|LOG_PANIC, "Bad expansion for socks_proxy in %s",
+    log_write(LOG_MAIN|LOG_PANIC, "Bad expansion for socks_proxy in %s",
       sc->tblock->drinst.name);
     return -1;
     }
   if (*ob->socks_proxy)
-    return socks_sock_connect(sc, early_data);
+    {
+    misc_module_info * mi = misc_mod_find(US"socks", NULL);
+    typedef int (*fn_t) (const smtp_connect_args *, const blob *);
+    return mi ? ((fn_t *) mi->functions)[SOCKS_CONNECT] (sc, early_data) : -1;
+    }
   }
 #endif
 
@@ -554,12 +554,12 @@ BOOL more = mode == SCMD_MORE;
 client_conn_ctx * cctx;
 const uschar * where;
 
-HDEBUG(D_transport|D_acl) debug_printf_indent("cmd buf flush %d bytes%s\n", n,
+HDEBUG(transport|acl) debug_printf_indent("cmd buf flush %d bytes%s\n", n,
   more ? " (more expected)" : "");
 
 if (!(cctx = outblock->cctx))
   {
-  log_write(0, LOG_MAIN|LOG_PANIC, "null conn-context pointer");
+  log_write(LOG_MAIN|LOG_PANIC, "null conn-context pointer");
   errno = 0;
   return FALSE;
   }
@@ -614,7 +614,7 @@ else
 
 if (rc <= 0)
   {
-  HDEBUG(D_transport|D_acl) debug_printf_indent("%s (fd %d) failed: %s\n",
+  HDEBUG(transport|acl) debug_printf_indent("%s (fd %d) failed: %s\n",
     where, cctx->sock, strerror(errno));
   return FALSE;
   }
@@ -665,12 +665,12 @@ if (format)
 
   va_start(ap, format);
   if (!string_vformat(&gs, SVFMT_TAINT_NOCHK, CS format, ap))
-    log_write_die(0, LOG_MAIN, "overlong write_command in outgoing "
+    log_write_die(LOG_MAIN, "overlong write_command in outgoing "
       "SMTP");
   va_end(ap);
 
   if (gs.ptr > outblock->buffersize)
-    log_write_die(0, LOG_MAIN, "overlong write_command in outgoing "
+    log_write_die(LOG_MAIN, "overlong write_command in outgoing "
       "SMTP");
 
   if (gs.ptr > outblock->buffersize - (outblock->ptr - outblock->buffer))
@@ -783,7 +783,7 @@ for (;;)
 
   if((rc = ip_recv(cctx, inblock->buffer, inblock->buffersize, timelimit)) <= 0)
     {
-    DEBUG(D_deliver|D_transport|D_acl|D_v)
+    DEBUG(deliver|transport|acl|v)
       debug_printf_indent(errno ? "  SMTP(%s)<<\n" : "  SMTP(closed)<<\n",
 	strerror(errno));
     break;
@@ -794,7 +794,7 @@ for (;;)
 
   ptrend = inblock->ptrend = inblock->buffer + rc;
   ptr = inblock->buffer;
-  DEBUG(D_transport|D_acl) debug_printf_indent("read response data: size=%d\n", rc);
+  DEBUG(transport|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
@@ -851,7 +851,7 @@ if (sx->pending_BANNER || sx->pending_EHLO)
   int rc;
   if ((rc = smtp_reap_early_pipe(sx, &count)) != OK)
     {
-    DEBUG(D_transport) debug_printf("failed reaping pipelined cmd responsess\n");
+    DEBUG(transport) debug_printf("failed reaping pipelined cmd responsess\n");
     if (rc == DEFER) errno = ERRNO_TLSFAILURE;
     goto out;
     }
@@ -866,7 +866,7 @@ for (;;)
   if ((count = read_response_line(&sx->inblock, ptr, size, timelimit)) < 0)
     return FALSE;
 
-  HDEBUG(D_transport|D_acl|D_v)
+  HDEBUG(transport|acl|v)
     debug_printf_indent("  %s %s\n", ptr == buffer ? "SMTP<<" : "      ", ptr);
 
   /* Check the format of the response: it must start with three digits; if
diff --git a/src/src/spam.c b/src/src/spam.c
index b8e6bebfb..3dce24c47 100644
--- a/src/src/spam.c
+++ b/src/src/spam.c
@@ -3,7 +3,7 @@
 *************************************************/
 
 /*
- * Copyright (c) The Exim Maintainers 2016 - 2025
+ * Copyright (c) The Exim Maintainers 2016 - 2026
  * Copyright (c) Tom Kistner <tom@duncanthrax.net> 2003 - 2015
  * License: GPL
  * SPDX-License-Identifier: GPL-2.0-or-later
@@ -15,11 +15,6 @@
 #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];
 const uschar * cached_user_name = NULL;
 BOOL spam_ok = FALSE;
 int spam_rc = 0;
@@ -123,12 +118,12 @@ if (Ustrncmp(param, "retry=", 6) == 0)
   return 0;
   }
 
-log_write(0, LOG_MAIN, "%s warning - invalid spamd parameter: '%s'",
+log_write(LOG_MAIN, "%s warning - invalid spamd parameter: '%s'",
   loglabel, param);
 return -1; /* syntax error */
 
 badval:
-  log_write(0, LOG_MAIN,
+  log_write(LOG_MAIN,
     "%s warning - invalid spamd %s value: '%s'", loglabel, name, s);
   return -1; /* syntax error */
 }
@@ -168,12 +163,57 @@ for (long rnd = random_number(weights), i = 0; i < num_servers; i++)
       return i;
   }
 
-log_write(0, LOG_MAIN|LOG_PANIC,
+log_write(LOG_MAIN|LOG_PANIC,
   "%s unknown error (memory/cpu corruption?)", loglabel);
 return -1;
 }
 
 
+/* Quote, if needed, the local-part of an address.
+Method copied from ${local_part:} coding.
+*/
+
+static const uschar *
+quote_addr_localpart(const uschar * s)
+{
+int start, end, end_l, domain;
+uschar * errmsg;
+
+if (!(s = parse_extract_address(s, &errmsg, &start, &end, &domain, FALSE)))
+  log_write(LOG_MAIN|LOG_PANIC_DIE, "failed splitting address: %s", errmsg);
+
+end_l = domain == 0 ? end : domain-1;
+for (int i = start; i < end_l; i++)
+  {
+  uschar c = s[i];
+  if (  !isalnum(c)
+     && strchr("!#$%&'*+-/=?^_`{|}~", c) == NULL
+     && (c != '.' || i == 0 || !s[i+1])
+     )
+    {						/* local-part needs quoting */
+    gstring * g = string_catn(NULL, US"\"", 1);
+
+    for (const uschar * t = s + start; t < s + end_l; t++)
+      if (*t == '\n')
+	g = string_catn(g, US"\\n", 2);
+      else if (*t == '\r')
+	g = string_catn(g, US"\\r", 2);
+      else
+	{
+	if (*t == '\\' || *t == '"')
+	  g = string_catn(g, US"\\", 1);
+	g = string_catn(g, t, 1);
+	}
+
+    g = domain == 0
+      ? string_catn(g, US"\"", 1)
+      : string_fmt_append(g, "\"@%s", s + domain);
+    return string_from_gstring(g);
+    }
+  }
+return s;
+}
+
 int
 spam(const uschar **listptr)
 {
@@ -185,7 +225,6 @@ client_conn_ctx spamd_cctx = {.sock = -1};
 uschar spamd_buffer[32600];
 int i, j, offset;
 uschar spamd_version[8], spamd_short_result[8];
-uschar spamd_score_char;
 double spamd_threshold, spamd_score, spamd_reject_score;
 int spamd_report_offset;
 uschar *p,*q;
@@ -216,13 +255,13 @@ if (*spamd_address != '$')
   spamd_address_work = spamd_address;
 else if (!(spamd_address_work = expand_string(spamd_address)))
   {
-  log_write(0, LOG_MAIN|LOG_PANIC,
+  log_write(LOG_MAIN|LOG_PANIC,
     "%s spamd_address starts with $, but expansion failed: %s",
     loglabel, expand_string_message);
   return DEFER;
   }
 
-DEBUG(D_acl) debug_printf_indent("spamd: addrlist '%s'\n", spamd_address_work);
+DEBUG(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
@@ -239,7 +278,7 @@ if (spam_ok && Ustrcmp(cached_user_name, user_name) == 0)
 
 if (!(mbox_file = spool_mbox(&mbox_size, NULL, NULL)))
   {								/* error while spooling */
-  log_write(0, LOG_MAIN|LOG_PANIC,
+  log_write(LOG_MAIN|LOG_PANIC,
 	 "%s error while creating mbox spool file", loglabel);
   return DEFER;
   }
@@ -263,7 +302,7 @@ start = time(NULL);
     unsigned args;
     uschar * s;
 
-    DEBUG(D_acl) debug_printf_indent("spamd: addr entry '%s'\n", address);
+    DEBUG(acl) debug_printf_indent("spamd: addr entry '%s'\n", address);
     sd = store_get(sizeof(spamd_address_container), GET_UNTAINTED);
 
     for (sublist = address, args = 0, spamd_param_init(sd);
@@ -271,7 +310,7 @@ start = time(NULL);
 	 args++
 	 )
       {
-	DEBUG(D_acl) debug_printf_indent("spamd:  addr parm '%s'\n", s);
+	DEBUG(acl) debug_printf_indent("spamd:  addr parm '%s'\n", s);
 	switch (args)
 	{
 	case 0:   sd->hostspec = s;
@@ -285,7 +324,7 @@ start = time(NULL);
       }
     if (args < 2)
       {
-      log_write(0, LOG_MAIN,
+      log_write(LOG_MAIN,
 	"%s warning - invalid spamd address: '%s'", loglabel, address);
       continue;
       }
@@ -298,7 +337,7 @@ start = time(NULL);
   /* check if we have at least one server */
   if (!num_servers)
     {
-    log_write(0, LOG_MAIN|LOG_PANIC,
+    log_write(LOG_MAIN|LOG_PANIC,
        "%s no useable spamd server addresses in spamd_address configuration option.",
        loglabel);
     goto defer;
@@ -310,7 +349,7 @@ start = time(NULL);
     {
     uschar * errstr;
 
-    DEBUG(D_acl) debug_printf_indent("spamd: trying server %s\n", sd->hostspec);
+    DEBUG(acl) debug_printf_indent("spamd: trying server %s\n", sd->hostspec);
 
     for (;;)
       {
@@ -319,19 +358,19 @@ start = time(NULL);
          || sd->retry == 0
 	 )
 	break;
-      DEBUG(D_acl) debug_printf_indent("spamd: server %s: retry conn\n", sd->hostspec);
+      DEBUG(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);
+    log_write(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);
+      log_write(LOG_MAIN|LOG_PANIC, "%s all spamd servers failed", loglabel);
       goto defer;
       }
     sd = spamd_address_vector[current_server];
@@ -353,7 +392,7 @@ if (sd->is_rspamd)
 
   for (int i = 0; i < recipients_count; i++)
     req_str = string_append(req_str, 3,
-      "Rcpt: <", recipients_list[i].address, ">\r\n");
+      "Rcpt: <", quote_addr_localpart(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)
@@ -378,7 +417,7 @@ else
 if (wrote == -1)
   {
   (void)close(spamd_cctx.sock);
-  log_write(0, LOG_MAIN|LOG_PANIC, "%s spamd %s send failed: %s",
+  log_write(LOG_MAIN|LOG_PANIC, "%s spamd %s send failed: %s",
 	    loglabel, callout_address, strerror(errno));
   goto defer;
   }
@@ -413,13 +452,13 @@ again:
     else if (result < 1)
       {
       if (result == -1)
-	log_write(0, LOG_MAIN|LOG_PANIC,
+	log_write(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,
+	log_write(LOG_MAIN|LOG_PANIC,
 	  "%s timed out writing spamd %s, socket", loglabel, callout_address);
 	}
       (void)close(spamd_cctx.sock);
@@ -429,7 +468,7 @@ again:
     wrote = send(spamd_cctx.sock,spamd_buffer + offset,read - offset,0);
     if (wrote == -1)
       {
-      log_write(0, LOG_MAIN|LOG_PANIC,
+      log_write(LOG_MAIN|LOG_PANIC,
 	  "%s %s on spamd %s socket", loglabel, callout_address, strerror(errno));
       (void)close(spamd_cctx.sock);
       goto defer;
@@ -445,7 +484,7 @@ while (!feof(mbox_file) && !ferror(mbox_file));
 
 if (ferror(mbox_file))
   {
-  log_write(0, LOG_MAIN|LOG_PANIC,
+  log_write(LOG_MAIN|LOG_PANIC,
     "%s error reading spool file: %s", loglabel, strerror(errno));
   (void)close(spamd_cctx.sock);
   goto defer;
@@ -468,7 +507,7 @@ spamd_buffer[offset] = '\0';	/* guard byte */
 /* error handling */
 if (errno != 0)
   {
-  log_write(0, LOG_MAIN|LOG_PANIC,
+  log_write(LOG_MAIN|LOG_PANIC,
        "%s error reading from spamd %s, socket: %s", loglabel, callout_address, strerror(errno));
   (void)close(spamd_cctx.sock);
   return DEFER;
@@ -487,7 +526,7 @@ if (sd->is_rspamd)
      || spamd_report_offset >= offset		/* verify within buffer */
      )
     {
-    log_write(0, LOG_MAIN|LOG_PANIC,
+    log_write(LOG_MAIN|LOG_PANIC,
 	      "%s cannot parse spamd %s, output: %d", loglabel, callout_address, r);
     return DEFER;
     }
@@ -497,10 +536,8 @@ if (sd->is_rspamd)
   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';
+    for (q = p; *q != '\r' && q - p < 32; ) q++;
+    spam_action = string_copyn(p, q - p);
     }
   }
 else
@@ -516,70 +553,53 @@ else
 	   "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,
+	log_write(LOG_MAIN|LOG_PANIC,
 		  "%s cannot parse spamd %s output", loglabel, callout_address);
 	return DEFER;
 	}
     }
 
-  Ustrcpy(spam_action_buffer,
-    spamd_score >= spamd_threshold ? US"reject" : US"no action");
+  spam_action = spamd_score >= spamd_threshold ? US"reject" : US"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';
+  gstring * g = NULL;
 
-spam_report = spam_report_buffer;
-spam_action = spam_action_buffer;
+  for (const uschar * p = &spamd_buffer[spamd_report_offset]; *p; p++)
+    if (*p != '\r')
+      {
+      g = string_catn(g, p, 1);
+
+      /* add an extra space after the newline to ensure
+      that it is treated as a header continuation line */
+      if (*p == '\n') g = string_catn(g, US" ", 1);
+      }
+
+  for (uschar c; (c = gstring_last_char(g)) && c <= ' '; )
+    gstring_trim(g, 1);
+
+  spam_report = string_from_gstring(g);
+  }
 
 /* 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;
+j = abs((int)spamd_score);
+j = MIN(j, MAX_SPAM_BAR_CHARS);
+if (j == 0)
+  spam_bar = string_copyn(US"/", 1);
 else
   {
-  spam_bar_buffer[0] = '/';
-  i = 1;
+  gstring * g = string_get(j+1);
+  while(j--) g = string_catn(g, spamd_score > 0 ? US"+" : US"-", 1);
+  spam_bar = string_from_gstring(g);
   }
-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;
+spam_score = string_sprintf("%.1f", spamd_score);
 
 /* 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;
+spam_score_int = string_sprintf("%.0f", spamd_score*10);
 
 /* compare threshold against score */
 spam_rc = spamd_score >= spamd_threshold
diff --git a/src/src/spool_in.c b/src/src/spool_in.c
index ff9edfc2e..60b1ef244 100644
--- a/src/src/spool_in.c
+++ b/src/src/spool_in.c
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) The Exim Maintainers 2020 - 2025 */
+/* Copyright (c) The Exim Maintainers 2020 - 2026 */
 /* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 /* SPDX-License-Identifier: GPL-2.0-or-later */
@@ -56,7 +56,7 @@ for (int i = 0; i < 2; i++)
 
   set_subdir_str(message_subdir, id, i);
   fname = spool_fname(US"input", message_subdir, id, US"-D");
-  DEBUG(D_deliver) debug_printf_indent("Trying spool file %s\n", fname);
+  DEBUG(deliver) debug_printf_indent("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
@@ -71,18 +71,18 @@ for (int i = 0; i < 2; i++)
     {
     if (i == 0) continue;
     if (!f.queue_running)
-      log_write(0, LOG_MAIN, "Spool%s%s file %s-D not found",
+      log_write(LOG_MAIN, "Spool%s%s file %s-D not found",
 	*queue_name ? US" Q=" : US"",
 	*queue_name ? queue_name : US"",
 	id);
-    else DEBUG(D_deliver)
+    else DEBUG(deliver)
       debug_printf("Spool%s%s file %s-D not found\n",
 	*queue_name ? US" Q=" : US"",
 	*queue_name ? queue_name : US"",
 	id);
     }
   else
-    log_write(0, LOG_MAIN, "Spool error for %s: %s", fname, strerror(errno));
+    log_write(LOG_MAIN, "Spool error for %s: %s", fname, strerror(errno));
   errno = save_errno;
   return -1;
   }
@@ -106,7 +106,8 @@ lock_data.l_len = spool_data_start_offset(id);
 
 if (fcntl(fd, F_SETLK, &lock_data) < 0)
   {
-  log_write(L_skip_delivery, LOG_MAIN,
+  if (LOGGING(skip_delivery))
+    log_write(LOG_MAIN,
       "Spool file for %s is locked (another process is handling this message)",
       id);
   (void)close(fd);
@@ -245,7 +246,7 @@ local_scan_data = NULL;
 max_received_linelength = 0;
 message_linecount = 0;
 received_protocol = NULL;
-received_count = 0;
+received_count = recipients_count = 0;
 recipients_list = NULL;
 sender_address = NULL;
 sender_fullhost = NULL;
@@ -263,15 +264,9 @@ f.spool_file_wireformat = FALSE;
 #endif
 tree_nonrecipients = NULL;
 
-#ifdef EXPERIMENTAL_BRIGHTMAIL
-bmi_run = 0;
-bmi_verdicts = NULL;
-#endif
-
 #ifndef DISABLE_DKIM
 f.dkim_disable_verify = FALSE;
 # ifdef COMPILE_UTILITY
-dkim_signers = NULL;
 dkim_collect_input = 0;
 #else
   {
@@ -296,10 +291,8 @@ tls_in.sni = NULL;
 tls_in.ocsp = OCSP_NOT_REQ;
 #endif
 
-#ifdef WITH_CONTENT_SCAN
-spam_bar = NULL;
-spam_score = NULL;
-spam_score_int = NULL;
+#if defined(WITH_CONTENT_SCAN) && !defined(COMPILE_UTILITY)
+spam_action = spam_report = spam_bar = spam_score = spam_score_int = NULL;
 #endif
 
 #if defined(SUPPORT_I18N) && !defined(COMPILE_UTILITY)
@@ -406,7 +399,7 @@ for (int i = 0; i < 2; i++)
 errno = 0;
 
 #ifndef COMPILE_UTILITY
-DEBUG(D_deliver) debug_printf_indent("reading spool file %s\n", fname);
+DEBUG(deliver) debug_printf_indent("reading spool file %s\n", fname);
 #endif  /* COMPILE_UTILITY */
 
 /* The first line of a spool file contains the message id followed by -H (i.e.
@@ -484,7 +477,7 @@ if (f.running_in_test_harness)
 #endif
 
 #ifndef COMPILE_UTILITY
-DEBUG(D_deliver) debug_printf_indent("user=%s uid=%ld gid=%ld sender=%s\n",
+DEBUG(deliver) debug_printf_indent("user=%s uid=%ld gid=%ld sender=%s\n",
   originator_login, (long int)originator_uid, (long int)originator_gid,
   sender_address);
 #endif
@@ -527,7 +520,7 @@ for (;;)
       const lookup_info * li;
       if (!(li= search_findtype(var, s - var)))
 	{
-	DEBUG(D_any)
+	DEBUG(any)
 	  debug_printf("Unrecognised quoter %.*s\n", (int)(s - var), var+1);
 	where = NULL;
 	goto SPOOL_FORMAT_ERROR;
@@ -615,10 +608,6 @@ for (;;)
       body_linecount = Uatoi(var + 14);
     else if (Ustrncmp(p, "ody_zerocount", 13) == 0)
       body_zerocount = Uatoi(var + 14);
-#ifdef EXPERIMENTAL_BRIGHTMAIL
-    else if (Ustrncmp(p, "mi_verdicts ", 12) == 0)
-      bmi_verdicts = string_copy_taint(var + 13, proto_mem);
-#endif
     break;
 
     case 'd':
@@ -630,7 +619,13 @@ for (;;)
       dsn_envid = string_copy_taint(var + 10, proto_mem);
 #ifndef COMPILE_UTILITY
     else if (Ustrncmp(p, "ebug_selector ", 14) == 0)
-      debug_selector = strtol(CS var + 15, NULL, 0);
+      {
+      const uschar * s = var + 15;
+      int n;
+      sscanf(CS s, SC_EXIM_BITMASK "%n", &debug_selector[0], &n);
+      for (int i = 1; i < DEBUG_SELECTOR_SIZE && *(s += n) == ','; i++)
+	sscanf(CS ++s, SC_EXIM_BITMASK "%n", &debug_selector[i], &n);
+      }
     else if (Ustrncmp(p, "ebuglog_name ", 13) == 0)
       debug_logging_from_spool(var + 14);
 #endif
@@ -802,7 +797,7 @@ host_build_sender_fullhost();
 #endif  /* COMPILE_UTILITY */
 
 #ifndef COMPILE_UTILITY
-DEBUG(D_deliver)
+DEBUG(deliver)
   debug_printf_indent("sender_local=%d ident=%s\n", f.sender_local,
     sender_ident ? sender_ident : US"unset");
 #endif  /* COMPILE_UTILITY */
@@ -816,7 +811,7 @@ if (Ustrncmp(big_buffer, "XX\n", 3) != 0 &&
     goto SPOOL_FORMAT_ERROR;
 
 #ifndef COMPILE_UTILITY
-DEBUG(D_deliver) debug_print_tree("Non-recipients", tree_nonrecipients);
+DEBUG(deliver) debug_print_tree("Non-recipients", tree_nonrecipients);
 #endif  /* COMPILE_UTILITY */
 
 /* After reading the tree, the next line has not yet been read into the
@@ -829,7 +824,7 @@ if (sscanf(CS big_buffer, "%d", &rcount) != 1 || rcount > 16384)
   goto SPOOL_FORMAT_ERROR;
 
 #ifndef COMPILE_UTILITY
-DEBUG(D_deliver) debug_printf_indent("recipients_count=%d\n", rcount);
+DEBUG(deliver) debug_printf_indent("recipients_count=%d\n", rcount);
 #endif  /* COMPILE_UTILITY */
 
 recipients_list_max = rcount;
@@ -842,12 +837,8 @@ the Coverity error on recipients_count */
 where = US"recipient";
 for (recipients_count = 0; recipients_count < rcount; recipients_count++)
   {
-  int nn;
-  int pno = -1;
-  int dsn_flags = 0;
-  uschar *orcpt = NULL;
-  uschar *errors_to = NULL;
-  uschar *p;
+  int nn, pno = -1, dsn_flags = 0;
+  uschar * orcpt = NULL, * errors_to = NULL, * p;
 
   if (fgets_big_buffer(fp) == NULL) goto SPOOL_READ_ERROR;
   nn = Ustrlen(big_buffer);
@@ -872,13 +863,10 @@ for (recipients_count = 0; recipients_count < rcount; recipients_count++)
   additional data. Otherwise, the possibilities are as follows:
 
   Exim 3 type:       <address><space><digits>,<digits>,<digits>
+    - no longer handled
 
-    The second set of digits is the parent number for one_time addresses. The
-    other values were remnants of earlier experiments that were abandoned.
-
-  Exim 4 first type: <address><space><digits>
-
-    The digits are the parent number for one_time addresses.
+  Exim 4 first type (pre 4.50): <address><space><digits>
+    - no longer handled
 
   Exim 4 new type:   <address><space><data>#<type bits>
 
@@ -893,33 +881,17 @@ for (recipients_count = 0; recipients_count < rcount; recipients_count++)
      with orcpt len(orcpt),dsn_flags
    */
 
-  while (isdigit(*p)) p--;
-
-  /* Handle Exim 3 spool files */
+  while (p > big_buffer && isdigit(*p)) p--;
 
-  if (*p == ',')
-    {
-    int dummy;
-#if !defined (COMPILE_UTILITY)
-    DEBUG(D_deliver) debug_printf_indent("**** SPOOL_IN - Exim 3 spool file\n");
-#endif
-    while (isdigit(*(--p)) || *p == ',');
-    if (*p == ' ')
-      {
-      *p++ = 0;
-      (void)sscanf(CS p, "%d,%d", &dummy, &pno);
-      }
-    }
-
-  /* Handle early Exim 4 spool files */
+  /* Fail Exim 3 and pre-4.50 spool files */
 
-  else if (*p == ' ')
+  if (*p == ',' || *p == ' ')
     {
-#if !defined (COMPILE_UTILITY)
-    DEBUG(D_deliver) debug_printf_indent("**** SPOOL_IN - early Exim 4 spool file\n");
-#endif
-    *p++ = 0;
-    (void)sscanf(CS p, "%d", &pno);
+    log_write(LOG_MAIN|LOG_PANIC, "Spool%s%s file %s bad format",
+      *queue_name ? US" Q=" : US"",
+      *queue_name ? queue_name : US"",
+      fname);
+    return spool_read_hdrerror;
     }
 
   /* Handle current format Exim 4 spool files */
@@ -929,7 +901,7 @@ for (recipients_count = 0; recipients_count < rcount; recipients_count++)
     int flags;
 
 #if !defined (COMPILE_UTILITY)
-    DEBUG(D_deliver) debug_printf_indent("**** SPOOL_IN - Exim standard format spoolfile\n");
+    DEBUG(deliver) debug_printf_indent("**** SPOOL_IN - Exim standard format spoolfile\n");
 #endif
 
     (void)sscanf(CS p+1, "%d", &flags);
@@ -937,10 +909,10 @@ for (recipients_count = 0; recipients_count < rcount; recipients_count++)
     if (flags & 0x01)      /* one_time data exists */
       {
       int len;
-      while (isdigit(*(--p)) || *p == ',' || *p == '-');
+      while (p > big_buffer && (isdigit(*(--p)) || *p == ',' || *p == '-')) ;
       (void)sscanf(CS p+1, "%d,%d", &len, &pno);
       *p = 0;
-      if (len > 0)
+      if (len > 0 && p > big_buffer + len)
         {
         p -= len;
         errors_to = string_copy_taint(p, GET_TAINTED);
@@ -948,13 +920,13 @@ for (recipients_count = 0; recipients_count < rcount; recipients_count++)
       }
 
     *--p = 0;   /* Terminate address */
-    if (flags & 0x02)      /* one_time data exists */
+    if (flags & 0x02)      /* orcpt data exists */
       {
       int len;
-      while (isdigit(*(--p)) || *p == ',' || *p == '-');
+      while (p > big_buffer && (isdigit(*(--p)) || *p == ',' || *p == '-')) ;
       (void)sscanf(CS p+1, "%d,%d", &len, &dsn_flags);
       *p = 0;
-      if (len > 0)
+      if (len > 0 && p > big_buffer + len)
         {
         p -= len;
         orcpt = string_copy_taint(p, GET_TAINTED);
@@ -965,13 +937,13 @@ for (recipients_count = 0; recipients_count < rcount; recipients_count++)
     }
 #if !defined(COMPILE_UTILITY)
   else
-    { DEBUG(D_deliver) debug_printf_indent("**** SPOOL_IN - No additional fields\n"); }
+    { DEBUG(deliver) debug_printf_indent("**** SPOOL_IN - No additional fields\n"); }
 
   if (orcpt || dsn_flags)
-    DEBUG(D_deliver) debug_printf_indent("**** SPOOL_IN - address: <%s> orcpt: <%s> dsn_flags: 0x%x\n",
+    DEBUG(deliver) debug_printf_indent("**** SPOOL_IN - address: <%s> orcpt: <%s> dsn_flags: 0x%x\n",
       big_buffer, orcpt, dsn_flags);
   if (errors_to)
-    DEBUG(D_deliver) debug_printf_indent("**** SPOOL_IN - address: <%s> errorsto: <%s>\n",
+    DEBUG(deliver) debug_printf_indent("**** SPOOL_IN - address: <%s> errorsto: <%s>\n",
       big_buffer, errors_to);
 #endif
 
@@ -1044,7 +1016,7 @@ line count by adding the body linecount to the header linecount. Close the file
 and give a positive response. */
 
 #ifndef COMPILE_UTILITY
-DEBUG(D_deliver) debug_printf_indent("body_linecount=%d message_linecount=%d\n",
+DEBUG(deliver) debug_printf_indent("body_linecount=%d message_linecount=%d\n",
   body_linecount, message_linecount);
 #endif  /* COMPILE_UTILITY */
 
@@ -1064,7 +1036,7 @@ if (errno != 0)
   n = errno;
 
 #ifndef COMPILE_UTILITY
-  DEBUG(D_any) debug_printf("Error while reading spool file %s\n", fname);
+  DEBUG(any) debug_printf("Error while reading spool file %s\n", fname);
 #endif  /* COMPILE_UTILITY */
 
   fclose(fp);
@@ -1075,7 +1047,7 @@ if (errno != 0)
 SPOOL_FORMAT_ERROR:
 
 #ifndef COMPILE_UTILITY
-DEBUG(D_any) debug_printf("Format error in spool file %s%s%s\n", fname,
+DEBUG(any) debug_printf("Format error in spool file %s%s%s\n", fname,
   where ? ": " : "", where ? where : US"");
 #else
 where = where;	/* compiler quietening */
@@ -1104,7 +1076,7 @@ uschar * yield = NULL;
 if (!(fp = Ufopen(spool_fname(US"input", message_subdir, id, US"-H"), "rb")))
   return NULL;
 
-DEBUG(D_deliver) debug_printf_indent("reading spool file %s-H\n", id);
+DEBUG(deliver) debug_printf_indent("reading spool file %s-H\n", id);
 
 /* Skip the line with the copy of the filename, then the line with login/uid/gid.
 Read the next line, which should be the envelope sender.
diff --git a/src/src/spool_mbox.c b/src/src/spool_mbox.c
index 425f36e9f..bc4fe0a8d 100644
--- a/src/src/spool_mbox.c
+++ b/src/src/spool_mbox.c
@@ -54,7 +54,7 @@ if (!spool_mbox_ok)
   s = string_sprintf("scan/%s", message_id);
   if (!directory_make(spool_directory, s, 0750, FALSE))
     {
-    log_write(0, LOG_MAIN|LOG_PANIC, "%s",
+    log_write(LOG_MAIN|LOG_PANIC, "%s",
       string_open_failed("scan directory %s/scan/%s", spool_directory, s));
     goto OUT;
     }
@@ -63,7 +63,7 @@ if (!spool_mbox_ok)
 
   if (!(mbox_file = modefopen(mbox_path, "wb", SPOOL_MODE)))
     {
-    log_write(0, LOG_MAIN|LOG_PANIC, "%s",
+    log_write(LOG_MAIN|LOG_PANIC, "%s",
       string_open_failed("scan file %s", mbox_path));
     goto OUT;
     }
@@ -81,7 +81,7 @@ if (!spool_mbox_ok)
   if (s)
     if (fwrite(s, Ustrlen(s), 1, mbox_file) != 1)
       {
-      log_write(0, LOG_MAIN|LOG_PANIC, "Error/short write while writing \
+      log_write(LOG_MAIN|LOG_PANIC, "Error/short write while writing \
 	  mailbox headers to %s", mbox_path);
       goto OUT;
       }
@@ -93,7 +93,7 @@ if (!spool_mbox_ok)
     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 \
+	log_write(LOG_MAIN|LOG_PANIC, "Error/short write while writing \
 	    message headers to %s", mbox_path);
 	goto OUT;
 	}
@@ -102,7 +102,7 @@ if (!spool_mbox_ok)
 
   if (fwrite("\n", 1, 1, mbox_file) != 1)
     {
-    log_write(0, LOG_MAIN|LOG_PANIC, "Error/short write while writing \
+    log_write(LOG_MAIN|LOG_PANIC, "Error/short write while writing \
       message headers to %s", mbox_path);
     goto OUT;
     }
@@ -125,7 +125,7 @@ if (!spool_mbox_ok)
 
   if (!l_data_file)
     {
-    log_write(0, LOG_MAIN|LOG_PANIC, "Could not open datafile for message %s",
+    log_write(LOG_MAIN|LOG_PANIC, "Could not open datafile for message %s",
       message_id);
     goto OUT;
     }
@@ -166,7 +166,7 @@ if (!spool_mbox_ok)
     if (j > 0)
       if (fwrite(buffer, j, 1, mbox_file) != 1)
         {
-	log_write(0, LOG_MAIN|LOG_PANIC, "Error/short write while writing \
+	log_write(LOG_MAIN|LOG_PANIC, "Error/short write while writing \
 	    message body to %s", mbox_path);
 	goto OUT;
 	}
@@ -185,7 +185,7 @@ if (!spool_mbox_ok)
 if (  !(yield = Ufopen(mbox_path,"rb"))
    || fstat(fileno(yield), &statbuf) != 0
    )
-  log_write(0, LOG_MAIN|LOG_PANIC, "%s",
+  log_write(LOG_MAIN|LOG_PANIC, "%s",
     string_open_failed( "scan file %s", mbox_path));
 else
   *mbox_file_size = statbuf.st_size;
@@ -231,14 +231,14 @@ if (spool_mbox_ok && !f.no_mbox_unspool)
     file_path = string_sprintf("%s/%s", mbox_path, name);
     debug_printf("unspool_mbox(): unlinking '%s'\n", file_path);
     if (unlink(CS file_path) != 0)
-      log_write(0, LOG_MAIN|LOG_PANIC, "unlink(%s): %s", file_path, strerror(errno));
+      log_write(LOG_MAIN|LOG_PANIC, "unlink(%s): %s", file_path, strerror(errno));
     }
 
   closedir(tempdir);
 
   /* remove directory */
   if (rmdir(CS mbox_path) != 0)
-    log_write(0, LOG_MAIN|LOG_PANIC, "rmdir(%s): %s", mbox_path, strerror(errno));
+    log_write(LOG_MAIN|LOG_PANIC, "rmdir(%s): %s", mbox_path, strerror(errno));
   store_reset(reset_point);
   }
 spool_mbox_ok = 0;
diff --git a/src/src/spool_out.c b/src/src/spool_out.c
index e5cded966..81be7198b 100644
--- a/src/src/spool_out.c
+++ b/src/src/spool_out.c
@@ -52,7 +52,7 @@ if (f) (void)fclose(f);
 if (errmsg)
   *errmsg = msg;
 else
-  log_write_die(0, LOG_MAIN, "%s", msg);
+  log_write_die(LOG_MAIN, "%s", msg);
 
 return -1;
 }
@@ -84,7 +84,7 @@ have the same pid. We therefore have one go at unlinking it before giving up.
 
 if (fd < 0 && errno == EEXIST)
   {
-  DEBUG(D_any) debug_printf("%s exists: unlinking\n", temp_name);
+  DEBUG(any) debug_printf("%s exists: unlinking\n", temp_name);
   Uunlink(temp_name);
   fd = Uopen(temp_name, O_RDWR|O_CREAT|O_EXCL, SPOOL_MODE);
   }
@@ -96,7 +96,7 @@ automatically. */
 if (fd >= 0)
   if (exim_fchown(fd, exim_uid, exim_gid, temp_name) || fchmod(fd, SPOOL_MODE))
     {
-    DEBUG(D_any) debug_printf("failed setting perms on %s\n", temp_name);
+    DEBUG(any) debug_printf("failed setting perms on %s\n", temp_name);
     (void) close(fd); fd = -1;
     Uunlink(temp_name);
     }
@@ -165,7 +165,7 @@ uschar * tname = spool_fname(US"input", message_subdir, US"hdr.", message_id);
 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);
+DEBUG(receive|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
@@ -232,8 +232,10 @@ tree_walk(acl_var_m, &acl_var_write, fp);
 
 if (*debuglog_name)
   {
-  fprintf(fp, "-debug_selector 0x%x\n", debug_selector);
-  fprintf(fp, "-debuglog_name %s\n", debuglog_name);
+  fprintf(fp, "-debug_selector 0x" PR_EXIM_BITMASK, debug_selector[0]);
+  for (int i = 1; i < DEBUG_SELECTOR_SIZE; i++)
+    fprintf(fp, ",0x" PR_EXIM_BITMASK, debug_selector[i]);
+  fprintf(fp, "\n-debuglog_name %s\n", debuglog_name);
   }
 
 if (f.spool_file_wireformat)
@@ -269,15 +271,12 @@ if (spam_score_int) spool_var_write(fp, US"spam_score_int", spam_score_int);
 if (f.deliver_manual_thaw) fprintf(fp, "-manual_thaw\n");
 if (f.sender_set_untrusted) fprintf(fp, "-sender_set_untrusted\n");
 
-#ifdef EXPERIMENTAL_BRIGHTMAIL
-if (bmi_verdicts) spool_var_write(fp, US"bmi_verdicts", bmi_verdicts);
-#endif
-
 #ifndef DISABLE_TLS
 if (tls_in.certificate_verified) fprintf(fp, "-tls_certificate_verified\n");
 if (tls_in.cipher) spool_var_write(fp, US"tls_cipher", tls_in.cipher);
 if (tls_in.peercert)
   {
+  /* -- marks as tainted */
   if (tls_export_cert(big_buffer, big_buffer_size, tls_in.peercert))
     fprintf(fp, "--tls_peercert %s\n", CS big_buffer);
   }
@@ -305,9 +304,9 @@ if (message_smtputf8)
 #endif
 
 /* Write the dsn flags to the spool header file */
-/* DEBUG(D_deliver) debug_printf("DSN: Write SPOOL: -dsn_envid %s\n", dsn_envid); */
+/* DEBUG(deliver) debug_printf("DSN: Write SPOOL: -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(deliver) debug_printf("DSN: Write SPOOL: -dsn_ret %d\n", dsn_ret); */
 if (dsn_ret) fprintf(fp, "-dsn_ret %d\n", dsn_ret);
 
 /* To complete the envelope, write out the tree of non-recipients, followed by
@@ -322,7 +321,7 @@ for (int i = 0; i < recipients_count; i++)
   recipient_item *r = recipients_list + i;
   const uschar *address = zap_newlines(r->address);
 
-  /* DEBUG(D_deliver) debug_printf("DSN: Flags: 0x%x\n", r->dsn_flags); */
+  /* DEBUG(deliver) debug_printf("DSN: Flags: 0x%x\n", r->dsn_flags); */
 
   if (r->pno < 0 && !r->errors_to && r->dsn_flags == 0)
     fprintf(fp, "%s\n", address);
@@ -337,7 +336,7 @@ for (int i = 0; i < recipients_count; i++)
       r->dsn_flags, errors_to, Ustrlen(errors_to), r->pno);
     }
 
-    DEBUG(D_deliver) debug_printf("DSN: **** SPOOL_OUT - "
+    DEBUG(deliver) debug_printf("DSN: **** SPOOL_OUT - "
       "address: <%s> errorsto: <%s> orcpt: <%s> dsn_flags: 0x%x\n",
       r->address, r->errors_to, r->orcpt, r->dsn_flags);
   }
@@ -392,7 +391,7 @@ if (fclose(fp) != 0)
 incarnation. */
 
 fname = spool_fname(US"input", message_subdir, id, US"-H");
-DEBUG(D_receive|D_deliver) debug_printf("Renaming spool header file: %s\n", fname);
+DEBUG(receive|deliver) debug_printf("Renaming spool header file: %s\n", fname);
 
 if (Urename(tname, fname) < 0)
   return spool_write_error(where, errmsg, US"rename", tname, NULL);
@@ -429,7 +428,7 @@ if (close(fd) < 0)
 /* Return the number of characters in the headers, which is the file size, less
 the preliminary stuff, less the additional count fields on the headers. */
 
-DEBUG(D_receive) debug_printf("Size of headers = %d\n",
+DEBUG(receive) debug_printf("Size of headers = %d\n",
   (int)(statbuf.st_size - size_correction));
 
 return statbuf.st_size - size_correction;
@@ -466,7 +465,7 @@ uschar * fname = spool_fname(string_sprintf("%s%s", from, dir), subdir, id, suff
 uschar * tname = spool_q_fname(string_sprintf("%s%s", to,   dir), dq, subdir, id, suffix);
 if (Ulink(fname, tname) < 0 && (!noentok || errno != ENOENT))
   {
-  log_write(0, LOG_MAIN|LOG_PANIC, "link(%q, %q) failed while moving "
+  log_write(LOG_MAIN|LOG_PANIC, "link(%q, %q) failed while moving "
     "message: %s", fname, tname, strerror(errno));
   return FALSE;
   }
@@ -502,7 +501,7 @@ break_link(const uschar * dir, const uschar * subdir, const uschar * id,
 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(%q) failed while moving "
+  log_write(LOG_MAIN|LOG_PANIC, "unlink(%q) failed while moving "
     "message: %s", fname, strerror(errno));
   return FALSE;
   }
@@ -566,7 +565,7 @@ if (!break_link(US"input",  subdir, id, US"-H", from, FALSE) ||
     !break_link(US"msglog", subdir, id, US"", from, TRUE))
   return FALSE;
 
-log_write(0, LOG_MAIN, "moved from %s%s%s%sinput, %smsglog to %s%s%s%sinput, %smsglog",
+log_write(LOG_MAIN, "moved from %s%s%s%sinput, %smsglog to %s%s%s%sinput, %smsglog",
    *queue_name?"(":"", *queue_name?queue_name:US"", *queue_name?") ":"",
    from, from,
    *dest_qname?"(":"", *dest_qname?dest_qname:US"", *dest_qname?") ":"",
diff --git a/src/src/std-crypto.c b/src/src/std-crypto.c
index 29efa6997..127f7ab34 100644
--- a/src/src/std-crypto.c
+++ b/src/src/std-crypto.c
@@ -1017,7 +1017,7 @@ for (int first = 0, last = dh_constants_count; last > first; )
   if (c == 0)
     {
     if (dp->logging)
-      log_write(0, dp->logging,
+      log_write(dp->logging,
 	"WARNING: deprecated Diffie-Hellman parameter '%s' used", dp->label);
     return dp->pem;
     }
diff --git a/src/src/store.c b/src/src/store.c
index 4daa92512..3735f8d76 100644
--- a/src/src/store.c
+++ b/src/src/store.c
@@ -210,12 +210,17 @@ static const uschar * poolclass[N_PAIRED_POOLS] = {
 [POOL_TAINT_SEARCH] =	US"tainted",
 [POOL_TAINT_MESSAGE] =	US"tainted",
 };
+
+
+static dns_answer * dnsa_tainted = NULL;
+
 #endif
 
 
 static void * internal_store_malloc(size_t, const char *, int);
 static void   internal_store_free(void *, const char *, int linenumber);
 
+
 /******************************************************************************/
 
 static void
@@ -280,11 +285,22 @@ for (pp = paired_pools; pp < paired_pools + N_PAIRED_POOLS; pp++)
 #ifndef COMPILE_UTILITY
 stackdump();
 #endif
-log_write_die(0, LOG_MAIN,
+log_write_die(LOG_MAIN,
   "bad memory reference; pool not found, at %s %d", func, linenumber);
 return NULL;
 }
 
+
+/******************************************************************************/
+static BOOL
+is_tainted_dnsa(const void * p)
+{
+#ifndef COMPILE_UTILITY
+for (dns_answer * dnsa = dnsa_tainted; dnsa; dnsa = dnsa->next)
+  if (CS p >= CS dnsa && CS p < CS(dnsa+1)) return TRUE;
+#endif
+return FALSE;
+}
 /******************************************************************************/
 /* Test if a pointer refers to tainted memory.
 
@@ -321,14 +337,14 @@ for (quoted_pooldesc * qp = quoted_pools; qp; qp = qp->next)
   for (b = qp->pool.chainbase; b; b = b->next)
     if (is_pointer_in_block(b, p)) return TRUE;
 
-return FALSE;
+return is_tainted_dnsa(p);
 }
 
 
 void
 die_tainted(const uschar * msg, const uschar * func, int line)
 {
-log_write_die(0, LOG_MAIN, "Taint mismatch, %s: %s %d\n",
+log_write_die(LOG_MAIN, "Taint mismatch, %s: %s %d\n",
 	msg, func, line);
 }
 
@@ -372,7 +388,7 @@ store_writeprotect(int pool)
 #if !defined(COMPILE_UTILITY) && !defined(MISSING_POSIX_MEMALIGN)
 for (storeblock * b =  paired_pools[pool].chainbase; b; b = b->next)
   if (mprotect(b, ALIGNED_SIZEOF_STOREBLOCK + b->length, PROT_READ) != 0)
-    DEBUG(D_any) debug_printf("config block mprotect: (%d) %s\n", errno, strerror(errno));
+    DEBUG(any) debug_printf("config block mprotect: (%d) %s\n", errno, strerror(errno));
 #endif
 }
 
@@ -388,7 +404,7 @@ does this to return a current watermark value for a later release of
 allocated store. */
 
 if (size < 0 || size >= INT_MAX/2)
-  log_write_die(0, LOG_MAIN,
+  log_write_die(LOG_MAIN,
             "bad memory allocation requested (%d bytes) from %s %d",
             size, func, linenumber);
 
@@ -444,7 +460,7 @@ if (size > pp->yield_length)
       int err = posix_memalign((void **)&newblock,
 				pgsize, (mlength + pgsize - 1) & ~(pgsize - 1));
       if (err)
-	log_write_die(0, LOG_MAIN,
+	log_write_die(LOG_MAIN,
 	  "failed to alloc (using posix_memalign) %d bytes of memory: '%s'"
 	  "called from line %d in %s",
 	  size, strerror(err), linenumber, func);
@@ -528,7 +544,7 @@ if (!quoter_name)
   giving warnings. */
 
 #ifndef COMPILE_UTILITY
-  DEBUG(D_memory)
+  DEBUG(memory)
     debug_printf("---%d Get %6p %5d %-14s %4d\n", pool,
       pp->store_last_get, size, func, linenumber);
 #endif
@@ -536,13 +552,13 @@ if (!quoter_name)
 #ifndef COMPILE_UTILITY
 else
   {
-  DEBUG(D_memory)
+  DEBUG(memory)
     debug_printf("allocating quoted-block for quoter %u (from %s %d)\n",
       quoter, func, linenumber);
   if (!(pp = pool_for_quoter(quoter, NULL)))
     pp = quoted_pool_new(quoter, quoter_name);
   yield = pool_get(pp, size, FALSE, func, linenumber);
-  DEBUG(D_memory)
+  DEBUG(memory)
     debug_printf("---QQ Get %6p %5d %-14s %4d\n",
       pp->store_last_get, size, func, linenumber);
   }
@@ -605,13 +621,13 @@ store_force_get_quoted(int size, unsigned quoter, const uschar * quoter_name,
 pooldesc * pp = pool_for_quoter(quoter, NULL);
 void * yield;
 
-DEBUG(D_memory)
+DEBUG(memory)
   debug_printf("allocating quoted-block for quoter %u (from %s %d)\n", quoter, func, linenumber);
 
 if (!pp) pp = quoted_pool_new(quoter, quoter_name);
 yield = pool_get(pp, size, FALSE, func, linenumber);
 
-DEBUG(D_memory)
+DEBUG(memory)
   debug_printf("---QQ Get %6p %5d %-14s %4d\n",
     pp->store_last_get, size, func, linenumber);
 
@@ -670,7 +686,7 @@ const void * p_qfn, * q_qfn;
 
 if (!p_name)
   {
-  DEBUG(D_any) debug_printf("No quoter name for addr\n");
+  DEBUG(any) debug_printf("No quoter name for addr\n");
   return FALSE;
   }
 
@@ -751,7 +767,7 @@ int inc = newsize - oldsize;
 int rounded_oldsize = oldsize;
 
 if (oldsize < 0 || newsize < oldsize || newsize >= INT_MAX/2)
-  log_write_die(0, LOG_MAIN,
+  log_write_die(LOG_MAIN,
             "bad memory extension requested (%d -> %d bytes) at %s %d",
             oldsize, newsize, func, linenumber);
 
@@ -766,7 +782,7 @@ if (CS ptr + rounded_oldsize != CS (pp->next_yield) ||
 giving warnings. */
 
 #ifndef COMPILE_UTILITY
-DEBUG(D_memory)
+DEBUG(memory)
   {
   quoted_pooldesc * qp;
   for (qp = quoted_pools; qp; qp = qp->next)
@@ -851,7 +867,7 @@ if (CS ptr < bc || CS ptr > bc + b->length)
     if (CS ptr >= bc && CS ptr <= bc + b->length) break;
     }
   if (!b)
-    log_write_die(0, LOG_MAIN, "internal error: store_reset(%p) "
+    log_write_die(LOG_MAIN, "internal error: store_reset(%p) "
       "failed: pool=%d %-14s %4d", ptr, pool, func, linenumber);
   }
 
@@ -924,7 +940,7 @@ while ((b = bb))
 giving warnings. */
 
 #ifndef COMPILE_UTILITY
-DEBUG(D_memory)
+DEBUG(memory)
   debug_printf("---%d Rst %6p %5d %-14s %4d\tpool %d\n", pool, ptr,
     count + oldmalloc - pool_malloc,
     func, linenumber, pool_malloc);
@@ -942,10 +958,10 @@ store_reset_3(rmark r, const char * func, int linenumber)
 void ** ptr = r;
 
 if (store_pool >= POOL_TAINT_BASE)
-  log_write_die(0, LOG_MAIN,
+  log_write_die(LOG_MAIN,
     "store_reset called for pool %d: %s %d\n", store_pool, func, linenumber);
 if (!r)
-  log_write_die(0, LOG_MAIN,
+  log_write_die(LOG_MAIN,
     "store_reset called with bad mark: %s %d\n", func, linenumber);
 
 internal_store_reset(*ptr, store_pool + POOL_TAINT_BASE, func, linenumber);
@@ -1007,7 +1023,7 @@ if ((pp = pool_current_for_pointer(ptr)))
   giving warnings. */
 
 #ifndef COMPILE_UTILITY
-  DEBUG(D_memory)
+  DEBUG(memory)
     {
     quoted_pooldesc * qp;
     for (qp = quoted_pools; qp; qp = qp->next)
@@ -1024,7 +1040,7 @@ if ((pp = pool_current_for_pointer(ptr)))
   return;
   }
 #ifndef COMPILE_UTILITY
-DEBUG(D_memory)
+DEBUG(memory)
   debug_printf("non-last memory release try: %s %d\n", func, linenumber);
 #endif
 }
@@ -1037,13 +1053,13 @@ store_mark_3(const char * func, int linenumber)
 void ** p;
 
 #ifndef COMPILE_UTILITY
-DEBUG(D_memory)
+DEBUG(memory)
   debug_printf("---%d Mrk                    %-14s %4d\tpool %d\n",
     store_pool, func, linenumber, pool_malloc);
 #endif  /* COMPILE_UTILITY */
 
 if (store_pool >= POOL_TAINT_BASE)
-  log_write_die(0, LOG_MAIN,
+  log_write_die(LOG_MAIN,
     "store_mark called for pool %d: %s %d\n", store_pool, func, linenumber);
 
 /* Stash a mark for the tainted-twin release, in the untainted twin. Return
@@ -1095,7 +1111,7 @@ for (storeblock * b =  pp->chainbase; b; b = b->next)
     from giving warnings. */
 
 #ifndef COMPILE_UTILITY
-    DEBUG(D_memory)
+    DEBUG(memory)
       debug_printf("-Release %6p %-20s %4d %d\n", (void *)bb, func,
 	linenumber, pool_malloc);
 
@@ -1143,7 +1159,7 @@ BOOL release_ok = !is_tainted(oldblock) && pp->store_last_get == oldblock;		/*XX
 uschar * newblock;
 
 if (len < 0 || len > newsize)
-  log_write_die(0, LOG_MAIN,
+  log_write_die(LOG_MAIN,
             "bad memory extension requested (%d -> %d bytes) at %s %d",
             len, newsize, func, linenumber);
 
@@ -1181,7 +1197,7 @@ void * yield;
 a negative int, to the (unsigned, wider) size_t */
 
 if (size >= INT_MAX/2)
-  log_write_die(0, LOG_MAIN,
+  log_write_die(LOG_MAIN,
     "bad internal_store_malloc request (" SIZE_T_FMT " bytes) from %s %d",
     size, func, line);
 
@@ -1189,11 +1205,11 @@ size += sizeof(size_t);	/* space to store the size, used under debug */
 if (size < 16) size = 16;
 
 if (!(yield = malloc(size)))
-  log_write_die(0, LOG_MAIN, "failed to malloc " SIZE_T_FMT " bytes of memory: "
+  log_write_die(LOG_MAIN, "failed to malloc " SIZE_T_FMT " bytes of memory: "
     "called from line %d in %s", size, line, func);
 
 #ifndef COMPILE_UTILITY
-DEBUG(D_any) *(size_t *)yield = size;
+DEBUG(any) *(size_t *)yield = size;
 #endif
 yield = US yield + sizeof(size_t);
 
@@ -1209,7 +1225,7 @@ is not filled with zeros so as to catch problems. */
 
 if (f.running_in_test_harness)
   memset(yield, 0xF0, size - sizeof(size_t));
-DEBUG(D_memory) debug_printf("--Malloc %6p %5lu bytes\t%-20s %4d\tpool %5d  nonpool %5d\n",
+DEBUG(memory) debug_printf("--Malloc %6p %5lu bytes\t%-20s %4d\tpool %5d  nonpool %5d\n",
   yield, size, func, line, pool_malloc, nonpool_malloc);
 #endif  /* COMPILE_UTILITY */
 
@@ -1217,7 +1233,7 @@ return yield;
 }
 
 void *
-store_malloc_3(size_t size, const char *func, int linenumber)
+store_malloc_3(size_t size, const char * func, int linenumber)
 {
 if (n_nonpool_blocks++ > max_nonpool_blocks)
   max_nonpool_blocks = n_nonpool_blocks;
@@ -1244,8 +1260,8 @@ internal_store_free(void * block, const char * func, int linenumber)
 {
 uschar * p = US block - sizeof(size_t);
 #ifndef COMPILE_UTILITY
-DEBUG(D_any) nonpool_malloc -= *(size_t *)p;
-DEBUG(D_memory) debug_printf("----Free %6p %5ld bytes\t%-20s %4d\n",
+DEBUG(any) nonpool_malloc -= *(size_t *)p;
+DEBUG(memory) debug_printf("----Free %6p %5ld bytes\t%-20s %4d\n",
 		    block, *(size_t *)p, func, linenumber);
 #endif
 free(p);
@@ -1258,13 +1274,36 @@ n_nonpool_blocks--;
 internal_store_free(block, func, linenumber);
 }
 
+/******************************************************************************/
+#ifndef COMPILE_UTILITY
+/* Block-handling for dns answers - a bit over 64k each.
+We maintain a list for taint-tracking; these are from the outside so always
+tainted. Expect less than 3, so a linked-list is fine. */
+
+dns_answer *
+store_get_dns_answer_trc(const uschar * func, unsigned line)
+{
+dns_answer * dnsa = store_malloc_3(sizeof(dns_answer), CCS func, line);
+dnsa->next = dnsa_tainted;
+return dnsa_tainted = dnsa;
+}
+
+void
+store_free_dns_answer_trc(dns_answer * dnsa, const uschar * func, unsigned line)
+{
+for (dns_answer ** dp = &dnsa_tainted; *dp; dp = &((*dp)->next))
+  if (*dp == dnsa) { *dp = (*dp)->next; break; }
+store_free_3(dnsa, CCS func, line);
+}
+#endif	/*COMPILE_UTILITY*/
+
 /******************************************************************************/
 /* Stats output on process exit */
 void
 store_exit(void)
 {
 #ifndef COMPILE_UTILITY
-DEBUG(D_memory)
+DEBUG(memory)
  {
  int i;
  debug_printf("----Exit nonpool max: %3d kB in %d blocks\n",
diff --git a/src/src/string.c b/src/src/string.c
index 1acb31449..e26ab96d0 100644
--- a/src/src/string.c
+++ b/src/src/string.c
@@ -15,6 +15,11 @@ utilities and tests, and are cut out by the COMPILE_UTILITY macro. */
 #include <assert.h>
 
 
+#ifdef COMPILE_UTILITY
+BOOL print_topbitchars = FALSE;	/* referenced by string_printing3() */
+#endif
+
+
 #ifndef COMPILE_UTILITY
 /*************************************************
 *            Test for IP address                 *
@@ -310,7 +315,6 @@ return ch;
 
 
 
-#ifndef COMPILE_UTILITY
 /*************************************************
 *          Ensure string is printable            *
 *************************************************/
@@ -325,19 +329,20 @@ Arguments:
   flags		Bit 0: convert tabs.
 		Bit 1: convert spaces.
 		Bit 2: convert doublequotes.
+  len		if >= 0, max size of input string
+		otherwise, NUL-terminated
 
 Returns:        string with non-printers encoded as printing sequences
 */
 
-const uschar *
-string_printing2(const uschar * s, int flags)
+static const uschar *
+string_printing3(const uschar * s, int flags, int len)
 {
-int nonprintcount = 0;
-int length = 0;
-const uschar *t = s;
-uschar *ss, *tt;
+int nonprintcount = 0, olen = 0;
+const uschar * t = s;
+uschar * ss, * tt;
 
-while (*t)
+for (int n = len; *t && (n < 0 || n-- > 0); )
   {
   int c = *t++;
   if (  !mac_isprint(c)
@@ -345,21 +350,25 @@ while (*t)
      || flags & SP_SPACE && c == ' '
      || flags & SP_DQUOTES && c == '"'
      ) nonprintcount++;
-  length++;
+  olen++;
   }
 
-if (nonprintcount == 0) return s;
+if (nonprintcount == 0)
+  return *t ? string_copyn(s, len) : s;
 
 /* Get a new block of store guaranteed big enough to hold the
 expanded string. */
 
-tt = ss = store_get(length + nonprintcount * 3 + 1, s);
+tt = ss = store_get(olen + nonprintcount * 3 + 1, s);
 
 /* Copy everything, escaping non printers. */
 
-for (t = s; *t; )
+for (t = s; *t && (len < 0 || len-- > 0); )
   {
   int c = *t;
+
+  /*XXX does \ go through unchanged here?  Since we use it for escaping,
+  surely it should be doubled? */
   if (  mac_isprint(c)
      && (!(flags & SP_TAB) || c != '\t')
      && (!(flags & SP_SPACE) || c != ' ')
@@ -386,7 +395,12 @@ for (t = s; *t; )
 *tt = 0;
 return ss;
 }
-#endif  /* COMPILE_UTILITY */
+
+const uschar *
+string_printing2(const uschar * s, int flags)
+{
+return string_printing3(s, flags, -1);
+}
 
 /*************************************************
 *        Undo printing escapes in string         *
@@ -601,12 +615,14 @@ allow_utf8_domains is set true and UTF-8 characters are used in domain
 names. Backslash can also be used to escape other characters, though we
 shouldn't come across them in domain names.
 
+We always return tainted mem, as some callers use stack-auto source buffers.
+
 Argument:   the domain name string
 Returns:    copy of string in new store, de-escaped
 */
 
 uschar *
-string_copy_dnsdomain(uschar * s)
+string_copy_dnsdomain(const uschar * s)
 {
 uschar * yield;
 uschar * ss = yield = store_get(Ustrlen(s) + 1, GET_TAINTED);	/* always treat as tainted */
@@ -732,7 +748,7 @@ g = string_vformat_trc(g, func, line, STRING_SPRINTF_BUFFER_SIZE,
 va_end(ap);
 
 if (!g)
-  log_write_die(0, LOG_MAIN,
+  log_write_die(LOG_MAIN,
     "string_sprintf expansion was longer than %d; format string was (%s)\n"
     " called from %s %d\n",
     STRING_SPRINTF_BUFFER_SIZE, format, func, line);
@@ -814,10 +830,8 @@ Returns:         pointer to substring in string, or NULL if not found
 const uschar *
 strstric_c(const uschar * s, const uschar * t, BOOL space_follows)
 {
-const uschar * p = t;
-const uschar * yield = NULL;
-int cl = tolower(*p);
-int cu = toupper(*p);
+const uschar * p = t, * yield = NULL;
+int cl = tolower(*p), cu = toupper(*p);
 
 while (*s)
   {
@@ -846,12 +860,8 @@ while (*s)
 return NULL;
 }
 
-/*XXX C11 apparently has "generic functions", which allow the tracking
-of a parameter's type through to the return type.  Thit would neatly
-permit a single function name to be used, with better dev-safety, for this. */
-
 uschar *
-strstric(const uschar * s, const uschar * t, BOOL space_follows)
+strstric_nc(const uschar * s, const uschar * t, BOOL space_follows)
 {
 return US strstric_c(s, t, space_follows);
 }
@@ -940,7 +950,7 @@ if (sep <= 0)
     {
     if (!is_tainted(s))
       sep = s[1];
-    else DEBUG(D_any) 
+    else DEBUG(any) 
       debug_printf("attempt to use tainted change-of-seperator spec (%s %d)\n",
 		    config_filename, config_lineno);
     if (*++s) ++s;
@@ -1047,7 +1057,7 @@ return NULL;
 ************************************************/
 /* 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. A zero-length element results in a single space addition.
 
 Despite having the same growable-string interface as string_cat() the list is
 always returned null-terminated.
@@ -1069,13 +1079,18 @@ uschar * sp;
 if (list && list->ptr)
   list = string_catn(list, &sep, 1);
 
-while((sp = Ustrchr(ele, sep)))
+if (!*ele)
+  list = string_catn(list, US" ", 1);
+else
   {
-  list = string_catn(list, ele, sp-ele+1);
-  list = string_catn(list, &sep, 1);
-  ele = sp+1;
+  while((sp = Ustrchr(ele, sep)))
+    {
+    list = string_catn(list, ele, sp-ele+1);
+    list = string_catn(list, &sep, 1);
+    ele = sp+1;
+    }
+  list = string_cat(list, ele);
   }
-list = string_cat(list, ele);
 (void) string_from_gstring(list);
 return list;
 }
@@ -1190,13 +1205,13 @@ existing length of the string. */
 unsigned inc = oldsize < 4096 ? 127 : 1023;
 
 if (g->ptr < 0 || g->ptr > g->size || g->size >= INT_MAX/2)
-  log_write_die(0, LOG_MAIN,
+  log_write_die(LOG_MAIN,
     "internal error in gstring_grow (ptr %d size %d)", g->ptr, g->size);
 
 if (count <= 0) return;
 
 if (count >= INT_MAX/2 - g->ptr)
-  log_write_die(0, LOG_MAIN,
+  log_write_die(LOG_MAIN,
     "internal error in gstring_grow (ptr %d count %d)", g->ptr, count);
 
 g->size = (p + count + inc + 1) & ~inc;		/* one for a NUL */
@@ -1215,6 +1230,7 @@ if (!store_extend(g->s, oldsize, g->size))
   g->s = store_newblock(g->s, g->size, p);
 }
 
+#endif	/*!COMPILE_UTILITY*/
 
 
 /*************************************************
@@ -1247,7 +1263,7 @@ string_catn(gstring * g, const uschar * s, int count)
 int p;
 
 if (count < 0)
-  log_write_die(0, LOG_MAIN,
+  log_write_die(LOG_MAIN,
     "internal error in string_catn (count %d)", count);
 if (count == 0) return g;
 
@@ -1272,7 +1288,7 @@ else if (is_incompatible(g->s, s))
   }
 
 if (g->ptr < 0 || g->ptr > g->size)
-  log_write_die(0, LOG_MAIN,
+  log_write_die(LOG_MAIN,
     "internal error in string_catn (ptr %d size %d)", g->ptr, g->size);
 
 p = g->ptr;
@@ -1290,6 +1306,7 @@ return g;
 
 
 
+#ifndef COMPILE_UTILITY
 /*************************************************
 *        Append strings to another string        *
 *************************************************/
@@ -1410,15 +1427,15 @@ enum ltypes { L_NORMAL=1, L_SHORT=2, L_LONG=3, L_LONGLONG=4, L_LONGDOUBLE=5, L_S
 int width, precision, initial_off, lim, need;
 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 */
-
 #ifdef COMPILE_UTILITY
 assert(!(flags & SVFMT_EXTEND));
 assert(g);
 #else
 
+string_datestamp_offset = -1;	/* Datestamp not inserted */
+string_datestamp_length = 0;	/* Datestamp not inserted */
+string_datestamp_type = 0;	/* Datestamp not inserted */
+
 /* Ensure we have a string, to save on checking later */
 if (!g) g = string_get(16);
 
@@ -1532,7 +1549,7 @@ while (*fp)
 	gp = CS g->s + g->ptr;
 	}
       strncpy(newformat, item_start, fp - item_start);
-      newformat[fp - item_start] = 0;
+      newformat[fp - item_start] = '\0';
 
       /* Short int is promoted to int when passing through ..., so we must use
       int for va_arg(). */
@@ -1566,7 +1583,7 @@ while (*fp)
       if ((ptr = va_arg(ap, void *)))
 	{
 	strncpy(newformat, item_start, fp - item_start);
-	newformat[fp - item_start] = 0;
+	newformat[fp - item_start] = '\0';
 	g->ptr += sprintf(gp, newformat, ptr);
 	}
       else
@@ -1586,21 +1603,31 @@ while (*fp)
     case 'E':
     case 'g':
     case 'G':
-      if (precision < 0) precision = 6;
-      if ((need = g->ptr + precision + 8) > lim)
+      {
+      unsigned nsize;
+      union { double d; long double ld; } u;
+
+      strncpy(newformat, item_start, fp - item_start);
+      newformat[fp-item_start] = '\0';
+      if (length == L_LONGDOUBLE)	u.ld = va_arg(ap, long double);
+      else				u.d = va_arg(ap, double);
+
+      need = length == L_LONGDOUBLE
+	    ? snprintf(gp, 0, newformat, u.ld)
+	    : snprintf(gp, 0, newformat, u.d);
+
+      if ((nsize = g->ptr + need) > lim)
 	{
-	if (!(flags & SVFMT_EXTEND || need >= size_limit)) return NULL;
-	gstring_grow(g, precision+8);
+	if (!(flags & SVFMT_EXTEND || nsize >= size_limit)) return NULL;
+	gstring_grow(g, need);
 	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));
+      g->ptr += length == L_LONGDOUBLE
+		? sprintf(gp, newformat, u.ld)
+		: sprintf(gp, newformat, u.d);
       break;
+      }
 
     /* String types */
 
@@ -1624,6 +1651,7 @@ while (*fp)
       g->s[g->ptr++] = (uschar) va_arg(ap, int);
       break;
 
+#ifndef COMPILE_UTILITY
     case 'D':                   /* Insert daily datestamp for log file names */
       s = CS tod_stamp(tod_log_datestamp_daily);
       string_datestamp_offset = g->ptr;		/* Passed back via global */
@@ -1639,6 +1667,7 @@ while (*fp)
       string_datestamp_type = tod_log_datestamp_monthly;
       slen = string_datestamp_length;
       goto INSERT_STRING;
+#endif
 
     case 'Y':			/* gstring pointer */
       {
@@ -1660,7 +1689,7 @@ while (*fp)
       {
       gstring * zg = NULL;
       s = va_arg(ap, char *);
-      if (IS_DEBUG(D_noutf8))
+      if (IS_DEBUG(noutf8))
 	for ( ; *s; s++)
 	  zg = string_catn(zg, CUS (*s == 'K' ? "|" : s), 1);
       else
@@ -1686,15 +1715,25 @@ while (*fp)
 
     case 'W':			/* Maybe mark up ctrls, spaces & newlines */
       s = va_arg(ap, char *);
-      if (s && !IS_DEBUG(D_noutf8))
+      if (s && !IS_DEBUG(noutf8))
 	{
 	gstring * zg = NULL;
 	int p = precision;
 
+	if (!*s)	/* output something distinctive for an empty input */
+	  {
+	  zg = string_catn(zg, CUS "e" UTF8_COMB_BRIDGE_BELOW
+				  "m" UTF8_COMB_BRIDGE_BELOW
+				  "p" UTF8_COMB_BRIDGE_BELOW
+				  "t" UTF8_COMB_BRIDGE_BELOW
+				  "y" UTF8_COMB_BRIDGE_BELOW, 15);
+	  if (precision >= 0) precision += 15;
+	  }
+
 	/* If a precision was given, we can handle embedded NULs. Take it as
 	applying to the input and expand it for the transformed result */
 
-	for ( ; precision >= 0 || *s; s++)
+	else for ( ; precision >= 0 || *s; s++)
 	  if (p >= 0 && --p < 0)
 	    break;
 	  else switch (*s)
@@ -1708,10 +1747,10 @@ while (*fp)
 	      if (precision >= 0) precision += 3;
 	      break;
 	    default:
-	      if (*s <= ' ')
+	      if (* US s <= ' ')
 		{	/* base of UTF8 symbols for ASCII control chars */
 		uschar ctrl_symbol[3] = {[0]=0xe2, [1]=0x90, [2]=0x80};
-		ctrl_symbol[2] |= *s;
+		ctrl_symbol[2] |= * US s;
 		zg = string_catn(zg, ctrl_symbol, 3);
 		if (precision >= 0) precision += 2;
 		}
@@ -1782,12 +1821,14 @@ while (*fp)
 	{ s = "<NULL>"; precision = slen = 6; }
       }
       goto INSERT_GSTRING;
+#endif
 
     case 'q':			/* string, to be wrapped in "" and with tab & " escaped */
       if ((s = va_arg(ap, char *)))
 	{
 	gstring * zg = string_catn(NULL, US"\"", 1);
-	zg = string_cat(zg, string_printing2(US s, SP_TAB | SP_DQUOTES));
+	zg = string_cat(zg,
+			string_printing3(US s, SP_TAB | SP_DQUOTES, precision));
 	zg = string_catn(zg, US"\"", 1);
 	s = CS zg->s; precision = slen = gstring_length(zg);
 	}
@@ -1795,7 +1836,6 @@ while (*fp)
 	{ s = "<NULL>"; precision = slen = 6; }
       goto INSERT_GSTRING;
 
-#endif
     case 's':
     case 'S':                   /* Forces *lower* case */
     case 'T':                   /* Forces *upper* case */
@@ -1817,9 +1857,9 @@ while (*fp)
 	else
 	  die_tainted(US"string_vformat", func, line);
 #endif
-
-    INSERT_STRING:              /* Come to from %D or %M above */
-
+#ifndef COMPILE_UTILITY
+    INSERT_STRING:              /* Come from %D or %M above */
+#endif
       {
       BOOL truncated = FALSE;
 
@@ -1876,15 +1916,15 @@ while (*fp)
 
     default:
       strncpy(newformat, item_start, fp - item_start);
-      newformat[fp-item_start] = 0;
-      log_write_die(0, LOG_MAIN, "string_format: unsupported type "
+      newformat[fp-item_start] = '\0';
+      log_write_die(LOG_MAIN, "string_format: unsupported type "
 	"in %q in %q", newformat, format);
       break;
     }
   }
 
 if (g->ptr > g->size)
-  log_write_die(0, LOG_MAIN,
+  log_write_die(LOG_MAIN,
     "string_format internal error: caller %s %d", func, line);
 return g;
 }
@@ -2076,7 +2116,8 @@ while (fgets(CS buffer, sizeof(buffer), stdin) != NULL)
 
     else
       {
-      uschar *sss = malloc(s - ss + 1);
+      uschar * sss = malloc(s - ss + 1);
+      if (!sss) { perror("malloc"); return EXIT_FAILURE; }
       Ustrncpy(sss, ss, s-ss);
       args[n++] = sss;
       }
@@ -2099,7 +2140,7 @@ while (fgets(CS buffer, sizeof(buffer), stdin) != NULL)
   if (countset) printf("count=%d\n", count);
   }
 
-return 0;
+return EXIT_SUCCESS;
 }
 #endif
 
diff --git a/src/src/structs.h b/src/src/structs.h
index 8f3eaacbf..8be68f123 100644
--- a/src/src/structs.h
+++ b/src/src/structs.h
@@ -47,11 +47,11 @@ typedef struct macro_item {
   const uschar * replacement;
 } macro_item;
 
-/* Structure for bit tables for debugging and logging */
+/* Structure for bit table for logging */
 
 typedef struct bit_table {
-  uschar *name;
-  int bit;
+  uschar *	name;
+  unsigned	logchan_bit;
 } bit_table;
 
 /* Block for holding a uid and gid, possibly unset, and an initgroups flag. */
@@ -153,6 +153,7 @@ typedef struct driver_instance {
 typedef struct driver_info {
   struct driver_info * next;
   uschar *driver_name;            /* Name of driver */
+  uschar *avail_string;		  /* if set, display rather than name */
 
   optionlist *options;            /* Table of private options names */
   int    *options_count;          /* -> Number of entries in table */
@@ -299,9 +300,6 @@ typedef struct router_instance {
   driver_instance drinst;
 
   uschar *address_data;           /* Arbitrary data */
-#ifdef EXPERIMENTAL_BRIGHTMAIL
-  uschar *bmi_rule;               /* Brightmail AntiSpam rule checking */
-#endif
   uschar *cannot_route_message;   /* Used when routing fails */
   uschar *condition;              /* General condition */
   uschar *current_directory;      /* For use during delivery */
@@ -330,11 +328,6 @@ typedef struct router_instance {
   uschar *transport_name;         /* Transport name */
 
   BOOL    address_test;           /* Use this router when testing addresses */
-#ifdef EXPERIMENTAL_BRIGHTMAIL
-  BOOL    bmi_deliver_alternate;  /* TRUE => BMI said that message should be delivered to alternate location */
-  BOOL    bmi_deliver_default;    /* TRUE => BMI said that message should be delivered to default location */
-  BOOL    bmi_dont_deliver;       /* TRUE => BMI said that message should not be delivered at all */
-#endif
   BOOL    expn;                   /* Use this router when processing EXPN */
   BOOL    caseful_local_part;     /* TRUE => don't lowercase */
   BOOL    check_local_user;       /* TRUE => check local user */
@@ -567,7 +560,7 @@ typedef struct address_item {
   uschar *message;                /* error message */
   uschar *user_message;           /* error message that can be sent over SMTP
                                      or quoted in bounce message */
-  const uschar *onetime_parent;         /* saved original parent for onetime */
+  const uschar *onetime_parent;   /* saved original parent for onetime */
   uschar **pipe_expandn;          /* numeric expansions for pipe from filter */
   uschar *return_filename;        /* name of return file */
   uschar *self_hostname;          /* after self=pass */
@@ -596,7 +589,7 @@ typedef struct address_item {
   uschar *auth_id;		  /* auth "login" name used by transport */
   uschar *auth_sndr;		  /* AUTH arg to SMTP MAIL, used by transport */
 
-  uschar *dsn_orcpt;              /* DSN orcpt value */
+  const uschar *dsn_orcpt;        /* DSN orcpt value */
   int     dsn_flags;              /* DSN flags */
   int     dsn_aware;              /* DSN aware flag */
 
@@ -687,8 +680,8 @@ typedef struct {
 
 typedef struct error_block {
   struct error_block *next;
-  const uschar *text1;
-  uschar *text2;
+  const uschar * text1;
+  const uschar * text2;
 } error_block;
 
 /* Chain of file names when processing the queue */
@@ -767,17 +760,18 @@ typedef struct search_cache {
 uncompressed, but the data pointer is into the raw data. */
 
 typedef struct {
-  uschar        name[DNS_MAXNAME];      /* domain name */
-  int           type;                   /* record type */
-  unsigned short ttl;		        /* time-to-live, seconds */
-  int           size;                   /* size of data */
-  const uschar *data;                   /* pointer to data */
+  uschar       *name;			/* domain name */
+  int           type;			/* record type */
+  unsigned short ttl;			/* time-to-live, seconds */
+  int           size;			/* size of data */
+  const uschar *data;			/* pointer to data */
 } dns_record;
 
 /* Structure for holding the result of a DNS query.  A touch over
 64k big, so take care to release as soon as possible. */
 
-typedef struct {
+typedef struct dns_answer {
+  struct dns_answer * next;
   int     answerlen;              /* length of the answer */
   uschar  answer[NS_MAXMSG];      /* the answer itself */
 } dns_answer;
@@ -833,7 +827,7 @@ typedef struct {
 
 #ifdef SUPPORT_DANE
   BOOL			dane:1;		/* TLSA says connection must do dane */
-  dns_answer		tlsa_dnsa;	/* strictly, this should use tainted mem */
+  dns_answer *		tlsa_dnsa;
 #endif
 } smtp_connect_args;
 
@@ -989,6 +983,7 @@ enum vtypes {
   vtype_ino,            /* value is address of ino_t (not always an int) */
   vtype_uid,            /* value is address of uid_t (not always an int) */
   vtype_gid,            /* value is address of gid_t (not always an int) */
+  vtype_boolint,        /* value is address of bool; present as int */
   vtype_bool,           /* value is address of bool */
   vtype_stringptr,      /* value is address of pointer to string */
   vtype_msgbody,        /* as stringptr, but read when first required */
@@ -1016,7 +1011,8 @@ enum vtypes {
 #ifndef DISABLE_DKIM
   vtype_dkim,           /* Lookup of value in DKIM signature */
 #endif
-  vtype_module,		/* variable lives in a module; value is module name */
+  vtype_lookup_module,	/* variable lives in a module; value is module name */
+  vtype_misc_module,	/* variable lives in a module; value is module name */
 };
 
 /* Type for main variable table */
@@ -1053,5 +1049,39 @@ typedef struct misc_module_info {
 
 #define MISC_MODULE_MAGIC	0x4d4d4d31	/* MMM1 */
 
+
+/* Spool list ordering */
+typedef enum {
+  SLIST_ORDER_UNDEFINED, SLIST_OLDER_FIRST, SLIST_RANDOM, SLIST_NEWER_FIRST
+} s_order_t;
+
+
+/* Input processing layer */
+typedef struct in_processing {
+  /* get byte, refill if needed w/ byte count limit */
+  int	(*in_getc)(const struct in_processing *, unsigned);
+
+  /* predicate: byte avail for reading (without refill) */
+  BOOL	(*in_hasc)(const struct in_processing *);	
+
+  /* get many bytes, with refill; arg req/ret byte cnt */
+  const uschar * (*in_getbuf)(const struct in_processing *, unsigned *);
+
+  /* get up to the given number of bytes from any cached data, and feed to dkim. */
+  void	(*in_getcache)(const struct in_processing *, unsigned);
+
+  /* push one byte back into buffer */
+  int	(*in_ungetc)(const struct in_processing *, int);
+
+  /* predicate: EOF status */
+  int	(*in_feof)(const struct in_processing *);
+
+  /* predicate: error status */
+  int	(*in_ferror)(const struct in_processing *);
+
+  const struct in_processing * in_lower;	/* underlying layer, or NULL */
+  const uschar * in_layer_name;
+} in_processing;
+
 #endif	/* whole file */
 /* End of structs.h */
diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c
index bb42d709d..d6e538c9d 100644
--- a/src/src/tls-gnu.c
+++ b/src/src/tls-gnu.c
@@ -57,11 +57,6 @@ require current GnuTLS, then we'll drop support for the ancient libraries).
 #if GNUTLS_VERSION_NUMBER >= 0x030000
 # define SUPPORT_SELFSIGN	/* Uncertain what version is first usable but 2.12.23 is not */
 #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
@@ -78,6 +73,11 @@ require current GnuTLS, then we'll drop support for the ancient libraries).
 # define GNUTLS_AUTO_GLOBAL_INIT
 # define GNUTLS_AUTO_PKCS11_MANUAL
 #endif
+#if GNUTLS_VERSION_NUMBER >= 0x030306
+# define SUPPORT_CA_DIR
+#else
+# undef  SUPPORT_CA_DIR
+#endif
 #if (GNUTLS_VERSION_NUMBER >= 0x030404) \
   || (GNUTLS_VERSION_NUMBER >= 0x030311) && (GNUTLS_VERSION_NUMBER & 0xffff00 == 0x030300)
 # ifndef DISABLE_OCSP
@@ -90,6 +90,9 @@ require current GnuTLS, then we'll drop support for the ancient libraries).
 #if GNUTLS_VERSION_NUMBER >= 0x030506 && !defined(DISABLE_OCSP)
 # define SUPPORT_SRV_OCSP_STACK
 #endif
+#if GNUTLS_VERSION_NUMBER >= 0x030600
+# define EXIM_TLS_KEX_GROUP
+#endif
 #if GNUTLS_VERSION_NUMBER >= 0x030603
 # define EXIM_HAVE_TLS1_3
 # define SUPPORT_GNUTLS_EXT_RAW_PARSE
@@ -390,7 +393,7 @@ Returns:    DEFER/FAIL
 */
 
 static int
-tls_error(const uschar *prefix, const uschar *msg, const host_item *host,
+tls_error(const uschar * prefix, const uschar * msg, const host_item * host,
   uschar ** errstr)
 {
 if (errstr)
@@ -401,7 +404,7 @@ return host ? FAIL : DEFER;
 
 /* Returns:    DEFER/FAIL */
 static int
-tls_error_gnu(exim_gnutls_state_st * state, const uschar *prefix, int err,
+tls_error_gnu(exim_gnutls_state_st * state, const uschar * prefix, int err,
   uschar ** errstr)
 {
 return tls_error(prefix,
@@ -458,8 +461,7 @@ return FALSE;
 static int
 tls_g_init(uschar ** errstr)
 {
-int rc;
-DEBUG(D_tls) debug_printf("GnuTLS global init required\n");
+DEBUG(tls) debug_printf("GnuTLS global init required\n");
 
 #if defined(HAVE_GNUTLS_PKCS11) && !defined(GNUTLS_AUTO_PKCS11_MANUAL)
 /* By default, gnutls_global_init will init PKCS11 support in auto mode,
@@ -469,17 +471,23 @@ environment variables are used and so breaks for users calling mailq.
 To prevent this, we init PKCS11 first, which is the documented approach. */
 
 if (!gnutls_allow_auto_pkcs11)
+  {
+  int rc;
   if ((rc = gnutls_pkcs11_init(GNUTLS_PKCS11_FLAG_MANUAL, NULL)))
     return tls_error_gnu(NULL, US"gnutls_pkcs11_init", rc, errstr);
+  }
 #endif
 
 #ifndef GNUTLS_AUTO_GLOBAL_INIT
-if ((rc = gnutls_global_init()))
-  return tls_error_gnu(NULL, US"gnutls_global_init", rc, errstr);
+  {
+  int rc;
+  if ((rc = gnutls_global_init()))
+    return tls_error_gnu(NULL, US"gnutls_global_init", rc, errstr);
+  }
 #endif
 
 #if EXIM_GNUTLS_LIBRARY_LOG_LEVEL >= 0
-DEBUG(D_tls)
+DEBUG(tls)
   {
   gnutls_global_set_log_function(exim_gnutls_logger_cb);
   /* arbitrarily chosen level; bump up to 9 for more */
@@ -489,7 +497,7 @@ DEBUG(D_tls)
 
 #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");
+  log_write(LOG_MAIN, "OCSP unusable with this GnuTLS library version");
 #endif
 
 exim_gnutls_base_init_done = TRUE;
@@ -561,11 +569,11 @@ uschar * errstr;
 if (errno == 0)
   if (rc == GNUTLS_E_INVALID_SESSION)
     {
-    DEBUG(D_tls) debug_printf("- INVALID_SESSION with zero errno\n");
+    DEBUG(tls) debug_printf("- INVALID_SESSION with zero errno\n");
     return;
     }
   else if (text)
-    DEBUG(D_tls) debug_printf("- zero errno; %s\n", text);
+    DEBUG(tls) debug_printf("- zero errno; %s\n", text);
 
 msg = rc == GNUTLS_E_FATAL_ALERT_RECEIVED
   ? string_sprintf("A TLS fatal alert has been received: %s",
@@ -578,16 +586,23 @@ msg = rc == GNUTLS_E_FATAL_ALERT_RECEIVED
 
 (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
+if (!state->host)
   {
   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);
+  log_write(LOG_MAIN, "TLS error on %s %s", conn_info, errstr);
   }
+else if (  !tls_out.smtp_quit
+#ifdef GNUTLS_E_PREMATURE_TERMINATION
+	|| rc != GNUTLS_E_PREMATURE_TERMINATION
+#endif
+	)
+  log_write(LOG_MAIN, "H=%s [%s] TLS error on connection %s",
+    state->host->name, state->host->address, errstr);
+else DEBUG(tls)
+  debug_printf("H=%s [%s] TLS error on connection %s\n",
+    state->host->name, state->host->address, errstr);
 }
 
 
@@ -596,19 +611,20 @@ static BOOL
 tls_refill(unsigned lim)
 {
 exim_gnutls_state_st * state = &state_server;
+uschar * s = state->xfer_buffer + IN_UNGETC_MAX;
 ssize_t inbytes;
 
-DEBUG(D_tls) debug_printf("Calling gnutls_record_recv"
+DEBUG(tls) debug_printf("Calling gnutls_record_recv"
   "(session=%p, buffer=%p, buffersize=%u)\n",
-  state->session, state->xfer_buffer, ssl_xfer_buffer_size);
+  state->session, s, ssl_xfer_buffer_rsize);
 
 sigalrm_seen = FALSE;
 if (smtp_receive_timeout > 0) ALARM(smtp_receive_timeout);
 
 errno = 0;
 do
-  inbytes = gnutls_record_recv(state->session, state->xfer_buffer,
-    MIN(ssl_xfer_buffer_size, lim));
+  inbytes = gnutls_record_recv(state->session, s,
+    MIN(ssl_xfer_buffer_rsize, lim));
 while (inbytes == GNUTLS_E_AGAIN);
 
 if (smtp_receive_timeout > 0) ALARM_CLR(0);
@@ -623,20 +639,20 @@ 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. */
+TLS session has been closed down (for incoming), not that the socket itself has
+been closed down. Revert to non-TLS handling for both directions. */
 
 if (sigalrm_seen)
   {
-  DEBUG(D_tls) debug_printf("Got tls read timeout\n");
+  DEBUG(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");
-  tls_close_notify();
+  DEBUG(tls) debug_printf("Got TLS_EOF\n");
+  tls_close(NULL, TLS_SHUTDOWN_NOWAIT);
   return FALSE;
   }
 
@@ -644,16 +660,17 @@ else if (inbytes == 0)
 
 else if (inbytes < 0)
   {
-  DEBUG(D_tls) debug_printf("%s: err from gnutls_record_recv\n", __FUNCTION__);
+  DEBUG(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;
   }
+DEBUG(tls) debug_printf("Got " SSIZE_T_FMT " bytes\n", inbytes);
 #ifndef DISABLE_DKIM
-smtp_verify_feed(state->xfer_buffer, inbytes);
+smtp_verify_feed(s, inbytes);
 #endif
-state->xfer_buffer_hwm = (int) inbytes;
-state->xfer_buffer_lwm = 0;
+state->xfer_buffer_hwm = (int) inbytes + IN_UNGETC_MAX;
+state->xfer_buffer_lwm = IN_UNGETC_MAX;
 return TRUE;
 }
 
@@ -667,7 +684,7 @@ return TRUE;
     { \
     if (rc != GNUTLS_E_SUCCESS) \
       { \
-      DEBUG(D_tls) debug_printf("TLS: cert problem: %s: %s\n", \
+      DEBUG(tls) debug_printf("TLS: cert problem: %s: %s\n", \
 	(Label), gnutls_strerror(rc)); \
       return rc; \
       } \
@@ -721,7 +738,7 @@ tls_support * tlsp = state->tlsp;
 tlsp->active.sock = state->fd_out;
 tlsp->active.tls_ctx = state;
 
-DEBUG(D_tls) debug_printf("cipher: %s\n", state->ciphersuite);
+DEBUG(tls) debug_printf("cipher: %s\n", state->ciphersuite);
 
 tlsp->certificate_verified = state->peer_cert_verified;
 #ifdef SUPPORT_DANE
@@ -760,7 +777,7 @@ tlsp->channelbinding = NULL;
     rc = gnutls_session_channel_binding(state->session, GNUTLS_CB_TLS_UNIQUE, &channel);
 
   if (rc)
-    { DEBUG(D_tls) debug_printf("extracting channel binding: %s\n", gnutls_strerror(rc)); }
+    { DEBUG(tls) debug_printf("extracting channel binding: %s\n", gnutls_strerror(rc)); }
   else
     {
     int old_pool = store_pool;
@@ -771,7 +788,7 @@ tlsp->channelbinding = NULL;
     tlsp->channelbinding = b64encode_taint(CUS channel.data, (int)channel.size,
 		!tlsp->channelbind_exporter && state->host ? GET_TAINTED : GET_UNTAINTED);
     store_pool = old_pool;
-    DEBUG(D_tls) debug_printf("Have channel bindings cached for possible auth usage\n");
+    DEBUG(tls) debug_printf("Have channel bindings cached for possible auth usage\n");
     }
   }
 #endif
@@ -823,9 +840,8 @@ const uschar * filename = NULL;
 size_t sz;
 uschar * exp_tls_dhparam;
 BOOL use_file_in_spool = FALSE;
-const host_item * host = NULL; /* dummy for macros */
 
-DEBUG(D_tls) debug_printf("Initialising GnuTLS server params\n");
+DEBUG(tls) debug_printf("Initialising GnuTLS server params\n");
 
 if ((rc = gnutls_dh_params_init(&dh_server_params)))
   return tls_error_gnu(NULL, US"gnutls_dh_params_init", rc, errstr);
@@ -838,7 +854,7 @@ if (!expand_check(tls_dhparam, US"tls_dhparam", &exp_tls_dhparam, errstr))
 
 if (!exp_tls_dhparam)
   {
-  DEBUG(D_tls) debug_printf("Loading default hard-coded DH params\n");
+  DEBUG(tls) debug_printf("Loading default hard-coded DH params\n");
   m.data = US std_dh_prime_default();
   m.size = Ustrlen(m.data);
   }
@@ -846,7 +862,7 @@ else if (Ustrcmp(exp_tls_dhparam, "historic") == 0)
   use_file_in_spool = TRUE;
 else if (Ustrcmp(exp_tls_dhparam, "none") == 0)
   {
-  DEBUG(D_tls) debug_printf("Requested no DH parameters\n");
+  DEBUG(tls) debug_printf("Requested no DH parameters\n");
   return FAIL;
   }
 else if (exp_tls_dhparam[0] != '/')
@@ -862,7 +878,7 @@ if (m.data)
   {
   if ((rc = gnutls_dh_params_import_pkcs3(dh_server_params, &m, GNUTLS_X509_FMT_PEM)))
     return tls_error_gnu(NULL, US"gnutls_dh_params_import_pkcs3", rc, errstr);
-  DEBUG(D_tls) debug_printf("Loaded fixed standard D-H parameters\n");
+  DEBUG(tls) debug_printf("Loaded fixed standard D-H parameters\n");
   return OK;
   }
 
@@ -872,12 +888,12 @@ different filename and ensure we have sufficient bits. */
 
 if (!(dh_bits = gnutls_sec_param_to_pk_bits(GNUTLS_PK_DH, GNUTLS_SEC_PARAM_NORMAL)))
   return tls_error(US"gnutls_sec_param_to_pk_bits() failed", NULL, NULL, errstr);
-DEBUG(D_tls)
+DEBUG(tls)
   debug_printf("GnuTLS tells us that for D-H PK, NORMAL is %d bits\n",
       dh_bits);
 #else
 dh_bits = EXIM_SERVER_DH_BITS_PRE2_12;
-DEBUG(D_tls)
+DEBUG(tls)
   debug_printf("GnuTLS lacks gnutls_sec_param_to_pk_bits(), using %d bits\n",
       dh_bits);
 #endif
@@ -885,7 +901,7 @@ DEBUG(D_tls)
 /* Some clients have hard-coded limits. */
 if (dh_bits > tls_dh_max_bits)
   {
-  DEBUG(D_tls)
+  DEBUG(tls)
     debug_printf("tls_dh_max_bits clamping override, using %d bits instead\n",
         tls_dh_max_bits);
   dh_bits = tls_dh_max_bits;
@@ -946,7 +962,7 @@ if ((fd = Uopen(filename, O_RDONLY, 0)) >= 0)
   store_free(m.data);
   if (rc)
     return tls_error_gnu(NULL, US"gnutls_dh_params_import_pkcs3", rc, errstr);
-  DEBUG(D_tls) debug_printf("read D-H parameters from file %q\n", filename);
+  DEBUG(tls) debug_printf("read D-H parameters from file %q\n", filename);
   }
 
 /* If the file does not exist, fall through to compute new data and cache it.
@@ -955,7 +971,7 @@ If there was any other opening error, it is serious. */
 else if (errno == ENOENT)
   {
   rc = -1;
-  DEBUG(D_tls)
+  DEBUG(tls)
     debug_printf("D-H parameter cache file %q does not exist\n", filename);
   }
 else
@@ -993,12 +1009,12 @@ if (rc < 0)
   if (dh_bits >= EXIM_CLIENT_DH_MIN_BITS + 10)
     {
     dh_bits_gen = dh_bits - 10;
-    DEBUG(D_tls)
+    DEBUG(tls)
       debug_printf("being paranoid about DH generation, make it '%d' bits'\n",
           dh_bits_gen);
     }
 
-  DEBUG(D_tls)
+  DEBUG(tls)
     debug_printf("requesting generation of %d bit Diffie-Hellman prime ...\n",
         dh_bits_gen);
   if ((rc = gnutls_dh_params_generate2(dh_server_params, dh_bits_gen)))
@@ -1046,10 +1062,10 @@ if (rc < 0)
     return tls_error_sys(string_sprintf("failed to rename %q as %q",
           temp_fn, filename), errno, NULL, errstr);
 
-  DEBUG(D_tls) debug_printf("wrote D-H parameters to file %q\n", filename);
+  DEBUG(tls) debug_printf("wrote D-H parameters to file %q\n", filename);
   }
 
-DEBUG(D_tls) debug_printf("initialized server D-H parameters\n");
+DEBUG(tls) debug_printf("initialized server D-H parameters\n");
 return OK;
 }
 
@@ -1073,7 +1089,7 @@ rc = GNUTLS_E_NO_CERTIFICATE_FOUND;
 if (TRUE) goto err;
 #endif
 
-DEBUG(D_tls) debug_printf("TLS: generating selfsigned server cert\n");
+DEBUG(tls) debug_printf("TLS: generating selfsigned server cert\n");
 where = US"initialising pkey";
 if ((rc = gnutls_x509_privkey_init(&pkey))) goto err;
 
@@ -1166,11 +1182,11 @@ server_ocsp_stapling_cb(gnutls_session_t session, void * ptr,
   gnutls_datum_t * ocsp_response)
 {
 int ret;
-DEBUG(D_tls) debug_printf("OCSP stapling callback: %s\n", US ptr);
+DEBUG(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",
+  DEBUG(tls) debug_printf("Failed to load ocsp stapling file %s\n",
 			      CS ptr);
   tls_in.ocsp = OCSP_NOT_RESP;
   return GNUTLS_E_NO_CERTIFICATE_STATUS;
@@ -1193,7 +1209,7 @@ https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-valu
 switch (tls_id)
   {
   case 5:	/* Status Request */
-    DEBUG(D_tls) debug_printf("Seen status_request extension from client\n");
+    DEBUG(tls) debug_printf("Seen status_request extension from client\n");
     tls_in.ocsp = OCSP_NOT_RESP;
     break;
 #ifdef EXIM_HAVE_ALPN
@@ -1206,19 +1222,22 @@ switch (tls_id)
     a single bad ALPN being offered (the common case). */
     {
     gstring * g = NULL;
+    int asize;
 
-    DEBUG(D_tls) debug_printf("Seen ALPN extension from client (s=%u):", size);
-    for (const uschar * s = data+2; s-data < size-1; s += *s + 1)
+    DEBUG(tls) debug_printf("Seen ALPN extension from client (s=%u):", size);
+    for (const uschar * s = data+2;
+	 s-data < size && (asize = *s++) > 0 && s+asize - data <= size;
+	 s += asize)
       {
       server_seen_alpn++;
-      g = string_append_listele_n(g, ':', s+1, *s);
-      DEBUG(D_tls) debug_printf(" '%.*s'", (int)*s, s+1);
+      g = string_append_listele_n(g, ':', s, asize);
+      DEBUG(tls) debug_printf(" '%.*s'", asize, s);
       }
-    DEBUG(D_tls) debug_printf("\n");
+    DEBUG(tls) debug_printf("\n");
     if (server_seen_alpn > 1)
       {
-      log_write(0, LOG_MAIN, "TLS ALPN (%Y) rejected", g);
-      DEBUG(D_tls) debug_printf("TLS: too many ALPNs presented in handshake\n");
+      log_write(LOG_MAIN, "TLS ALPN (%Y) rejected", g);
+      DEBUG(tls) debug_printf("TLS: too many ALPNs presented in handshake\n");
       return GNUTLS_E_NO_APPLICATION_PROTOCOL;
       }
     break;
@@ -1250,7 +1269,7 @@ tls_server_servercerts_ext(void * ctx, unsigned tls_id,
 /* https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml */
 if (FALSE && tls_id == 5)	/* status_request */
   {
-  DEBUG(D_tls) debug_printf("Seen status_request extension\n");
+  DEBUG(tls) debug_printf("Seen status_request extension\n");
   tls_in.ocsp = exim_testharness_disable_ocsp_validity_check
     ? OCSP_VFY_NOT_TRIED : OCSP_VFIED;	/* We know that GnuTLS verifies responses */
   }
@@ -1286,7 +1305,7 @@ static int
 tls_server_certstatus_cb(gnutls_session_t session, unsigned int htype,
   unsigned when, unsigned int incoming, const gnutls_datum_t * msg)
 {
-DEBUG(D_tls) debug_printf("Sending certificate-status\n");		/*XXX we get this for tls1.2 but not for 1.3 */
+DEBUG(tls) debug_printf("Sending certificate-status\n");		/*XXX we get this for tls1.2 but not for 1.3 */
 # ifdef SUPPORT_SRV_OCSP_STACK
 tls_in.ocsp = exim_testharness_disable_ocsp_validity_check
   ? OCSP_VFY_NOT_TRIED : OCSP_VFIED;	/* We know that GnuTLS verifies responses */
@@ -1332,7 +1351,7 @@ extern char ** environ;
 if (environ) for (uschar ** p = USS environ; *p; p++)
   if (Ustrncmp(*p, "EXIM_TESTHARNESS_DISABLE_OCSPVALIDITYCHECK", 42) == 0)
     {
-    DEBUG(D_tls) debug_printf("Permitting known bad OCSP response\n");
+    DEBUG(tls) debug_printf("Permitting known bad OCSP response\n");
     exim_testharness_disable_ocsp_validity_check = TRUE;
     }
 }
@@ -1360,7 +1379,7 @@ if (server && tls_ocsp_file)
   }
 # endif
 #endif
-DEBUG(D_tls)
+DEBUG(tls)
   debug_printf("TLS: basic cred init, %s\n", server ? "server" : "client");
 }
 
@@ -1370,12 +1389,14 @@ creds_load_server_certs(exim_gnutls_state_st * state, const uschar * cert,
   const uschar * pkey, const uschar * ocsp, uschar ** errstr)
 {
 const uschar * clist = cert, * klist = pkey, * kfile, * cfile;
-int csep = 0, ksep = 0, cnt = 0, rc;
+int csep = 0, ksep = 0, rc;
 #ifndef DISABLE_OCSP
 uschar * ofile;
 const uschar * olist;
 # ifdef SUPPORT_GNUTLS_EXT_RAW_PARSE
 gnutls_x509_crt_fmt_t ocsp_fmt = GNUTLS_X509_FMT_DER;
+# else
+int ocsp_file_cnt = 0;
 # endif
 
 if (!expand_check(ocsp, US"tls_ocsp_file", &ofile, errstr))
@@ -1392,7 +1413,7 @@ while (cfile = string_nextinlist(&clist, &csep, NULL, 0))
   else
     {
     int gnutls_cert_index = -rc;
-    DEBUG(D_tls) debug_printf("TLS: cert/key %d %s registered\n",
+    DEBUG(tls) debug_printf("TLS: cert/key %d %s registered\n",
 			      gnutls_cert_index, cfile);
 
 #ifndef DISABLE_OCSP
@@ -1402,12 +1423,12 @@ while (cfile = string_nextinlist(&clist, &csep, NULL, 0))
       /* Set the OCSP stapling server info */
       if (gnutls_buggy_ocsp)
 	{
-	DEBUG(D_tls)
+	DEBUG(tls)
 	  debug_printf("GnuTLS library is buggy for OCSP; avoiding\n");
 	}
       else if ((ofile = string_nextinlist(&olist, &osep, NULL, 0)))
 	{
-	DEBUG(D_tls) debug_printf("OCSP response file %d  = %s\n",
+	DEBUG(tls) debug_printf("OCSP response file %d  = %s\n",
 				  gnutls_cert_index, ofile);
 # ifdef SUPPORT_GNUTLS_EXT_RAW_PARSE
 	if (Ustrncmp(ofile, US"PEM ", 4) == 0)
@@ -1427,7 +1448,7 @@ while (cfile = string_nextinlist(&clist, &csep, NULL, 0))
 	  return tls_error_gnu(state,
 		  US"gnutls_certificate_set_ocsp_status_request_file2",
 		  rc, errstr);
-	DEBUG(D_tls)
+	DEBUG(tls)
 	  debug_printf(" %d response%s loaded\n", rc, rc>1 ? "s":"");
 
 	/* Arrange callbacks for OCSP request observability */
@@ -1450,9 +1471,9 @@ while (cfile = string_nextinlist(&clist, &csep, NULL, 0))
 	else
 #  endif
 	  {
-	  if (cnt++ > 0)
+	  if (ocsp_file_cnt++ > 0)
 	    {
-	    DEBUG(D_tls)
+	    DEBUG(tls)
 	      debug_printf("oops; multiple OCSP files not supported\n");
 	    break;
 	    }
@@ -1462,7 +1483,7 @@ while (cfile = string_nextinlist(&clist, &csep, NULL, 0))
 # endif	/* SUPPORT_GNUTLS_EXT_RAW_PARSE */
 	}
       else
-	DEBUG(D_tls) debug_printf("ran out of OCSP response files in list\n");
+	DEBUG(tls) debug_printf("ran out of OCSP response files in list\n");
       }
 #endif /* DISABLE_OCSP */
     }
@@ -1475,7 +1496,7 @@ creds_load_client_certs(exim_gnutls_state_st * state, const host_item * host,
 {
 int rc = tls_add_certfile(state, host, cert, pkey, errstr);
 if (rc > 0) return rc;
-DEBUG(D_tls) debug_printf("TLS: cert/key registered\n");
+DEBUG(tls) debug_printf("TLS: cert/key registered\n");
 return OK;
 }
 
@@ -1494,7 +1515,7 @@ else
   {
   if (Ustat(bundle, &statbuf) < 0)
     {
-    log_write(0, LOG_MAIN|LOG_PANIC, "could not stat '%s' "
+    log_write(LOG_MAIN|LOG_PANIC, "could not stat '%s' "
 	"(tls_verify_certificates): %s", bundle, strerror(errno));
     return DEFER;
     }
@@ -1507,18 +1528,18 @@ else
   So s/!S_ISREG/S_ISDIR/ and change some messaging ... */
   if (S_ISDIR(statbuf.st_mode))
     {
-    log_write(0, LOG_MAIN|LOG_PANIC,
+    log_write(LOG_MAIN|LOG_PANIC,
 	"tls_verify_certificates %q is a directory", bundle);
     return DEFER;
     }
 #endif
 
-  DEBUG(D_tls) debug_printf("verify certificates = %s size=" OFF_T_FMT "\n",
+  DEBUG(tls) debug_printf("verify certificates = %s size=" OFF_T_FMT "\n",
 	  bundle, statbuf.st_size);
 
   if (statbuf.st_size == 0)
     {
-    DEBUG(D_tls)
+    DEBUG(tls)
       debug_printf("cert file empty, no certs, no verification, ignoring any CRL\n");
     return OK;
     }
@@ -1549,7 +1570,7 @@ else
 
 if (cert_count < 0)
   return tls_error_gnu(state, US"setting certificate trust", cert_count, errstr);
-DEBUG(D_tls)
+DEBUG(tls)
   debug_printf("Added %d certificate authorities\n", cert_count);
 
 return OK;
@@ -1560,13 +1581,13 @@ static int
 creds_load_crl(exim_gnutls_state_st * state, const uschar * crl, uschar ** errstr)
 {
 int cert_count;
-DEBUG(D_tls) debug_printf("loading CRL file = %s\n", crl);
+DEBUG(tls) debug_printf("loading CRL file = %s\n", crl);
 if ((cert_count = gnutls_certificate_set_x509_crl_file(state->lib_state.x509_cred,
     CS crl, GNUTLS_X509_FMT_PEM)) < 0)
   return tls_error_gnu(state, US"gnutls_certificate_set_x509_crl_file",
 	    cert_count, errstr);
 
-DEBUG(D_tls) debug_printf("Processed %d CRLs\n", cert_count);
+DEBUG(tls) debug_printf("Processed %d CRLs\n", cert_count);
 return OK;
 }
 
@@ -1578,7 +1599,7 @@ creds_load_pristring(exim_gnutls_state_st * state, const uschar * p,
 if (!p)
   {
   p = exim_default_gnutls_priority;
-  DEBUG(D_tls)
+  DEBUG(tls)
     debug_printf("GnuTLS using default session cipher/priority %q\n", p);
   }
 return gnutls_priority_init( (gnutls_priority_t *) &state->lib_state.pri_cache,
@@ -1621,7 +1642,7 @@ if (  opt_set_and_noexpand(tls_certificate)
 # endif
      && tls_set_watch(tls_privatekey, TRUE))
     {
-    DEBUG(D_tls) debug_printf("TLS: preloading server certs\n");
+    DEBUG(tls) debug_printf("TLS: preloading server certs\n");
     if (creds_load_server_certs(&state_server, tls_certificate,
 	  tls_privatekey && *tls_privatekey ? tls_privatekey : tls_certificate,
 # ifdef DISABLE_OCSP
@@ -1646,7 +1667,7 @@ else if (  !tls_certificate && !tls_privatekey
     }
   }
 else
-  DEBUG(D_tls) debug_printf("TLS: not preloading server certs\n");
+  DEBUG(tls) debug_printf("TLS: not preloading server certs\n");
 
 /* If tls_verify_certificates is non-empty and has no $, load CAs.
 If none was configured and we can't handle "system", treat as empty. */
@@ -1659,7 +1680,7 @@ if (  opt_set_and_noexpand(tls_verify_certificates)
   {
   if (tls_set_watch(tls_verify_certificates, FALSE))
     {
-    DEBUG(D_tls) debug_printf("TLS: preloading CA bundle for server\n");
+    DEBUG(tls) debug_printf("TLS: preloading CA bundle for server\n");
     if (creds_load_cabundle(&state_server, tls_verify_certificates,
 			    NULL, &dummy_errstr) != OK)
       return lifetime;
@@ -1671,18 +1692,18 @@ if (  opt_set_and_noexpand(tls_verify_certificates)
       {
       if (tls_set_watch(tls_crl, FALSE))
 	{
-	DEBUG(D_tls) debug_printf("TLS: preloading CRL for server\n");
+	DEBUG(tls) debug_printf("TLS: preloading CRL for server\n");
 	if (creds_load_crl(&state_server, tls_crl, &dummy_errstr) != OK)
 	  return lifetime;
 	state_server.lib_state.crl = TRUE;
 	}
       }
     else
-      DEBUG(D_tls) debug_printf("TLS: not preloading CRL for server\n");
+      DEBUG(tls) debug_printf("TLS: not preloading CRL for server\n");
     }
   }
 else
-  DEBUG(D_tls) debug_printf("TLS: not preloading CA bundle for server\n");
+  DEBUG(tls) debug_printf("TLS: not preloading CA bundle for server\n");
 #endif	/* EXIM_HAVE_INOTIFY */
 
 /* If tls_require_ciphers is non-empty and has no $, load the
@@ -1692,14 +1713,14 @@ ciphers priority cache.  If unset, load with the default.
 if (!tls_require_ciphers || opt_set_and_noexpand(tls_require_ciphers))
   {
   const char * dummy_errpos;
-  DEBUG(D_tls) debug_printf("TLS: preloading cipher list for server: %s\n",
+  DEBUG(tls) debug_printf("TLS: preloading cipher list for server: %s\n",
 		  tls_require_ciphers);
   if (  creds_load_pristring(&state_server, tls_require_ciphers, &dummy_errpos)
      == OK)
     state_server.lib_state.pri_string = TRUE;
   }
 else
-  DEBUG(D_tls) debug_printf("TLS: not preloading cipher list for server\n");
+  DEBUG(tls) debug_printf("TLS: not preloading cipher list for server\n");
 return lifetime;
 }
 
@@ -1746,7 +1767,7 @@ if (  opt_set_and_noexpand(ob->tls_certificate)
     {
     const uschar * pkey = ob->tls_privatekey;
 
-    DEBUG(D_tls)
+    DEBUG(tls)
       debug_printf("TLS: preloading client certs for transport '%s'\n", trname);
 
     /* The state->lib_state.x509_cred is used for the certs load, and is the sole
@@ -1760,7 +1781,7 @@ if (  opt_set_and_noexpand(ob->tls_certificate)
     }
   }
 else
-  DEBUG(D_tls)
+  DEBUG(tls)
     debug_printf("TLS: not preloading client certs, for transport '%s'\n", trname);
 
 /* If tls_verify_certificates is non-empty and has no $, load CAs.
@@ -1774,7 +1795,7 @@ if (  opt_set_and_noexpand(ob->tls_verify_certificates)
   {
   if (!watch || tls_set_watch(ob->tls_verify_certificates, FALSE))
     {
-    DEBUG(D_tls)
+    DEBUG(tls)
       debug_printf("TLS: preloading CA bundle for transport '%s'\n", trname);
     if (creds_load_cabundle(&tpt_dummy_state, ob->tls_verify_certificates,
 			    dummy_host, &dummy_errstr) != OK)
@@ -1785,18 +1806,18 @@ if (  opt_set_and_noexpand(ob->tls_verify_certificates)
       {
       if (!watch || tls_set_watch(ob->tls_crl, FALSE))
 	{
-	DEBUG(D_tls) debug_printf("TLS: preloading CRL for transport '%s'\n", trname);
+	DEBUG(tls) debug_printf("TLS: preloading CRL for transport '%s'\n", trname);
 	if (creds_load_crl(&tpt_dummy_state, ob->tls_crl, &dummy_errstr) != OK)
 	  return;
 	ob->tls_preload.crl = TRUE;
 	}
       }
     else
-      DEBUG(D_tls) debug_printf("TLS: not preloading CRL, for transport '%s'\n", trname);
+      DEBUG(tls) debug_printf("TLS: not preloading CRL, for transport '%s'\n", trname);
     }
   }
 else
-  DEBUG(D_tls)
+  DEBUG(tls)
       debug_printf("TLS: not preloading CA bundle, for transport '%s'\n", trname);
 
 /* We do not preload tls_require_ciphers to to the transport as it implicitly
@@ -1873,7 +1894,7 @@ if (!host)	/* server */
 	  || 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");
+      DEBUG(tls) debug_printf("We will re-expand TLS session files if we receive SNI\n");
       state->trigger_sni_changes = TRUE;
       }
     }
@@ -1921,7 +1942,7 @@ if (!state->lib_state.conn_certs)
     if (!host)
       return tls_install_selfsign(state, errstr);
     else
-      DEBUG(D_tls) debug_printf("TLS: no client certificate specified; okay\n");
+      DEBUG(tls) debug_printf("TLS: no client certificate specified; okay\n");
 
   if (  state->tls_privatekey && !Expand_check_tlsvar(tls_privatekey, errstr)
      || f.expand_string_forcedfail
@@ -1943,7 +1964,7 @@ if (!state->lib_state.conn_certs)
   if (state->exp_tls_certificate && *state->exp_tls_certificate)
     {
     BOOL load = TRUE;
-    DEBUG(D_tls) debug_printf("certificate file = %s\nkey file = %s\n",
+    DEBUG(tls) debug_printf("certificate file = %s\nkey file = %s\n",
 	state->exp_tls_certificate, state->exp_tls_privatekey);
 
     if (state->received_sni)
@@ -1951,12 +1972,12 @@ if (!state->lib_state.conn_certs)
 	 && Ustrcmp(state->exp_tls_privatekey,  saved_tls_privatekey)  == 0
 	 )
 	{
-	DEBUG(D_tls) debug_printf("TLS SNI: cert and key unchanged\n");
+	DEBUG(tls) debug_printf("TLS SNI: cert and key unchanged\n");
 	load = FALSE;	/* avoid re-loading the same certs */
 	}
       else		/* unload the pre-SNI certs before loading new ones */
 	{
-	DEBUG(D_tls) debug_printf("TLS SNI: have a changed cert/key pair\n");
+	DEBUG(tls) debug_printf("TLS SNI: have a changed cert/key pair\n");
 	gnutls_certificate_free_keys(state->lib_state.x509_cred);
 	}
 
@@ -1974,14 +1995,14 @@ if (!state->lib_state.conn_certs)
 			      errstr)
        )  )
       {
-      DEBUG(D_tls) debug_printf("load-cert: '%s'\n", *errstr);
+      DEBUG(tls) debug_printf("load-cert: '%s'\n", *errstr);
       return rc;
       }
     }
   }
 else
   {
-  DEBUG(D_tls)
+  DEBUG(tls)
     debug_printf("%s certs were preloaded\n", host ? "client" : "server");
 
   if (!state->tls_privatekey) state->tls_privatekey = state->tls_certificate;
@@ -2020,7 +2041,7 @@ if (!state->lib_state.cabundle)
     if (!(state->exp_tls_verify_certificates &&
 	  *state->exp_tls_verify_certificates))
       {
-      DEBUG(D_tls)
+      DEBUG(tls)
 	debug_printf("TLS: tls_verify_certificates expanded empty, ignoring\n");
       /* With no tls_verify_certificates, we ignore tls_crl too */
       return OK;
@@ -2028,7 +2049,7 @@ if (!state->lib_state.cabundle)
     }
   else
     {
-    DEBUG(D_tls)
+    DEBUG(tls)
       debug_printf("TLS: tls_verify_certificates not set or empty, ignoring\n");
     return OK;
     }
@@ -2037,7 +2058,7 @@ if (!state->lib_state.cabundle)
   }
 else
   {
-  DEBUG(D_tls)
+  DEBUG(tls)
     debug_printf("%s CA bundle was preloaded\n", host ? "client" : "server");
   state->exp_tls_verify_certificates = US state->tls_verify_certificates;
 
@@ -2058,7 +2079,7 @@ if (!state->lib_state.crl)
   }
 else
   {
-  DEBUG(D_tls)
+  DEBUG(tls)
       debug_printf("%s CRL was preloaded\n", host ? "client" : "server");
   state->exp_tls_crl = US state->tls_crl;
   }
@@ -2156,15 +2177,12 @@ if (host)
   /* 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), GET_UNTAINTED);
-  store_pool = old_pool;
+  state = store_get_perm(sizeof(exim_gnutls_state_st), GET_UNTAINTED);
 
   memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init));
   state->lib_state = ob->tls_preload;
   state->tlsp = tlsp;
-  DEBUG(D_tls) debug_printf("initialising GnuTLS client session\n");
+  DEBUG(tls) debug_printf("initialising GnuTLS client session\n");
   rc = gnutls_init(&state->session, GNUTLS_CLIENT);
 
   state->tls_certificate =	ob->tls_certificate;
@@ -2183,12 +2201,12 @@ else
   state = &state_server;
   state->tlsp = tlsp;
 
-  DEBUG(D_tls) debug_printf("initialising GnuTLS server session\n");
+  DEBUG(tls) debug_printf("initialising GnuTLS server session\n");
 
 #ifdef EXIM_TLS_EARLY_BANNER
   if (state->early_banner)
     {
-    DEBUG(D_tls) debug_printf("TLS: setting early-start flag\n");
+    DEBUG(tls) debug_printf("TLS: setting early-start flag\n");
     flags |= GNUTLS_ENABLE_EARLY_START;
     }
 #endif
@@ -2209,7 +2227,7 @@ state->host = host;
 /* This handles the variables that might get re-expanded after TLS SNI;
 tls_certificate, tls_privatekey, tls_verify_certificates, tls_crl */
 
-DEBUG(D_tls)
+DEBUG(tls)
   debug_printf("Expanding various TLS configuration options for session credentials\n");
 if ((rc = tls_expand_session_files(state, errstr)) != OK) return rc;
 
@@ -2225,7 +2243,7 @@ if (host)
     return DEFER;
   if (state->tlsp->sni && *state->tlsp->sni)
     {
-    DEBUG(D_tls)
+    DEBUG(tls)
       debug_printf("Setting TLS client SNI to %q\n", state->tlsp->sni);
     sz = Ustrlen(state->tlsp->sni);
     if ((rc = gnutls_server_name_set(state->session,
@@ -2234,7 +2252,7 @@ if (host)
     }
   }
 else if (state->tls_sni)
-  DEBUG(D_tls) debug_printf("*** PROBABLY A BUG *** " \
+  DEBUG(tls) debug_printf("*** PROBABLY A BUG *** " \
       "have an SNI set for a server [%s]\n", state->tls_sni);
 
 if (!state->lib_state.pri_string)
@@ -2255,7 +2273,7 @@ if (!state->lib_state.pri_string)
     if (state->exp_tls_require_ciphers && *state->exp_tls_require_ciphers)
       {
       p = state->exp_tls_require_ciphers;
-      DEBUG(D_tls) debug_printf("GnuTLS session cipher/priority %q\n", p);
+      DEBUG(tls) debug_printf("GnuTLS session cipher/priority %q\n", p);
       }
     }
 
@@ -2267,7 +2285,7 @@ if (!state->lib_state.pri_string)
   }
 else
   {
-  DEBUG(D_tls) debug_printf("cipher list preloaded\n");
+  DEBUG(tls) debug_printf("cipher list preloaded\n");
   state->exp_tls_require_ciphers = US state->tls_require_ciphers;
   }
 
@@ -2285,10 +2303,10 @@ decides to make that trade-off. */
 if (gnutls_compat_mode)
   {
 #if LIBGNUTLS_VERSION_NUMBER >= 0x020104
-  DEBUG(D_tls) debug_printf("lowering GnuTLS security, compatibility mode\n");
+  DEBUG(tls) debug_printf("lowering GnuTLS security, compatibility mode\n");
   gnutls_session_enable_compatibility_mode(state->session);
 #else
-  DEBUG(D_tls) debug_printf("Unable to set gnutls_compat_mode - GnuTLS version too old\n");
+  DEBUG(tls) debug_printf("Unable to set gnutls_compat_mode - GnuTLS version too old\n");
 #endif
   }
 
@@ -2359,7 +2377,6 @@ gnutls_kx_algorithm_t kx;
 gnutls_mac_algorithm_t mac;
 gnutls_certificate_type_t ct;
 gnutls_x509_crt_t crt;
-uschar * dn_buf;
 size_t sz;
 
 if (state->have_set_peerdn)
@@ -2381,16 +2398,20 @@ kx =
 old_pool = store_pool;
   {
   tls_support * tlsp = state->tlsp;
+  gstring * g = NULL;
+#ifdef EXIM_TLS_KEX_GROUP
+  const char * kex_gp = gnutls_group_get_name(gnutls_group_get(session));
+#endif
+
   store_pool = POOL_PERM;
 
 #ifdef SUPPORT_GNUTLS_SESS_DESC
     {
-    gstring * g = NULL;
     uschar * s = US gnutls_session_get_desc(session), c;
 
     if (!s)
       {
-      DEBUG(D_tls) debug_printf("TLS: session init still incomplete\n");
+      DEBUG(tls) debug_printf("TLS: session init still incomplete\n");
       store_pool = old_pool;
       return DEFER;
       }
@@ -2420,15 +2441,25 @@ old_pool = store_pool;
       if ((c = *s) && *++s == '-') g = string_catn(g, US"__", 2);
       /* now on _ between groups */
       }
-    g = string_catn(g, US":", 1);
-    g = string_cat(g, string_sprintf("%d", (int) gnutls_cipher_get_key_size(cipher) * 8));
+    g = string_fmt_append(g, ":%d", 
+		      (int) gnutls_cipher_get_key_size(cipher) * 8);
+
+# ifdef EXIM_TLS_KEX_GROUP
+    if (kex_gp)
+      g = string_fmt_append(g, ":%s", kex_gp);
+# endif
     state->ciphersuite = string_from_gstring(g);
     }
 #else
-  state->ciphersuite = string_sprintf("%s:%s:%d",
-      gnutls_protocol_get_name(protocol),
-      gnutls_cipher_suite_get_name(kx, cipher, mac),
-      (int) gnutls_cipher_get_key_size(cipher) * 8);
+  g = string_fmt_append(NULL, "%s:%s:%d",
+			gnutls_protocol_get_name(protocol),
+			gnutls_cipher_suite_get_name(kx, cipher, mac),
+			(int) gnutls_cipher_get_key_size(cipher) * 8);
+# ifdef EXIM_TLS_KEX_GROUP
+  if (kex_gp)
+    g = string_fmt_append(g, ":%s", kex_gp);
+# endif
+  state->ciphersuite = string_from_gstring(g);
 
   /* I don't see a way that spaces could occur, in the current GnuTLS
   code base, but it was a concern in the old code and perhaps older GnuTLS
@@ -2437,7 +2468,7 @@ old_pool = store_pool;
   for (uschar * p = state->ciphersuite; *p; p++) if (isspace(*p)) *p = '-';
   tlsp->ver = string_copyn(state->ciphersuite,
 			Ustrchr(state->ciphersuite, ':') - state->ciphersuite);
-#endif
+#endif /*SUPPORT_GNUTLS_SESS_DESC*/
 
 /* debug_printf("peer_status: ciphersuite %s\n", state->ciphersuite); */
 
@@ -2453,7 +2484,7 @@ cert_list = gnutls_certificate_get_peers(session, &cert_list_size);
 
 if (!cert_list || cert_list_size == 0)
   {
-  DEBUG(D_tls) debug_printf("TLS: no certificate from peer (%p & %d)\n",
+  DEBUG(tls) debug_printf("TLS: no certificate from peer (%p & %d)\n",
       cert_list, cert_list_size);
   if (state->verify_requirement >= VERIFY_REQUIRED)
     return tls_error(US"certificate verification failed",
@@ -2464,7 +2495,7 @@ if (!cert_list || cert_list_size == 0)
 if ((ct = gnutls_certificate_type_get(session)) != GNUTLS_CRT_X509)
   {
   const uschar * ctn = US gnutls_certificate_type_get_name(ct);
-  DEBUG(D_tls)
+  DEBUG(tls)
     debug_printf("TLS: peer cert not X.509 but instead %q\n", ctn);
   if (state->verify_requirement >= VERIFY_REQUIRED)
     return tls_error(US"certificate verification not possible, unhandled type",
@@ -2476,7 +2507,7 @@ if ((ct = gnutls_certificate_type_get(session)) != GNUTLS_CRT_X509)
   do { \
     if (rc != GNUTLS_E_SUCCESS) \
       { \
-      DEBUG(D_tls) debug_printf("TLS: peer cert problem: %s: %s\n", \
+      DEBUG(tls) debug_printf("TLS: peer cert problem: %s: %s\n", \
 	(Label), gnutls_strerror(rc)); \
       if (state->verify_requirement >= VERIFY_REQUIRED) \
 	return tls_error_gnu(state, (Label), rc, errstr); \
@@ -2489,18 +2520,26 @@ exim_gnutls_peer_err(US"cert 0");
 
 state->tlsp->peercert = state->peercert = crt;
 
+state->peerdn = US"";
 sz = 0;
-rc = gnutls_x509_crt_get_dn(crt, NULL, &sz);
-if (rc != GNUTLS_E_SHORT_MEMORY_BUFFER)
+if (!(rc = gnutls_x509_crt_get_dn(crt, NULL, &sz)))
+  { DEBUG(tls) debug_printf_indent("TLS: zero-length DN\n"); }
+else if (rc == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
+  { DEBUG(tls) debug_printf_indent("TLS: no DN\n"); }
+else
   {
-  exim_gnutls_peer_err(US"getting size for cert DN failed");
-  return FAIL; /* should not happen */
-  }
-dn_buf = store_get_perm(sz, GET_TAINTED);
-rc = gnutls_x509_crt_get_dn(crt, CS dn_buf, &sz);
-exim_gnutls_peer_err(US"failed to extract certificate DN [gnutls_x509_crt_get_dn(cert 0)]");
+  uschar * dn_buf;
+  if (rc != GNUTLS_E_SHORT_MEMORY_BUFFER)
+    {
+    exim_gnutls_peer_err(US"getting size for cert DN failed");
+    return FAIL; /* should not happen */
+    }
+  dn_buf = store_get_perm(sz, GET_TAINTED);
+  rc = gnutls_x509_crt_get_dn(crt, CS dn_buf, &sz);
+  exim_gnutls_peer_err(US"failed to extract certificate DN [gnutls_x509_crt_get_dn(cert 0)]");
 
-state->peerdn = dn_buf;
+  state->peerdn = dn_buf;
+  }
 
 return OK;
 #undef exim_gnutls_peer_err
@@ -2533,7 +2572,7 @@ verify_certificate(exim_gnutls_state_st * state, uschar ** errstr)
 int rc;
 uint verify;
 
-DEBUG(D_tls) debug_printf("TLS: checking peer certificate\n");
+DEBUG(tls) debug_printf("TLS: checking peer certificate\n");
 *errstr = NULL;
 rc = peer_status(state, errstr);
 
@@ -2607,7 +2646,7 @@ else
 			    ? DANE_VFLAG_ONLY_CHECK_EE_USAGE : 0,
 			    &verify)))
 	    {
-	    DEBUG(D_tls)
+	    DEBUG(tls)
 	      debug_printf("TLSA record problem: %s\n", dane_strerror(rc));
 	    }
 	  else if (verify == 0)	/* verification passed */
@@ -2684,7 +2723,7 @@ if (rc < 0 || verify & (GNUTLS_CERT_INVALID|GNUTLS_CERT_REVOKED))
   if (!*errstr)
     {
 #ifdef GNUTLS_CERT_VFY_STATUS_PRINT
-    DEBUG(D_tls)
+    DEBUG(tls)
       {
       gnutls_datum_t txt;
 
@@ -2701,13 +2740,13 @@ if (rc < 0 || verify & (GNUTLS_CERT_INVALID|GNUTLS_CERT_REVOKED))
       ? US"certificate revoked" : US"certificate invalid";
     }
 
-  DEBUG(D_tls)
+  DEBUG(tls)
     debug_printf("TLS certificate verification failed (%s): peerdn=%q\n",
         *errstr, state->peerdn ? state->peerdn : US"<unset>");
 
   if (state->verify_requirement >= VERIFY_REQUIRED)
     goto badcert;
-  DEBUG(D_tls)
+  DEBUG(tls)
     debug_printf("TLS verify failure overridden (host in tls_try_verify_hosts)\n");
   }
 
@@ -2723,7 +2762,7 @@ else
 		CS state->exp_tls_verify_cert_hostnames)
      )
     {
-    DEBUG(D_tls)
+    DEBUG(tls)
       debug_printf("TLS certificate verification failed: cert name mismatch (per GnuTLS)\n");
     if (state->verify_requirement >= VERIFY_REQUIRED)
       goto badcert;
@@ -2731,7 +2770,7 @@ else
     }
 
   state->peer_cert_verified = TRUE;
-  DEBUG(D_tls) debug_printf("TLS certificate verified: peerdn=%q\n",
+  DEBUG(tls) debug_printf("TLS certificate verified: peerdn=%q\n",
       state->peerdn ? state->peerdn : US"<unset>");
   }
 
@@ -2767,10 +2806,10 @@ 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);
+    DEBUG(tls) debug_printf("GnuTLS<%d> empty debug message\n", level);
     return;
     }
-  DEBUG(D_tls) debug_printf("GnuTLS<%d>: %s%s", level, message,
+  DEBUG(tls) debug_printf("GnuTLS<%d>: %s%s", level, message,
       message[len-1] == '\n' ? "" : "\n");
 }
 #endif
@@ -2799,13 +2838,13 @@ char sni_name[MAX_HOST_LEN];
 size_t data_len = MAX_HOST_LEN;
 exim_gnutls_state_st *state = &state_server;
 unsigned int sni_type;
-int rc, old_pool;
+int rc;
 uschar * dummy_errstr;
 
 rc = gnutls_server_name_get(session, sni_name, &data_len, &sni_type, 0);
 if (rc != GNUTLS_E_SUCCESS)
   {
-  DEBUG(D_tls)
+  DEBUG(tls)
     if (rc == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
       debug_printf("TLS: no SNI presented in handshake\n");
     else
@@ -2816,20 +2855,17 @@ if (rc != GNUTLS_E_SUCCESS)
 
 if (sni_type != GNUTLS_NAME_DNS)
   {
-  DEBUG(D_tls) debug_printf("TLS: ignoring SNI of unhandled type %u\n", sni_type);
+  DEBUG(tls) debug_printf("TLS: ignoring SNI of unhandled type %u\n", sni_type);
   return 0;
   }
 
 /* We now have a UTF-8 string in sni_name */
-old_pool = store_pool;
-store_pool = POOL_PERM;
-state->received_sni = string_copy_taint(US sni_name, GET_TAINTED);
-store_pool = old_pool;
+state->received_sni = string_copy_perm(US sni_name, TRUE);
 
 /* We set this one now so that variable expansions below will work */
 state->tlsp->sni = state->received_sni;
 
-DEBUG(D_tls) debug_printf("Received TLS SNI %q%s\n", sni_name,
+DEBUG(tls) debug_printf("Received TLS SNI %q%s\n", sni_name,
     state->trigger_sni_changes ? "" : " (unused for certificate selection)");
 
 if (!state->trigger_sni_changes)
@@ -2839,7 +2875,7 @@ 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. */
-  DEBUG(D_tls) debug_printf("expansion for SNI-dependent session files failed\n");
+  DEBUG(tls) debug_printf("expansion for SNI-dependent session files failed\n");
   return GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE;
   }
 
@@ -2876,7 +2912,7 @@ if ((cert_list = gnutls_certificate_get_peers(session, &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",
+      DEBUG(tls) debug_printf("TLS: peer cert problem: depth %d: %s\n",
 	cert_list_size, gnutls_strerror(rc));
       break;
       }
@@ -2885,7 +2921,7 @@ if ((cert_list = gnutls_certificate_get_peers(session, &cert_list_size)))
     if ((yield = event_raise(state->event_action,
 		US"tls:cert", string_sprintf("%d", cert_list_size), &errno)))
       {
-      log_write(0, LOG_MAIN,
+      log_write(LOG_MAIN,
 		"SSL verify denied by event-action: depth=%d: %s",
 		cert_list_size, yield);
       return 1;                     /* reject */
@@ -2953,7 +2989,7 @@ static int
 tls_server_ticket_cb(gnutls_session_t sess, u_int htype, unsigned when,
   unsigned incoming, const gnutls_datum_t * msg)
 {
-DEBUG(D_tls) debug_printf("newticket cb (on server)\n");
+DEBUG(tls) debug_printf("newticket cb (on server)\n");
 tls_in.resumption |= RESUME_CLIENT_REQUESTED;
 return 0;
 }
@@ -2976,7 +3012,7 @@ if (verify_check_host(&tls_resumption_hosts) == OK)
 	      &server_sessticket_key)))
     tls_in.resumption |= RESUME_SERVER_TICKET;
   else
-    DEBUG(D_tls)
+    DEBUG(tls)
       debug_printf("enabling session tickets: %s\n", US gnutls_strerror(rc));
 
   /* Try to tell if we see a ticket request */
@@ -2998,12 +3034,12 @@ if (gnutls_session_resumption_requested(state->session))
   in the Client Hello, for 1.2) which mucks up our logic. */
 
   tls_in.resumption |= RESUME_CLIENT_SUGGESTED;
-  DEBUG(D_tls) debug_printf("client requested resumption\n");
+  DEBUG(tls) debug_printf("client requested resumption\n");
   }
 if (gnutls_session_is_resumed(state->session))
   {
   tls_in.resumption |= RESUME_USED;
-  DEBUG(D_tls) debug_printf("Session resumed\n");
+  DEBUG(tls) debug_printf("Session resumed\n");
   }
 }
 #endif	/* EXIM_HAVE_TLS_RESUME */
@@ -3025,7 +3061,7 @@ if (!expand_check(*tls_alpn, US"tls_alpn", &exp_alpn, errstr))
 
 if (!exp_alpn)
   {
-  DEBUG(D_tls) debug_printf("Setting TLS ALPN forced to fail, not sending\n");
+  DEBUG(tls) debug_printf("Setting TLS ALPN forced to fail, not sending\n");
   *plist = NULL;
   }
 else
@@ -3066,7 +3102,7 @@ if (tls_alpn_plist(&local_alpn, &plist, &plen, errstr) && plist)
     gnutls_handshake_set_hook_function(state->session,
       GNUTLS_HANDSHAKE_ANY, GNUTLS_HOOK_POST, tls_server_hook_cb);
   else
-    DEBUG(D_tls)
+    DEBUG(tls)
       debug_printf("setting alpn protocols: %s\n", US gnutls_strerror(rc));
   }
 }
@@ -3117,19 +3153,19 @@ if (tls_in.active.sock >= 0)
 
 if (verify_check_host(&tls_verify_hosts) == OK)
   {
-  DEBUG(D_tls)
+  DEBUG(tls)
     debug_printf("TLS: a client certificate will be required\n");
   state_server.verify_requirement = VERIFY_REQUIRED;
   }
 else if (verify_check_host(&tls_try_verify_hosts) == OK)
   {
-  DEBUG(D_tls)
+  DEBUG(tls)
     debug_printf("TLS: a client certificate will be requested but not required\n");
   state_server.verify_requirement = VERIFY_OPTIONAL;
   }
 else
   {
-  DEBUG(D_tls)
+  DEBUG(tls)
     debug_printf("TLS: a client certificate will not be requested\n");
   state_server.verify_requirement = VERIFY_NONE;
   }
@@ -3137,7 +3173,7 @@ else
 /* Initialize the library. If it fails, it will already have logged the error
 and sent an SMTP response. */
 
-DEBUG(D_tls) debug_printf("initialising GnuTLS as a server\n");
+DEBUG(tls) debug_printf("initialising GnuTLS as a server\n");
 
   {
 #ifdef MEASURE_TIMING
@@ -3218,7 +3254,7 @@ ALARM_CLR(0);
 
 if (rc != GNUTLS_E_SUCCESS)
   {
-  DEBUG(D_tls) debug_printf(" error %d from gnutls_handshake: %s\n",
+  DEBUG(tls) debug_printf(" error %d from gnutls_handshake: %s\n",
     rc, gnutls_strerror(rc));
 
   /* It seems that, except in the case of a timeout, we have to close the
@@ -3259,11 +3295,11 @@ if (  gnutls_protocol_get_version(state->session) > GNUTLS_TLS1_2
    && state->early_banner)
   {
   tls_write(NULL, banner->s, banner->ptr, SP_NO_MORE);
-  DEBUG(D_receive)
+  DEBUG(receive)
     { gstring_trim(banner, 2); debug_printf("SMTP>> %Y\n", banner); }
   gstring_reset(banner);
 
-  DEBUG(D_tls) debug_printf("TLS: wait for handshake complete\n");
+  DEBUG(tls) debug_printf("TLS: wait for handshake complete\n");
   tls_refill(GETC_BUFFER_UNLIMITED);
   }
 #endif
@@ -3277,12 +3313,12 @@ if (gnutls_session_get_flags(state->session) & GNUTLS_SFLAGS_EXT_MASTER_SECRET)
 tls_server_resume_posthandshake(state);
 #endif
 
-DEBUG(D_tls) post_handshake_debug(state);
+DEBUG(tls) post_handshake_debug(state);
 
 #ifdef EXIM_HAVE_ALPN
 if (server_seen_alpn > 0)
   {
-  DEBUG(D_tls)
+  DEBUG(tls)
     {		/* The client offered ALPN.  See what was negotiated. */
     gnutls_datum_t p = {.size = 0};
     int rc = gnutls_alpn_get_selected_protocol(state->session, &p);
@@ -3301,9 +3337,9 @@ else if (server_seen_alpn == 0)
     return FAIL;
     }
   else
-    DEBUG(D_tls) debug_printf("TLS: no ALPN presented in handshake\n");
+    DEBUG(tls) debug_printf("TLS: no ALPN presented in handshake\n");
 else
-  DEBUG(D_tls) debug_printf("TLS: was not watching for ALPN\n");
+  DEBUG(tls) debug_printf("TLS: was not watching for ALPN\n");
 #endif
 
 /* Verify after the fact */
@@ -3315,7 +3351,7 @@ if (!verify_certificate(state, errstr))
     (void) tls_error(US"certificate verification failed", *errstr, NULL, errstr);
     return FAIL;
     }
-  DEBUG(D_tls)
+  DEBUG(tls)
     debug_printf("TLS: continuing on only because verification was optional, after: %s\n",
 	*errstr);
   }
@@ -3327,13 +3363,8 @@ extract_exim_vars_from_tls_state(state);
 /* TLS has been set up. Adjust the input functions to read via TLS,
 and initialize appropriately. */
 
-receive_getc = tls_getc;
-receive_getbuf = tls_getbuf;
-receive_get_cache = tls_get_cache;
-receive_hasc = tls_hasc;
-receive_ungetc = tls_ungetc;
-receive_feof = tls_feof;
-receive_ferror = tls_ferror;
+rx_prc = tls_push_receive_functions(rx_prc);
+DEBUG(receive) debug_print_processing_stack();
 
 return OK;
 }
@@ -3353,7 +3384,7 @@ if (verify_check_given_host(CUSS &ob->tls_verify_cert_hostnames, host) == OK)
 #else
     host->certname;
 #endif
-  DEBUG(D_tls)
+  DEBUG(tls)
     debug_printf("TLS: server cert verification includes hostname: %q\n",
 		    state->exp_tls_verify_cert_hostnames);
   }
@@ -3364,7 +3395,7 @@ if (verify_check_given_host(CUSS &ob->tls_verify_cert_hostnames, host) == OK)
 
 #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
+GnuTLS-DANE's preferred format.  Hang it on the state struct for later
 use in DANE verification.
 
 We point at the dnsa data not copy it, so it must remain valid until
@@ -3373,7 +3404,7 @@ after verification is done.*/
 static BOOL
 dane_tlsa_load(exim_gnutls_state_st * state, const dns_answer * dnsa)
 {
-dns_scan dnss;
+dns_scan dnss = {0};
 int i;
 const char **	dane_data;
 int *		dane_data_len;
@@ -3392,10 +3423,9 @@ for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
     ) if (rr->type == T_TLSA && rr->size > 3)
   {
   const uschar * p = rr->data;
-/*XXX need somehow to mark rr and its data as tainted.  Doues this mean copying it? */
   uint8_t usage = p[0], sel = p[1], type = p[2];
 
-  DEBUG(D_tls)
+  DEBUG(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)
@@ -3447,7 +3477,7 @@ tls_retrieve_session(tls_support * tlsp, gnutls_session_t session,
 tlsp->resumption = RESUME_SUPPORTED;
 
 if (!conn_args->have_lbserver)
-  { DEBUG(D_tls) debug_printf(
+  { DEBUG(tls) debug_printf(
       "resumption not supported: no LB detection done (continued-conn?)\n"); }
 else if (verify_check_given_host(CUSS &ob->tls_resumption_hosts, conn_args->host) == OK)
   {
@@ -3468,15 +3498,15 @@ else if (verify_check_given_host(CUSS &ob->tls_resumption_hosts, conn_args->host
       if (!(rc = gnutls_session_set_data(session,
 		    CUS dt->session, (size_t)len - sizeof(dbdata_tls_session))))
 	{
-	DEBUG(D_tls) debug_printf("good session\n");
+	DEBUG(tls) debug_printf("good session\n");
 	tlsp->resumption |= RESUME_CLIENT_SUGGESTED;
 	}
-      else DEBUG(D_tls) debug_printf("setting session resumption data: %s\n",
+      else DEBUG(tls) debug_printf("setting session resumption data: %s\n",
 	    US gnutls_strerror(rc));
     dbfn_close(dbm_file);
     }
   }
-else DEBUG(D_tls) debug_printf("no resumption for this host\n");
+else DEBUG(tls) debug_printf("no resumption for this host\n");
 }
 
 
@@ -3493,7 +3523,7 @@ if (gnutls_session_get_flags(session) & GNUTLS_SFLAGS_SESSION_TICKET)
   gnutls_datum_t tkt;
   int rc;
 
-  DEBUG(D_tls) debug_printf("server offered session ticket\n");
+  DEBUG(tls) debug_printf("server offered session ticket\n");
   tlsp->ticket_received = TRUE;
   tlsp->resumption |= RESUME_SERVER_TICKET;
 
@@ -3504,7 +3534,7 @@ if (gnutls_session_get_flags(session) & GNUTLS_SFLAGS_SESSION_TICKET)
       int dlen = sizeof(dbdata_tls_session) + tkt.size;
       dbdata_tls_session * dt = store_get(dlen, GET_TAINTED);
 
-      DEBUG(D_tls) debug_printf(" session data size %u\n", (unsigned)tkt.size);
+      DEBUG(tls) debug_printf(" session data size %u\n", (unsigned)tkt.size);
       memcpy(dt->session, tkt.data, tkt.size);
       gnutls_free(tkt.data);
 
@@ -3514,16 +3544,16 @@ if (gnutls_session_get_flags(session) & GNUTLS_SFLAGS_SESSION_TICKET)
 	dbfn_write(dbm_file, tlsp->resume_index, dt, dlen);
 	dbfn_close(dbm_file);
 
-	DEBUG(D_tls)
+	DEBUG(tls)
 	  debug_printf(" wrote session db (len %u)\n", (unsigned)dlen);
 	}
       }
     else
-      { DEBUG(D_tls)
+      { DEBUG(tls)
       debug_printf(" extract session data: %s\n", US gnutls_strerror(rc));
       }
-  else DEBUG(D_tls)
-      debug_printf(" host not resmable; not saving ticket\n");
+  else DEBUG(tls)
+      debug_printf(" host not resumable; not saving ticket\n");
   }
 }
 
@@ -3540,7 +3570,7 @@ tls_client_ticket_cb(gnutls_session_t sess, u_int htype, unsigned when,
 exim_gnutls_state_st * state = gnutls_session_get_ptr(sess);
 tls_support * tlsp = state->tlsp;
 
-DEBUG(D_tls) debug_printf("newticket cb (on client)\n");
+DEBUG(tls) debug_printf("newticket cb (on client)\n");
 
 if (!tlsp->ticket_received)
   tls_save_session(tlsp, sess, state->host);
@@ -3566,7 +3596,7 @@ tls_client_resume_posthandshake(exim_gnutls_state_st * state,
 {
 if (gnutls_session_is_resumed(state->session))
   {
-  DEBUG(D_tls) debug_printf("Session resumed\n");
+  DEBUG(tls) debug_printf("Session resumed\n");
   tlsp->resumption |= RESUME_USED;
   }
 
@@ -3613,7 +3643,7 @@ BOOL request_ocsp = require_ocsp ? TRUE
   : 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", cctx->sock);
+DEBUG(tls) debug_printf("initialising GnuTLS as a client on fd %d\n", cctx->sock);
 
 #ifdef SUPPORT_DANE
 /* If dane is flagged, have either request or require dane for this host, and
@@ -3664,10 +3694,10 @@ if (ob->tls_alpn)
       return FALSE;
       }
     else
-      DEBUG(D_tls) debug_printf("Setting TLS ALPN '%s'\n", ob->tls_alpn);
+      DEBUG(tls) debug_printf("Setting TLS ALPN '%s'\n", ob->tls_alpn);
   }
 #else
-  log_write(0, LOG_MAIN,
+  log_write(LOG_MAIN,
 	  "ALPN unusable with this GnuTLS library version; ignoring %q\n",
           ob->tls_alpn);
 #endif
@@ -3676,14 +3706,14 @@ if (ob->tls_alpn)
   int dh_min_bits = ob->tls_dh_min_bits;
   if (dh_min_bits < EXIM_CLIENT_DH_MIN_MIN_BITS)
     {
-    DEBUG(D_tls)
+    DEBUG(tls)
       debug_printf("WARNING: tls_dh_min_bits far too low,"
 		    " clamping %d up to %d\n",
 	  dh_min_bits, EXIM_CLIENT_DH_MIN_MIN_BITS);
     dh_min_bits = EXIM_CLIENT_DH_MIN_MIN_BITS;
     }
 
-  DEBUG(D_tls) debug_printf("Setting D-H prime minimum"
+  DEBUG(tls) debug_printf("Setting D-H prime minimum"
 		    " acceptable bits to %d\n",
       dh_min_bits);
   gnutls_dh_set_prime_bits(state->session, dh_min_bits);
@@ -3694,9 +3724,9 @@ 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 */
 
 #ifdef SUPPORT_DANE
-if (conn_args->dane && dane_tlsa_load(state, &conn_args->tlsa_dnsa))
+if (conn_args->dane && dane_tlsa_load(state, conn_args->tlsa_dnsa))
   {
-  DEBUG(D_tls)
+  DEBUG(tls)
     debug_printf("TLS: server certificate DANE required\n");
   state->verify_requirement = VERIFY_DANE;
   gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUIRE);
@@ -3711,7 +3741,7 @@ else
        )
   {
   tls_client_setup_hostname_checks(host, state, ob);
-  DEBUG(D_tls)
+  DEBUG(tls)
     debug_printf("TLS: server certificate verification required\n");
   state->verify_requirement = VERIFY_REQUIRED;
   gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUIRE);
@@ -3719,14 +3749,14 @@ else
 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(tls)
     debug_printf("TLS: server certificate verification optional\n");
   state->verify_requirement = VERIFY_OPTIONAL;
   gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUEST);
   }
 else
   {
-  DEBUG(D_tls)
+  DEBUG(tls)
     debug_printf("TLS: server certificate verification not required\n");
   state->verify_requirement = VERIFY_NONE;
   gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_IGNORE);
@@ -3736,7 +3766,7 @@ else
 			/* supported since GnuTLS 3.1.3 */
 if (request_ocsp)
   {
-  DEBUG(D_tls) debug_printf("TLS: will request OCSP stapling\n");
+  DEBUG(tls) debug_printf("TLS: will request OCSP stapling\n");
   if ((rc = gnutls_ocsp_status_request_enable_client(state->session,
 		    NULL, 0, NULL)) != OK)
     {
@@ -3764,7 +3794,7 @@ gnutls_transport_set_ptr(state->session, (gnutls_transport_ptr_t)(long) cctx->so
 state->fd_in = cctx->sock;
 state->fd_out = cctx->sock;
 
-DEBUG(D_tls) debug_printf("about to gnutls_handshake\n");
+DEBUG(tls) debug_printf("about to gnutls_handshake\n");
 /* There doesn't seem to be a built-in timeout on connection. */
 
 sigalrm_seen = FALSE;
@@ -3786,7 +3816,7 @@ if (rc != GNUTLS_E_SUCCESS)
   return FALSE;
   }
 
-DEBUG(D_tls) post_handshake_debug(state);
+DEBUG(tls) post_handshake_debug(state);
 
 /* Verify late */
 
@@ -3804,7 +3834,7 @@ if (gnutls_session_get_flags(state->session) & GNUTLS_SFLAGS_EXT_MASTER_SECRET)
 #ifndef DISABLE_OCSP
 if (request_ocsp)
   {
-  DEBUG(D_tls)
+  DEBUG(tls)
     {
     gnutls_datum_t stapling;
     gnutls_ocsp_resp_t resp;
@@ -3841,7 +3871,7 @@ if (request_ocsp)
     }
   else
     {
-    DEBUG(D_tls) debug_printf("Passed OCSP checking\n");
+    DEBUG(tls) debug_printf("Passed OCSP checking\n");
     tlsp->ocsp = OCSP_VFIED;
     }
   }
@@ -3857,7 +3887,7 @@ if (ob->tls_alpn)	/* We requested. See what was negotiated. */
   gnutls_datum_t p = {.size = 0};
 
   if (gnutls_alpn_get_selected_protocol(state->session, &p) == 0)
-    { DEBUG(D_tls) debug_printf("ALPN negotiated: '%.*s'\n", (int)p.size, p.data); }
+    { DEBUG(tls) debug_printf("ALPN negotiated: '%.*s'\n", (int)p.size, p.data); }
   else if (verify_check_given_host(CUSS &ob->hosts_require_alpn, host) == OK)
     {
     gnutls_alert_send(state->session, GNUTLS_AL_FATAL, GNUTLS_A_NO_APPLICATION_PROTOCOL);
@@ -3865,7 +3895,7 @@ if (ob->tls_alpn)	/* We requested. See what was negotiated. */
     return FALSE;
     }
   else
-    DEBUG(D_tls) debug_printf("No ALPN negotiated");
+    DEBUG(tls) debug_printf("No ALPN negotiated");
   }
 #endif
 
@@ -3895,7 +3925,7 @@ if (!tlsp || tlsp->active.sock < 0) return;  /* TLS was not active */
 
 tls_write(ct_ctx, NULL, 0, FALSE);	/* flush write buffer */
 
-HDEBUG(D_transport|D_tls|D_acl|D_v) debug_printf_indent("  SMTP(TLS shutdown)>>\n");
+HDEBUG(transport|tls|acl|v) debug_printf_indent("  SMTP(TLS shutdown)>>\n");
 gnutls_bye(state->session, GNUTLS_SHUT_WR);
 }
 
@@ -3926,7 +3956,7 @@ if (!tlsp || tlsp->active.sock < 0) return;  /* TLS was not active */
 
 if (do_shutdown)
   {
-  DEBUG(D_tls) debug_printf("tls_close(): shutting down TLS%s\n",
+  DEBUG(tls) debug_printf("tls_close(): shutting down TLS%s\n",
     do_shutdown > TLS_SHUTDOWN_NOWAIT ? " (with response-wait)" : "");
 
   tls_write(ct_ctx, NULL, 0, FALSE);	/* flush write buffer */
@@ -3952,15 +3982,7 @@ if (do_shutdown)
   }
 
 if (!ct_ctx)	/* server */
-  {
-  receive_getc =	smtp_getc;
-  receive_getbuf =	smtp_getbuf;
-  receive_get_cache =	smtp_get_cache;
-  receive_hasc =	smtp_hasc;
-  receive_ungetc =	smtp_ungetc;
-  receive_feof =	smtp_feof;
-  receive_ferror =	smtp_ferror;
-  }
+  tls_receive_done();
 
 gnutls_deinit(state->session);
 tlsp->active.sock = -1;
@@ -3980,9 +4002,14 @@ if (state->xfer_buffer) store_free(state->xfer_buffer);
 *************************************************/
 
 /* This gets the next byte from the TLS input buffer. If the buffer is empty,
-it refills the buffer via the GnuTLS reading function.
-Only used by the server-side TLS.
+it refills the buffer via the SSL reading function.
+If that errors - or the TLS inbound has been closed and we are receiving a
+mail message - return EOF.  For the closed case while between messages, drop
+to the cleartext layer getc; this supports continued-connection receive when
+the sender cannot maintain a TLS session (for Exim, when configured to not
+use a tls-proxy process).
 
+Only used by the server-side TLS.
 This feeds DKIM and should be used for all message-body reads.
 
 Arguments:  lim		Maximum amount to read/buffer
@@ -3990,13 +4017,14 @@ Returns:    the next character or EOF
 */
 
 int
-tls_getc(unsigned lim)
+tls_getc(const const in_processing * inp, unsigned lim)
 {
 exim_gnutls_state_st * state = &state_server;
 
 if (state->xfer_buffer_lwm >= state->xfer_buffer_hwm)
   if (!tls_refill(lim))
-    return state->xfer_error ? EOF : smtp_getc(lim);
+    return state->xfer_error || message_id[0]
+      ? EOF : inp_getc(inp->in_lower, lim);
 
 /* Something in the buffer; return next uschar */
 
@@ -4004,23 +4032,24 @@ return state->xfer_buffer[state->xfer_buffer_lwm++];
 }
 
 BOOL
-tls_hasc(void)
+tls_hasc(const in_processing * inp)
 {
 const exim_gnutls_state_st * state = &state_server;
 return state->xfer_buffer_lwm < state->xfer_buffer_hwm;
 }
 
-uschar *
-tls_getbuf(unsigned * len)
+const uschar *
+tls_getbuf(const in_processing * inp, unsigned * len)
 {
 exim_gnutls_state_st * state = &state_server;
 unsigned size;
-uschar * buf;
+const uschar * buf;
 
 if (state->xfer_buffer_lwm >= state->xfer_buffer_hwm)
   if (!tls_refill(*len))
     {
-    if (!state->xfer_error) return smtp_getbuf(len);
+    if (!state->xfer_error && !message_id[0])
+      return inp_getbuf(inp->in_lower, len);
     *len = 0;
     return NULL;
     }
@@ -4036,7 +4065,7 @@ return buf;
 
 /* Get up to the given number of bytes from any cached data, and feed to dkim. */
 void
-tls_get_cache(unsigned lim)
+tls_get_cache(const in_processing * inp, unsigned lim)
 {
 #ifndef DISABLE_DKIM
 exim_gnutls_state_st * state = &state_server;
@@ -4083,12 +4112,12 @@ if (len > INT_MAX)
   len = INT_MAX;
 
 if (state->xfer_buffer_lwm < state->xfer_buffer_hwm)
-  DEBUG(D_tls)
+  DEBUG(tls)
     debug_printf("*** PROBABLY A BUG *** " \
         "tls_read() called with data in the tls_getc() buffer, %d ignored\n",
         state->xfer_buffer_hwm - state->xfer_buffer_lwm);
 
-DEBUG(D_tls)
+DEBUG(tls)
   debug_printf("Calling gnutls_record_recv(session=%p, buffer=%p, len=" SIZE_T_FMT ")\n",
       state->session, buff, len);
 
@@ -4100,11 +4129,11 @@ while (inbytes == GNUTLS_E_AGAIN);
 if (inbytes > 0) return inbytes;
 if (inbytes == 0)
   {
-  DEBUG(D_tls) debug_printf("Got TLS_EOF\n");
+  DEBUG(tls) debug_printf("Got TLS_EOF\n");
   }
 else
   {
-  DEBUG(D_tls) debug_printf("%s: err from gnutls_record_recv\n", __FUNCTION__);
+  DEBUG(tls) debug_printf("%s: err from gnutls_record_recv\n", __FUNCTION__);
   record_io_error(state, (int)inbytes, US"recv", NULL);
   }
 
@@ -4142,18 +4171,18 @@ exim_gnutls_state_st * state = ct_ctx ? ct_ctx : &state_server;
 #ifdef SUPPORT_CORK
 if (more && !state->corked)
   {
-  DEBUG(D_tls) debug_printf("gnutls_record_cork(session=%p)\n", state->session);
+  DEBUG(tls) debug_printf("gnutls_record_cork(session=%p)\n", state->session);
   gnutls_record_cork(state->session);
   state->corked = TRUE;
   }
 #endif
 
-DEBUG(D_tls) debug_printf("%s(%p, " SIZE_T_FMT "%s)\n", __FUNCTION__,
+DEBUG(tls) debug_printf("%s(%p, " SIZE_T_FMT "%s)\n", __FUNCTION__,
   buff, left, more ? ", more" : "");
 
 while (left > 0)
   {
-  DEBUG(D_tls) debug_printf("gnutls_record_send(session=%p, buffer=%p, left=" SIZE_T_FMT ")\n",
+  DEBUG(tls) debug_printf("gnutls_record_send(session=%p, buffer=%p, left=" SIZE_T_FMT ")\n",
       state->session, buff, left);
 
   errno = 0;
@@ -4161,7 +4190,7 @@ while (left > 0)
     outbytes = gnutls_record_send(state->session, buff, left);
   while (outbytes == GNUTLS_E_AGAIN);
 
-  DEBUG(D_tls) debug_printf("outbytes=" SSIZE_T_FMT "\n", outbytes);
+  DEBUG(tls) debug_printf("outbytes=" SSIZE_T_FMT "\n", outbytes);
 
   if (outbytes < 0)
     {
@@ -4171,16 +4200,16 @@ while (left > 0)
        )
       {					/* Outlook, dammit */
       if (LOGGING(protocol_detail))
-	log_write(0, LOG_MAIN, "[%s] after QUIT, client reset TCP before"
+	log_write(LOG_MAIN, "[%s] after QUIT, client reset TCP before"
 	  " SMTP response and TLS close\n", sender_host_address);
       else
-	DEBUG(D_tls) debug_printf("[%s] SSL_write: after QUIT,"
+	DEBUG(tls) debug_printf("[%s] SSL_write: after QUIT,"
 	  " client reset TCP before TLS close\n", sender_host_address);
       }
     else
 #endif
       {
-      DEBUG(D_tls) debug_printf("%s: gnutls_record_send err\n", __FUNCTION__);
+      DEBUG(tls) debug_printf("%s: gnutls_record_send err\n", __FUNCTION__);
       record_io_error(state, outbytes, US"send", NULL);
       }
     return -1;
@@ -4197,7 +4226,7 @@ while (left > 0)
 
 if (len > INT_MAX)
   {
-  DEBUG(D_tls)
+  DEBUG(tls)
     debug_printf("Whoops!  Wrote more bytes (" SIZE_T_FMT ") than INT_MAX\n",
         len);
   len = INT_MAX;
@@ -4206,7 +4235,7 @@ if (len > INT_MAX)
 #ifdef SUPPORT_CORK
 if (!more && state->corked)
   {
-  DEBUG(D_tls) debug_printf("gnutls_record_uncork(session=%p)\n", state->session);
+  DEBUG(tls) debug_printf("gnutls_record_uncork(session=%p)\n", state->session);
   do
     /* We can't use GNUTLS_RECORD_WAIT here, as it retries on
     GNUTLS_E_AGAIN || GNUTLS_E_INTR, which would break our timeout set by alarm().
@@ -4271,7 +4300,7 @@ if (i < needed_len)
 i = gnutls_rnd(GNUTLS_RND_NONCE, smallbuf, needed_len);
 if (i < 0)
   {
-  DEBUG(D_all) debug_printf("gnutls_rnd() failed, using fallback\n");
+  DEBUG(all) debug_printf("gnutls_rnd() failed, using fallback\n");
   return vaguely_random_number_fallback(max);
   }
 r = 0;
@@ -4325,7 +4354,7 @@ uschar * dummy_errstr;
 #endif
 
 if (exim_gnutls_base_init_done)
-  log_write(0, LOG_MAIN|LOG_PANIC,
+  log_write(LOG_MAIN|LOG_PANIC,
       "already initialised GnuTLS, Exim developer bug");
 
 #if defined(HAVE_GNUTLS_PKCS11) && !defined(GNUTLS_AUTO_PKCS11_MANUAL)
@@ -4351,7 +4380,7 @@ if (!expand_check(tls_require_ciphers, US"tls_require_ciphers", &expciphers,
 if (!(expciphers && *expciphers))
   return_deinit(NULL);
 
-DEBUG(D_tls)
+DEBUG(tls)
   debug_printf("tls_require_ciphers expands to %q\n", expciphers);
 
 rc = gnutls_priority_init(&priority_cache, CS expciphers, &errpos);
@@ -4379,8 +4408,9 @@ host_item * h;
 int old_pool = store_pool;
 
 store_pool = POOL_PERM;
-state = store_get(sizeof(exim_gnutls_state_st), GET_UNTAINTED);
-h = store_get(sizeof(host_item), GET_UNTAINTED);
+state = store_get(sizeof(exim_gnutls_state_st) + sizeof(host_item),
+		  GET_UNTAINTED);
+h = ((host_item *)state) + 1;
 
 memset(h, 0, sizeof(host_item));
 h->name = h->address = string_copy(ipaddr);
@@ -4412,12 +4442,8 @@ store_pool = old_pool;
 void
 tls_state_out_to_in(int newfd, const uschar * ipaddr, int port)
 {
-host_item * h;
-int old_pool = store_pool;
+host_item * h = store_get_perm(sizeof(host_item), GET_UNTAINTED);
 
-store_pool = POOL_PERM;
-h = store_get(sizeof(host_item), GET_UNTAINTED);
-store_pool = old_pool;
 memset(h, 0, sizeof(host_item));
 h->name = h->address = string_copy(ipaddr);
 h->port = port;
diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c
index 9c896ca5b..41292708e 100644
--- a/src/src/tls-openssl.c
+++ b/src/src/tls-openssl.c
@@ -86,6 +86,9 @@ change this guard and punt the issue for a while longer. */
 # if OPENSSL_VERSION_NUMBER >= 0x010101000L
 #  define EXIM_TLS_EARLY_BANNER
 # endif
+# if OPENSSL_VERSION_NUMBER >= 0x030200000L
+#  define EXIM_TLS_KEX_GROUP
+# endif
 # if OPENSSL_VERSION_NUMBER <  0x030200020L
 #  define EXIM_OPENSSL_BOGUS_SERVER_ALPN	/*XXX when was this fixed? */
 # endif
@@ -503,12 +506,28 @@ if (!msg)
   }
 
 msg = string_sprintf("(%s): %s", prefix, msg);
-DEBUG(D_tls) debug_printf("TLS error '%s'\n", msg);
+DEBUG(tls) debug_printf("TLS error '%s'\n", msg);
 if (errstr) *errstr = msg;
 return host ? FAIL : DEFER;
 }
 
 
+static void
+tls_debug_err(SSL * ssl, uschar * where, int rc)
+{
+BIO * bio = BIO_new(BIO_s_mem());
+char * buf = NULL;
+size_t len;
+int error = SSL_get_error(ssl, rc);
+
+ERR_error_string_n(ERR_peek_error(), ssl_errstring, sizeof(ssl_errstring));
+ERR_print_errors(bio);
+len = BIO_get_mem_data(bio, &buf);
+debug_printf("%s: error %d\n%.*s\n", where, error, (int)len, buf);
+BIO_free (bio);
+}
+
+
 
 /**************************************************
 * General library initalisation                   *
@@ -594,7 +613,7 @@ else
   {
   if (Ustrcmp(dhexpanded, "none") == 0)
     {
-    DEBUG(D_tls) debug_printf("Requested no DH parameters.\n");
+    DEBUG(tls) debug_printf("Requested no DH parameters.\n");
     return TRUE;
     }
 
@@ -653,19 +672,19 @@ if (dh_bitsize <= tls_dh_max_bits)
       == 0)
     {
     ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring));
-    log_write(0, LOG_MAIN|LOG_PANIC, "TLS error (D-H param setting '%s'): %s",
+    log_write(LOG_MAIN|LOG_PANIC, "TLS error (D-H param setting '%s'): %s",
 	dhexpanded ? dhexpanded : US"default", ssl_errstring);
 #if OPENSSL_VERSION_NUMBER >= 0x30000000L
     /* EVP_PKEY_free(pkey);  crashes */
 #endif
     }
   else
-    DEBUG(D_tls)
+    DEBUG(tls)
       debug_printf(" Diffie-Hellman initialized from %s with %d-bit prime\n",
 	dhexpanded ? dhexpanded : US"default", dh_bitsize);
   }
 else
-  DEBUG(D_tls)
+  DEBUG(tls)
     debug_printf(" dhparams '%s' %d bits, is > tls_dh_max_bits limit of %d\n",
 	dhexpanded ? dhexpanded : US"default", dh_bitsize, tls_dh_max_bits);
 
@@ -698,21 +717,21 @@ static uschar *
 init_ecdh_auto(const SSL_CTX * sctx)
 {
 #if OPENSSL_VERSION_NUMBER < 0x10002000L
-DEBUG(D_tls) debug_printf(
+DEBUG(tls) debug_printf(
   " ECDH OpenSSL < 1.0.2: temp key parameter settings: overriding \"auto\" with \"prime256v1\"\n");
 return US"prime256v1";
 
 #else
 # if defined SSL_CTRL_SET_ECDH_AUTO
 
-DEBUG(D_tls) debug_printf(
+DEBUG(tls) debug_printf(
   " ECDH OpenSSL 1.0.2+: temp key parameter settings: autoselection\n");
 SSL_CTX_set_ecdh_auto(sctx, 1);
 return NULL;
 
 # else
 
-DEBUG(D_tls) debug_printf(
+DEBUG(tls) debug_printf(
   " ECDH OpenSSL 1.1.0+: temp key parameter settings: library default selection\n");
 return NULL;
 
@@ -773,7 +792,7 @@ for (ngroups = 0;
     )
   if (Ustrcmp(curve, "auto") == 0)
     {
-    DEBUG(D_tls) if (ngroups > 0)
+    DEBUG(tls) if (ngroups > 0)
       debug_printf(" tls_eccurve 'auto' item takes precedence\n");
     if ((exp_curve = init_ecdh_auto(sctx))) break; /* have a curve name to set */
     return TRUE;				   /* all done */
@@ -793,7 +812,7 @@ for (ngroups = 0; curve = string_nextinlist(&curves_list, &sep, NULL, 0);
      )
     {
     uschar * s = string_sprintf("Unknown curve name in tls_eccurve '%s'", curve);
-    DEBUG(D_tls) debug_printf("TLS error: %s\n", s);
+    DEBUG(tls) debug_printf("TLS error: %s\n", s);
     if (errstr) *errstr = s;
     return FALSE;
     }
@@ -804,7 +823,7 @@ for (ngroups = 0; curve = string_nextinlist(&curves_list, &sep, NULL, 0);
 if ((rc = SSL_CTX_set1_groups(sctx, nids, ngroups)) == 0)
   tls_error(string_sprintf("Error enabling '%s' group(s)", exp_curve), NULL, NULL, errstr);
 else
-  DEBUG(D_tls) debug_printf(" ECDH: enabled '%s' group(s)\n", exp_curve);
+  DEBUG(tls) debug_printf(" ECDH: enabled '%s' group(s)\n", exp_curve);
 
 #  else		/* Cannot handle a list; only 1 element nids array */
  {
@@ -821,7 +840,7 @@ else
   if ((rc = SSL_CTX_set_tmp_ecdh(sctx, ecdh)) == 0)
     tls_error(string_sprintf("Error enabling '%s' curve", exp_curve), NULL, NULL, errstr);
   else
-    DEBUG(D_tls) debug_printf(" ECDH: enabled '%s' curve\n", exp_curve);
+    DEBUG(tls) debug_printf(" ECDH: enabled '%s' curve\n", exp_curve);
   EC_KEY_free(ecdh);
  }
 #  endif	/*!EXIM_HAVE_OPENSSL_SET1_GROUPS*/
@@ -851,7 +870,7 @@ rsa_callback(SSL *s, int export, int keylength)
 RSA *rsa_key;
 BIGNUM *bn = BN_new();
 
-DEBUG(D_tls) debug_printf("Generating %d bit RSA key...\n", keylength);
+DEBUG(tls) debug_printf("Generating %d bit RSA key...\n", keylength);
 
 if (  !BN_set_word(bn, (unsigned long)RSA_F4)
    || !(rsa_key = RSA_new())
@@ -860,7 +879,7 @@ if (  !BN_set_word(bn, (unsigned long)RSA_F4)
 
   {
   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",
+  log_write(LOG_MAIN|LOG_PANIC, "TLS error (RSA_generate_key): %s",
     ssl_errstring);
   return NULL;
   }
@@ -883,7 +902,7 @@ EVP_PKEY * pkey;
 X509_NAME * name;
 uschar * where;
 
-DEBUG(D_tls) debug_printf("TLS: generating selfsigned server cert\n");
+DEBUG(tls) debug_printf("TLS: generating selfsigned server cert\n");
 where = US"allocating pkey";
 if (!(pkey = EVP_PKEY_new()))
   goto err;
@@ -968,7 +987,7 @@ Returns:    nothing
 static void
 info_callback(const SSL * s, int where, int ret)
 {
-DEBUG(D_tls)
+DEBUG(tls)
   {
   gstring * g = NULL;
 
@@ -983,28 +1002,28 @@ DEBUG(D_tls)
   if (where & SSL_CB_HANDSHAKE_DONE)  g = string_append_listele(g, ',', US"hshake_done");
 
   if (where & SSL_CB_LOOP)
-     debug_printf("SSL %s: %s\n", g->s, SSL_state_string_long(s));
+     debug_printf("SSL %Y: %s\n", g, SSL_state_string_long(s));
   else if (where & SSL_CB_ALERT)
-    debug_printf("SSL %s %s:%s\n", g->s,
+    debug_printf("SSL %Y %s:%s\n", g,
 	  SSL_alert_type_string_long(ret), SSL_alert_desc_string_long(ret));
   else if (where & SSL_CB_EXIT)
     {
     if (ret <= 0)
-      debug_printf("SSL %s: %s in %s\n", g->s,
+      debug_printf("SSL %Y: %s in %s\n", g,
 	ret == 0 ? "failed" : "error", SSL_state_string_long(s));
     }
   else if (where & (SSL_CB_HANDSHAKE_START | SSL_CB_HANDSHAKE_DONE))
-     debug_printf("SSL %s: %s\n", g->s, SSL_state_string_long(s));
+     debug_printf("SSL %Y: %s\n", g, SSL_state_string_long(s));
   }
 }
 
 #ifdef OPENSSL_HAVE_KEYLOG_CB
 static void
-keylog_callback(const SSL *ssl, const char *line)
+keylog_callback(const SSL * ssl, const char * line)
 {
 char * filename;
 FILE * fp;
-DEBUG(D_tls) debug_printf("%.200s\n", line);
+DEBUG(tls) debug_printf("%.200s\n", line);
 if (!(filename = getenv("SSLKEYLOGFILE"))) return;
 if (!(fp = fopen(filename, "a"))) return;
 fprintf(fp, "%s\n", line);
@@ -1028,13 +1047,13 @@ X509 * old_cert;
 ev = tlsp == &tls_out ? client_static_state->event_action : event_action;
 if (ev)
   {
-  DEBUG(D_tls) debug_printf("verify_event: %s %d\n", what, depth);
+  DEBUG(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), &errno)))
     {
-    log_write(0, LOG_MAIN, "[%s] %s verify denied by event-action: "
+    log_write(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);
@@ -1044,7 +1063,7 @@ if (ev)
       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 "
+    DEBUG(tls) debug_printf("Event-action verify failure overridden "
       "(host in tls_try_verify_hosts)\n");
     tlsp->verify_override = TRUE;
     }
@@ -1099,8 +1118,8 @@ uschar dn[256];
 
 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",
+  DEBUG(tls) debug_printf("X509_NAME_oneline() error\n");
+  log_write(LOG_MAIN, "[%s] SSL verify error: internal error",
     tlsp == &tls_out ? deliver_host_address : sender_host_address);
   return 0;
   }
@@ -1112,7 +1131,7 @@ if (preverify_ok == 0)
   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",
+  log_write(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);
@@ -1123,14 +1142,14 @@ if (preverify_ok == 0)
       tlsp->peercert = X509_dup(cert);	/* record failing cert */
     return 0;				/* reject */
     }
-  DEBUG(D_tls) debug_printf("SSL verify failure overridden (host in "
+  DEBUG(tls) debug_printf("SSL verify failure overridden (host in "
     "tls_try_verify_hosts)\n");
   tlsp->verify_override = TRUE;
   }
 
 else if (depth != 0)
   {
-  DEBUG(D_tls) debug_printf("SSL verify ok: depth=%d SN=%s\n", depth, dn);
+  DEBUG(tls) debug_printf("SSL verify ok: depth=%d SN=%s\n", depth, dn);
 #ifndef DISABLE_EVENT
     if (verify_event(tlsp, cert, depth, dn, calledp, optionalp, US"SSL"))
       return 0;				/* reject, with peercert set */
@@ -1158,7 +1177,7 @@ else
     int rc;
     while ((name = string_nextinlist(&list, &sep, NULL, 0)))
       {
-      DEBUG(D_tls|D_lookup) debug_printf_indent("%s suitable for cert, per OpenSSL?", name);
+      DEBUG(tls|lookup) debug_printf_indent("%s suitable for cert, per OpenSSL?", name);
       if ((rc = X509_check_host(cert, CCS name, 0,
 		  X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS
 		  | X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS,
@@ -1166,14 +1185,14 @@ else
 	{
 	if (rc < 0)
 	  {
-	  log_write(0, LOG_MAIN, "[%s] SSL verify error: internal error",
+	  log_write(LOG_MAIN, "[%s] SSL verify error: internal error",
 	    tlsp == &tls_out ? deliver_host_address : sender_host_address);
 	  name = NULL;
 	  }
-	DEBUG(D_tls|D_lookup) debug_printf_indent("  yes\n");
+	DEBUG(tls|lookup) debug_printf_indent("  yes\n");
 	break;
 	}
-      else DEBUG(D_tls|D_lookup) debug_printf_indent("  no\n");
+      else DEBUG(tls|lookup) debug_printf_indent("  no\n");
       }
     if (!name)
 #else
@@ -1184,7 +1203,7 @@ else
         ? string_sprintf(" (during %c-verify for [%s])",
 	  *verify_mode, sender_host_address)
 	: US"";
-      log_write(0, LOG_MAIN,
+      log_write(LOG_MAIN,
 	"[%s] SSL verify error%s: certificate name mismatch: DN=%q H=%q",
 	tlsp == &tls_out ? deliver_host_address : sender_host_address,
 	extra, dn, verify_cert_hostnames);
@@ -1195,7 +1214,7 @@ else
 	  tlsp->peercert = X509_dup(cert);	/* record failing cert */
 	return 0;				/* reject */
 	}
-      DEBUG(D_tls) debug_printf("SSL verify name failure overridden (host in "
+      DEBUG(tls) debug_printf("SSL verify name failure overridden (host in "
 	"tls_try_verify_hosts)\n");
       tlsp->verify_override = TRUE;
       }
@@ -1206,7 +1225,7 @@ else
     return 0;				/* reject, with peercert set */
 #endif
 
-  DEBUG(D_tls) debug_printf("SSL%s verify ok: depth=0 SN=%s\n",
+  DEBUG(tls) debug_printf("SSL%s verify ok: depth=0 SN=%s\n",
     *calledp ? "" : " authenticated", dn);
   *calledp = TRUE;
   }
@@ -1246,14 +1265,14 @@ BOOL dummy_called, optional = FALSE;
 
 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",
+  DEBUG(tls) debug_printf("X509_NAME_oneline() error\n");
+  log_write(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",
+DEBUG(tls) debug_printf("verify_callback_client_dane: %s depth %d %s\n",
   preverify_ok ? "ok":"BAD", depth, dn);
 
 #ifndef DISABLE_EVENT
@@ -1267,7 +1286,7 @@ if (preverify_ok == 1)
 else
   {
   int err = X509_STORE_CTX_get_error(x509ctx);
-  DEBUG(D_tls)
+  DEBUG(tls)
     debug_printf(" - err %d '%s'\n", err, X509_verify_cert_error_string(err));
   if (err == X509_V_ERR_APPLICATION_VERIFICATION)
     preverify_ok = 1;
@@ -1314,7 +1333,7 @@ ASN1_GENERALIZEDTIME * rev, * thisupd, * nextupd;
 STACK_OF(X509) * sk;
 int status, reason, i;
 
-DEBUG(D_tls)
+DEBUG(tls)
   debug_printf("tls_ocsp_file (%s)  '%s'\n", is_pem ? "PEM" : "DER", filename);
 
 if (!filename || !*filename) return;
@@ -1322,7 +1341,7 @@ if (!filename || !*filename) return;
 ERR_clear_error();
 if (!(bio = BIO_new_file(CS filename, "rb")))
   {
-  log_write(0, LOG_MAIN|LOG_PANIC,
+  log_write(LOG_MAIN|LOG_PANIC,
     "Failed to open OCSP response file %q: %.100s",
     filename, ERR_reason_error_string(ERR_get_error()));
   return;
@@ -1335,7 +1354,7 @@ if (is_pem)
   long len;
   if (!PEM_read_bio(bio, &dummy, &dummy, &data, &len))
     {
-    log_write(0, LOG_MAIN|LOG_PANIC, "Failed to read PEM file %q: %.100s",
+    log_write(LOG_MAIN|LOG_PANIC, "Failed to read PEM file %q: %.100s",
       filename, ERR_reason_error_string(ERR_get_error()));
     return;
     }
@@ -1349,14 +1368,14 @@ BIO_free(bio);
 
 if (!resp)
   {
-  log_write(0, LOG_MAIN|LOG_PANIC, "Error reading OCSP response from %q: %s",
+  log_write(LOG_MAIN|LOG_PANIC, "Error reading OCSP response from %q: %s",
       filename, ERR_reason_error_string(ERR_get_error()));
   return;
   }
 
 if ((status = OCSP_response_status(resp)) != OCSP_RESPONSE_STATUS_SUCCESSFUL)
   {
-  DEBUG(D_tls) debug_printf("OCSP response not valid: %s (%d)\n",
+  DEBUG(tls) debug_printf("OCSP response not valid: %s (%d)\n",
       OCSP_response_status_str(status), status);
   goto bad;
   }
@@ -1371,7 +1390,7 @@ if ((status = OCSP_response_status(resp)) != OCSP_RESPONSE_STATUS_SUCCESSFUL)
 
 if (!(basic_response = OCSP_response_get1_basic(resp)))
   {
-  DEBUG(D_tls)
+  DEBUG(tls)
     debug_printf("OCSP response parse error: unable to extract basic response.\n");
   goto bad;
   }
@@ -1410,7 +1429,7 @@ library does it for us anyway?  */
 
 if ((i = OCSP_basic_verify(basic_response, sk, NULL, OCSP_NOVERIFY)) < 0)
   {
-  DEBUG(D_tls)
+  DEBUG(tls)
     {
     ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring));
     debug_printf("OCSP response has bad signature: %s\n", US ssl_errstring);
@@ -1431,7 +1450,7 @@ XXX that will change when we add support for (TLS1.3) whole-chain stapling
 
 if (!(single_response = OCSP_resp_get0(basic_response, 0)))
   {
-  DEBUG(D_tls)
+  DEBUG(tls)
     debug_printf("Unable to get first response from OCSP basic response.\n");
   goto bad;
   }
@@ -1439,7 +1458,7 @@ if (!(single_response = OCSP_resp_get0(basic_response, 0)))
 status = OCSP_single_get0_status(single_response, &reason, &rev, &thisupd, &nextupd);
 if (status != V_OCSP_CERTSTATUS_GOOD)
   {
-  DEBUG(D_tls) debug_printf("OCSP response bad cert status: %s (%d) %s (%d)\n",
+  DEBUG(tls) debug_printf("OCSP response bad cert status: %s (%d) %s (%d)\n",
       OCSP_cert_status_str(status), status,
       OCSP_crl_reason_str(reason), reason);
   goto bad;
@@ -1447,7 +1466,7 @@ if (status != V_OCSP_CERTSTATUS_GOOD)
 
 if (!OCSP_check_validity(thisupd, nextupd, EXIM_OCSP_SKEW_SECONDS, EXIM_OCSP_MAX_AGE))
   {
-  DEBUG(D_tls)
+  DEBUG(tls)
     {
     BIO * bp = BIO_new(BIO_s_mem());
     uschar * s = NULL;
@@ -1479,7 +1498,7 @@ bad:
     if (environ) for (uschar ** 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");
+	DEBUG(tls) debug_printf("Supplying known bad OCSP response\n");
 	goto supply_response;
 	}
     }
@@ -1505,7 +1524,7 @@ static int
 tls_add_certfile(SSL_CTX * sctx, const exim_openssl_state_st * cbinfo,
   const uschar * file, uschar ** errstr)
 {
-DEBUG(D_tls) debug_printf("tls_certificate file '%s'\n", file);
+DEBUG(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),
@@ -1517,7 +1536,7 @@ static int
 tls_add_pkeyfile(SSL_CTX * sctx, const exim_openssl_state_st * cbinfo,
   const uschar * file, uschar ** errstr)
 {
-DEBUG(D_tls) debug_printf("tls_privatekey file  '%s'\n", file);
+DEBUG(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);
@@ -1597,7 +1616,7 @@ else
       if (  state->u_ocsp.server.file_expanded && olist
 	 && (Ustrcmp(olist, state->u_ocsp.server.file_expanded) == 0))
 	{
-	DEBUG(D_tls) debug_printf(" - value unchanged, using existing values\n");
+	DEBUG(tls) debug_printf(" - value unchanged, using existing values\n");
 	olist = NULL;
 	}
       else
@@ -1629,7 +1648,7 @@ else
 	    ocsp_load_response(state, ofile, fmt_pem);
 	    }
 	  else
-	    DEBUG(D_tls) debug_printf("ran out of ocsp file list\n");
+	    DEBUG(tls) debug_printf("ran out of ocsp file list\n");
 #endif
 	}
       }
@@ -1693,7 +1712,7 @@ static int
 server_load_ciphers(SSL_CTX * ctx, exim_openssl_state_st * state,
   uschar * ciphers, uschar ** errstr)
 {
-DEBUG(D_tls) debug_printf("required ciphers: %s\n", ciphers);
+DEBUG(tls) debug_printf("required ciphers: %s\n", ciphers);
 if (!SSL_CTX_set_cipher_list(ctx, CS ciphers))
   return tls_error(US"SSL_CTX_set_cipher_list", NULL, NULL, errstr);
 state->server_cipher_list = ciphers;
@@ -1716,7 +1735,7 @@ if (!(ctx = SSL_CTX_new(host ? SSLv23_client_method() : SSLv23_server_method()))
 /* Set up the information callback, which outputs if debugging is at a suitable
 level. */
 
-DEBUG(D_tls)
+DEBUG(tls)
   {
   SSL_CTX_set_info_callback(ctx, info_callback);
 #if defined(EXIM_HAVE_OPENSSL_TRACE) && !defined(OPENSSL_NO_SSL_TRACE)
@@ -1754,20 +1773,20 @@ state_server.lib_state.lib_ctx = ctx;
 
 if (opt_unset_or_noexpand(tls_dhparam))
   {
-  DEBUG(D_tls) debug_printf("TLS: preloading DH params '%s' for server\n", tls_dhparam);
+  DEBUG(tls) debug_printf("TLS: preloading DH params '%s' for server\n", tls_dhparam);
   if (init_dh(ctx, tls_dhparam, &dummy_errstr))
     state_server.lib_state.dh = TRUE;
   }
 else
-  DEBUG(D_tls) debug_printf("TLS: not preloading DH params for server\n");
+  DEBUG(tls) debug_printf("TLS: not preloading DH params for server\n");
 if (opt_unset_or_noexpand(tls_eccurve))
   {
-  DEBUG(D_tls) debug_printf("TLS: preloading ECDH curve '%s' for server\n", tls_eccurve);
+  DEBUG(tls) debug_printf("TLS: preloading ECDH curve '%s' for server\n", tls_eccurve);
   if (init_ecdh(ctx, &dummy_errstr))
     state_server.lib_state.ecdh = TRUE;
   }
 else
-  DEBUG(D_tls) debug_printf("TLS: not preloading ECDH curve for server\n");
+  DEBUG(tls) debug_printf("TLS: not preloading ECDH curve for server\n");
 
 #if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT)
 /* If we can, preload the Authorities for checking client certs against.
@@ -1785,7 +1804,7 @@ if (  opt_set_and_noexpand(tls_verify_certificates)
      && tls_set_watch(tls_crl, FALSE))
     {
     uschar * v_certs = tls_verify_certificates;
-    DEBUG(D_tls) debug_printf("TLS: preloading CA bundle for server\n");
+    DEBUG(tls) debug_printf("TLS: preloading CA bundle for server\n");
 
     if (setup_certs(ctx, &v_certs, tls_crl, NULL, &dummy_errstr) == OK)
       state_server.lib_state.cabundle = TRUE;
@@ -1813,7 +1832,7 @@ if (  opt_set_and_noexpand(tls_verify_certificates)
 	state_server.u_ocsp.server.file = tls_ocsp_file;
 # endif
 
-	DEBUG(D_tls) debug_printf("TLS: preloading server certs\n");
+	DEBUG(tls) debug_printf("TLS: preloading server certs\n");
 	if (tls_expand_session_files(ctx, &state_server, &dummy_errstr) == OK)
 	  state_server.lib_state.conn_certs = TRUE;
 	}
@@ -1831,11 +1850,11 @@ if (  opt_set_and_noexpand(tls_verify_certificates)
 	}
       }
     else
-      DEBUG(D_tls) debug_printf("TLS: not preloading server certs\n");
+      DEBUG(tls) debug_printf("TLS: not preloading server certs\n");
 	}
   }
 else
-  DEBUG(D_tls) debug_printf("TLS: not preloading CA bundle for server\n");
+  DEBUG(tls) debug_printf("TLS: not preloading CA bundle for server\n");
 
 
 #endif	/* EXIM_HAVE_INOTIFY */
@@ -1845,14 +1864,14 @@ else
 
 if (opt_set_and_noexpand(tls_require_ciphers))
   {
-  DEBUG(D_tls) debug_printf("TLS: preloading cipher list for server\n");
+  DEBUG(tls) debug_printf("TLS: preloading cipher list for server\n");
   normalise_ciphers(&tls_require_ciphers, tls_require_ciphers);
   if (server_load_ciphers(ctx, &state_server, tls_require_ciphers,
 			  &dummy_errstr) == OK)
     state_server.lib_state.pri_string = TRUE;
   }
 else
-  DEBUG(D_tls) debug_printf("TLS: not preloading cipher list for server\n");
+  DEBUG(tls) debug_printf("TLS: not preloading cipher list for server\n");
 return lifetime;
 }
 
@@ -1894,7 +1913,7 @@ if (  opt_set_and_noexpand(ob->tls_certificate)
     {
     uschar * pkey = ob->tls_privatekey;
 
-    DEBUG(D_tls)
+    DEBUG(tls)
       debug_printf("TLS: preloading client certs for transport '%s'\n", trname);
 
     if (  tls_add_certfile(ctx, &tpt_dummy_state, ob->tls_certificate,
@@ -1907,7 +1926,7 @@ if (  opt_set_and_noexpand(ob->tls_certificate)
     }
   }
 else
-  DEBUG(D_tls)
+  DEBUG(tls)
     debug_printf("TLS: not preloading client certs, for transport '%s'\n", trname);
 
 
@@ -1921,7 +1940,7 @@ if (  opt_set_and_noexpand(ob->tls_verify_certificates)
      )
     {
     uschar * v_certs = ob->tls_verify_certificates;
-    DEBUG(D_tls)
+    DEBUG(tls)
       debug_printf("TLS: preloading CA bundle for transport '%s'\n", trname);
 
     if (setup_certs(ctx, &v_certs,
@@ -1930,7 +1949,7 @@ if (  opt_set_and_noexpand(ob->tls_verify_certificates)
     }
   }
 else
-  DEBUG(D_tls)
+  DEBUG(tls)
     debug_printf("TLS: not preloading CA bundle, for transport '%s'\n", trname);
 
 #endif /*EXIM_HAVE_INOTIFY*/
@@ -2046,7 +2065,7 @@ if (exim_tk.name[0])
 
 if (f.running_in_test_harness) ssl_session_timeout = TESTSUITE_TICKET_LIFE;
 
-DEBUG(D_tls) debug_printf("OpenSSL: %s STEK\n", exim_tk.name[0] ? "rotating" : "creating");
+DEBUG(tls) debug_printf("OpenSSL: %s STEK\n", exim_tk.name[0] ? "rotating" : "creating");
 if (RAND_bytes(exim_tk.aes_key, sizeof(exim_tk.aes_key)) <= 0) return;
 if (RAND_bytes(exim_tk.hmac_key, sizeof(exim_tk.hmac_key)) <= 0) return;
 if (RAND_bytes(exim_tk.name+1, sizeof(exim_tk.name)-1) <= 0) return;
@@ -2101,7 +2120,7 @@ tk_hmac_init(
   params[2] = OSSL_PARAM_construct_end();
   if (EVP_MAC_CTX_set_params(hctx, params) == 0)
     {
-    DEBUG(D_tls) debug_printf("EVP_MAC_CTX_set_params: %s\n",
+    DEBUG(tls) debug_printf("EVP_MAC_CTX_set_params: %s\n",
       ERR_reason_error_string(ERR_get_error()));
     return 0; /* error in mac initialisation */
     }
@@ -2126,7 +2145,7 @@ exim_stek * key;
 
 if (enc)
   {
-  DEBUG(D_tls) debug_printf("ticket_key_callback: create new session\n");
+  DEBUG(tls) debug_printf("ticket_key_callback: create new session\n");
   tlsp->resumption |= RESUME_CLIENT_REQUESTED;
 
   if (RAND_bytes(iv, EVP_MAX_IV_LENGTH) <= 0)
@@ -2135,24 +2154,24 @@ if (enc)
   if (!(key = tk_current()))	/* current key doesn't exist or isn't valid */
      return 0;			/* key couldn't be created */
   memcpy(key_name, key->name, 16);
-  DEBUG(D_tls) debug_printf("STEK expire " TIME_T_FMT "\n", key->expire - time(NULL));
+  DEBUG(tls) debug_printf("STEK expire " TIME_T_FMT "\n", key->expire - time(NULL));
 
   if (tk_hmac_init(hctx, key) == 0) return 0;
   EVP_EncryptInit_ex(c_ctx, key->aes_cipher, NULL, key->aes_key, iv);
 
-  DEBUG(D_tls) debug_printf("ticket created\n");
+  DEBUG(tls) debug_printf("ticket created\n");
   return 1;
   }
 else
   {
   time_t now = time(NULL);
 
-  DEBUG(D_tls) debug_printf("ticket_key_callback: retrieve session\n");
+  DEBUG(tls) debug_printf("ticket_key_callback: retrieve session\n");
   tlsp->resumption |= RESUME_CLIENT_SUGGESTED;
 
   if (!(key = tk_find(key_name)) || key->expire < now)
     {
-    DEBUG(D_tls)
+    DEBUG(tls)
       {
       debug_printf("ticket not usable (%s)\n", key ? "expired" : "not found");
       if (key) debug_printf("STEK expire " TIME_T_FMT "\n", key->expire - now);
@@ -2163,7 +2182,7 @@ else
   if (tk_hmac_init(hctx, key) == 0) return 0;
   EVP_DecryptInit_ex(c_ctx, key->aes_cipher, NULL, key->aes_key, iv);
 
-  DEBUG(D_tls) debug_printf("ticket usable, STEK expire " TIME_T_FMT "\n", key->expire - now);
+  DEBUG(tls) debug_printf("ticket usable, STEK expire " TIME_T_FMT "\n", key->expire - now);
 
   /* The ticket lifetime and renewal are the same as the STEK lifetime and
   renewal, which is overenthusiastic.  A factor of, say, 3x longer STEK would
@@ -2215,19 +2234,16 @@ tls_servername_cb(SSL * s, int * ad ARG_UNUSED, void * arg)
 const char * servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name);
 exim_openssl_state_st * state = (exim_openssl_state_st *) arg;
 int rc;
-int old_pool = store_pool;
 uschar * errstr;
 
 if (!servername)
   return SSL_TLSEXT_ERR_OK;
 
-DEBUG(D_tls) debug_printf("Received TLS SNI %q%s\n", servername,
+DEBUG(tls) debug_printf("Received TLS SNI %q%s\n", servername,
     reexpand_tls_files_for_sni ? "" : " (unused for certificate selection)");
 
 /* Make the extension value available for expansion */
-store_pool = POOL_PERM;
-tls_in.sni = string_copy_taint(US servername, GET_TAINTED);
-store_pool = old_pool;
+tls_in.sni = string_copy_perm(US servername, TRUE);
 
 if (!reexpand_tls_files_for_sni)
   return SSL_TLSEXT_ERR_OK;
@@ -2288,12 +2304,12 @@ OCSP information. */
 if ((rc = tls_expand_session_files(server_sni, state, &errstr)) != OK)
   goto bad;
 
-DEBUG(D_tls) debug_printf("Switching SSL context.\n");
+DEBUG(tls) debug_printf("Switching SSL context.\n");
 SSL_set_SSL_CTX(s, server_sni);
 return SSL_TLSEXT_ERR_OK;
 
 bad:
-  log_write(0, LOG_MAIN|LOG_PANIC, "%s", errstr);
+  log_write(LOG_MAIN|LOG_PANIC, "%s", errstr);
   return SSL_TLSEXT_ERR_ALERT_FATAL;
 }
 
@@ -2316,7 +2332,7 @@ tls_server_alpn_cb(SSL * ssl, const uschar ** out, uschar * outlen,
 gstring * g = NULL;
 
 server_seen_alpn = TRUE;
-DEBUG(D_tls)
+DEBUG(tls)
   {
   debug_printf("Received TLS ALPN offer:");
   for (int pos = 0, siz; pos < inlen; pos += siz+1)
@@ -2389,7 +2405,7 @@ ocsp_resplist * olist = state->u_ocsp.server.olist;
 uschar * response_der;	/*XXX blob */
 int response_der_len;
 
-DEBUG(D_tls)
+DEBUG(tls)
   debug_printf("Received TLS status request (OCSP stapling); %s response list\n",
     olist ? "have" : "lack");
 
@@ -2417,7 +2433,7 @@ if (!olist)
       (OCSP_CERTID *) cid);
     resp_bn = ASN1_INTEGER_to_BN(res_cert_serial, NULL);
 
-    DEBUG(D_tls)
+    DEBUG(tls)
       {
       debug_printf("cert serial: %s\n", BN_bn2hex(cert_bn));
       debug_printf("resp serial: %s\n", BN_bn2hex(resp_bn));
@@ -2425,7 +2441,7 @@ if (!olist)
 
     if (BN_cmp(cert_bn, resp_bn) == 0)
       {
-      DEBUG(D_tls) debug_printf("matched serial for ocsp\n");
+      DEBUG(tls) debug_printf("matched serial for ocsp\n");
 
       /*XXX TODO: check the rest of the list for duplicate matches.
       If any, need to also check the Issuer Name hash.
@@ -2434,18 +2450,18 @@ if (!olist)
 
       break;
       }
-    DEBUG(D_tls) debug_printf("not match serial for ocsp\n");
+    DEBUG(tls) debug_printf("not match serial for ocsp\n");
     }
   if (!olist)
     {
-    DEBUG(D_tls) debug_printf("failed to find match for ocsp\n");
+    DEBUG(tls) debug_printf("failed to find match for ocsp\n");
     return SSL_TLSEXT_ERR_NOACK;
     }
  }
 #else
 if (olist->next)
   {
-  DEBUG(D_tls) debug_printf("OpenSSL version too early to support multi-leaf OCSP\n");
+  DEBUG(tls) debug_printf("OpenSSL version too early to support multi-leaf OCSP\n");
   return SSL_TLSEXT_ERR_NOACK;
   }
 #endif
@@ -2469,7 +2485,7 @@ add_chain_to_store(X509_STORE * store, STACK_OF(X509) * sk,
 {
 int idx;
 
-DEBUG(D_tls)
+DEBUG(tls)
   {
   debug_printf("chain for %s:\n", debug_text);
   x509_stack_dump_cert_s_names(sk);
@@ -2491,20 +2507,20 @@ OCSP_RESPONSE * rsp;
 OCSP_BASICRESP * bs;
 int i;
 
-DEBUG(D_tls) debug_printf("Received TLS status callback (OCSP stapling):\n");
+DEBUG(tls) debug_printf("Received TLS status callback (OCSP stapling):\n");
 len = SSL_get_tlsext_status_ocsp_resp(ssl, &p);
 if(!p)
   {				/* Expect this when we requested ocsp but got none */
   if (SSL_session_reused(ssl) && tls_out.ocsp == OCSP_VFIED)
     {
-    DEBUG(D_tls) debug_printf(" null, but resumed; ocsp vfy stored with session is good\n");
+    DEBUG(tls) debug_printf(" null, but resumed; ocsp vfy stored with session is good\n");
     return 1;
     }
 
   if (cbinfo->u_ocsp.client.verify_required && LOGGING(tls_cipher))
-    log_write(0, LOG_MAIN, "Required TLS certificate status not received");
+    log_write(LOG_MAIN, "Required TLS certificate status not received");
   else
-    DEBUG(D_tls) debug_printf(" null\n");
+    DEBUG(tls) debug_printf(" null\n");
 
   if (!cbinfo->u_ocsp.client.verify_required)
     return 1;
@@ -2517,9 +2533,9 @@ if (!(rsp = d2i_OCSP_RESPONSE(NULL, &p, len)))
   {
   tls_out.ocsp = OCSP_FAILED;	/*XXX should use tlsp-> to permit concurrent outbound */
   if (LOGGING(tls_cipher))
-    log_write(0, LOG_MAIN, "Received TLS cert status response, parse error");
+    log_write(LOG_MAIN, "Received TLS cert status response, parse error");
   else
-    DEBUG(D_tls) debug_printf(" parse error\n");
+    DEBUG(tls) debug_printf(" parse error\n");
   return 0;
   }
 
@@ -2527,9 +2543,9 @@ if (!(bs = OCSP_response_get1_basic(rsp)))
   {
   tls_out.ocsp = OCSP_FAILED;
   if (LOGGING(tls_cipher))
-    log_write(0, LOG_MAIN, "Received TLS cert status response, error parsing response");
+    log_write(LOG_MAIN, "Received TLS cert status response, error parsing response");
   else
-    DEBUG(D_tls) debug_printf(" error parsing response\n");
+    DEBUG(tls) debug_printf(" error parsing response\n");
   OCSP_RESPONSE_free(rsp);
   return 0;
   }
@@ -2549,7 +2565,7 @@ if (!(bs = OCSP_response_get1_basic(rsp)))
     STACK_OF(OCSP_SINGLERESP) * sresp = bs->tbsResponseData->responses;
 #endif
 
-    DEBUG(D_tls) bp = BIO_new(BIO_s_mem());
+    DEBUG(tls) bp = BIO_new(BIO_s_mem());
 
     /* Use the CA & chain that verified the server cert to verify the stapled info */
     /*XXX could we do an event here, for observability of ocsp?  What reasonable data could we give access to? */
@@ -2567,7 +2583,7 @@ if (!(bs = OCSP_response_get1_basic(rsp)))
        && (have_verified_OCSP_signer =
 	OCSP_resp_get0_signer(bs, &signer, SSL_get0_verified_chain(ssl)) == 1))
       {
-      DEBUG(D_tls)
+      DEBUG(tls)
 	debug_printf("signer for OCSP basicres is in the verified chain;"
 		      " shortcut its verification\n");
       }
@@ -2589,7 +2605,7 @@ if (!(bs = OCSP_response_get1_basic(rsp)))
       }
    }
 
-    DEBUG(D_tls)
+    DEBUG(tls)
       {
       debug_printf("Untrusted intermediate cert stack (from SSL_get_peer_cert_chain()):\n");
       x509_stack_dump_cert_s_names(SSL_get_peer_cert_chain(ssl));
@@ -2656,7 +2672,7 @@ if (!(bs = OCSP_response_get1_basic(rsp)))
 #endif
 		OCSP_NOEXPLICIT)) <= 0)
       {
-      DEBUG(D_tls) debug_printf("OCSP_basic_verify() fail: returned %d\n", i);
+      DEBUG(tls) debug_printf("OCSP_basic_verify() fail: returned %d\n", i);
       if (ERR_peek_error())
 	{
 	tls_out.ocsp = OCSP_FAILED;
@@ -2673,13 +2689,13 @@ if (!(bs = OCSP_response_get1_basic(rsp)))
 
 	  X509_NAME_oneline(X509_get_subject_name(SSL_get_peer_certificate(ssl)),
 						  CS peerdn, sizeof(peerdn));
-	  log_write(0, LOG_MAIN,
+	  log_write(LOG_MAIN,
 		"[%s] %s Received TLS cert (DN: '%.*s') status response, "
 		"itself unverifiable: %s",
 		deliver_host_address, deliver_host,
 		(int)sizeof(peerdn), peerdn, errstr);
 	  }
-	DEBUG(D_tls)
+	DEBUG(tls)
 	  {
 	  uschar * s = NULL;
 	  int flen;
@@ -2693,11 +2709,11 @@ if (!(bs = OCSP_response_get1_basic(rsp)))
 	goto failed;
 	}
       else
-	DEBUG(D_tls) debug_printf("no explicit trust for OCSP signing"
+	DEBUG(tls) debug_printf("no explicit trust for OCSP signing"
 	  " in the root CA certificate; ignoring\n");
       }
 
-    DEBUG(D_tls) debug_printf("OCSP response well-formed and signed OK\n");
+    DEBUG(tls) debug_printf("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
@@ -2728,7 +2744,7 @@ if (!(bs = OCSP_response_get1_basic(rsp)))
       status = OCSP_single_get0_status(single, &reason, &rev,
 		  &thisupd, &nextupd);
 
-      DEBUG(D_tls)
+      DEBUG(tls)
 	{
 	time_print(bp, "This OCSP Update", thisupd);
 	if (nextupd) time_print(bp, "Next OCSP Update", nextupd);
@@ -2737,14 +2753,14 @@ if (!(bs = OCSP_response_get1_basic(rsp)))
 	    EXIM_OCSP_SKEW_SECONDS, EXIM_OCSP_MAX_AGE))
 	{
 	tls_out.ocsp = OCSP_FAILED;
-	DEBUG(D_tls) ERR_print_errors(bp);
+	DEBUG(tls) ERR_print_errors(bp);
 	cbinfo->u_ocsp.client.verify_errstr =
 		    US"(SSL_connect) Server certificate status is out-of-date";
-	log_write(0, LOG_MAIN, "OCSP dates invalid");
+	log_write(LOG_MAIN, "OCSP dates invalid");
 	goto failed;
 	}
 
-      DEBUG(D_tls) BIO_printf(bp, "Certificate status: %s\n",
+      DEBUG(tls) BIO_printf(bp, "Certificate status: %s\n",
 		    OCSP_cert_status_str(status));
       switch(status)
 	{
@@ -2753,15 +2769,15 @@ if (!(bs = OCSP_response_get1_basic(rsp)))
 	case V_OCSP_CERTSTATUS_REVOKED:
 	  cbinfo->u_ocsp.client.verify_errstr =
 			US"(SSL_connect) Server certificate revoked";
-	  log_write(0, LOG_MAIN, "Server certificate revoked%s%s",
+	  log_write(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);
+	  DEBUG(tls) time_print(bp, "Revocation Time", rev);
 	  break;
 	default:
 	  cbinfo->u_ocsp.client.verify_errstr =
 			US"(SSL_connect) Server certificate has unknown status";
-	  log_write(0, LOG_MAIN,
+	  log_write(LOG_MAIN,
 	      "Server certificate status unknown, in OCSP stapling");
 	  break;
 	}
@@ -2777,7 +2793,7 @@ if (!(bs = OCSP_response_get1_basic(rsp)))
     tls_out.ocsp = OCSP_FAILED;
     i = cbinfo->u_ocsp.client.verify_required ? 0 : 1;
   good:
-    DEBUG(D_tls)
+    DEBUG(tls)
       {
       uschar * s = NULL;
       int dlen = (int) BIO_get_mem_data(bp, CSS &s);
@@ -2905,7 +2921,7 @@ if (init_options)
   /* Should the server offer session resumption? */
   if (!host && verify_check_host(&tls_resumption_hosts) == OK)
     {
-    DEBUG(D_tls) debug_printf("tls_resumption_hosts overrides openssl_options\n");
+    DEBUG(tls) debug_printf("tls_resumption_hosts overrides openssl_options\n");
     init_options &= ~SSL_OP_NO_TICKET;
     tlsp->resumption |= RESUME_SERVER_TICKET; /* server will give ticket on request */
     tlsp->host_resumable = TRUE;
@@ -2915,7 +2931,7 @@ if (init_options)
 #ifdef OPENSSL_MIN_PROTO_VERSION
   SSL_CTX_set_min_proto_version(ctx, SSL3_VERSION);
 #endif
-  DEBUG(D_tls) debug_printf("setting  SSL CTX options: %016lx\n", init_options);
+  DEBUG(tls) debug_printf("setting  SSL CTX options: %016lx\n", init_options);
   SSL_CTX_set_options(ctx, init_options);
    {
     uint64_t readback = SSL_CTX_clear_options(ctx, ~init_options);
@@ -2925,7 +2941,7 @@ if (init_options)
    }
   }
 else
-  DEBUG(D_tls) debug_printf("no SSL CTX options to set\n");
+  DEBUG(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
@@ -2943,12 +2959,12 @@ will never be used because we use a new context every time. */
 if (!host)
   {
   if (state->lib_state.dh)
-    { DEBUG(D_tls) debug_printf("TLS: DH params were preloaded\n"); }
+    { DEBUG(tls) debug_printf("TLS: DH params were preloaded\n"); }
   else
     if (!init_dh(ctx, state->dhparam, errstr)) return DEFER;
 
   if (state->lib_state.ecdh)
-    { DEBUG(D_tls) debug_printf("TLS: ECDH curve was preloaded\n"); }
+    { DEBUG(tls) debug_printf("TLS: ECDH curve was preloaded\n"); }
   else
     if (!init_ecdh(ctx, errstr)) return DEFER;
   }
@@ -2957,7 +2973,7 @@ if (!host)
 
 if (state->lib_state.conn_certs)
   {
-  DEBUG(D_tls)
+  DEBUG(tls)
     debug_printf("TLS: %s certs were preloaded\n", host ? "client":"server");
   }
 else
@@ -2978,7 +2994,7 @@ else
 #ifndef DISABLE_OCSP
   if (!host && !(state->u_ocsp.server.verify_stack = sk_X509_new_null()))
     {
-    DEBUG(D_tls) debug_printf("failed to create stack for stapling verify\n");
+    DEBUG(tls) debug_printf("failed to create stack for stapling verify\n");
     return FAIL;
     }
 #endif
@@ -3022,7 +3038,7 @@ else			/* client */
     {
     if (!(state->u_ocsp.client.verify_store = X509_STORE_new()))
       {
-      DEBUG(D_tls) debug_printf("failed to create store for stapling verify\n");
+      DEBUG(tls) debug_printf("failed to create store for stapling verify\n");
       return FAIL;
       }
 
@@ -3042,7 +3058,7 @@ SSL_CTX_set_tmp_rsa_callback(ctx, rsa_callback);
 The period appears to be also used for (server-generated) session tickets */
 
 SSL_CTX_set_timeout(ctx, ssl_session_timeout);
-DEBUG(D_tls) debug_printf("Initialized TLS\n");
+DEBUG(tls) debug_printf("Initialized TLS\n");
 
 *caller_state = state;
 
@@ -3057,8 +3073,11 @@ return OK;
 *************************************************/
 
 /*
-Argument:   pointer to an SSL structure for the connection
-	    pointer to number of bits for cipher
+Arguments:
+  ssl	pointer to an SSL structure for the connection
+  ver	TLS version string
+  bits	pointer for return of number of bits for cipher
+
 Returns:    pointer to allocated string in perm-pool
 */
 
@@ -3076,9 +3095,17 @@ uschar * s;
 SSL_CIPHER_get_bits(c, bits);
 
 store_pool = POOL_PERM;
-s = string_sprintf("%s:%s:%u", ver, SSL_CIPHER_get_name(c), *bits);
+  {
+#ifdef EXIM_TLS_KEX_GROUP
+  const char * cs = SSL_get0_group_name(ssl);
+  if (cs)
+    s = string_sprintf("%s:%s:%u:%s", ver, SSL_CIPHER_get_name(c), *bits, cs);
+  else
+#endif
+    s = string_sprintf("%s:%s:%u", ver, SSL_CIPHER_get_name(c), *bits);
+  }
 store_pool = pool;
-DEBUG(D_tls) debug_printf("Cipher: %s\n", s);
+DEBUG(tls) debug_printf("Cipher: %s\n", s);
 return s;
 }
 
@@ -3103,13 +3130,8 @@ return cipher_stdname(id >> 8, id & 0xff);
 static const uschar *
 tlsver_name(const SSL * ssl)
 {
-const uschar * s;
-uschar * p;
-int pool = store_pool;
+uschar * s = string_copy_perm(US SSL_get_version(ssl), FALSE), * p;
 
-store_pool = POOL_PERM;
-s = string_copy(US SSL_get_version(ssl));
-store_pool = pool;
 if ((p = Ustrchr(s, 'v')))	/* TLSv1.2 -> TLS1.2 */
   for (;; p++) if (!(*p = p[1])) break;
 return CUS s;
@@ -3132,15 +3154,11 @@ if (!tlsp->peercert)
 /* 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"); }
+    { DEBUG(tls) debug_printf("X509_NAME_oneline() error\n"); }
   else
     {
-    int oldpool = store_pool;
-
     peerdn[siz-1] = '\0';		/* paranoia */
-    store_pool = POOL_PERM;
-    tlsp->peerdn = string_copy(peerdn);
-    store_pool = oldpool;
+    tlsp->peerdn = string_copy_perm(peerdn, TRUE);
 
     /* We used to set CV in the cert-verify callbacks (either plain or dane)
     but they don't get called on session-resumption.  So use the official
@@ -3215,7 +3233,7 @@ uschar * expcerts, * expcrl;
 
 if (!expand_check(*certsp, US"tls_verify_certificates", &expcerts, errstr))
   return DEFER;
-DEBUG(D_tls) debug_printf("tls_verify_certificates: %s\n", expcerts);
+DEBUG(tls) debug_printf("tls_verify_certificates: %s\n", expcerts);
 
 *certsp = expcerts;
 if (expcerts && *expcerts)
@@ -3232,7 +3250,7 @@ if (expcerts && *expcerts)
 
     if (Ustat(expcerts, &statbuf) < 0)
       {
-      log_write(0, LOG_MAIN|LOG_PANIC,
+      log_write(LOG_MAIN|LOG_PANIC,
 	"failed to stat %s for certificates", expcerts);
       return DEFER;
       }
@@ -3265,7 +3283,7 @@ This is inconsistent with the need to verify the OCSP proof of the server cert.
 	   && !chain_from_pem_file(file, vp)
 	   )
 	  {
-	  log_write(0, LOG_MAIN|LOG_PANIC,
+	  log_write(LOG_MAIN|LOG_PANIC,
 	    "failed to load cert chain from %s", file);
 	  return DEFER;
 	  }
@@ -3298,11 +3316,11 @@ This is inconsistent with the need to verify the OCSP proof of the server cert.
 	int i = sk_X509_NAME_num(names);
 
 	if (!host) SSL_CTX_set_client_CA_list(sctx, names);
-	DEBUG(D_tls) debug_printf("Added %d additional certificate authorit%s\n",
+	DEBUG(tls) debug_printf("Added %d additional certificate authorit%s\n",
 				    i, i>1 ? "ies":"y");
 	}
       else
-	DEBUG(D_tls)
+	DEBUG(tls)
 	  debug_printf("Added dir for additional certificate authorities\n");
       }
     }
@@ -3325,7 +3343,7 @@ This is inconsistent with the need to verify the OCSP proof of the server cert.
     struct stat statbufcrl;
     if (Ustat(expcrl, &statbufcrl) < 0)
       {
-      log_write(0, LOG_MAIN|LOG_PANIC,
+      log_write(LOG_MAIN|LOG_PANIC,
         "failed to stat %s for certificates revocation lists", expcrl);
       return DEFER;
       }
@@ -3338,13 +3356,13 @@ This is inconsistent with the need to verify the OCSP proof of the server cert.
         {
         file = NULL;
         dir = expcrl;
-        DEBUG(D_tls) debug_printf("SSL CRL value is a directory %s\n", dir);
+        DEBUG(tls) debug_printf("SSL CRL value is a directory %s\n", dir);
         }
       else
         {
         file = expcrl;
         dir = NULL;
-        DEBUG(D_tls) debug_printf("SSL CRL value is a file %s\n", file);
+        DEBUG(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, errstr);
@@ -3422,7 +3440,7 @@ if (len > 0)
   store_pool = POOL_PERM;
     tlsp->channelbinding = b64encode_taint(CUS s, (int)len, taintval);
   store_pool = old_pool;
-  DEBUG(D_tls) debug_printf("Have channel bindings cached for possible auth usage %p %p\n", tlsp->channelbinding, tlsp);
+  DEBUG(tls) debug_printf("Have channel bindings cached for possible auth usage %p %p\n", tlsp->channelbinding, tlsp);
   }
 }
 
@@ -3490,7 +3508,7 @@ TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256
 */
 
 if (state_server.lib_state.pri_string)
-  { DEBUG(D_tls) debug_printf("TLS: cipher list was preloaded\n"); }
+  { DEBUG(tls) debug_printf("TLS: cipher list was preloaded\n"); }
 else
   {
   if (!expand_check(tls_require_ciphers, US"tls_require_ciphers", &expciphers, errstr))
@@ -3528,7 +3546,7 @@ verify_client_cert = TRUE;
 
   if (state_server.lib_state.cabundle)
     {
-    DEBUG(D_tls) debug_printf("TLS: CA bundle for server was preloaded\n");
+    DEBUG(tls) debug_printf("TLS: CA bundle for server was preloaded\n");
     setup_cert_verify(ctx, server_verify_optional, verify_callback_server);
     }
   else
@@ -3609,7 +3627,7 @@ if (  tls_in.on_connect			/* Not usable for STARTTLS */
     case SSL_READ_EARLY_DATA_ERROR:
       {
       int err = SSL_get_error(ssl, SSL_READ_EARLY_DATA_ERROR);
-      DEBUG(D_tls) debug_printf("SSL_read_early_data: %d\n", err);
+      DEBUG(tls) debug_printf("SSL_read_early_data: %d\n", err);
       if (err == SSL_ERROR_SYSCALL)
 	{
 	if (!errno)
@@ -3620,17 +3638,17 @@ if (  tls_in.on_connect			/* Not usable for STARTTLS */
 #endif
 	  return FAIL;
 	  }
-	DEBUG(D_tls) debug_printf(" - syscall %s\n", strerror(errno));
+	DEBUG(tls) debug_printf(" - syscall %s\n", strerror(errno));
 	}
       return tls_error(US"SSL_read_early_data", NULL, NULL, errstr);
       }
 
     case SSL_READ_EARLY_DATA_SUCCESS:
-      DEBUG(D_tls) debug_printf("TLS: unexpected early data from client!\n");
+      DEBUG(tls) debug_printf("TLS: unexpected early data from client!\n");
       return tls_error(US"SSL_read_early_data", NULL, NULL, errstr);
 
     case SSL_READ_EARLY_DATA_FINISH:
-      DEBUG(D_tls) debug_printf("TLS: No early-data from client; good\n");
+      DEBUG(tls) debug_printf("TLS: No early-data from client; good\n");
     }
 
     if (  SSL_version(ssl) > TLS1_2_VERSION	/* not sure is safe pre 1.3 */
@@ -3643,22 +3661,22 @@ if (  tls_in.on_connect			/* Not usable for STARTTLS */
       int len = gstring_length(banner);
       size_t n_bytes;
 
-      DEBUG(D_tls) debug_printf("TLS: writing early-data\n");
+      DEBUG(tls) debug_printf("TLS: writing early-data\n");
       if (!SSL_write_early_data(ssl, banner->s, len, &n_bytes))
 	{
-	DEBUG(D_tls)
+	DEBUG(tls)
 	  debug_printf("SSL_write_early_data: %d\n", SSL_get_error(ssl, 0));
 	return tls_error(US"SSL_write_early_data", NULL, NULL, errstr);
 	}
       if (n_bytes != len)
 	{
-	DEBUG(D_tls)
+	DEBUG(tls)
 	  debug_printf("SSL_write_early_data: wrote %d (expected %d)\n",
 			(int)n_bytes, len);
 	return tls_error(US"SSL_write_early_data", NULL, NULL, errstr);
 	}
 
-      DEBUG(D_receive) 
+      DEBUG(receive) 
 	{ gstring_trim(banner, 2); debug_printf("SMTP>> %Y\n", banner); }
 
       /* Ensure smtp_start_session does not repeat the banner */
@@ -3668,7 +3686,7 @@ if (  tls_in.on_connect			/* Not usable for STARTTLS */
   }
 # endif /*EXIM_TLS_EARLY_BANNER*/
 
-DEBUG(D_tls) debug_printf("Calling SSL_accept\n");
+DEBUG(tls) debug_printf("Calling SSL_accept\n");
 
 ERR_clear_error();
 sigalrm_seen = FALSE;
@@ -3685,7 +3703,7 @@ if (rc <= 0)
       break;
 
     case SSL_ERROR_ZERO_RETURN:
-      DEBUG(D_tls) debug_printf("Got SSL_ERROR_ZERO_RETURN\n");
+      DEBUG(tls) debug_printf("Got SSL_ERROR_ZERO_RETURN\n");
       (void) tls_error(US"SSL_accept", NULL, sigalrm_seen ? US"timed out" : NULL, errstr);
 #ifndef DISABLE_EVENT
       (void) event_raise(event_action, US"tls:fail:connect", *errstr, NULL);
@@ -3717,7 +3735,7 @@ if (rc <= 0)
     default:
       {
       uschar * s;
-      DEBUG(D_tls) debug_printf("Got SSL error %d\n", error);
+      DEBUG(tls) debug_printf("Got SSL error %d\n", error);
       if (error == SSL_ERROR_SYSCALL)
 	{
 	if (!errno)
@@ -3729,7 +3747,7 @@ if (rc <= 0)
 	  return FAIL;
 	  }
 	s = string_sprintf("syscall %s", strerror(errno));
-	DEBUG(D_tls) debug_printf(" - %s\n", s);
+	DEBUG(tls) debug_printf(" - %s\n", s);
 	}
       else
 	s = string_sprintf("ret %d", error);
@@ -3745,7 +3763,7 @@ if (rc <= 0)
     }
   }
 
-DEBUG(D_tls) debug_printf("SSL_accept was successful\n");
+DEBUG(tls) debug_printf("SSL_accept was successful\n");
 ERR_clear_error();	/* Even success can leave errors in the stack. Seen with
 			anon-authentication ciphersuite negotiated. */
 
@@ -3753,14 +3771,14 @@ ERR_clear_error();	/* Even success can leave errors in the stack. Seen with
 if (SSL_session_reused(ssl))
   {
   tls_in.resumption |= RESUME_USED;
-  DEBUG(D_tls) debug_printf("Session reused\n");
+  DEBUG(tls) debug_printf("Session reused\n");
   }
 #endif
 
 #ifdef EXIM_HAVE_ALPN
 /* If require-alpn, check server_seen_alpn here.  Else abort TLS */
 if (!tls_alpn || !*tls_alpn)
-  { DEBUG(D_tls) debug_printf("TLS: was not watching for ALPN\n"); }
+  { DEBUG(tls) debug_printf("TLS: was not watching for ALPN\n"); }
 else if (server_fail_alpn)
     {
     uschar * s = string_sprintf("Bad ALPN presented (%Y)", server_fail_alpn);
@@ -3777,8 +3795,8 @@ else if (!server_seen_alpn)
     return FAIL;
     }
   else
-    { DEBUG(D_tls) debug_printf("TLS: no ALPN presented in handshake\n"); }
-else DEBUG(D_tls)
+    { DEBUG(tls) debug_printf("TLS: no ALPN presented in handshake\n"); }
+else DEBUG(tls)
   {
   const uschar * name;
   unsigned len;
@@ -3808,7 +3826,7 @@ tls_in.ver = tlsver_name(ssl);
 tls_in.cipher = construct_cipher_name(ssl, tls_in.ver, &tls_in.bits);
 tls_in.cipher_stdname = cipher_stdname_ssl(ssl);
 
-DEBUG(D_tls)
+DEBUG(tls)
   {
   uschar buf[2048];
   if (SSL_get_shared_ciphers(ssl, CS buf, sizeof(buf)))
@@ -3840,16 +3858,11 @@ tls_get_channel_binding(ssl, &tls_in, GET_UNTAINTED);
    Hence no need to duplicate for _in and _out.
  */
 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 = IN_UNGETC_MAX;
 ssl_xfer_eof = ssl_xfer_error = FALSE;
 
-receive_getc = tls_getc;
-receive_getbuf = tls_getbuf;
-receive_get_cache = tls_get_cache;
-receive_hasc = tls_hasc;
-receive_ungetc = tls_ungetc;
-receive_feof = tls_feof;
-receive_ferror = tls_ferror;
+rx_prc = tls_push_receive_functions(rx_prc);
+DEBUG(receive) debug_print_processing_stack();
 
 tls_in.active.sock = smtp_out_fd;
 tls_in.active.tls_ctx = NULL;	/* not using explicit ctx for server-side */
@@ -3890,7 +3903,7 @@ else
 
   if (state->lib_state.cabundle)
     {
-    DEBUG(D_tls) debug_printf("TLS: CA bundle for tpt was preloaded\n");
+    DEBUG(tls) debug_printf("TLS: CA bundle for tpt was preloaded\n");
     setup_cert_verify(ctx, client_verify_optional, verify_callback_client);
     }
   else
@@ -3910,7 +3923,7 @@ if (verify_check_given_host(CUSS &ob->tls_verify_cert_hostnames, host) == OK)
 #else
     host->certname;
 #endif
-  DEBUG(D_tls) debug_printf("Cert hostname to check: %q\n",
+  DEBUG(tls) debug_printf("Cert hostname to check: %q\n",
 		    state->verify_cert_hostnames);
   }
 return OK;
@@ -3922,7 +3935,7 @@ static int
 dane_tlsa_load(SSL * ssl, const host_item * host, const dns_answer * dnsa,
   uschar ** errstr)
 {
-dns_scan dnss;
+dns_scan dnss = {0};
 const char * hostnames[2] = { CS host->name, NULL };
 int found = 0;
 
@@ -3968,7 +3981,7 @@ for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
 if (found)
   return OK;
 
-log_write(0, LOG_MAIN, "DANE error: No usable TLSA records");
+log_write(LOG_MAIN, "DANE error: No usable TLSA records");
 return DEFER;
 }
 #endif	/*SUPPORT_DANE*/
@@ -3989,7 +4002,7 @@ if (tlsp->host_resumable)
   open_db dbblock, * dbm_file;
 
   tlsp->resumption |= RESUME_CLIENT_REQUESTED;
-  DEBUG(D_tls)
+  DEBUG(tls)
     debug_printf("checking for resumable session for %s\n", tlsp->resume_index);
   if ((dbm_file = dbfn_open(US"tls", O_RDWR|O_CREAT, &dbblock, FALSE, FALSE)))
     {
@@ -4001,7 +4014,7 @@ if (tlsp->host_resumable)
       len -= sizeof(dbdata_tls_session);
       if (!(d2i_SSL_SESSION(&ss, &sess_asn1, (long)len)))
 	{
-	DEBUG(D_tls)
+	DEBUG(tls)
 	  {
 	  ERR_error_string_n(ERR_get_error(),
 	    ssl_errstring, sizeof(ssl_errstring));
@@ -4016,20 +4029,20 @@ if (tlsp->host_resumable)
 #else			/* Use, fairly arbitrilarily, what we as server would */
 	  f.running_in_test_harness ? TESTSUITE_TICKET_LIFE : ssl_session_timeout;
 #endif
-	time_t now = time(NULL), expires = lifetime + dt->time_stamp;
+	time_t now = time(NULL), expires = lifetime + dt->gen.time_stamp;
 	if (expires < now)
 	  {
-	  DEBUG(D_tls) debug_printf("session expired (by " TIME_T_FMT "s from %lus)\n", now - expires, lifetime);
+	  DEBUG(tls) debug_printf("session expired (by " TIME_T_FMT "s from %lus)\n", now - expires, lifetime);
 	  dbfn_delete(dbm_file, tlsp->resume_index);
 	  }
 	else if (SSL_set_session(ssl, ss))
 	  {
-	  DEBUG(D_tls) debug_printf("good session (" TIME_T_FMT "s left of %lus)\n", expires - now, lifetime);
+	  DEBUG(tls) debug_printf("good session (" TIME_T_FMT "s left of %lus)\n", expires - now, lifetime);
 	  tlsp->resumption |= RESUME_CLIENT_SUGGESTED;
 	  tlsp->verify_override = dt->verify_override;
 	  tlsp->ocsp = dt->ocsp;
 	  }
-	else DEBUG(D_tls)
+	else DEBUG(tls)
 	  {
 	  ERR_error_string_n(ERR_get_error(),
 	    ssl_errstring, sizeof(ssl_errstring));
@@ -4038,7 +4051,7 @@ if (tlsp->host_resumable)
 	}
       }
     else
-      DEBUG(D_tls) debug_printf("no session record\n");
+      DEBUG(tls) debug_printf("no session record\n");
     dbfn_close(dbm_file);
     }
   }
@@ -4053,7 +4066,7 @@ tls_save_session_cb(SSL * ssl, SSL_SESSION * ss)
 exim_openssl_state_st * cbinfo = SSL_get_ex_data(ssl, tls_exdata_idx);
 tls_support * tlsp;
 
-DEBUG(D_tls) debug_printf("tls_save_session_cb\n");
+DEBUG(tls) debug_printf("tls_save_session_cb\n");
 
 if (!cbinfo || !(tlsp = cbinfo->tlsp)->host_resumable) return 0;
 
@@ -4067,7 +4080,7 @@ if (SSL_SESSION_is_resumable(ss)) 	/* 1.1.1 */
   uschar * s = dt->session;
   open_db dbblock, * dbm_file;
 
-  DEBUG(D_tls) debug_printf("session is resumable\n");
+  DEBUG(tls) debug_printf("session is resumable\n");
   tlsp->resumption |= RESUME_SERVER_TICKET;	/* server gave us a ticket */
 
   dt->verify_override = tlsp->verify_override;
@@ -4078,7 +4091,7 @@ if (SSL_SESSION_is_resumable(ss)) 	/* 1.1.1 */
     {
     dbfn_write(dbm_file, tlsp->resume_index, dt, dlen);
     dbfn_close(dbm_file);
-    DEBUG(D_tls) debug_printf("wrote session (len %u) to db\n",
+    DEBUG(tls) debug_printf("wrote session (len %u) to db\n",
 		  (unsigned)dlen);
     }
   }
@@ -4109,7 +4122,7 @@ tls_client_ssl_resume_prehandshake(SSL * ssl, tls_support * tlsp,
 {
 if (tlsp->host_resumable)
   {
-  DEBUG(D_tls)
+  DEBUG(tls)
     debug_printf("tls_resumption_hosts overrides openssl_options, enabling tickets\n");
   SSL_clear_options(ssl, SSL_OP_NO_TICKET);
 
@@ -4134,7 +4147,7 @@ tls_client_resume_posthandshake(exim_openssl_client_tls_ctx * exim_client_ctx,
 {
 if (SSL_session_reused(exim_client_ctx->ssl))
   {
-  DEBUG(D_tls) debug_printf("The session was reused\n");
+  DEBUG(tls) debug_printf("The session was reused\n");
   tlsp->resumption |= RESUME_USED;
   }
 }
@@ -4160,7 +4173,7 @@ if (!expand_check(*tls_alpn, US"tls_alpn", &exp_alpn, errstr))
 
 if (!exp_alpn)
   {
-  DEBUG(D_tls) debug_printf("Setting TLS ALPN forced to fail, not sending\n");
+  DEBUG(tls) debug_printf("Setting TLS ALPN forced to fail, not sending\n");
   *plist = NULL;
   }
 else
@@ -4221,11 +4234,9 @@ 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), GET_UNTAINTED);
+exim_client_ctx = store_get_perm(sizeof(exim_openssl_client_tls_ctx),
+				GET_UNTAINTED);
 exim_client_ctx->corked = NULL;
-store_pool = rc;
 
 #ifdef SUPPORT_DANE
 tlsp->tlsa_usage = 0;
@@ -4260,7 +4271,7 @@ tlsp->tlsa_usage = 0;
 # if defined(SUPPORT_DANE) && !defined(EXIM_HAVE_OPENSSL_OCSP_RESP_GET0_SIGNER)
   if (conn_args->dane && (require_ocsp || request_ocsp))
     {
-    DEBUG(D_tls) debug_printf("OpenSSL version to early to combine OCSP"
+    DEBUG(tls) debug_printf("OpenSSL version to early to combine OCSP"
 			      " and DANE; disabling OCSP\n");
     require_ocsp = request_ocsp = FALSE;
     }
@@ -4311,7 +4322,7 @@ if (!expciphers)
 
 if (expciphers)
   {
-  DEBUG(D_tls) debug_printf("required ciphers: %s\n", expciphers);
+  DEBUG(tls) debug_printf("required ciphers: %s\n", expciphers);
   if (!SSL_CTX_set_cipher_list(exim_client_ctx->ctx, CS expciphers))
     {
     tls_error(US"SSL_CTX_set_cipher_list", host, NULL, errstr);
@@ -4336,7 +4347,7 @@ if (conn_args->dane)
     tls_error(US"context init", host, NULL, errstr);
     return FALSE;
     }
-  DEBUG(D_tls) debug_printf("since dane-mode conn, not loading the usual CA bundle\n");
+  DEBUG(tls) debug_printf("since dane-mode conn, not loading the usual CA bundle\n");
   }
 else
 
@@ -4351,7 +4362,7 @@ if (ob->tls_sni)
   if (!expand_check(ob->tls_sni, US"tls_sni", &tlsp->sni, errstr))
     return FALSE;
   if (!tlsp->sni)
-    { DEBUG(D_tls) debug_printf("Setting TLS SNI forced to fail, not sending\n"); }
+    { DEBUG(tls) debug_printf("Setting TLS SNI forced to fail, not sending\n"); }
   else if (!Ustrlen(tlsp->sni))
     tlsp->sni = NULL;
   }
@@ -4371,10 +4382,10 @@ if (ob->tls_alpn)
       return FALSE;
       }
     else
-      DEBUG(D_tls) debug_printf("Setting TLS ALPN '%s'\n", ob->tls_alpn);
+      DEBUG(tls) debug_printf("Setting TLS ALPN '%s'\n", ob->tls_alpn);
   }
 #else
-  log_write(0, LOG_MAIN,
+  log_write(LOG_MAIN,
 	  "ALPN unusable with this OpenSSL library version; ignoring %q\n",
           ob->tls_alpn);
 #endif
@@ -4384,7 +4395,7 @@ if (ob->tls_alpn)
 will be very low. */
 
 if (!conn_args->have_lbserver)	/* wanted for tls_client_resmption_key() */
-  { DEBUG(D_tls) debug_printf("resumption not supported on continued-connection\n"); }
+  { DEBUG(tls) debug_printf("resumption not supported on continued-connection\n"); }
 else if (verify_check_given_host(CUSS &ob->tls_resumption_hosts, host) == OK)
   tls_client_ctx_resume_prehandshake(exim_client_ctx, conn_args, tlsp, ob);
 #endif
@@ -4401,13 +4412,13 @@ SSL_set_connect_state(exim_client_ctx->ssl);
 
 if (tlsp->sni)
   {
-  DEBUG(D_tls) debug_printf("Setting TLS SNI %q\n", tlsp->sni);
+  DEBUG(tls) debug_printf("Setting TLS SNI %q\n", tlsp->sni);
   SSL_set_tlsext_host_name(exim_client_ctx->ssl, tlsp->sni);
   }
 
 #ifdef SUPPORT_DANE
 if (conn_args->dane)
-  if (dane_tlsa_load(exim_client_ctx->ssl, host, &conn_args->tlsa_dnsa, errstr) != OK)
+  if (dane_tlsa_load(exim_client_ctx->ssl, host, conn_args->tlsa_dnsa, errstr) != OK)
     return FALSE;
 #endif
 
@@ -4451,7 +4462,7 @@ client_static_state->event_action = tb ? tb->event_action : NULL;
 
 /* There doesn't seem to be a built-in timeout on connection. */
 
-DEBUG(D_tls) debug_printf("Calling SSL_connect\n");
+DEBUG(tls) debug_printf("Calling SSL_connect\n");
 sigalrm_seen = FALSE;
 ALARM(ob->command_timeout);
 rc = SSL_connect(exim_client_ctx->ssl);
@@ -4473,7 +4484,7 @@ if (rc <= 0)
   return FALSE;
   }
 
-DEBUG(D_tls)
+DEBUG(tls)
   {
   debug_printf("SSL_connect succeeded\n");
   tls_dump_keylog(exim_client_ctx->ssl);
@@ -4491,7 +4502,7 @@ if (ob->tls_alpn)	/* We requested. See what was negotiated. */
 
   SSL_get0_alpn_selected(exim_client_ctx->ssl, &name, &len);
   if (len > 0)
-    { DEBUG(D_tls) debug_printf("ALPN negotiated %u: '%.*s'\n", len, (int)*name, name+1); }
+    { DEBUG(tls) debug_printf("ALPN negotiated %u: '%.*s'\n", len, (int)*name, name+1); }
   else if (verify_check_given_host(CUSS &ob->hosts_require_alpn, host) == OK)
     {
     /* Would like to send a relevant fatal Alert, but OpenSSL has no API */
@@ -4533,16 +4544,15 @@ static BOOL
 tls_refill(unsigned lim)
 {
 SSL * ssl = state_server.lib_state.lib_ssl;
-int error;
-int inbytes;
+uschar * s = ssl_xfer_buffer + IN_UNGETC_MAX;
+int error, inbytes;
 
-DEBUG(D_tls) debug_printf("Calling SSL_read(%p, %p, %u)\n", ssl,
-  ssl_xfer_buffer, ssl_xfer_buffer_size);
+DEBUG(tls) debug_printf("Calling SSL_read(tls_refill %p, %p, %u)\n",
+  ssl, s, ssl_xfer_buffer_rsize);
 
 ERR_clear_error();
 if (smtp_receive_timeout > 0) ALARM(smtp_receive_timeout);
-inbytes = SSL_read(ssl, CS ssl_xfer_buffer,
-		  MIN(ssl_xfer_buffer_size, lim));
+inbytes = SSL_read(ssl, CS s, MIN(ssl_xfer_buffer_rsize, lim));
 error = SSL_get_error(ssl, inbytes);
 if (smtp_receive_timeout > 0) ALARM_CLR(0);
 
@@ -4562,42 +4572,51 @@ non-SSL handling. */
 switch(error)
   {
   case SSL_ERROR_NONE:
+    DEBUG(tls) debug_printf("inbytes=%d\n", inbytes);
     break;
 
   case SSL_ERROR_ZERO_RETURN:
-    DEBUG(D_tls) debug_printf("Got SSL_ERROR_ZERO_RETURN\n");
+    DEBUG(tls) debug_printf("Got SSL_ERROR_ZERO_RETURN\n");
 
     if (SSL_get_shutdown(ssl) == SSL_RECEIVED_SHUTDOWN)
 	  SSL_shutdown(ssl);
 
-    tls_close_notify();
+    tls_close(NULL, TLS_NO_SHUTDOWN);
     return FALSE;
 
   /* Handle genuine errors */
   case SSL_ERROR_SSL:
-    {
-    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 */
-    ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring));
-    log_write(0, LOG_MAIN, "TLS error (SSL_read): on %s %s", conn_info, ssl_errstring);
-    ssl_xfer_error = TRUE;
+    if (SSL_get_shutdown(ssl) == SSL_RECEIVED_SHUTDOWN)
+      {
+      DEBUG(tls) debug_printf_indent("SSL_read: have rxd shutdown\n");
+      tls_close(NULL, TLS_SHUTDOWN_NOWAIT);
+      }
+    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 */
+      ERR_error_string_n(ERR_peek_error(), ssl_errstring, sizeof(ssl_errstring));
+
+      DEBUG(tls) tls_debug_err(ssl, US"SSL_read", inbytes);
+      log_write(LOG_MAIN, "TLS error (SSL_read): on %s %s", conn_info, ssl_errstring);
+      ssl_xfer_error = TRUE;
+      }
     return FALSE;
-    }
 
   default:
-    DEBUG(D_tls) debug_printf("Got SSL error %d\n", error);
-    DEBUG(D_tls) if (error == SSL_ERROR_SYSCALL)
+    DEBUG(tls) debug_printf("Got SSL error %d\n", error);
+    DEBUG(tls) if (error == SSL_ERROR_SYSCALL)
       debug_printf(" - syscall %s\n", strerror(errno));
     ssl_xfer_error = TRUE;
     return FALSE;
   }
 
 #ifndef DISABLE_DKIM
-smtp_verify_feed(ssl_xfer_buffer, inbytes);
+smtp_verify_feed(s, inbytes);
 #endif
-ssl_xfer_buffer_hwm = inbytes;
-ssl_xfer_buffer_lwm = 0;
+ssl_xfer_buffer_hwm = inbytes + IN_UNGETC_MAX;
+ssl_xfer_buffer_lwm = IN_UNGETC_MAX;
 return TRUE;
 }
 
@@ -4608,6 +4627,11 @@ return TRUE;
 
 /* This gets the next byte from the TLS input buffer. If the buffer is empty,
 it refills the buffer via the SSL reading function.
+If that errors - or the TLS inbound has been closed and we are receiving a
+mail message - return EOF.  For the closed case while between messages, drop
+to the cleartext layer getc; this supports continued-connection receive when
+the sender cannot maintain a TLS session (for Exim, when configured to not
+use a tls-proxy process).
 
 Arguments:  lim		Maximum amount to read/buffer
 Returns:    the next character or EOF
@@ -4616,11 +4640,11 @@ Only used by the server-side TLS.
 */
 
 int
-tls_getc(unsigned lim)
+tls_getc(const const in_processing * inp, unsigned lim)
 {
 if (ssl_xfer_buffer_lwm >= ssl_xfer_buffer_hwm)
   if (!tls_refill(lim))
-    return ssl_xfer_error ? EOF : smtp_getc(lim);
+    return ssl_xfer_error || message_id[0] ? EOF : inp_getc(inp->in_lower, lim);
 
 /* Something in the buffer; return next uschar */
 
@@ -4628,21 +4652,21 @@ return ssl_xfer_buffer[ssl_xfer_buffer_lwm++];
 }
 
 BOOL
-tls_hasc(void)
+tls_hasc(const const in_processing * inp)
 {
 return ssl_xfer_buffer_lwm < ssl_xfer_buffer_hwm;
 }
 
-uschar *
-tls_getbuf(unsigned * len)
+const uschar *
+tls_getbuf(const in_processing * inp, unsigned * len)
 {
 unsigned size;
-uschar * buf;
+const uschar * buf;
 
 if (ssl_xfer_buffer_lwm >= ssl_xfer_buffer_hwm)
   if (!tls_refill(*len))
     {
-    if (!ssl_xfer_error) return smtp_getbuf(len);
+    if (!ssl_xfer_error && !message_id[0]) return inp_getbuf(inp->in_lower, len);
     *len = 0;
     return NULL;
     }
@@ -4657,7 +4681,7 @@ return buf;
 
 
 void
-tls_get_cache(unsigned lim)
+tls_get_cache(const in_processing * inp, unsigned lim)
 {
 #ifndef DISABLE_DKIM
 int n = ssl_xfer_buffer_hwm - ssl_xfer_buffer_lwm;
@@ -4679,7 +4703,7 @@ if (ssl_xfer_buffer_lwm < ssl_xfer_buffer_hwm) return TRUE;
 
 FD_ZERO(&fds);
 FD_SET(tls_in.active.sock, &fds);
-return select(tls_in.active.sock+ 1, (SELECT_ARG2_TYPE *)&fds,
+return select(tls_in.active.sock + 1, (SELECT_ARG2_TYPE *)&fds,
 	      NULL, NULL, &tzero) > 0;
 }
 
@@ -4701,29 +4725,30 @@ Only used by the client-side TLS.
 */
 
 int
-tls_read(void * ct_ctx, uschar *buff, size_t len)
+tls_read(void * ct_ctx, uschar * buff, size_t len)
 {
 SSL * ssl = ct_ctx ? ((exim_openssl_client_tls_ctx *)ct_ctx)->ssl
 		  : state_server.lib_state.lib_ssl;
 int inbytes;
 int error;
 
-DEBUG(D_tls) debug_printf("Calling SSL_read(%p, %p, %u)\n", ssl,
-  buff, (unsigned int)len);
+DEBUG(tls) debug_printf("Calling SSL_read(tls_read %p, %p, %u)\n",
+  ssl, buff, (unsigned int)len);
 
 ERR_clear_error();
 inbytes = SSL_read(ssl, CS buff, len);
 error = SSL_get_error(ssl, inbytes);
 
-if (error == SSL_ERROR_ZERO_RETURN)
-  {
-  DEBUG(D_tls) debug_printf("Got SSL_ERROR_ZERO_RETURN\n");
-  return -1;
-  }
-else if (error != SSL_ERROR_NONE)
-  return -1;
+if (error == SSL_ERROR_NONE)
+  return inbytes;
 
-return inbytes;
+else DEBUG(tls)
+  if (error == SSL_ERROR_ZERO_RETURN)
+    debug_printf("Got SSL_ERROR_ZERO_RETURN\n");
+  else
+    tls_debug_err(ssl, US"SSL_read", inbytes);
+ERR_clear_error();
+return -1;
 }
 
 
@@ -4761,7 +4786,7 @@ gstring ** corkedp = ct_ctx
   ? &((exim_openssl_client_tls_ctx *)ct_ctx)->corked : &server_corked;
 gstring * corked = *corkedp;
 
-DEBUG(D_tls) debug_printf("%s(%p, %lu%s)\n", __FUNCTION__,
+DEBUG(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
@@ -4797,11 +4822,11 @@ if (more || corked)
 
 for (int left = len; left > 0;)
   {
-  DEBUG(D_tls) debug_printf("SSL_write(%p, %p, %d)\n", ssl, buff, left);
+  DEBUG(tls) debug_printf("SSL_write(%p, %p, %d)\n", ssl, buff, left);
   ERR_clear_error();
   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);
+  DEBUG(tls) debug_printf("outbytes=%d error=%d\n", outbytes, error);
   switch (error)
     {
     case SSL_ERROR_NONE:	/* the usual case */
@@ -4811,30 +4836,30 @@ for (int left = len; left > 0;)
 
     case SSL_ERROR_SSL:
       ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring));
-      log_write(0, LOG_MAIN, "TLS error (SSL_write): %s", ssl_errstring);
+      log_write(LOG_MAIN, "TLS error (SSL_write): %s", ssl_errstring);
       return -1;
 
     case SSL_ERROR_ZERO_RETURN:
-      log_write(0, LOG_MAIN, "SSL channel closed on write");
+      log_write(LOG_MAIN, "SSL channel closed on write");
       return -1;
 
     case SSL_ERROR_SYSCALL:
       if (errno == 0)
-	{ DEBUG(D_tls) debug_printf("- SSL_ERROR_SYSCALL with zero errno\n"); }
+	{ DEBUG(tls) debug_printf("- SSL_ERROR_SYSCALL with zero errno\n"); }
       else if (ct_ctx || errno != ECONNRESET || !f.smtp_in_quit)
-	log_write(0, LOG_MAIN, "SSL_write: (from %s) syscall: %s",
+	log_write(LOG_MAIN, "SSL_write: (from %s) syscall: %s",
 	  sender_fullhost ? sender_fullhost : US"<unknown>",
 	  strerror(errno));
       else if (LOGGING(protocol_detail))
-	log_write(0, LOG_MAIN, "[%s] after QUIT, client reset TCP before"
+	log_write(LOG_MAIN, "[%s] after QUIT, client reset TCP before"
 	  " SMTP response and TLS close\n", sender_host_address);
       else
-	DEBUG(D_tls) debug_printf("[%s] SSL_write: after QUIT,"
+	DEBUG(tls) debug_printf("[%s] SSL_write: after QUIT,"
 	  " client reset TCP before TLS close\n", sender_host_address);
       return -1;
 
     default:
-      log_write(0, LOG_MAIN, "SSL_write error %d", error);
+      log_write(LOG_MAIN, "SSL_write error %d", error);
       return -1;
     }
   }
@@ -4860,13 +4885,10 @@ if ((o_ctx ? tls_out.active.sock : tls_in.active.sock) < 0)
 
 tls_write(ct_ctx, NULL, 0, FALSE);	/* flush write buffer */
 
-HDEBUG(D_transport|D_tls|D_acl|D_v) debug_printf_indent("  SMTP(TLS shutdown)>>\n");
-rc = SSL_shutdown(ssl);
-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);
-  }
+HDEBUG(transport|tls|acl|v) debug_printf_indent("  SMTP(TLS shutdown)>>\n");
+ERR_clear_error();
+if ((rc = SSL_shutdown(ssl)) < 0)
+  DEBUG(tls) tls_debug_err(ssl, US"SSL_shutdown", rc);
 }
 
 /*************************************************
@@ -4900,11 +4922,12 @@ if (*fdp < 0) return;  /* TLS was not active */
 if (do_shutdown > TLS_NO_SHUTDOWN)
   {
   int rc;
-  DEBUG(D_tls) debug_printf("tls_close(): shutting down TLS%s\n",
+  DEBUG(tls) debug_printf("tls_close(): shutting down TLS%s\n",
     do_shutdown > TLS_SHUTDOWN_NOWAIT ? " (with response-wait)" : "");
 
   tls_write(ct_ctx, NULL, 0, FALSE);	/* flush write buffer */
 
+  ERR_clear_error();
   if (  (  do_shutdown >= TLS_SHUTDOWN_WONLY
 	|| (rc = SSL_shutdown(*sslp)) == 0	/* send "close notify" alert */
 	)
@@ -4919,11 +4942,7 @@ if (do_shutdown > TLS_NO_SHUTDOWN)
     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);
-    }
+  if (rc < 0) DEBUG(tls) tls_debug_err(*sslp, US"SSL_shutdown", rc);
   }
 
 if (!o_ctx)		/* server side */
@@ -4933,16 +4952,7 @@ if (!o_ctx)		/* server side */
   state_server.u_ocsp.server.verify_stack = NULL;
 #endif
 
-  receive_getc =	smtp_getc;
-  receive_getbuf =	smtp_getbuf;
-  receive_get_cache =	smtp_get_cache;
-  receive_hasc =	smtp_hasc;
-  receive_ungetc =	smtp_ungetc;
-  receive_feof =	smtp_feof;
-  receive_ferror =	smtp_ferror;
-  tls_in.active.tls_ctx = NULL;
-  tls_in.sni = NULL;
-  /* Leave bits, peercert, cipher, peerdn, certificate_verified set, for logging */
+  tls_receive_done();
   }
 
 SSL_free(*sslp);
@@ -4986,7 +4996,7 @@ normalise_ciphers(&expciphers, tls_require_ciphers);
 err = NULL;
 if (lib_ctx_new(&ctx, NULL, &err) == OK)
   {
-  DEBUG(D_tls)
+  DEBUG(tls)
     debug_printf("tls_require_ciphers expands to %q\n", expciphers);
 
   if (!SSL_CTX_set_cipher_list(ctx, CS expciphers))
@@ -5113,7 +5123,7 @@ i = RAND_bytes(smallbuf, needed_len);
 
 if (i < 0)
   {
-  DEBUG(D_all)
+  DEBUG(all)
     debug_printf("OpenSSL RAND_pseudo_bytes() not supported by RAND method, using fallback.\n");
   return vaguely_random_number_fallback(max);
   }
@@ -5223,7 +5233,7 @@ for (uschar * s = exp; *s; /**/)
     break;
   if (*s != '+' && *s != '-')
     {
-    DEBUG(D_tls) debug_printf("malformed openssl option setting: "
+    DEBUG(tls) debug_printf("malformed openssl option setting: "
         "+ or - expected but found %q\n", s);
     return FALSE;
     }
@@ -5233,10 +5243,10 @@ for (uschar * s = exp; *s; /**/)
   item_parsed = tls_openssl_one_option_parse(string_copyn(s, end-s), &item);
   if (!item_parsed)
     {
-    DEBUG(D_tls) debug_printf("openssl option setting unrecognised: %q\n", s);
+    DEBUG(tls) debug_printf("openssl option setting unrecognised: %q\n", s);
     return FALSE;
     }
-  DEBUG(D_tls) debug_printf("openssl option, %s %08lx: %08lx (%s)\n",
+  DEBUG(tls) debug_printf("openssl option, %s %08lx: %08lx (%s)\n",
       adding ? "adding to    " : "removing from", result, item, s);
   if (adding)
     result |= item;
@@ -5257,18 +5267,16 @@ void
 tls_state_in_to_out(int newfd, const uschar * ipaddr, int port)
 {
 exim_openssl_client_tls_ctx * exim_client_ctx;
-int old_pool = store_pool;
 
 state_server.is_server = FALSE;
 state_server.tlsp = &tls_out;
 client_static_state = &state_server;
 
-store_pool = POOL_PERM;
-exim_client_ctx = store_get(sizeof(exim_openssl_client_tls_ctx), GET_UNTAINTED);
+exim_client_ctx = store_get_perm(sizeof(exim_openssl_client_tls_ctx),
+				GET_UNTAINTED);
 exim_client_ctx->ctx = client_static_state->lib_state.lib_ctx;
 exim_client_ctx->ssl = client_static_state->lib_state.lib_ssl;
 exim_client_ctx->corked = NULL;
-store_pool = old_pool;
 
 SSL_set_fd(exim_client_ctx->ssl, newfd);
 
@@ -5287,7 +5295,7 @@ tls_state_out_to_in(int newfd, const uschar * ipaddr, int port)
 exim_openssl_client_tls_ctx * exim_client_ctx = tls_out.active.tls_ctx;
 
 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 = IN_UNGETC_MAX;
 ssl_xfer_eof = ssl_xfer_error = FALSE;
 
 state_server.is_server = TRUE;
diff --git a/src/src/tls.c b/src/src/tls.c
index dd82c7156..d3ae3e7db 100644
--- a/src/src/tls.c
+++ b/src/src/tls.c
@@ -25,6 +25,9 @@ functions from the OpenSSL or GNU TLS libraries. */
 # error One of USE_OPENSSL or USE_GNUTLS must be defined for a TLS build
 #endif
 
+/* Space to reserve at start of buffer for ungetc ops. */
+
+#define IN_UNGETC_MAX	2
 
 #if defined(MACRO_PREDEF) && !defined(DISABLE_TLS)
 # include "macro_predef.h"
@@ -71,11 +74,13 @@ functions and the common functions below.
 We're moving away from this; GnuTLS is already using a state, which
 can switch, so we can do TLS callouts during ACLs. */
 
-static const int ssl_xfer_buffer_size = 4096;
+static const int ssl_xfer_buffer_size = 8192;
+static const int ssl_xfer_buffer_rsize = 8192 - IN_UNGETC_MAX;
+
 #ifdef USE_OPENSSL
 static uschar *ssl_xfer_buffer = NULL;
-static int ssl_xfer_buffer_lwm = 0;
-static int ssl_xfer_buffer_hwm = 0;
+static int ssl_xfer_buffer_lwm = IN_UNGETC_MAX;		/* read point */
+static int ssl_xfer_buffer_hwm = IN_UNGETC_MAX;
 static int ssl_xfer_eof = FALSE;
 static BOOL ssl_xfer_error = FALSE;
 #endif
@@ -117,7 +122,7 @@ else if (  !(*result = expand_string(US s)) /* need to clean up const more */
 	)
   {
   *errstr = US"Internal error";
-  log_write(0, LOG_MAIN|LOG_PANIC, "expansion of %s failed: %s", name,
+  log_write(LOG_MAIN|LOG_PANIC, "expansion of %s failed: %s", name,
     expand_string_message);
   return FALSE;
   }
@@ -168,13 +173,13 @@ if (errno != EINVAL)
 /* not a symlink */
 s = string_copyn(filename, s - filename);	/* mem released by tls_set_watch */
 
-DEBUG(D_tls) debug_printf("watch dir '%s'\n", s);
+DEBUG(tls) debug_printf("watch dir '%s'\n", s);
 
 if (inotify_add_watch(tls_watch_fd, CCS s,
       IN_ONESHOT | IN_CLOSE_WRITE | IN_DELETE | IN_DELETE_SELF
       | IN_MOVED_FROM | IN_MOVED_TO | IN_MOVE_SELF) >= 0)
   return TRUE;
-DEBUG(D_tls) debug_printf("notify_add_watch: %s\n", strerror(errno));
+DEBUG(tls) debug_printf("notify_add_watch: %s\n", strerror(errno));
 return FALSE;
 }
 # endif
@@ -207,7 +212,7 @@ for (;;)
     {
     if ((fd1 = open(CCS filename, O_RDONLY | O_NOFOLLOW)) < 0)
       { s = US"open file"; goto bad; }
-    DEBUG(D_tls) debug_printf("watch file '%s':\t%d\n", filename, fd1);
+    DEBUG(tls) debug_printf("watch file '%s':\t%d\n", filename, fd1);
     EV_SET(&kev[kev_used++],
 	(uintptr_t)fd1,
 	EVFILT_VNODE,
@@ -218,7 +223,7 @@ for (;;)
 	NULL);
     cnt++;
     }
-  DEBUG(D_tls) debug_printf("watch dir  '%s':\t%d\n", s, fd2);
+  DEBUG(tls) debug_printf("watch dir  '%s':\t%d\n", s, fd2);
   EV_SET(&kev[kev_used++],
 	(uintptr_t)fd2,
 	EVFILT_VNODE,
@@ -251,7 +256,7 @@ if (kevent(tls_watch_fd, &kev[kev_used-cnt], cnt, NULL, 0, NULL) >= 0)
 s = US"kevent";
 
 bad:
-DEBUG(D_tls)
+DEBUG(tls)
   if (errno)
     debug_printf("%s: %s: %s\n", __FUNCTION__, s, strerror(errno));
   else
@@ -274,7 +279,7 @@ BOOL rc = FALSE;
 if (!filename || !*filename) return TRUE;
 if (Ustrncmp(filename, "system", 6) == 0) return TRUE;
 
-DEBUG(D_tls) debug_printf("tls_set_watch: '%s'\n", filename);
+DEBUG(tls) debug_printf("tls_set_watch: '%s'\n", filename);
 
 if (  tls_watch_fd < 0
 # ifdef EXIM_HAVE_INOTIFY
@@ -285,7 +290,7 @@ if (  tls_watch_fd < 0
 # endif
    )
     {
-    DEBUG(D_tls) debug_printf("inotify_init: %s\n", strerror(errno));
+    DEBUG(tls) debug_printf("inotify_init: %s\n", strerror(errno));
     return FALSE;
     }
 
@@ -301,7 +306,7 @@ else
   rc = tls_set_one_watch(filename);
 
 store_reset(r);
-if (!rc) DEBUG(D_tls) debug_printf("tls_set_watch() fail on '%s': %s\n", filename, strerror(errno));
+if (!rc) DEBUG(tls) debug_printf("tls_set_watch() fail on '%s': %s\n", filename, strerror(errno));
 return rc;
 }
 
@@ -345,7 +350,7 @@ if (tls_watch_fd < 0) return;
 /* Close the files we had open for kevent */
 for (int i = 0; i < kev_used; i++)
   {
-  DEBUG(D_tls) debug_printf("closing watch fd: %d\n", (int) kev[i].ident);
+  DEBUG(tls) debug_printf("closing watch fd: %d\n", (int) kev[i].ident);
   (void) close((int) kev[i].ident);
   kev[i].ident = (uintptr_t)-1;
   }
@@ -409,7 +414,7 @@ if (tls_creds_expire && time(NULL) >= tls_creds_expire)
   generate a new one.  Reload the rest of the creds also as the machinery
   is all there. */
 
-  DEBUG(D_tls) debug_printf("selfsign cert rotate\n");
+  DEBUG(tls) debug_printf("selfsign cert rotate\n");
   tls_creds_expire = 0;
   tls_daemon_creds_reload();
   return old_watch_fd;
@@ -421,7 +426,7 @@ else if (tls_watch_trigger_time && time(NULL) >= tls_watch_trigger_time + 5)
   Dump the set of watches and arrange to reload cached creds (which
   will set up new watches). */
 
-  DEBUG(D_tls) debug_printf("watch triggered\n");
+  DEBUG(tls) debug_printf("watch triggered\n");
   tls_watch_trigger_time = tls_creds_expire = 0;
   tls_daemon_creds_reload();
   return old_watch_fd;
@@ -491,8 +496,10 @@ static void tls_client_resmption_key(tls_support *, const smtp_connect_args *,
 *           TLS version of ungetc                *
 *************************************************/
 
-/* Puts a character back in the input buffer. Only ever
-called once.
+/* Put a character back in the input buffer. Usually only called once, with
+a char just gotten - but the bdat layer, for certain error situations, can call
+a sequence of two.
+
 Only used by the server-side TLS.
 
 Arguments:
@@ -502,10 +509,10 @@ Returns:       the character
 */
 
 int
-tls_ungetc(int ch)
+tls_ungetc(const in_processing * inp, int ch)
 {
 if (ssl_xfer_buffer_lwm <= 0)
-  log_write_die(0, LOG_MAIN, "buffer underflow in tls_ungetc");
+  log_write_die(LOG_MAIN, "buffer underflow in tls_ungetc");
 
 ssl_xfer_buffer[--ssl_xfer_buffer_lwm] = ch;
 return ch;
@@ -525,7 +532,7 @@ Returns:       non-zero if the eof flag is set
 */
 
 int
-tls_feof(void)
+tls_feof(const in_processing * inp)
 {
 return (int)ssl_xfer_eof;
 }
@@ -547,7 +554,7 @@ Returns:       non-zero if the error flag is set
 */
 
 int
-tls_ferror(void)
+tls_ferror(const in_processing * inp)
 {
 return (int)ssl_xfer_error;
 }
@@ -571,7 +578,65 @@ return ssl_xfer_buffer_lwm < ssl_xfer_buffer_hwm;
 }
 
 
+
+/******************************************************************************/
+/* Push TLS receive processing onto the stack. The functions do not directly use
+the underlying stack element, but this lets us unstack neatly on a TLS close.
+We do not expect more than one TLS layer active, so can use a static struct
+rather than allocating.
+
+Argument: current stack top.
+Return the putative stack top, but let the caller actually set the modification.
+*/
+
+const in_processing *
+tls_push_receive_functions(const in_processing * inp)
+{
+static in_processing tls_proc = {
+  .in_layer_name =	US"tls",
+  .in_getc =	tls_getc,
+  .in_getbuf =	tls_getbuf,
+  .in_getcache =tls_get_cache,
+  .in_hasc =	tls_hasc,
+  .in_ungetc =	tls_ungetc,
+  .in_feof =	tls_feof,
+  .in_ferror =	tls_ferror
+};
+
+/* Only legitimate over smtp layer. */
+
+if (ANY_DEBUG || f.running_in_test_harness)
+  if (Ustrcmp(inp->in_layer_name, "smtp") != 0)
+    log_write(LOG_PANIC_DIE,
+	      "%s: bad substrate %s", __FUNCTION__, inp->in_layer_name);
+
+tls_proc.in_lower = inp;
+return &tls_proc;
+}
+
+/* Pop TLS receive processing */
+
+const in_processing *
+tls_pop_receive_functions(const in_processing * inp)
+{
+const in_processing * lwr = inp->in_lower;
+
+if (ANY_DEBUG || f.running_in_test_harness)
+  {
+  if (Ustrcmp(inp->in_layer_name, "tls") != 0)
+    log_write(LOG_PANIC_DIE,
+	      "%s: bad current layer %s", __FUNCTION__, inp->in_layer_name);
+  if (Ustrcmp(lwr->in_layer_name, "smtp") != 0)
+    log_write(LOG_PANIC_DIE,
+	      "%s: bad substrate %s", __FUNCTION__, lwr->in_layer_name);
+  }
+
+return lwr;
+}
+
+
 #endif  /*DISABLE_TLS*/
+/******************************************************************************/
 
 void
 tls_modify_variables(tls_support * dest_tsp)
@@ -686,18 +751,18 @@ int cmp_sep = 0;
 if ((altnames = tls_cert_subject_altname(cert, US"dns")))
   {
   int alt_sep = '\n';
-  DEBUG(D_tls|D_lookup) debug_printf_indent("cert has SAN\n");
+  DEBUG(tls|lookup) debug_printf_indent("cert has SAN\n");
   while ((cmpname = string_nextinlist(&namelist, &cmp_sep, NULL, 0)))
     {
     const uschar * an = altnames;
-    DEBUG(D_tls|D_lookup) debug_printf_indent(" %s in SANs?", cmpname);
+    DEBUG(tls|lookup) debug_printf_indent(" %s in SANs?", cmpname);
     while ((certname = string_nextinlist(&an, &alt_sep, NULL, 0)))
       if (is_name_match(cmpname, certname))
 	{
-	DEBUG(D_tls|D_lookup) debug_printf_indent("  yes (matched %s)\n", certname);
+	DEBUG(tls|lookup) debug_printf_indent("  yes (matched %s)\n", certname);
 	return TRUE;
 	}
-    DEBUG(D_tls|D_lookup) debug_printf_indent(" no (end of SAN list)\n");
+    DEBUG(tls|lookup) debug_printf_indent(" no (end of SAN list)\n");
     }
   }
 
@@ -709,7 +774,7 @@ else if ((subjdn = tls_cert_subject(cert, NULL)))
   while ((cmpname = string_nextinlist(&namelist, &cmp_sep, NULL, 0)))
     {
     const uschar * sn = subjdn;
-    DEBUG(D_tls|D_lookup) debug_printf_indent(" %s in SN?", cmpname);
+    DEBUG(tls|lookup) debug_printf_indent(" %s in SN?", cmpname);
     while ((certname = string_nextinlist(&sn, &sn_sep, NULL, 0)))
       if (  *certname++ == 'C'
 	 && *certname++ == 'N'
@@ -717,10 +782,10 @@ else if ((subjdn = tls_cert_subject(cert, NULL)))
 	 && is_name_match(cmpname, certname)
 	 )
 	{
-	DEBUG(D_tls|D_lookup) debug_printf_indent("  yes (matched %s)\n", certname);
+	DEBUG(tls|lookup) debug_printf_indent("  yes (matched %s)\n", certname);
 	return TRUE;
 	}
-    DEBUG(D_tls|D_lookup) debug_printf_indent(" no (end of CN)\n");
+    DEBUG(tls|lookup) debug_printf_indent(" no (end of CN)\n");
     }
   }
 return FALSE;
@@ -747,13 +812,13 @@ if (path)
     unsetenv("SSLKEYLOGFILE");
   else if (*path != '/')
     {
-    DEBUG(D_tls)
+    DEBUG(tls)
       debug_printf("prepending spooldir to  env SSLKEYLOGFILE\n");
     setenv("SSLKEYLOGFILE", CCS string_sprintf("%s/%s", spool_directory, path), 1);
     }
   else if (Ustrncmp(path, spool_directory, Ustrlen(spool_directory)) != 0)
     {
-    DEBUG(D_tls)
+    DEBUG(tls)
       debug_printf("removing env SSLKEYLOGFILE=%s: not under spooldir\n", path);
     unsetenv("SSLKEYLOGFILE");
     }
@@ -791,7 +856,7 @@ if (  !tls_advertise_hosts
    )
   return TRUE;
 else if (!nowarn && !tls_certificate)
-  log_write(0, LOG_MAIN,
+  log_write(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");
 
@@ -799,7 +864,7 @@ oldsignal = signal(SIGCHLD, SIG_DFL);
 
 fflush(NULL);
 if ((pid = exim_fork(US"cipher-validate")) < 0)
-  log_write_die(0, LOG_MAIN, "fork failed for TLS check");
+  log_write_die(LOG_MAIN, "fork failed for TLS check");
 
 if (pid == 0)
   {
@@ -809,8 +874,7 @@ if (pid == 0)
         US"calling tls_validate_require_cipher");
 
   if ((errmsg = tls_validate_require_cipher()))
-    log_write_die(0, LOG_CONFIG,
-        "tls_require_ciphers invalid: %s", errmsg);
+    log_write_die(LOG_CONFIG, "tls_require_ciphers invalid: %s", errmsg);
   fflush(NULL);
   exim_underbar_exit(EXIT_SUCCESS);
   }
@@ -819,9 +883,9 @@ do {
   rc = waitpid(pid, &status, 0);
 } while (rc < 0 && errno == EINTR);
 
-DEBUG(D_tls)
-  debug_printf("tls_validate_require_cipher child %d ended: status=0x%x\n",
-      (int)pid, status);
+DEBUG(tls)
+  debug_printf("tls_validate_require_cipher child " PID_T_FMT
+      " ended: status=0x%x\n", pid, status);
 
 signal(SIGCHLD, oldsignal);
 
@@ -839,7 +903,7 @@ tls_client_resmption_key(tls_support * tlsp,
 hctx * h = &tlsp->resume_hctx;
 blob b;
 
-DEBUG(D_tls) if (conn_args->host_lbserver)
+DEBUG(tls) if (conn_args->host_lbserver)
   debug_printf("TLS: lbserver '%s'\n", conn_args->host_lbserver);
 
 # ifdef EXIM_HAVE_SHA2
@@ -850,7 +914,7 @@ exim_sha_init(h, HASH_SHA1);
 exim_sha_update_string(h, conn_args->host_lbserver);
 # ifdef SUPPORT_DANE
 if (conn_args->dane)
-  exim_sha_update(h,  CUS &conn_args->tlsa_dnsa, sizeof(dns_answer));
+  exim_sha_update(h,  CUS conn_args->tlsa_dnsa, sizeof(dns_answer));
 # endif
 exim_sha_update_string(h, conn_args->host->address);
 exim_sha_update(h,   CUS &conn_args->host->port, sizeof(conn_args->host->port));
@@ -863,7 +927,7 @@ exim_sha_update_string(h, ob->tls_alpn);
 # endif
 exim_sha_finish(h, &b);
 tlsp->resume_index = string_sprintf("%.*H", (int)b.len, b.data);
-DEBUG(D_tls) debug_printf("TLS: resume session index %s\n", tlsp->resume_index);
+DEBUG(tls) debug_printf("TLS: resume session index %s\n", tlsp->resume_index);
 #endif
 }
 
@@ -879,7 +943,10 @@ tls_client_adjunct_start(host_item * host, client_conn_ctx * cctx,
 {
 union sockaddr_46 interface_sock;
 EXIM_SOCKLEN_T size = sizeof(interface_sock);
+
+/* Large (64k if DANE supported) */
 smtp_connect_args conn_args = {.host = host };
+
 tls_support tls_dummy = { .sni = NULL };
 uschar * errstr;
 
diff --git a/src/src/tlscert-gnu.c b/src/src/tlscert-gnu.c
index 6bbea7516..159905f41 100644
--- a/src/src/tlscert-gnu.c
+++ b/src/src/tlscert-gnu.c
@@ -37,7 +37,7 @@ const uschar * cp;
 if ((fail = gnutls_x509_crt_export((gnutls_x509_crt_t)cert,
     GNUTLS_X509_FMT_PEM, buf, &sz)))
   {
-  log_write(0, LOG_MAIN, "TLS error in certificate export: %s",
+  log_write(LOG_MAIN, "TLS error in certificate export: %s",
     gnutls_strerror(fail));
   return FALSE;
   }
@@ -71,7 +71,7 @@ datum.data = string_unprinting(US buf);
 datum.size = Ustrlen(datum.data);
 if ((rc = gnutls_x509_crt_import(crt, &datum, GNUTLS_X509_FMT_PEM)))
   {
-  log_write(0, LOG_MAIN, "TLS error in certificate import: %s",
+  log_write(LOG_MAIN, "TLS error in certificate import: %s",
     gnutls_strerror(rc));
   crt = NULL;
   }
@@ -150,6 +150,7 @@ if ((ret = gnutls_x509_crt_get_issuer_dn(cert, CS cp, &siz))
     != GNUTLS_E_SHORT_MEMORY_BUFFER)
   return g_err("gi0", __FUNCTION__, ret);
 
+/*XXX we might want to distinguish ourcert from peercert (but this is safe) */
 cp = store_get(siz, GET_TAINTED);
 if ((ret = gnutls_x509_crt_get_issuer_dn(cert, CS cp, &siz)) < 0)
   return g_err("gi1", __FUNCTION__, ret);
@@ -226,14 +227,15 @@ return algo < 0 ? NULL : string_copy(US gnutls_sign_get_name(algo));
 uschar *
 tls_cert_subject(void * cert, const uschar * mod)
 {
-uschar * cp = NULL;
+uschar * cp;
 int ret;
 size_t siz = 0;
 
-if ((ret = gnutls_x509_crt_get_dn(cert, CS cp, &siz))
+if ((ret = gnutls_x509_crt_get_dn(cert, NULL, &siz))
     != GNUTLS_E_SHORT_MEMORY_BUFFER)
   return g_err("gs0", __FUNCTION__, ret);
 
+/*XXX we might want to distinguish ourcert from peercert (but this is safe) */
 cp = store_get(siz, GET_TAINTED);
 if ((ret = gnutls_x509_crt_get_dn(cert, CS cp, &siz)) < 0)
   return g_err("gs1", __FUNCTION__, ret);
@@ -262,6 +264,7 @@ 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);
 
+/*XXX we might want to distinguish ourcert from peercert (but this is safe) */
 cp1 = store_get(siz*4 + 1, GET_TAINTED);
 
 ret = gnutls_x509_crt_get_extension_by_oid ((gnutls_x509_crt_t)cert,
@@ -317,6 +320,7 @@ for (int index = 0;; index++)
       return g_err("gs0", __FUNCTION__, ret);
     }
 
+  /*XXX we might want to distinguish ourcert from peercert (but this is safe) */
   ele = store_get(siz+1, GET_TAINTED);
   if ((ret = gnutls_x509_crt_get_subject_alt_name(
     (gnutls_x509_crt_t)cert, index, ele, &siz, NULL)) < 0)
@@ -347,7 +351,8 @@ tls_cert_ocsp_uri(void * cert, const uschar * mod)
 gnutls_datum_t uri;
 int ret;
 uschar sep = '\n';
-gstring * list = NULL;
+/*XXX we might want to distinguish ourcert from peercert (but this is safe) */
+gstring * list = string_get_tainted(0, GET_TAINTED);
 
 if (mod)
   if (*mod == '>' && *++mod) sep = *mod++;
@@ -381,7 +386,8 @@ tls_cert_crl_uri(void * cert, const uschar * mod)
 {
 int ret;
 uschar sep = '\n';
-gstring * list = NULL;
+/*XXX we might want to distinguish ourcert from peercert (but this is safe) */
+gstring * list = string_get_tainted(0, GET_TAINTED);
 
 if (mod)
   if (*mod == '>' && *++mod) sep = *mod++;
@@ -394,7 +400,7 @@ for (int index = 0;; index++)
     (gnutls_x509_crt_t)cert, index, NULL, &siz, NULL, NULL))
     {
     case GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE:
-      return string_from_gstring(list);
+      return gstring_length(list) > 0 ? string_from_gstring(list) : NULL;
     case GNUTLS_E_SHORT_MEMORY_BUFFER:
       break;
     default:
@@ -429,7 +435,7 @@ if (  (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",
+  log_write(LOG_MAIN, "TLS error in certificate export: %s",
     gnutls_strerror(fail));
   return NULL;
   }
@@ -449,6 +455,7 @@ if ((ret = gnutls_x509_crt_get_fingerprint(cert, algo, NULL, &siz))
     != GNUTLS_E_SHORT_MEMORY_BUFFER)
   return g_err("gf0", __FUNCTION__, ret);
 
+/*XXX we might want to distinguish ourcert from peercert (but this is safe) */
 cp = store_get(siz*3+1, GET_TAINTED);
 if ((ret = gnutls_x509_crt_get_fingerprint(cert, algo, cp, &siz)) < 0)
   return g_err("gf1", __FUNCTION__, ret);
diff --git a/src/src/tlscert-openssl.c b/src/src/tlscert-openssl.c
index cdac8f6b2..ce4951c5e 100644
--- a/src/src/tlscert-openssl.c
+++ b/src/src/tlscert-openssl.c
@@ -45,7 +45,7 @@ BIO * bp = BIO_new(BIO_s_mem());
 BOOL fail;
 
 if ((fail = PEM_write_bio_X509(bp, (X509 *)cert) ? 0 : 1))
-  log_write(0, LOG_MAIN, "TLS error in certificate export: %s",
+  log_write(LOG_MAIN, "TLS error in certificate export: %s",
     ERR_error_string(ERR_get_error(), NULL));
 else
   {
@@ -79,7 +79,7 @@ if (x) X509_free(x);
 
 bp = BIO_new_mem_buf(US cp, -1);
 if (!(x = PEM_read_bio_X509(bp, NULL, 0, NULL)))
-  log_write(0, LOG_MAIN, "TLS error in certificate import: %s",
+  log_write(LOG_MAIN, "TLS error in certificate import: %s",
     ERR_error_string(ERR_get_error(), NULL));
 
 *cert = (void *)x;
@@ -213,7 +213,9 @@ uschar *
 tls_cert_issuer(void * cert, const uschar * mod)
 {
 uschar * cp = x509_name_copy(X509_get_issuer_name((X509 *)cert));
-return mod ? tls_field_from_dn(cp, mod) : cp;
+if (mod) cp = tls_field_from_dn(cp, mod);
+/*XXX we might want to distinguish ourcert from peercert (but this is safe) */
+return cp ? string_copy_taint(cp, GET_TAINTED) : cp;
 }
 
 uschar *
@@ -312,7 +314,9 @@ uschar *
 tls_cert_subject(void * cert, const uschar * mod)
 {
 uschar * cp = x509_name_copy(X509_get_subject_name((X509 *)cert));
-return mod ? tls_field_from_dn(cp, mod) : cp;
+if (mod) cp = tls_field_from_dn(cp, mod);
+/*XXX we might want to distinguish ourcert from peercert (but this is safe) */
+return cp ? string_copy_taint(cp, GET_TAINTED) : cp;
 }
 
 uschar *
@@ -345,6 +349,7 @@ M_ASN1_OCTET_STRING_print(bp, adata);
 /* binary data, DER encoded */
 /* just dump for now */
 len = BIO_get_mem_data(bp, &cp1);
+/*XXX we might want to distinguish ourcert from peercert (but this is safe) */
 cp3 = cp2 = store_get(len*3+1, GET_TAINTED);
 
 while(len)
@@ -360,14 +365,13 @@ return cp3;
 uschar *
 tls_cert_subject_altname(void * cert, const uschar * mod)
 {
-gstring * list = NULL;
 STACK_OF(GENERAL_NAME) * san = (STACK_OF(GENERAL_NAME) *)
   X509_get_ext_d2i((X509 *)cert, NID_subject_alt_name, NULL, NULL);
 uschar osep = '\n';
-uschar * tag = US"";
-uschar * ele;
-int match = -1;
-int len;
+uschar * tag = US"", * ele;
+int match = -1,  len;
+/*XXX we might want to distinguish ourcert from peercert (but this is safe) */
+gstring * list = string_get_tainted(0, GET_TAINTED);
 
 if (!san) return NULL;
 
@@ -417,7 +421,7 @@ while (sk_GENERAL_NAME_num(san) > 0)
   }
 
 sk_GENERAL_NAME_free(san);
-return string_from_gstring(list);
+return gstring_length(list) > 0 ? string_from_gstring(list) : NULL;
 }
 
 uschar *
@@ -427,7 +431,8 @@ STACK_OF(ACCESS_DESCRIPTION) * ads = (STACK_OF(ACCESS_DESCRIPTION) *)
   X509_get_ext_d2i((X509 *)cert, NID_info_access, NULL, NULL);
 int adsnum = sk_ACCESS_DESCRIPTION_num(ads);
 uschar sep = '\n';
-gstring * list = NULL;
+/*XXX we might want to distinguish ourcert from peercert (but this is safe) */
+gstring * list = string_get_tainted(0, GET_TAINTED);
 
 if (mod)
   if (*mod == '>' && *++mod) sep = *mod++;
@@ -442,7 +447,7 @@ for (int i = 0; i < adsnum; i++)
       ASN1_STRING_length(ad->location->d.ia5));
   }
 sk_ACCESS_DESCRIPTION_free(ads);
-return string_from_gstring(list);
+return gstring_length(list) > 0 ? string_from_gstring(list) : NULL;
 }
 
 uschar *
@@ -453,7 +458,8 @@ STACK_OF(DIST_POINT) * dps = (STACK_OF(DIST_POINT) *)
     NULL, NULL);
 DIST_POINT * dp;
 uschar sep = '\n';
-gstring * list = NULL;
+/*XXX we might want to distinguish ourcert from peercert (but this is safe) */
+gstring * list = string_get_tainted(0, GET_TAINTED);
 
 if (mod)
   if (*mod == '>' && *++mod) sep = *mod++;
@@ -473,7 +479,7 @@ if (dps) for (int i = 0, dpsnum = sk_DIST_POINT_num(dps); i < dpsnum; i++)
 	  ASN1_STRING_length(np->d.uniformResourceIdentifier));
     }
 sk_DIST_POINT_free(dps);
-return string_from_gstring(list);
+return gstring_length(list) > 0 ? string_from_gstring(list) : NULL;
 }
 
 
@@ -488,7 +494,7 @@ 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",
+  log_write(LOG_MAIN, "TLS error in certificate export: %s",
     ERR_error_string(ERR_get_error(), NULL));
 else
   {
diff --git a/src/src/transport.c b/src/src/transport.c
index d88653964..9644d9c4a 100644
--- a/src/src/transport.c
+++ b/src/src/transport.c
@@ -215,11 +215,11 @@ for (transport_instance * t = transports; t; t = t->drinst.next)
   {
   const transport_info * ti = t->drinst.info;
   if (!ti->local && t->shadow)
-    log_write_die(0, LOG_CONFIG,
+    log_write_die(LOG_CONFIG,
       "shadow transport not allowed on non-local transport %s", t->drinst.name);
 
   if (t->body_only && t->headers_only)
-    log_write_die(0, LOG_CONFIG,
+    log_write_die(LOG_CONFIG,
       "%s transport: body_only and headers_only are mutually exclusive",
       t->drinst.name);
   }
@@ -300,7 +300,7 @@ normal cases, it is only ever executed once. */
 
 for (int i = 0; i < 100; i++)
   {
-  DEBUG(D_transport)
+  DEBUG(transport)
     debug_printf("writing data block fd=%d size=%d timeout=%d%s\n",
       fd, len, local_timeout, more ? " (more expected)" : "");
 
@@ -353,7 +353,7 @@ for (int i = 0; i < 100; i++)
     len -= rc;
     block += rc;
     transport_count += rc;
-    DEBUG(D_transport) debug_printf("write incomplete (%d)\n", rc);
+    DEBUG(transport) debug_printf("write incomplete (%d)\n", rc);
     goto CHECK_TIMEOUT;   /* A few lines below */
     }
 
@@ -362,7 +362,7 @@ for (int i = 0; i < 100; i++)
 
   if (save_errno == EINTR)
     {
-    DEBUG(D_transport)
+    DEBUG(transport)
       debug_printf("write interrupted before anything written\n");
     goto CHECK_TIMEOUT;   /* A few lines below */
     }
@@ -372,7 +372,7 @@ for (int i = 0; i < 100; i++)
 
   if (save_errno == EAGAIN)
     {
-    DEBUG(D_transport)
+    DEBUG(transport)
       debug_printf("write temporarily locked out, waiting 1 sec\n");
     sleep(1);
 
@@ -390,7 +390,7 @@ for (int i = 0; i < 100; i++)
 
   /* Otherwise there's been an error */
 
-  DEBUG(D_transport) debug_printf("writing error %d: %s\n", save_errno,
+  DEBUG(transport) debug_printf("writing error %d: %s\n", save_errno,
     strerror(save_errno));
   errno = save_errno;
   return FALSE;
@@ -455,7 +455,7 @@ that the result will never be expanded. */
 
 va_start(ap, format);
 if (!string_vformat(&gs, SVFMT_TAINT_NOCHK, format, ap))
-  log_write_die(0, LOG_MAIN, "overlong formatted string in transport");
+  log_write_die(LOG_MAIN, "overlong formatted string in transport");
 va_end(ap);
 tctx.u.fd = fd;
 return transport_write_block(&tctx, gs.s, gs.ptr, FALSE);
@@ -548,7 +548,7 @@ for (const uschar * ptr = start; ptr < end; ptr++)
 
   if ((fl_len = chunk_ptr - deliver_out_buffer) > mlen)
     {
-    DEBUG(D_transport) debug_printf("flushing headers buffer\n");
+    DEBUG(transport) debug_printf("flushing headers buffer\n");
 
     /* If CHUNKING, prefix with BDAT (size) NON-LAST.  Also, reap responses
     from previous SMTP commands. */
@@ -705,10 +705,10 @@ Returns:    FALSE if writing failed
 */
 
 static BOOL
-write_env_to(address_item *p, struct aci **pplist, struct aci **pdlist,
-  BOOL *first, transport_ctx * tctx)
+write_env_to(address_item * p, struct aci ** pplist, struct aci ** pdlist,
+  BOOL * first, transport_ctx * tctx)
 {
-address_item *pp;
+address_item * pp;
 struct aci *ppp;
 
 /* Do nothing if we have already handled this address. If not, remember it
@@ -725,8 +725,7 @@ ppp->ptr = p;
 
 for (pp = p;; pp = pp->parent)
   {
-  address_item *dup;
-  for (dup = addr_duplicate; dup; dup = dup->next)
+  for (address_item * 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;
@@ -835,8 +834,13 @@ for (header_line * h = header_list; h; h = h->next) if (h->type != htype_old)
 		  tblock->rewrite_existflags, FALSE)))
 	{
 	len = hh->slen;
-	if (tctx->options & topt_truncate_headers && len > 998) len = 998;
-	if (!sendfn(tctx, hh->text, len)) return FALSE;
+	if (tctx->options & topt_truncate_headers && len > 998)
+	  {
+	  if (  !sendfn(tctx, hh->text, 991)
+	     || !sendfn(tctx, US"<trunc>\n", 8)) return FALSE;
+	  }
+	else
+	  if (!sendfn(tctx, hh->text, len)) return FALSE;
 	store_reset(reset_point);
 	continue;     /* With the next header line */
 	}
@@ -845,14 +849,19 @@ for (header_line * h = header_list; h; h = h->next) if (h->type != htype_old)
     /* Either no rewriting rules, or it didn't get rewritten */
 
     len = h->slen;
-    if (tctx->options & topt_truncate_headers && len > 998) len = 998;
-    if (!sendfn(tctx, h->text, len)) return FALSE;
+    if (tctx->options & topt_truncate_headers && len > 998)
+      {
+      if (  !sendfn(tctx, h->text, 991)
+	 || !sendfn(tctx, US"<trunc>\n", 8)) return FALSE;
+      }
+    else
+      if (!sendfn(tctx, h->text, len)) return FALSE;
     }
 
   /* Header removed */
 
   else
-    DEBUG(D_transport) debug_printf("removed header line:\n %s---\n", h->text);
+    DEBUG(transport) debug_printf("removed header line:\n %s---\n", h->text);
   }
 
 /* Add on any address-specific headers. If there are multiple addresses,
@@ -879,7 +888,7 @@ if (addr)
       if (i == 1)
 	{
 	if (!sendfn(tctx, h->text, h->slen)) return FALSE;
-	DEBUG(D_transport)
+	DEBUG(transport)
 	  debug_printf("added header line(s):\n %s---\n", h->text);
 	}
       }
@@ -906,7 +915,7 @@ if (tblock && (list = CUS tblock->add_headers))
 	if (!sendfn(tctx, s, len)) return FALSE;
 	if (s[len-1] != '\n' && !sendfn(tctx, US"\n", 1))
 	  return FALSE;
-	DEBUG(D_transport)
+	DEBUG(transport)
 	  {
 	  debug_printf("added header line:\n %s", s);
 	  if (s[len-1] != '\n') debug_printf("\n");
@@ -991,7 +1000,8 @@ Returns:                TRUE on success; FALSE (with errno) on failure.
 static BOOL
 internal_transport_write_message(transport_ctx * tctx, int size_limit)
 {
-int len, size = 0;
+int len;
+size_t size = 0;
 
 /* Initialize pointer in output buffer. */
 
@@ -1126,7 +1136,7 @@ if (tctx->options & topt_use_bdat)
 
   if (size > DELIVER_OUT_BUFFER_SIZE && hsize > 0)
     {
-    DEBUG(D_transport)
+    DEBUG(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)
@@ -1176,7 +1186,7 @@ if (  f.spool_file_wireformat
     size -= len;
     }
 
-  DEBUG(D_transport) debug_printf("using sendfile for body\n");
+  DEBUG(transport) debug_printf("using sendfile for body\n");
 
   while(size > 0)
     {
@@ -1186,10 +1196,10 @@ if (  f.spool_file_wireformat
   return copied >= 0;
   }
 #else
-DEBUG(D_transport) debug_printf("cannot use sendfile for body: no support\n");
+DEBUG(transport) debug_printf("cannot use sendfile for body: no support\n");
 #endif
 
-DEBUG(D_transport)
+DEBUG(transport)
   if (!(tctx->options & topt_no_body))
     debug_printf("cannot use sendfile for body: %s\n",
       !f.spool_file_wireformat ? "spoolfile not wireformat"
@@ -1316,9 +1326,9 @@ write_pid = (pid_t)(-1);
   }
 if (filter_pid < 0) goto TIDY_UP;      /* errno set */
 
-DEBUG(D_transport)
-  debug_printf("process %d running as transport filter: fd_write=%d fd_read=%d\n",
-    (int)filter_pid, fd_write, fd_read);
+DEBUG(transport) debug_printf("process " PID_T_FMT
+    " running as transport filter: fd_write=%d fd_read=%d\n",
+    filter_pid, fd_write, fd_read);
 
 /* Fork subprocess to write the message to the filter, and return the result
 via a(nother) pipe. While writing to the filter, we do not do the CRLF,
@@ -1371,15 +1381,15 @@ if (write_pid < 0)
 
 testharness_pause_ms(250);
 
-DEBUG(D_transport)
-  debug_printf("process %d writing to transport filter\n", (int)write_pid);
+DEBUG(transport)
+  debug_printf("process "PID_T_FMT " writing to transport filter\n", write_pid);
 
 /* Copy the message from the filter to the output fd. A read error leaves len
 == -1 and errno set. We need to apply a timeout to the read, to cope with
 the case when the filter gets stuck, but it can be quite a long one. The
 default is 5m, but this is now configurable. */
 
-DEBUG(D_transport) debug_printf("copying from the filter\n");
+DEBUG(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
@@ -1397,7 +1407,7 @@ for (;;)
   ALARM_CLR(0);
   if (sigalrm_seen)
     {
-    DEBUG(D_transport) debug_printf("timed out reading from filter\n");
+    DEBUG(transport) debug_printf("timed out reading from filter\n");
     errno = ETIMEDOUT;
     f.transport_filter_timed_out = TRUE;
     goto TIDY_UP;
@@ -1441,20 +1451,20 @@ if (!yield)
 
 /* Wait for the filter process to complete. */
 
-DEBUG(D_transport) debug_printf("waiting for filter process\n");
+DEBUG(transport) debug_printf("waiting for filter process\n");
 if (filter_pid > 0 && (rc = child_close(filter_pid, 30)) != 0 && yield)
   {
   yield = FALSE;
   save_errno = ERRNO_FILTER_FAIL;
   tctx->addr->more_errno = rc;
-  DEBUG(D_transport) debug_printf("filter process returned %d\n", rc);
+  DEBUG(transport) debug_printf("filter process returned %d\n", rc);
   }
 
 /* Wait for the writing process to complete. If it ends successfully,
 read the results from its pipe, provided we haven't already had a filter
 process failure. */
 
-DEBUG(D_transport) debug_printf("waiting for writing process\n");
+DEBUG(transport) debug_printf("waiting for writing process\n");
 if (write_pid > 0)
   {
   rc = child_close(write_pid, 30);
@@ -1464,7 +1474,7 @@ if (write_pid > 0)
       BOOL ok;
       if (read(pfd[pipe_read], (void *)&ok, sizeof(BOOL)) != sizeof(BOOL))
 	{
-	DEBUG(D_transport)
+	DEBUG(transport)
 	  debug_printf("pipe read from writing process: %s\n", strerror(errno));
 	save_errno = ERRNO_FILTER_FAIL;
         yield = FALSE;
@@ -1483,7 +1493,7 @@ if (write_pid > 0)
       yield = FALSE;
       save_errno = ERRNO_FILTER_FAIL;
       tctx->addr->more_errno = rc;
-      DEBUG(D_transport) debug_printf("writing process returned %d\n", rc);
+      DEBUG(transport) debug_printf("writing process returned %d\n", rc);
       }
   }
 (void)close(pfd[pipe_read]);
@@ -1512,7 +1522,7 @@ if (yield)
 else
   errno = save_errno;      /* From some earlier error */
 
-DEBUG(D_transport)
+DEBUG(transport)
   {
   debug_printf("end of filtering transport writing: yield=%d\n", yield);
   if (!yield)
@@ -1569,12 +1579,12 @@ open_db dbblock, * dbp;
 
 if (!is_new_message_id(message_id))
   {
-  DEBUG(D_transport) debug_printf("message_id %s is not new format; "
+  DEBUG(transport) debug_printf("message_id %s is not new format; "
     "skipping wait-%s database update\n", message_id, tpname);
   return;
   }
 
-DEBUG(D_transport)
+DEBUG(transport)
   { debug_printf("updating wait-%s database\n", tpname); acl_level++; }
 
 /* Open the database (or transaction) for this transport */
@@ -1624,7 +1634,7 @@ for (host_item * host = hostlist; host; host = host->next)
 
     if (!is_new_message_id(s))
       {
-      DEBUG(D_hints_lookup)
+      DEBUG(hints_lookup)
 	debug_printf_indent("NOTE: old or corrupt message-id found in wait=%.200s"
 	  " hints DB; deleting records for %s\n", tpname, host->name);
 
@@ -1660,7 +1670,7 @@ for (host_item * host = hostlist; host; host = host->next)
 
   if (already)
     {
-    DEBUG(D_transport) debug_printf("already listed for %s\n", host->name);
+    DEBUG(transport) debug_printf("already listed for %s\n", host->name);
     continue;
     }
 
@@ -1703,7 +1713,7 @@ for (host_item * host = hostlist; host; host = host->next)
   /* Update the database */
 
   dbfn_write(dbp, host->name, host_record, sizeof(dbdata_wait) + host_length);
-  DEBUG(D_transport) debug_printf("added %.*s to queue for %s\n",
+  DEBUG(transport) debug_printf("added %.*s to queue for %s\n",
 				  MESSAGE_ID_LENGTH, message_id, host->name);
   }
 
@@ -1715,7 +1725,7 @@ else
   dbfn_close(dbp);
 
 out:
-  DEBUG(D_transport) acl_level--;
+  DEBUG(transport) acl_level--;
   return;
 }
 
@@ -1768,7 +1778,7 @@ open_db dbblock, * dbp;
 int         i;
 struct stat statbuf;
 
-DEBUG(D_transport)
+DEBUG(transport)
   {
   debug_printf("transport_check_waiting entered\n");
   debug_printf("  sequence=%d local_max=%d global_max=%d\n",
@@ -1782,7 +1792,7 @@ connection. */
 if (connection_max_messages >= 0) local_message_max = connection_max_messages;
 if (local_message_max > 0 && continue_sequence >= local_message_max)
   {
-  DEBUG(D_transport)
+  DEBUG(transport)
     debug_printf_indent("max messages for one connection reached: returning\n");
   goto retfalse;
   }
@@ -1795,7 +1805,7 @@ if ( continue_wait_db
 		      O_RDWR, &dbblock, TRUE, TRUE))
    )
   {
-  DEBUG(D_transport)
+  DEBUG(transport)
     debug_printf_indent("no messages waiting for %s\n", hostname);
   goto retfalse;
   }
@@ -1804,7 +1814,7 @@ if ( continue_wait_db
 
 if (!(host_record = dbfn_read(dbp, hostname)))
   {
-  DEBUG(D_transport)
+  DEBUG(transport)
     debug_printf_indent("no messages waiting for %s\n", hostname);
   goto dbclose_false;
   }
@@ -1814,7 +1824,7 @@ don't try to use it. */
 
 if (host_record->count > WAIT_NAME_MAX)
   {
-  log_write(0, LOG_MAIN|LOG_PANIC, "smtp-wait database entry for %s has bad "
+  log_write(LOG_MAIN|LOG_PANIC, "smtp-wait database entry for %s has bad "
     "count=%d (max=%d)", hostname, host_record->count, WAIT_NAME_MAX);
   goto dbclose_false;
   }
@@ -1849,7 +1859,7 @@ while (1)
     if (!is_new_message_id(host_record->text + (i * MESSAGE_ID_LENGTH)))
       {
       uschar buffer[256];
-      DEBUG(D_hints_lookup)
+      DEBUG(hints_lookup)
 	debug_printf_indent("NOTE: old or corrupt message-id found in"
 	  " wait=%.200s hints DB; deleting records for %s\n",
 	  transport_name, hostname);
@@ -1872,7 +1882,7 @@ while (1)
   for (i = 0; i < msgq_count; ++i)
     if (Ustrcmp(msgq[i].message_id, message_id) == 0)
       {
-      DEBUG(D_hints_lookup)
+      DEBUG(hints_lookup)
 	debug_printf_indent("dropping current msg from list\n");
       msgq[i].bKeep = FALSE;
       break;
@@ -1890,7 +1900,7 @@ while (1)
       msgq[i].bKeep = FALSE;
     else if (!oicf_func || oicf_func(mid, oicf_data))
       {
-      DEBUG(D_hints_lookup)
+      DEBUG(hints_lookup)
 	debug_printf_indent("acceptable next: %s\n", mid);
       Ustrcpy_nt(new_message_id, mid);
       msgq[i].bKeep = FALSE;
@@ -1902,7 +1912,7 @@ while (1)
   /* re-count */
   for (msgq_actual = 0, i = 0; i < msgq_count; ++i)
     if (msgq[i].bKeep) msgq_actual++;
-  DEBUG(D_hints_lookup)
+  DEBUG(hints_lookup)
     debug_printf_indent("%d left in this record\n", msgq_actual);
 
   /* reassemble the host record, based on removed message ids, from in
@@ -1971,7 +1981,7 @@ while (1)
 
   if (host_length <= 0)
     {
-    DEBUG(D_transport|D_hints_lookup)
+    DEBUG(transport|hints_lookup)
       debug_printf_indent("waiting messages already delivered\n");
     goto dbclose_false;
     }
@@ -1981,7 +1991,7 @@ while (1)
 
   if (!bContinuation)
     {
-    DEBUG(D_hints_lookup) debug_printf_indent("no further records\n");
+    DEBUG(hints_lookup) debug_printf_indent("no further records\n");
     Ustrcpy(new_message_id, message_id);
     goto dbclose_false;
     }
@@ -2003,7 +2013,7 @@ if (continue_wait_db)
 else
   dbfn_close(dbp);
 
-DEBUG(D_transport)
+DEBUG(transport)
   {
   acl_level--;
   debug_printf("transport_check_waiting: TRUE (found %s)\n", new_message_id);
@@ -2017,7 +2027,7 @@ dbclose_false:
     dbfn_close(dbp);
 
 retfalse:
-  DEBUG(D_transport)
+  DEBUG(transport)
     {acl_level--; debug_printf("transport_check_waiting: FALSE\n"); }
   return FALSE;
 }
@@ -2026,16 +2036,17 @@ retfalse:
 *    Deliver waiting message down same socket    *
 *************************************************/
 
-/* Just the regain-root-privilege exec portion */
+/* Just the regain-root-privilege exec portion.
+Callers: delivery_re_exec(), deliver_message() */
+
 void
-transport_do_pass_socket(const uschar * transport_name, const uschar * hostname,
-  const uschar * hostaddress, int hostport, uschar * id, int socket_fd)
+transport_do_pass_socket(uschar * id, int socket_fd)
 {
 int i = 14;
 const uschar **argv;
 
 #ifndef DISABLE_TLS
-if (smtp_peer_options & OPTION_TLS) i += 6;
+if (cutthrough.peer_options & OPTION_TLS) i += 6;
 #endif
 #ifndef DISABLE_ESMTP_LIMITS
 if (continue_limit_mail || continue_limit_rcpt || continue_limit_rcptdom)
@@ -2052,27 +2063,27 @@ 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";
+if (cutthrough.peer_options & OPTION_CHUNKING)	argv[i++] = US"-MCK";
+if (cutthrough.peer_options & OPTION_DSN)	argv[i++] = US"-MCD";
+if (cutthrough.peer_options & OPTION_PIPE)	argv[i++] = US"-MCP";
+if (cutthrough.peer_options & OPTION_SIZE)	argv[i++] = US"-MCS";
 #ifndef DISABLE_TLS
-if (smtp_peer_options & OPTION_TLS)
-  if (tls_out.active.sock >= 0 || continue_proxy_cipher)
+if (cutthrough.peer_options & OPTION_TLS)
+  if (cutthrough.is_tls)
     {
     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;
+    argv[i++] = cutthrough.snd_ip;
+    argv[i++] = string_sprintf("%d", cutthrough.snd_port);
+    argv[i++] = cutthrough.cipher;
 
-    if (tls_out.sni)
+    if (cutthrough.sni)
       {
       argv[i++] =
 #ifdef SUPPORT_DANE
-        tls_out.dane_verified ? US"-MCr" :
+        cutthrough.is_dane ? US"-MCr" :
 #endif
         US"-MCs";
-      argv[i++] = tls_out.sni;
+      argv[i++] = cutthrough.sni;
       }
     }
   else
@@ -2108,11 +2119,11 @@ if (proxy_session)
 #endif
 
 argv[i++] = US"-MC";
-argv[i++] = US transport_name;
-argv[i++] = US hostname;
-argv[i++] = US hostaddress;
-argv[i++] = string_sprintf("%d", hostport);
-argv[i++] = string_sprintf("%d", continue_sequence + 1);
+argv[i++] = US cutthrough.transport;
+argv[i++] = US cutthrough.host.name;
+argv[i++] = US cutthrough.host.address;
+argv[i++] = string_sprintf("%d", cutthrough.host.port);
+argv[i++] = string_sprintf("%d", continue_sequence + 1); /*XXX always 0+1 */
 argv[i++] = id;
 argv[i++] = NULL;
 
@@ -2124,12 +2135,12 @@ if (socket_fd != 0)
   (void)close(socket_fd);
   }
 
-DEBUG(D_exec) debug_print_argv(argv);
+DEBUG(exec) debug_print_argv(argv);
 exim_nullstd();                          /* Ensure std{out,err} exist */
 /* argv[0] should be untainted, from child_exec_exim() */
 execv(CS argv[0], (char *const *)argv);
 
-DEBUG(D_any) debug_printf("execv failed: %s\n", strerror(errno));
+DEBUG(any) debug_printf("execv failed: %s\n", strerror(errno));
 _exit(errno);         /* Note: must be _exit(), NOT exit() */
 }
 
@@ -2258,7 +2269,7 @@ If the parent of the top address has an original part of "system-filter", this
 pipe was set up by the system filter, and we can permit the expansion of
 $recipients. */
 
-DEBUG(D_transport)
+DEBUG(transport)
   {
   debug_printf("direct command:\n");
   for (int i = 0; argv[i]; i++)
@@ -2272,7 +2283,7 @@ if (flags & TSUC_EXPAND_ARGS)
 
   for (int i = 0; argv[i]; i++)
     {
-    DEBUG(D_expand) debug_printf_indent("arg %d\n", i);
+    DEBUG(expand) debug_printf_indent("arg %d\n", i);
 
     /* Handle special fudge for passing an address list */
 
@@ -2304,7 +2315,7 @@ if (flags & TSUC_EXPAND_ARGS)
 	This is a hole in the taint-pretection, mitigated only in that
 	shell-syntax metachars cannot be injected via this route. */
 
-	DEBUG(D_transport) if (is_tainted(ad->address))
+	DEBUG(transport) if (is_tainted(ad->address))
 	  debug_printf("tainted element '%s' from $pipe_addresses\n", ad->address);
 
 	argv[i++] = ad->address;
@@ -2329,7 +2340,7 @@ if (flags & TSUC_EXPAND_ARGS)
       /* We can never have more then the argv we will be loading into */
       address_pipe_max_args = max_args - argcount + 1;
 
-      DEBUG(D_transport)
+      DEBUG(transport)
         debug_printf("address_pipe_max_args=%d\n", address_pipe_max_args);
 
       /* We allocate an additional for (uschar *)0 */
@@ -2447,7 +2458,7 @@ if (flags & TSUC_EXPAND_ARGS)
       if (  f.running_in_test_harness && is_tainted(expanded_arg)
 	 && Ustrcmp(etext, "queryprogram router") == 0)
 	{			/* hack, would be good to not need it */
-	DEBUG(D_transport)
+	DEBUG(transport)
 	  debug_printf("SPECIFIC TESTSUITE EXEMPTION: tainted arg '%s'\n",
 		      expanded_arg);
 	}
@@ -2458,7 +2469,7 @@ if (flags & TSUC_EXPAND_ARGS)
       }
     }
 
-  DEBUG(D_transport)
+  DEBUG(transport)
     {
     debug_printf("direct command after expansion:\n");
     for (int i = 0; argv[i]; i++)
diff --git a/src/src/transports/Makefile b/src/src/transports/Makefile
index 454d36347..761cf01c8 100644
--- a/src/src/transports/Makefile
+++ b/src/src/transports/Makefile
@@ -11,11 +11,12 @@
 
 # MAGIC-TAG-MODS-OBJ-RULES-GO-HERE
 
-OBJ += smtp_socks.o tf_maildir.o
+OBJ += tf_maildir.o
 
-all:		transports.a $(MODS)
+all:	transports.a $(MODS)
+	$(FE):
 
-transports.a:    $(OBJ) smtp_socks.o tf_maildir.o
+transports.a:    $(OBJ) tf_maildir.o
 		 @$(RM_COMMAND) -f transports.a
 		 @echo "$(AR) transports.a"
 		 @$(AR) transports.a $(OBJ)
@@ -38,7 +39,6 @@ lmtp.o lmtp.so:			lmtp.c lmtp.h
 pipe.o pipe.so:			pipe.c pipe.h
 queuefile.o queuefile.so:	queuefile.c queuefile.h
 smtp.o:				smtp.c smtp.h
-smtp_socks.o:			smtp_socks.c smtp.h
 
 tf_maildir.o:			tf_maildir.c tf_maildir.h appendfile.h
 
diff --git a/src/src/transports/appendfile.c b/src/src/transports/appendfile.c
index ebf11b7f7..acbcd73d6 100644
--- a/src/src/transports/appendfile.c
+++ b/src/src/transports/appendfile.c
@@ -343,7 +343,7 @@ if (ob->lock_retries == 0) ob->lock_retries = 1;
 /* Only one of a file name or directory name must be given. */
 
 if (ob->filename && ob->dirname)
-  log_write_die(0, LOG_CONFIG_FOR, "%s transport:\n  "
+  log_write_die(LOG_CONFIG_FOR, "%s transport:\n  "
   "only one of \"file\" or \"directory\" can be specified", trname);
 
 /* If a file name was specified, neither quota_filecount nor quota_directory
@@ -352,10 +352,10 @@ must be given. */
 if (ob->filename)
   {
   if (ob->quota_filecount)
-    log_write_die(0, LOG_CONFIG_FOR, "%s transport:\n  "
+    log_write_die(LOG_CONFIG_FOR, "%s transport:\n  "
       "quota_filecount must not be set without \"directory\"", trname);
   if (ob->quota_directory)
-    log_write_die(0, LOG_CONFIG_FOR, "%s transport:\n  "
+    log_write_die(LOG_CONFIG_FOR, "%s transport:\n  "
       "quota_directory must not be set without \"directory\"", trname);
   }
 
@@ -370,7 +370,7 @@ requested, the default for fcntl is FALSE. */
 if (ob->use_flock)
   {
   #ifdef NO_FLOCK
-  log_write_die(0, LOG_CONFIG_FOR, "%s transport:\n  "
+  log_write_die(LOG_CONFIG_FOR, "%s transport:\n  "
     "flock() support was not available in the operating system when this "
     "binary was built", trname);
   #endif  /* NO_FLOCK */
@@ -395,7 +395,7 @@ if (ob->mbx_format)
 #endif  /* SUPPORT_MBX */
 
 if (!ob->use_fcntl && !ob->use_flock && !ob->use_lockfile && !ob->use_mbx_lock)
-  log_write_die(0, LOG_CONFIG_FOR, "%s transport:\n  "
+  log_write_die(LOG_CONFIG_FOR, "%s transport:\n  "
     "no locking configured", trname);
 
 /* Unset timeouts for non-used locking types */
@@ -410,20 +410,20 @@ be set. */
 if (ob->dirname)
   {
   if (ob->maildir_format && ob->mailstore_format)
-    log_write_die(0, LOG_CONFIG_FOR, "%s transport:\n  "
+    log_write_die(LOG_CONFIG_FOR, "%s transport:\n  "
       "only one of maildir and mailstore may be specified", trname);
   if (ob->quota_filecount != NULL && !ob->quota)
-    log_write_die(0, LOG_CONFIG_FOR, "%s transport:\n  "
+    log_write_die(LOG_CONFIG_FOR, "%s transport:\n  "
       "quota must be set if quota_filecount is set", trname);
   if (ob->quota_directory != NULL && !ob->quota)
-    log_write_die(0, LOG_CONFIG_FOR, "%s transport:\n  "
+    log_write_die(LOG_CONFIG_FOR, "%s transport:\n  "
       "quota must be set if quota_directory is set", trname);
   }
 
 /* If a fixed uid field is set, then a gid field must also be set. */
 
 if (tblock->uid_set && !tblock->gid_set && !tblock->expand_gid)
-  log_write_die(0, LOG_CONFIG,
+  log_write_die(LOG_CONFIG,
     "user set without group for the %s transport", trname);
 
 /* If "create_file" is set, check that a valid option is given, and set the
@@ -436,7 +436,7 @@ if ((s = ob->create_file_string ) && *s)
   else if (*s == '/' || Ustrcmp(s, "belowhome") == 0)	val = create_belowhome;
   else if (Ustrcmp(s, "inhome") == 0)			val = create_inhome;
   else
-    log_write_die(0, LOG_CONFIG,
+    log_write_die(LOG_CONFIG,
       "invalid value given for \"create_file\" for the %s transport: '%s'",
       trname, s);
   ob->create_file = val;
@@ -520,13 +520,13 @@ struct servent *sp;
 host_item host;
 uschar * s;
 
-DEBUG(D_transport) debug_printf("notify_comsat called\n");
+DEBUG(transport) debug_printf("notify_comsat called\n");
 
 s = string_sprintf("%.200s@" OFF_T_FMT "\n", user, offset);
 
 if ((sp = getservbyname("biff", "udp")) == NULL)
   {
-  DEBUG(D_transport) debug_printf("biff/udp is an unknown service");
+  DEBUG(transport) debug_printf("biff/udp is an unknown service");
   return;
   }
 
@@ -543,7 +543,7 @@ can be changed. (But actually, comsat is probably dying out anyway.) */
 /******
 if (host_find_byname(&host, NULL, 0, NULL, FALSE) == HOST_FIND_FAILED)
   {
-  DEBUG(D_transport) debug_printf("\"localhost\" unknown\n");
+  DEBUG(transport) debug_printf("\"localhost\" unknown\n");
   return;
   }
 ******/
@@ -556,7 +556,7 @@ for (host_item * h = &host; h; h = h->next)
   int sock, rc;
   int host_af = Ustrchr(h->address, ':') != NULL ? AF_INET6 : AF_INET;
 
-  DEBUG(D_transport) debug_printf("calling comsat on %s\n", h->address);
+  DEBUG(transport) debug_printf("calling comsat on %s\n", h->address);
 
   if ((sock = ip_socket(SOCK_DGRAM, host_af)) < 0) continue;
 
@@ -567,7 +567,7 @@ for (host_item * h = &host; h; h = h->next)
   (void)close(sock);
 
   if (rc >= 0) break;
-  DEBUG(D_transport)
+  DEBUG(transport)
     debug_printf("send to comsat failed for %s: %s\n", strerror(errno),
       h->address);
   }
@@ -602,7 +602,7 @@ int len = read(cfd, data, sizeof(data));
 int sep = 0;
 const uschar * s;
 
-DEBUG(D_transport) debug_printf("checking file format\n");
+DEBUG(transport) debug_printf("checking file format\n");
 
 /* An empty file matches the current transport */
 
@@ -622,7 +622,7 @@ while ((s = string_nextinlist(&format, &sep, big_buffer, big_buffer_size)))
     for (transport_instance * tt = transports; tt; tt = tt->drinst.next)
       if (Ustrcmp(tp, tt->drinst.name) == 0)
         {
-        DEBUG(D_transport)
+        DEBUG(transport)
           debug_printf("file format -> %s transport\n", tt->drinst.name);
         return tt;
         }
@@ -710,7 +710,7 @@ for (struct dirent * ent; ent = readdir(dir); )
       if (endptr == name + ovec[3])
         {
         sum += size;
-        DEBUG(D_transport)
+        DEBUG(transport)
           debug_printf("check_dir_size: size from %s is " OFF_T_FMT "\n", name,
             size);
 	/* pcre2_match_data_free(md);	gen ctx needs no free */
@@ -718,7 +718,7 @@ for (struct dirent * ent; ent = readdir(dir); )
         }
       }
     /* pcre2_match_data_free(md);	gen ctx needs no free */
-    DEBUG(D_transport)
+    DEBUG(transport)
       debug_printf("check_dir_size: regex did not match %s\n", name);
     }
 
@@ -728,7 +728,7 @@ for (struct dirent * ent; ent = readdir(dir); )
 
   if (Ustat(path, &statbuf) < 0)
     {
-    DEBUG(D_transport)
+    DEBUG(transport)
       debug_printf("check_dir_size: stat error %d for %s: %s\n", errno, path,
         strerror(errno));
     }
@@ -740,7 +740,7 @@ for (struct dirent * ent; ent = readdir(dir); )
   }
 
 closedir(dir);
-DEBUG(D_transport)
+DEBUG(transport)
   debug_printf("check_dir_size: dir=%s sum=" OFF_T_FMT " count=%d\n", dirname,
     sum, count);
 
@@ -867,7 +867,7 @@ if (saved_size == 0)
     return DEFER;
   }
 
-DEBUG(D_transport) debug_printf("copying MBX message from temporary file\n");
+DEBUG(transport) debug_printf("copying MBX message from temporary file\n");
 
 /* Now construct the message's header from the time and the RFC822 file
 size, including CRLFs, which is the size of the input (temporary) file. */
@@ -987,7 +987,7 @@ if (deliver_dir  &&  create_file != create_anywhere)
       if (Ustrncmp(rph, big_buffer, rlen) != 0)
         {
         yield = FALSE;
-        DEBUG(D_transport) debug_printf("Real path %q does not match %q\n",
+        DEBUG(transport) debug_printf("Real path %q does not match %q\n",
           big_buffer, deliver_dir);
         }
       }
@@ -1189,7 +1189,7 @@ int maildir_save_errno;
 #endif
 
 
-DEBUG(D_transport) debug_printf("appendfile transport entered\n");
+DEBUG(transport) debug_printf("appendfile transport entered\n");
 
 /* An "address_file" or "address_directory" transport is used to deliver to
 files specified via .forward or an alias file. Prior to release 4.20, the
@@ -1299,7 +1299,7 @@ else
     mbf_unix;
   }
 
-DEBUG(D_transport)
+DEBUG(transport)
   {
   debug_printf("appendfile: mode=%o notify_comsat=%d quota=" OFF_T_FMT
     "%s%s"
@@ -1329,7 +1329,7 @@ DEBUG(D_transport)
 
 if (f.dont_deliver)
   {
-  DEBUG(D_transport)
+  DEBUG(transport)
     debug_printf("*** delivery by %s transport bypassed by -N option\n",
       trname);
   addr->transport_return = OK;
@@ -1366,7 +1366,7 @@ if (!isdirectory)
      && ob->create_file == create_belowhome)
     if (is_tainted(path))
       {
-      DEBUG(D_transport) debug_printf("below-home: de-tainting path '%s'\n", path);
+      DEBUG(transport) debug_printf("below-home: de-tainting path '%s'\n", path);
       path = string_copy_taint(path, GET_UNTAINTED);
       }
 
@@ -1388,7 +1388,7 @@ if (!isdirectory)
       addr->message =
         string_sprintf("failed to create directories for %s: %s", path,
           exim_errstr(errno));
-      DEBUG(D_transport) debug_printf("%s transport: %s\n", trname, path);
+      DEBUG(transport) debug_printf("%s transport: %s\n", trname, path);
       return FALSE;
       }
     }
@@ -1564,9 +1564,9 @@ if (!isdirectory)
     /* cf. exim_lock.c */
     lockname = string_sprintf("%s.lock", filename);
     hitchname = string_sprintf( "%s.%s.%08x.%08x", lockname, primary_hostname,
-      (unsigned int)(time(NULL)), (unsigned int)getpid());
+      (unsigned int)(time(NULL)), (unsigned long)getpid());
 
-    DEBUG(D_transport) debug_printf("lock name: %s\nhitch name: %s\n", lockname,
+    DEBUG(transport) debug_printf("lock name: %s\nhitch name: %s\n", lockname,
       hitchname);
 
     /* Lock file creation retry loop */
@@ -1580,9 +1580,9 @@ if (!isdirectory)
         {
         addr->basic_errno = errno;
         addr->message =
-          string_sprintf("creating lock file hitching post %s "
-            "(euid=%ld egid=%ld)", hitchname, (long int)geteuid(),
-            (long int)getegid());
+          string_sprintf("creating lock file hitching post %s %s "
+            "(euid=%ld egid=%ld)", hitchname,
+	    strerror(errno), (long int)geteuid(), (long int)getegid());
         return FALSE;
         }
 
@@ -1608,14 +1608,14 @@ if (!isdirectory)
         if (ob->lockfile_timeout > 0 && Ustat(lockname, &statbuf) == 0 &&
             time(NULL) - statbuf.st_ctime > ob->lockfile_timeout)
           {
-          DEBUG(D_transport) debug_printf("unlinking timed-out lock file\n");
+          DEBUG(transport) debug_printf("unlinking timed-out lock file\n");
           Uunlink(lockname);
           }
-        DEBUG(D_transport) debug_printf("link of hitching post failed - retrying\n");
+        DEBUG(transport) debug_printf("link of hitching post failed - retrying\n");
         continue;
         }
 
-      DEBUG(D_transport) debug_printf("lock file created\n");
+      DEBUG(transport) debug_printf("lock file created\n");
       break;
       }
 
@@ -1809,7 +1809,7 @@ if (!isdirectory)
         int diffs = oldmode ^ mode;
         if (addr->mode > 0 || (diffs & oldmode) == diffs)
           {
-          DEBUG(D_transport) debug_printf("chmod %o %s\n", mode, filename);
+          DEBUG(transport) debug_printf("chmod %o %s\n", mode, filename);
           if (Uchmod(filename, mode) < 0)
             {
             addr->basic_errno = errno;
@@ -1854,8 +1854,8 @@ if (!isdirectory)
           }
         addr->basic_errno = errno;
         if (isfifo)
-          addr->message = string_sprintf("while opening named pipe %s "
-            "(could mean no process is reading it)", filename);
+          addr->message = string_sprintf("while opening named pipe %q: %s "
+            "(could mean no process is reading it)", filename, strerror(errno));
         else if (errno != EWOULDBLOCK)
           addr->message = string_sprintf("while opening mailbox %s", filename);
         goto RETURN;
@@ -2092,18 +2092,18 @@ if (!isdirectory)
               statbuf.st_dev == ostatbuf.st_dev &&
               statbuf.st_ino == ostatbuf.st_ino)
             break;
-          DEBUG(D_transport) debug_printf("MBX lockfile %s changed "
+          DEBUG(transport) debug_printf("MBX lockfile %s changed "
             "between creation and locking\n", mbx_lockname);
           }
 
-        DEBUG(D_transport) debug_printf("failed to lock %s: %s\n", mbx_lockname,
+        DEBUG(transport) debug_printf("failed to lock %s: %s\n", mbx_lockname,
           strerror(errno));
         (void)close(mbx_lockfd);
         mbx_lockfd = -1;
         }
       else
         {
-        DEBUG(D_transport) debug_printf("failed to fstat or get read lock on %s: %s\n",
+        DEBUG(transport) debug_printf("failed to fstat or get read lock on %s: %s\n",
           filename, strerror(errno));
         }
       }
@@ -2111,7 +2111,7 @@ if (!isdirectory)
 
     else break;   /* No on-file locking required; break the open/lock loop */
 
-    DEBUG(D_transport)
+    DEBUG(transport)
       debug_printf("fcntl(), flock(), or MBX locking failed - retrying\n");
 
     (void)close(fd);
@@ -2155,7 +2155,7 @@ if (!isdirectory)
     goto RETURN;
     }
 
-  DEBUG(D_transport) debug_printf("mailbox %s is locked\n", filename);
+  DEBUG(transport) debug_printf("mailbox %s is locked\n", filename);
 
   /* Save access time (for subsequent restoration), modification time (for
   restoration if updating fails), size of file (for comsat and for re-setting if
@@ -2206,7 +2206,7 @@ else
   if (is_tainted(path))
     if (ob->create_file == create_belowhome)
       {
-      DEBUG(D_transport) debug_printf("below-home: de-tainting path '%s'\n", path);
+      DEBUG(transport) debug_printf("below-home: de-tainting path '%s'\n", path);
       path = string_copy_taint(path, GET_UNTAINTED);
       }
     else
@@ -2240,7 +2240,7 @@ else
 		  MCS_NOFLAGS, &addr->message, pcre_gen_cmp_ctx)))
         return FALSE;
 
-      DEBUG(D_transport) debug_printf("using regex for file sizes: %s\n",
+      DEBUG(transport) debug_printf("using regex for file sizes: %s\n",
         ob->quota_size_regex);
       }
 
@@ -2291,7 +2291,7 @@ else
             {
             *slash = 0;
             check_path = new_check_path;
-            DEBUG(D_transport) debug_printf("maildirfolder file exists: "
+            DEBUG(transport) debug_printf("maildirfolder file exists: "
               "quota check directory changed to %s\n", check_path);
             }
           }
@@ -2322,7 +2322,7 @@ else
 	    MCS_NOFLAGS, &addr->message, pcre_gen_cmp_ctx)))
         return FALSE;
 
-      DEBUG(D_transport)
+      DEBUG(transport)
         debug_printf("using regex for maildir directory selection: %s\n",
           ob->maildir_dir_regex);
 
@@ -2339,7 +2339,7 @@ else
 	if (!regex_match(dir_regex, s, -1, NULL))
           {
           disable_quota = TRUE;
-          DEBUG(D_transport) debug_printf("delivery directory does not match "
+          DEBUG(transport) debug_printf("delivery directory does not match "
             "maildir_quota_directory_regex: disabling quota\n");
           }
         }
@@ -2402,7 +2402,7 @@ else
     {
     off_t size;
     int filecount = 0;
-    DEBUG(D_transport)
+    DEBUG(transport)
       debug_printf("quota checks on directory %s\n", check_path);
     size = check_dir_size(check_path, &filecount,  re);
     if (mailbox_size < 0) mailbox_size = size;
@@ -2417,10 +2417,10 @@ else
 
   if (mbformat == mbf_smail)
     {
-    DEBUG(D_transport)
+    DEBUG(transport)
       debug_printf("delivering to new file in %s\n", path);
-    filename = dataname =
-      string_sprintf("%s/temp.%d.%s", path, (int)getpid(), primary_hostname);
+    filename = dataname = string_sprintf("%s/temp." PID_T_FMT ".%s",
+					path, getpid(), primary_hostname);
     fd = Uopen(filename, O_WRONLY|O_CREAT, mode);
     if (fd < 0 &&                                 /* failed to open, and */
         (errno != ENOENT ||                       /* either not non-exist */
@@ -2441,7 +2441,7 @@ else
 
   else if (mbformat == mbf_maildir)
     {
-    DEBUG(D_transport)
+    DEBUG(transport)
       debug_printf("delivering in maildir format in %s\n", path);
 
     GET_OPTION("maildir_tag");
@@ -2484,7 +2484,7 @@ else
         {
         if ((fd = Uopen(filename, O_WRONLY | O_CREAT | O_EXCL, mode)) >= 0)
 	  break;
-        DEBUG (D_transport) debug_printf ("open failed for %s: %s\n",
+        DEBUG(transport) debug_printf ("open failed for %s: %s\n",
           filename, strerror(errno));
         }
 
@@ -2492,7 +2492,7 @@ else
 
       if (i >= ob->maildir_retries)
         {
-	DEBUG(D_transport)
+	DEBUG(transport)
 	  {
 	  char buf[PATH_MAX];
 	  debug_printf(" (euid=%ld egid=%ld cwd=%s)\n",
@@ -2542,7 +2542,7 @@ else
     mailstore_basename = string_sprintf("%s/%s-%s", path, message_id,
       string_base62_64((long int)getpid()));
 
-    DEBUG(D_transport)
+    DEBUG(transport)
       debug_printf("delivering in mailstore format in %s\n", path);
 
     filename = string_sprintf("%s.tmp", mailstore_basename);
@@ -2648,7 +2648,7 @@ else
       return FALSE;
       }
 
-    DEBUG(D_transport) debug_printf("Envelope file %s written\n", filename);
+    DEBUG(transport) debug_printf("Envelope file %s written\n", filename);
 
     /* Now open the data file, and ensure that it has the correct ownership and
     mode. */
@@ -2689,7 +2689,7 @@ else
 prefix line, and followed by any configured suffix line. If there are any
 writing errors, we must defer. */
 
-DEBUG(D_transport) debug_printf("writing to file %s\n", dataname);
+DEBUG(transport) debug_printf("writing to file %s\n", dataname);
 
 yield = OK;
 errno = 0;
@@ -2701,7 +2701,7 @@ included in the check). */
 
 if (!disable_quota && ob->quota_value > 0)
   {
-  DEBUG(D_transport)
+  DEBUG(transport)
     {
     debug_printf("Exim quota = " OFF_T_FMT " old size = " OFF_T_FMT
       " this message = %d (%sincluded)\n",
@@ -2714,24 +2714,24 @@ if (!disable_quota && ob->quota_value > 0)
   if (mailbox_size + (ob->quota_is_inclusive ? message_size:0) > ob->quota_value)
     if (!ob->quota_no_check)
       {
-      DEBUG(D_transport) debug_printf("mailbox quota exceeded\n");
+      DEBUG(transport) debug_printf("mailbox quota exceeded\n");
       yield = DEFER;
       errno = ERRNO_EXIMQUOTA;
       }
     else
-      DEBUG(D_transport) debug_printf("mailbox quota exceeded but ignored\n");
+      DEBUG(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");
+      DEBUG(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)
+    else DEBUG(transport) if (ob->quota_filecount_no_check)
       debug_printf("mailbox file count quota exceeded but ignored\n");
 
   }
@@ -2741,7 +2741,7 @@ if (verify_mode)
   addr->basic_errno = errno;
   addr->message = US"Over quota";
   addr->transport_return = yield;
-  DEBUG(D_transport)
+  DEBUG(transport)
     debug_printf("appendfile (verify) yields %d with errno=%d more_errno=%d\n",
       yield, addr->basic_errno, addr->more_errno);
 
@@ -2767,7 +2767,7 @@ if (yield == OK && ob->mbx_format)
     }
   save_fd = fd;
   fd = fileno(temp_file);
-  DEBUG(D_transport) debug_printf("writing to temporary file\n");
+  DEBUG(transport) debug_printf("writing to temporary file\n");
   }
 #endif  /* SUPPORT_MBX */
 
@@ -2936,7 +2936,7 @@ if (!disable_quota && THRESHOLD_CHECK)
   off_t threshold = ob->quota_warn_threshold_value;
   if (ob->quota_warn_threshold_is_percent)
     threshold = (off_t)(((double)ob->quota_value * threshold) / 100);
-  DEBUG(D_transport)
+  DEBUG(transport)
     debug_printf("quota = " OFF_T_FMT
       " threshold = " OFF_T_FMT
       " old size = " OFF_T_FMT
@@ -2990,14 +2990,14 @@ if (yield != OK)
       struct stat statbuf;
       if (Ustat("new", &statbuf) < 0)
         {
-        DEBUG(D_transport) debug_printf("maildir quota exceeded: "
+        DEBUG(transport) debug_printf("maildir quota exceeded: "
           "stat error %d for \"new\": %s\n", errno, strerror(errno));
         }
       else   /* Want a repeatable time when in test harness */
         addr->more_errno = f.running_in_test_harness ? 10 :
           (int)time(NULL) - statbuf.st_mtime;
 
-      DEBUG(D_transport)
+      DEBUG(transport)
         debug_printf("maildir: time since \"new\" directory modified = %s\n",
         readconf_printtime(addr->more_errno));
       }
@@ -3017,7 +3017,7 @@ if (yield != OK)
     addr->message = US"mailbox is full";
     #endif  /* EDQUOT */
     addr->user_message = US"mailbox is full";
-    DEBUG(D_transport) debug_printf("System quota exceeded for %s%s%s\n",
+    DEBUG(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));
@@ -3031,7 +3031,7 @@ if (yield != OK)
       "(MTA-imposed%s quota exceeded while writing to %s)", filecount_msg,
         dataname);
     addr->user_message = US"mailbox is full";
-    DEBUG(D_transport) debug_printf("Exim%s quota exceeded for %s%s%s\n",
+    DEBUG(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));
@@ -3084,7 +3084,7 @@ if (yield != OK)
   fcntl() call (BSDI & FreeBSD do not). */
 
   if (!isdirectory && ftruncate(fd, saved_size))
-    DEBUG(D_transport) debug_printf("Error resetting file size\n");
+    DEBUG(transport) debug_printf("Error resetting file size\n");
   }
 
 /* Handle successful writing - we want the modification time to be now for
@@ -3129,7 +3129,7 @@ else
       uschar *renamename = newname;
       fd = -1;
 
-      DEBUG(D_transport) debug_printf("renaming temporary file\n");
+      DEBUG(transport) debug_printf("renaming temporary file\n");
 
       /* If there is no rename name set, we are in a non-maildir, non-mailstore
       situation. The name is built by expanding the directory_file option, and
@@ -3170,7 +3170,7 @@ else
           renamename = string_sprintf("%s/%s", path, renameleaf);
           if (Ulink(filename, renamename) < 0)
             {
-            DEBUG(D_transport) debug_printf("link failed: %s\n",
+            DEBUG(transport) debug_printf("link failed: %s\n",
               strerror(errno));
             if (errno != EEXIST || i >= 4 ||
                 Ustrcmp(renameleaf, old_renameleaf) == 0)
@@ -3182,7 +3182,7 @@ else
               break;
               }
             old_renameleaf = renameleaf;
-            DEBUG(D_transport) debug_printf("%s exists - trying again\n",
+            DEBUG(transport) debug_printf("%s exists - trying again\n",
               renamename);
             }
           else
@@ -3240,7 +3240,7 @@ else
 
         else
           {
-          DEBUG(D_transport) debug_printf("renamed %s as %s\n", filename,
+          DEBUG(transport) debug_printf("renamed %s as %s\n", filename,
             renamename);
           filename = dataname = NULL;   /* Prevents attempt to unlink at end */
           }
@@ -3263,7 +3263,7 @@ if (ob->notify_comsat && yield == OK && deliver_localpart)
 
 /* Pass back the final return code in the address structure */
 
-DEBUG(D_transport)
+DEBUG(transport)
   debug_printf("appendfile yields %d with errno=%d more_errno=%d\n",
     yield, addr->basic_errno, addr->more_errno);
 
@@ -3287,7 +3287,7 @@ if (mbx_lockfd >= 0)
   if (yield == OK && apply_lock(fd, F_WRLCK, ob->use_fcntl, 0,
       ob->use_flock, 0) >= 0)
     {
-    DEBUG(D_transport)
+    DEBUG(transport)
       debug_printf("unlinking MBX lock file %s\n", mbx_lockname);
     Uunlink(mbx_lockname);
     }
@@ -3343,12 +3343,23 @@ ret_panic:
 transport_info appendfile_transport_info = {
 .drinfo = {
   .driver_name =	US"appendfile",
+  .avail_string =	US" appendfile"
+# ifdef SUPPORT_MAILDIR
+			  "/maildir"
+# endif
+# ifdef SUPPORT_MAILSTORE
+			  "/mailstore"
+# endif
+# ifdef SUPPORT_MBX
+			  "/mbx"
+# endif
+			,
   .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,
-# ifdef DYNLOOKUP
+# if TRANSPORT_APPENDFILE==2
   .dyn_magic =		TRANSPORT_MAGIC,
 # endif
   },
diff --git a/src/src/transports/autoreply.c b/src/src/transports/autoreply.c
index 62eb888d0..da1c2f6bc 100644
--- a/src/src/transports/autoreply.c
+++ b/src/src/transports/autoreply.c
@@ -95,7 +95,7 @@ autoreply_transport_options_block *ob =
 /* If a fixed uid field is set, then a gid field must also be set. */
 
 if (tblock->uid_set && !tblock->gid_set && tblock->expand_gid == NULL)
-  log_write_die(0, LOG_CONFIG,
+  log_write_die(LOG_CONFIG,
     "user set without group for the %s transport", t->name);
 }
 
@@ -210,7 +210,7 @@ while (*s)
 
   if (rc == OK)                         /* Remove this address */
     {
-    DEBUG(D_transport)
+    DEBUG(transport)
       debug_printf("discarding recipient %s (matched never_mail)\n", next);
     hit = TRUE;
     if (terminator == ',') e++;
@@ -266,10 +266,8 @@ autoreply_transport_entry(
 {
 autoreply_transport_options_block * ob = tblock->drinst.options_block;
 const uschar * trname = tblock->drinst.name;
-int fd, pid, rc;
-int cache_fd = -1;
-int cache_size = 0;
-int add_size = 0;
+pid_t pid;
+int fd, rc, cache_fd = -1, cache_size = 0, add_size = 0;
 EXIM_DB * dbm_file = NULL;
 BOOL file_expand, return_message;
 const uschar * from, * reply_to, * to, * cc, * bcc, * subject, * headers;
@@ -279,7 +277,7 @@ header_line * h;
 time_t now = time(NULL), once_repeat_sec = 0;
 FILE * ff = NULL, * fp;
 
-DEBUG(D_transport) debug_printf("%s transport entered\n", trname);
+DEBUG(transport) debug_printf("%s transport entered\n", trname);
 
 /* Set up for the good case */
 
@@ -293,7 +291,7 @@ it has to be expanded here. */
 
 if (addr->reply)
   {
-  DEBUG(D_transport) debug_printf("taking data from address\n");
+  DEBUG(transport) debug_printf("taking data from address\n");
   from = addr->reply->from;
   reply_to = addr->reply->reply_to;
   to = addr->reply->to;
@@ -314,7 +312,7 @@ else
   {
   const uschar * oncerepeat;
 
-  DEBUG(D_transport) debug_printf("taking data from transport\n");
+  DEBUG(transport) debug_printf("taking data from transport\n");
   GET_OPTION("once_repeat");	oncerepeat = ob->once_repeat;
   GET_OPTION("from"); 		from = ob->from;
   GET_OPTION("reply_to");	reply_to = ob->reply_to;
@@ -376,7 +374,7 @@ if (ob->never_mail)
 
   if (!to && !cc && !bcc)
     {
-    DEBUG(D_transport)
+    DEBUG(transport)
       debug_printf("*** all recipients removed by never_mail\n");
     return OK;
     }
@@ -386,7 +384,7 @@ if (ob->never_mail)
 
 if (f.dont_deliver)
   {
-  DEBUG(D_transport)
+  DEBUG(transport)
     debug_printf("*** delivery by %s transport bypassed by -N option\n",
       trname);
   return FALSE;
@@ -447,7 +445,7 @@ if (oncelog && *oncelog && to)
       goto END_OFF;
       }
 
-    DEBUG(D_transport) debug_printf("%d bytes read from %s\n", cache_size, oncelog);
+    DEBUG(transport) debug_printf("%d bytes read from %s\n", cache_size, oncelog);
 
     /* Scan the data for this recipient. Each entry in the file starts with
     a time_t sized time value, followed by the address, followed by a binary
@@ -509,7 +507,7 @@ if (oncelog && *oncelog && to)
       goto END_OFF;
       }
 
-    DEBUG(D_transport) debug_printf("message previously sent to %s%s\n", to,
+    DEBUG(transport) debug_printf("message previously sent to %s%s\n", to,
       (once_repeat_sec > 0)? " and repeat time not reached" : "");
     log_fd = logfile ? Uopen(logfile, O_WRONLY|O_APPEND|O_CREAT, ob->mode) : -1;
     if (log_fd >= 0)
@@ -519,13 +517,13 @@ if (oncelog && *oncelog && to)
       while(*ptr) ptr++;
       if(write(log_fd, log_buffer, ptr - log_buffer) != ptr-log_buffer
         || close(log_fd))
-        DEBUG(D_transport) debug_printf("Problem writing log file %s for %s "
+        DEBUG(transport) debug_printf("Problem writing log file %s for %s "
           "transport\n", logfile, trname);
       }
     goto END_OFF;
     }
 
-  DEBUG(D_transport) debug_printf("%s %s\n", (then <= 0)?
+  DEBUG(transport) debug_printf("%s %s\n", (then <= 0)?
     "no previous message sent to" : "repeat time reached for", to);
   }
 
@@ -560,7 +558,7 @@ if ((pid = child_open_exim(&fd, US"autoreply")) < 0)
   addr->basic_errno = errno;
   addr->message = string_sprintf("Failed to create child process to send "
     "message from %s transport: %s", trname, strerror(errno));
-  DEBUG(D_transport) debug_printf("%s\n", addr->message);
+  DEBUG(transport) debug_printf("%s\n", addr->message);
   if (dbm_file) exim_dbclose(dbm_file);
   return FALSE;
   }
@@ -569,14 +567,15 @@ if ((pid = child_open_exim(&fd, US"autoreply")) < 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. */
 
+transport_count = 0;
 fp = fdopen(fd, "wb");
 
-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);
+if (from)	transport_count += fprintf(fp, "From: %s\n", from) + 1;
+if (reply_to)	transport_count += fprintf(fp, "Reply-To: %s\n", reply_to) + 1;
+if (to)		transport_count += fprintf(fp, "To: %s\n", to) + 1;
+if (cc)		transport_count += fprintf(fp, "Cc: %s\n", cc) + 1;
+if (bcc)	transport_count += fprintf(fp, "Bcc: %s\n", bcc) + 1;
+if (subject)	transport_count += fprintf(fp, "Subject: %s\n", subject) + 1;
 
 /* Generate In-Reply-To from the message_id header; there should
 always be one, but code defensively. */
@@ -588,24 +587,34 @@ if (h)
   {
   message_id = Ustrchr(h->text, ':') + 1;
   Uskip_whitespace(&message_id);
-  fprintf(fp, "In-Reply-To: %s", message_id);
+  transport_count += fprintf(fp, "In-Reply-To: %s", message_id);
   }
 
+/*XXX byte count? */
 moan_write_references(fp, message_id);
 
 /* Add an Auto-Submitted: header */
 
-fprintf(fp, "Auto-Submitted: auto-replied\n");
+transport_count += fprintf(fp, "Auto-Submitted: auto-replied\n") + 1;
 
 /* Add any specially requested headers */
 
-if (headers) fprintf(fp, "%s\n", headers);
+if (headers)
+ {
+  int i = 1;
+  transport_count += fprintf(fp, "%s\n", headers);
+  for (const uschar * s = headers; *s; s++) if (*s == '\n') i++;
+  transport_count += i;
+ }
+
 fprintf(fp, "\n");
 
 if (text)
   {
-  fprintf(fp, "%s", CS text);
-  if (text[Ustrlen(text)-1] != '\n') fprintf(fp, "\n");
+  int i = fprintf(fp, "%s", CS text);
+  if (text[Ustrlen(text)-1] != '\n') { fprintf(fp, "\n"); i++; }
+  for (const uschar * s = text; *s; s++) if (*s == '\n') i++;
+  transport_count += i;
   }
 
 if (ff)
@@ -614,13 +623,17 @@ if (ff)
     if (file_expand)
       {
       const uschar * s = expand_string(big_buffer);
-      if (!s) DEBUG(D_transport)
+      int i;
+      if (!s) DEBUG(transport)
 	debug_printf("error while expanding line from file:\n  %s\n  %s\n",
 	  big_buffer, expand_string_message);
-      fprintf(fp, "%s", s ? CS s : CS big_buffer);
+      if (!s) s = big_buffer;
+      i = fprintf(fp, "%s", CS s);
+      for (const uschar * t = s; *t; t++) if (*t == '\n') i++;
+      transport_count += i;
       }
     else
-      fprintf(fp, "%s", CS big_buffer);
+      transport_count += fprintf(fp, "%s", CS big_buffer) + 1;
 
   (void) fclose(ff);
   }
@@ -656,17 +669,17 @@ if (return_message)
       DELIVER_IN_BUFFER_SIZE;
     if (fstat(deliver_datafile, &statbuf) == 0 && statbuf.st_size > max)
       {
-      fprintf(fp, "\n%s"
+     	transport_count += 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);
+        (max/1000)*1000) + 5;
       }
-    else fprintf(fp, "\n%s\n", rubric);
+    else
+      transport_count += fprintf(fp, "\n%s\n", rubric) + 3;
     }
-  else fprintf(fp, "\n%s\n", rubric);
+  else	transport_count += fprintf(fp, "\n%s\n", rubric) + 3;
 
   fflush(fp);
-  transport_count = 0;
   transport_write_message(&tctx, bounce_return_size_limit);
   }
 
@@ -709,7 +722,7 @@ if (cache_fd >= 0)
 
     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 "
+      DEBUG(transport) debug_printf("Problem writing cache file %s for %s "
 	"transport\n", oncelog, trname);
     }
   }
@@ -740,7 +753,7 @@ message, we do not fail. */
 if (rc != 0)
   if (rc == EXIT_NORECIPIENTS)
     {
-    DEBUG(D_any) debug_printf("%s transport: message contained no recipients\n",
+    DEBUG(any) debug_printf("%s transport: message contained no recipients\n",
       trname);
     }
   else
@@ -770,7 +783,7 @@ if (logfile)
     /* Use taint-unchecked routines for writing into log_buffer, trusting
     that we'll never expand it. */
 
-    DEBUG(D_transport) debug_printf("logging message details\n");
+    DEBUG(transport) debug_printf("logging message details\n");
     g = string_fmt_append_f(g, SVFMT_TAINT_NOCHK, "%s\n", tod_stamp(tod_log));
     if (from)
       g = string_fmt_append_f(g, SVFMT_TAINT_NOCHK, "  From: %s\n", from);
@@ -785,10 +798,10 @@ if (logfile)
     if (headers)
       g = string_fmt_append_f(g, SVFMT_TAINT_NOCHK, "  %s\n", headers);
     if(write(log_fd, g->s, g->ptr) != g->ptr || close(log_fd))
-      DEBUG(D_transport) debug_printf("Problem writing log file %s for %s "
+      DEBUG(transport) debug_printf("Problem writing log file %s for %s "
         "transport\n", logfile, trname);
     }
-  else DEBUG(D_transport) debug_printf("Failed to open log file %s for %s "
+  else DEBUG(transport) debug_printf("Failed to open log file %s for %s "
     "transport: %s\n", logfile, trname, strerror(errno));
   }
 
@@ -796,7 +809,7 @@ END_OFF:
 if (dbm_file) exim_dbclose(dbm_file);
 if (cache_fd > 0) (void)close(cache_fd);
 
-DEBUG(D_transport) debug_printf("%s transport succeeded\n", trname);
+DEBUG(transport) debug_printf("%s transport succeeded\n", trname);
 
 return FALSE;
 }
@@ -817,7 +830,7 @@ transport_info autoreply_transport_info = {
   .options_block =	&autoreply_transport_option_defaults,
   .options_len =	sizeof(autoreply_transport_options_block),
   .init =		autoreply_transport_init,
-# ifdef DYNLOOKUP
+# if TRANSPORT_AUTOREPLY==2
   .dyn_magic =		TRANSPORT_MAGIC,
 # endif
   },
diff --git a/src/src/transports/lmtp.c b/src/src/transports/lmtp.c
index 3403dce7a..9f8e73110 100644
--- a/src/src/transports/lmtp.c
+++ b/src/src/transports/lmtp.c
@@ -84,14 +84,14 @@ lmtp_transport_options_block * ob = t->options_block;
 /* Either the command field or the socket field must be set */
 
 if ((ob->cmd == NULL) == (ob->skt == NULL))
-  log_write_die(0, LOG_CONFIG,
+  log_write_die(LOG_CONFIG,
     "one (and only one) of command or socket must be set for the %s transport",
     tblock->drinst.name);
 
 /* If a fixed uid field is set, then a gid field must also be set. */
 
 if (tblock->uid_set && !tblock->gid_set && tblock->expand_gid == NULL)
-  log_write_die(0, LOG_CONFIG,
+  log_write_die(LOG_CONFIG,
     "user set without group for the %s transport", tblock->drinst.name);
 
 /* Set up the bitwise options for transport_write_message from the various
@@ -243,11 +243,11 @@ if (!string_vformat(&gs, SVFMT_TAINT_NOCHK, CS format, ap))
   return FALSE;
   }
 va_end(ap);
-DEBUG(D_transport|D_v) debug_printf("  LMTP>> %Y", &gs);
+DEBUG(transport|v) debug_printf("  LMTP>> %Y", &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));
+DEBUG(transport) debug_printf("write failed: %s\n", strerror(errno));
 return FALSE;
 }
 
@@ -327,7 +327,7 @@ for (;;)
 
     else if (errno == EINTR)
       {
-      DEBUG(D_transport) debug_printf("EINTR while reading LMTP response\n");
+      DEBUG(transport) debug_printf("EINTR while reading LMTP response\n");
       continue;
       }
 
@@ -350,7 +350,7 @@ for (;;)
 
   if (ptr[count-1] != '\n')
     {
-    DEBUG(D_transport)
+    DEBUG(transport)
       {
       debug_printf("LMTP input line incomplete in one buffer:\n  ");
       for (int i = 0; i < count; i++)
@@ -370,7 +370,7 @@ for (;;)
   while (count > 0 && isspace(ptr[count-1])) count--;
   ptr[count] = 0;
 
-  DEBUG(D_transport|D_v)
+  DEBUG(transport|v)
     {
     uschar *s = ptr;
     uschar *t = ptr;
@@ -483,7 +483,7 @@ const uschar * sockname = NULL;
 const uschar ** argv;
 uschar buffer[256];
 
-DEBUG(D_transport) debug_printf("%s transport entered\n", trname);
+DEBUG(transport) debug_printf("%s transport entered\n", trname);
 
 /* Initialization ensures that either a command or a socket is specified, but
 not both. When a command is specified, call the common function for creating an
@@ -491,7 +491,7 @@ argument list and expanding the items. */
 
 if (ob->cmd)
   {
-  DEBUG(D_transport) debug_printf("using command %s\n", ob->cmd);
+  DEBUG(transport) debug_printf("using command %s\n", ob->cmd);
   sprintf(CS buffer, "%.50s transport", trname);
   if (!transport_set_up_command(&argv, ob->cmd, TSUC_EXPAND_ARGS, PANIC,
 	addrlist, buffer, NULL))
@@ -519,7 +519,7 @@ leader, so we can kill it and all its children on an error. */
 
 else
   {
-  DEBUG(D_transport) debug_printf("using socket %s\n", ob->skt);
+  DEBUG(transport) debug_printf("using socket %s\n", ob->skt);
   if (!(sockname = expand_string(ob->skt)))
     {
     addrlist->message = string_sprintf("Expansion of %q (socket setting "
@@ -650,7 +650,7 @@ if (send_data)
   sigalrm_seen = FALSE;
   transport_write_timeout = timeout;
   Ustrcpy(big_buffer, US"sending data block");   /* For error messages */
-  DEBUG(D_transport|D_v)
+  DEBUG(transport|v)
     debug_printf("  LMTP>> writing message and terminating \".\"\n");
 
   transport_count = 0;
@@ -795,14 +795,14 @@ RETURN:
 if (fd_in >= 0) (void)close(fd_in);
 if (fd_out >= 0) (void)fclose(out);
 
-DEBUG(D_transport)
+DEBUG(transport)
   debug_printf("%s transport yields %d\n", trname, yield);
 
 return yield;
 
 
 MINUS_N:
-  DEBUG(D_transport)
+  DEBUG(transport)
     debug_printf("*** delivery by %s transport bypassed by -N option",
       trname);
   addrlist->transport_return = OK;
@@ -825,7 +825,7 @@ transport_info lmtp_transport_info = {
   .options_block =	&lmtp_transport_option_defaults,
   .options_len =	sizeof(lmtp_transport_options_block),
   .init =		lmtp_transport_init,
-# ifdef DYNLOOKUP
+# if TRANSPORT_LMTP==2
   .dyn_magic =		TRANSPORT_MAGIC,
 # endif
   },
diff --git a/src/src/transports/pipe.c b/src/src/transports/pipe.c
index 68036ce03..6a5175438 100644
--- a/src/src/transports/pipe.c
+++ b/src/src/transports/pipe.c
@@ -155,7 +155,7 @@ if (ob->permit_coredump)
 #ifdef SETRLIMIT_NOT_SUPPORTED
     if (errno != ENOSYS && errno != ENOTSUP)
 #endif
-      log_write(0, LOG_MAIN,
+      log_write(LOG_MAIN,
           "delivery setrlimit(RLIMIT_CORE, RLIM_INFINITY) failed: %s",
           strerror(errno));
     }
@@ -190,14 +190,14 @@ tblock->setup = pipe_transport_setup;
 
 if (tblock->deliver_as_creator && (tblock->uid_set || tblock->gid_set ||
   tblock->expand_uid != NULL || tblock->expand_gid != NULL))
-    log_write_die(0, LOG_CONFIG,
+    log_write_die(LOG_CONFIG,
       "both pipe_as_creator and an explicit uid/gid are set for the %s "
         "transport", trname);
 
 /* If a fixed uid field is set, then a gid field must also be set. */
 
 if (tblock->uid_set && !tblock->gid_set && tblock->expand_gid == NULL)
-  log_write_die(0, LOG_CONFIG,
+  log_write_die(LOG_CONFIG,
     "user set without group for the %s transport", trname);
 
 /* Temp_errors must consist only of digits and colons, but there can be
@@ -207,7 +207,7 @@ if (ob->temp_errors != NULL && Ustrcmp(ob->temp_errors, "*") != 0)
   {
   size_t p = Ustrspn(ob->temp_errors, "0123456789: ");
   if (ob->temp_errors[p] != 0)
-    log_write_die(0, LOG_CONFIG,
+    log_write_die(LOG_CONFIG,
       "temp_errors must be a list of numbers or an asterisk for the %s "
       "transport", trname);
   }
@@ -216,12 +216,12 @@ if (ob->temp_errors != NULL && Ustrcmp(ob->temp_errors, "*") != 0)
 should be set. */
 
 if (tblock->return_output && tblock->return_fail_output)
-  log_write_die(0, LOG_CONFIG,
+  log_write_die(LOG_CONFIG,
     "both return_output and return_fail_output set for %s transport",
     trname);
 
 if (tblock->log_output && tblock->log_fail_output)
-  log_write_die(0, LOG_CONFIG,
+  log_write_die(LOG_CONFIG,
     "both log_output and log_fail_output set for the %s transport",
     trname);
 
@@ -248,14 +248,14 @@ else
 /* The restrict_to_path  and use_shell options are incompatible */
 
 if (ob->restrict_to_path && ob->use_shell)
-  log_write_die(0, LOG_CONFIG,
+  log_write_die(LOG_CONFIG,
     "both restrict_to_path and use_shell set for %s transport",
     trname);
 
 /* The allow_commands and use_shell options are incompatible */
 
 if (ob->allow_commands && ob->use_shell)
-  log_write_die(0, LOG_CONFIG,
+  log_write_die(LOG_CONFIG,
     "both allow_commands and use_shell set for %s transport",
     trname);
 
@@ -436,7 +436,7 @@ if (expand_arguments)
   uschar * p = Ustrstr(cmd, "pipe_addresses");
   gstring * g = NULL;
 
-  DEBUG(D_transport)
+  DEBUG(transport)
     debug_printf("shell pipe command before expansion:\n  %s\n", cmd);
 
   /* Allow $recipients in the expansion iff it comes from a system filter */
@@ -457,7 +457,7 @@ if (expand_arguments)
 
     for (address_item * ad = addr; ad; ad = ad->next)
       {
-      DEBUG(D_transport) if (is_tainted(ad->address))
+      DEBUG(transport) if (is_tainted(ad->address))
 	debug_printf("tainted element '%s' from $pipe_addresses\n", ad->address);
 
       /*XXX string_append_listele() ? */
@@ -482,12 +482,12 @@ if (expand_arguments)
     return FALSE;
     }
 
-  DEBUG(D_transport)
+  DEBUG(transport)
     debug_printf("shell pipe command after expansion:\n  %s\n", argv[2]);
   }
 else
   {
-  DEBUG(D_transport)
+  DEBUG(transport)
     debug_printf("shell pipe command (no expansion):\n  %s\n", cmd);
   argv[2] = cmd;
   }
@@ -530,7 +530,7 @@ transport_ctx tctx = {
   ob->options | topt_not_socket /* set at initialization time */
 };
 
-DEBUG(D_transport) debug_printf("%s transport entered\n", trname);
+DEBUG(transport) debug_printf("%s transport entered\n", trname);
 
 /* Set up for the good case */
 
@@ -581,7 +581,7 @@ if (!cmd || !*cmd)
   }
 if (is_tainted(cmd))
   {
-  DEBUG(D_transport) debug_printf("cmd '%s' is tainted\n", cmd);
+  DEBUG(transport) debug_printf("cmd '%s' is tainted\n", cmd);
   addr->message = string_sprintf("Tainted '%s' (command "
     "for %s transport) not permitted", cmd, trname);
   addr->transport_return = PANIC;
@@ -680,7 +680,7 @@ envp[envcount] = NULL;
 
 if (f.dont_deliver)
   {
-  DEBUG(D_transport)
+  DEBUG(transport)
     debug_printf("*** delivery by %s transport bypassed by -N option",
       trname);
   return FALSE;
@@ -748,17 +748,17 @@ if (outpid == 0)
     {
     if (addr->return_file >= 0)
       if(write(addr->return_file, big_buffer, rc) != rc)
-        DEBUG(D_transport) debug_printf("Problem writing to return_file\n");
+        DEBUG(transport) debug_printf("Problem writing to return_file\n");
     count += rc;
     if (count > ob->max_output)
       {
-      DEBUG(D_transport) debug_printf("Too much output from pipe - killed\n");
+      DEBUG(transport) debug_printf("Too much output from pipe - killed\n");
       if (addr->return_file >= 0)
 	{
         uschar *message = US"\n\n*** Too much output - remainder discarded ***\n";
         rc = Ustrlen(message);
         if(write(addr->return_file, message, rc) != rc)
-          DEBUG(D_transport) debug_printf("Problem writing to return_file\n");
+          DEBUG(transport) debug_printf("Problem writing to return_file\n");
 	}
       killpg(pid, SIGKILL);
       break;
@@ -781,7 +781,7 @@ any debugging output is likely to be in the same order.) */
 
 testharness_pause_ms(500);
 
-DEBUG(D_transport) debug_printf("Writing message to pipe\n");
+DEBUG(transport) debug_printf("Writing message to pipe\n");
 
 /* Arrange to time out writes if there is a timeout set. */
 
@@ -1113,7 +1113,7 @@ are complete before we pass this point. */
 
 while (wait(&rc) >= 0);
 
-DEBUG(D_transport) debug_printf("%s transport yielded %d\n", trname,
+DEBUG(transport) debug_printf("%s transport yielded %d\n", trname,
   addr->transport_return);
 
 /* If there has been a problem, the message in addr->message contains details
@@ -1142,7 +1142,7 @@ transport_info pipe_transport_info = {
   .options_block =	&pipe_transport_option_defaults,
   .options_len =	sizeof(pipe_transport_options_block),
   .init =		pipe_transport_init,
-# ifdef DYNLOOKUP
+# if TRANSPORT_PIPE==2
   .dyn_magic =		TRANSPORT_MAGIC,
 # endif
   },
diff --git a/src/src/transports/queuefile.c b/src/src/transports/queuefile.c
index 7ae71a8da..0ea5e6c45 100644
--- a/src/src/transports/queuefile.c
+++ b/src/src/transports/queuefile.c
@@ -63,7 +63,7 @@ void queuefile_transport_init(driver_instance * t)
 queuefile_transport_options_block * ob = t->options_block;
 
 if (!ob->dirname)
-  log_write_die(0, LOG_CONFIG,
+  log_write_die(LOG_CONFIG,
     "directory must be set for the %s transport", t->name);
 }
 
@@ -127,7 +127,7 @@ dstpath = string_sprintf("%s/%s-%s", dstpath, message_id, suffix);
 
 if (link_file)
   {
-  DEBUG(D_transport) debug_printf("%s transport, linking %s => %s\n",
+  DEBUG(transport) debug_printf("%s transport, linking %s => %s\n",
     trname, srcpath, dstpath);
 
   if (linkat(sdfd, CCS filename, ddfd, CCS filename, 0) >= 0)
@@ -138,7 +138,7 @@ if (link_file)
   }
 else					/* use data copy */
   {
-  DEBUG(D_transport) debug_printf("%s transport, copying %s => %s\n",
+  DEBUG(transport) debug_printf("%s transport, copying %s => %s\n",
     trname, srcpath, dstpath);
 
   if (  (s = dstpath,
@@ -185,7 +185,7 @@ uschar * s, * dstdir;
 struct stat dstatbuf, sstatbuf;
 int ddfd = -1, sdfd = -1;
 
-DEBUG(D_transport)
+DEBUG(transport)
   debug_printf("%s transport entered\n", trname);
 
 #ifndef O_DIRECTORY
@@ -242,7 +242,7 @@ can_link = (dstatbuf.st_dev == sstatbuf.st_dev);
 
 if (f.dont_deliver)
   {
-  DEBUG(D_transport)
+  DEBUG(transport)
     debug_printf("*** delivery by %s transport bypassed by -N option\n",
       trname);
   addr->transport_return = OK;
@@ -251,26 +251,26 @@ if (f.dont_deliver)
 
 /* Link or copy the header and data spool files */
 
-DEBUG(D_transport)
+DEBUG(transport)
   debug_printf("%s transport, copying header file\n", trname);
 
 if (!copy_spool_files(tblock, addr, dstdir, sdfd, ddfd, can_link, -1))
   goto RETURN;
 
-DEBUG(D_transport)
+DEBUG(transport)
   debug_printf("%s transport, copying data file\n", trname);
 
 if (!copy_spool_files(tblock, addr, dstdir, sdfd, ddfd, can_link,
 	deliver_datafile))
   {
-  DEBUG(D_transport)
+  DEBUG(transport)
     debug_printf("%s transport, copying data file failed, "
       "unlinking the header file\n", trname);
   Uunlink(string_sprintf("%s/%s-H", dstdir, message_id));
   goto RETURN;
   }
 
-DEBUG(D_transport)
+DEBUG(transport)
   debug_printf("%s transport succeeded\n", trname);
 
 addr->transport_return = OK;
@@ -300,7 +300,7 @@ transport_info queuefile_transport_info = {
   .options_block =	&queuefile_transport_option_defaults,
   .options_len =	sizeof(queuefile_transport_options_block),
   .init =		queuefile_transport_init,
-# ifdef DYNLOOKUP
+# if EXPERIMENTAL_QUEUEFILE==2
   .dyn_magic =		TRANSPORT_MAGIC,
 # endif
   },
diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c
index e9dc0957c..a6a3c93ce 100644
--- a/src/src/transports/smtp.c
+++ b/src/src/transports/smtp.c
@@ -335,7 +335,7 @@ if (tf)
   tf->search_parents = ob->dns_search_parents;
   tf->helo_data = ob->helo_data;
 
-  if (exp_bool(addrlist, US"transport", tblock->drinst.name, D_transport,
+  if (exp_bool(addrlist, US"transport", tblock->drinst.name, IS_DEBUG(transport),
 	      US"hosts_randomize", ob->hosts_randomize,
 	      ob->expand_hosts_randomize, &tf->hosts_randomize) != OK)
     return DEFER;
@@ -417,7 +417,7 @@ tblock->setup = smtp_transport_setup;
 
 if (ob->command_timeout <= 0 || ob->data_timeout <= 0 ||
     ob->final_timeout <= 0)
-  log_write_die(0, LOG_CONFIG,
+  log_write_die(LOG_CONFIG,
     "command, data, or final timeout value is zero for %s transport",
       t->name);
 
@@ -607,7 +607,7 @@ switch(*errno_value)
 #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);
+    DEBUG(deliver|transport) debug_printf("%s\n", *message);
     return TRUE;
 #endif
   }
@@ -675,7 +675,7 @@ if (suffix)
 else
   message = string_fmt_append(message, " %s", exim_errstr(basic_errno));
 
-log_write(0, LOG_MAIN, "%Y", message);
+log_write(LOG_MAIN, "%Y", message);
 deliver_msglog("%s %.*s\n", tod_stamp(tod_log), message->ptr, message->s);
 }
 
@@ -892,7 +892,7 @@ if (limit_mail && limit_mail < sx->max_mail) sx->max_mail = limit_mail;
 if (limit_rcpt && limit_rcpt < sx->max_rcpt) sx->max_rcpt = limit_rcpt;
 if (limit_rcptdom)
   {
-  DEBUG(D_transport) debug_printf("will treat as !multi_domain\n");
+  DEBUG(transport) debug_printf("will treat as !multi_domain\n");
   sx->single_rcpt_domain = TRUE;
   }
 }
@@ -956,7 +956,7 @@ if ((dbm_file = dbfn_open(US"misc", O_RDWR|O_CREAT, &dbblock, TRUE, TRUE)))
   const uschar * ehlo_resp_key = ehlo_cache_key(sx);
   dbdata_ehlo_resp er = { .data = sx->ehlo_resp };
 
-  HDEBUG(D_transport)
+  HDEBUG(transport)
 # ifndef DISABLE_ESMTP_LIMITS
     if (sx->ehlo_resp.limit_mail || sx->ehlo_resp.limit_rcpt || sx->ehlo_resp.limit_rcptdom)
       debug_printf("writing clr %04x/%04x cry %04x/%04x lim %05d/%05d/%05d\n",
@@ -984,7 +984,7 @@ if (  sx->early_pipe_active
    && (dbm_file = dbfn_open(US"misc", O_RDWR|O_CREAT, &dbblock, TRUE, TRUE)))
   {
   const uschar * ehlo_resp_key = ehlo_cache_key(sx);
-  HDEBUG(D_transport)
+  HDEBUG(transport)
     {
     const dbdata_ehlo_resp * er;
 
@@ -992,7 +992,7 @@ if (  sx->early_pipe_active
       debug_printf("no ehlo-resp record!\n");
     else
       debug_printf("ehlo-resp record is %.0f seconds old\n",
-		    difftime(time(NULL), er->time_stamp));
+		    difftime(time(NULL), er->gen.time_stamp));
     }
 
   dbfn_delete(dbm_file, ehlo_resp_key);
@@ -1007,7 +1007,7 @@ open_db dbblock;
 open_db * dbm_file;
 
 if (!(dbm_file = dbfn_open(US"misc", O_RDONLY, &dbblock, FALSE, TRUE)))
-  { DEBUG(D_transport) debug_printf("ehlo-cache: no misc DB\n"); }
+  { DEBUG(transport) debug_printf("ehlo-cache: no misc DB\n"); }
 else
   {
   const uschar * ehlo_resp_key = ehlo_cache_key(sx);
@@ -1016,11 +1016,11 @@ else
   if (!(er = dbfn_read_enforce_length(dbm_file, ehlo_resp_key, sizeof(dbdata_ehlo_resp))))
     {
     dbfn_close(dbm_file);
-    DEBUG(D_transport) debug_printf("no ehlo-resp record\n");
+    DEBUG(transport) debug_printf("no ehlo-resp record\n");
     }
-  else if (time(NULL) - er->time_stamp > retry_data_expire)
+  else if (time(NULL) - er->gen.time_stamp > retry_data_expire)
     {
-    DEBUG(D_transport) debug_printf("ehlo-resp record too old\n");
+    DEBUG(transport) debug_printf("ehlo-resp record too old\n");
     dbfn_close(dbm_file);
     if ((dbm_file = dbfn_open(US"misc", O_RDWR|O_CREAT, &dbblock, TRUE, TRUE)))
       {
@@ -1030,7 +1030,7 @@ else
     }
   else
     {
-    DEBUG(D_transport)
+    DEBUG(transport)
 # ifndef DISABLE_ESMTP_LIMITS
       if (er->data.limit_mail || er->data.limit_rcpt || er->data.limit_rcptdom)
 	debug_printf("EHLO response bits from cache:"
@@ -1084,7 +1084,7 @@ for (au = auths, authnum = 0; au; au = au->drinst.next, authnum++)
 	{ authbits |= BIT(authnum); break; }
     }
 
-DEBUG(D_transport)
+DEBUG(transport)
   debug_printf("server offers %s AUTH, methods '%s', usable-bitmap 0x%04x\n",
     tls_out.active.sock >= 0 ? "crypted" : "plaintext", names, authbits);
 
@@ -1126,13 +1126,11 @@ sx->pending_EHLO = FALSE;
 
 if (pending_BANNER)
   {
-  const uschar * s;
-
-  DEBUG(D_transport) debug_printf("%s expect banner\n", __FUNCTION__);
+  DEBUG(transport) debug_printf("%s expect banner\n", __FUNCTION__);
   (*countp)--;
   if (!smtp_reap_banner(sx))
     {
-    DEBUG(D_transport) debug_printf("bad banner\n");
+    DEBUG(transport) debug_printf("bad banner\n");
     if (tls_out.active.sock >= 0) rc = DEFER;
     goto fail;
     }
@@ -1140,9 +1138,12 @@ if (pending_BANNER)
 
 # if !defined(DISABLE_TLS) && !defined(DISABLE_TLS_RESUME)
   GET_OPTION("host_name_extract");
-  s = ((smtp_transport_options_block *)sx->conn_args.ob)->host_name_extract;
-  if (!s) s = HNE_DEFAULT;
-  ehlo_response_lbserver(sx, s);
+    {
+    const uschar * s;
+    s = ((smtp_transport_options_block *)sx->conn_args.ob)->host_name_extract;
+    if (!s) s = HNE_DEFAULT;
+    ehlo_response_lbserver(sx, s);
+    }
 # endif
   }
 
@@ -1151,11 +1152,11 @@ if (pending_EHLO)
   unsigned peer_offered;
   unsigned short authbits = 0, * ap;
 
-  DEBUG(D_transport) debug_printf("%s expect ehlo\n", __FUNCTION__);
+  DEBUG(transport) debug_printf("%s expect ehlo\n", __FUNCTION__);
   (*countp)--;
   if (!smtp_reap_ehlo(sx))
     {
-    DEBUG(D_transport) debug_printf("bad response for EHLO\n");
+    DEBUG(transport) debug_printf("bad response for EHLO\n");
     if (tls_out.active.sock >= 0) rc = DEFER;
     goto fail;
     }
@@ -1179,7 +1180,7 @@ if (pending_EHLO)
   if (  peer_offered != sx->peer_offered
      || (authbits = study_ehlo_auths(sx)) != *ap)
     {
-    HDEBUG(D_transport)
+    HDEBUG(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);
@@ -1210,7 +1211,7 @@ if (pending_EHLO)
           || sx->peer_limit_rcptdom != sx->ehlo_resp.limit_rcptdom
        )  )
       {
-      HDEBUG(D_transport)
+      HDEBUG(transport)
 	{
 	debug_printf("EHLO LIMITS changed:");
 	if (sx->peer_limit_mail != sx->ehlo_resp.limit_mail)
@@ -1297,13 +1298,13 @@ responses before returning, except after I/O errors and timeouts. */
 
 if (sx->pending_MAIL)
   {
-  DEBUG(D_transport) debug_printf("%s expect mail\n", __FUNCTION__);
+  DEBUG(transport) debug_printf("%s expect mail\n", __FUNCTION__);
   count--;
   sx->pending_MAIL = sx->RCPT_452 = FALSE;
   if (!smtp_read_response(sx, sx->buffer, sizeof(sx->buffer),
 			  '2', ob->command_timeout))
     {
-    DEBUG(D_transport) debug_printf("bad response for MAIL\n");
+    DEBUG(transport) debug_printf("bad response for MAIL\n");
     Ustrcpy(big_buffer, mail_command);  /* Fits, because it came from there! */
     if (errno == ERRNO_TLSFAILURE)
       return RESP_EHLO_ERR_TLS;
@@ -1353,7 +1354,7 @@ while (count-- > 0)
   else
     { clearflag(addr, af_cont_conn); setflag(addr, af_new_conn); }
 
-  DEBUG(D_transport) debug_printf("%s expect rcpt for %s\n", __FUNCTION__, addr->address);
+  DEBUG(transport) debug_printf("%s expect rcpt for %s\n", __FUNCTION__, addr->address);
   if (smtp_read_response(sx, sx->buffer, sizeof(sx->buffer),
 			  '2', ob->command_timeout))
     {
@@ -1449,7 +1450,7 @@ while (count-- > 0)
 	  {
 	  if (!sx->RCPT_452)		/* initialised at MAIL-ack above */
 	    {
-	    DEBUG(D_transport)
+	    DEBUG(transport)
 	      debug_printf("%s: seen first 452 too-many-rcpts\n", __FUNCTION__);
 	    sx->RCPT_452 = TRUE;
 	    sx->next_addr = addr;
@@ -1469,7 +1470,7 @@ while (count-- > 0)
 	    if (LOGGING(outgoing_port))
 	      g = log_portnum(g, sx->port == PORT_NONE ? 25 : sx->port);
 	    g = string_fmt_append(g, " %s", addr->message);
-	    log_write(0, LOG_MAIN, "%Y", g);
+	    log_write(LOG_MAIN, "%Y", g);
 	    gstring_reset(g); gstring_release_unused(g);
 	    }
 
@@ -1510,7 +1511,7 @@ previously or in this block, the response is ignored. */
 
 if (pending_DATA != 0)
   {
-  DEBUG(D_transport) debug_printf("%s expect data\n", __FUNCTION__);
+  DEBUG(transport) debug_printf("%s expect data\n", __FUNCTION__);
   if (!smtp_read_response(sx, sx->buffer, sizeof(sx->buffer),
 			'3', ob->command_timeout))
     {
@@ -1531,7 +1532,7 @@ if (pending_DATA != 0)
       return RESP_MAIL_OR_DATA_ERROR;
       }
     (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 "
+    DEBUG(transport) debug_printf("%s\nerror for DATA ignored: pipelining "
       "is in use and there were no good recipients\n", msg);
     }
   }
@@ -1580,7 +1581,7 @@ driver_srcline = au->drinst.srcline;
   sx->outblock.authenticating = FALSE;
   }
 driver_srcfile = authenticator_name = NULL; driver_srcline = 0;
-DEBUG(D_transport) debug_printf("%s authenticator yielded %s\n",
+DEBUG(transport) debug_printf("%s authenticator yielded %s\n",
   au->drinst.name, rc_names[rc]);
 
 /* A temporary authentication failure must hold up delivery to
@@ -1615,12 +1616,12 @@ switch(rc)
       sender_host_authenticated = au->drinst.name;
       if ((logmsg = event_raise(sx->conn_args.tblock->event_action,
 				US"auth:fail", sx->buffer, NULL)))
-	log_write(0, LOG_MAIN, "%s", logmsg);
+	log_write(LOG_MAIN, "%s", logmsg);
       sender_host_authenticated = save_name;
      }
 #endif
     if (!logmsg)
-      log_write(0, LOG_MAIN, "%s authenticator failed H=%s [%s] %s",
+      log_write(LOG_MAIN, "%s authenticator failed H=%s [%s] %s",
 	au->drinst.name, host->name, host->address, sx->buffer);
     break;
     }
@@ -1633,7 +1634,7 @@ switch(rc)
 
   case CANCELLED:
     if (*sx->buffer != 0)
-      log_write(0, LOG_MAIN, "%s authenticator cancelled "
+      log_write(LOG_MAIN, "%s authenticator cancelled "
 	"authentication H=%s [%s] %s", au->drinst.name, host->name,
 	host->address, sx->buffer);
     break;
@@ -1711,7 +1712,7 @@ if (  sx->esmtp
   if (  require_auth
      || verify_check_given_host(CUSS &ob->hosts_try_auth, host) == OK)
     {
-    DEBUG(D_transport) debug_printf("scanning authentication mechanisms\n");
+    DEBUG(transport) debug_printf("scanning authentication mechanisms\n");
     fail_reason = US"no common mechanisms were found";
 
 #ifndef DISABLE_PIPE_CONNECT
@@ -1736,7 +1737,7 @@ if (  sx->esmtp
 	     && !expand_check_condition(au->client_condition, au->drinst.name,
 		     US"client authenticator"))
 	    {
-	    DEBUG(D_transport) debug_printf("skipping %s authenticator: %s\n",
+	    DEBUG(transport) debug_printf("skipping %s authenticator: %s\n",
 	      au->drinst.name, "client_condition is false");
 	    continue;
 	    }
@@ -1770,7 +1771,7 @@ if (  sx->esmtp
 	    &&  !expand_check_condition(au->client_condition, au->drinst.name,
 		   US"client authenticator")))
 	{
-	DEBUG(D_transport) debug_printf("skipping %s authenticator: %s\n",
+	DEBUG(transport) debug_printf("skipping %s authenticator: %s\n",
 	  au->drinst.name,
 	  au->client ? "client_condition is false"
 		    : "not configured as a client");
@@ -1952,12 +1953,12 @@ if (!(new_sender_address = spool_sender_from_msgid(message_id)))
 
 message_local_identity =
   smtp_local_identity(new_sender_address, s_compare->tblock);
-DEBUG(D_transport)
+DEBUG(transport)
   debug_printf_indent("message local identity: %q\n", message_local_identity);
 
 current_local_identity =
   smtp_local_identity(s_compare->current_sender_address, s_compare->tblock);
-DEBUG(D_transport)
+DEBUG(transport)
   debug_printf_indent("current local identity: %q\n", current_local_identity);
 
 return Ustrcmp(current_local_identity, message_local_identity) == 0;
@@ -2097,7 +2098,7 @@ if (sx->pending_BDAT)
 
 if (flags & tc_reap_prev  &&  prev_cmd_count > 0)
   {
-  DEBUG(D_transport) debug_printf("look for %d responses"
+  DEBUG(transport) debug_printf("look for %d responses"
     " for previous pipelined cmds\n", prev_cmd_count);
 
   switch(sync_responses(sx, prev_cmd_count, 0))
@@ -2124,7 +2125,7 @@ if (flags & tc_reap_prev  &&  prev_cmd_count > 0)
 
 if (sx->pending_BDAT)
   {
-  DEBUG(D_transport) debug_printf("look for one response for BDAT\n");
+  DEBUG(transport) debug_printf("look for one response for BDAT\n");
 
   if (!smtp_read_response(sx, sx->buffer, sizeof(sx->buffer), '2',
        ob->command_timeout))
@@ -2159,7 +2160,11 @@ int rc;
 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, &sx->conn_args.tlsa_dnsa, sx->dane_required))
+  {
+  if (!sx->conn_args.tlsa_dnsa)
+    sx->conn_args.tlsa_dnsa = store_get_dns_answer();
+  switch (rc = tlsa_lookup(sx->conn_args.host,
+			    sx->conn_args.tlsa_dnsa, sx->dane_required))
     {
     case OK:		sx->conn_args.dane = TRUE;
 #ifdef EXPERIMENTAL_SRV_SMTPS
@@ -2181,6 +2186,7 @@ if(  sx->dane_required
 # endif
 			return rc;
     }
+  }
 return OK;
 }
 #endif
@@ -2226,7 +2232,7 @@ sx->conn_args.ob = ob;
 #ifdef EXPERIMENTAL_SRV_SMTPS
 if ((sx->smtps = sx->conn_args.host->tls_needs == SRV_TLS_ON_CONNECT))
   {
-  DEBUG(D_transport) debug_printf("tls-on-connect required by smtp context\n");
+  DEBUG(transport) debug_printf("tls-on-connect required by smtp context\n");
   }
 else
 #endif
@@ -2236,17 +2242,17 @@ else
     if ((sx->smtps = strcmpic(ob->protocol, US"smtps") == 0
 		  || strcmpic(ob->protocol, US"submissions") == 0))
       {
-      DEBUG(D_transport)
+      DEBUG(transport)
 	debug_printf(" tls-on-connect required by transport option\n");
       }
     else if ((sx->lmtp = strcmpic(ob->protocol, US"lmtp") == 0))
       {
-      DEBUG(D_transport)
+      DEBUG(transport)
 	debug_printf(" LMTP required by transport option\n");
       }
     else if (strcmpic(ob->protocol, US"smtp") != 0)
       {
-      log_write(0, LOG_MAIN|LOG_PANIC,
+      log_write(LOG_MAIN|LOG_PANIC,
 	"bad protocol option in transport: '%s'\n", ob->protocol);
       return ERROR;
       }
@@ -2348,7 +2354,7 @@ if (continue_hostname && continue_proxy_cipher)
   existing conn drop the connection to force a new one. */
 
   if (ob->tls_sni && !(sni = expand_string(ob->tls_sni)))
-    log_write(0, LOG_MAIN|LOG_PANIC,
+    log_write(LOG_MAIN|LOG_PANIC,
       "<%s>: failed to expand transport's tls_sni value: %s",
       sx->addrlist->address, expand_string_message);
 
@@ -2366,7 +2372,7 @@ if (continue_hostname && continue_proxy_cipher)
 # endif
   else
     {
-    DEBUG(D_transport)
+    DEBUG(transport)
 # ifdef SUPPORT_DANE
       if (continue_proxy_dane != sx->conn_args.dane)
 	debug_printf(
@@ -2379,8 +2385,9 @@ if (continue_hostname && continue_proxy_cipher)
 
     smtp_debug_cmd(US"QUIT", 0);
     if (write(0, "QUIT\r\n", 6) < 0)
-      DEBUG(D_any) debug_printf("stupid compiler\n");
+      DEBUG(any) debug_printf("stupid compiler\n");
     close(0);
+    tls_out.active.sock = -1;
     continue_hostname = continue_proxy_cipher = NULL;
     f.continue_more = FALSE;
     continue_sequence = 1;	/* Ensure proper logging of non-cont-conn */
@@ -2394,7 +2401,7 @@ specially so they can be identified for retries. */
 
 if (!continue_hostname || atrn_domains)
   {
-  if (sx->verify) HDEBUG(D_verify)
+  if (sx->verify) HDEBUG(verify)
     debug_printf("interface=%s port=%d\n", sx->conn_args.interface, sx->port);
 
   /* Get the actual port the connection will use, into sx->conn_args.host */
@@ -2424,7 +2431,7 @@ if (!continue_hostname || atrn_domains)
 # endif
       return FAIL;
       }
-      else DEBUG(D_transport)
+      else DEBUG(transport)
 	debug_printf("lack of DNSSEC traceability precludes DANE\n");
     }
 #endif	/*DANE*/
@@ -2441,7 +2448,7 @@ if (!continue_hostname || atrn_domains)
 
   if (atrn_mode && *atrn_mode == 'P')
     {
-    DEBUG(D_transport)
+    DEBUG(transport)
       debug_printf("ATRN mode: TCP%s connection already present\n",
 		    tls_out.active.sock >= 0 ? "/TLS" : "");
     sx->cctx.sock = 0;
@@ -2473,13 +2480,13 @@ if (!continue_hostname || atrn_domains)
 	if (  read_ehlo_cache_entry(sx)
 	   && sx->ehlo_resp.cleartext_features & OPTION_EARLY_PIPE)
 	  {
-	  DEBUG(D_transport)
+	  DEBUG(transport)
 	    debug_printf("Using cached cleartext PIPECONNECT\n");
 	  sx->early_pipe_active = TRUE;
 	  sx->peer_offered = sx->ehlo_resp.cleartext_features;
 	  }
 	}
-      else DEBUG(D_transport)
+      else DEBUG(transport)
 	debug_printf("helo needs $sending_ip_address; avoid early-pipelining\n");
 
   CONNECT_RETRY:
@@ -2506,7 +2513,7 @@ if (!continue_hostname || atrn_domains)
 	if (sx->conn_args.host->tls_needs == SRV_TLS_ON_CONNECT)
 	  {
   SRV_SMTPS_RETRY:
-	  DEBUG(D_transport)
+	  DEBUG(transport)
 	    debug_printf(" TCP connect failed for port %d;"
 	      " retrying with require-STARTTLS on tpt option port\n", sx->port);
 	  sx->smtps = FALSE;
@@ -2563,7 +2570,7 @@ is the non-TFO-C case for smtps, where the Client Hello will go on the 3rd-ack.
   if (sx->helo_data)
     if (!(sx->helo_data = expand_string(sx->helo_data)))
       if (sx->verify)
-	log_write(0, LOG_MAIN|LOG_PANIC,
+	log_write(LOG_MAIN|LOG_PANIC,
 	  "<%s>: failed to expand transport's helo_data value for callout: %s",
 	  sx->addrlist->address, expand_string_message);
 
@@ -2575,7 +2582,7 @@ is the non-TFO-C case for smtps, where the Client Hello will go on the 3rd-ack.
 					      &expand_string_message)),
 	expand_string_message)
       if (sx->verify)
-	log_write(0, LOG_MAIN|LOG_PANIC,
+	log_write(LOG_MAIN|LOG_PANIC,
 	  "<%s>: failed to expand transport's helo_data value for callout: %s",
 	  sx->addrlist->address, expand_string_message);
       else
@@ -2676,8 +2683,6 @@ goto SEND_QUIT;
 #ifndef DISABLE_TLS
   if (sx->smtps)
     {
-    const uschar * s;
-
     smtp_peer_options |= OPTION_TLS;
     suppress_tls = FALSE;
     ob->tls_tempfail_tryclear = FALSE;
@@ -2689,8 +2694,11 @@ goto SEND_QUIT;
     force resumption attempts. */
 
     GET_OPTION("host_name_extract");
-    if (!(s = ob->host_name_extract)) s = US"never-LB";
-    ehlo_response_lbserver(sx, s);
+      {
+      const uschar * s;
+      if (!(s = ob->host_name_extract)) s = US"never-LB";
+      ehlo_response_lbserver(sx, s);
+      }
 # endif
     smtp_record_protocol_sequence(sx, US"s");
     goto TLS_NEGOTIATE;
@@ -2721,13 +2729,13 @@ goto SEND_QUIT;
       if (  (ob->hosts_require_auth || ob->hosts_try_auth)
 	 && f.smtp_in_early_pipe_no_auth)
 	{
-	DEBUG(D_transport)
+	DEBUG(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) != RESP_NOERROR)
 	  {
-	  HDEBUG(D_transport)
+	  HDEBUG(transport)
 	    debug_printf("failed reaping pipelined cmd responses\n");
 	  goto RESPONSE_FAILED;
 	  }
@@ -2740,7 +2748,7 @@ goto SEND_QUIT;
 	goto RESPONSE_FAILED;
     }
   else
-    DEBUG(D_transport)
+    DEBUG(transport)
       debug_printf("not sending EHLO (host matches hosts_avoid_esmtp)\n");
 
 #ifndef DISABLE_PIPE_CONNECT
@@ -2783,8 +2791,6 @@ goto SEND_QUIT;
     if (!sx->early_pipe_active)
 #endif
       {
-      const uschar * s;
-
       sx->peer_offered = ehlo_response(sx->buffer,
 	OPTION_TLS	/* others checked later */
 #ifndef DISABLE_PIPE_CONNECT
@@ -2814,7 +2820,7 @@ goto SEND_QUIT;
 	if (  (sx->peer_offered & (OPTION_PIPE | OPTION_EARLY_PIPE))
 	   == (OPTION_PIPE | OPTION_EARLY_PIPE))
 	  {
-	  DEBUG(D_transport)
+	  DEBUG(transport)
 	    debug_printf("PIPECONNECT usable in future for this IP\n");
 	  sx->ehlo_resp.cleartext_auths = study_ehlo_auths(sx);
 	  write_ehlo_cache_entry(sx);
@@ -2823,8 +2829,11 @@ goto SEND_QUIT;
 #endif
 #if !defined(DISABLE_TLS) && !defined(DISABLE_TLS_RESUME)
       GET_OPTION("host_name_extract");
-      if (!(s = ob->host_name_extract)) s = HNE_DEFAULT;
-      ehlo_response_lbserver(sx, s);
+	{
+	const uschar * s;
+	if (!(s = ob->host_name_extract)) s = HNE_DEFAULT;
+	ehlo_response_lbserver(sx, s);
+	}
 #endif
       }
 
@@ -2836,7 +2845,7 @@ goto SEND_QUIT;
 # ifdef EXPERIMENTAL_SRV_SMTPS
     else if (sx->conn_args.host->tls_needs == SRV_STARTTLS_MUST)
       {
-      log_write(0, LOG_MAIN,
+      log_write(LOG_MAIN,
 	  "Connection aborted; STARTTLS support by %s [%s] required by DNS SRV"
 	  " but STARTTLS not offered",
 	sx->conn_args.host->name, sx->conn_args.host->address);
@@ -2875,13 +2884,14 @@ else
 
     if (sx->conn_args.host->port != continue_host_port)
       {
-      DEBUG(D_transport)
+      DEBUG(transport)
 	debug_printf("Closing continued connection due to port mismatch:"
 	  " %d/%d\n", sx->conn_args.host->port, continue_host_port);
       smtp_debug_cmd(US"QUIT", 0);
       if (write(0, "QUIT\r\n", 6) < 0)
-	DEBUG(D_any) debug_printf("stupid compiler\n");
+	DEBUG(any) debug_printf("stupid compiler\n");
       close(0);
+      tls_out.active.sock = -1;
       continue_hostname = continue_proxy_cipher = NULL;
       f.continue_more = FALSE;
       continue_sequence = 1;	/* Ensure proper logging of non-cont-conn */
@@ -2909,7 +2919,7 @@ else
      )
     {
     sx->pipelining_used = pipelining_active = !!(smtp_peer_options & OPTION_PIPE);
-    HDEBUG(D_transport) debug_printf("continued connection, %s TLS\n",
+    HDEBUG(transport) debug_printf("continued connection, %s TLS\n",
       continue_proxy_cipher ? "proxied" : "verify conn with");
 
     tls_out.certificate_verified = !!(continue_flags & CTF_CV);
@@ -2921,7 +2931,7 @@ else
 #endif
     return OK;
     }
-  HDEBUG(D_transport) debug_printf("continued connection, no TLS\n");
+  HDEBUG(transport) debug_printf("continued connection, no TLS\n");
   }
 
 /* If TLS is available on this connection, whether continued or not, attempt to
@@ -2955,7 +2965,7 @@ if (  smtp_peer_options & OPTION_TLS
     {
     if (sync_responses(sx, 2, 0) != RESP_NOERROR)
       {
-      HDEBUG(D_transport)
+      HDEBUG(transport)
 	debug_printf("failed reaping pipelined cmd responses\n");
       close(sx->cctx.sock);
       sx->cctx.sock = -1;
@@ -3007,12 +3017,12 @@ if (  smtp_peer_options & OPTION_TLS
       be called again to try in clear on a new connection, if the options permit
       it for this host. */
   TLS_CONN_FAILED:
-      DEBUG(D_tls) debug_printf("TLS session fail: %s\n", tls_errstr);
+      DEBUG(tls) debug_printf("TLS session fail: %s\n", tls_errstr);
 
 # ifdef SUPPORT_DANE
       if (sx->conn_args.dane)
         {
-	log_write(0, LOG_MAIN,
+	log_write(LOG_MAIN,
 	  "DANE attempt failed; TLS connection to %s [%s]: %s",
 	  sx->conn_args.host->name, sx->conn_args.host->address, tls_errstr);
 #  ifndef DISABLE_EVENT
@@ -3027,6 +3037,7 @@ if (  smtp_peer_options & OPTION_TLS
       sx->send_quit = FALSE;
       goto TLS_FAILED;
       }
+    tls_out.smtp_quit = FALSE;
     sx->send_tlsclose = TRUE;
 
 # ifdef TCP_FASTOPEN
@@ -3040,7 +3051,7 @@ if (  smtp_peer_options & OPTION_TLS
 
     if (sx->inblock.ptr != sx->inblock.ptrend)
       {
-      DEBUG(D_tls)
+      DEBUG(tls)
 	{
 	int i = sx->inblock.ptrend - sx->inblock.ptr;
 	debug_printf("unused data in input buffer after ack for STARTTLS:\n"
@@ -3100,7 +3111,7 @@ if (tls_out.active.sock >= 0)
     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 PIPECONNECT\n");
+      DEBUG(transport) debug_printf("Using cached crypted PIPECONNECT\n");
     }
 #endif
 #ifdef EXPERIMENTAL_ESMTP_LIMITS
@@ -3132,7 +3143,7 @@ if (tls_out.active.sock >= 0)
   else
     {
     greeting_cmd = US"HELO";
-    DEBUG(D_transport)
+    DEBUG(transport)
       debug_printf("not sending EHLO (host matches hosts_avoid_esmtp)\n");
     }
 
@@ -3276,7 +3287,7 @@ if (   !continue_hostname && (!atrn_domains || atrn_mode && *atrn_mode == 'C')
        && 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",
+    DEBUG(transport) debug_printf("%susing PIPELINING\n",
       smtp_peer_options & OPTION_PIPE ? "" : "not ");
 
     if (  sx->peer_offered & OPTION_CHUNKING
@@ -3284,7 +3295,7 @@ if (   !continue_hostname && (!atrn_domains || atrn_mode && *atrn_mode == 'C')
       smtp_peer_options |= OPTION_CHUNKING;
 
     if (smtp_peer_options & OPTION_CHUNKING)
-      DEBUG(D_transport) debug_printf("CHUNKING usable\n");
+      DEBUG(transport) debug_printf("CHUNKING usable\n");
 
 #ifndef DISABLE_PRDR
     if (  sx->peer_offered & OPTION_PRDR
@@ -3292,12 +3303,12 @@ if (   !continue_hostname && (!atrn_domains || atrn_mode && *atrn_mode == 'C')
       smtp_peer_options |= OPTION_PRDR;
 
     if (smtp_peer_options & OPTION_PRDR)
-      DEBUG(D_transport) debug_printf("PRDR usable\n");
+      DEBUG(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",
+    DEBUG(transport) debug_printf("%susing DSN\n",
 			sx->peer_offered & OPTION_DSN ? "" : "not ");
 
 #ifndef DISABLE_PIPE_CONNECT
@@ -3308,7 +3319,7 @@ if (   !continue_hostname && (!atrn_domains || atrn_mode && *atrn_mode == 'C')
        && ( sx->ehlo_resp.cleartext_features | sx->ehlo_resp.crypted_features)
 	  & OPTION_EARLY_PIPE)
       {
-      DEBUG(D_transport) debug_printf("PIPECONNECT usable in future for this IP\n");
+      DEBUG(transport) debug_printf("PIPECONNECT usable in future for this IP\n");
       sx->ehlo_resp.crypted_auths = study_ehlo_auths(sx);
       write_ehlo_cache_entry(sx);
       }
@@ -3373,7 +3384,7 @@ if (sx->addrlist->prop.utf8_msg)
 
   sx->utf8_needed = !sx->addrlist->prop.utf8_downcvt
 		    && !sx->addrlist->prop.utf8_downcvt_maybe;
-  DEBUG(D_transport) if (!sx->utf8_needed)
+  DEBUG(transport) if (!sx->utf8_needed)
     debug_printf("utf8: %s downconvert\n",
       sx->addrlist->prop.utf8_downcvt ? "mandatory" : "optional");
   }
@@ -3484,7 +3495,10 @@ FAILED:
 SEND_QUIT:
 
 if (sx->send_quit)
+  {
   (void)smtp_write_command(sx, SCMD_FLUSH, "QUIT\r\n");
+  tls_out.smtp_quit = TRUE;
+  }
 
 #ifndef DISABLE_TLS
 if (sx->cctx.tls_ctx)
@@ -3503,7 +3517,7 @@ 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");
+HDEBUG(transport|acl|v) debug_printf_indent("  SMTP(close)>>\n");
 if (sx->send_quit)
   {
   shutdown(sx->cctx.sock, SHUT_WR);
@@ -3771,7 +3785,7 @@ for (addr = sx->first_addr, address_count = 0, pipe_limit = 100;
      && Ustrcmp(addr->domain, sx->first_addr->domain) != 0	/* dom diff from first */
      )
     {
-    DEBUG(D_transport) debug_printf("skipping different domain %s\n", addr->domain);
+    DEBUG(transport) debug_printf("skipping different domain %s\n", addr->domain);
 
     /* Ensure the smtp-response reaper does not think the address had a RCPT
     command sent for it.  Reset to PENDING_DEFER in smtp_deliver(), where we
@@ -3835,7 +3849,7 @@ for (addr = sx->first_addr, address_count = 0, pipe_limit = 100;
 
 	      if (sx->RCPT_452)
 		{
-		DEBUG(D_transport) debug_printf("seen 452 too-many-rcpts\n");
+		DEBUG(transport) debug_printf("seen 452 too-many-rcpts\n");
 		sx->RCPT_452 = FALSE;
 		/* sx->next_addr has been reset for fast_retry */
 		return sw_mrc_ok;
@@ -3875,7 +3889,7 @@ Do blocking full-size writes, and reads under a timeout.  Once both input
 channels are closed, exit the process.
 
 Arguments:
-  ct_ctx	tls context
+  ctx		client context
   buf		space to use for buffering
   bufsiz	size of buffer
   pfd		pipe filedescriptor array; [0] is comms to proxied process
@@ -3886,23 +3900,25 @@ Does not return.
 */
 
 void
-smtp_proxy_tls(void * ct_ctx, uschar * buf, size_t bsize, int * pfd,
+smtp_proxy_tls(client_conn_ctx * ctx, uschar * buf, size_t bsize, int * pfd,
   int timeout, const uschar * host)
 {
-struct pollfd p[2] = {{.fd = tls_out.active.sock, .events = POLLIN},
+struct pollfd p[2] = {{.fd = ctx->sock, .events = POLLIN},
 		      {.fd = pfd[0], .events = POLLIN}};
-int rc, i;
+void * tls_ctx = ctx->tls_ctx;
+pid_t pid;
 BOOL send_tls_shutdown = TRUE;
 
 acl_level++;
 close(pfd[1]);
-if ((rc = exim_fork(US"tls-proxy")))
-  _exit(rc < 0 ? EXIT_FAILURE : EXIT_SUCCESS);
+if ((pid = exim_fork(US"tls-proxy")))
+  _exit(pid < 0 ? EXIT_FAILURE : EXIT_SUCCESS);
 
 set_process_info("proxying TLS connection for continued transport to %s\n", host);
 
 do
   {
+  int rc;
   time_t time_left = timeout;
   time_t time_start = time(NULL);
 
@@ -3916,7 +3932,7 @@ do
 
     if (rc <= 0)
       {
-      DEBUG(D_transport) if (rc == 0) debug_printf("%s: timed out\n", __FUNCTION__);
+      DEBUG(transport) if (rc == 0) debug_printf("%s: timed out\n", __FUNCTION__);
       goto done;
       }
 
@@ -3924,25 +3940,25 @@ do
 
     if (p[0].revents & POLLERR || p[1].revents & POLLERR)
       {
-      DEBUG(D_transport) debug_printf("select: exceptional cond on %s fd\n",
+      DEBUG(transport) debug_printf("poll: err cond on %s fd\n",
 	p[0].revents & POLLERR ? "tls" : "proxy");
       if (!(p[0].revents & POLLIN || p[1].events & POLLIN))
 	goto done;
-      DEBUG(D_transport) debug_printf("- but also readable; no exit yet\n");
+      DEBUG(transport) debug_printf("- but also readable; no exit yet\n");
       }
     }
   while (rc < 0 || !(p[0].revents & POLLIN || p[1].revents & POLLIN));
 
   /* handle inbound data */
   if (p[0].revents & POLLIN)
-    if ((rc = tls_read(ct_ctx, buf, bsize)) <= 0)	/* Expect -1 for EOF; */
+    if ((rc = tls_read(tls_ctx, buf, bsize)) <= 0)	/* Expect -1 for EOF; */
       {				    /* that reaps the TLS Close Notify record */
       p[0].fd = -1;
       shutdown(pfd[0], SHUT_WR);
       timeout = 5;
       }
     else
-      for (int nbytes = 0; rc - nbytes > 0; nbytes += i)
+      for (int nbytes = 0, i; rc - nbytes > 0; nbytes += i)
 	if ((i = write(pfd[0], buf + nbytes, rc - nbytes)) < 0) goto done;
 
   /* Handle outbound data.  We cannot yet combine payload and the TLS-close
@@ -3959,19 +3975,19 @@ do
 # ifdef EXIM_TCP_CORK  /* Use _CORK to get TLS Close Notify in FIN segment */
       (void) setsockopt(tls_out.active.sock, IPPROTO_TCP, EXIM_TCP_CORK, US &on, sizeof(on));
 # endif
-      tls_shutdown_wr(ct_ctx);
+      tls_shutdown_wr(tls_ctx);
       send_tls_shutdown = FALSE;
       shutdown(tls_out.active.sock, SHUT_WR);
       }
     else
-      for (int nbytes = 0; rc - nbytes > 0; nbytes += i)
-	if ((i = tls_write(ct_ctx, buf + nbytes, rc - nbytes, FALSE)) < 0)
+      for (int nbytes = 0, i; rc - nbytes > 0; nbytes += i)
+	if ((i = tls_write(tls_ctx, buf + nbytes, rc - nbytes, FALSE)) < 0)
 	  goto done;
   }
 while (p[0].fd >= 0 || p[1].fd >= 0);
 
 done:
-  if (send_tls_shutdown) tls_close(ct_ctx, TLS_SHUTDOWN_NOWAIT);
+  if (send_tls_shutdown) tls_close(tls_ctx, TLS_SHUTDOWN_NOWAIT);
   testharness_pause_ms(100);	/* let logging complete */
   exim_exit(EXIT_SUCCESS);
 }
@@ -4038,12 +4054,13 @@ int yield = OK;
 int save_errno;
 int rc;
 
-uschar *message = NULL;
-smtp_context * sx = store_get(sizeof(*sx), GET_TAINTED);	/* tainted, for the data buffers */
+uschar * message = NULL;
+
+/* Large (12k).  Tainted, for the data buffers */
+smtp_context * sx = store_get(sizeof(*sx), GET_TAINTED);
+
 BOOL pass_message = FALSE;
-#ifndef DISABLE_ESMTP_LIMITS
 BOOL mail_limit = FALSE;
-#endif
 #ifdef SUPPORT_DANE
 BOOL dane_held;
 #endif
@@ -4096,7 +4113,7 @@ if (sx->conn_args.dane)
     if (  a->transport_return == PENDING_DEFER
        && Ustrcmp(dane_domain, a->domain) != 0)
       {
-      DEBUG(D_transport) debug_printf("DANE: holding %s for later\n", a->domain);
+      DEBUG(transport) debug_printf("DANE: holding %s for later\n", a->domain);
       dane_held = TRUE;
       a->transport_return = DANE;
       }
@@ -4135,7 +4152,7 @@ if (tblock->filter_command)
      )
     {
     smtp_peer_options &= ~OPTION_CHUNKING;
-    DEBUG(D_transport) debug_printf("CHUNKING not usable due to transport filter\n");
+    DEBUG(transport) debug_printf("CHUNKING not usable due to transport filter\n");
     }
   }
 
@@ -4234,7 +4251,7 @@ if (  !(smtp_peer_options & OPTION_CHUNKING)
 
 #ifndef DISABLE_PIPE_CONNECT
     case -5:				/* TLS first-read error */
-    case -4:  HDEBUG(D_transport)
+    case -4:  HDEBUG(transport)
 		debug_printf("failed reaping pipelined cmd responses\n");
 #endif
     default: goto RESPONSE_FAILED;       /* I/O error, or any MAIL/DATA error */
@@ -4304,7 +4321,7 @@ else
   sigalrm_seen = FALSE;
   transport_write_timeout = ob->data_timeout;
   smtp_command = US"sending data block";   /* For error messages */
-  DEBUG(D_transport|D_v)
+  DEBUG(transport|v)
     if (smtp_peer_options & OPTION_CHUNKING)
       debug_printf("         will write message using CHUNKING\n");
     else
@@ -4389,7 +4406,7 @@ else
 	     (oicf)smtp_are_same_identities, (void*)&t_compare);
     if (!tcw)
       {
-      HDEBUG(D_transport) debug_printf("will pipeline QUIT\n");
+      HDEBUG(transport) debug_printf("will pipeline QUIT\n");
       tctx.options |= topt_no_flush;
       }
 
@@ -4473,9 +4490,10 @@ else
 	sx->send_tlsclose = FALSE;	/* avoid later repeat */
 	}
 #endif
-      HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP(shutdown)>>\n");
+      HDEBUG(transport|acl|v) debug_printf_indent("  SMTP(shutdown)>>\n");
       shutdown(sx->cctx.sock, SHUT_WR);	/* flush output buffer, with TCP FIN */
       }
+    tls_out.smtp_quit = TRUE;
     }
 
   if (smtp_peer_options & OPTION_CHUNKING && sx->cmd_count > 1)
@@ -4496,7 +4514,7 @@ else
 
 #ifndef DISABLE_PIPE_CONNECT
       case RESP_EHLO_ERR_TLS:				/* TLS first-read error */
-      case RESP_EPIPE_EHLO_ERR:  HDEBUG(D_transport)
+      case RESP_EPIPE_EHLO_ERR:  HDEBUG(transport)
 		  debug_printf("failed reaping pipelined cmd responses\n");
 #endif
       default:		     goto RESPONSE_FAILED;	/* I/O error, or any MAIL/DATA error */
@@ -4675,10 +4693,10 @@ else
         else
           sprintf(CS sx->buffer, "%.500s\n", addr->unique);
 
-        DEBUG(D_deliver) debug_printf("S:journalling %s", sx->buffer);
+        DEBUG(deliver) debug_printf("S:journalling %s", 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(LOG_MAIN|LOG_PANIC, "failed to write journal for "
             "%s: %s", sx->buffer, strerror(errno));
         }
       }
@@ -4722,10 +4740,10 @@ else
 	  else
 	    sprintf(CS sx->buffer, "%.500s\n", addr->unique);
 
-	  DEBUG(D_deliver) debug_printf("journalling(PRDR) %s\n", sx->buffer);
+	  DEBUG(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 "
+	    log_write(LOG_MAIN|LOG_PANIC, "failed to write journal for "
 	      "%s: %s", sx->buffer, strerror(errno));
 	  }
 	else if (addr->transport_return == DEFER)
@@ -4737,7 +4755,7 @@ else
     /* Ensure the journal file is pushed out to disk. */
 
     if (EXIMfsync(journal_fd) < 0)
-      log_write(0, LOG_MAIN|LOG_PANIC, "failed to fsync journal: %s",
+      log_write(LOG_MAIN|LOG_PANIC, "failed to fsync journal: %s",
         strerror(errno));
     }
   }
@@ -4818,7 +4836,7 @@ if (!sx->ok)
 	      break;
 	  if (!addr)	/* all rcpts fates determined */
 	    {
-	    log_write(0, LOG_MAIN, "peer close after all rcpt responses;"
+	    log_write(LOG_MAIN, "peer close after all rcpt responses;"
 	      " converting i/o-error to no-error");
 	    sx->ok = TRUE;
 	    goto happy;
@@ -4884,7 +4902,7 @@ if (!sx->ok)
         *message_defer = TRUE;
         }
 #ifdef TIOCOUTQ
-      DEBUG(D_transport) if (sx->cctx.sock >= 0)
+      DEBUG(transport) if (sx->cctx.sock >= 0)
 	{
 	int n;
 	if (ioctl(sx->cctx.sock, TIOCOUTQ, &n) == 0)
@@ -4954,16 +4972,15 @@ hosts_nopass_tls. */
 
 happy:
 
-DEBUG(D_transport)
+DEBUG(transport)
   debug_printf("ok=%d send_quit=%d send_rset=%d continue_more=%d "
     "yield=%d first_address is %sNULL\n", sx->ok, sx->send_quit,
     sx->send_rset, f.continue_more, yield, sx->first_addr ? "not " : "");
 
 if (sx->completed_addr && sx->ok && sx->send_quit)
-#ifndef DISABLE_ESMTP_LIMITS
   if (mail_limit = continue_sequence >= sx->max_mail)
     {
-    DEBUG(D_transport)
+    DEBUG(transport)
       debug_printf("reached limit %u for MAILs per conn\n", sx->max_mail);
     /* We will close the smtp session and connection, and clear
     continue_hostname.  Then if there are further addrs for the message we will
@@ -4973,7 +4990,6 @@ if (sx->completed_addr && sx->ok && sx->send_quit)
     process. */
     }
   else
-#endif
     {
     BOOL send_rst;
     smtp_compare_t t_compare =
@@ -5021,7 +5037,7 @@ if (sx->completed_addr && sx->ok && sx->send_quit)
 	    &pass_message);
 	  if (!sx->send_quit)
 	    {
-	    DEBUG(D_transport) debug_printf("H=%s [%s] %s\n",
+	    DEBUG(transport) debug_printf("H=%s [%s] %s\n",
 	      host->name, host->address, msg);
 	    }
 	  }
@@ -5097,16 +5113,16 @@ if (sx->completed_addr && sx->ok && sx->send_quit)
 	    smtp_peer_options |= OPTION_TLS;
 	    if ((sx->ok = socketpair(AF_UNIX, SOCK_STREAM, 0, pfd) == 0))
 	      {
-	      int pid = exim_fork(US"tls-proxy-interproc");
+	      pid_t pid = exim_fork(US"tls-proxy-interproc/t");
 	      if (pid == 0)	/* child; fork again to disconnect totally */
 		{
 		/* does not return */
-		smtp_proxy_tls(sx->cctx.tls_ctx, sx->buffer, sizeof(sx->buffer),
+		smtp_proxy_tls(&sx->cctx, sx->buffer, sizeof(sx->buffer),
 			      pfd, ob->command_timeout, host->name);
 		}
 
 	      if (pid < 0)
-		log_write_die(0, LOG_PANIC_DIE, "fork failed");
+		log_write_die(LOG_PANIC_DIE, "fork failed");
 
 	      close(pfd[0]);
 	      continue_fd = pfd[1];
@@ -5170,6 +5186,10 @@ if (sx->send_quit)
   {			/* Use _MORE to get QUIT in FIN segment */
   (void)smtp_write_command(sx, SCMD_MORE, "QUIT\r\n");
 #ifndef DISABLE_TLS
+  /* This flag is a custom hack to avoid logging, under GnuTLS, Google's
+  habit of just dropping the TCP conn (which violates TLS spec) */
+  tls_out.smtp_quit = TRUE;
+
   if (sx->cctx.tls_ctx && sx->send_tlsclose)
     {
 # ifdef EXIM_TCP_CORK	/* Use _CORK to get TLS Close Notify in FIN segment */
@@ -5200,7 +5220,7 @@ if (sx->send_quit)
   means we (client) take the TIME_WAIT state, so the server (which likely has a
   higher connection rate) does not have to. */
 
-  HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP(shutdown)>>\n");
+  HDEBUG(transport|acl|v) debug_printf_indent("  SMTP(shutdown)>>\n");
   shutdown(sx->cctx.sock, SHUT_WR);
   }
 
@@ -5244,7 +5264,7 @@ if (sx->send_quit || tcw_done && !tcw)
      && fcntl(sx->cctx.sock, F_SETFL, O_NONBLOCK) == 0)
     for (int i = 16, n;						/* drain socket */
 	 (n = read(sx->cctx.sock, sx->inbuffer, sizeof(sx->inbuffer))) > 0 && i > 0;
-	 i--) HDEBUG(D_transport|D_acl|D_v)
+	 i--) HDEBUG(transport|acl|v)
       {
       int m = MIN(n, 64);
       debug_printf_indent("  SMTP(drain %d bytes)<< %.*s\n", n, m, sx->inbuffer);
@@ -5252,7 +5272,7 @@ if (sx->send_quit || tcw_done && !tcw)
 	debug_printf("0x%02x\n", sx->inbuffer[m]);
       }
   }
-HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP(close)>>\n");
+HDEBUG(transport|acl|v) debug_printf_indent("  SMTP(close)>>\n");
 (void)close(sx->cctx.sock);
 sx->cctx.sock = -1;
 continue_hostname = NULL;
@@ -5276,7 +5296,7 @@ if (dane_held)
 	to get the domain string for SNI */
 
 	sx->first_addr = a;
-	DEBUG(D_transport) debug_printf("DANE: go-around for %s\n", a->domain);
+	DEBUG(transport) debug_printf("DANE: go-around for %s\n", a->domain);
 	}
       }
   continue_sequence = 1;			/* for consistency */
@@ -5284,7 +5304,6 @@ if (dane_held)
   }
 #endif
 
-#ifndef DISABLE_ESMTP_LIMITS
 if (mail_limit && sx->first_addr)
   {
   /* Reset the sequence count since we closed the connection.  This is flagged
@@ -5294,7 +5313,6 @@ if (mail_limit && sx->first_addr)
   continue_sequence = 1;			/* for consistency */
   goto REPEAT_CONN;				/* open a fresh connection */
   }
-#endif
 
 OUT:
   smtp_debug_cmd_report();
@@ -5334,7 +5352,7 @@ smtp_transport_closedown(transport_instance * tblock)
 {
 const smtp_transport_options_block * ob = tblock->drinst.options_block;
 client_conn_ctx cctx;
-smtp_context sx = {0};
+smtp_context sx = {0};		/* Large (12k, 66k if DANE supported) */
 uschar buffer[256];
 uschar inbuffer[4096];
 uschar outbuffer[16];
@@ -5357,6 +5375,7 @@ sx.outblock.cmd_count = 0;
 sx.outblock.authenticating = FALSE;
 
 (void)smtp_write_command(&sx, SCMD_FLUSH, "QUIT\r\n");
+tls_out.smtp_quit = TRUE;
 (void)smtp_read_response(&sx, buffer, sizeof(buffer), '2', ob->command_timeout);
 (void)close(cctx.sock);
 }
@@ -5368,7 +5387,7 @@ int
 smtp_write_atrn(address_item * addr, cut_t * cutp)
 {
 const smtp_transport_options_block * ob = addr->transport->drinst.options_block;
-smtp_context sx = {0};
+smtp_context sx = {0};		/* Large (12k, 66k if DANE supported */
 uschar buffer[256];
 uschar inbuffer[4096];
 uschar outbuffer[256];
@@ -5474,7 +5493,7 @@ uschar * expanded_hosts = NULL, * pistring;
 uschar * tid = string_sprintf("%s transport", trname);
 host_item * hostlist = addrlist->host_list, * host = NULL;
 
-DEBUG(D_transport)
+DEBUG(transport)
   {
   debug_printf("%s transport entered\n", trname);
   for (address_item * addr = addrlist; addr; addr = addr->next)
@@ -5532,7 +5551,7 @@ if (!hostlist || (ob->hosts_override && ob->hosts))
     return FALSE;   /* Only top address has status */
     }
 
-  DEBUG(D_transport) debug_printf("using the transport's hosts: %s\n",
+  DEBUG(transport) debug_printf("using the transport's hosts: %s\n",
     ob->hosts);
 
   /* If the transport's host list contains no '$' characters, and we are not
@@ -5556,7 +5575,7 @@ if (!hostlist || (ob->hosts_override && ob->hosts))
         addrlist->transport_return = f.search_find_defer ? DEFER : PANIC;
         return FALSE;     /* Only top address has status */
         }
-      DEBUG(D_transport)
+      DEBUG(transport)
 	debug_printf("expanded list of hosts %q to %q\n", s, expanded_hosts);
       s = expanded_hosts;
       }
@@ -5565,7 +5584,7 @@ if (!hostlist || (ob->hosts_override && ob->hosts))
 
     if (is_tainted(s))
       {
-      log_write(0, LOG_MAIN|LOG_PANIC,
+      log_write(LOG_MAIN|LOG_PANIC,
 	"attempt to use tainted host list '%s' from '%s' in transport %s",
 	s, ob->hosts, trname);
       /* Avoid leaking info to an attacker */
@@ -5577,7 +5596,7 @@ if (!hostlist || (ob->hosts_override && ob->hosts))
     /* Get the hosts_randomize transport option, expanding if needed */
       {
       BOOL randomize;
-      if (exp_bool(addrlist, US"transport", tblock->drinst.name, D_transport,
+      if (exp_bool(addrlist, US"transport", tblock->drinst.name, IS_DEBUG(transport),
 	  US"hosts_randomize", ob->hosts_randomize, ob->expand_hosts_randomize,
 	  &randomize))
 	{
@@ -5617,7 +5636,7 @@ connection). */
 else if (hostlist->mx == MX_NONE && !continue_hostname)
   {
   BOOL randomize;
-  if (exp_bool(addrlist, US"transport", tblock->drinst.name, D_transport,
+  if (exp_bool(addrlist, US"transport", tblock->drinst.name, IS_DEBUG(transport),
       US"hosts_randomize", ob->hosts_randomize, ob->expand_hosts_randomize,
       &randomize))
     {
@@ -5738,7 +5757,7 @@ retry_non_continued:
 
     if (total_hosts_tried > 0)
       {
-      DEBUG(D_transport|D_acl|D_v)
+      DEBUG(transport|acl|v)
 	debug_printf("Clearing TFO as not first host for message\n");
       ob->hosts_try_fastopen = US"";
       }
@@ -5769,12 +5788,12 @@ retry_non_continued:
 
       if (host->status >= hstatus_unusable)
         {
-        DEBUG(D_transport) debug_printf("%s has no address and is unusable - skipping\n",
+        DEBUG(transport) debug_printf("%s has no address and is unusable - skipping\n",
           host->name);
         continue;
         }
 
-      DEBUG(D_transport) debug_printf("getting address for %s\n", host->name);
+      DEBUG(transport) debug_printf("getting address for %s\n", host->name);
 
       /* The host name is permitted to have an attached port. Find it, and
       strip it from the name. Just remember it for now. */
@@ -5817,7 +5836,7 @@ retry_non_continued:
         retry_add_item(addrlist, string_sprintf("R:%s", host->name), 0);
         expired = FALSE;
         if (rc == HOST_FIND_AGAIN) hosts_defer++; else hosts_fail++;
-        DEBUG(D_transport) debug_printf("rc = %s for %s\n", (rc == HOST_FIND_AGAIN)?
+        DEBUG(transport) debug_printf("rc = %s for %s\n", (rc == HOST_FIND_AGAIN)?
           "HOST_FIND_AGAIN" : "HOST_FIND_FAILED", host->name);
         host->status = hstatus_unusable;
 
@@ -5886,12 +5905,15 @@ retry_non_continued:
 	      &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL) == OK)
        )
       {
-      DEBUG(D_transport) debug_printf("first-pass routing only\n");
+      DEBUG(transport) debug_printf("first-pass routing only\n");
       expired = FALSE;
       for (address_item * addr = addrlist; addr; addr = addr->next)
         if (addr->transport_return == DEFER)
+	  {
+	  addr->basic_errno = ERRNO_PASSONE;
 	  addr->message = US"first-pass only routing due to -odqs, "
 			    "queue_smtp_domains or control=queue";
+	  }
       continue;      /* With next host */
       }
 
@@ -5940,7 +5962,7 @@ retry_non_continued:
 		|| Ustrcmp(interface, sending_ip_address) != 0)
 	     )
 	    {
-	    DEBUG(D_transport) debug_printf_indent(
+	    DEBUG(transport) debug_printf_indent(
 	      "tpt interface option mismatch with continued-connection\n");
 	    /* Close the conn and recheck retry info */
 	    continue_host_tried = FALSE;
@@ -5965,7 +5987,7 @@ retry_non_continued:
       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", trname, D_transport,
+      if (exp_bool(addrlist, US"transport", trname, IS_DEBUG(transport),
 		US"retry_include_ip_address", ob->retry_include_ip_address,
 		ob->expand_retry_include_ip_address, &incl_ip) != OK)
 	continue;	/* with next host */
@@ -5973,7 +5995,7 @@ retry_non_continued:
       host_is_expired = retry_check_address(addrlist->domain, host, pistring,
         incl_ip, &retry_host_key, &retry_message_key);
 
-      DEBUG(D_transport) debug_printf("%s [%s]%s retry-status = %s\n", host->name,
+      DEBUG(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"
@@ -6016,7 +6038,7 @@ retry_non_continued:
          || host->status != hstatus_unusable_expired
 	 || host->last_try > received_time.tv_sec)
         continue;
-      DEBUG(D_transport) debug_printf("trying expired host %s [%s]%s\n",
+      DEBUG(transport) debug_printf("trying expired host %s [%s]%s\n",
           host->name, host->address, pistring);
       host_is_expired = TRUE;
       }
@@ -6039,7 +6061,7 @@ retry_non_continued:
       serialize_key = string_sprintf("host-serialize-%s", host->name);
       if (!enq_start(serialize_key, 1))
         {
-        DEBUG(D_transport)
+        DEBUG(transport)
           debug_printf("skipping host %s because another Exim process "
             "is connected to it\n", host->name);
         hosts_serial++;
@@ -6059,11 +6081,11 @@ retry_non_continued:
       /* Obscure situation; at least one case (bug 3059, fixed) where
       a previous host try returned DEFER, but having moved all
       recipients away from DEFER (the waiting-to-be-done state). */
-      DEBUG(D_transport) debug_printf("no pending recipients\n");
+      DEBUG(transport) debug_printf("no pending recipients\n");
       goto END_TRANSPORT;
       }
 
-    DEBUG(D_transport) debug_printf("delivering %s to %s [%s] (%s%s)\n",
+    DEBUG(transport) debug_printf("delivering %s to %s [%s] (%s%s)\n",
       message_id, host->name, host->address, addrlist->address,
       addrlist->next ? ", ..." : "");
 
@@ -6085,7 +6107,7 @@ retry_non_continued:
         addr->special_action = '*';
         addr->message = US"delivery bypassed by -N option";
         }
-      DEBUG(D_transport)
+      DEBUG(transport)
         {
         debug_printf("*** delivery by %s transport bypassed by -N option\n"
                      "*** host and remaining hosts:\n", trname);
@@ -6127,13 +6149,13 @@ retry_non_continued:
 
       if (!host_is_expired && ++unexpired_hosts_tried >= ob->hosts_max_try)
         {
-        DEBUG(D_transport)
+        DEBUG(transport)
           debug_printf("hosts_max_try limit reached with this host\n");
         for (host_item * 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 "
+	  DEBUG(transport) debug_printf("however, a higher MX host exists "
 	    "and will be tried\n");
 	  break;
 	  }
@@ -6185,7 +6207,7 @@ retry_non_continued:
 	 && verify_check_given_host(CUSS &ob->hosts_require_tls, host) != OK
 	 )
         {
-        log_write(0, LOG_MAIN,
+        log_write(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);
@@ -6237,7 +6259,7 @@ retry_non_continued:
       if (!retry_host_key)
         {
 	BOOL incl_ip;
-	if (exp_bool(addrlist, US"transport", trname, D_transport,
+	if (exp_bool(addrlist, US"transport", trname, IS_DEBUG(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 */
@@ -6283,7 +6305,7 @@ retry_non_continued:
       if (!retry_message_key)
         {
 	BOOL incl_ip;
-	if (exp_bool(addrlist, US"transport", trname, D_transport,
+	if (exp_bool(addrlist, US"transport", trname, IS_DEBUG(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 */
@@ -6345,12 +6367,12 @@ retry_non_continued:
       if (timedout)
         {
         unexpired_hosts_tried--;
-        DEBUG(D_transport) debug_printf("temporary delivery error(s) override "
+        DEBUG(transport) debug_printf("temporary delivery error(s) override "
           "hosts_max_try (message older than host's retry time)\n");
         }
       }
 
-    DEBUG(D_transport)
+    DEBUG(transport)
       {
       if (unexpired_hosts_tried >= ob->hosts_max_try)
 	debug_printf("reached transport hosts_max_try limit %d\n",
@@ -6372,8 +6394,8 @@ retry_non_continued:
     {
     int fd = cutthrough.cctx.sock >= 0 ? cutthrough.cctx.sock : 0;
 
-    DEBUG(D_transport) debug_printf("no hosts match already-open connection\n");
-    DEBUG(D_transport) debug_printf("  SMTP>>QUIT\n");
+    DEBUG(transport) debug_printf("no hosts match already-open connection\n");
+    DEBUG(transport) debug_printf("  SMTP>>QUIT\n");
 #ifndef DISABLE_TLS
     /* A TLS conn could be open for a cutthrough, but not for a plain continued-
     transport */
@@ -6391,7 +6413,7 @@ retry_non_continued:
       (void) write(fd, US"QUIT\r\n", 6);
 #endif
 
-    DEBUG(D_transport) debug_printf("  SMTP(close)>>\n");
+    DEBUG(transport) debug_printf("  SMTP(close)>>\n");
     (void) close(fd);
     cutthrough.cctx.sock = -1;
     continue_hostname = NULL;
@@ -6403,7 +6425,7 @@ retry_non_continued:
   ob->delay_after_cutoff is FALSE. The second time round we will
   try those hosts that haven't been tried since the message arrived. */
 
-  DEBUG(D_transport)
+  DEBUG(transport)
     {
     debug_printf("all IP addresses skipped or deferred at least one address\n");
     if (expired && !ob->delay_after_cutoff && cutoff_retry == 0)
@@ -6447,13 +6469,13 @@ for (address_item * addr = addrlist; addr; addr = addr->next)
   if (host)
     if (total_hosts_tried >= ob->hosts_max_try_hardlimit)
       {
-      DEBUG(D_transport)
+      DEBUG(transport)
         debug_printf("hosts_max_try_hardlimit reached: behave as if all "
           "hosts were tried\n");
       }
     else
       {
-      DEBUG(D_transport)
+      DEBUG(transport)
         debug_printf("hosts_max_try limit caused some hosts to be skipped\n");
       setflag(addr, af_retry_skipped);
       }
@@ -6524,7 +6546,7 @@ if (update_waiting && tblock->connection_max_messages != 1)
 
 END_TRANSPORT:
 
-DEBUG(D_transport) debug_printf("Leaving %s transport\n", trname);
+DEBUG(transport) debug_printf("Leaving %s transport\n", trname);
 
 return TRUE;   /* Each address has its status */
 }
@@ -6545,7 +6567,7 @@ transport_info smtp_transport_info = {
   .options_block =	&smtp_transport_option_defaults,
   .options_len =	sizeof(smtp_transport_options_block),
   .init =		smtp_transport_init,
-# ifdef DYNLOOKUP
+# if TRANSPORT_SMTP==2
   .dyn_magic =		TRANSPORT_MAGIC,
 # endif
   },
diff --git a/src/src/transports/smtp.h b/src/src/transports/smtp.h
index df6c9e963..72b3b00ba 100644
--- a/src/src/transports/smtp.h
+++ b/src/src/transports/smtp.h
@@ -144,7 +144,7 @@ typedef struct {
   const uschar *	from_addr;
   address_item *	addrlist;
 
-  smtp_connect_args	conn_args;
+  smtp_connect_args	conn_args;	/* Large (if DANE supported) */
   int			port;
 
   BOOL verify:1;
@@ -202,7 +202,7 @@ typedef struct {
 
   unsigned	avoid_option;
   uschar *	igquotstr;
-  uschar *	helo_data;
+  const uschar * helo_data;
 #ifdef EXPERIMENTAL_DSN_INFO
   uschar *	smtp_greeting;
   uschar *	helo_response;
@@ -250,9 +250,4 @@ extern BOOL smtp_transport_entry(transport_instance *, address_item *);
 extern void smtp_transport_closedown(transport_instance *);
 
 
-
-#ifdef SUPPORT_SOCKS
-extern int     socks_sock_connect(smtp_connect_args *, const blob *);
-#endif
-
 /* End of transports/smtp.h */
diff --git a/src/src/transports/tf_maildir.c b/src/src/transports/tf_maildir.c
index 60716f1a8..28a5f6dd3 100644
--- a/src/src/transports/tf_maildir.c
+++ b/src/src/transports/tf_maildir.c
@@ -50,7 +50,7 @@ int i;
 struct stat statbuf;
 const char * const subdirs[] = { "/tmp", "/new", "/cur" };
 
-DEBUG(D_transport)
+DEBUG(transport)
   debug_printf("ensuring maildir directories exist in %s\n", path);
 
 /* First ensure that the path we have is a directory; if it does not exist,
@@ -102,7 +102,7 @@ for (i = 0; i < 4; i++)
         addr->basic_errno = errno;
         return FALSE;
         }
-      DEBUG(D_transport)
+      DEBUG(transport)
         debug_printf("created directory %s%s\n", path, mdir);
       break;   /* out of the race loop */
       }
@@ -145,7 +145,7 @@ if (maildirfolder_create_regex)
   {
   const pcre2_code * re;
 
-  DEBUG(D_transport) debug_printf("checking for maildirfolder requirement\n");
+  DEBUG(transport) debug_printf("checking for maildirfolder requirement\n");
 
   if (!(re = regex_compile(maildirfolder_create_regex,
 	      MCS_NOFLAGS, &addr->message, pcre_gen_cmp_ctx)))
@@ -156,7 +156,7 @@ if (maildirfolder_create_regex)
     uschar *fname = string_sprintf("%s/maildirfolder", path);
     if (Ustat(fname, &statbuf) == 0)
       {
-      DEBUG(D_transport) debug_printf("maildirfolder already exists\n");
+      DEBUG(transport) debug_printf("maildirfolder already exists\n");
       }
     else
       {
@@ -168,12 +168,12 @@ if (maildirfolder_create_regex)
         return FALSE;
         }
       (void)close(fd);
-      DEBUG(D_transport) debug_printf("created maildirfolder file\n");
+      DEBUG(transport) debug_printf("created maildirfolder file\n");
       }
     }
   else
     {
-    DEBUG(D_transport) debug_printf("maildirfolder file not required\n");
+    DEBUG(transport) debug_printf("maildirfolder file not required\n");
     }
   }
 
@@ -207,7 +207,7 @@ len = Ustrlen(buffer);
 if (lseek(fd, 0, SEEK_END) >= 0)
   {
   len = write(fd, buffer, len);
-  DEBUG(D_transport)
+  DEBUG(transport)
     debug_printf("added '%.*s' to maildirsize file\n", len-1, buffer);
   }
 }
@@ -265,7 +265,7 @@ for (struct dirent *ent; ent = readdir(dir); )
 
   if (dir_regex && !regex_match(dir_regex, name, -1, NULL))
     {
-    DEBUG(D_transport)
+    DEBUG(transport)
       debug_printf("skipping %s/%s: dir_regex does not match\n", path, name);
     continue;
     }
@@ -275,7 +275,7 @@ for (struct dirent *ent; ent = readdir(dir); )
   s = string_sprintf("%s/%s", path, name);
   if (Ustat(s, &statbuf) < 0)
     {
-    DEBUG(D_transport)
+    DEBUG(transport)
       debug_printf("maildir_compute_size: stat error %d for %s: %s\n", errno,
         s, strerror(errno));
     continue;
@@ -283,7 +283,7 @@ for (struct dirent *ent; ent = readdir(dir); )
 
   if ((statbuf.st_mode & S_IFMT) != S_IFDIR)
     {
-    DEBUG(D_transport)
+    DEBUG(transport)
       debug_printf("skipping %s/%s: not a directory\n", s, name);
     continue;
     }
@@ -306,7 +306,7 @@ for (struct dirent *ent; ent = readdir(dir); )
   }
 
 closedir(dir);
-DEBUG(D_transport)
+DEBUG(transport)
   {
   if (timestamp_only)
     debug_printf("maildir_compute_size (timestamp_only): %ld\n",
@@ -366,11 +366,11 @@ the same thing. */
 
 filename = string_sprintf("%s/maildirsize", path);
 
-DEBUG(D_transport) debug_printf("looking for maildirsize in %s\n", path);
+DEBUG(transport) debug_printf("looking for maildirsize in %s\n", path);
 if ((fd = Uopen(filename, O_RDWR|O_APPEND, ob->mode ? ob->mode : 0600)) < 0)
   {
   if (errno != ENOENT) return -1;
-  DEBUG(D_transport)
+  DEBUG(transport)
     debug_printf("%s does not exist: recalculating\n", filename);
   goto RECALCULATE;
   }
@@ -381,7 +381,7 @@ compute the maildir size from the file. */
 
 if ((count = read(fd, buffer, sizeof(buffer))) >= sizeof(buffer))
   {
-  DEBUG(D_transport)
+  DEBUG(transport)
     debug_printf("maildirsize file too big (%d): recalculating\n", count);
   goto RECALCULATE;
   }
@@ -389,7 +389,7 @@ buffer[count] = 0;   /* Ensure string terminated */
 
 /* Read the quota parameters from the first line of the data. */
 
-DEBUG(D_transport)
+DEBUG(transport)
   debug_printf("reading quota parameters from maildirsize data\n");
 
 for (;;)
@@ -404,7 +404,7 @@ for (;;)
     else if (*endptr == 'C') cached_quota_filecount = (int)n;
   if (!isalpha(*endptr++))
     {
-    DEBUG(D_transport)
+    DEBUG(transport)
       debug_printf("quota parameter number not followed by letter in "
         "\"%.*s\": recalculating maildirsize\n", (int)(endptr - buffer),
         buffer);
@@ -413,7 +413,7 @@ for (;;)
   if (*endptr == '\n' || !*endptr) break;
   if (*endptr++ != ',')
     {
-    DEBUG(D_transport)
+    DEBUG(transport)
       debug_printf("quota parameter not followed by comma in "
         "\"%.*s\": recalculating maildirsize\n", (int)(endptr - buffer),
         buffer);
@@ -427,7 +427,7 @@ for (;;)
 if (cached_quota != ob->quota_value ||
     cached_quota_filecount != ob->quota_filecount_value)
   {
-  DEBUG(D_transport)
+  DEBUG(transport)
     debug_printf("cached quota is out of date: recalculating\n"
       "  quota=" OFF_T_FMT " cached_quota=" OFF_T_FMT " filecount_quota=%d "
       "cached_quota_filecount=%d\n", ob->quota_value,
@@ -438,7 +438,7 @@ if (cached_quota != ob->quota_value ||
 /* Quota values agree; parse the rest of the data to get the sizes. At this
 stage, *endptr points either to 0 or to '\n'.  */
 
-DEBUG(D_transport)
+DEBUG(transport)
   debug_printf("computing maildir size from maildirsize data\n");
 
 for (; *endptr++ == '\n' && *endptr; )
@@ -462,7 +462,7 @@ if (!*endptr)
   {
   if (size < 0 || filecount < 0)
     {
-    DEBUG(D_transport) debug_printf("negative value in maildirsize "
+    DEBUG(transport) debug_printf("negative value in maildirsize "
       "(size=" OFF_T_FMT " count=%d): recalculating\n", size, filecount);
     goto RECALCULATE;
     }
@@ -477,7 +477,7 @@ if (!*endptr)
     struct stat statbuf;
     if (linecount > 1)
       {
-      DEBUG(D_transport) debug_printf("over quota and maildirsize has "
+      DEBUG(transport) debug_printf("over quota and maildirsize has "
         "more than 1 entry: recalculating\n");
       goto RECALCULATE;
       }
@@ -486,7 +486,7 @@ if (!*endptr)
 
     if (time(NULL) - statbuf.st_mtime > 15*60)
       {
-      DEBUG(D_transport) debug_printf("over quota and maildirsize is older "
+      DEBUG(transport) debug_printf("over quota and maildirsize is older "
         "than 15 minutes: recalculating\n");
       goto RECALCULATE;
       }
@@ -502,7 +502,7 @@ else
   uschar *tempname;
   struct timeval tv;
 
-  DEBUG(D_transport)
+  DEBUG(transport)
     {
     const uschar * p = endptr;
     while (p > buffer && p[-1] != '\n') p--;
@@ -527,8 +527,8 @@ else
     FALSE);
 
   (void)gettimeofday(&tv, NULL);
-  tempname = string_sprintf("%s/tmp/" TIME_T_FMT ".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" PID_T_FMT ".%s",
+    path, tv.tv_sec, tv.tv_usec, getpid(), primary_hostname);
 
   fd = Uopen(tempname, O_RDWR|O_CREAT|O_EXCL, ob->mode ? ob->mode : 0600);
   if (fd >= 0)
@@ -545,12 +545,12 @@ else
   /* If any of the directories have been modified since the last timestamp we
   saw, we have to junk this maildirsize file. */
 
-  DEBUG(D_transport) debug_printf("checking subdirectory timestamps\n");
+  DEBUG(transport) debug_printf("checking subdirectory timestamps\n");
   new_latest = 0;
   (void)maildir_compute_size(path, NULL, &new_latest , NULL, dir_regex, TRUE);
   if (new_latest > old_latest)
     {
-    DEBUG(D_transport) debug_printf("abandoning maildirsize because of "
+    DEBUG(transport) debug_printf("abandoning maildirsize because of "
       "a later subdirectory modification\n");
     (void)Uunlink(filename);
     (void)close(fd);
@@ -560,7 +560,7 @@ else
 
 /* Return the sizes and the file descriptor, if any */
 
-DEBUG(D_transport) debug_printf("returning maildir size=" OFF_T_FMT
+DEBUG(transport) debug_printf("returning maildir size=" OFF_T_FMT
   " filecount=%d\n", size, filecount);
 *returned_size = size;
 *returned_filecount = filecount;
diff --git a/src/src/utf8.c b/src/src/utf8.c
index c05853838..df741f5cf 100644
--- a/src/src/utf8.c
+++ b/src/src/utf8.c
@@ -47,37 +47,40 @@ The *err string pointer should be null before the call
 Return NULL for error, with optional errstr pointer filled in
 */
 
-uschar *
+const uschar *
 string_domain_utf8_to_alabel(const uschar * utf8, uschar ** err)
 {
-uschar * s1, * s;
+const uschar * cs;
+uschar * t, * s;
 int rc;
 
 #ifdef SUPPORT_I18N_2008
 /* Avoid lowercasing plain-ascii domains */
 if (!string_is_utf8(utf8))
-  return string_copy(utf8);
+  return 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. */
   {
+  const uschar * cs1;
   uschar c;
-  for (s1 = s = US utf8; (c = *s1); s1++) if (!(c & 0x80) && isupper(c))
+  for (cs1 = cs = utf8; (c = *cs1); cs1++) 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);
+    for (t = s + (cs1 - utf8); (c = *t); t++) if (!(c & 0x80) && isupper(c))
+      *t = tolower(c);
+    cs = s;
     break;
     }
   }
-if ((rc = idn2_lookup_u8((const uint8_t *) s, &s1, IDN2_NFC_INPUT)) != IDN2_OK)
+if ((rc = idn2_lookup_u8((const uint8_t *) cs, &t, 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))
+if (  (rc = idna_to_ascii_8z(CCS s, CSS &t, IDNA_ALLOW_UNASSIGNED))
    != IDNA_SUCCESS)
   {
   free(s);
@@ -86,9 +89,9 @@ if (  (rc = idna_to_ascii_8z(CCS s, CSS &s1, IDNA_ALLOW_UNASSIGNED))
   }
 free(s);
 #endif
-s = string_copy(s1);
-free(s1);
-return s;
+cs = string_copy(t);
+free(t);
+return cs;
 }
 
 
@@ -132,7 +135,7 @@ return s;
 /* the *err string pointer should be null before the call */
 
 
-uschar *
+const uschar *
 string_localpart_utf8_to_alabel(const uschar * utf8, uschar ** err)
 {
 size_t ucs4_len = 0;
@@ -141,7 +144,7 @@ size_t p_len;
 uschar * res;
 int rc;
 
-if (!string_is_utf8(utf8)) return string_copy(utf8);
+if (!string_is_utf8(utf8)) return utf8;
 
 p = (punycode_uint *) stringprep_utf8_to_ucs4(CCS utf8, -1, &ucs4_len);
 if (!p || !ucs4_len)
@@ -156,7 +159,7 @@ 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));
+  DEBUG(expand) debug_printf("l_u2a: bad '%s'\n", punycode_strerror(rc));
   free(p);
   if (err) *err = US punycode_strerror(rc);
   return NULL;
@@ -176,7 +179,7 @@ punycode_uint * p;
 int rc;
 uschar * s, * res;
 
-DEBUG(D_expand) debug_printf("l_a2u: '%s'\n", alabel);
+DEBUG(expand) debug_printf("l_a2u: '%s'\n", alabel);
 alabel += 4;
 p_len = Ustrlen(alabel);
 p = store_get((p_len+1) * sizeof(*p), alabel);
@@ -212,14 +215,14 @@ The *err string pointer should be null before the call.
 Return NULL on error, with (optional) errstring pointer filled in
 */
 
-uschar *
+const uschar *
 string_address_utf8_to_alabel(const uschar * utf8, uschar ** err)
 {
-uschar * l, * d;
+const uschar * l, * d;
 
 if (!*utf8) return string_copy(utf8);
 
-DEBUG(D_expand) debug_printf("addr from utf8 <%s>", utf8);
+DEBUG(expand) debug_printf("addr from utf8 <%s>", utf8);
 
 for (const uschar * s = utf8; *s; s++)
   if (*s == '@')
@@ -230,12 +233,12 @@ for (const uschar * s = utf8; *s; s++)
        )
       return NULL;
     l = string_sprintf("%s@%s", l, d);
-    DEBUG(D_expand) debug_printf(" -> <%s>\n", l);
+    DEBUG(expand) debug_printf(" -> <%s>\n", l);
     return l;
     }
 
 l =  string_localpart_utf8_to_alabel(utf8, err);
-DEBUG(D_expand) debug_printf(" -> <%s>\n", l);
+DEBUG(expand) debug_printf(" -> <%s>\n", l);
 return l;
 }
 
diff --git a/src/src/utils/eximstats.src b/src/src/utils/eximstats.src
index 84b60ceaa..41912c1fb 100644
--- a/src/src/utils/eximstats.src
+++ b/src/src/utils/eximstats.src
@@ -1852,7 +1852,7 @@ EoText
 sub generate_parser {
   my $parser = '
   my($ip,$host,$email,$edomain,$domain,$thissize,$size,$old,$new);
-  my($tod,$m_hour,$m_min,$id,$flag,$extra,$length);
+  my($tod,$m_hour,$m_min,$id,$flag,$offset,$length);
   my($seconds,$queued,$rcpt_time,$local_domain);
   my $rej_id = 0;
   while (<$fh>) {
@@ -1865,72 +1865,68 @@ sub generate_parser {
     $length = length($_);
     next if ($length < 38);
     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
+		(\\d{4}\\-\\d\\d-\\d\\d\\s	# 1: YYYYMMDD HHMMSS	11
+			(\\d\\d)		# 2: HH			 2
+			:			#			 1
+			(\\d\\d)		# 3: MM			 2
+			:\\d\\d			#			 3
+		)					#	=	19
+		(\\.\\d+)?			# 4: subseconds		(var)
+		(\s[-+]\\d{4})?			# 5: tz-offset		 6
+		(\s\\[\\d+\\])?			# 6: pid		(var)
 		/ox;
+    $offset = 19;
 
     $tod = defined($5) ?  $1 . $5 : $1;
     ($m_hour,$m_min) = ($2,$3);
 
+    # watch for subsecond precision
+    if (defined($4)) {
+      $offset += length($4);
+      next if ($length < 19 + $offset);
+    }
+
     # PH - watch for GMT offsets in the timestamp.
     if (defined($5)) {
-      $extra = 6;
+      $offset += 6;
       next if ($length < 44);
-    }
-    else {
-      $extra = 0;
-    }
-
-    # watch for subsecond precision
-    if (defined($4)) {
-      $extra += length($4);
-      next if ($length < 38 + $extra);
+      next if ($length < 19 + $offset);
     }
 
     # PH - watch for PID added after the timestamp.
     if (defined($6)) {
-      $extra += length($6);
-      next if ($length < 38 + $extra);
+      $offset += length($6);
+      next if ($length < 19 + $offset);
     }
 
-    # jgh 2025/09/30 - I really dislike this magic "20" offset...
-    # It would be better to develop it from the substrings captured so far.
+    # skip space after the initial groups
+    $offset++;
 
-    # $id = substr($_, 20 + $extra, 16);	# old ID was 16 chars
-    $id = substr($_, 20 + $extra, 23);		# new ID is 23 chars
+    $id = substr($_, $offset, 23);		# new ID is 23 chars, old 16
     $id =~ s/(\S+).*/$1/;
-    #
-    # jgh 2025/09/30 - very fragile when this word was not an ID;
-    # could be shorter than 16.
-    $extra += length($id) - 16;
 
-    # 37 = 20+16+1     so next field after ID.  Assuming there was really an ID.
-    $flag = substr($_, 37 + $extra, 2);
+    # skip ID and space
+    $offset += length($id) + 1;
+
+    $flag = substr($_, $offset, 2);
 
     # jgh 2025/09/30 -
-    # 2020-05-12 17:31:41.630 [23229] auth_login authenticator failed for (User) [185.143.75.81]:28556 I=[192.168.56.110]:25: 535 Incorrect authentication data (set_id=dropped@kiev.ua)
-    # OTOH the "refused" and "dropped" probably are referring to connectios
-    # rather than messages.
+    # 2020-05-12 17:31:41.630 [23229] auth_login authenticator failed for (User) [185.143.75.81]:28556 I=[192.168.56.110]:25: 535 Incorrect authentication data (set_id=dropped@foo.us)
+    # We now count these as cause for connection-drop, since the client often
+    # does on getting an auth-fail (distributed password-guessing attacks...).
     #
     # The "[-<>=*(]+" will be for ignoring accept & delivery lines.
     # What is the "SA"?  SpamAssissin ?
     if ($flag !~ /^([-<>=*(]+|SA)$/ && / (rejected|refused|dropped|authenticator failed)/) {
       $flag = "Re";
-      $extra -= 3;
+    } else {
+      # skip flag and space
+      $offset += 3;
     }
 
     # Rejects might have no MSGID...
-    # Note that $extra can go negative here.  Really, a $offset would be
-    # much more clear.
     if ($flag eq "Re" && $id !~ /^[-0-9a-zA-Z]+$/) {
-      $extra -= length($id) + 1;
+      $offset -= length($id) + 1;
       $id   = "reject:" . ++$rej_id;
     }
 ';
@@ -1947,11 +1943,13 @@ sub generate_parser {
   }
 
   $parser .= '
-    next unless ($flag =~ /<=|=>|->|==|\\*\\*|Co|SA|Re/);
+    next unless ($flag =~ /<=|\(=|=>|->|==|\\*\\*|Co|SA|Re/);
+
+    # TODO: handling for fakereject [ flag (= ]
 
-    #Strip away the timestamp, ID and flag to speed up later pattern matches.
-    #The flags include Co (Completed), Re (Rejected), and SA (SpamAssassin).
-    $_ = substr($_, 40 + $extra);  # PH
+    # Strip away the timestamp, ID and flag to speed up later pattern matches.
+    # The flags include Co (Completed), Re (Rejected), and SA (SpamAssassin).
+    $_ = substr($_, $offset);  # PH
 
     # Alias @message to the array of information about the message.
     # This minimises the number of calls to hash functions.
diff --git a/src/src/utils/exipick.src b/src/src/utils/exipick.src
index c441936a2..788f7efae 100644
--- a/src/src/utils/exipick.src
+++ b/src/src/utils/exipick.src
@@ -941,8 +941,6 @@ sub _parse_header {
     } elsif ($tag eq '-spam_score_int') {
       $self->{_vars}{spam_score_int} = $arg;
       $self->{_vars}{spam_score}     = $arg / 10;
-    } elsif ($tag eq '-bmi_verdicts') {
-      $self->{_vars}{bmi_verdicts} = $arg;
     } elsif ($tag eq '-host_lookup_deferred') {
       $self->{_vars}{host_lookup_deferred} = 1;
     } elsif ($tag eq '-host_lookup_failed') {
@@ -1569,10 +1567,6 @@ The value of AUTH= param for smtp messages, or a generated value from the callin
 
 Value of the header(s) with the same name with any RFC2047 words decoded if present.  See section 11.5 of Exim's spec.txt for full details.
 
-=item S + B<$bmi_verdicts>
-
-The verdict string provided by a Brightmail content scan
-
 =item N . B<$body_linecount>
 
 The number of lines in the message's body.
diff --git a/src/src/verify.c b/src/src/verify.c
index a12e74ce6..85c3a4932 100644
--- a/src/src/verify.c
+++ b/src/src/verify.c
@@ -16,7 +16,7 @@ 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 */
-static smtp_context ctctx;
+static smtp_context ctctx;		/* Large (12k, 66k if DANE supported */
 uschar ctbuffer[8192];
 
 
@@ -51,7 +51,7 @@ dbdata_callout_cache *cache_record;
 
 if (!(cache_record = dbfn_read_with_length(dbm_file, key, &length)))
   {
-  HDEBUG(D_verify) debug_printf_indent("callout cache: no %s record found for %s\n", type, key);
+  HDEBUG(verify) debug_printf_indent("callout cache: no %s record found for %s\n", type, key);
   return NULL;
   }
 
@@ -63,9 +63,9 @@ negative = cache_record->result != ccache_accept ||
 expire = negative? negative_expire : positive_expire;
 now = time(NULL);
 
-if (now - cache_record->time_stamp > expire)
+if (now - cache_record->gen.time_stamp > expire)
   {
-  HDEBUG(D_verify) debug_printf_indent("callout cache: %s record expired for %s\n", type, key);
+  HDEBUG(verify) debug_printf_indent("callout cache: %s record expired for %s\n", type, key);
   return NULL;
   }
 
@@ -81,7 +81,7 @@ if (type[0] == 'd' && cache_record->result != ccache_reject)
     {
     dbdata_callout_cache * new = store_get(sizeof(dbdata_callout_cache), GET_UNTAINTED);
     memcpy(new, cache_record, length);
-    new->postmaster_stamp = new->random_stamp = new->time_stamp;
+    new->postmaster_stamp = new->random_stamp = new->gen.time_stamp;
     cache_record = new;
     }
 
@@ -92,7 +92,7 @@ if (type[0] == 'd' && cache_record->result != ccache_reject)
     cache_record->random_result = ccache_unknown;
   }
 
-HDEBUG(D_verify) debug_printf_indent("callout cache: found %s record for %s\n", type, key);
+HDEBUG(verify) debug_printf_indent("callout cache: found %s record for %s\n", type, key);
 return cache_record;
 }
 
@@ -119,11 +119,11 @@ stage, unless caching has been disabled. */
 
 if (options & vopt_callout_no_cache)
   {
-  HDEBUG(D_verify) debug_printf_indent("callout cache: disabled by no_cache\n");
+  HDEBUG(verify) debug_printf_indent("callout cache: disabled by no_cache\n");
   }
 else if (!(dbm_file = dbfn_open(US"callout", O_RDWR|O_CREAT, &dbblock, FALSE, TRUE)))
   {
-  HDEBUG(D_verify) debug_printf_indent("callout cache: not available\n");
+  HDEBUG(verify) debug_printf_indent("callout cache: not available\n");
   }
 else
   {
@@ -153,7 +153,7 @@ else
     if (  cache_record->result == ccache_reject
        || *from_address == 0 && cache_record->result == ccache_reject_mfnull)
       {
-      HDEBUG(D_verify)
+      HDEBUG(verify)
 	debug_printf_indent("callout cache: domain gave initial rejection, or "
 	  "does not accept HELO or MAIL FROM:<>\n");
       setflag(addr, af_verify_nsfail);
@@ -174,14 +174,14 @@ else
     if (options & vopt_callout_random) switch(cache_record->random_result)
       {
       case ccache_accept:
-	HDEBUG(D_verify)
+	HDEBUG(verify)
 	  debug_printf_indent("callout cache: domain accepts random addresses\n");
 	*failure_ptr = US"random";
 	dbfn_close(dbm_file);
 	return TRUE;     /* Default yield is OK */
 
       case ccache_reject:
-	HDEBUG(D_verify)
+	HDEBUG(verify)
 	  debug_printf_indent("callout cache: domain rejects random addresses\n");
 	*opt_ptr = options & ~vopt_callout_random;
 	new_domain_record->random_result = ccache_reject;
@@ -189,7 +189,7 @@ else
 	break;
 
       default:
-	HDEBUG(D_verify)
+	HDEBUG(verify)
 	  debug_printf_indent("callout cache: need to check random address handling "
 	    "(not cached or cache expired)\n");
 	dbfn_close(dbm_file);
@@ -206,7 +206,7 @@ else
       if (cache_record->postmaster_result == ccache_reject)
 	{
 	setflag(addr, af_verify_pmfail);
-	HDEBUG(D_verify)
+	HDEBUG(verify)
 	  debug_printf_indent("callout cache: domain does not accept "
 	    "RCPT TO:<postmaster@domain>\n");
 	*yield = FAIL;
@@ -218,7 +218,7 @@ else
 	}
       if (cache_record->postmaster_result == ccache_unknown)
 	{
-	HDEBUG(D_verify)
+	HDEBUG(verify)
 	  debug_printf_indent("callout cache: need to check RCPT "
 	    "TO:<postmaster@domain> (not cached or cache expired)\n");
 	dbfn_close(dbm_file);
@@ -230,7 +230,7 @@ else
       that the value in the cache record is preserved (with its old timestamp).
       */
 
-      HDEBUG(D_verify) debug_printf_indent("callout cache: domain accepts RCPT "
+      HDEBUG(verify) debug_printf_indent("callout cache: domain accepts RCPT "
 	"TO:<postmaster@domain>\n");
       *pm_ptr = NULL;
       new_domain_record->postmaster_result = ccache_accept;
@@ -253,12 +253,12 @@ else
 
   if (cache_address_record->result == ccache_accept)
     {
-    HDEBUG(D_verify)
+    HDEBUG(verify)
       debug_printf_indent("callout cache: address record is positive\n");
     }
   else
     {
-    HDEBUG(D_verify)
+    HDEBUG(verify)
       debug_printf_indent("callout cache: address record is negative\n");
     addr->user_message = US"Previous (cached) callout verification failure";
     *failure_ptr = US"recipient";
@@ -296,13 +296,13 @@ 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, TRUE)))
     {
-    HDEBUG(D_verify) debug_printf_indent("callout cache: not available\n");
+    HDEBUG(verify) debug_printf_indent("callout cache: not available\n");
     }
   else
     {
     (void)dbfn_write(dbm_file, domain, dom_rec,
       (int)sizeof(dbdata_callout_cache));
-    HDEBUG(D_verify) debug_printf_indent("wrote callout cache domain record for %s:\n"
+    HDEBUG(verify) debug_printf_indent("wrote callout cache domain record for %s:\n"
       "  result=%d postmaster=%d random=%d\n",
       domain,
       dom_rec->result,
@@ -319,13 +319,13 @@ if (done  &&  addr_rec->result != ccache_unknown)
     dbm_file = dbfn_open(US"callout", O_RDWR|O_CREAT, &dbblock, FALSE, TRUE);
   if (!dbm_file)
     {
-    HDEBUG(D_verify) debug_printf_indent("no callout cache available\n");
+    HDEBUG(verify) debug_printf_indent("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_indent("wrote %s callout cache address record for %s\n",
+    HDEBUG(verify) debug_printf_indent("wrote %s callout cache address record for %s\n",
       addr_rec->result == ccache_accept ? "positive" : "negative",
       address_key);
     }
@@ -372,7 +372,7 @@ if (addr->transport == cutthrough.addr.transport)
 	      US"callout")
 	 || (port = smtp_get_port(tf->port, addr, US"callout")) < 0
 	 )
-	log_write(0, LOG_MAIN|LOG_PANIC, "<%s>: %s", addr->address,
+	log_write(LOG_MAIN|LOG_PANIC, "<%s>: %s", addr->address,
 	  addr->message);
 
       smtp_port_for_connect(host, port);
@@ -389,7 +389,7 @@ if (addr->transport == cutthrough.addr.transport)
 
 	/* Match!  Send the RCPT TO, set done from the response */
 
-	DEBUG(D_verify)
+	DEBUG(verify)
 	  debug_printf("already-open verify connection matches recipient\n");
 
 	done =
@@ -418,7 +418,7 @@ if (addr->transport == cutthrough.addr.transport)
 	  cancel_cutthrough_connection(TRUE, US"recipient rejected");
 	  if (!resp || errno == ETIMEDOUT)
 	    {
-	    HDEBUG(D_verify) debug_printf("SMTP timeout\n");
+	    HDEBUG(verify) debug_printf("SMTP timeout\n");
 	    }
 	  else if (errno == 0)
 	    {
@@ -538,7 +538,7 @@ if (options & vopt_is_recipient)
       transport_instance * tp = addr->transport;
       from_address = addr->prop.errors_address
 		  ? addr->prop.errors_address : sender_address;
-      DEBUG(D_verify)
+      DEBUG(verify)
 	debug_printf(" return-path from routed addr: %s\n", from_address);
 
       GET_OPTION("return_path");
@@ -549,7 +549,7 @@ if (options & vopt_is_recipient)
 	  from_address = new_return_path;
 	else if (!f.expand_string_forcedfail)
 	  return DEFER;
-	DEBUG(D_verify)
+	DEBUG(verify)
 	  debug_printf(" return-path from transport: %s\n", from_address);
 	}
       }
@@ -587,11 +587,11 @@ if (cached_callout_lookup(addr, address_key, from_address,
 
 if (!addr->transport)
   {
-  HDEBUG(D_verify) debug_printf("cannot callout via null transport\n");
+  HDEBUG(verify) debug_printf("cannot callout via null transport\n");
   }
 
 else if (Ustrcmp(addr->transport->drinst.driver_name, "smtp") != 0)
-  log_write(0, LOG_MAIN|LOG_PANIC|LOG_CONFIG_FOR, "callout transport '%s': %s is non-smtp",
+  log_write(LOG_MAIN|LOG_PANIC|LOG_CONFIG_FOR, "callout transport '%s': %s is non-smtp",
     addr->transport->drinst.name, addr->transport->drinst.driver_name);
 else
   {
@@ -609,7 +609,7 @@ else
     GET_OPTION("callout_random_local_part");
     if (  callout_random_local_part
        && !(random_local_part = expand_string(callout_random_local_part)))
-      log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
+      log_write(LOG_MAIN|LOG_PANIC, "failed to expand "
         "callout_random_local_part: %s", expand_string_message);
     }
 
@@ -665,7 +665,7 @@ coding means skipping this whole loop and doing the append separately.  */
 
     if (!host->address)
       {
-      DEBUG(D_verify) debug_printf("no IP address for host name %s: skipping\n",
+      DEBUG(verify) debug_printf("no IP address for host name %s: skipping\n",
         host->name);
       continue;
       }
@@ -674,7 +674,7 @@ coding means skipping this whole loop and doing the append separately.  */
 
     if (time(NULL) - callout_start_time >= callout_overall)
       {
-      HDEBUG(D_verify) debug_printf("overall timeout for callout exceeded\n");
+      HDEBUG(verify) debug_printf("overall timeout for callout exceeded\n");
       break;
       }
 
@@ -700,10 +700,11 @@ coding means skipping this whole loop and doing the append separately.  */
             US"callout")
        || (port = smtp_get_port(tf->port, addr, US"callout")) < 0
        )
-      log_write(0, LOG_MAIN|LOG_PANIC, "<%s>: %s", addr->address,
+      log_write(LOG_MAIN|LOG_PANIC, "<%s>: %s", addr->address,
         addr->message);
 
-    if (!sx) sx = store_get(sizeof(*sx), GET_TAINTED);	/* tainted buffers */
+    /* Large (12k, 66k if DANE supported.  Tainted, for the receive buffers */
+    if (!sx) sx = store_get(sizeof(*sx), GET_TAINTED);
     memset(sx, 0, sizeof(*sx));
 
     sx->addrlist = sx->first_addr = addr;
@@ -735,7 +736,7 @@ tls_retry_connection:
        && verify_check_given_host(CUSS &ob->hosts_require_tls, host) != OK
        )
       {
-      log_write(0, LOG_MAIN,
+      log_write(LOG_MAIN,
 	"%s: callout unencrypted to %s [%s] (not in hosts_require_tls)",
 	addr->message, host->name, host->address);
       addr->transport_return = PENDING_DEFER;
@@ -765,7 +766,7 @@ tls_retry_connection:
 	case ENETDOWN:
 	case ENETUNREACH:
 	case EINVAL:			/* OpenBSD gives this for netunreach */
-	  log_write(0, LOG_MAIN|LOG_PANIC,
+	  log_write(LOG_MAIN|LOG_PANIC,
 	    "%s verify %s (making callout connection): T=%s %s",
 	    options & vopt_is_recipient ? "sender" : "recipient",
 	    yield == FAIL ? "fail" : "defer",
@@ -887,13 +888,13 @@ tls_retry_connection:
 				'2', callout)))
 	      break;
 
-	    HDEBUG(D_acl|D_v)
+	    HDEBUG(acl|v)
 	      debug_printf_indent("problem after random/rset/mfrom; reopen conn\n");
 	    random_local_part = NULL;
 #ifndef DISABLE_TLS
 	    tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT);
 #endif
-	    HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP(close)>>\n");
+	    HDEBUG(transport|acl|v) debug_printf_indent("  SMTP(close)>>\n");
 	    (void)close(sx->cctx.sock);
 	    sx->cctx.sock = -1;
 #ifndef DISABLE_EVENT
@@ -978,7 +979,7 @@ tls_retry_connection:
       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");
+      HDEBUG(acl|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);
@@ -1039,7 +1040,7 @@ no_conn:
     switch(errno)
       {
       case ETIMEDOUT:
-	HDEBUG(D_verify) debug_printf("SMTP timeout\n");
+	HDEBUG(verify) debug_printf("SMTP timeout\n");
 	sx->send_quit = FALSE;
 	break;
 
@@ -1119,7 +1120,7 @@ no_conn:
       if (expand_string_nonempty(addr->transport->filter_command))
         {
         cutthrough.delivery= FALSE;
-        HDEBUG(D_acl|D_v) debug_printf("Cutthrough cancelled by presence of transport filter\n");
+        HDEBUG(acl|v) debug_printf("Cutthrough cancelled by presence of transport filter\n");
         }
 #ifndef DISABLE_DKIM
       /* DKIM signing needs to add a header after seeing the whole body, so we
@@ -1128,7 +1129,7 @@ no_conn:
       if (expand_string_nonempty(ob->dkim.dkim_domain))
         {
         cutthrough.delivery= FALSE;
-        HDEBUG(D_acl|D_v) debug_printf("Cutthrough cancelled by presence of DKIM signing\n");
+        HDEBUG(acl|v) debug_printf("Cutthrough cancelled by presence of DKIM signing\n");
         }
 #endif
 #ifdef EXPERIMENTAL_ARC
@@ -1136,7 +1137,7 @@ no_conn:
       if (expand_string_nonempty(ob->arc_sign))
         {
         cutthrough.delivery= FALSE;
-        HDEBUG(D_acl|D_v) debug_printf("Cutthrough cancelled by presence of ARC signing\n");
+        HDEBUG(acl|v) debug_printf("Cutthrough cancelled by presence of ARC signing\n");
         }
 #endif
       }
@@ -1152,12 +1153,19 @@ no_conn:
        && !sx->lmtp
        )
       {
-      HDEBUG(D_acl|D_v) debug_printf_indent("holding verify callout open for %s\n",
+      HDEBUG(acl|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;
+      if ((cutthrough.is_tls =	tls_out.active.sock >= 0))
+	{
+#ifdef SUPPORT_DANE
+	cutthrough.is_dane =	tls_out.sni && tls_out.dane_verified;
+#endif
+	cutthrough.sni =	tls_out.sni;
+	cutthrough.cipher =	tls_out.cipher;
+	}
       /* We assume no buffer in use in the outblock */
       cutthrough.cctx =		sx->cctx;
       cutthrough.nrcpt =	1;
@@ -1209,7 +1217,7 @@ no_conn:
 	  sx->cctx.tls_ctx = NULL;
 	  }
 #endif
-	HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP(close)>>\n");
+	HDEBUG(transport|acl|v) debug_printf_indent("  SMTP(close)>>\n");
 	(void)close(sx->cctx.sock);
 	sx->cctx.sock = -1;
 	smtp_debug_cmd_report();
@@ -1288,7 +1296,7 @@ int vopt, rc;
 get rewritten. */
 
 addr2 = *addr;
-HDEBUG(D_acl) debug_printf_indent("----------- %s cutthrough setup ------------\n",
+HDEBUG(acl) debug_printf_indent("----------- %s cutthrough setup ------------\n",
   rcpt_count > 1 ? "more" : "start");
 
 vopt = transport_sender
@@ -1299,7 +1307,7 @@ rc = verify_address(&addr2, -1, vopt, CUTTHROUGH_CMD_TIMEOUT, -1, -1,
 	NULL, NULL, NULL);
 addr->message = addr2.message;
 addr->user_message = addr2.user_message;
-HDEBUG(D_acl) debug_printf_indent("----------- end cutthrough setup ------------\n");
+HDEBUG(acl) debug_printf_indent("----------- end cutthrough setup ------------\n");
 return rc;
 }
 
@@ -1326,7 +1334,7 @@ if(
   return TRUE;
 }
 
-HDEBUG(D_transport|D_acl) debug_printf_indent("cutthrough_send failed: %s\n", strerror(errno));
+HDEBUG(transport|acl) debug_printf_indent("cutthrough_send failed: %s\n", strerror(errno));
 return FALSE;
 }
 
@@ -1410,7 +1418,7 @@ Used for
 static uschar
 cutthrough_response(client_conn_ctx * cctx, char expect, uschar ** copy, int timeout)
 {
-smtp_context sx = {0};
+smtp_context sx = {0};		/* Large (12k, 66k if DANE supported) */
 uschar inbuffer[4096];
 uschar responsebuffer[4096];
 
@@ -1481,7 +1489,7 @@ if(cutthrough.cctx.sock < 0 || cutthrough.callout_hold_only)
 /* 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_indent("----------- start cutthrough headers send -----------\n");
+HDEBUG(acl) debug_printf_indent("----------- start cutthrough headers send -----------\n");
 
 tctx.u.fd = cutthrough.cctx.sock;
 tctx.tblock = cutthrough.addr.transport;
@@ -1494,7 +1502,7 @@ tctx.options = topt_use_crlf;
 if (!transport_headers_send(&tctx, &cutthrough_write_chunk))
   return FALSE;
 
-HDEBUG(D_acl) debug_printf_indent("----------- done cutthrough headers send ------------\n");
+HDEBUG(acl) debug_printf_indent("----------- done cutthrough headers send ------------\n");
 return TRUE;
 }
 
@@ -1528,10 +1536,10 @@ if(fd >= 0)
     cutthrough.is_tls = FALSE;
     }
 #endif
-  HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP(close)>>\n");
+  HDEBUG(transport|acl|v) debug_printf_indent("  SMTP(close)>>\n");
   (void)close(fd);
   smtp_debug_cmd_report();
-  HDEBUG(D_acl) debug_printf_indent("----------- cutthrough shutdown (%s) ------------\n", why);
+  HDEBUG(acl) debug_printf_indent("----------- cutthrough shutdown (%s) ------------\n", why);
   }
 ctctx.outblock.ptr = ctbuffer;
 }
@@ -1549,7 +1557,7 @@ void
 release_cutthrough_connection(const uschar * why)
 {
 if (cutthrough.cctx.sock < 0) return;
-HDEBUG(D_acl) debug_printf_indent("release cutthrough conn: %s\n", why);
+HDEBUG(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;
@@ -1567,7 +1575,7 @@ uschar *
 cutthrough_finaldot(void)
 {
 uschar res;
-HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP>> .\n");
+HDEBUG(transport|acl|v) debug_printf_indent("  SMTP>> .\n");
 
 /* Assume data finshed with new-line */
 if(  !cutthrough_puts(US".", 1)
@@ -1737,7 +1745,7 @@ verify_address(address_item * vaddr, int fd, int options, int callout,
   uschar * pm_mailfrom, BOOL * routed)
 {
 BOOL allok = TRUE;
-BOOL full_info = fd >= 0 ? debug_selector != 0 : FALSE;
+BOOL full_info = fd >= 0 ? ANY_DEBUG : FALSE;
 BOOL expn         = (options & vopt_expn) != 0;
 BOOL success_on_redirect = (options & vopt_success_on_redirect) != 0;
 int i;
@@ -1784,7 +1792,7 @@ if (parse_find_at(address) == NULL)
   address = US rewrite_address_qualify(address, options & vopt_is_recipient);
   }
 
-DEBUG(D_verify)
+DEBUG(verify)
   {
   debug_printf(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
   debug_printf("%s %s\n", f.address_test_mode? "Testing" : "Verifying", address);
@@ -1856,7 +1864,7 @@ while (addr_new)
   addr_new = addr->next;
   addr->next = NULL;
 
-  DEBUG(D_verify)
+  DEBUG(verify)
     {
     debug_printf(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
     debug_printf("Considering %s\n", addr->address);
@@ -1960,7 +1968,7 @@ while (addr_new)
 	if (!ti->local)
 	  {
 	  if ((tp->setup)(tp, addr, &tf, 0, 0, NULL) != OK)
-	    log_write(0, LOG_MAIN|LOG_PANIC,
+	    log_write(LOG_MAIN|LOG_PANIC,
 	      "setup fail for %s transport for callout (%s)",
 	      tp->drinst.name, expand_string_message);
 
@@ -1975,7 +1983,7 @@ while (addr_new)
 	    host_list = NULL;    /* Ignore the router's hosts */
 
 	    if (!(s = expand_string(tf.hosts)))
-	      log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand list of hosts "
+	      log_write(LOG_MAIN|LOG_PANIC, "failed to expand list of hosts "
 		"%q in %s transport for callout: %s", tf.hosts,
 		tp->drinst.name, expand_string_message);
 	    else
@@ -2028,11 +2036,11 @@ while (addr_new)
 
       if (host_list)
         {
-        HDEBUG(D_verify)
+        HDEBUG(verify)
 	  debug_printf("Attempting full verification using callout\n");
         if (host_checking && !f.host_checking_callout)
           {
-          HDEBUG(D_verify)
+          HDEBUG(verify)
             debug_printf("... callout omitted by default when host testing\n"
               "(Use -bhc if you want the callouts to happen.)\n");
           }
@@ -2053,14 +2061,14 @@ while (addr_new)
         }
       else if (local_verify)
 	{
-        HDEBUG(D_verify) debug_printf("Attempting quota verification\n");
+        HDEBUG(verify) debug_printf("Attempting quota verification\n");
 
 	deliver_set_expansions(addr);
 	deliver_local(addr, TRUE);
 	rc = addr->transport_return;
 	}
       else
-        HDEBUG(D_verify) debug_printf("Cannot do callout: neither router nor "
+        HDEBUG(verify) debug_printf("Cannot do callout: neither router nor "
           "transport provided a host list, or transport is not smtp\n");
       }
     }
@@ -2338,9 +2346,9 @@ Returns:     OK
 */
 
 int
-verify_check_headers(uschar **msgptr)
+verify_check_headers(uschar ** msgptr)
 {
-uschar *colon, *s;
+uschar * colon, * s;
 int yield = OK;
 
 for (header_line * h = header_list; h && yield == OK; h = h->next)
@@ -2639,8 +2647,7 @@ int yield = FAIL;
 for (int i = 0; i < 3 && !done; i++)
   for (const header_line * h = header_list; h && !done; h = h->next)
     {
-    const uschar * endname, * s;
-    uschar * ss;
+    const uschar * endname, * s, * ss, * es;
 
     if (h->type != header_types[i]) continue;
     s = endname = Ustrchr(h->text, ':') + 1;
@@ -2652,38 +2659,36 @@ for (int i = 0; i < 3 && !done; i++)
 
     while (*s)
       {
-      int terminator, new_ok;
+      int new_ok;
       address_item * vaddr;
 
       while (isspace(*s) || *s == ',') s++;
       if (!*s) break;			/* End of header */
 
-      ss = parse_find_address_end(s, FALSE);
+      es = parse_find_address_end(s, FALSE);
 
       /* The terminator is a comma or end of header, but there may be white
       space preceding it (including newline for the last address). Move back
       past any white space so we can check against any cached envelope sender
       address verifications. */
 
-      while (isspace(ss[-1])) ss--;
-      terminator = *ss;
-      *ss = '\0';
+      while (isspace(es[-1])) es--;
+      ss = *es ? string_copyn(s, es - s) : s;
 
-      HDEBUG(D_verify) debug_printf("verifying %.*s header address %s\n",
-        (int)(endname - h->text), h->text, s);
+      HDEBUG(verify) debug_printf("verifying %.*s header address %s\n",
+        (int)(endname - h->text), h->text, ss);
 
       /* See if we have already verified this address as an envelope sender,
       and if so, use the previous answer. */
 
-      vaddr = verify_checked_sender(s);
+      vaddr = verify_checked_sender(ss);
 
-      if (vaddr != NULL &&                   /* Previously checked */
-           (callout <= 0 ||                  /* No callout needed; OR */
-            vaddr->special_action > 256))    /* Callout was done */
+      if (   vaddr				/* Previously checked */
+          && (  callout <= 0			/* No callout needed; OR */
+	     || vaddr->special_action > 256))	/* Callout was done */
         {
         new_ok = vaddr->special_action & 255;
-        HDEBUG(D_verify) debug_printf("previously checked as envelope sender\n");
-        *ss = terminator;  /* Restore shortened string */
+        HDEBUG(verify) debug_printf("previously checked as envelope sender\n");
         }
 
       /* Otherwise we run the verification now. We must restore the shortened
@@ -2693,9 +2698,8 @@ for (int i = 0; i < 3 && !done; i++)
       else
         {
         int start, end, domain;
-        const uschar * address = parse_extract_address(s, log_msgptr,
+        const uschar * address = parse_extract_address(ss, log_msgptr,
 						  &start, &end, &domain, FALSE);
-        *ss = terminator;
 
         /* If we found an empty address, just carry on with the next one, but
         kill the message. */
@@ -2703,7 +2707,7 @@ for (int i = 0; i < 3 && !done; i++)
         if (!address && Ustrcmp(*log_msgptr, "empty address") == 0)
           {
           *log_msgptr = NULL;
-          s = ss;
+          s = es;
           continue;
           }
 
@@ -2713,10 +2717,10 @@ for (int i = 0; i < 3 && !done; i++)
 
         if (!address)
           {
-          while (ss > s && isspace(ss[-1])) ss--;
+          while (es > s && isspace(es[-1])) es--;
           *log_msgptr = string_sprintf("syntax error in '%.*s' header when "
             "scanning for sender: %s in \"%.*s\"",
-            (int)(endname - h->text), h->text, *log_msgptr, (int)(ss - s), s);
+            (int)(endname - h->text), h->text, *log_msgptr, (int)(es - s), s);
           yield = FAIL;
           done = TRUE;
           break;
@@ -2759,7 +2763,7 @@ for (int i = 0; i < 3 && !done; i++)
 
       /* Move on to any more addresses in the header */
 
-      s = ss;
+      s = es;
       }     /* Next address */
 
     f.parse_allow_group = FALSE;
@@ -2815,7 +2819,7 @@ sender_ident = NULL;
 if (rfc1413_query_timeout <= 0 || verify_check_host(&rfc1413_hosts) != OK)
   return;
 
-DEBUG(D_ident) debug_printf("doing ident callback\n");
+DEBUG(ident) debug_printf("doing ident callback\n");
 
 /* Set up a connection to the ident port of the remote host. Bind the local end
 to the incoming interface address. If the sender host address is an IPv6
@@ -2826,7 +2830,7 @@ if ((ident_conn_ctx.sock = ip_socket(SOCK_STREAM, host_af)) < 0) return;
 
 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",
+  DEBUG(ident) debug_printf("bind socket for ident failed: %s\n",
     strerror(errno));
   goto END_OFF;
   }
@@ -2843,10 +2847,10 @@ if (ip_connect(ident_conn_ctx.sock, host_af, sender_host_address, port,
 		rfc1413_query_timeout, &early_data) < 0)
   {
   if (errno == ETIMEDOUT && LOGGING(ident_timeout))
-    log_write(0, LOG_MAIN, "ident connection to %s timed out",
+    log_write(LOG_MAIN, "ident connection to %s timed out",
       sender_host_address);
   else
-    DEBUG(D_ident) debug_printf("ident connection to %s failed: %s\n",
+    DEBUG(ident) debug_printf("ident connection to %s failed: %s\n",
       sender_host_address, strerror(errno));
   goto END_OFF;
   }
@@ -2925,7 +2929,7 @@ or Received: lines into which it gets inserted. We keep a maximum of 127
 characters. The deconst cast is ok as we fed a nonconst to string_printing() */
 
 sender_ident = US string_printing(string_copyn(p, 127));
-DEBUG(D_ident) debug_printf("sender_ident = %s\n", sender_ident);
+DEBUG(ident) debug_printf("sender_ident = %s\n", sender_ident);
 
 END_OFF:
 (void)close(ident_conn_ctx.sock);
@@ -3098,7 +3102,7 @@ if (iplookup)
   /* Find the search type */
 
   if (!(li = search_findtype(t, endname - t)))
-    log_write_die(0, LOG_MAIN, "%s", search_error_message);
+    log_write_die(LOG_MAIN, "%s", search_error_message);
 
   /* Adjust parameters for the type of lookup. For a query-style lookup, there
   is no file name, and the "key" is just the query. For query-style with a file
@@ -3141,7 +3145,7 @@ if (iplookup)
   of the caching arrangements. */
 
   if (!(handle = search_open(filename, li, 0, NULL, NULL)))
-    log_write_die(0, LOG_MAIN, "%s", search_error_message);
+    log_write_die(LOG_MAIN, "%s", search_error_message);
 
   result = search_find(handle, filename, key, -1, NULL, 0, 0, NULL, opts);
   if (valueptr) *valueptr = result;
@@ -3224,7 +3228,7 @@ if ((semicolon = Ustrchr(ss, ';')))
 
   if (!li)				/* Unknown lookup type */
     {
-    log_write(0, LOG_MAIN|LOG_PANIC, "%s in host list item %q",
+    log_write(LOG_MAIN|LOG_PANIC, "%s in host list item %q",
       search_error_message, ss);
     return DEFER;
     }
@@ -3248,7 +3252,7 @@ do a check on the name and all its aliases. */
 
 if (!sender_host_name)
   {
-  HDEBUG(D_host_lookup)
+  HDEBUG(host_lookup)
     debug_printf_indent("sender host name required, to match against %s\n", ss);
   expand_level++;
   if (host_lookup_failed || host_name_lookup() != OK)
@@ -3322,8 +3326,9 @@ determined from the IP address, the result is FAIL unless the item
 "+allow_unknown" was met earlier in the list, in which case OK is returned. */
 
 int
-verify_check_this_host(const uschar **listptr, unsigned int *cache_bits,
-  const uschar *host_name, const uschar *host_address, const uschar **valueptr)
+verify_check_this_host(const uschar * const * 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;
@@ -3390,7 +3395,7 @@ Returns:               the yield of verify_check_this_host(),
 */
 
 int
-verify_check_host(uschar **listptr)
+verify_check_host(const uschar * const * listptr)
 {
 return verify_check_this_host(CUSS listptr, sender_host_cache, NULL,
   sender_host_address ? sender_host_address : US"", NULL);
@@ -3413,18 +3418,17 @@ Arguments:
 */
 
 void
-invert_address(uschar *buffer, uschar *address)
+invert_address(uschar * buffer, const uschar * address)
 {
 int bin[4];
-uschar *bptr = buffer;
+uschar * bptr = buffer;
 
 /* If this is an IPv4 address mapped into IPv6 format, adjust the pointer
 to the IPv4 part only. */
 
 if (Ustrncmp(address, "::ffff:", 7) == 0) address += 7;
 
-/* Handle IPv4 address: when HAVE_IPV6 is false, the result of host_aton() is
-always 1. */
+/* Handle IPv4 address */
 
 if (host_aton(address, bin) == 1)
   {
@@ -3453,13 +3457,15 @@ else
       x >>= 4;
       }
     }
+#else
+bptr = buffer + 1;
 #endif
 
 /* Remove trailing period -- this is needed so that both arbitrary
 dnsbl keydomains and inverted addresses may be combined with the
 same format string, "%s.%s" */
 
-*(--bptr) = 0;
+*(--bptr) = '\0';
 }
 
 
@@ -3513,7 +3519,7 @@ if ((rc = verify_address(&vaddr, -1, vopt_is_recipient | vopt_quota,
     where, '\0', msg);
   }
 
-DEBUG(D_verify) debug_printf_indent("verify_quota: len %d\n", len);
+DEBUG(verify) debug_printf_indent("verify_quota: len %d\n", len);
 if (write(1, msg, len) != 0) ;
 return;
 }
@@ -3537,7 +3543,7 @@ if (!pos_cache && !neg_cache)
   return FALSE;
 if (!(dbm_file = dbfn_open(US"callout", O_RDWR|O_CREAT, &dbblock, FALSE, TRUE)))
   {
-  HDEBUG(D_verify) debug_printf_indent("quota cache: not available\n");
+  HDEBUG(verify) debug_printf_indent("quota cache: not available\n");
   return FALSE;
   }
 if (!(cache_address_record = (dbdata_callout_cache_address *)
@@ -3565,7 +3571,7 @@ if (!pos_cache && !neg_cache)
   return;
 if (!(dbm_file = dbfn_open(US"callout", O_RDWR|O_CREAT, &dbblock, FALSE, TRUE)))
   {
-  HDEBUG(D_verify) debug_printf_indent("quota cache: not available\n");
+  HDEBUG(verify) debug_printf_indent("quota cache: not available\n");
   return;
   }
 
@@ -3573,7 +3579,7 @@ cache_address_record.result = yield == OK ? ccache_accept : ccache_reject;
 
 (void)dbfn_write(dbm_file, rcpt, &cache_address_record,
 	(int)sizeof(dbdata_callout_cache_address));
-HDEBUG(D_verify) debug_printf_indent("wrote %s quota cache record for %s\n",
+HDEBUG(verify) debug_printf_indent("wrote %s quota cache record for %s\n",
       yield == OK ? "positive" : "negative", rcpt);
 
 dbfn_close(dbm_file);
@@ -3599,7 +3605,8 @@ int
 verify_quota_call(const uschar * rcpt, int pos_cache, int neg_cache,
   uschar ** msg)
 {
-int pfd[2], pid, save_errno, yield = FAIL;
+int pfd[2], save_errno, yield = FAIL;
+pid_t pid;
 void (*oldsignal)(int);
 const uschar * where = US"socketpair";
 
@@ -3607,7 +3614,7 @@ const uschar * where = US"socketpair";
 
 if (cached_quota_lookup(rcpt, &yield, pos_cache, neg_cache))
   {
-  HDEBUG(D_verify) debug_printf_indent("quota cache: address record is %s\n",
+  HDEBUG(verify) debug_printf_indent("quota cache: address record is %s\n",
     yield == OK ? "positive" : "negative");
   if (yield != OK)
     {
@@ -3650,7 +3657,7 @@ close(pfd[pipe_write]);
 
 if (pid < 0)
   {
-  DEBUG(D_verify) debug_printf_indent(" fork: %s\n", strerror(save_errno));
+  DEBUG(verify) debug_printf_indent(" fork: %s\n", strerror(save_errno));
   }
 else
   {
@@ -3673,19 +3680,19 @@ else
 	m > 0 ? string_copyn_taint(s, m, GET_UNTAINTED) : NULL;
       }
 
-    DEBUG(D_verify) debug_printf_indent("verify call response:"
+    DEBUG(verify) debug_printf_indent("verify call response:"
       " len %d yield %s errno '%s' where '%s' msg '%s'\n",
       n, rc_names[yield], strerror(save_errno), recipient_verify_failure, *msg);
 
     if (  yield == OK
        || save_errno == 0 && Ustrcmp(recipient_verify_failure, "quota") == 0)
       cache_quota_write(rcpt, yield, pos_cache, neg_cache);
-    else DEBUG(D_verify)
+    else DEBUG(verify)
       debug_printf_indent("result not cacheable\n");
     }
   else
     {
-    DEBUG(D_verify)
+    DEBUG(verify)
       debug_printf_indent("verify call response: waitpid status 0x%04x\n", status);
     }
   }
@@ -3696,7 +3703,7 @@ errno = save_errno;
 return yield;
 
 fail:
-DEBUG(D_verify) debug_printf_indent("verify_quota_call fail in %s\n", where);
+DEBUG(verify) debug_printf_indent("verify_quota_call fail in %s\n", where);
 return yield;
 }
 
diff --git a/src/src/xtextencode.c b/src/src/xtextencode.c
index 09d5cd7f8..02f762145 100644
--- a/src/src/xtextencode.c
+++ b/src/src/xtextencode.c
@@ -92,6 +92,7 @@ xtextdecode(const uschar * code, uschar ** ptr)
 int x;
 #ifdef COMPILE_UTILITY
 uschar * result = malloc(Ustrlen(code) + 1);
+if (!result) return -1;
 #else
 uschar * result = store_get(Ustrlen(code) + 1, code);
 #endif
