Fix smtp transport response to close after all rcpt fates determined. Bug 3059
[exim.git] / src / src / transports / smtp.c
index 7428134d403cba9d9947c41805b6c07329f39aac..c205145a70f50da8383c08e441425648142198a5 100644 (file)
@@ -2,8 +2,10 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
+/* Copyright (c) The Exim Maintainers 2020 - 2023 */
 /* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 /* 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 */
 
 #include "../exim.h"
 #include "smtp.h"
 
 #include "../exim.h"
 #include "smtp.h"
@@ -18,195 +20,132 @@ over TCP/IP. The options must be in alphabetic order (note that "_" comes
 before the lower case letters). Some live in the transport_instance block so as
 to be publicly visible; these are flagged with opt_public. */
 
 before the lower case letters). Some live in the transport_instance block so as
 to be publicly visible; these are flagged with opt_public. */
 
+#define LOFF(field) OPT_OFF(smtp_transport_options_block, field)
+
 optionlist smtp_transport_options[] = {
   { "*expand_multi_domain",             opt_stringptr | opt_hidden | opt_public,
 optionlist smtp_transport_options[] = {
   { "*expand_multi_domain",             opt_stringptr | opt_hidden | opt_public,
-      (void *)offsetof(transport_instance, expand_multi_domain) },
+      OPT_OFF(transport_instance, expand_multi_domain) },
   { "*expand_retry_include_ip_address", opt_stringptr | opt_hidden,
   { "*expand_retry_include_ip_address", opt_stringptr | opt_hidden,
-       (void *)(offsetof(smtp_transport_options_block, expand_retry_include_ip_address)) },
+      LOFF(expand_retry_include_ip_address) },
 
   { "address_retry_include_sender", opt_bool,
 
   { "address_retry_include_sender", opt_bool,
-      (void *)offsetof(smtp_transport_options_block, address_retry_include_sender) },
-  { "allow_localhost",      opt_bool,
-      (void *)offsetof(smtp_transport_options_block, allow_localhost) },
+      LOFF(address_retry_include_sender) },
+  { "allow_localhost",      opt_bool,     LOFF(allow_localhost) },
 #ifdef EXPERIMENTAL_ARC
 #ifdef EXPERIMENTAL_ARC
-  { "arc_sign", opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, arc_sign) },
-#endif
-  { "authenticated_sender", opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, authenticated_sender) },
-  { "authenticated_sender_force", opt_bool,
-      (void *)offsetof(smtp_transport_options_block, authenticated_sender_force) },
-  { "command_timeout",      opt_time,
-      (void *)offsetof(smtp_transport_options_block, command_timeout) },
-  { "connect_timeout",      opt_time,
-      (void *)offsetof(smtp_transport_options_block, connect_timeout) },
+  { "arc_sign", opt_stringptr,            LOFF(arc_sign) },
+#endif
+  { "authenticated_sender", opt_stringptr, LOFF(authenticated_sender) },
+  { "authenticated_sender_force", opt_bool, LOFF(authenticated_sender_force) },
+  { "command_timeout",      opt_time,     LOFF(command_timeout) },
+  { "connect_timeout",      opt_time,     LOFF(connect_timeout) },
   { "connection_max_messages", opt_int | opt_public,
   { "connection_max_messages", opt_int | opt_public,
-      (void *)offsetof(transport_instance, connection_max_messages) },
+      OPT_OFF(transport_instance, connection_max_messages) },
 # ifdef SUPPORT_DANE
 # ifdef SUPPORT_DANE
-  { "dane_require_tls_ciphers", opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, dane_require_tls_ciphers) },
+  { "dane_require_tls_ciphers", opt_stringptr, LOFF(dane_require_tls_ciphers) },
 # endif
 # endif
-  { "data_timeout",         opt_time,
-      (void *)offsetof(smtp_transport_options_block, data_timeout) },
-  { "delay_after_cutoff", opt_bool,
-      (void *)offsetof(smtp_transport_options_block, delay_after_cutoff) },
+  { "data_timeout",         opt_time,     LOFF(data_timeout) },
+  { "delay_after_cutoff",   opt_bool,     LOFF(delay_after_cutoff) },
 #ifndef DISABLE_DKIM
 #ifndef DISABLE_DKIM
-  { "dkim_canon", opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, dkim.dkim_canon) },
-  { "dkim_domain", opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, dkim.dkim_domain) },
-  { "dkim_hash", opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, dkim.dkim_hash) },
-  { "dkim_identity", opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, dkim.dkim_identity) },
-  { "dkim_private_key", opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, dkim.dkim_private_key) },
-  { "dkim_selector", opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, dkim.dkim_selector) },
-  { "dkim_sign_headers", opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, dkim.dkim_sign_headers) },
-  { "dkim_strict", opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, dkim.dkim_strict) },
-  { "dkim_timestamps", opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, dkim.dkim_timestamps) },
-#endif
-  { "dns_qualify_single",   opt_bool,
-      (void *)offsetof(smtp_transport_options_block, dns_qualify_single) },
-  { "dns_search_parents",   opt_bool,
-      (void *)offsetof(smtp_transport_options_block, dns_search_parents) },
-  { "dnssec_request_domains", opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, dnssec.request) },
-  { "dnssec_require_domains", opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, dnssec.require) },
-  { "dscp",                 opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, dscp) },
-  { "fallback_hosts",       opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, fallback_hosts) },
-  { "final_timeout",        opt_time,
-      (void *)offsetof(smtp_transport_options_block, final_timeout) },
-  { "gethostbyname",        opt_bool,
-      (void *)offsetof(smtp_transport_options_block, gethostbyname) },
-  { "helo_data",            opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, helo_data) },
-  { "hosts",                opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, hosts) },
-  { "hosts_avoid_esmtp",    opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, hosts_avoid_esmtp) },
-  { "hosts_avoid_pipelining", opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, hosts_avoid_pipelining) },
+  { "dkim_canon", opt_stringptr,          LOFF(dkim.dkim_canon) },
+  { "dkim_domain", opt_stringptr,         LOFF(dkim.dkim_domain) },
+  { "dkim_hash", opt_stringptr,                   LOFF(dkim.dkim_hash) },
+  { "dkim_identity", opt_stringptr,       LOFF(dkim.dkim_identity) },
+  { "dkim_private_key", opt_stringptr,    LOFF(dkim.dkim_private_key) },
+  { "dkim_selector", opt_stringptr,       LOFF(dkim.dkim_selector) },
+  { "dkim_sign_headers", opt_stringptr,           LOFF(dkim.dkim_sign_headers) },
+  { "dkim_strict", opt_stringptr,         LOFF(dkim.dkim_strict) },
+  { "dkim_timestamps", opt_stringptr,     LOFF(dkim.dkim_timestamps) },
+#endif
+  { "dns_qualify_single",   opt_bool,     LOFF(dns_qualify_single) },
+  { "dns_search_parents",   opt_bool,     LOFF(dns_search_parents) },
+  { "dnssec_request_domains", opt_stringptr, LOFF(dnssec.request) },
+  { "dnssec_require_domains", opt_stringptr, LOFF(dnssec.require) },
+  { "dscp",                 opt_stringptr, LOFF(dscp) },
+  { "fallback_hosts",       opt_stringptr, LOFF(fallback_hosts) },
+  { "final_timeout",        opt_time,     LOFF(final_timeout) },
+  { "gethostbyname",        opt_bool,     LOFF(gethostbyname) },
+  { "helo_data",            opt_stringptr, LOFF(helo_data) },
+#if !defined(DISABLE_TLS) && !defined(DISABLE_TLS_RESUME)
+  { "host_name_extract",    opt_stringptr, LOFF(host_name_extract) },
+# endif
+  { "hosts",                opt_stringptr, LOFF(hosts) },
+  { "hosts_avoid_esmtp",    opt_stringptr, LOFF(hosts_avoid_esmtp) },
+  { "hosts_avoid_pipelining", opt_stringptr, LOFF(hosts_avoid_pipelining) },
 #ifndef DISABLE_TLS
 #ifndef DISABLE_TLS
-  { "hosts_avoid_tls",      opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, hosts_avoid_tls) },
+  { "hosts_avoid_tls",      opt_stringptr, LOFF(hosts_avoid_tls) },
 #endif
 #endif
-  { "hosts_max_try",        opt_int,
-      (void *)offsetof(smtp_transport_options_block, hosts_max_try) },
-  { "hosts_max_try_hardlimit", opt_int,
-      (void *)offsetof(smtp_transport_options_block, hosts_max_try_hardlimit) },
+  { "hosts_max_try",        opt_int,      LOFF(hosts_max_try) },
+  { "hosts_max_try_hardlimit", opt_int,           LOFF(hosts_max_try_hardlimit) },
 #ifndef DISABLE_TLS
 #ifndef DISABLE_TLS
-  { "hosts_nopass_tls",     opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, hosts_nopass_tls) },
-  { "hosts_noproxy_tls",    opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, hosts_noproxy_tls) },
+  { "hosts_nopass_tls",     opt_stringptr, LOFF(hosts_nopass_tls) },
+  { "hosts_noproxy_tls",    opt_stringptr, LOFF(hosts_noproxy_tls) },
 #endif
 #endif
-  { "hosts_override",       opt_bool,
-      (void *)offsetof(smtp_transport_options_block, hosts_override) },
+  { "hosts_override",       opt_bool,     LOFF(hosts_override) },
 #ifndef DISABLE_PIPE_CONNECT
 #ifndef DISABLE_PIPE_CONNECT
-  { "hosts_pipe_connect",   opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, hosts_pipe_connect) },
+  { "hosts_pipe_connect",   opt_stringptr, LOFF(hosts_pipe_connect) },
 #endif
 #endif
-  { "hosts_randomize",      opt_bool,
-      (void *)offsetof(smtp_transport_options_block, hosts_randomize) },
+  { "hosts_randomize",      opt_bool,     LOFF(hosts_randomize) },
 #if !defined(DISABLE_TLS) && !defined(DISABLE_OCSP)
 #if !defined(DISABLE_TLS) && !defined(DISABLE_OCSP)
-  { "hosts_request_ocsp",   opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, hosts_request_ocsp) },
+  { "hosts_request_ocsp",   opt_stringptr, LOFF(hosts_request_ocsp) },
 #endif
 #endif
-  { "hosts_require_auth",   opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, hosts_require_auth) },
+  { "hosts_require_alpn",   opt_stringptr, LOFF(hosts_require_alpn) },
+  { "hosts_require_auth",   opt_stringptr, LOFF(hosts_require_auth) },
 #ifndef DISABLE_TLS
 # ifdef SUPPORT_DANE
 #ifndef DISABLE_TLS
 # ifdef SUPPORT_DANE
-  { "hosts_require_dane",   opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, hosts_require_dane) },
+  { "hosts_require_dane",   opt_stringptr, LOFF(hosts_require_dane) },
 # endif
 # ifndef DISABLE_OCSP
 # endif
 # ifndef DISABLE_OCSP
-  { "hosts_require_ocsp",   opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, hosts_require_ocsp) },
+  { "hosts_require_ocsp",   opt_stringptr, LOFF(hosts_require_ocsp) },
 # endif
 # endif
-  { "hosts_require_tls",    opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, hosts_require_tls) },
+  { "hosts_require_tls",    opt_stringptr, LOFF(hosts_require_tls) },
 #endif
 #endif
-  { "hosts_try_auth",       opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, hosts_try_auth) },
-  { "hosts_try_chunking",   opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, hosts_try_chunking) },
+  { "hosts_try_auth",       opt_stringptr, LOFF(hosts_try_auth) },
+  { "hosts_try_chunking",   opt_stringptr, LOFF(hosts_try_chunking) },
 #ifdef SUPPORT_DANE
 #ifdef SUPPORT_DANE
-  { "hosts_try_dane",       opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, hosts_try_dane) },
+  { "hosts_try_dane",       opt_stringptr, LOFF(hosts_try_dane) },
 #endif
 #endif
-  { "hosts_try_fastopen",   opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, hosts_try_fastopen) },
+  { "hosts_try_fastopen",   opt_stringptr, LOFF(hosts_try_fastopen) },
 #ifndef DISABLE_PRDR
 #ifndef DISABLE_PRDR
-  { "hosts_try_prdr",       opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, hosts_try_prdr) },
+  { "hosts_try_prdr",       opt_stringptr, LOFF(hosts_try_prdr) },
 #endif
 #ifndef DISABLE_TLS
 #endif
 #ifndef DISABLE_TLS
-  { "hosts_verify_avoid_tls", opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, hosts_verify_avoid_tls) },
-#endif
-  { "interface",            opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, interface) },
-  { "keepalive",            opt_bool,
-      (void *)offsetof(smtp_transport_options_block, keepalive) },
-  { "lmtp_ignore_quota",    opt_bool,
-      (void *)offsetof(smtp_transport_options_block, lmtp_ignore_quota) },
-  { "max_rcpt",             opt_int | opt_public,
-      (void *)offsetof(transport_instance, max_addresses) },
+  { "hosts_verify_avoid_tls", opt_stringptr, LOFF(hosts_verify_avoid_tls) },
+#endif
+  { "interface",            opt_stringptr, LOFF(interface) },
+  { "keepalive",            opt_bool,     LOFF(keepalive) },
+  { "lmtp_ignore_quota",    opt_bool,     LOFF(lmtp_ignore_quota) },
+  { "max_rcpt",             opt_stringptr | opt_public,
+      OPT_OFF(transport_instance, max_addresses) },
+  { "message_linelength_limit", opt_int,   LOFF(message_linelength_limit) },
   { "multi_domain",         opt_expand_bool | opt_public,
   { "multi_domain",         opt_expand_bool | opt_public,
-      (void *)offsetof(transport_instance, multi_domain) },
-  { "port",                 opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, port) },
-  { "protocol",             opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, protocol) },
-  { "retry_include_ip_address", opt_expand_bool,
-      (void *)offsetof(smtp_transport_options_block, retry_include_ip_address) },
-  { "serialize_hosts",      opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, serialize_hosts) },
-  { "size_addition",        opt_int,
-      (void *)offsetof(smtp_transport_options_block, size_addition) },
+      OPT_OFF(transport_instance, multi_domain) },
+  { "port",                 opt_stringptr, LOFF(port) },
+  { "protocol",             opt_stringptr, LOFF(protocol) },
+  { "retry_include_ip_address", opt_expand_bool, LOFF(retry_include_ip_address) },
+  { "serialize_hosts",      opt_stringptr, LOFF(serialize_hosts) },
+  { "size_addition",        opt_int,      LOFF(size_addition) },
 #ifdef SUPPORT_SOCKS
 #ifdef SUPPORT_SOCKS
-  { "socks_proxy",          opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, socks_proxy) },
+  { "socks_proxy",          opt_stringptr, LOFF(socks_proxy) },
 #endif
 #ifndef DISABLE_TLS
 #endif
 #ifndef DISABLE_TLS
-  { "tls_certificate",      opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, tls_certificate) },
-  { "tls_crl",              opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, tls_crl) },
-  { "tls_dh_min_bits",      opt_int,
-      (void *)offsetof(smtp_transport_options_block, tls_dh_min_bits) },
-  { "tls_privatekey",       opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, tls_privatekey) },
-  { "tls_require_ciphers",  opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, tls_require_ciphers) },
-# ifdef EXPERIMENTAL_TLS_RESUME
-  { "tls_resumption_hosts", opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, tls_resumption_hosts) },
+  { "tls_alpn",             opt_stringptr, LOFF(tls_alpn) },
+  { "tls_certificate",      opt_stringptr, LOFF(tls_certificate) },
+  { "tls_crl",              opt_stringptr, LOFF(tls_crl) },
+  { "tls_dh_min_bits",      opt_int,      LOFF(tls_dh_min_bits) },
+  { "tls_privatekey",       opt_stringptr, LOFF(tls_privatekey) },
+  { "tls_require_ciphers",  opt_stringptr, LOFF(tls_require_ciphers) },
+# ifndef DISABLE_TLS_RESUME
+  { "tls_resumption_hosts", opt_stringptr, LOFF(tls_resumption_hosts) },
 # endif
 # endif
-  { "tls_sni",              opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, tls_sni) },
-  { "tls_tempfail_tryclear", opt_bool,
-      (void *)offsetof(smtp_transport_options_block, tls_tempfail_tryclear) },
-  { "tls_try_verify_hosts", opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, tls_try_verify_hosts) },
-  { "tls_verify_cert_hostnames", opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block,tls_verify_cert_hostnames)},
-  { "tls_verify_certificates", opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, tls_verify_certificates) },
-  { "tls_verify_hosts",     opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, tls_verify_hosts) },
+  { "tls_sni",              opt_stringptr, LOFF(tls_sni) },
+  { "tls_tempfail_tryclear", opt_bool, LOFF(tls_tempfail_tryclear) },
+  { "tls_try_verify_hosts", opt_stringptr, LOFF(tls_try_verify_hosts) },
+  { "tls_verify_cert_hostnames", opt_stringptr, LOFF(tls_verify_cert_hostnames)},
+  { "tls_verify_certificates", opt_stringptr, LOFF(tls_verify_certificates) },
+  { "tls_verify_hosts",     opt_stringptr, LOFF(tls_verify_hosts) },
 #endif
 #ifdef SUPPORT_I18N
 #endif
 #ifdef SUPPORT_I18N
-  { "utf8_downconvert",            opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, utf8_downconvert) },
+  { "utf8_downconvert",            opt_stringptr, LOFF(utf8_downconvert) },
 #endif
 };
 
 #endif
 };
 
@@ -230,23 +169,12 @@ void smtp_transport_closedown(transport_instance *tblock) {}
 /* Default private options block for the smtp transport. */
 
 smtp_transport_options_block smtp_transport_option_defaults = {
 /* Default private options block for the smtp transport. */
 
 smtp_transport_options_block smtp_transport_option_defaults = {
-  .hosts =                     NULL,
-  .fallback_hosts =            NULL,
-  .hostlist =                  NULL,
-  .fallback_hostlist =         NULL,
+  /* All non-mentioned elements 0/NULL/FALSE */
   .helo_data =                 US"$primary_hostname",
   .helo_data =                 US"$primary_hostname",
-  .interface =                 NULL,
-  .port =                      NULL,
   .protocol =                  US"smtp",
   .protocol =                  US"smtp",
-  .dscp =                      NULL,
-  .serialize_hosts =           NULL,
-  .hosts_try_auth =            NULL,
-  .hosts_require_auth =                NULL,
   .hosts_try_chunking =                US"*",
 #ifdef SUPPORT_DANE
   .hosts_try_dane =            US"*",
   .hosts_try_chunking =                US"*",
 #ifdef SUPPORT_DANE
   .hosts_try_dane =            US"*",
-  .hosts_require_dane =                NULL,
-  .dane_require_tls_ciphers =  NULL,
 #endif
   .hosts_try_fastopen =                US"*",
 #ifndef DISABLE_PRDR
 #endif
   .hosts_try_fastopen =                US"*",
 #ifndef DISABLE_PRDR
@@ -254,19 +182,6 @@ smtp_transport_options_block smtp_transport_option_defaults = {
 #endif
 #ifndef DISABLE_OCSP
   .hosts_request_ocsp =                US"*",               /* hosts_request_ocsp (except under DANE; tls_client_start()) */
 #endif
 #ifndef DISABLE_OCSP
   .hosts_request_ocsp =                US"*",               /* hosts_request_ocsp (except under DANE; tls_client_start()) */
-  .hosts_require_ocsp =                NULL,
-#endif
-  .hosts_require_tls =         NULL,
-  .hosts_avoid_tls =           NULL,
-  .hosts_verify_avoid_tls =    NULL,
-  .hosts_avoid_pipelining =    NULL,
-#ifndef DISABLE_PIPE_CONNECT
-  .hosts_pipe_connect =                NULL,
-#endif
-  .hosts_avoid_esmtp =         NULL,
-#ifndef DISABLE_TLS
-  .hosts_nopass_tls =          NULL,
-  .hosts_noproxy_tls =         NULL,
 #endif
   .command_timeout =           5*60,
   .connect_timeout =           5*60,
 #endif
   .command_timeout =           5*60,
   .connect_timeout =           5*60,
@@ -275,62 +190,26 @@ smtp_transport_options_block smtp_transport_option_defaults = {
   .size_addition =             1024,
   .hosts_max_try =             5,
   .hosts_max_try_hardlimit =   50,
   .size_addition =             1024,
   .hosts_max_try =             5,
   .hosts_max_try_hardlimit =   50,
+  .message_linelength_limit =  998,
   .address_retry_include_sender = TRUE,
   .address_retry_include_sender = TRUE,
-  .allow_localhost =           FALSE,
-  .authenticated_sender_force =        FALSE,
-  .gethostbyname =             FALSE,
   .dns_qualify_single =                TRUE,
   .dns_qualify_single =                TRUE,
-  .dns_search_parents =                FALSE,
   .dnssec = { .request= US"*", .require=NULL },
   .delay_after_cutoff =                TRUE,
   .dnssec = { .request= US"*", .require=NULL },
   .delay_after_cutoff =                TRUE,
-  .hosts_override =            FALSE,
-  .hosts_randomize =           FALSE,
   .keepalive =                 TRUE,
   .keepalive =                 TRUE,
-  .lmtp_ignore_quota =         FALSE,
-  .expand_retry_include_ip_address =   NULL,
   .retry_include_ip_address =  TRUE,
   .retry_include_ip_address =  TRUE,
-#ifdef SUPPORT_SOCKS
-  .socks_proxy =               NULL,
-#endif
 #ifndef DISABLE_TLS
 #ifndef DISABLE_TLS
-  .tls_certificate =           NULL,
-  .tls_crl =                   NULL,
-  .tls_privatekey =            NULL,
-  .tls_require_ciphers =       NULL,
-  .tls_sni =                   NULL,
   .tls_verify_certificates =   US"system",
   .tls_dh_min_bits =           EXIM_CLIENT_DH_DEFAULT_MIN_BITS,
   .tls_tempfail_tryclear =     TRUE,
   .tls_verify_certificates =   US"system",
   .tls_dh_min_bits =           EXIM_CLIENT_DH_DEFAULT_MIN_BITS,
   .tls_tempfail_tryclear =     TRUE,
-# ifdef EXPERIMENTAL_TLS_RESUME
-  .tls_resumption_hosts =      NULL,
-# endif
-  .tls_verify_hosts =          NULL,
   .tls_try_verify_hosts =      US"*",
   .tls_verify_cert_hostnames = US"*",
 #endif
 #ifdef SUPPORT_I18N
   .tls_try_verify_hosts =      US"*",
   .tls_verify_cert_hostnames = US"*",
 #endif
 #ifdef SUPPORT_I18N
-  .utf8_downconvert =          NULL,
+  .utf8_downconvert =          US"-1",
 #endif
 #ifndef DISABLE_DKIM
  .dkim =
 #endif
 #ifndef DISABLE_DKIM
  .dkim =
-   {.dkim_domain =             NULL,
-    .dkim_identity =           NULL,
-    .dkim_private_key =                NULL,
-    .dkim_selector =           NULL,
-    .dkim_canon =              NULL,
-    .dkim_sign_headers =       NULL,
-    .dkim_strict =             NULL,
-    .dkim_hash =               US"sha256",
-    .dkim_timestamps =         NULL,
-    .dot_stuffed =             FALSE,
-    .force_bodyhash =          FALSE,
-# ifdef EXPERIMENTAL_ARC
-    .arc_signspec =            NULL,
-# endif
-    },
-# ifdef EXPERIMENTAL_ARC
-  .arc_sign =                  NULL,
-# endif
+   { .dkim_hash =              US"sha256", },
 #endif
 };
 
 #endif
 };
 
@@ -356,49 +235,56 @@ static BOOL    pipelining_active; /* current transaction is in pipe mode */
 
 static unsigned ehlo_response(uschar * buf, unsigned checks);
 
 
 static unsigned ehlo_response(uschar * buf, unsigned checks);
 
+/* sync_responses() return codes */
+
+#define RESP_BIT_HAD_5XX       BIT(1)
+#define RESP_BIT_HAD_2XX       BIT(0)
+#define RESP_HAD_2_AND_5       (RESP_BIT_HAD_2XX | RESP_BIT_HAD_5XX)
+#define RESP_NOERROR           0
+#define RESP_RCPT_TIMEO                -1
+#define RESP_RCPT_ERROR                -2
+#define RESP_MAIL_OR_DATA_ERROR        -3
+#define RESP_EPIPE_EHLO_ERR    -4
+#define RESP_EHLO_ERR_TLS      -5
 
 /******************************************************************************/
 
 void
 smtp_deliver_init(void)
 {
 
 /******************************************************************************/
 
 void
 smtp_deliver_init(void)
 {
-if (!regex_PIPELINING) regex_PIPELINING =
-  regex_must_compile(US"\\n250[\\s\\-]PIPELINING(\\s|\\n|$)", FALSE, TRUE);
-
-if (!regex_SIZE) regex_SIZE =
-  regex_must_compile(US"\\n250[\\s\\-]SIZE(\\s|\\n|$)", FALSE, TRUE);
-
-if (!regex_AUTH) regex_AUTH =
-  regex_must_compile(AUTHS_REGEX, FALSE, TRUE);
+struct list
+  {
+  const pcre2_code **  re;
+  const uschar *       string;
+  } list[] =
+  {
+    { &regex_AUTH,             AUTHS_REGEX },
+    { &regex_CHUNKING,         US"\\n250[\\s\\-]CHUNKING(\\s|\\n|$)" },
+    { &regex_DSN,              US"\\n250[\\s\\-]DSN(\\s|\\n|$)" },
+    { &regex_IGNOREQUOTA,      US"\\n250[\\s\\-]IGNOREQUOTA(\\s|\\n|$)" },
+    { &regex_PIPELINING,       US"\\n250[\\s\\-]PIPELINING(\\s|\\n|$)" },
+    { &regex_SIZE,             US"\\n250[\\s\\-]SIZE(\\s|\\n|$)" },
 
 #ifndef DISABLE_TLS
 
 #ifndef DISABLE_TLS
-if (!regex_STARTTLS) regex_STARTTLS =
-  regex_must_compile(US"\\n250[\\s\\-]STARTTLS(\\s|\\n|$)", FALSE, TRUE);
+    { &regex_STARTTLS,         US"\\n250[\\s\\-]STARTTLS(\\s|\\n|$)" },
 #endif
 #endif
-
-if (!regex_CHUNKING) regex_CHUNKING =
-  regex_must_compile(US"\\n250[\\s\\-]CHUNKING(\\s|\\n|$)", FALSE, TRUE);
-
 #ifndef DISABLE_PRDR
 #ifndef DISABLE_PRDR
-if (!regex_PRDR) regex_PRDR =
-  regex_must_compile(US"\\n250[\\s\\-]PRDR(\\s|\\n|$)", FALSE, TRUE);
+    { &regex_PRDR,             US"\\n250[\\s\\-]PRDR(\\s|\\n|$)" },
 #endif
 #endif
-
 #ifdef SUPPORT_I18N
 #ifdef SUPPORT_I18N
-if (!regex_UTF8) regex_UTF8 =
-  regex_must_compile(US"\\n250[\\s\\-]SMTPUTF8(\\s|\\n|$)", FALSE, TRUE);
+    { &regex_UTF8,             US"\\n250[\\s\\-]SMTPUTF8(\\s|\\n|$)" },
 #endif
 #endif
-
-if (!regex_DSN) regex_DSN  =
-  regex_must_compile(US"\\n250[\\s\\-]DSN(\\s|\\n|$)", FALSE, TRUE);
-
-if (!regex_IGNOREQUOTA) regex_IGNOREQUOTA =
-  regex_must_compile(US"\\n250[\\s\\-]IGNOREQUOTA(\\s|\\n|$)", FALSE, TRUE);
-
 #ifndef DISABLE_PIPE_CONNECT
 #ifndef DISABLE_PIPE_CONNECT
-if (!regex_EARLY_PIPE) regex_EARLY_PIPE =
-  regex_must_compile(US"\\n250[\\s\\-]" EARLY_PIPE_FEATURE_NAME "(\\s|\\n|$)", FALSE, TRUE);
+    { &regex_EARLY_PIPE,       US"\\n250[\\s\\-]" EARLY_PIPE_FEATURE_NAME "(\\s|\\n|$)" },
 #endif
 #endif
+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+    { &regex_LIMITS,           US"\\n250[\\s\\-]LIMITS\\s" },
+#endif
+  };
+
+for (struct list * l = list; l < list + nelem(list); l++)
+  if (!*l->re)
+    *l->re = regex_must_compile(l->string, MCS_NOFLAGS, TRUE);
 }
 
 
 }
 
 
@@ -430,10 +316,6 @@ smtp_transport_setup(transport_instance *tblock, address_item *addrlist,
 {
 smtp_transport_options_block *ob = SOB tblock->options_block;
 
 {
 smtp_transport_options_block *ob = SOB tblock->options_block;
 
-errmsg = errmsg;    /* Keep picky compilers happy */
-uid = uid;
-gid = gid;
-
 /* Pass back options if required. This interface is getting very messy. */
 
 if (tf)
 /* Pass back options if required. This interface is getting very messy. */
 
 if (tf)
@@ -478,7 +360,8 @@ Returns:    nothing
 void
 smtp_transport_init(transport_instance *tblock)
 {
 void
 smtp_transport_init(transport_instance *tblock)
 {
-smtp_transport_options_block *ob = SOB tblock->options_block;
+smtp_transport_options_block * ob = SOB tblock->options_block;
+int old_pool = store_pool;
 
 /* Retry_use_local_part defaults FALSE if unset */
 
 
 /* Retry_use_local_part defaults FALSE if unset */
 
@@ -509,12 +392,14 @@ if (ob->command_timeout <= 0 || ob->data_timeout <= 0 ||
 /* If hosts_override is set and there are local hosts, set the global
 flag that stops verify from showing router hosts. */
 
 /* If hosts_override is set and there are local hosts, set the global
 flag that stops verify from showing router hosts. */
 
-if (ob->hosts_override && ob->hosts != NULL) tblock->overrides_hosts = TRUE;
+if (ob->hosts_override && ob->hosts) tblock->overrides_hosts = TRUE;
 
 /* If there are any fallback hosts listed, build a chain of host items
 for them, but do not do any lookups at this time. */
 
 
 /* If there are any fallback hosts listed, build a chain of host items
 for them, but do not do any lookups at this time. */
 
-host_build_hostlist(&(ob->fallback_hostlist), ob->fallback_hosts, FALSE);
+store_pool = POOL_PERM;
+host_build_hostlist(&ob->fallback_hostlist, ob->fallback_hosts, FALSE);
+store_pool = old_pool;
 }
 
 
 }
 
 
@@ -537,6 +422,7 @@ Arguments:
   host           if set, mark addrs as having used this host
   smtp_greeting  from peer
   helo_response  from peer
   host           if set, mark addrs as having used this host
   smtp_greeting  from peer
   helo_response  from peer
+  start                 points to timestamp of delivery start
 
 If errno_value has the special value ERRNO_CONNECTTIMEOUT, ETIMEDOUT is put in
 the errno field, and RTEF_CTOUT is ORed into the more_errno field, to indicate
 
 If errno_value has the special value ERRNO_CONNECTTIMEOUT, ETIMEDOUT is put in
 the errno field, and RTEF_CTOUT is ORed into the more_errno field, to indicate
@@ -547,23 +433,29 @@ Returns:       nothing
 
 static void
 set_errno(address_item *addrlist, int errno_value, uschar *msg, int rc,
 
 static void
 set_errno(address_item *addrlist, int errno_value, uschar *msg, int rc,
-  BOOL pass_message, host_item * host
+  BOOL pass_message, host_item * host,
 #ifdef EXPERIMENTAL_DSN_INFO
 #ifdef EXPERIMENTAL_DSN_INFO
-  , const uschar * smtp_greeting, const uschar * helo_response
+  const uschar * smtp_greeting, const uschar * helo_response,
 #endif
 #endif
+  struct timeval * start
   )
 {
 int orvalue = 0;
   )
 {
 int orvalue = 0;
+struct timeval deliver_time;
+
 if (errno_value == ERRNO_CONNECTTIMEOUT)
   {
   errno_value = ETIMEDOUT;
   orvalue = RTEF_CTOUT;
   }
 if (errno_value == ERRNO_CONNECTTIMEOUT)
   {
   errno_value = ETIMEDOUT;
   orvalue = RTEF_CTOUT;
   }
+timesince(&deliver_time, start);
+
 for (address_item * addr = addrlist; addr; addr = addr->next)
   if (addr->transport_return >= PENDING)
     {
     addr->basic_errno = errno_value;
     addr->more_errno |= orvalue;
 for (address_item * addr = addrlist; addr; addr = addr->next)
   if (addr->transport_return >= PENDING)
     {
     addr->basic_errno = errno_value;
     addr->more_errno |= orvalue;
+    addr->delivery_time = deliver_time;
     if (msg)
       {
       addr->message = msg;
     if (msg)
       {
       addr->message = msg;
@@ -587,14 +479,14 @@ for (address_item * addr = addrlist; addr; addr = addr->next)
 }
 
 static void
 }
 
 static void
-set_errno_nohost(address_item *addrlist, int errno_value, uschar *msg, int rc,
-  BOOL pass_message)
+set_errno_nohost(address_item * addrlist, int errno_value, uschar * msg, int rc,
+  BOOL pass_message, struct timeval * start)
 {
 {
-set_errno(addrlist, errno_value, msg, rc, pass_message, NULL
+set_errno(addrlist, errno_value, msg, rc, pass_message, NULL,
 #ifdef EXPERIMENTAL_DSN_INFO
 #ifdef EXPERIMENTAL_DSN_INFO
-         , NULL, NULL
+         NULL, NULL,
 #endif
 #endif
-         );
+         start);
 }
 
 
 }
 
 
@@ -742,8 +634,8 @@ if (suffix)
 else
   message = string_fmt_append(message, " %s", exim_errstr(basic_errno));
 
 else
   message = string_fmt_append(message, " %s", exim_errstr(basic_errno));
 
-log_write(0, LOG_MAIN, "%s", string_from_gstring(message));
-deliver_msglog("%s %s\n", tod_stamp(tod_log), message->s);
+log_write(0, LOG_MAIN, "%Y", message);
+deliver_msglog("%s %.*s\n", tod_stamp(tod_log), message->ptr, message->s);
 }
 
 static void
 }
 
 static void
@@ -775,8 +667,7 @@ static void
 deferred_event_raise(address_item * addr, host_item * host, uschar * evstr)
 {
 uschar * action = addr->transport->event_action;
 deferred_event_raise(address_item * addr, host_item * host, uschar * evstr)
 {
 uschar * action = addr->transport->event_action;
-const uschar * save_domain;
-uschar * save_local;
+const uschar * save_domain, * save_local;
 
 if (!action)
   return;
 
 if (!action)
   return;
@@ -801,7 +692,8 @@ deliver_localpart = addr->local_part;
        : string_copy(addr->message)
       : addr->basic_errno > 0
        ? string_copy(US strerror(addr->basic_errno))
        : string_copy(addr->message)
       : addr->basic_errno > 0
        ? string_copy(US strerror(addr->basic_errno))
-       : NULL);
+       : NULL,
+      NULL);
 
 deliver_localpart = save_local;
 deliver_domain =    save_domain;
 
 deliver_localpart = save_local;
 deliver_domain =    save_domain;
@@ -834,7 +726,19 @@ return count;
 static BOOL
 smtp_reap_banner(smtp_context * sx)
 {
 static BOOL
 smtp_reap_banner(smtp_context * sx)
 {
-BOOL good_response = smtp_read_response(sx, sx->buffer, sizeof(sx->buffer),
+BOOL good_response;
+#if defined(__linux__) && defined(TCP_QUICKACK)
+  {    /* Hack to get QUICKACK disabled; has to be right after 3whs, and has to on->off */
+  int sock = sx->cctx.sock;
+  struct pollfd p = {.fd = sock, .events = POLLOUT};
+  if (poll(&p, 1, 1000) >= 0)  /* retval test solely for compiler quitening */
+    {
+    (void) setsockopt(sock, IPPROTO_TCP, TCP_QUICKACK, US &on, sizeof(on));
+    (void) setsockopt(sock, IPPROTO_TCP, TCP_QUICKACK, US &off, sizeof(off));
+    }
+  }
+#endif
+good_response = smtp_read_response(sx, sx->buffer, sizeof(sx->buffer),
   '2', (SOB sx->conn_args.ob)->command_timeout);
 #ifdef EXPERIMENTAL_DSN_INFO
 sx->smtp_greeting = string_copy(sx->buffer);
   '2', (SOB sx->conn_args.ob)->command_timeout);
 #ifdef EXPERIMENTAL_DSN_INFO
 sx->smtp_greeting = string_copy(sx->buffer);
@@ -862,12 +766,112 @@ sx->helo_response = string_copy(sx->buffer);
 #endif
 #ifndef DISABLE_EVENT
 (void) event_raise(sx->conn_args.tblock->event_action,
 #endif
 #ifndef DISABLE_EVENT
 (void) event_raise(sx->conn_args.tblock->event_action,
-  US"smtp:ehlo", sx->buffer);
+  US"smtp:ehlo", sx->buffer, NULL);
 #endif
 return TRUE;
 }
 
 
 #endif
 return TRUE;
 }
 
 
+#if !defined(DISABLE_TLS) && !defined(DISABLE_TLS_RESUME)
+
+/* Grab a string differentiating server behind a loadbalancer, for TLS
+resumption when such servers do not share a session-cache */
+
+static void
+ehlo_response_lbserver(smtp_context * sx, const uschar * name_extract)
+{
+const uschar * s;
+uschar * save_item = iterate_item;
+
+if (sx->conn_args.have_lbserver)
+  return;
+iterate_item = sx->buffer;
+s = expand_cstring(name_extract);
+iterate_item = save_item;
+sx->conn_args.host_lbserver = s && !*s ? NULL : s;
+sx->conn_args.have_lbserver = TRUE;
+}
+#endif
+
+
+
+/******************************************************************************/
+
+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+/* If TLS, or TLS not offered, called with the EHLO response in the buffer.
+Check it for a LIMITS keyword and parse values into the smtp context structure.
+
+We don't bother with peers that we won't talk TLS to, even though they can,
+just ignore their LIMITS advice (if any) and treat them as if they do not.
+This saves us dealing with a duplicate set of values. */
+
+static void
+ehlo_response_limits_read(smtp_context * sx)
+{
+uschar * match;
+
+/* matches up to just after the first space after the keyword */
+
+if (regex_match(regex_LIMITS, sx->buffer, -1, &match))
+  for (const uschar * s = sx->buffer + Ustrlen(match); *s; )
+    {
+    while (isspace(*s)) s++;
+    if (*s == '\n') break;
+
+    if (strncmpic(s, US"MAILMAX=", 8) == 0)
+      {
+      sx->peer_limit_mail = atoi(CS (s += 8));
+      while (isdigit(*s)) s++;
+      }
+    else if (strncmpic(s, US"RCPTMAX=", 8) == 0)
+      {
+      sx->peer_limit_rcpt = atoi(CS (s += 8));
+      while (isdigit(*s)) s++;
+      }
+    else if (strncmpic(s, US"RCPTDOMAINMAX=", 14) == 0)
+      {
+      sx->peer_limit_rcptdom = atoi(CS (s += 14));
+      while (isdigit(*s)) s++;
+      }
+    else
+      while (*s && !isspace(*s)) s++;
+    }
+}
+
+/* Apply given values to the current connection */
+static void
+ehlo_limits_apply(smtp_context * sx,
+  unsigned limit_mail, unsigned limit_rcpt, unsigned limit_rcptdom)
+{
+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");
+  sx->single_rcpt_domain = TRUE;
+  }
+}
+
+/* Apply values from EHLO-resp to the current connection */
+static void
+ehlo_response_limits_apply(smtp_context * sx)
+{
+ehlo_limits_apply(sx, sx->peer_limit_mail, sx->peer_limit_rcpt,
+  sx->peer_limit_rcptdom);
+}
+
+/* Apply values read from cache to the current connection */
+static void
+ehlo_cache_limits_apply(smtp_context * sx)
+{
+# ifndef DISABLE_PIPE_CONNECT
+ehlo_limits_apply(sx, sx->ehlo_resp.limit_mail, sx->ehlo_resp.limit_rcpt,
+  sx->ehlo_resp.limit_rcptdom);
+# endif
+}
+#endif /*EXPERIMENTAL_ESMTP_LIMITS*/
+
+/******************************************************************************/
 
 #ifndef DISABLE_PIPE_CONNECT
 static uschar *
 
 #ifndef DISABLE_PIPE_CONNECT
 static uschar *
@@ -881,19 +885,45 @@ return Ustrchr(host->address, ':')
     host->port == PORT_NONE ? sx->port : host->port);
 }
 
     host->port == PORT_NONE ? sx->port : host->port);
 }
 
+/* Cache EHLO-response info for use by early-pipe.
+Called
+- During a normal flow on EHLO response (either cleartext or under TLS),
+  when we are willing to do PIPECONNECT and it is offered
+- During an early-pipe flow on receiving the actual EHLO response and noting
+  disparity versus the cached info used, when PIPECONNECT is still being offered
+
+We assume that suitable values have been set in the sx.ehlo_resp structure for
+features and auths; we handle the copy of limits. */
+
 static void
 static void
-write_ehlo_cache_entry(const smtp_context * sx)
+write_ehlo_cache_entry(smtp_context * sx)
 {
 open_db dbblock, * dbm_file;
 
 {
 open_db dbblock, * dbm_file;
 
+# ifdef EXPERIMENTAL_ESMTP_LIMITS
+sx->ehlo_resp.limit_mail = sx->peer_limit_mail;
+sx->ehlo_resp.limit_rcpt = sx->peer_limit_rcpt;
+sx->ehlo_resp.limit_rcptdom = sx->peer_limit_rcptdom;
+# endif
+
 if ((dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE, TRUE)))
   {
   uschar * ehlo_resp_key = ehlo_cache_key(sx);
   dbdata_ehlo_resp er = { .data = sx->ehlo_resp };
 
 if ((dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE, TRUE)))
   {
   uschar * ehlo_resp_key = ehlo_cache_key(sx);
   dbdata_ehlo_resp er = { .data = sx->ehlo_resp };
 
-  HDEBUG(D_transport) debug_printf("writing clr %04x/%04x cry %04x/%04x\n",
-    sx->ehlo_resp.cleartext_features, sx->ehlo_resp.cleartext_auths,
-    sx->ehlo_resp.crypted_features, sx->ehlo_resp.crypted_auths);
+  HDEBUG(D_transport)
+# ifdef EXPERIMENTAL_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",
+       sx->ehlo_resp.cleartext_features, sx->ehlo_resp.cleartext_auths,
+       sx->ehlo_resp.crypted_features, sx->ehlo_resp.crypted_auths,
+       sx->ehlo_resp.limit_mail, sx->ehlo_resp.limit_rcpt,
+       sx->ehlo_resp.limit_rcptdom);
+    else
+# endif
+      debug_printf("writing clr %04x/%04x cry %04x/%04x\n",
+       sx->ehlo_resp.cleartext_features, sx->ehlo_resp.cleartext_auths,
+       sx->ehlo_resp.crypted_features, sx->ehlo_resp.crypted_auths);
 
   dbfn_write(dbm_file, ehlo_resp_key, &er, (int)sizeof(er));
   dbfn_close(dbm_file);
 
   dbfn_write(dbm_file, ehlo_resp_key, &er, (int)sizeof(er));
   dbfn_close(dbm_file);
@@ -909,6 +939,16 @@ if (  sx->early_pipe_active
    && (dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE, TRUE)))
   {
   uschar * ehlo_resp_key = ehlo_cache_key(sx);
    && (dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE, TRUE)))
   {
   uschar * ehlo_resp_key = ehlo_cache_key(sx);
+  HDEBUG(D_transport)
+    {
+    dbdata_ehlo_resp * er;
+
+    if (!(er = dbfn_read_enforce_length(dbm_file, ehlo_resp_key, sizeof(dbdata_ehlo_resp))))
+      debug_printf("no ehlo-resp record!\n");
+    else
+      debug_printf("ehlo-resp record is %d seconds old\n", time(NULL) - er->time_stamp);
+    }
+
   dbfn_delete(dbm_file, ehlo_resp_key);
   dbfn_close(dbm_file);
   }
   dbfn_delete(dbm_file, ehlo_resp_key);
   dbfn_close(dbm_file);
   }
@@ -927,7 +967,7 @@ else
   uschar * ehlo_resp_key = ehlo_cache_key(sx);
   dbdata_ehlo_resp * er;
 
   uschar * ehlo_resp_key = ehlo_cache_key(sx);
   dbdata_ehlo_resp * er;
 
-  if (!(er = dbfn_read(dbm_file, ehlo_resp_key)))
+  if (!(er = dbfn_read_enforce_length(dbm_file, ehlo_resp_key, sizeof(dbdata_ehlo_resp))))
     { DEBUG(D_transport) debug_printf("no ehlo-resp record\n"); }
   else if (time(NULL) - er->time_stamp > retry_data_expire)
     {
     { DEBUG(D_transport) debug_printf("no ehlo-resp record\n"); }
   else if (time(NULL) - er->time_stamp > retry_data_expire)
     {
@@ -938,12 +978,26 @@ else
     }
   else
     {
     }
   else
     {
+    DEBUG(D_transport)
+# ifdef EXPERIMENTAL_ESMTP_LIMITS
+      if (er->data.limit_mail || er->data.limit_rcpt || er->data.limit_rcptdom)
+       debug_printf("EHLO response bits from cache:"
+         " cleartext 0x%04x/0x%04x crypted 0x%04x/0x%04x lim %05d/%05d/%05d\n",
+         er->data.cleartext_features, er->data.cleartext_auths,
+         er->data.crypted_features, er->data.crypted_auths,
+         er->data.limit_mail, er->data.limit_rcpt, er->data.limit_rcptdom);
+      else
+# endif
+       debug_printf("EHLO response bits from cache:"
+         " cleartext 0x%04x/0x%04x crypted 0x%04x/0x%04x\n",
+         er->data.cleartext_features, er->data.cleartext_auths,
+         er->data.crypted_features, er->data.crypted_auths);
+
     sx->ehlo_resp = er->data;
     sx->ehlo_resp = er->data;
+# ifdef EXPERIMENTAL_ESMTP_LIMITS
+    ehlo_cache_limits_apply(sx);
+# endif
     dbfn_close(dbm_file);
     dbfn_close(dbm_file);
-    DEBUG(D_transport) debug_printf(
-       "EHLO response bits from cache: cleartext 0x%04x/0x%04x crypted 0x%04x/0x%04x\n",
-       er->data.cleartext_features, er->data.cleartext_auths,
-       er->data.crypted_features, er->data.crypted_auths);
     return TRUE;
     }
   dbfn_close(dbm_file);
     return TRUE;
     }
   dbfn_close(dbm_file);
@@ -954,7 +1008,7 @@ return FALSE;
 
 
 /* Return an auths bitmap for the set of AUTH methods offered by the server
 
 
 /* Return an auths bitmap for the set of AUTH methods offered by the server
-which match our authenticators. */
+which match our client-side authenticators. */
 
 static unsigned short
 study_ehlo_auths(smtp_context * sx)
 
 static unsigned short
 study_ehlo_auths(smtp_context * sx)
@@ -965,7 +1019,7 @@ uschar authnum;
 unsigned short authbits = 0;
 
 if (!sx->esmtp) return 0;
 unsigned short authbits = 0;
 
 if (!sx->esmtp) return 0;
-if (!regex_AUTH) regex_AUTH = regex_must_compile(AUTHS_REGEX, FALSE, TRUE);
+if (!regex_AUTH) regex_AUTH = regex_must_compile(AUTHS_REGEX, MCS_NOFLAGS, TRUE);
 if (!regex_match_and_setup(regex_AUTH, sx->buffer, 0, -1)) return 0;
 expand_nmax = -1;                                              /* reset */
 names = string_copyn(expand_nstring[1], expand_nlength[1]);
 if (!regex_match_and_setup(regex_AUTH, sx->buffer, 0, -1)) return 0;
 expand_nmax = -1;                                              /* reset */
 names = string_copyn(expand_nstring[1], expand_nlength[1]);
@@ -973,16 +1027,14 @@ names = string_copyn(expand_nstring[1], expand_nlength[1]);
 for (au = auths, authnum = 0; au; au = au->next, authnum++) if (au->client)
   {
   const uschar * list = names;
 for (au = auths, authnum = 0; au; au = au->next, authnum++) if (au->client)
   {
   const uschar * list = names;
-  int sep = ' ';
-  uschar name[32];
-
-  while (string_nextinlist(&list, &sep, name, sizeof(name)))
-    if (strcmpic(au->public_name, name) == 0)
+  uschar * s;
+  for (int sep = ' '; s = string_nextinlist(&list, &sep, NULL, 0); )
+    if (strcmpic(au->public_name, s) == 0)
       { authbits |= BIT(authnum); break; }
   }
 
 DEBUG(D_transport)
       { authbits |= BIT(authnum); break; }
   }
 
 DEBUG(D_transport)
-  debug_printf("server offers %s AUTH, methods '%s', bitmap 0x%04x\n",
+  debug_printf("server offers %s AUTH, methods '%s', usable-bitmap 0x%04x\n",
     tls_out.active.sock >= 0 ? "crypted" : "plaintext", names, authbits);
 
 if (tls_out.active.sock >= 0)
     tls_out.active.sock >= 0 ? "crypted" : "plaintext", names, authbits);
 
 if (tls_out.active.sock >= 0)
@@ -1023,6 +1075,8 @@ sx->pending_EHLO = FALSE;
 
 if (pending_BANNER)
   {
 
 if (pending_BANNER)
   {
+  const uschar * s;
+
   DEBUG(D_transport) debug_printf("%s expect banner\n", __FUNCTION__);
   (*countp)--;
   if (!smtp_reap_banner(sx))
   DEBUG(D_transport) debug_printf("%s expect banner\n", __FUNCTION__);
   (*countp)--;
   if (!smtp_reap_banner(sx))
@@ -1031,6 +1085,13 @@ if (pending_BANNER)
     if (tls_out.active.sock >= 0) rc = DEFER;
     goto fail;
     }
     if (tls_out.active.sock >= 0) rc = DEFER;
     goto fail;
     }
+  /*XXX EXPERIMENTAL_ESMTP_LIMITS ? */
+
+# ifndef DISABLE_TLS_RESUME
+  s = ((smtp_transport_options_block *)sx->conn_args.ob)->host_name_extract;
+  if (!s) s = HNE_DEFAULT;
+  ehlo_response_lbserver(sx, s);
+# endif
   }
 
 if (pending_EHLO)
   }
 
 if (pending_EHLO)
@@ -1047,8 +1108,9 @@ if (pending_EHLO)
     goto fail;
     }
 
     goto fail;
     }
 
-  /* Compare the actual EHLO response to the cached value we assumed;
-  on difference, dump or rewrite the cache and arrange for a retry. */
+  /* Compare the actual EHLO response extensions and AUTH methods to the cached
+  value we assumed; on difference, dump or rewrite the cache and arrange for a
+  retry. */
 
   ap = tls_out.active.sock < 0
       ? &sx->ehlo_resp.cleartext_auths : &sx->ehlo_resp.crypted_auths;
 
   ap = tls_out.active.sock < 0
       ? &sx->ehlo_resp.cleartext_auths : &sx->ehlo_resp.crypted_auths;
@@ -1058,6 +1120,10 @@ if (pending_EHLO)
        | OPTION_CHUNKING | OPTION_PRDR | OPTION_DSN | OPTION_PIPE | OPTION_SIZE
        | OPTION_UTF8 | OPTION_EARLY_PIPE
        );
        | OPTION_CHUNKING | OPTION_PRDR | OPTION_DSN | OPTION_PIPE | OPTION_SIZE
        | OPTION_UTF8 | OPTION_EARLY_PIPE
        );
+# ifdef EXPERIMENTAL_ESMTP_LIMITS
+  if (tls_out.active.sock >= 0 || !(peer_offered & OPTION_TLS))
+    ehlo_response_limits_read(sx);
+# endif
   if (  peer_offered != sx->peer_offered
      || (authbits = study_ehlo_auths(sx)) != *ap)
     {
   if (  peer_offered != sx->peer_offered
      || (authbits = study_ehlo_auths(sx)) != *ap)
     {
@@ -1065,16 +1131,47 @@ if (pending_EHLO)
       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);
       debug_printf("EHLO %s extensions changed, 0x%04x/0x%04x -> 0x%04x/0x%04x\n",
                    tls_out.active.sock < 0 ? "cleartext" : "crypted",
                    sx->peer_offered, *ap, peer_offered, authbits);
-    *(tls_out.active.sock < 0
-      ? &sx->ehlo_resp.cleartext_features : &sx->ehlo_resp.crypted_features) = peer_offered;
-    *ap = authbits;
     if (peer_offered & OPTION_EARLY_PIPE)
     if (peer_offered & OPTION_EARLY_PIPE)
+      {
+      *(tls_out.active.sock < 0
+       ? &sx->ehlo_resp.cleartext_features : &sx->ehlo_resp.crypted_features) =
+         peer_offered;
+      *ap = authbits;
       write_ehlo_cache_entry(sx);
       write_ehlo_cache_entry(sx);
+      }
     else
     else
+      {
       invalidate_ehlo_cache_entry(sx);
       invalidate_ehlo_cache_entry(sx);
+      sx->early_pipe_active = FALSE;   /* cancel further early-pipe on this conn */
+      }
 
     return OK;         /* just carry on */
     }
 
     return OK;         /* just carry on */
     }
+# ifdef EXPERIMENTAL_ESMTP_LIMITS
+    /* If we are handling LIMITS, compare the actual EHLO LIMITS values with the
+    cached values and invalidate cache if different.  OK to carry on with
+    connect since values are advisory. */
+    {
+    if (  (tls_out.active.sock >= 0 || !(peer_offered & OPTION_TLS))
+       && (  sx->peer_limit_mail != sx->ehlo_resp.limit_mail
+          || sx->peer_limit_rcpt != sx->ehlo_resp.limit_rcpt
+          || sx->peer_limit_rcptdom != sx->ehlo_resp.limit_rcptdom
+       )  )
+      {
+      HDEBUG(D_transport)
+       {
+       debug_printf("EHLO LIMITS changed:");
+       if (sx->peer_limit_mail != sx->ehlo_resp.limit_mail)
+         debug_printf(" MAILMAX %u -> %u\n", sx->ehlo_resp.limit_mail, sx->peer_limit_mail);
+       else if (sx->peer_limit_rcpt != sx->ehlo_resp.limit_rcpt)
+         debug_printf(" RCPTMAX %u -> %u\n", sx->ehlo_resp.limit_rcpt, sx->peer_limit_rcpt);
+       else
+         debug_printf(" RCPTDOMAINMAX %u -> %u\n", sx->ehlo_resp.limit_rcptdom, sx->peer_limit_rcptdom);
+       }
+      invalidate_ehlo_cache_entry(sx);
+      }
+    }
+# endif
   }
 return OK;
 
   }
 return OK;
 
@@ -1083,7 +1180,7 @@ fail:
   (void) smtp_discard_responses(sx, sx->conn_args.ob, *countp);
   return rc;
 }
   (void) smtp_discard_responses(sx, sx->conn_args.ob, *countp);
   return rc;
 }
-#endif
+#endif /*!DISABLE_PIPE_CONNECT*/
 
 
 /*************************************************
 
 
 /*************************************************
@@ -1139,7 +1236,7 @@ int yield = 0;
 #ifndef DISABLE_PIPE_CONNECT
 int rc;
 if ((rc = smtp_reap_early_pipe(sx, &count)) != OK)
 #ifndef DISABLE_PIPE_CONNECT
 int rc;
 if ((rc = smtp_reap_early_pipe(sx, &count)) != OK)
-  return rc == FAIL ? -4 : -5;
+  return rc == FAIL ? RESP_EPIPE_EHLO_ERR : RESP_EHLO_ERR_TLS;
 #endif
 
 /* Handle the response for a MAIL command. On error, reinstate the original
 #endif
 
 /* Handle the response for a MAIL command. On error, reinstate the original
@@ -1150,14 +1247,14 @@ if (sx->pending_MAIL)
   {
   DEBUG(D_transport) debug_printf("%s expect mail\n", __FUNCTION__);
   count--;
   {
   DEBUG(D_transport) debug_printf("%s expect mail\n", __FUNCTION__);
   count--;
-  sx->pending_MAIL = FALSE;
+  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");
     Ustrcpy(big_buffer, mail_command);  /* Fits, because it came from there! */
     if (errno == ERRNO_TLSFAILURE)
   if (!smtp_read_response(sx, sx->buffer, sizeof(sx->buffer),
                          '2', ob->command_timeout))
     {
     DEBUG(D_transport) debug_printf("bad response for MAIL\n");
     Ustrcpy(big_buffer, mail_command);  /* Fits, because it came from there! */
     if (errno == ERRNO_TLSFAILURE)
-      return -5;
+      return RESP_EHLO_ERR_TLS;
     if (errno == 0 && sx->buffer[0] != 0)
       {
       int save_errno = 0;
     if (errno == 0 && sx->buffer[0] != 0)
       {
       int save_errno = 0;
@@ -1177,7 +1274,7 @@ if (sx->pending_MAIL)
       addr->host_used = sx->conn_args.host;
       addr = addr->next;
       }
       addr->host_used = sx->conn_args.host;
       addr = addr->next;
       }
-    return -3;
+    return RESP_MAIL_OR_DATA_ERROR;
     }
   }
 
     }
   }
 
@@ -1191,16 +1288,16 @@ while (count-- > 0)
   {
   while (addr->transport_return != PENDING_DEFER)
     if (!(addr = addr->next))
   {
   while (addr->transport_return != PENDING_DEFER)
     if (!(addr = addr->next))
-      return -2;
+      return RESP_RCPT_ERROR;
 
   /* The address was accepted */
   addr->host_used = sx->conn_args.host;
 
 
   /* The address was accepted */
   addr->host_used = sx->conn_args.host;
 
-  DEBUG(D_transport) debug_printf("%s expect rcpt\n", __FUNCTION__);
+  DEBUG(D_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))
     {
   if (smtp_read_response(sx, sx->buffer, sizeof(sx->buffer),
                          '2', ob->command_timeout))
     {
-    yield |= 1;
+    yield |= RESP_BIT_HAD_2XX;
     addr->transport_return = PENDING_OK;
 
     /* If af_dr_retry_exists is set, there was a routing delay on this address;
     addr->transport_return = PENDING_OK;
 
     /* If af_dr_retry_exists is set, there was a routing delay on this address;
@@ -1219,7 +1316,7 @@ while (count-- > 0)
   /* Error on first TLS read */
 
   else if (errno == ERRNO_TLSFAILURE)
   /* Error on first TLS read */
 
   else if (errno == ERRNO_TLSFAILURE)
-    return -5;
+    return RESP_EHLO_ERR_TLS;
 
   /* Timeout while reading the response */
 
 
   /* Timeout while reading the response */
 
@@ -1227,10 +1324,10 @@ while (count-- > 0)
     {
     uschar *message = string_sprintf("SMTP timeout after RCPT TO:<%s>",
                transport_rcpt_address(addr, sx->conn_args.tblock->rcpt_include_affixes));
     {
     uschar *message = string_sprintf("SMTP timeout after RCPT TO:<%s>",
                transport_rcpt_address(addr, sx->conn_args.tblock->rcpt_include_affixes));
-    set_errno_nohost(sx->first_addr, ETIMEDOUT, message, DEFER, FALSE);
+    set_errno_nohost(sx->first_addr, ETIMEDOUT, message, DEFER, FALSE, &sx->delivery_start);
     retry_add_item(addr, addr->address_retry_key, 0);
     update_waiting = FALSE;
     retry_add_item(addr, addr->address_retry_key, 0);
     update_waiting = FALSE;
-    return -1;
+    return RESP_RCPT_TIMEO;
     }
 
   /* Handle other errors in obtaining an SMTP response by returning -1. This
     }
 
   /* Handle other errors in obtaining an SMTP response by returning -1. This
@@ -1248,7 +1345,7 @@ while (count-- > 0)
     g = string_fmt_append_f(g, SVFMT_TAINT_NOCHK, "RCPT TO:<%s>",
       transport_rcpt_address(addr, sx->conn_args.tblock->rcpt_include_affixes));
     string_from_gstring(g);
     g = string_fmt_append_f(g, SVFMT_TAINT_NOCHK, "RCPT TO:<%s>",
       transport_rcpt_address(addr, sx->conn_args.tblock->rcpt_include_affixes));
     string_from_gstring(g);
-    return -2;
+    return RESP_RCPT_ERROR;
     }
 
   /* Handle SMTP permanent and temporary response codes. */
     }
 
   /* Handle SMTP permanent and temporary response codes. */
@@ -1268,7 +1365,7 @@ while (count-- > 0)
     if (sx->buffer[0] == '5')
       {
       addr->transport_return = FAIL;
     if (sx->buffer[0] == '5')
       {
       addr->transport_return = FAIL;
-      yield |= 2;
+      yield |= RESP_BIT_HAD_5XX;
       }
 
     /* The response was 4xx */
       }
 
     /* The response was 4xx */
@@ -1288,9 +1385,9 @@ while (count-- > 0)
        /* If a 452 and we've had at least one 2xx or 5xx, set next_addr to the
        start point for another MAIL command. */
 
        /* If a 452 and we've had at least one 2xx or 5xx, set next_addr to the
        start point for another MAIL command. */
 
-       if (addr->more_errno >> 8 == 52  &&  yield & 3)
+       if (addr->more_errno >> 8 == 52  &&  yield > 0)
          {
          {
-         if (!sx->RCPT_452)
+         if (!sx->RCPT_452)            /* initialised at MAIL-ack above */
            {
            DEBUG(D_transport)
              debug_printf("%s: seen first 452 too-many-rcpts\n", __FUNCTION__);
            {
            DEBUG(D_transport)
              debug_printf("%s: seen first 452 too-many-rcpts\n", __FUNCTION__);
@@ -1337,6 +1434,8 @@ while (count-- > 0)
        }
       }
     }
        }
       }
     }
+  if (count && !(addr = addr->next))
+    return RESP_RCPT_ERROR;
   }       /* Loop for next RCPT response */
 
 /* Update where to start at for the next block of responses, unless we
   }       /* Loop for next RCPT response */
 
 /* Update where to start at for the next block of responses, unless we
@@ -1358,16 +1457,16 @@ if (pending_DATA != 0)
     BOOL pass_message;
 
     if (errno == ERRNO_TLSFAILURE)     /* Error on first TLS read */
     BOOL pass_message;
 
     if (errno == ERRNO_TLSFAILURE)     /* Error on first TLS read */
-      return -5;
+      return RESP_EHLO_ERR_TLS;
 
 
-    if (pending_DATA > 0 || (yield & 1) != 0)
+    if (pending_DATA > 0 || yield & RESP_BIT_HAD_2XX)
       {
       if (errno == 0 && sx->buffer[0] == '4')
        {
        errno = ERRNO_DATA4XX;
        sx->first_addr->more_errno |= ((sx->buffer[1] - '0')*10 + sx->buffer[2] - '0') << 8;
        }
       {
       if (errno == 0 && sx->buffer[0] == '4')
        {
        errno = ERRNO_DATA4XX;
        sx->first_addr->more_errno |= ((sx->buffer[1] - '0')*10 + sx->buffer[2] - '0') << 8;
        }
-      return -3;
+      return 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 "
       }
     (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 "
@@ -1395,11 +1494,18 @@ smtp_transport_options_block * ob = sx->conn_args.ob;   /* transport options */
 host_item * host = sx->conn_args.host;                 /* host to deliver to */
 int rc;
 
 host_item * host = sx->conn_args.host;                 /* host to deliver to */
 int rc;
 
+/* Set up globals for error messages */
+
+authenticator_name = au->name;
+driver_srcfile = au->srcfile;
+driver_srcline = au->srcline;
+
 sx->outblock.authenticating = TRUE;
 rc = (au->info->clientcode)(au, sx, ob->command_timeout,
                            sx->buffer, sizeof(sx->buffer));
 sx->outblock.authenticating = FALSE;
 sx->outblock.authenticating = TRUE;
 rc = (au->info->clientcode)(au, sx, ob->command_timeout,
                            sx->buffer, sizeof(sx->buffer));
 sx->outblock.authenticating = FALSE;
-DEBUG(D_transport) debug_printf("%s authenticator yielded %d\n", au->name, rc);
+driver_srcfile = authenticator_name = NULL; driver_srcline = 0;
+DEBUG(D_transport) debug_printf("%s authenticator yielded %s\n", au->name, rc_names[rc]);
 
 /* A temporary authentication failure must hold up delivery to
 this host. After a permanent authentication failure, we carry on
 
 /* A temporary authentication failure must hold up delivery to
 this host. After a permanent authentication failure, we carry on
@@ -1423,10 +1529,25 @@ switch(rc)
   /* Failure after reading a response */
 
   case FAIL:
   /* Failure after reading a response */
 
   case FAIL:
+    {
+    uschar * logmsg = NULL;
+
     if (errno != 0 || sx->buffer[0] != '5') return FAIL;
     if (errno != 0 || sx->buffer[0] != '5') return FAIL;
-    log_write(0, LOG_MAIN, "%s authenticator failed H=%s [%s] %s",
-      au->name, host->name, host->address, sx->buffer);
+#ifndef DISABLE_EVENT
+     {
+      uschar * save_name = sender_host_authenticated;
+      sender_host_authenticated = au->name;
+      if ((logmsg = event_raise(sx->conn_args.tblock->event_action, US"auth:fail",
+                               sx->buffer, NULL)))
+       log_write(0, LOG_MAIN, "%s", logmsg);
+      sender_host_authenticated = save_name;
+     }
+#endif
+    if (!logmsg)
+      log_write(0, LOG_MAIN, "%s authenticator failed H=%s [%s] %s",
+       au->name, host->name, host->address, sx->buffer);
     break;
     break;
+    }
 
   /* Failure by some other means. In effect, the authenticator
   decided it wasn't prepared to handle this case. Typically this
 
   /* Failure by some other means. In effect, the authenticator
   decided it wasn't prepared to handle this case. Typically this
@@ -1445,7 +1566,7 @@ switch(rc)
 
   case ERROR:
     set_errno_nohost(sx->addrlist, ERRNO_AUTHPROB, string_copy(sx->buffer),
 
   case ERROR:
     set_errno_nohost(sx->addrlist, ERRNO_AUTHPROB, string_copy(sx->buffer),
-             DEFER, FALSE);
+             DEFER, FALSE, &sx->delivery_start);
     return ERROR;
   }
 return OK;
     return ERROR;
   }
 return OK;
@@ -1486,7 +1607,7 @@ f.smtp_authenticated = FALSE;
 client_authenticator = client_authenticated_id = client_authenticated_sender = NULL;
 
 if (!regex_AUTH)
 client_authenticator = client_authenticated_id = client_authenticated_sender = NULL;
 
 if (!regex_AUTH)
-  regex_AUTH = regex_must_compile(AUTHS_REGEX, FALSE, TRUE);
+  regex_AUTH = regex_must_compile(AUTHS_REGEX, MCS_NOFLAGS, TRUE);
 
 /* Is the server offering AUTH? */
 
 
 /* Is the server offering AUTH? */
 
@@ -1612,9 +1733,12 @@ if (  sx->esmtp
 
 if (require_auth == OK && !f.smtp_authenticated)
   {
 
 if (require_auth == OK && !f.smtp_authenticated)
   {
+#ifndef DISABLE_PIPE_CONNECT
+  invalidate_ehlo_cache_entry(sx);
+#endif
   set_errno_nohost(sx->addrlist, ERRNO_AUTHFAIL,
     string_sprintf("authentication required but %s", fail_reason), DEFER,
   set_errno_nohost(sx->addrlist, ERRNO_AUTHFAIL,
     string_sprintf("authentication required but %s", fail_reason), DEFER,
-    FALSE);
+    FALSE, &sx->delivery_start);
   return DEFER;
   }
 
   return DEFER;
   }
 
@@ -1625,9 +1749,9 @@ return OK;
 /* Construct AUTH appendix string for MAIL TO */
 /*
 Arguments
 /* Construct AUTH appendix string for MAIL TO */
 /*
 Arguments
-  buffer       to build string
+  sx           context for smtp connection
+  p            point in sx->buffer to build string
   addrlist      chain of potential addresses to deliver
   addrlist      chain of potential addresses to deliver
-  ob           transport options
 
 Globals                f.smtp_authenticated
                client_authenticated_sender
 
 Globals                f.smtp_authenticated
                client_authenticated_sender
@@ -1635,29 +1759,31 @@ Return  True on error, otherwise buffer has (possibly empty) terminated string
 */
 
 static BOOL
 */
 
 static BOOL
-smtp_mail_auth_str(uschar *buffer, unsigned bufsize, address_item *addrlist,
-                   smtp_transport_options_block *ob)
+smtp_mail_auth_str(smtp_context * sx, uschar * p, address_item * addrlist)
 {
 {
+smtp_transport_options_block * ob = sx->conn_args.ob;
 uschar * local_authenticated_sender = authenticated_sender;
 
 #ifdef notdef
 uschar * local_authenticated_sender = authenticated_sender;
 
 #ifdef notdef
-  debug_printf("smtp_mail_auth_str: as<%s> os<%s> SA<%s>\n", authenticated_sender, ob->authenticated_sender, f.smtp_authenticated?"Y":"N");
+  debug_printf("smtp_mail_auth_str: as<%s> os<%s> SA<%s>\n",
+    authenticated_sender, ob->authenticated_sender, f.smtp_authenticated?"Y":"N");
 #endif
 
 if (ob->authenticated_sender)
   {
 #endif
 
 if (ob->authenticated_sender)
   {
-  uschar *new = expand_string(ob->authenticated_sender);
+  uschar * new = expand_string(ob->authenticated_sender);
   if (!new)
     {
     if (!f.expand_string_forcedfail)
       {
       uschar *message = string_sprintf("failed to expand "
         "authenticated_sender: %s", expand_string_message);
   if (!new)
     {
     if (!f.expand_string_forcedfail)
       {
       uschar *message = string_sprintf("failed to expand "
         "authenticated_sender: %s", expand_string_message);
-      set_errno_nohost(addrlist, ERRNO_EXPANDFAIL, message, DEFER, FALSE);
+      set_errno_nohost(addrlist, ERRNO_EXPANDFAIL, message, DEFER, FALSE, &sx->delivery_start);
       return TRUE;
       }
     }
       return TRUE;
       }
     }
-  else if (*new) local_authenticated_sender = new;
+  else if (*new)
+    local_authenticated_sender = new;
   }
 
 /* Add the authenticated sender address if present */
   }
 
 /* Add the authenticated sender address if present */
@@ -1665,13 +1791,13 @@ if (ob->authenticated_sender)
 if (  (f.smtp_authenticated || ob->authenticated_sender_force)
    && local_authenticated_sender)
   {
 if (  (f.smtp_authenticated || ob->authenticated_sender_force)
    && local_authenticated_sender)
   {
-  string_format_nt(buffer, bufsize, " AUTH=%s",
+  string_format_nt(p, sizeof(sx->buffer) - (p-sx->buffer), " AUTH=%s",
     auth_xtextencode(local_authenticated_sender,
       Ustrlen(local_authenticated_sender)));
   client_authenticated_sender = string_copy(local_authenticated_sender);
   }
 else
     auth_xtextencode(local_authenticated_sender,
       Ustrlen(local_authenticated_sender)));
   client_authenticated_sender = string_copy(local_authenticated_sender);
   }
 else
-  *buffer= 0;
+  *= 0;
 
 return FALSE;
 }
 
 return FALSE;
 }
@@ -1680,8 +1806,8 @@ return FALSE;
 
 typedef struct smtp_compare_s
 {
 
 typedef struct smtp_compare_s
 {
-    uschar                          *current_sender_address;
-    struct transport_instance       *tblock;
+    const uschar *             current_sender_address;
+    struct transport_instance *        tblock;
 } smtp_compare_t;
 
 
 } smtp_compare_t;
 
 
@@ -1690,7 +1816,7 @@ sender_address, helo_data and tls_certificate if enabled.
 */
 
 static uschar *
 */
 
 static uschar *
-smtp_local_identity(uschar * sender, struct transport_instance * tblock)
+smtp_local_identity(const uschar * sender, struct transport_instance * tblock)
 {
 address_item * addr1;
 uschar * if1 = US"";
 {
 address_item * addr1;
 uschar * if1 = US"";
@@ -1698,7 +1824,7 @@ uschar * helo1 = US"";
 #ifndef DISABLE_TLS
 uschar * tlsc1 = US"";
 #endif
 #ifndef DISABLE_TLS
 uschar * tlsc1 = US"";
 #endif
-uschar * save_sender_address = sender_address;
+const uschar * save_sender_address = sender_address;
 uschar * local_identity = NULL;
 smtp_transport_options_block * ob = SOB tblock->options_block;
 
 uschar * local_identity = NULL;
 smtp_transport_options_block * ob = SOB tblock->options_block;
 
@@ -1744,8 +1870,9 @@ uschar * message_local_identity,
 current_local_identity =
   smtp_local_identity(s_compare->current_sender_address, s_compare->tblock);
 
 current_local_identity =
   smtp_local_identity(s_compare->current_sender_address, s_compare->tblock);
 
-if (!(new_sender_address = deliver_get_sender_address(message_id)))
-    return 0;
+if (!(new_sender_address = spool_sender_from_msgid(message_id)))
+  return FALSE;
+
 
 message_local_identity =
   smtp_local_identity(new_sender_address, s_compare->tblock);
 
 message_local_identity =
   smtp_local_identity(new_sender_address, s_compare->tblock);
@@ -1758,57 +1885,65 @@ return Ustrcmp(current_local_identity, message_local_identity) == 0;
 static unsigned
 ehlo_response(uschar * buf, unsigned checks)
 {
 static unsigned
 ehlo_response(uschar * buf, unsigned checks)
 {
-size_t bsize = Ustrlen(buf);
+PCRE2_SIZE bsize = Ustrlen(buf);
+pcre2_match_data * md = pcre2_match_data_create(1, pcre_gen_ctx);
 
 /* debug_printf("%s: check for 0x%04x\n", __FUNCTION__, checks); */
 
 #ifndef DISABLE_TLS
 if (  checks & OPTION_TLS
 
 /* debug_printf("%s: check for 0x%04x\n", __FUNCTION__, checks); */
 
 #ifndef DISABLE_TLS
 if (  checks & OPTION_TLS
-   && pcre_exec(regex_STARTTLS, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
+   && pcre2_match(regex_STARTTLS,
+                 (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_gen_mtc_ctx) < 0)
 #endif
   checks &= ~OPTION_TLS;
 
 if (  checks & OPTION_IGNQ
 #endif
   checks &= ~OPTION_TLS;
 
 if (  checks & OPTION_IGNQ
-   && pcre_exec(regex_IGNOREQUOTA, NULL, CS buf, bsize, 0,
-               PCRE_EOPT, NULL, 0) < 0)
+   && pcre2_match(regex_IGNOREQUOTA,
+                 (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_gen_mtc_ctx) < 0)
   checks &= ~OPTION_IGNQ;
 
 if (  checks & OPTION_CHUNKING
   checks &= ~OPTION_IGNQ;
 
 if (  checks & OPTION_CHUNKING
-   && pcre_exec(regex_CHUNKING, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
+   && pcre2_match(regex_CHUNKING,
+                 (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_gen_mtc_ctx) < 0)
   checks &= ~OPTION_CHUNKING;
 
 #ifndef DISABLE_PRDR
 if (  checks & OPTION_PRDR
   checks &= ~OPTION_CHUNKING;
 
 #ifndef DISABLE_PRDR
 if (  checks & OPTION_PRDR
-   && pcre_exec(regex_PRDR, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
+   && pcre2_match(regex_PRDR,
+                 (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_gen_mtc_ctx) < 0)
 #endif
   checks &= ~OPTION_PRDR;
 
 #ifdef SUPPORT_I18N
 if (  checks & OPTION_UTF8
 #endif
   checks &= ~OPTION_PRDR;
 
 #ifdef SUPPORT_I18N
 if (  checks & OPTION_UTF8
-   && pcre_exec(regex_UTF8, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
+   && pcre2_match(regex_UTF8,
+                 (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_gen_mtc_ctx) < 0)
 #endif
   checks &= ~OPTION_UTF8;
 
 if (  checks & OPTION_DSN
 #endif
   checks &= ~OPTION_UTF8;
 
 if (  checks & OPTION_DSN
-   && pcre_exec(regex_DSN, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
+   && pcre2_match(regex_DSN,
+                 (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_gen_mtc_ctx) < 0)
   checks &= ~OPTION_DSN;
 
 if (  checks & OPTION_PIPE
   checks &= ~OPTION_DSN;
 
 if (  checks & OPTION_PIPE
-   && pcre_exec(regex_PIPELINING, NULL, CS buf, bsize, 0,
-               PCRE_EOPT, NULL, 0) < 0)
+   && pcre2_match(regex_PIPELINING,
+                 (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_gen_mtc_ctx) < 0)
   checks &= ~OPTION_PIPE;
 
 if (  checks & OPTION_SIZE
   checks &= ~OPTION_PIPE;
 
 if (  checks & OPTION_SIZE
-   && pcre_exec(regex_SIZE, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
+   && pcre2_match(regex_SIZE,
+                 (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_gen_mtc_ctx) < 0)
   checks &= ~OPTION_SIZE;
 
 #ifndef DISABLE_PIPE_CONNECT
 if (  checks & OPTION_EARLY_PIPE
   checks &= ~OPTION_SIZE;
 
 #ifndef DISABLE_PIPE_CONNECT
 if (  checks & OPTION_EARLY_PIPE
-   && pcre_exec(regex_EARLY_PIPE, NULL, CS buf, bsize, 0,
-               PCRE_EOPT, NULL, 0) < 0)
+   && pcre2_match(regex_EARLY_PIPE,
+                 (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_gen_mtc_ctx) < 0)
 #endif
   checks &= ~OPTION_EARLY_PIPE;
 
 #endif
   checks &= ~OPTION_EARLY_PIPE;
 
+/* pcre2_match_data_free(md);  gen ctx needs no free */
 /* debug_printf("%s: found     0x%04x\n", __FUNCTION__, checks); */
 return checks;
 }
 /* debug_printf("%s: found     0x%04x\n", __FUNCTION__, checks); */
 return checks;
 }
@@ -1884,18 +2019,18 @@ if (flags & tc_reap_prev  &&  prev_cmd_count > 0)
 
   switch(sync_responses(sx, prev_cmd_count, 0))
     {
 
   switch(sync_responses(sx, prev_cmd_count, 0))
     {
-    case 1:                            /* 2xx (only) => OK */
-    case 3: sx->good_RCPT = TRUE;      /* 2xx & 5xx => OK & progress made */
-    case 2: sx->completed_addr = TRUE; /* 5xx (only) => progress made */
-    case 0: break;                     /* No 2xx or 5xx, but no probs */
+    case RESP_BIT_HAD_2XX:                             /* OK */
+    case RESP_HAD_2_AND_5: sx->good_RCPT = TRUE;       /* OK & progress made */
+    case RESP_BIT_HAD_5XX: sx->completed_addr = TRUE;  /* progress made */
+    case RESP_NOERROR:    break;       /* No 2xx or 5xx, but no probs */
 
 
-    case -5: errno = ERRNO_TLSFAILURE;
-            return DEFER;
+    case RESP_EHLO_ERR_TLS:errno = ERRNO_TLSFAILURE;
+                          return DEFER;
 #ifndef DISABLE_PIPE_CONNECT
 #ifndef DISABLE_PIPE_CONNECT
-    case -4:                           /* non-2xx for pipelined banner or EHLO */
+    case RESP_EPIPE_EHLO_ERR:          /* non-2xx for pipelined banner or EHLO */
 #endif
 #endif
-    case -1:                           /* Timeout on RCPT */
-    default: return ERROR;             /* I/O error, or any MAIL/DATA error */
+    case RESP_RCPT_TIMEO:              /* Timeout on RCPT */
+    default:              return ERROR;/* I/O error, or any MAIL/DATA error */
     }
   cmd_count = 1;
   if (!sx->pending_BDAT)
     }
   cmd_count = 1;
   if (!sx->pending_BDAT)
@@ -1933,6 +2068,36 @@ return OK;
 
 
 
 
 
 
+#ifdef SUPPORT_DANE
+static int
+check_force_dane_conn(smtp_context * sx, smtp_transport_options_block * ob)
+{
+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))
+    {
+    case OK:           sx->conn_args.dane = TRUE;
+                       ob->tls_tempfail_tryclear = FALSE;      /* force TLS */
+                       ob->tls_sni = sx->conn_args.host->name; /* force SNI */
+                       break;
+    case FAIL_FORCED:  break;
+    default:           set_errno_nohost(sx->addrlist, ERRNO_DNSDEFER,
+                           string_sprintf("DANE error: tlsa lookup %s",
+                             rc_to_string(rc)),
+                           rc, FALSE, &sx->delivery_start);
+# ifndef DISABLE_EVENT
+                       (void) event_raise(sx->conn_args.tblock->event_action,
+                         US"dane:fail", sx->dane_required
+                           ?  US"dane-required" : US"dnssec-invalid",
+                         NULL);
+# endif
+                       return rc;
+    }
+return OK;
+}
+#endif
 
 
 /*************************************************
 
 
 /*************************************************
@@ -1941,7 +2106,7 @@ return OK;
 
 /*
 Arguments:
 
 /*
 Arguments:
-  ctx            connection context
+  sx             connection context
   suppress_tls    if TRUE, don't attempt a TLS connection - this is set for
                     a second attempt after TLS initialization fails
 
   suppress_tls    if TRUE, don't attempt a TLS connection - this is set for
                     a second attempt after TLS initialization fails
 
@@ -1965,43 +2130,32 @@ int yield = OK;
 uschar * tls_errstr;
 #endif
 
 uschar * tls_errstr;
 #endif
 
+/* Many lines of clearing individual elements of *sx that used to
+be here have been replaced by a full memset to zero (de41aff051).
+There are two callers, this file and verify.c .  Now we only set
+up nonzero elements. */
+
 sx->conn_args.ob = ob;
 
 sx->lmtp = strcmpic(ob->protocol, US"lmtp") == 0;
 sx->smtps = strcmpic(ob->protocol, US"smtps") == 0;
 sx->conn_args.ob = ob;
 
 sx->lmtp = strcmpic(ob->protocol, US"lmtp") == 0;
 sx->smtps = strcmpic(ob->protocol, US"smtps") == 0;
-/* sx->ok = FALSE; */
 sx->send_rset = TRUE;
 sx->send_quit = TRUE;
 sx->setting_up = TRUE;
 sx->esmtp = TRUE;
 sx->send_rset = TRUE;
 sx->send_quit = TRUE;
 sx->setting_up = TRUE;
 sx->esmtp = TRUE;
-/* sx->esmtp_sent = FALSE; */
-#ifdef SUPPORT_I18N
-/* sx->utf8_needed = FALSE; */
-#endif
 sx->dsn_all_lasthop = TRUE;
 #ifdef SUPPORT_DANE
 sx->dsn_all_lasthop = TRUE;
 #ifdef SUPPORT_DANE
-/* sx->conn_args.dane = FALSE; */
 sx->dane_required =
   verify_check_given_host(CUSS &ob->hosts_require_dane, sx->conn_args.host) == OK;
 #endif
 sx->dane_required =
   verify_check_given_host(CUSS &ob->hosts_require_dane, sx->conn_args.host) == OK;
 #endif
-#ifndef DISABLE_PIPE_CONNECT
-/* sx->early_pipe_active = sx->early_pipe_ok = FALSE; */
-/* sx->ehlo_resp.cleartext_features = sx->ehlo_resp.crypted_features = 0; */
-/* sx->pending_BANNER = sx->pending_EHLO = sx->pending_MAIL = FALSE; */
-#endif
 
 
-if ((sx->max_rcpt = sx->conn_args.tblock->max_addresses) == 0) sx->max_rcpt = 999999;
-/* sx->peer_offered = 0; */
-/* sx->avoid_option = 0; */
+if ((sx->max_mail = sx->conn_args.tblock->connection_max_messages) == 0)
+  sx->max_mail = UNLIMITED_ADDRS;
+sx->max_rcpt = expand_max_rcpt(sx->conn_args.tblock->max_addresses);
 sx->igquotstr = US"";
 if (!sx->helo_data) sx->helo_data = ob->helo_data;
 sx->igquotstr = US"";
 if (!sx->helo_data) sx->helo_data = ob->helo_data;
-#ifdef EXPERIMENTAL_DSN_INFO
-/* sx->smtp_greeting = NULL; */
-/* sx->helo_response = NULL; */
-#endif
 
 smtp_command = US"initial connection";
 
 smtp_command = US"initial connection";
-/* sx->buffer[0] = '\0'; */
 
 /* Set up the buffer for reading SMTP response packets. */
 
 
 /* Set up the buffer for reading SMTP response packets. */
 
@@ -2015,9 +2169,6 @@ sx->inblock.ptrend = sx->inbuffer;
 sx->outblock.buffer = sx->outbuffer;
 sx->outblock.buffersize = sizeof(sx->outbuffer);
 sx->outblock.ptr = sx->outbuffer;
 sx->outblock.buffer = sx->outbuffer;
 sx->outblock.buffersize = sizeof(sx->outbuffer);
 sx->outblock.ptr = sx->outbuffer;
-/* sx->outblock.cmd_count = 0; */
-/* sx->outblock.authenticating = FALSE; */
-/* sx->outblock.conn_args = NULL; */
 
 /* Reset the parameters of a TLS session. */
 
 
 /* Reset the parameters of a TLS session. */
 
@@ -2030,7 +2181,7 @@ tls_out.peerdn = NULL;
 tls_out.sni = NULL;
 #endif
 tls_out.ocsp = OCSP_NOT_REQ;
 tls_out.sni = NULL;
 #endif
 tls_out.ocsp = OCSP_NOT_REQ;
-#ifdef EXPERIMENTAL_TLS_RESUME
+#ifndef DISABLE_TLS_RESUME
 tls_out.resumption = 0;
 #endif
 tls_out.ver = NULL;
 tls_out.resumption = 0;
 #endif
 tls_out.ver = NULL;
@@ -2046,10 +2197,65 @@ tls_modify_variables(&tls_out);
 if (sx->smtps)
   {
   set_errno_nohost(sx->addrlist, ERRNO_TLSFAILURE, US"TLS support not available",
 if (sx->smtps)
   {
   set_errno_nohost(sx->addrlist, ERRNO_TLSFAILURE, US"TLS support not available",
-           DEFER, FALSE);
+           DEFER, FALSE, &sx->delivery_start);
   return ERROR;
   }
   return ERROR;
   }
-#endif
+#else
+
+/* If we have a proxied TLS connection, check usability for this message */
+
+if (continue_hostname && continue_proxy_cipher)
+  {
+  int rc;
+  const uschar * sni = US"";
+
+# ifdef SUPPORT_DANE
+  /* Check if the message will be DANE-verified; if so force TLS and its SNI */
+
+  tls_out.dane_verified = FALSE;
+  smtp_port_for_connect(sx->conn_args.host, sx->port);
+  if (  sx->conn_args.host->dnssec == DS_YES
+     && (rc = check_force_dane_conn(sx, ob)) != OK
+     )
+    return rc;
+# endif
+
+  /* If the SNI or the DANE status required for the new message differs from the
+  existing conn drop the connection to force a new one. */
+
+  if (ob->tls_sni && !(sni = expand_cstring(ob->tls_sni)))
+    log_write(0, LOG_MAIN|LOG_PANIC,
+      "<%s>: failed to expand transport's tls_sni value: %s",
+      sx->addrlist->address, expand_string_message);
+
+# ifdef SUPPORT_DANE
+  if (  (continue_proxy_sni ? (Ustrcmp(continue_proxy_sni, sni) == 0) : !*sni)
+     && continue_proxy_dane == sx->conn_args.dane)
+    {
+    tls_out.sni = US sni;
+    if ((tls_out.dane_verified = continue_proxy_dane))
+      sx->conn_args.host->dnssec = DS_YES;
+    }
+# else
+  if ((continue_proxy_sni ? (Ustrcmp(continue_proxy_sni, sni) == 0) : !*sni))
+    tls_out.sni = US sni;
+# endif
+  else
+    {
+    DEBUG(D_transport)
+      debug_printf("Closing proxied-TLS connection due to SNI mismatch\n");
+
+    smtp_debug_cmd(US"QUIT", 0);
+    write(0, "QUIT\r\n", 6);
+    close(0);
+    continue_hostname = continue_proxy_cipher = NULL;
+    f.continue_more = FALSE;
+    continue_sequence = 1;     /* Unfortunately, this process cannot affect success log
+                               which is done by delivery proc.  Would have to pass this
+                               back through reporting pipe. */
+    }
+  }
+#endif /*!DISABLE_TLS*/
 
 /* Make a connection to the host if this isn't a continued delivery, and handle
 the initial interaction and HELO/EHLO/LHLO. Connect timeout errors are handled
 
 /* Make a connection to the host if this isn't a continued delivery, and handle
 the initial interaction and HELO/EHLO/LHLO. Connect timeout errors are handled
@@ -2060,6 +2266,11 @@ if (!continue_hostname)
   if (sx->verify)
     HDEBUG(D_verify) debug_printf("interface=%s port=%d\n", sx->conn_args.interface, sx->port);
 
   if (sx->verify)
     HDEBUG(D_verify) debug_printf("interface=%s port=%d\n", sx->conn_args.interface, sx->port);
 
+  /* Arrange to report to calling process this is a new connection */
+
+  clearflag(sx->first_addr, af_cont_conn);
+  setflag(sx->first_addr, af_new_conn);
+
   /* Get the actual port the connection will use, into sx->conn_args.host */
 
   smtp_port_for_connect(sx->conn_args.host, sx->port);
   /* Get the actual port the connection will use, into sx->conn_args.host */
 
   smtp_port_for_connect(sx->conn_args.host, sx->port);
@@ -2073,35 +2284,17 @@ if (!continue_hostname)
     if (sx->conn_args.host->dnssec == DS_YES)
       {
       int rc;
     if (sx->conn_args.host->dnssec == DS_YES)
       {
       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))
-         {
-         case OK:              sx->conn_args.dane = TRUE;
-                               ob->tls_tempfail_tryclear = FALSE;
-                               break;
-         case FAIL_FORCED:     break;
-         default:              set_errno_nohost(sx->addrlist, ERRNO_DNSDEFER,
-                                 string_sprintf("DANE error: tlsa lookup %s",
-                                   rc_to_string(rc)),
-                                 rc, FALSE);
-# ifndef DISABLE_EVENT
-                               (void) event_raise(sx->conn_args.tblock->event_action,
-                                 US"dane:fail", sx->dane_required
-                                   ?  US"dane-required" : US"dnssec-invalid");
-# endif
-                               return rc;
-         }
+      if ((rc = check_force_dane_conn(sx, ob)) != OK)
+       return rc;
       }
     else if (sx->dane_required)
       {
       set_errno_nohost(sx->addrlist, ERRNO_DNSDEFER,
        string_sprintf("DANE error: %s lookup not DNSSEC", sx->conn_args.host->name),
       }
     else if (sx->dane_required)
       {
       set_errno_nohost(sx->addrlist, ERRNO_DNSDEFER,
        string_sprintf("DANE error: %s lookup not DNSSEC", sx->conn_args.host->name),
-       FAIL, FALSE);
+       FAIL, FALSE, &sx->delivery_start);
 # ifndef DISABLE_EVENT
       (void) event_raise(sx->conn_args.tblock->event_action,
 # ifndef DISABLE_EVENT
       (void) event_raise(sx->conn_args.tblock->event_action,
-       US"dane:fail", US"dane-required");
+       US"dane:fail", US"dane-required", NULL);
 # endif
       return FAIL;
       }
 # endif
       return FAIL;
       }
@@ -2112,7 +2305,13 @@ if (!continue_hostname)
 
   sx->cctx.tls_ctx = NULL;
   sx->inblock.cctx = sx->outblock.cctx = &sx->cctx;
 
   sx->cctx.tls_ctx = NULL;
   sx->inblock.cctx = sx->outblock.cctx = &sx->cctx;
+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+  sx->peer_limit_mail = sx->peer_limit_rcpt = sx->peer_limit_rcptdom =
+#endif
   sx->avoid_option = sx->peer_offered = smtp_peer_options = 0;
   sx->avoid_option = sx->peer_offered = smtp_peer_options = 0;
+#ifndef DISABLE_CLIENT_CMD_LOG
+  client_cmd_log = NULL;
+#endif
 
 #ifndef DISABLE_PIPE_CONNECT
   if (  verify_check_given_host(CUSS &ob->hosts_pipe_connect,
 
 #ifndef DISABLE_PIPE_CONNECT
   if (  verify_check_given_host(CUSS &ob->hosts_pipe_connect,
@@ -2122,6 +2321,7 @@ if (!continue_hostname)
     the helo string might use it avoid doing early-pipelining. */
 
     if (  !sx->helo_data
     the helo string might use it avoid doing early-pipelining. */
 
     if (  !sx->helo_data
+       || sx->conn_args.interface
        || !Ustrstr(sx->helo_data, "$sending_ip_address")
        || Ustrstr(sx->helo_data, "def:sending_ip_address")
        )
        || !Ustrstr(sx->helo_data, "$sending_ip_address")
        || Ustrstr(sx->helo_data, "def:sending_ip_address")
        )
@@ -2131,34 +2331,48 @@ if (!continue_hostname)
         && sx->ehlo_resp.cleartext_features & OPTION_EARLY_PIPE)
        {
        DEBUG(D_transport)
         && sx->ehlo_resp.cleartext_features & OPTION_EARLY_PIPE)
        {
        DEBUG(D_transport)
-         debug_printf("Using cached cleartext PIPE_CONNECT\n");
+         debug_printf("Using cached cleartext PIPECONNECT\n");
        sx->early_pipe_active = TRUE;
        sx->peer_offered = sx->ehlo_resp.cleartext_features;
        }
       }
     else DEBUG(D_transport)
        sx->early_pipe_active = TRUE;
        sx->peer_offered = sx->ehlo_resp.cleartext_features;
        }
       }
     else DEBUG(D_transport)
-      debug_printf("helo needs $sending_ip_address\n");
+      debug_printf("helo needs $sending_ip_address; avoid early-pipelining\n");
 
 
+PIPE_CONNECT_RETRY:
   if (sx->early_pipe_active)
   if (sx->early_pipe_active)
+    {
     sx->outblock.conn_args = &sx->conn_args;
     sx->outblock.conn_args = &sx->conn_args;
+    (void) smtp_boundsock(&sx->conn_args);
+    }
   else
 #endif
     {
   else
 #endif
     {
-    if ((sx->cctx.sock = smtp_connect(&sx->conn_args, NULL)) < 0)
+    blob lazy_conn = {.data = NULL};
+    /* For TLS-connect, a TFO lazy-connect is useful since the Client Hello
+    can go on the TCP SYN. */
+
+    if ((sx->cctx.sock = smtp_connect(&sx->conn_args,
+                           sx->smtps ? &lazy_conn : NULL)) < 0)
       {
       set_errno_nohost(sx->addrlist,
        errno == ETIMEDOUT ? ERRNO_CONNECTTIMEOUT : errno,
        sx->verify ? US strerror(errno) : NULL,
       {
       set_errno_nohost(sx->addrlist,
        errno == ETIMEDOUT ? ERRNO_CONNECTTIMEOUT : errno,
        sx->verify ? US strerror(errno) : NULL,
-       DEFER, FALSE);
+       DEFER, FALSE, &sx->delivery_start);
       sx->send_quit = FALSE;
       return DEFER;
       }
       sx->send_quit = FALSE;
       return DEFER;
       }
+#ifdef TCP_QUICKACK
+    (void) setsockopt(sx->cctx.sock, IPPROTO_TCP, TCP_QUICKACK, US &off,
+                       sizeof(off));
+#endif
     }
   /* Expand the greeting message while waiting for the initial response. (Makes
   sense if helo_data contains ${lookup dnsdb ...} stuff). The expansion is
     }
   /* Expand the greeting message while waiting for the initial response. (Makes
   sense if helo_data contains ${lookup dnsdb ...} stuff). The expansion is
-  delayed till here so that $sending_interface and $sending_port are set. */
-/*XXX early-pipe: they still will not be. Is there any way to find out what they
-will be?  Somehow I doubt it. */
+  delayed till here so that $sending_ip_address and $sending_port are set.
+  Those will be known even for a TFO lazy-connect, having been set by the bind().
+  For early-pipe, we are ok if binding to a local interface; otherwise (if
+  $sending_ip_address is seen in helo_data) we disabled early-pipe above. */
 
   if (sx->helo_data)
     if (!(sx->helo_data = expand_string(sx->helo_data)))
 
   if (sx->helo_data)
     if (!(sx->helo_data = expand_string(sx->helo_data)))
@@ -2198,10 +2412,6 @@ will be?  Somehow I doubt it. */
     else
 #endif
       {
     else
 #endif
       {
-#ifdef TCP_QUICKACK
-      (void) setsockopt(sx->cctx.sock, IPPROTO_TCP, TCP_QUICKACK, US &off,
-                       sizeof(off));
-#endif
       if (!smtp_reap_banner(sx))
        goto RESPONSE_FAILED;
       }
       if (!smtp_reap_banner(sx))
        goto RESPONSE_FAILED;
       }
@@ -2211,12 +2421,12 @@ will be?  Somehow I doubt it. */
       uschar * s;
       lookup_dnssec_authenticated = sx->conn_args.host->dnssec==DS_YES ? US"yes"
        : sx->conn_args.host->dnssec==DS_NO ? US"no" : NULL;
       uschar * s;
       lookup_dnssec_authenticated = sx->conn_args.host->dnssec==DS_YES ? US"yes"
        : sx->conn_args.host->dnssec==DS_NO ? US"no" : NULL;
-      s = event_raise(sx->conn_args.tblock->event_action, US"smtp:connect", sx->buffer);
+      s = event_raise(sx->conn_args.tblock->event_action, US"smtp:connect", sx->buffer, NULL);
       if (s)
        {
        set_errno_nohost(sx->addrlist, ERRNO_EXPANDFAIL,
          string_sprintf("deferred by smtp:connect event expansion: %s", s),
       if (s)
        {
        set_errno_nohost(sx->addrlist, ERRNO_EXPANDFAIL,
          string_sprintf("deferred by smtp:connect event expansion: %s", s),
-         DEFER, FALSE);
+         DEFER, FALSE, &sx->delivery_start);
        yield = DEFER;
        goto SEND_QUIT;
        }
        yield = DEFER;
        goto SEND_QUIT;
        }
@@ -2230,7 +2440,7 @@ will be?  Somehow I doubt it. */
       {
       message = string_sprintf("failed to expand helo_data: %s",
         expand_string_message);
       {
       message = string_sprintf("failed to expand helo_data: %s",
         expand_string_message);
-      set_errno_nohost(sx->addrlist, ERRNO_EXPANDFAIL, message, DEFER, FALSE);
+      set_errno_nohost(sx->addrlist, ERRNO_EXPANDFAIL, message, DEFER, FALSE, &sx->delivery_start);
       yield = DEFER;
       goto SEND_QUIT;
       }
       yield = DEFER;
       goto SEND_QUIT;
       }
@@ -2279,10 +2489,21 @@ goto SEND_QUIT;
 #ifndef DISABLE_TLS
   if (sx->smtps)
     {
 #ifndef DISABLE_TLS
   if (sx->smtps)
     {
+    const uschar * s;
+
     smtp_peer_options |= OPTION_TLS;
     suppress_tls = FALSE;
     ob->tls_tempfail_tryclear = FALSE;
     smtp_command = US"SSL-on-connect";
     smtp_peer_options |= OPTION_TLS;
     suppress_tls = FALSE;
     ob->tls_tempfail_tryclear = FALSE;
     smtp_command = US"SSL-on-connect";
+
+# ifndef DISABLE_TLS_RESUME
+    /* Having no EHLO response yet, cannot peek there for a servername to detect
+    an LB.  Call this anyway, so that a dummy host_name_extract option value can
+    force resumption attempts. */
+
+    if (!(s = ob->host_name_extract)) s = US"never-LB";
+    ehlo_response_lbserver(sx, s);
+# endif
     goto TLS_NEGOTIATE;
     }
 #endif
     goto TLS_NEGOTIATE;
     }
 #endif
@@ -2313,7 +2534,7 @@ goto SEND_QUIT;
        DEBUG(D_transport) debug_printf("may need to auth, so pipeline no further\n");
        if (smtp_write_command(sx, SCMD_FLUSH, NULL) < 0)
          goto SEND_FAILED;
        DEBUG(D_transport) debug_printf("may need to auth, so pipeline no further\n");
        if (smtp_write_command(sx, SCMD_FLUSH, NULL) < 0)
          goto SEND_FAILED;
-       if (sync_responses(sx, 2, 0) != 0)
+       if (sync_responses(sx, 2, 0) != RESP_NOERROR)
          {
          HDEBUG(D_transport)
            debug_printf("failed reaping pipelined cmd responses\n");
          {
          HDEBUG(D_transport)
            debug_printf("failed reaping pipelined cmd responses\n");
@@ -2340,8 +2561,8 @@ goto SEND_QUIT;
       int n = sizeof(sx->buffer);
       uschar * rsp = sx->buffer;
 
       int n = sizeof(sx->buffer);
       uschar * rsp = sx->buffer;
 
-      if (sx->esmtp_sent && (n = Ustrlen(sx->buffer)) < sizeof(sx->buffer)/2)
-       { rsp = sx->buffer + n + 1; n = sizeof(sx->buffer) - n; }
+      if (sx->esmtp_sent && (n = Ustrlen(sx->buffer) + 1) < sizeof(sx->buffer)/2)
+       { rsp = sx->buffer + n; n = sizeof(sx->buffer) - n; }
 
       if (smtp_write_command(sx, SCMD_FLUSH, "HELO %s\r\n", sx->helo_data) < 0)
        goto SEND_FAILED;
 
       if (smtp_write_command(sx, SCMD_FLUSH, "HELO %s\r\n", sx->helo_data) < 0)
        goto SEND_FAILED;
@@ -2370,6 +2591,8 @@ goto SEND_QUIT;
     if (!sx->early_pipe_active)
 #endif
       {
     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
       sx->peer_offered = ehlo_response(sx->buffer,
        OPTION_TLS      /* others checked later */
 #ifndef DISABLE_PIPE_CONNECT
@@ -2384,6 +2607,13 @@ goto SEND_QUIT;
          )
 #endif
        );
          )
 #endif
        );
+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+      if (tls_out.active.sock >= 0 || !(sx->peer_offered & OPTION_TLS))
+       {
+       ehlo_response_limits_read(sx);
+       ehlo_response_limits_apply(sx);
+       }
+#endif
 #ifndef DISABLE_PIPE_CONNECT
       if (sx->early_pipe_ok)
        {
 #ifndef DISABLE_PIPE_CONNECT
       if (sx->early_pipe_ok)
        {
@@ -2392,11 +2622,15 @@ goto SEND_QUIT;
        if (  (sx->peer_offered & (OPTION_PIPE | OPTION_EARLY_PIPE))
           == (OPTION_PIPE | OPTION_EARLY_PIPE))
          {
        if (  (sx->peer_offered & (OPTION_PIPE | OPTION_EARLY_PIPE))
           == (OPTION_PIPE | OPTION_EARLY_PIPE))
          {
-         DEBUG(D_transport) debug_printf("PIPE_CONNECT usable in future for this IP\n");
+         DEBUG(D_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);
          }
        }
          sx->ehlo_resp.cleartext_auths = study_ehlo_auths(sx);
          write_ehlo_cache_entry(sx);
          }
        }
+#endif
+#ifndef DISABLE_TLS_RESUME
+      if (!(s = ob->host_name_extract)) s = HNE_DEFAULT;
+      ehlo_response_lbserver(sx, s);
 #endif
       }
 
 #endif
       }
 
@@ -2437,6 +2671,14 @@ else
     }
   sx->inblock.cctx = sx->outblock.cctx = &sx->cctx;
   smtp_command = big_buffer;
     }
   sx->inblock.cctx = sx->outblock.cctx = &sx->cctx;
   smtp_command = big_buffer;
+  sx->peer_offered = smtp_peer_options;
+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+  /* Limits passed by cmdline over exec. */
+  ehlo_limits_apply(sx,
+                   sx->peer_limit_mail = continue_limit_mail,
+                   sx->peer_limit_rcpt = continue_limit_rcpt,
+                   sx->peer_limit_rcptdom = continue_limit_rcptdom);
+#endif
   sx->helo_data = NULL;                /* ensure we re-expand ob->helo_data */
 
   /* For a continued connection with TLS being proxied for us, or a
   sx->helo_data = NULL;                /* ensure we re-expand ob->helo_data */
 
   /* For a continued connection with TLS being proxied for us, or a
@@ -2447,7 +2689,6 @@ else
          && cutthrough.is_tls)
      )
     {
          && cutthrough.is_tls)
      )
     {
-    sx->peer_offered = smtp_peer_options;
     sx->pipelining_used = pipelining_active = !!(smtp_peer_options & OPTION_PIPE);
     HDEBUG(D_transport) debug_printf("continued connection, %s TLS\n",
       continue_proxy_cipher ? "proxied" : "verify conn with");
     sx->pipelining_used = pipelining_active = !!(smtp_peer_options & OPTION_PIPE);
     HDEBUG(D_transport) debug_printf("continued connection, %s TLS\n",
       continue_proxy_cipher ? "proxied" : "verify conn with");
@@ -2479,13 +2720,20 @@ if (  smtp_peer_options & OPTION_TLS
 
 #ifndef DISABLE_PIPE_CONNECT
   /* If doing early-pipelining reap the banner and EHLO-response but leave
 
 #ifndef DISABLE_PIPE_CONNECT
   /* If doing early-pipelining reap the banner and EHLO-response but leave
-  the response for the STARTTLS we just sent alone. */
+  the response for the STARTTLS we just sent alone.  On fail, assume wrong
+  cached capability and retry with the pipelining disabled. */
 
 
-  if (sx->early_pipe_active && sync_responses(sx, 2, 0) != 0)
+  if (sx->early_pipe_active)
     {
     {
-    HDEBUG(D_transport)
-      debug_printf("failed reaping pipelined cmd responses\n");
-    goto RESPONSE_FAILED;
+    if (sync_responses(sx, 2, 0) != RESP_NOERROR)
+      {
+      HDEBUG(D_transport)
+       debug_printf("failed reaping pipelined cmd responses\n");
+      close(sx->cctx.sock);
+      sx->cctx.sock = -1;
+      sx->early_pipe_active = FALSE;
+      goto PIPE_CONNECT_RETRY;
+      }
     }
 #endif
 
     }
 #endif
 
@@ -2514,14 +2762,13 @@ if (  smtp_peer_options & OPTION_TLS
   else
   TLS_NEGOTIATE:
     {
   else
   TLS_NEGOTIATE:
     {
+    sx->conn_args.sending_ip_address = sending_ip_address;
     if (!tls_client_start(&sx->cctx, &sx->conn_args, sx->addrlist, &tls_out, &tls_errstr))
       {
       /* TLS negotiation failed; give an error. From outside, this function may
       be called again to try in clear on a new connection, if the options permit
       it for this host. */
     if (!tls_client_start(&sx->cctx, &sx->conn_args, sx->addrlist, &tls_out, &tls_errstr))
       {
       /* TLS negotiation failed; give an error. From outside, this function may
       be called again to try in clear on a new connection, if the options permit
       it for this host. */
-#ifdef USE_GNUTLS
-  GNUTLS_CONN_FAILED:
-#endif
+  TLS_CONN_FAILED:
       DEBUG(D_tls) debug_printf("TLS session fail: %s\n", tls_errstr);
 
 # ifdef SUPPORT_DANE
       DEBUG(D_tls) debug_printf("TLS session fail: %s\n", tls_errstr);
 
 # ifdef SUPPORT_DANE
@@ -2532,7 +2779,7 @@ if (  smtp_peer_options & OPTION_TLS
          sx->conn_args.host->name, sx->conn_args.host->address, tls_errstr);
 #  ifndef DISABLE_EVENT
        (void) event_raise(sx->conn_args.tblock->event_action,
          sx->conn_args.host->name, sx->conn_args.host->address, tls_errstr);
 #  ifndef DISABLE_EVENT
        (void) event_raise(sx->conn_args.tblock->event_action,
-         US"dane:fail", US"validation-failure");       /* could do with better detail */
+         US"dane:fail", US"validation-failure", NULL); /* could do with better detail */
 #  endif
        }
 # endif
 #  endif
        }
 # endif
@@ -2542,8 +2789,25 @@ if (  smtp_peer_options & OPTION_TLS
       sx->send_quit = FALSE;
       goto TLS_FAILED;
       }
       sx->send_quit = FALSE;
       goto TLS_FAILED;
       }
+    sx->send_tlsclose = TRUE;
+
+    /* TLS session is set up.  Check the inblock fill level.  If there is
+    content then as we have not yet done a tls read it must have arrived before
+    the TLS handshake, in-clear.  That violates the sync requirement of the
+    STARTTLS RFC, so fail. */
 
 
-    /* TLS session is set up */
+    if (sx->inblock.ptr != sx->inblock.ptrend)
+      {
+      DEBUG(D_tls)
+       {
+       int i = sx->inblock.ptrend - sx->inblock.ptr;
+       debug_printf("unused data in input buffer after ack for STARTTLS:\n"
+         "'%.*s'%s\n",
+         i > 100 ? 100 : i, sx->inblock.ptr, i > 100 ? "..." : "");
+       }
+      tls_errstr = US"synch error before connect";
+      goto TLS_CONN_FAILED;
+      }
 
     smtp_peer_options_wrap = smtp_peer_options;
     for (address_item * addr = sx->addrlist; addr; addr = addr->next)
 
     smtp_peer_options_wrap = smtp_peer_options;
     for (address_item * addr = sx->addrlist; addr; addr = addr->next)
@@ -2577,7 +2841,7 @@ if (tls_out.active.sock >= 0)
     {
     uschar *message = string_sprintf("failed to expand helo_data: %s",
       expand_string_message);
     {
     uschar *message = string_sprintf("failed to expand helo_data: %s",
       expand_string_message);
-    set_errno_nohost(sx->addrlist, ERRNO_EXPANDFAIL, message, DEFER, FALSE);
+    set_errno_nohost(sx->addrlist, ERRNO_EXPANDFAIL, message, DEFER, FALSE, &sx->delivery_start);
     yield = DEFER;
     goto SEND_QUIT;
     }
     yield = DEFER;
     goto SEND_QUIT;
     }
@@ -2594,9 +2858,17 @@ 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)))
     sx->peer_offered = sx->ehlo_resp.crypted_features;
     if ((sx->early_pipe_active =
         !!(sx->ehlo_resp.crypted_features & OPTION_EARLY_PIPE)))
-      DEBUG(D_transport) debug_printf("Using cached crypted PIPE_CONNECT\n");
+      DEBUG(D_transport) debug_printf("Using cached crypted PIPECONNECT\n");
     }
 #endif
     }
 #endif
+#ifdef EXPERIMMENTAL_ESMTP_LIMITS
+  /* As we are about to send another EHLO, forget any LIMITS received so far. */
+  sx->peer_limit_mail = sx->peer_limit_rcpt = sx->peer_limit_rcptdom = 0;
+  if ((sx->max_mail = sx->conn_args.tblock->connection_max_message) == 0)
+    sx->max_mail = UNLIMITED_ADDRS;
+  sx->max_rcpt = expand_max_rcpt(sx->conn_args.tblock->max_addresses);
+  sx->single_rcpt_domain = FALSE;
+#endif
 
   /* For SMTPS we need to wait for the initial OK response. */
   if (sx->smtps)
 
   /* For SMTPS we need to wait for the initial OK response. */
   if (sx->smtps)
@@ -2648,7 +2920,7 @@ if (tls_out.active.sock >= 0)
       Can it do that, with all the flexibility we need? */
 
       tls_errstr = US"error on first read";
       Can it do that, with all the flexibility we need? */
 
       tls_errstr = US"error on first read";
-      goto GNUTLS_CONN_FAILED;
+      goto TLS_CONN_FAILED;
       }
 #else
       goto RESPONSE_FAILED;
       }
 #else
       goto RESPONSE_FAILED;
@@ -2676,7 +2948,8 @@ else if (  sx->smtps
     (void) event_raise(sx->conn_args.tblock->event_action, US"dane:fail",
       smtp_peer_options & OPTION_TLS
       ? US"validation-failure"         /* could do with better detail */
     (void) event_raise(sx->conn_args.tblock->event_action, US"dane:fail",
       smtp_peer_options & OPTION_TLS
       ? US"validation-failure"         /* could do with better detail */
-      : US"starttls-not-supported");
+      : US"starttls-not-supported",
+      NULL);
 # endif
   goto TLS_FAILED;
   }
 # endif
   goto TLS_FAILED;
   }
@@ -2687,7 +2960,7 @@ so its response needs to be analyzed. If TLS is not active and this is a
 continued session down a previously-used socket, we haven't just done EHLO, so
 we skip this. */
 
 continued session down a previously-used socket, we haven't just done EHLO, so
 we skip this. */
 
-if (continue_hostname == NULL
+if (   !continue_hostname
 #ifndef DISABLE_TLS
     || tls_out.active.sock >= 0
 #endif
 #ifndef DISABLE_TLS
     || tls_out.active.sock >= 0
 #endif
@@ -2726,6 +2999,14 @@ if (continue_hostname == NULL
     if (tls_out.active.sock >= 0)
       sx->ehlo_resp.crypted_features = sx->peer_offered;
 #endif
     if (tls_out.active.sock >= 0)
       sx->ehlo_resp.crypted_features = sx->peer_offered;
 #endif
+
+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+    if (tls_out.active.sock >= 0 || !(sx->peer_offered & OPTION_TLS))
+      {
+      ehlo_response_limits_read(sx);
+      ehlo_response_limits_apply(sx);
+      }
+#endif
     }
 
     /* Set for IGNOREQUOTA if the response to LHLO specifies support and the
     }
 
     /* Set for IGNOREQUOTA if the response to LHLO specifies support and the
@@ -2750,18 +3031,18 @@ if (continue_hostname == NULL
       smtp_peer_options & OPTION_PIPE ? "" : "not ");
 
     if (  sx->peer_offered & OPTION_CHUNKING
       smtp_peer_options & OPTION_PIPE ? "" : "not ");
 
     if (  sx->peer_offered & OPTION_CHUNKING
-       && verify_check_given_host(CUSS &ob->hosts_try_chunking, sx->conn_args.host) != OK)
-      sx->peer_offered &= ~OPTION_CHUNKING;
+       && verify_check_given_host(CUSS &ob->hosts_try_chunking, sx->conn_args.host) == OK)
+      smtp_peer_options |= OPTION_CHUNKING;
 
 
-    if (sx->peer_offered & OPTION_CHUNKING)
+    if (smtp_peer_options & OPTION_CHUNKING)
       DEBUG(D_transport) debug_printf("CHUNKING usable\n");
 
 #ifndef DISABLE_PRDR
     if (  sx->peer_offered & OPTION_PRDR
       DEBUG(D_transport) debug_printf("CHUNKING usable\n");
 
 #ifndef DISABLE_PRDR
     if (  sx->peer_offered & OPTION_PRDR
-       && verify_check_given_host(CUSS &ob->hosts_try_prdr, sx->conn_args.host) != OK)
-      sx->peer_offered &= ~OPTION_PRDR;
+       && verify_check_given_host(CUSS &ob->hosts_try_prdr, sx->conn_args.host) == OK)
+      smtp_peer_options |= OPTION_PRDR;
 
 
-    if (sx->peer_offered & OPTION_PRDR)
+    if (smtp_peer_options & OPTION_PRDR)
       DEBUG(D_transport) debug_printf("PRDR usable\n");
 #endif
 
       DEBUG(D_transport) debug_printf("PRDR usable\n");
 #endif
 
@@ -2778,7 +3059,7 @@ if (continue_hostname == NULL
        && ( sx->ehlo_resp.cleartext_features | sx->ehlo_resp.crypted_features)
          & OPTION_EARLY_PIPE)
       {
        && ( sx->ehlo_resp.cleartext_features | sx->ehlo_resp.crypted_features)
          & OPTION_EARLY_PIPE)
       {
-      DEBUG(D_transport) debug_printf("PIPE_CONNECT usable in future for this IP\n");
+      DEBUG(D_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);
       }
       sx->ehlo_resp.crypted_auths = study_ehlo_auths(sx);
       write_ehlo_cache_entry(sx);
       }
@@ -2819,7 +3100,7 @@ if (sx->addrlist->prop.utf8_msg)
       {
       message = string_sprintf("failed to expand utf8_downconvert: %s",
         expand_string_message);
       {
       message = string_sprintf("failed to expand utf8_downconvert: %s",
         expand_string_message);
-      set_errno_nohost(sx->addrlist, ERRNO_EXPANDFAIL, message, DEFER, FALSE);
+      set_errno_nohost(sx->addrlist, ERRNO_EXPANDFAIL, message, DEFER, FALSE, &sx->delivery_start);
       yield = DEFER;
       goto SEND_QUIT;
       }
       yield = DEFER;
       goto SEND_QUIT;
       }
@@ -2879,7 +3160,7 @@ return OK;
       set_errno_nohost(sx->addrlist,
        errno == ETIMEDOUT ? ERRNO_CONNECTTIMEOUT : errno,
        sx->verify ? US strerror(errno) : NULL,
       set_errno_nohost(sx->addrlist,
        errno == ETIMEDOUT ? ERRNO_CONNECTTIMEOUT : errno,
        sx->verify ? US strerror(errno) : NULL,
-       DEFER, FALSE);
+       DEFER, FALSE, &sx->delivery_start);
       sx->send_quit = FALSE;
       return DEFER;
       }
       sx->send_quit = FALSE;
       return DEFER;
       }
@@ -2893,7 +3174,7 @@ return OK;
 
   SEND_FAILED:
     code = '4';
 
   SEND_FAILED:
     code = '4';
-    message = US string_sprintf("send() to %s [%s] failed: %s",
+    message = US string_sprintf("smtp send to %s [%s] failed: %s",
       sx->conn_args.host->name, sx->conn_args.host->address, strerror(errno));
     sx->send_quit = FALSE;
     yield = DEFER;
       sx->conn_args.host->name, sx->conn_args.host->address, strerror(errno));
     sx->send_quit = FALSE;
     yield = DEFER;
@@ -2943,11 +3224,11 @@ FAILED:
 #endif
            ? FAIL : DEFER,
            pass_message,
 #endif
            ? FAIL : DEFER,
            pass_message,
-           errno == ECONNREFUSED ? NULL : sx->conn_args.host
+           errno == ECONNREFUSED ? NULL : sx->conn_args.host,
 #ifdef EXPERIMENTAL_DSN_INFO
 #ifdef EXPERIMENTAL_DSN_INFO
-           , sx->smtp_greeting, sx->helo_response
+           sx->smtp_greeting, sx->helo_response,
 #endif
 #endif
-           );
+           &sx->delivery_start);
   }
 
 
   }
 
 
@@ -2959,7 +3240,11 @@ if (sx->send_quit)
 #ifndef DISABLE_TLS
 if (sx->cctx.tls_ctx)
   {
 #ifndef DISABLE_TLS
 if (sx->cctx.tls_ctx)
   {
-  tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT);
+  if (sx->send_tlsclose)
+    {
+    tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT);
+    sx->send_tlsclose = FALSE;
+    }
   sx->cctx.tls_ctx = NULL;
   }
 #endif
   sx->cctx.tls_ctx = NULL;
   }
 #endif
@@ -2982,9 +3267,10 @@ if (sx->send_quit)
 sx->cctx.sock = -1;
 
 #ifndef DISABLE_EVENT
 sx->cctx.sock = -1;
 
 #ifndef DISABLE_EVENT
-(void) event_raise(sx->conn_args.tblock->event_action, US"tcp:close", NULL);
+(void) event_raise(sx->conn_args.tblock->event_action, US"tcp:close", NULL, NULL);
 #endif
 
 #endif
 
+smtp_debug_cmd_report();
 continue_transport = NULL;
 continue_hostname = NULL;
 return yield;
 continue_transport = NULL;
 continue_hostname = NULL;
 return yield;
@@ -3027,7 +3313,7 @@ Or just forget about lines?  Or inflate by a fixed proportion? */
 request that */
 
 sx->prdr_active = FALSE;
 request that */
 
 sx->prdr_active = FALSE;
-if (sx->peer_offered & OPTION_PRDR)
+if (smtp_peer_options & OPTION_PRDR)
   for (address_item * addr = addrlist; addr; addr = addr->next)
     if (addr->transport_return == PENDING_DEFER)
       {
   for (address_item * addr = addrlist; addr; addr = addr->next)
     if (addr->transport_return == PENDING_DEFER)
       {
@@ -3055,7 +3341,7 @@ if (  sx->peer_offered & OPTION_UTF8
 
 /* check if all addresses have DSN-lasthop flag; do not send RET and ENVID if so */
 for (sx->dsn_all_lasthop = TRUE, addr = addrlist, address_count = 0;
 
 /* check if all addresses have DSN-lasthop flag; do not send RET and ENVID if so */
 for (sx->dsn_all_lasthop = TRUE, addr = addrlist, address_count = 0;
-     addr && address_count < sx->max_rcpt;
+     addr && address_count < sx->max_rcpt;     /*XXX maybe also || sx->single_rcpt_domain ? */
      addr = addr->next) if (addr->transport_return == PENDING_DEFER)
   {
   address_count++;
      addr = addr->next) if (addr->transport_return == PENDING_DEFER)
   {
   address_count++;
@@ -3089,10 +3375,7 @@ Other expansion failures are serious. An empty result is ignored, but there is
 otherwise no check - this feature is expected to be used with LMTP and other
 cases where non-standard addresses (e.g. without domains) might be required. */
 
 otherwise no check - this feature is expected to be used with LMTP and other
 cases where non-standard addresses (e.g. without domains) might be required. */
 
-if (smtp_mail_auth_str(p, sizeof(sx->buffer) - (p-sx->buffer), addrlist, sx->conn_args.ob))
-  return ERROR;
-
-return OK;
+return smtp_mail_auth_str(sx, p, addrlist) ? ERROR : OK;
 }
 
 
 }
 
 
@@ -3132,27 +3415,24 @@ if (sx->peer_offered & OPTION_DSN && !(addr->dsn_flags & rf_dsnlasthop))
 
 
 
 
 
 
-/*
-Return:
- 0     good, rcpt results in addr->transport_return (PENDING_OK, DEFER, FAIL)
- -1    MAIL response error
- -2    any non-MAIL read i/o error
- -3    non-MAIL response timeout
- -4    internal error; channel still usable
- -5    transmit failed
+/* Send MAIL FROM and RCPT TO commands.
+See sw_mrc_t definition for return codes.
  */
 
  */
 
-int
+sw_mrc_t
 smtp_write_mail_and_rcpt_cmds(smtp_context * sx, int * yield)
 {
 address_item * addr;
 smtp_write_mail_and_rcpt_cmds(smtp_context * sx, int * yield)
 {
 address_item * addr;
+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+address_item * restart_addr = NULL;
+#endif
 int address_count, pipe_limit;
 int rc;
 
 if (build_mailcmd_options(sx, sx->first_addr) != OK)
   {
   *yield = ERROR;
 int address_count, pipe_limit;
 int rc;
 
 if (build_mailcmd_options(sx, sx->first_addr) != OK)
   {
   *yield = ERROR;
-  return -4;
+  return sw_mrc_bad_internal;
   }
 
 /* From here until we send the DATA command, we can make use of PIPELINING
   }
 
 /* From here until we send the DATA command, we can make use of PIPELINING
@@ -3164,7 +3444,7 @@ buffer. */
 sx->pending_MAIL = TRUE;     /* The block starts with MAIL */
 
   {
 sx->pending_MAIL = TRUE;     /* The block starts with MAIL */
 
   {
-  uschar * s = sx->from_addr;
+  const uschar * s = sx->from_addr;
 #ifdef SUPPORT_I18N
   uschar * errstr = NULL;
 
 #ifdef SUPPORT_I18N
   uschar * errstr = NULL;
 
@@ -3178,9 +3458,9 @@ sx->pending_MAIL = TRUE;     /* The block starts with MAIL */
     {
     if (s = string_address_utf8_to_alabel(s, &errstr), errstr)
       {
     {
     if (s = string_address_utf8_to_alabel(s, &errstr), errstr)
       {
-      set_errno_nohost(sx->addrlist, ERRNO_EXPANDFAIL, errstr, DEFER, FALSE);
+      set_errno_nohost(sx->addrlist, ERRNO_EXPANDFAIL, errstr, DEFER, FALSE, &sx->delivery_start);
       *yield = ERROR;
       *yield = ERROR;
-      return -4;
+      return sw_mrc_bad_internal;
       }
     setflag(sx->addrlist, af_utf8_downcvt);
     }
       }
     setflag(sx->addrlist, af_utf8_downcvt);
     }
@@ -3195,7 +3475,7 @@ mail_command = string_copy(big_buffer);  /* Save for later error message */
 switch(rc)
   {
   case -1:                /* Transmission error */
 switch(rc)
   {
   case -1:                /* Transmission error */
-    return -5;
+    return sw_mrc_bad_mail;
 
   case +1:                /* Cmd was sent */
     if (!smtp_read_response(sx, sx->buffer, sizeof(sx->buffer), '2',
 
   case +1:                /* Cmd was sent */
     if (!smtp_read_response(sx, sx->buffer, sizeof(sx->buffer), '2',
@@ -3206,7 +3486,7 @@ switch(rc)
        errno = ERRNO_MAIL4XX;
        sx->addrlist->more_errno |= ((sx->buffer[1] - '0')*10 + sx->buffer[2] - '0') << 8;
        }
        errno = ERRNO_MAIL4XX;
        sx->addrlist->more_errno |= ((sx->buffer[1] - '0')*10 + sx->buffer[2] - '0') << 8;
        }
-      return -1;
+      return sw_mrc_bad_mail;
       }
     sx->pending_MAIL = FALSE;
     break;
       }
     sx->pending_MAIL = FALSE;
     break;
@@ -3230,12 +3510,30 @@ that max_rcpt will be large, so all addresses will be done at once.
 For verify we flush the pipeline after any (the only) rcpt address. */
 
 for (addr = sx->first_addr, address_count = 0, pipe_limit = 100;
 For verify we flush the pipeline after any (the only) rcpt address. */
 
 for (addr = sx->first_addr, address_count = 0, pipe_limit = 100;
-     addr  &&  address_count < sx->max_rcpt;
+     addr &&  address_count < sx->max_rcpt;
      addr = addr->next) if (addr->transport_return == PENDING_DEFER)
   {
   int cmds_sent;
   BOOL no_flush;
      addr = addr->next) if (addr->transport_return == PENDING_DEFER)
   {
   int cmds_sent;
   BOOL no_flush;
-  uschar * rcpt_addr;
+  const uschar * rcpt_addr;
+
+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+  if (  sx->single_rcpt_domain                                 /* restriction on domains */
+     && address_count > 0                                      /* not first being sent */
+     && Ustrcmp(addr->domain, sx->first_addr->domain) != 0     /* dom diff from first */
+     )
+    {
+    DEBUG(D_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
+    goto SEND_MESSAGE.  */
+
+    addr->transport_return = SKIP;
+    if (!restart_addr) restart_addr = addr;    /* note restart point */
+    continue;                                  /* skip this one */
+    }
+#endif
 
   addr->dsn_aware = sx->peer_offered & OPTION_DSN
     ? dsn_support_yes : dsn_support_no;
 
   addr->dsn_aware = sx->peer_offered & OPTION_DSN
     ? dsn_support_yes : dsn_support_no;
@@ -3263,26 +3561,26 @@ for (addr = sx->first_addr, address_count = 0, pipe_limit = 100;
     {
     /*XXX could we use a per-address errstr here? Not fail the whole send? */
     errno = ERRNO_EXPANDFAIL;
     {
     /*XXX could we use a per-address errstr here? Not fail the whole send? */
     errno = ERRNO_EXPANDFAIL;
-    return -5;         /*XXX too harsh? */
+    return sw_mrc_tx_fail;             /*XXX too harsh? */
     }
 #endif
 
   cmds_sent = smtp_write_command(sx, no_flush ? SCMD_BUFFER : SCMD_FLUSH,
     "RCPT TO:<%s>%s%s\r\n", rcpt_addr, sx->igquotstr, sx->buffer);
 
     }
 #endif
 
   cmds_sent = smtp_write_command(sx, no_flush ? SCMD_BUFFER : SCMD_FLUSH,
     "RCPT TO:<%s>%s%s\r\n", rcpt_addr, sx->igquotstr, sx->buffer);
 
-  if (cmds_sent < 0) return -5;
+  if (cmds_sent < 0) return sw_mrc_tx_fail;
   if (cmds_sent > 0)
     {
     switch(sync_responses(sx, cmds_sent, 0))
       {
   if (cmds_sent > 0)
     {
     switch(sync_responses(sx, cmds_sent, 0))
       {
-      case 3: sx->ok = TRUE;                   /* 2xx & 5xx => OK & progress made */
-      case 2: sx->completed_addr = TRUE;       /* 5xx (only) => progress made */
+      case RESP_HAD_2_AND_5: sx->ok = TRUE;    /* OK & progress made */
+      case RESP_BIT_HAD_5XX: sx->completed_addr = TRUE;        /* progress made */
              break;
 
              break;
 
-      case 1: sx->ok = TRUE;                   /* 2xx (only) => OK, but if LMTP, */
+      case RESP_BIT_HAD_2XX: sx->ok = TRUE;    /* OK, but if LMTP, */
              if (!sx->lmtp)                    /*  can't tell about progress yet */
                sx->completed_addr = TRUE;
              if (!sx->lmtp)                    /*  can't tell about progress yet */
                sx->completed_addr = TRUE;
-      case 0:                                  /* No 2xx or 5xx, but no probs */
+      case RESP_NOERROR:                       /* No 2xx or 5xx, but no probs */
              /* If any RCPT got a 452 response then next_addr has been updated
              for restarting with a new MAIL on the same connection.  Send no more
              RCPTs for this MAIL. */
              /* If any RCPT got a 452 response then next_addr has been updated
              for restarting with a new MAIL on the same connection.  Send no more
              RCPTs for this MAIL. */
@@ -3292,24 +3590,28 @@ for (addr = sx->first_addr, address_count = 0, pipe_limit = 100;
                DEBUG(D_transport) debug_printf("seen 452 too-many-rcpts\n");
                sx->RCPT_452 = FALSE;
                /* sx->next_addr has been reset for fast_retry */
                DEBUG(D_transport) debug_printf("seen 452 too-many-rcpts\n");
                sx->RCPT_452 = FALSE;
                /* sx->next_addr has been reset for fast_retry */
-               return 0;
+               return sw_mrc_ok;
                }
              break;
 
                }
              break;
 
-      case -1: return -3;                      /* Timeout on RCPT */
-      case -2: return -2;                      /* non-MAIL read i/o error */
-      default: return -1;                      /* any MAIL error */
+      case RESP_RCPT_TIMEO:        return sw_mrc_nonmail_read_timeo;
+      case RESP_RCPT_ERROR:        return sw_mrc_bad_read;
+      default:                     return sw_mrc_bad_mail;     /* any MAIL error */
 
 #ifndef DISABLE_PIPE_CONNECT
 
 #ifndef DISABLE_PIPE_CONNECT
-      case -4: return -1;                      /* non-2xx for pipelined banner or EHLO */
-      case -5: return -1;                      /* TLS first-read error */
+      case RESP_EPIPE_EHLO_ERR:            return sw_mrc_bad_mail;     /* non-2xx for pipelined banner or EHLO */
+      case RESP_EHLO_ERR_TLS:      return sw_mrc_bad_mail;     /* TLS first-read error */
 #endif
       }
     }
   }      /* Loop for next address */
 
 #endif
       }
     }
   }      /* Loop for next address */
 
+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+sx->next_addr = restart_addr ? restart_addr : addr;
+#else
 sx->next_addr = addr;
 sx->next_addr = addr;
-return 0;
+#endif
+return sw_mrc_ok;
 }
 
 
 }
 
 
@@ -3330,44 +3632,35 @@ Arguments:
   bufsiz       size of buffer
   pfd          pipe filedescriptor array; [0] is comms to proxied process
   timeout      per-read timeout, seconds
   bufsiz       size of buffer
   pfd          pipe filedescriptor array; [0] is comms to proxied process
   timeout      per-read timeout, seconds
+  host         hostname of remote
 
 Does not return.
 */
 
 void
 smtp_proxy_tls(void * ct_ctx, uschar * buf, size_t bsize, int * pfd,
 
 Does not return.
 */
 
 void
 smtp_proxy_tls(void * ct_ctx, uschar * buf, size_t bsize, int * pfd,
-  int timeout)
+  int timeout, const uschar * host)
 {
 {
-fd_set rfds, efds;
-int max_fd = MAX(pfd[0], tls_out.active.sock) + 1;
+struct pollfd p[2] = {{.fd = tls_out.active.sock, .events = POLLIN},
+                     {.fd = pfd[0], .events = POLLIN}};
 int rc, i;
 int rc, i;
+BOOL send_tls_shutdown = TRUE;
 
 close(pfd[1]);
 
 close(pfd[1]);
-if ((rc = fork()))
-  {
-  DEBUG(D_transport) debug_printf("proxy-proc final-pid %d\n", rc);
+if ((rc = exim_fork(US"tls-proxy")))
   _exit(rc < 0 ? EXIT_FAILURE : EXIT_SUCCESS);
   _exit(rc < 0 ? EXIT_FAILURE : EXIT_SUCCESS);
-  }
 
 
-testharness_pause_ms(100); /* let parent debug out */
-set_process_info("proxying TLS connection for continued transport");
-FD_ZERO(&rfds);
-FD_SET(tls_out.active.sock, &rfds);
-FD_SET(pfd[0], &rfds);
+set_process_info("proxying TLS connection for continued transport to %s\n", host);
 
 
-for (int fd_bits = 3; fd_bits; )
+do
   {
   time_t time_left = timeout;
   time_t time_start = time(NULL);
 
   /* wait for data */
   {
   time_t time_left = timeout;
   time_t time_start = time(NULL);
 
   /* wait for data */
-  efds = rfds;
   do
     {
   do
     {
-    struct timeval tv = { time_left, 0 };
-
-    rc = select(max_fd,
-      (SELECT_ARG2_TYPE *)&rfds, NULL, (SELECT_ARG2_TYPE *)&efds, &tv);
+    rc = poll(p, 2, time_left * 1000);
 
     if (rc < 0 && errno == EINTR)
       if ((time_left -= time(NULL) - time_start) > 0) continue;
 
     if (rc < 0 && errno == EINTR)
       if ((time_left -= time(NULL) - time_start) > 0) continue;
@@ -3378,53 +3671,58 @@ for (int fd_bits = 3; fd_bits; )
       goto done;
       }
 
       goto done;
       }
 
-    if (FD_ISSET(tls_out.active.sock, &efds) || FD_ISSET(pfd[0], &efds))
+    /* For errors where not readable, bomb out */
+
+    if (p[0].revents & POLLERR || p[1].revents & POLLERR)
       {
       DEBUG(D_transport) debug_printf("select: exceptional cond on %s fd\n",
       {
       DEBUG(D_transport) debug_printf("select: exceptional cond on %s fd\n",
-       FD_ISSET(pfd[0], &efds) ? "proxy" : "tls");
-      goto done;
+       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");
       }
     }
       }
     }
-  while (rc < 0 || !(FD_ISSET(tls_out.active.sock, &rfds) || FD_ISSET(pfd[0], &rfds)));
+  while (rc < 0 || !(p[0].revents & POLLIN || p[1].revents & POLLIN));
 
   /* handle inbound data */
 
   /* handle inbound data */
-  if (FD_ISSET(tls_out.active.sock, &rfds))
-    if ((rc = tls_read(ct_ctx, buf, bsize)) <= 0)
-      {
-      fd_bits &= ~1;
-      FD_CLR(tls_out.active.sock, &rfds);
+  if (p[0].revents & POLLIN)
+    if ((rc = tls_read(ct_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
       shutdown(pfd[0], SHUT_WR);
       timeout = 5;
       }
     else
-      {
       for (int nbytes = 0; rc - nbytes > 0; nbytes += i)
        if ((i = write(pfd[0], buf + nbytes, rc - nbytes)) < 0) goto done;
       for (int nbytes = 0; rc - nbytes > 0; nbytes += i)
        if ((i = write(pfd[0], buf + nbytes, rc - nbytes)) < 0) goto done;
-      }
-  else if (fd_bits & 1)
-    FD_SET(tls_out.active.sock, &rfds);
 
 
-  /* handle outbound data */
-  if (FD_ISSET(pfd[0], &rfds))
+  /* Handle outbound data.  We cannot combine payload and the TLS-close
+  due to the limitations of the (pipe) channel feeding us.  Maybe use a unix-domain
+  socket? */
+  if (p[1].revents & POLLIN)
     if ((rc = read(pfd[0], buf, bsize)) <= 0)
       {
     if ((rc = read(pfd[0], buf, bsize)) <= 0)
       {
-      fd_bits = 0;
-      tls_close(ct_ctx, TLS_SHUTDOWN_NOWAIT);
-      ct_ctx = NULL;
+      p[1].fd = -1;
+
+# 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);
+      send_tls_shutdown = FALSE;
+      shutdown(tls_out.active.sock, SHUT_WR);
       }
     else
       }
     else
-      {
       for (int nbytes = 0; rc - nbytes > 0; nbytes += i)
        if ((i = tls_write(ct_ctx, buf + nbytes, rc - nbytes, FALSE)) < 0)
          goto done;
       for (int nbytes = 0; rc - nbytes > 0; nbytes += i)
        if ((i = tls_write(ct_ctx, buf + nbytes, rc - nbytes, FALSE)) < 0)
          goto done;
-      }
-  else if (fd_bits & 2)
-    FD_SET(pfd[0], &rfds);
   }
   }
+while (p[0].fd >= 0 || p[1].fd >= 0);
 
 done:
 
 done:
+  if (send_tls_shutdown) tls_close(ct_ctx, TLS_SHUTDOWN_NOWAIT);
+  ct_ctx = NULL;
   testharness_pause_ms(100);   /* let logging complete */
   testharness_pause_ms(100);   /* let logging complete */
-  exim_exit(0, US"TLS proxy");
+  exim_exit(EXIT_SUCCESS);
 }
 #endif
 
 }
 #endif
 
@@ -3435,8 +3733,9 @@ done:
 
 /* If continue_hostname is not null, we get here only when continuing to
 deliver down an existing channel. The channel was passed as the standard
 
 /* If continue_hostname is not null, we get here only when continuing to
 deliver down an existing channel. The channel was passed as the standard
-input. TLS is never active on a passed channel; the previous process always
-closes it down before passing the connection on.
+input. TLS is never active on a passed channel; the previous process either
+closes it down before passing the connection on, or inserts a TLS-proxy
+process and passes on a cleartext conection.
 
 Otherwise, we have to make a connection to the remote host, and do the
 initial protocol exchange.
 
 Otherwise, we have to make a connection to the remote host, and do the
 initial protocol exchange.
@@ -3472,6 +3771,9 @@ Returns:          OK    - the connection was made and the delivery attempted;
                           and there was a problem setting it up; OR helo_data
                           or add_headers or authenticated_sender is specified
                           for this transport, and the string failed to expand
                           and there was a problem setting it up; OR helo_data
                           or add_headers or authenticated_sender is specified
                           for this transport, and the string failed to expand
+
+               For all non-OK returns the first addr of the list carries the
+               time taken for the attempt.
 */
 
 static int
 */
 
 static int
@@ -3483,36 +3785,70 @@ smtp_transport_options_block * ob = SOB tblock->options_block;
 int yield = OK;
 int save_errno;
 int rc;
 int yield = OK;
 int save_errno;
 int rc;
-struct timeval start_delivery_time;
 
 
-BOOL pass_message = FALSE;
 uschar *message = NULL;
 uschar new_message_id[MESSAGE_ID_LENGTH + 1];
 uschar *message = NULL;
 uschar new_message_id[MESSAGE_ID_LENGTH + 1];
-smtp_context * sx = store_get(sizeof(*sx), TRUE);      /* tainted, for the data buffers */
+smtp_context * sx = store_get(sizeof(*sx), GET_TAINTED);       /* tainted, for the data buffers */
+BOOL pass_message = FALSE;
+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+BOOL mail_limit = FALSE;
+#endif
+#ifdef SUPPORT_DANE
+BOOL dane_held;
+#endif
+BOOL tcw_done = FALSE, tcw = FALSE;
 
 
-gettimeofday(&start_delivery_time, NULL);
-suppress_tls = suppress_tls;  /* stop compiler warning when no TLS support */
 *message_defer = FALSE;
 
 memset(sx, 0, sizeof(*sx));
 sx->addrlist = addrlist;
 sx->conn_args.host = host;
 *message_defer = FALSE;
 
 memset(sx, 0, sizeof(*sx));
 sx->addrlist = addrlist;
 sx->conn_args.host = host;
-sx->conn_args.host_af = host_af,
+sx->conn_args.host_af = host_af;
 sx->port = defport;
 sx->conn_args.interface = interface;
 sx->helo_data = NULL;
 sx->conn_args.tblock = tblock;
 sx->port = defport;
 sx->conn_args.interface = interface;
 sx->helo_data = NULL;
 sx->conn_args.tblock = tblock;
-/* sx->verify = FALSE; */
+sx->conn_args.sock = -1;
+gettimeofday(&sx->delivery_start, NULL);
 sx->sync_addr = sx->first_addr = addrlist;
 
 sx->sync_addr = sx->first_addr = addrlist;
 
-/* Get the channel set up ready for a message (MAIL FROM being the next
-SMTP command to send */
+REPEAT_CONN:
+#ifdef SUPPORT_DANE
+dane_held = FALSE;
+#endif
+
+/* Get the channel set up ready for a message, MAIL FROM being the next
+SMTP command to send. */
 
 if ((rc = smtp_setup_conn(sx, suppress_tls)) != OK)
 
 if ((rc = smtp_setup_conn(sx, suppress_tls)) != OK)
-  return rc;
+  {
+  timesince(&addrlist->delivery_time, &sx->delivery_start);
+  yield = rc;
+  goto TIDYUP;
+  }
+
+#ifdef SUPPORT_DANE
+/* If the connection used DANE, ignore for now any addresses with incompatible
+domains.  The SNI has to be the domain.  Arrange a whole new TCP conn later,
+just in case only TLS isn't enough. */
+
+if (sx->conn_args.dane)
+  {
+  const uschar * dane_domain = sx->first_addr->domain;
+
+  for (address_item * a = sx->first_addr->next; a; a = a->next)
+    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);
+      dane_held = TRUE;
+      a->transport_return = DANE;
+      }
+  }
+#endif
 
 /* If there is a filter command specified for this transport, we can now
 
 /* If there is a filter command specified for this transport, we can now
-set it up. This cannot be done until the identify of the host is known. */
+set it up. This cannot be done until the identity of the host is known. */
 
 if (tblock->filter_command)
   {
 
 if (tblock->filter_command)
   {
@@ -3522,11 +3858,11 @@ if (tblock->filter_command)
   yield ERROR. */
 
   if (!transport_set_up_command(&transport_filter_argv,
   yield ERROR. */
 
   if (!transport_set_up_command(&transport_filter_argv,
-       tblock->filter_command, TRUE, DEFER, addrlist,
-       string_sprintf("%.50s transport", tblock->name), NULL))
+       tblock->filter_command, TSUC_EXPAND_ARGS, DEFER, addrlist,
+       string_sprintf("%.50s transport filter", tblock->name), NULL))
     {
     set_errno_nohost(addrlist->next, addrlist->basic_errno, addrlist->message, DEFER,
     {
     set_errno_nohost(addrlist->next, addrlist->basic_errno, addrlist->message, DEFER,
-      FALSE);
+      FALSE, &sx->delivery_start);
     yield = ERROR;
     goto SEND_QUIT;
     }
     yield = ERROR;
     goto SEND_QUIT;
     }
@@ -3534,7 +3870,7 @@ if (tblock->filter_command)
   if (  transport_filter_argv
      && *transport_filter_argv
      && **transport_filter_argv
   if (  transport_filter_argv
      && *transport_filter_argv
      && **transport_filter_argv
-     && sx->peer_offered & OPTION_CHUNKING
+     && smtp_peer_options & OPTION_CHUNKING
 #ifndef DISABLE_DKIM
     /* When dkim signing, chunking is handled even with a transport-filter */
      && !(ob->dkim.dkim_private_key && ob->dkim.dkim_domain && ob->dkim.dkim_selector)
 #ifndef DISABLE_DKIM
     /* When dkim signing, chunking is handled even with a transport-filter */
      && !(ob->dkim.dkim_private_key && ob->dkim.dkim_domain && ob->dkim.dkim_selector)
@@ -3542,7 +3878,7 @@ if (tblock->filter_command)
 #endif
      )
     {
 #endif
      )
     {
-    sx->peer_offered &= ~OPTION_CHUNKING;
+    smtp_peer_options &= ~OPTION_CHUNKING;
     DEBUG(D_transport) debug_printf("CHUNKING not usable due to transport filter\n");
     }
   }
     DEBUG(D_transport) debug_printf("CHUNKING not usable due to transport filter\n");
     }
   }
@@ -3570,7 +3906,6 @@ always has a sequence number greater than one. */
 
 if (continue_hostname && continue_sequence == 1)
   {
 
 if (continue_hostname && continue_sequence == 1)
   {
-  sx->peer_offered = smtp_peer_options;
   /* sx->pending_MAIL = FALSE; */
   sx->ok = TRUE;
   /* sx->next_addr = NULL; */
   /* sx->pending_MAIL = FALSE; */
   sx->ok = TRUE;
   /* sx->next_addr = NULL; */
@@ -3584,11 +3919,12 @@ else
 
   switch(smtp_write_mail_and_rcpt_cmds(sx, &yield))
     {
 
   switch(smtp_write_mail_and_rcpt_cmds(sx, &yield))
     {
-    case 0:            break;
-    case -1: case -2:  goto RESPONSE_FAILED;
-    case -3:           goto END_OFF;
-    case -4:           goto SEND_QUIT;
-    default:           goto SEND_FAILED;
+    case sw_mrc_ok:                    break;
+    case sw_mrc_bad_mail:              goto RESPONSE_FAILED;
+    case sw_mrc_bad_read:              goto RESPONSE_FAILED;
+    case sw_mrc_nonmail_read_timeo:    goto END_OFF;
+    case sw_mrc_bad_internal:          goto SEND_QUIT;
+    default:                           goto SEND_FAILED;
     }
 
   /* If we are an MUA wrapper, abort if any RCPTs were rejected, either
     }
 
   /* If we are an MUA wrapper, abort if any RCPTs were rejected, either
@@ -3605,7 +3941,7 @@ else
        {
        /*XXX could we find a better errno than 0 here? */
        set_errno_nohost(addrlist, 0, a->message, FAIL,
        {
        /*XXX could we find a better errno than 0 here? */
        set_errno_nohost(addrlist, 0, a->message, FAIL,
-         testflag(a, af_pass_message));
+         testflag(a, af_pass_message), &sx->delivery_start);
        sx->ok = FALSE;
        break;
        }
        sx->ok = FALSE;
        break;
        }
@@ -3620,23 +3956,25 @@ are pipelining. The responses are all handled by sync_responses().
 If using CHUNKING, do not send a BDAT until we know how big a chunk we want
 to send is. */
 
 If using CHUNKING, do not send a BDAT until we know how big a chunk we want
 to send is. */
 
-if (  !(sx->peer_offered & OPTION_CHUNKING)
+if (  !(smtp_peer_options & OPTION_CHUNKING)
    && (sx->ok || (pipelining_active && !mua_wrapper)))
   {
   int count = smtp_write_command(sx, SCMD_FLUSH, "DATA\r\n");
 
   if (count < 0) goto SEND_FAILED;
    && (sx->ok || (pipelining_active && !mua_wrapper)))
   {
   int count = smtp_write_command(sx, SCMD_FLUSH, "DATA\r\n");
 
   if (count < 0) goto SEND_FAILED;
+
   switch(sync_responses(sx, count, sx->ok ? +1 : -1))
     {
   switch(sync_responses(sx, count, sx->ok ? +1 : -1))
     {
-    case 3: sx->ok = TRUE;            /* 2xx & 5xx => OK & progress made */
-    case 2: sx->completed_addr = TRUE;    /* 5xx (only) => progress made */
-    break;
+    case RESP_HAD_2_AND_5: sx->ok = TRUE;      /* OK & progress made */
+    case RESP_BIT_HAD_5XX: sx->completed_addr = TRUE; /* progress made */
+                          break;
 
 
-    case 1: sx->ok = TRUE;            /* 2xx (only) => OK, but if LMTP, */
-    if (!sx->lmtp) sx->completed_addr = TRUE; /* can't tell about progress yet */
-    case 0: break;                     /* No 2xx or 5xx, but no probs */
+    case RESP_BIT_HAD_2XX: sx->ok = TRUE;      /* OK, but if LMTP, */
+                          if (!sx->lmtp)       /* can't tell about progress yet */
+                           sx->completed_addr = TRUE;
+    case RESP_NOERROR:    break;               /* No 2xx or 5xx, but no probs */
 
 
-    case -1: goto END_OFF;             /* Timeout on RCPT */
+    case RESP_RCPT_TIMEO:  goto END_OFF;
 
 #ifndef DISABLE_PIPE_CONNECT
     case -5:                           /* TLS first-read error */
 
 #ifndef DISABLE_PIPE_CONNECT
     case -5:                           /* TLS first-read error */
@@ -3657,7 +3995,7 @@ well as body. Set the appropriate timeout value to be used for each chunk.
 (Haven't been able to make it work using select() for writing yet.) */
 
 if (  !sx->ok
 (Haven't been able to make it work using select() for writing yet.) */
 
 if (  !sx->ok
-   && (!(sx->peer_offered & OPTION_CHUNKING) || !pipelining_active))
+   && (!(smtp_peer_options & OPTION_CHUNKING) || !pipelining_active))
   {
   /* Save the first address of the next batch. */
   sx->first_addr = sx->next_addr;
   {
   /* Save the first address of the next batch. */
   sx->first_addr = sx->next_addr;
@@ -3686,7 +4024,7 @@ else
   of responses.  The callback needs a whole bunch of state so set up
   a transport-context structure to be passed around. */
 
   of responses.  The callback needs a whole bunch of state so set up
   a transport-context structure to be passed around. */
 
-  if (sx->peer_offered & OPTION_CHUNKING)
+  if (smtp_peer_options & OPTION_CHUNKING)
     {
     tctx.check_string = tctx.escape_string = NULL;
     tctx.options |= topt_use_bdat;
     {
     tctx.check_string = tctx.escape_string = NULL;
     tctx.options |= topt_use_bdat;
@@ -3711,10 +4049,10 @@ else
   transport_write_timeout = ob->data_timeout;
   smtp_command = US"sending data block";   /* For error messages */
   DEBUG(D_transport|D_v)
   transport_write_timeout = ob->data_timeout;
   smtp_command = US"sending data block";   /* For error messages */
   DEBUG(D_transport|D_v)
-    if (sx->peer_offered & OPTION_CHUNKING)
+    if (smtp_peer_options & OPTION_CHUNKING)
       debug_printf("         will write message using CHUNKING\n");
     else
       debug_printf("         will write message using CHUNKING\n");
     else
-      debug_printf("  SMTP>> writing message and terminating \".\"\n");
+      debug_printf("  SMTP>> (writing message)\n");
   transport_count = 0;
 
 #ifndef DISABLE_DKIM
   transport_count = 0;
 
 #ifndef DISABLE_DKIM
@@ -3751,6 +4089,46 @@ else
   report_time_since(&t0, US"dkim_exim_sign_init (delta)");
 # endif
   }
   report_time_since(&t0, US"dkim_exim_sign_init (delta)");
 # endif
   }
+#endif
+
+  /* See if we can pipeline QUIT.  Reasons not to are
+  - pipelining not active
+  - not ok to send quit
+  - errors in amtp transation responses
+  - more addrs to send for this message or this host
+  - this message was being retried
+  - more messages for this host
+  If we can, we want the message-write to not flush (the tail end of) its data out.  */
+
+  if (  sx->pipelining_used
+     && (sx->ok && sx->completed_addr || smtp_peer_options & OPTION_CHUNKING)
+     && sx->send_quit
+     && !(sx->first_addr || f.continue_more)
+     && f.deliver_firsttime
+     )
+    {
+    smtp_compare_t t_compare =
+      {.tblock = tblock, .current_sender_address = sender_address};
+
+    tcw_done = TRUE;
+    tcw =
+#ifndef DISABLE_TLS
+          (  tls_out.active.sock < 0  &&  !continue_proxy_cipher
+           || verify_check_given_host(CUSS &ob->hosts_nopass_tls, host) != OK
+          )
+        &&
+#endif
+           transport_check_waiting(tblock->name, host->name,
+             tblock->connection_max_messages, new_message_id,
+            (oicf)smtp_are_same_identities, (void*)&t_compare);
+    if (!tcw)
+      {
+      HDEBUG(D_transport) debug_printf("will pipeline QUIT\n");
+      tctx.options |= topt_no_flush;
+      }
+    }
+
+#ifndef DISABLE_DKIM
   sx->ok = dkim_transport_write_message(&tctx, &ob->dkim, CUSS &message);
 #else
   sx->ok = transport_write_message(&tctx, 0);
   sx->ok = dkim_transport_write_message(&tctx, &ob->dkim, CUSS &message);
 #else
   sx->ok = transport_write_message(&tctx, 0);
@@ -3779,27 +4157,70 @@ else
 
   smtp_command = US"end of data";
 
 
   smtp_command = US"end of data";
 
-  if (sx->peer_offered & OPTION_CHUNKING && sx->cmd_count > 1)
+  /* If we can pipeline a QUIT with the data them send it now.  If a new message
+  for this host appeared in the queue while data was being sent, we will not see
+  it and it will have to wait for a queue run.  If there was one but another
+  thread took it, we might attempt to send it - but locking of spoolfiles will
+  detect that. Use _MORE to get QUIT in FIN segment. */
+
+  if (tcw_done && !tcw)
+    {
+    /*XXX jgh 2021/03/10 google et. al screwup.  G, at least, sends TCP FIN in response to TLS
+    close-notify.  Under TLS 1.3, violating RFC.
+    However, TLS 1.2 does not have half-close semantics. */
+
+    if (     sx->cctx.tls_ctx
+#if 0 && !defined(DISABLE_TLS)
+          && Ustrcmp(tls_out.ver, "TLS1.3") != 0
+#endif
+       || !f.deliver_firsttime
+       )
+      {                                /* Send QUIT now and not later */
+      (void)smtp_write_command(sx, SCMD_FLUSH, "QUIT\r\n");
+      sx->send_quit = FALSE;
+      }
+    else
+      {                                /* add QUIT to the output buffer */
+      (void)smtp_write_command(sx, SCMD_MORE, "QUIT\r\n");
+      sx->send_quit = FALSE;   /* avoid sending it later */
+
+#ifndef DISABLE_TLS
+      if (sx->cctx.tls_ctx && sx->send_tlsclose)       /* need to send TLS Close Notify */
+       {
+# ifdef EXIM_TCP_CORK          /* Use _CORK to get Close Notify in FIN segment */
+       (void) setsockopt(sx->cctx.sock, IPPROTO_TCP, EXIM_TCP_CORK, US &on, sizeof(on));
+# endif
+       tls_shutdown_wr(sx->cctx.tls_ctx);
+       sx->send_tlsclose = FALSE;      /* avoid later repeat */
+       }
+#endif
+      HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP(shutdown)>>\n");
+      shutdown(sx->cctx.sock, SHUT_WR);        /* flush output buffer, with TCP FIN */
+      }
+    }
+
+  if (smtp_peer_options & OPTION_CHUNKING && sx->cmd_count > 1)
     {
     /* Reap any outstanding MAIL & RCPT commands, but not a DATA-go-ahead */
     switch(sync_responses(sx, sx->cmd_count-1, 0))
       {
     {
     /* Reap any outstanding MAIL & RCPT commands, but not a DATA-go-ahead */
     switch(sync_responses(sx, sx->cmd_count-1, 0))
       {
-      case 3: sx->ok = TRUE;            /* 2xx & 5xx => OK & progress made */
-      case 2: sx->completed_addr = TRUE;    /* 5xx (only) => progress made */
-             break;
+      case RESP_HAD_2_AND_5: sx->ok = TRUE;            /* OK & progress made */
+      case RESP_BIT_HAD_5XX: sx->completed_addr = TRUE;        /* progress made */
+                            break;
 
 
-      case 1: sx->ok = TRUE;           /* 2xx (only) => OK, but if LMTP, */
-      if (!sx->lmtp) sx->completed_addr = TRUE; /* can't tell about progress yet */
-      case 0: break;                   /* No 2xx or 5xx, but no probs */
+      case RESP_BIT_HAD_2XX: sx->ok = TRUE;            /*  OK, but if LMTP, */
+                            if (!sx->lmtp)             /* can't tell about progress yet */
+                              sx->completed_addr = TRUE;
+      case RESP_NOERROR:     break;                    /* No 2xx or 5xx, but no probs */
 
 
-      case -1: goto END_OFF;           /* Timeout on RCPT */
+      case RESP_RCPT_TIMEO: goto END_OFF;              /* Timeout on RCPT */
 
 #ifndef DISABLE_PIPE_CONNECT
 
 #ifndef DISABLE_PIPE_CONNECT
-      case -5:                         /* TLS first-read error */
-      case -4:  HDEBUG(D_transport)
+      case RESP_EHLO_ERR_TLS:                          /* TLS first-read error */
+      case RESP_EPIPE_EHLO_ERR:  HDEBUG(D_transport)
                  debug_printf("failed reaping pipelined cmd responses\n");
 #endif
                  debug_printf("failed reaping pipelined cmd responses\n");
 #endif
-      default: goto RESPONSE_FAILED;   /* I/O error, or any MAIL/DATA error */
+      default:              goto RESPONSE_FAILED;      /* I/O error, or any MAIL/DATA error */
       }
     }
 
       }
     }
 
@@ -3858,7 +4279,7 @@ else
     int len;
     uschar * conf = NULL;
 
     int len;
     uschar * conf = NULL;
 
-    timesince(&delivery_time, &start_delivery_time);
+    timesince(&delivery_time, &sx->delivery_start);
     sx->send_rset = FALSE;
     pipelining_active = FALSE;
 
     sx->send_rset = FALSE;
     pipelining_active = FALSE;
 
@@ -3871,15 +4292,16 @@ else
           !sx->lmtp
        )
       {
           !sx->lmtp
        )
       {
-      const uschar *s = string_printing(sx->buffer);
+      const uschar * s = string_printing(sx->buffer);
       /* deconst cast ok here as string_printing was checked to have alloc'n'copied */
       /* deconst cast ok here as string_printing was checked to have alloc'n'copied */
-      conf = (s == sx->buffer)? US string_copy(s) : US s;
+      conf = s == sx->buffer ? US string_copy(s) : US s;
       }
 
     /* Process all transported addresses - for LMTP or PRDR, read a status for
       }
 
     /* Process all transported addresses - for LMTP or PRDR, read a status for
-    each one. */
+    each one. We used to drop out at first_addr, until someone returned a 452
+    followed by a 250... and we screwed up the accepted addresses. */
 
 
-    for (address_item * addr = addrlist; addr != sx->first_addr; addr = addr->next)
+    for (address_item * addr = addrlist; addr; addr = addr->next)
       {
       if (addr->transport_return != PENDING_OK) continue;
 
       {
       if (addr->transport_return != PENDING_OK) continue;
 
@@ -3933,9 +4355,8 @@ else
       actual host that was used. */
 
       addr->transport_return = OK;
       actual host that was used. */
 
       addr->transport_return = OK;
-      addr->more_errno = delivery_time.tv_sec;
-      addr->delivery_usec = delivery_time.tv_usec;
       addr->host_used = host;
       addr->host_used = host;
+      addr->delivery_time = delivery_time;
       addr->special_action = flag;
       addr->message = conf;
 
       addr->special_action = flag;
       addr->message = conf;
 
@@ -3952,7 +4373,7 @@ else
 #ifndef DISABLE_PRDR
       if (sx->prdr_active) setflag(addr, af_prdr_used);
 #endif
 #ifndef DISABLE_PRDR
       if (sx->prdr_active) setflag(addr, af_prdr_used);
 #endif
-      if (sx->peer_offered & OPTION_CHUNKING) setflag(addr, af_chunking_used);
+      if (smtp_peer_options & OPTION_CHUNKING) setflag(addr, af_chunking_used);
       flag = '-';
 
 #ifndef DISABLE_PRDR
       flag = '-';
 
 #ifndef DISABLE_PRDR
@@ -3969,7 +4390,7 @@ else
         else
           sprintf(CS sx->buffer, "%.500s\n", addr->unique);
 
         else
           sprintf(CS sx->buffer, "%.500s\n", addr->unique);
 
-        DEBUG(D_deliver) debug_printf("S:journalling %s\n", sx->buffer);
+        DEBUG(D_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 "
         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 "
@@ -4023,6 +4444,7 @@ else
              "%s: %s", sx->buffer, strerror(errno));
          }
        else if (addr->transport_return == DEFER)
              "%s: %s", sx->buffer, strerror(errno));
          }
        else if (addr->transport_return == DEFER)
+         /*XXX magic value -2 ? maybe host+message ? */
          retry_add_item(addr, addr->address_retry_key, -2);
       }
 #endif
          retry_add_item(addr, addr->address_retry_key, -2);
       }
 #endif
@@ -4053,7 +4475,8 @@ if (!sx->ok)
     {
     save_errno = errno;
     message = NULL;
     {
     save_errno = errno;
     message = NULL;
-    sx->send_quit = check_response(host, &save_errno, addrlist->more_errno,
+    /* Clear send_quit flag if needed.  Do not set. */
+    sx->send_quit &= check_response(host, &save_errno, addrlist->more_errno,
       sx->buffer, &code, &message, &pass_message);
     goto FAILED;
     }
       sx->buffer, &code, &message, &pass_message);
     goto FAILED;
     }
@@ -4062,7 +4485,7 @@ if (!sx->ok)
     {
     save_errno = errno;
     code = '4';
     {
     save_errno = errno;
     code = '4';
-    message = string_sprintf("send() to %s [%s] failed: %s",
+    message = string_sprintf("smtp send to %s [%s] failed: %s",
       host->name, host->address, message ? message : US strerror(save_errno));
     sx->send_quit = FALSE;
     goto FAILED;
       host->name, host->address, message ? message : US strerror(save_errno));
     sx->send_quit = FALSE;
     goto FAILED;
@@ -4095,15 +4518,51 @@ if (!sx->ok)
        break;
 
       case ERRNO_SMTPCLOSED:
        break;
 
       case ERRNO_SMTPCLOSED:
-       message_error = Ustrncmp(smtp_command,"end ",4) == 0;
+       /* If the peer closed the TCP connection after end-of-data, but before
+       we could send QUIT, do TLS close, etc - call it a message error.
+       Otherwise, if all the recipients have been dealt with, call a close no
+       error at all; each address_item should have a suitable result already
+       (2xx: PENDING_OK, 4xx: DEFER, 5xx: FAIL) */
+
+       if (!(message_error = Ustrncmp(smtp_command,"end ",4) == 0))
+         {
+         address_item * addr;
+         for (addr = sx->addrlist; addr; addr = addr->next)
+           if (addr->transport_return == PENDING_DEFER)
+             break;
+         if (!addr)    /* all rcpts fates determined */
+           {
+           log_write(0, LOG_MAIN, "peer close after all rcpt responses;"
+             " converting i/o-error to no-error");
+           sx->ok = TRUE;
+           goto happy;
+           }
+         }
        break;
 
        break;
 
+#ifndef DISABLE_DKIM
+      case EACCES:
+       /* DKIM signing failure: avoid thinking we pipelined quit,
+       just abandon the message and close the socket. */
+
+       message_error = FALSE;
+# ifndef DISABLE_TLS
+       if (sx->cctx.tls_ctx)
+         {
+         tls_close(sx->cctx.tls_ctx,
+                   sx->send_tlsclose ? TLS_SHUTDOWN_WAIT : TLS_SHUTDOWN_WONLY);
+         sx->cctx.tls_ctx = NULL;
+         }
+# endif
+       break;
+#endif
       default:
        message_error = FALSE;
        break;
       }
 
       default:
        message_error = FALSE;
        break;
       }
 
-    /* Handle the cases that are treated as message errors. These are:
+    /* Handle the cases that are treated as message errors (as opposed to
+    host-errors). These are:
 
       (a) negative response or timeout after MAIL
       (b) negative response after DATA
 
       (a) negative response or timeout after MAIL
       (b) negative response after DATA
@@ -4138,8 +4597,15 @@ if (!sx->ok)
 
         *message_defer = TRUE;
         }
 
         *message_defer = TRUE;
         }
+#ifdef TIOCOUTQ
+      DEBUG(D_transport) if (sx->cctx.sock >= 0)
+       {
+       int n;
+       if (ioctl(sx->cctx.sock, TIOCOUTQ, &n) == 0)
+         debug_printf("%d bytes remain in socket output buffer\n", n);
+       }
+#endif
       }
       }
-
     /* Otherwise, we have an I/O error or a timeout other than after MAIL or
     ".", or some other transportation error. We defer all addresses and yield
     DEFER, except for the case of failed add_headers expansion, or a transport
     /* Otherwise, we have an I/O error or a timeout other than after MAIL or
     ".", or some other transportation error. We defer all addresses and yield
     DEFER, except for the case of failed add_headers expansion, or a transport
@@ -4165,11 +4631,11 @@ if (!sx->ok)
       }
     }
 
       }
     }
 
-  set_errno(addrlist, save_errno, set_message, set_rc, pass_message, host
+  set_errno(addrlist, save_errno, set_message, set_rc, pass_message, host,
 #ifdef EXPERIMENTAL_DSN_INFO
 #ifdef EXPERIMENTAL_DSN_INFO
-           , sx->smtp_greeting, sx->helo_response
+           sx->smtp_greeting, sx->helo_response,
 #endif
 #endif
-           );
+           &sx->delivery_start);
   }
 
 /* If all has gone well, send_quit will be set TRUE, implying we can end the
   }
 
 /* If all has gone well, send_quit will be set TRUE, implying we can end the
@@ -4200,91 +4666,112 @@ connection to a new process. However, not all servers can handle this (Exim
 can), so we do not pass such a connection on if the host matches
 hosts_nopass_tls. */
 
 can), so we do not pass such a connection on if the host matches
 hosts_nopass_tls. */
 
+happy:
+
 DEBUG(D_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)
 DEBUG(D_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)
-  {
-  BOOL more;
-  smtp_compare_t t_compare;
-
-  t_compare.tblock = tblock;
-  t_compare.current_sender_address = sender_address;
-
-  if (  sx->first_addr != NULL
-     || f.continue_more
-     || (
-#ifndef DISABLE_TLS
-          (  tls_out.active.sock < 0  &&  !continue_proxy_cipher
-           || verify_check_given_host(CUSS &ob->hosts_nopass_tls, host) != OK
-          )
-        &&
+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+  if (mail_limit = continue_sequence >= sx->max_mail)
+    {
+    DEBUG(D_transport)
+      debug_printf("reached limit %u for MAILs per conn\n", sx->max_mail);
+    }
+  else
 #endif
 #endif
-           transport_check_waiting(tblock->name, host->name,
-             tblock->connection_max_messages, new_message_id, &more,
-            (oicf)smtp_are_same_identities, (void*)&t_compare)
-     )  )
     {
     {
-    uschar *msg;
-    BOOL pass_message;
-
-    if (sx->send_rset)
-      if (! (sx->ok = smtp_write_command(sx, SCMD_FLUSH, "RSET\r\n") >= 0))
-        {
-        msg = US string_sprintf("send() to %s [%s] failed: %s", host->name,
-          host->address, strerror(errno));
-        sx->send_quit = FALSE;
-        }
-      else if (! (sx->ok = smtp_read_response(sx, sx->buffer, sizeof(sx->buffer),
-                 '2', ob->command_timeout)))
-        {
-        int code;
-        sx->send_quit = check_response(host, &errno, 0, sx->buffer, &code, &msg,
-          &pass_message);
-        if (!sx->send_quit)
-          {
-          DEBUG(D_transport) debug_printf("H=%s [%s] %s\n",
-           host->name, host->address, msg);
-          }
-        }
-
-    /* Either RSET was not needed, or it succeeded */
+    smtp_compare_t t_compare =
+      {.tblock = tblock, .current_sender_address = sender_address};
 
 
-    if (sx->ok)
-      {
+    if (  sx->first_addr                       /* more addrs for this message */
+       || f.continue_more                      /* more addrs for continued-host */
+       || tcw_done && tcw                      /* more messages for host */
+       || (
 #ifndef DISABLE_TLS
 #ifndef DISABLE_TLS
-      int pfd[2];
-#endif
-      int socket_fd = sx->cctx.sock;
-
+            (  tls_out.active.sock < 0  &&  !continue_proxy_cipher
+            || verify_check_given_host(CUSS &ob->hosts_nopass_tls, host) != OK
+            )
+         &&
+#endif
+            transport_check_waiting(tblock->name, host->name,
+              sx->max_mail, new_message_id,
+              (oicf)smtp_are_same_identities, (void*)&t_compare)
+       )  )
+      {
+      uschar *msg;
+      BOOL pass_message;
 
 
-      if (sx->first_addr != NULL)      /* More addresses still to be sent */
-        {                              /*   in this run of the transport */
-        continue_sequence++;           /* Causes * in logging */
-       pipelining_active = sx->pipelining_used;    /* was cleared at DATA */
-        goto SEND_MESSAGE;
-        }
+      if (sx->send_rset)
+       if (! (sx->ok = smtp_write_command(sx, SCMD_FLUSH, "RSET\r\n") >= 0))
+         {
+         msg = US string_sprintf("smtp send to %s [%s] failed: %s", host->name,
+           host->address, strerror(errno));
+         sx->send_quit = FALSE;
+         }
+       else if (! (sx->ok = smtp_read_response(sx, sx->buffer, sizeof(sx->buffer),
+                   '2', ob->command_timeout)))
+         {
+         int code;
+         sx->send_quit = check_response(host, &errno, 0, sx->buffer, &code, &msg,
+           &pass_message);
+         if (!sx->send_quit)
+           {
+           DEBUG(D_transport) debug_printf("H=%s [%s] %s\n",
+             host->name, host->address, msg);
+           }
+         }
 
 
-      /* Unless caller said it already has more messages listed for this host,
-      pass the connection on to a new Exim process (below, the call to
-      transport_pass_socket).  If the caller has more ready, just return with
-      the connection still open. */
+      /* Either RSET was not needed, or it succeeded */
 
 
+      if (sx->ok)
+       {
 #ifndef DISABLE_TLS
 #ifndef DISABLE_TLS
-      if (tls_out.active.sock >= 0)
-       if (  f.continue_more
-          || verify_check_given_host(CUSS &ob->hosts_noproxy_tls, host) == OK)
-         {
-         /* Before passing the socket on, or returning to caller with it still
-         open, we must shut down TLS.  Not all MTAs allow for the continuation
-         of the SMTP session when TLS is shut down. We test for this by sending
-         a new EHLO. If we don't get a good response, we don't attempt to pass
-         the socket on. */
+       int pfd[2];
+#endif
+       int socket_fd = sx->cctx.sock;
+
+       if (sx->first_addr)             /* More addresses still to be sent */
+         {                             /*   for this message              */
+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+         /* Any that we marked as skipped, reset to do now */
+         for (address_item * a = sx->first_addr; a; a = a->next)
+           if (a->transport_return == SKIP)
+             a->transport_return = PENDING_DEFER;
+#endif
+         continue_sequence++;                          /* for consistency */
+         clearflag(sx->first_addr, af_new_conn);
+         setflag(sx->first_addr, af_cont_conn);        /* Causes * in logging */
+         pipelining_active = sx->pipelining_used;      /* was cleared at DATA */
+         goto SEND_MESSAGE;
+         }
+
+       /* Unless caller said it already has more messages listed for this host,
+       pass the connection on to a new Exim process (below, the call to
+       transport_pass_socket).  If the caller has more ready, just return with
+       the connection still open. */
 
 
-         tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_WAIT);
+#ifndef DISABLE_TLS
+       if (tls_out.active.sock >= 0)
+         if (  f.continue_more
+            || verify_check_given_host(CUSS &ob->hosts_noproxy_tls, host) == OK)
+           {
+           /* Before passing the socket on, or returning to caller with it still
+           open, we must shut down TLS.  Not all MTAs allow for the continuation
+           of the SMTP session when TLS is shut down. We test for this by sending
+           a new EHLO. If we don't get a good response, we don't attempt to pass
+           the socket on.
+           NB: TLS close is *required* per RFC 9266 when tls-exporter info has
+           been used, which we do under TLSv1.3 for the gsasl SCRAM*PLUS methods.
+           But we were always doing it anyway. */
+
+         tls_close(sx->cctx.tls_ctx,
+           sx->send_tlsclose ? TLS_SHUTDOWN_WAIT : TLS_SHUTDOWN_WONLY);
+         sx->send_tlsclose = FALSE;
          sx->cctx.tls_ctx = NULL;
          sx->cctx.tls_ctx = NULL;
+         tls_out.active.sock = -1;
          smtp_peer_options = smtp_peer_options_wrap;
          sx->ok = !sx->smtps
            && smtp_write_command(sx, SCMD_FLUSH, "EHLO %s\r\n", sx->helo_data)
          smtp_peer_options = smtp_peer_options_wrap;
          sx->ok = !sx->smtps
            && smtp_write_command(sx, SCMD_FLUSH, "EHLO %s\r\n", sx->helo_data)
@@ -4292,118 +4779,110 @@ if (sx->completed_addr && sx->ok && sx->send_quit)
            && smtp_read_response(sx, sx->buffer, sizeof(sx->buffer),
                                      '2', ob->command_timeout);
 
            && smtp_read_response(sx, sx->buffer, sizeof(sx->buffer),
                                      '2', ob->command_timeout);
 
-         if (sx->ok && f.continue_more)
-           return yield;               /* More addresses for another run */
-         }
-       else
-         {
-         /* Set up a pipe for proxying TLS for the new transport process */
-
-         smtp_peer_options |= OPTION_TLS;
-         if ((sx->ok = socketpair(AF_UNIX, SOCK_STREAM, 0, pfd) == 0))
-           socket_fd = pfd[1];
+           if (sx->ok && f.continue_more)
+             goto TIDYUP;              /* More addresses for another run */
+           }
          else
          else
-           set_errno(sx->first_addr, errno, US"internal allocation problem",
-                   DEFER, FALSE, host
+           {
+           /* Set up a pipe for proxying TLS for the new transport process */
+
+           smtp_peer_options |= OPTION_TLS;
+           if ((sx->ok = socketpair(AF_UNIX, SOCK_STREAM, 0, pfd) == 0))
+             socket_fd = pfd[1];
+           else
+             set_errno(sx->first_addr, errno, US"internal allocation problem",
+                     DEFER, FALSE, host,
 # ifdef EXPERIMENTAL_DSN_INFO
 # ifdef EXPERIMENTAL_DSN_INFO
-                   , sx->smtp_greeting, sx->helo_response
+                     sx->smtp_greeting, sx->helo_response,
 # endif
 # endif
-                   );
-         }
-      else
+                     &sx->delivery_start);
+           }
+       else
 #endif
 #endif
-       if (f.continue_more)
-         return yield;                 /* More addresses for another run */
+         if (f.continue_more)
+           goto TIDYUP;                        /* More addresses for another run */
 
 
-      /* If the socket is successfully passed, we mustn't send QUIT (or
-      indeed anything!) from here. */
+       /* If the socket is successfully passed, we mustn't send QUIT (or
+       indeed anything!) from here. */
 
 
-/*XXX DSN_INFO: assume likely to do new HELO; but for greet we'll want to
-propagate it from the initial
-*/
-      if (sx->ok && transport_pass_socket(tblock->name, host->name,
-           host->address, new_message_id, socket_fd))
-       {
-        sx->send_quit = FALSE;
-
-       /* We have passed the client socket to a fresh transport process.
-       If TLS is still active, we need to proxy it for the transport we
-       just passed the baton to.  Fork a child to to do it, and return to
-       get logging done asap.  Which way to place the work makes assumptions
-       about post-fork prioritisation which may not hold on all platforms. */
-#ifndef DISABLE_TLS
-       if (tls_out.active.sock >= 0)
+  /*XXX DSN_INFO: assume likely to do new HELO; but for greet we'll want to
+  propagate it from the initial
+  */
+       if (sx->ok && transport_pass_socket(tblock->name, host->name,
+             host->address, new_message_id, socket_fd
+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+             , sx->peer_limit_mail, sx->peer_limit_rcpt, sx->peer_limit_rcptdom
+#endif
+             ))
          {
          {
-         int pid = fork();
-         if (pid == 0)         /* child; fork again to disconnect totally */
-           {
-           testharness_pause_ms(100); /* let parent debug out */
-           /* does not return */
-           smtp_proxy_tls(sx->cctx.tls_ctx, sx->buffer, sizeof(sx->buffer), pfd,
-                           ob->command_timeout);
-           }
+         sx->send_quit = FALSE;
 
 
-         if (pid > 0)          /* parent */
+         /* We have passed the client socket to a fresh transport process.
+         If TLS is still active, we need to proxy it for the transport we
+         just passed the baton to.  Fork a child to to do it, and return to
+         get logging done asap.  Which way to place the work makes assumptions
+         about post-fork prioritisation which may not hold on all platforms. */
+#ifndef DISABLE_TLS
+         if (tls_out.active.sock >= 0)
            {
            {
-           DEBUG(D_transport) debug_printf("proxy-proc inter-pid %d\n", pid);
-           close(pfd[0]);
-           /* tidy the inter-proc to disconn the proxy proc */
-           waitpid(pid, NULL, 0);
-           tls_close(sx->cctx.tls_ctx, TLS_NO_SHUTDOWN);
-           sx->cctx.tls_ctx = NULL;
-           (void)close(sx->cctx.sock);
-           sx->cctx.sock = -1;
-           continue_transport = NULL;
-           continue_hostname = NULL;
-           return yield;
+           int pid = exim_fork(US"tls-proxy-interproc");
+           if (pid == 0)               /* child; fork again to disconnect totally */
+             {
+             /* does not return */
+             smtp_proxy_tls(sx->cctx.tls_ctx, sx->buffer, sizeof(sx->buffer), pfd,
+                             ob->command_timeout, host->name);
+             }
+
+           if (pid > 0)                /* parent */
+             {
+             close(pfd[0]);
+             /* tidy the inter-proc to disconn the proxy proc */
+             waitpid(pid, NULL, 0);
+             tls_close(sx->cctx.tls_ctx, TLS_NO_SHUTDOWN);
+             sx->cctx.tls_ctx = NULL;
+             (void)close(sx->cctx.sock);
+             sx->cctx.sock = -1;
+             continue_transport = NULL;
+             continue_hostname = NULL;
+             goto TIDYUP;
+             }
+           log_write(0, LOG_PANIC_DIE, "fork failed");
            }
            }
-         log_write(0, LOG_PANIC_DIE, "fork failed");
-         }
 #endif
 #endif
+         }
        }
        }
-      }
 
 
-    /* If RSET failed and there are addresses left, they get deferred. */
-    else
-      set_errno(sx->first_addr, errno, msg, DEFER, FALSE, host
+      /* If RSET failed and there are addresses left, they get deferred. */
+      else
+       set_errno(sx->first_addr, errno, msg, DEFER, FALSE, host,
 #ifdef EXPERIMENTAL_DSN_INFO
 #ifdef EXPERIMENTAL_DSN_INFO
-                 , sx->smtp_greeting, sx->helo_response
+                   sx->smtp_greeting, sx->helo_response,
 #endif
 #endif
-                 );
+                   &sx->delivery_start);
+      }
     }
     }
-  }
 
 /* End off tidily with QUIT unless the connection has died or the socket has
 
 /* End off tidily with QUIT unless the connection has died or the socket has
-been passed to another process. There has been discussion on the net about what
-to do after sending QUIT. The wording of the RFC suggests that it is necessary
-to wait for a response, but on the other hand, there isn't anything one can do
-with an error response, other than log it. Exim used to do that. However,
-further discussion suggested that it is positively advantageous not to wait for
-the response, but to close the session immediately. This is supposed to move
-the TCP/IP TIME_WAIT state from the server to the client, thereby removing some
-load from the server. (Hosts that are both servers and clients may not see much
-difference, of course.) Further discussion indicated that this was safe to do
-on Unix systems which have decent implementations of TCP/IP that leave the
-connection around for a while (TIME_WAIT) after the application has gone away.
-This enables the response sent by the server to be properly ACKed rather than
-timed out, as can happen on broken TCP/IP implementations on other OS.
-
-This change is being made on 31-Jul-98. After over a year of trouble-free
-operation, the old commented-out code was removed on 17-Sep-99. */
+been passed to another process. */
 
 SEND_QUIT:
 
 SEND_QUIT:
-#ifdef TCP_CORK
-(void) setsockopt(sx->cctx.sock, IPPROTO_TCP, TCP_CORK, US &on, sizeof(on));
+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
+  if (sx->cctx.tls_ctx && sx->send_tlsclose)
+    {
+# ifdef EXIM_TCP_CORK  /* Use _CORK to get TLS Close Notify in FIN segment */
+    (void) setsockopt(sx->cctx.sock, IPPROTO_TCP, EXIM_TCP_CORK, US &on, sizeof(on));
+# endif
+    tls_shutdown_wr(sx->cctx.tls_ctx);
+    sx->send_tlsclose = FALSE;
+    }
 #endif
 #endif
-if (sx->send_quit) (void)smtp_write_command(sx, SCMD_FLUSH, "QUIT\r\n");
+  }
 
 END_OFF:
 
 
 END_OFF:
 
-#ifndef DISABLE_TLS
-tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT);
-sx->cctx.tls_ctx = NULL;
-#endif
-
 /* Close the socket, and return the appropriate value, first setting
 works because the NULL setting is passed back to the calling process, and
 remote_max_parallel is forced to 1 when delivering over an existing connection,
 /* Close the socket, and return the appropriate value, first setting
 works because the NULL setting is passed back to the calling process, and
 remote_max_parallel is forced to 1 when delivering over an existing connection,
@@ -4414,24 +4893,122 @@ writing RSET might have failed, or there may be other addresses whose hosts are
 specified in the transports, and therefore not visible at top level, in which
 case continue_more won't get set. */
 
 specified in the transports, and therefore not visible at top level, in which
 case continue_more won't get set. */
 
-HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP(close)>>\n");
 if (sx->send_quit)
   {
 if (sx->send_quit)
   {
+  /* This flushes data queued in the socket, being the QUIT and any TLS Close,
+  sending them along with the client FIN flag.  Us (we hope) sending FIN first
+  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");
   shutdown(sx->cctx.sock, SHUT_WR);
   shutdown(sx->cctx.sock, SHUT_WR);
-  millisleep(20);
-  testharness_pause_ms(200);
-  if (fcntl(sx->cctx.sock, F_SETFL, O_NONBLOCK) == 0)
-    for (int i = 16; read(sx->cctx.sock, sx->inbuffer, sizeof(sx->inbuffer)) > 0 && i > 0;)
-      i--;                             /* drain socket */
   }
   }
-(void)close(sx->cctx.sock);
 
 
-#ifndef DISABLE_EVENT
-(void) event_raise(tblock->event_action, US"tcp:close", NULL);
+if (sx->send_quit || tcw_done && !tcw)
+  {
+  /* Wait for (we hope) ack of our QUIT, and a server FIN.  Discard any data
+  received, then discard the socket.  Any packet received after then, or receive
+  data still in the socket, will get a RST - hence the pause/drain. */
+
+  /* Reap the response to QUIT, timing out after one second */
+  (void) smtp_read_response(sx, sx->buffer, sizeof(sx->buffer), '2', 1);
+#ifndef DISABLE_TLS
+  if (sx->cctx.tls_ctx)
+    {
+    int n;
+
+    /* Reap the TLS Close Notify from the server, timing out after one second */
+    sigalrm_seen = FALSE;
+    ALARM(1);
+    do
+      n = tls_read(sx->cctx.tls_ctx, sx->inbuffer, sizeof(sx->inbuffer));
+    while (!sigalrm_seen && n > 0);
+    ALARM_CLR(0);
+
+    if (sx->send_tlsclose)
+      {
+# ifdef EXIM_TCP_CORK
+      (void) setsockopt(sx->cctx.sock, IPPROTO_TCP, EXIM_TCP_CORK, US &on, sizeof(on));
+# endif
+      tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_WAIT);
+      }
+    else
+      tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_WONLY);
+    sx->cctx.tls_ctx = NULL;
+    }
 #endif
 
 #endif
 
+  /* Drain any trailing data from the socket before close, to avoid sending a RST */
+
+  if (  poll_one_fd(sx->cctx.sock, POLLIN, 20) != 0            /* 20ms */
+     && 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)
+      {
+      int m = MIN(n, 64);
+      debug_printf_indent("  SMTP(drain %d bytes)<< %.*s\n", n, m, sx->inbuffer);
+      for (m = 0; m < n; m++)
+       debug_printf("0x%02x\n", sx->inbuffer[m]);
+      }
+  }
+HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP(close)>>\n");
+(void)close(sx->cctx.sock);
+sx->cctx.sock = -1;
 continue_transport = NULL;
 continue_hostname = NULL;
 continue_transport = NULL;
 continue_hostname = NULL;
+smtp_debug_cmd_report();
+
+#ifndef DISABLE_EVENT
+(void) event_raise(tblock->event_action, US"tcp:close", NULL, NULL);
+#endif
+
+#ifdef SUPPORT_DANE
+if (dane_held)
+  {
+  sx->first_addr = NULL;
+  for (address_item * a = sx->addrlist->next; a; a = a->next)
+    if (a->transport_return == DANE)
+      {
+      a->transport_return = PENDING_DEFER;
+      if (!sx->first_addr)
+       {
+       /* Remember the new start-point in the addrlist, for smtp_setup_conn()
+       to get the domain string for SNI */
+
+       sx->first_addr = a;
+       clearflag(a, af_cont_conn);
+       setflag(a, af_new_conn);                /* clear * from logging */
+       DEBUG(D_transport) debug_printf("DANE: go-around for %s\n", a->domain);
+       }
+      }
+  continue_sequence = 1;                       /* for consistency */
+  goto REPEAT_CONN;
+  }
+#endif
+
+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+if (mail_limit && sx->first_addr)
+  {
+  /* Reset the sequence count since we closed the connection.  This is flagged
+  on the pipe back to the delivery process so that a non-continued-conn delivery
+  is logged. */
+
+  continue_sequence = 1;                       /* for consistency */
+  clearflag(sx->first_addr, af_cont_conn);
+  setflag(sx->first_addr, af_new_conn);                /* clear  * from logging */
+  goto REPEAT_CONN;
+  }
+#endif
+
+return yield;
+
+TIDYUP:
+#ifdef SUPPORT_DANE
+if (dane_held) for (address_item * a = sx->addrlist->next; a; a = a->next)
+  if (a->transport_return == DANE)
+    a->transport_return = PENDING_DEFER;
+#endif
 return yield;
 }
 
 return yield;
 }
 
@@ -4460,7 +5037,7 @@ smtp_transport_closedown(transport_instance *tblock)
 {
 smtp_transport_options_block * ob = SOB tblock->options_block;
 client_conn_ctx cctx;
 {
 smtp_transport_options_block * ob = SOB tblock->options_block;
 client_conn_ctx cctx;
-smtp_context sx;
+smtp_context sx = {0};
 uschar buffer[256];
 uschar inbuffer[4096];
 uschar outbuffer[16];
 uschar buffer[256];
 uschar inbuffer[4096];
 uschar outbuffer[16];
@@ -4508,16 +5085,16 @@ Returns:       the first address for this delivery
 */
 
 static address_item *
 */
 
 static address_item *
-prepare_addresses(address_item *addrlist, host_item *host)
+prepare_addresses(address_item * addrlist, host_item * host)
 {
 {
-address_item *first_addr = NULL;
+address_item * first_addr = NULL;
 for (address_item * addr = addrlist; addr; addr = addr->next)
   if (addr->transport_return == DEFER)
     {
     if (!first_addr) first_addr = addr;
     addr->transport_return = PENDING_DEFER;
     addr->basic_errno = 0;
 for (address_item * addr = addrlist; addr; addr = addr->next)
   if (addr->transport_return == DEFER)
     {
     if (!first_addr) first_addr = addr;
     addr->transport_return = PENDING_DEFER;
     addr->basic_errno = 0;
-    addr->more_errno = (host->mx >= 0)? 'M' : 'A';
+    addr->more_errno = host->mx >= 0 ? 'M' : 'A';
     addr->message = NULL;
 #ifndef DISABLE_TLS
     addr->cipher = NULL;
     addr->message = NULL;
 #ifndef DISABLE_TLS
     addr->cipher = NULL;
@@ -4549,24 +5126,17 @@ FALSE. */
 
 BOOL
 smtp_transport_entry(
 
 BOOL
 smtp_transport_entry(
-  transport_instance *tblock,      /* data for this instantiation */
-  address_item *addrlist)          /* addresses we are working on */
+  transport_instance * tblock,      /* data for this instantiation */
+  address_item * addrlist)          /* addresses we are working on */
 {
 int defport;
 {
 int defport;
-int hosts_defer = 0;
-int hosts_fail  = 0;
-int hosts_looked_up = 0;
-int hosts_retry = 0;
-int hosts_serial = 0;
-int hosts_total = 0;
-int total_hosts_tried = 0;
+int hosts_defer = 0, hosts_fail  = 0, hosts_looked_up = 0;
+int hosts_retry = 0, hosts_serial = 0, hosts_total = 0, total_hosts_tried = 0;
 BOOL expired = TRUE;
 BOOL expired = TRUE;
-uschar *expanded_hosts = NULL;
-uschar *pistring;
-uschar *tid = string_sprintf("%s transport", tblock->name);
-smtp_transport_options_block *ob = SOB tblock->options_block;
-host_item *hostlist = addrlist->host_list;
-host_item *host = NULL;
+uschar * expanded_hosts = NULL, * pistring;
+uschar * tid = string_sprintf("%s transport", tblock->name);
+smtp_transport_options_block * ob = SOB tblock->options_block;
+host_item * hostlist = addrlist->host_list, * host = NULL;
 
 DEBUG(D_transport)
   {
 
 DEBUG(D_transport)
   {
@@ -4585,12 +5155,28 @@ DEBUG(D_transport)
       cutthrough.cctx.sock >= 0 ? cutthrough.cctx.sock : 0);
   }
 
       cutthrough.cctx.sock >= 0 ? cutthrough.cctx.sock : 0);
   }
 
+/* Check the restrictions on line length */
+
+if (max_received_linelength > ob->message_linelength_limit)
+  {
+  struct timeval now;
+  gettimeofday(&now, NULL);
+
+  for (address_item * addr = addrlist; addr; addr = addr->next)
+    if (addr->transport_return == DEFER)
+      addr->transport_return = PENDING_DEFER;
+
+  set_errno_nohost(addrlist, ERRNO_SMTPFORMAT,
+    US"message has lines too long for transport", FAIL, TRUE, &now);
+  goto END_TRANSPORT;
+  }
+
 /* Set the flag requesting that these hosts be added to the waiting
 database if the delivery fails temporarily or if we are running with
 queue_smtp or a 2-stage queue run. This gets unset for certain
 kinds of error, typically those that are specific to the message. */
 
 /* Set the flag requesting that these hosts be added to the waiting
 database if the delivery fails temporarily or if we are running with
 queue_smtp or a 2-stage queue run. This gets unset for certain
 kinds of error, typically those that are specific to the message. */
 
-update_waiting =  TRUE;
+update_waiting = TRUE;
 
 /* If a host list is not defined for the addresses - they must all have the
 same one in order to be passed to a single transport - or if the transport has
 
 /* If a host list is not defined for the addresses - they must all have the
 same one in order to be passed to a single transport - or if the transport has
@@ -4622,7 +5208,7 @@ if (!hostlist || (ob->hosts_override && ob->hosts))
     {
     uschar *s = ob->hosts;
 
     {
     uschar *s = ob->hosts;
 
-    if (Ustrchr(s, '$') != NULL)
+    if (Ustrchr(s, '$'))
       {
       if (!(expanded_hosts = expand_string(s)))
         {
       {
       if (!(expanded_hosts = expand_string(s)))
         {
@@ -4778,16 +5364,23 @@ retry_non_continued:
        && total_hosts_tried < ob->hosts_max_try_hardlimit;
        host = nexthost)
     {
        && total_hosts_tried < ob->hosts_max_try_hardlimit;
        host = nexthost)
     {
-    int rc;
-    int host_af;
-    BOOL host_is_expired = FALSE;
-    BOOL message_defer = FALSE;
-    BOOL some_deferred = FALSE;
-    address_item *first_addr = NULL;
-    uschar *interface = NULL;
-    uschar *retry_host_key = NULL;
-    uschar *retry_message_key = NULL;
-    uschar *serialize_key = NULL;
+    int rc, host_af;
+    BOOL host_is_expired = FALSE, message_defer = FALSE, some_deferred = FALSE;
+    address_item * first_addr = NULL;
+    uschar * interface = NULL;
+    uschar * retry_host_key = NULL, * retry_message_key = NULL;
+    uschar * serialize_key = NULL;
+
+    /* Deal slightly better with a possible Linux kernel bug that results
+    in intermittent TFO-conn fails deep into the TCP flow.  Bug 2907 tracks.
+    Hack: Clear TFO option for any further hosts on this tpt run. */
+
+    if (total_hosts_tried > 0)
+      {
+      DEBUG(D_transport|D_acl|D_v)
+       debug_printf("Clearing TFO as not first host for message\n");
+      ob->hosts_try_fastopen = US"";
+      }
 
     /* Default next host is next host. :-) But this can vary if the
     hosts_max_try limit is hit (see below). It may also be reset if a host
 
     /* Default next host is next host. :-) But this can vary if the
     hosts_max_try limit is hit (see below). It may also be reset if a host
@@ -4925,15 +5518,19 @@ retry_non_continued:
     were not in it. We don't want to hold up all SMTP deliveries! Except when
     doing a two-stage queue run, don't do this if forcing. */
 
     were not in it. We don't want to hold up all SMTP deliveries! Except when
     doing a two-stage queue run, don't do this if forcing. */
 
-    if ((!f.deliver_force || f.queue_2stage) && (f.queue_smtp ||
-        match_isinlist(addrlist->domain,
-         (const uschar **)&queue_smtp_domains, 0,
-          &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL) == OK))
+    if (  (!f.deliver_force || f.queue_2stage)
+       && (  f.queue_smtp
+         || match_isinlist(addrlist->domain,
+             CUSS &queue_smtp_domains, 0,
+             &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL) == OK)
+       )
       {
       {
+      DEBUG(D_transport) debug_printf("first-pass routing only\n");
       expired = FALSE;
       for (address_item * addr = addrlist; addr; addr = addr->next)
         if (addr->transport_return == DEFER)
       expired = FALSE;
       for (address_item * addr = addrlist; addr; addr = addr->next)
         if (addr->transport_return == DEFER)
-         addr->message = US"domain matches queue_smtp_domains, or -odqs set";
+         addr->message = US"first-pass only routing due to -odqs, "
+                           "queue_smtp_domains or control=queue";
       continue;      /* With next host */
       }
 
       continue;      /* With next host */
       }
 
@@ -4966,7 +5563,7 @@ retry_non_continued:
     because connections to the same host from a different interface should be
     treated separately. */
 
     because connections to the same host from a different interface should be
     treated separately. */
 
-    host_af = Ustrchr(host->address, ':') == NULL ? AF_INET : AF_INET6;
+    host_af = Ustrchr(host->address, ':') ? AF_INET6 : AF_INET;
       {
       uschar * s = ob->interface;
       if (s && *s)
       {
       uschar * s = ob->interface;
       if (s && *s)
@@ -5079,7 +5676,14 @@ retry_non_continued:
     out the result of previous attempts, and finding the first address that
     is still to be delivered. */
 
     out the result of previous attempts, and finding the first address that
     is still to be delivered. */
 
-    first_addr = prepare_addresses(addrlist, host);
+    if (!(first_addr = prepare_addresses(addrlist, host)))
+      {
+      /* 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");
+      goto END_TRANSPORT;
+      }
 
     DEBUG(D_transport) debug_printf("delivering %s to %s [%s] (%s%s)\n",
       message_id, host->name, host->address, addrlist->address,
 
     DEBUG(D_transport) debug_printf("delivering %s to %s [%s] (%s%s)\n",
       message_id, host->name, host->address, addrlist->address,
@@ -5094,7 +5698,9 @@ retry_non_continued:
 
     if (f.dont_deliver)
       {
 
     if (f.dont_deliver)
       {
-      set_errno_nohost(addrlist, 0, NULL, OK, FALSE);
+      struct timeval now;
+      gettimeofday(&now, NULL);
+      set_errno_nohost(addrlist, 0, NULL, OK, FALSE, &now);
       for (address_item * addr = addrlist; addr; addr = addr->next)
         {
         addr->host_used = host;
       for (address_item * addr = addrlist; addr; addr = addr->next)
         {
         addr->host_used = host;
@@ -5129,11 +5735,11 @@ retry_non_continued:
       {
       host_item * thost;
       /* Make a copy of the host if it is local to this invocation
       {
       host_item * thost;
       /* Make a copy of the host if it is local to this invocation
-       of the transport. */
+      of the transport. */
 
       if (expanded_hosts)
        {
 
       if (expanded_hosts)
        {
-       thost = store_get(sizeof(host_item), FALSE);
+       thost = store_get(sizeof(host_item), GET_UNTAINTED);
        *thost = *host;
        thost->name = string_copy(host->name);
        thost->address = string_copy(host->address);
        *thost = *host;
        thost->name = string_copy(host->name);
        thost->address = string_copy(host->address);
@@ -5219,7 +5825,12 @@ retry_non_continued:
 #ifndef DISABLE_EVENT
       /* If the last host gave a defer raise a per-message event */
 
 #ifndef DISABLE_EVENT
       /* If the last host gave a defer raise a per-message event */
 
-      if (!nexthost && (message_defer || rc == DEFER))
+      if (  !(  nexthost
+            && unexpired_hosts_tried < ob->hosts_max_try
+            && total_hosts_tried < ob->hosts_max_try_hardlimit
+            )
+         && (message_defer || rc == DEFER)
+        )
        deferred_event_raise(first_addr, host, US"msg:defer");
 #endif
       }
        deferred_event_raise(first_addr, host, US"msg:defer");
 #endif
       }
@@ -5318,20 +5929,14 @@ retry_non_continued:
     if (rc == OK)
       for (address_item * addr = addrlist; addr; addr = addr->next)
         if (addr->transport_return == DEFER)
     if (rc == OK)
       for (address_item * addr = addrlist; addr; addr = addr->next)
         if (addr->transport_return == DEFER)
-          {
-          some_deferred = TRUE;
-          break;
-          }
+          { some_deferred = TRUE; break; }
 
     /* If no addresses deferred or the result was ERROR, return. We do this for
     ERROR because a failing filter set-up or add_headers expansion is likely to
     fail for any host we try. */
 
     if (rc == ERROR || (rc == OK && !some_deferred))
 
     /* If no addresses deferred or the result was ERROR, return. We do this for
     ERROR because a failing filter set-up or add_headers expansion is likely to
     fail for any host we try. */
 
     if (rc == ERROR || (rc == OK && !some_deferred))
-      {
-      DEBUG(D_transport) debug_printf("Leaving %s transport\n", tblock->name);
-      return TRUE;    /* Each address has its status */
-      }
+      goto END_TRANSPORT;
 
     /* If the result was DEFER or some individual addresses deferred, let
     the loop run to try other hosts with the deferred addresses, except for the
 
     /* If the result was DEFER or some individual addresses deferred, let
     the loop run to try other hosts with the deferred addresses, except for the
@@ -5351,7 +5956,7 @@ retry_non_continued:
     if ((rc == DEFER || some_deferred) && nexthost)
       {
       BOOL timedout;
     if ((rc == DEFER || some_deferred) && nexthost)
       {
       BOOL timedout;
-      retry_config *retry = retry_find_config(host->name, NULL, 0, 0);
+      retry_config * retry = retry_find_config(host->name, NULL, 0, 0);
 
       if (retry && retry->rules)
         {
 
       if (retry && retry->rules)
         {