tidying
[exim.git] / src / src / transports / smtp.c
index 6a979a243811f53e206f136b0ffd9a92f47cc8e3..ed5f83b3ee939c294209909abe354fdb007e10c8 100644 (file)
@@ -2,9 +2,10 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
+/* Copyright (c) The Exim Maintainers 2020 - 2022 */
 /* Copyright (c) University of Cambridge 1995 - 2018 */
 /* Copyright (c) University of Cambridge 1995 - 2018 */
-/* Copyright (c) The Exim Maintainers 2020 - 2021 */
 /* See the file NOTICE for conditions of use and distribution. */
 /* 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"
@@ -64,6 +65,9 @@ optionlist smtp_transport_options[] = {
   { "final_timeout",        opt_time,     LOFF(final_timeout) },
   { "gethostbyname",        opt_bool,     LOFF(gethostbyname) },
   { "helo_data",            opt_stringptr, LOFF(helo_data) },
   { "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) },
   { "hosts",                opt_stringptr, LOFF(hosts) },
   { "hosts_avoid_esmtp",    opt_stringptr, LOFF(hosts_avoid_esmtp) },
   { "hosts_avoid_pipelining", opt_stringptr, LOFF(hosts_avoid_pipelining) },
@@ -199,6 +203,9 @@ smtp_transport_options_block smtp_transport_option_defaults = {
   .tls_tempfail_tryclear =     TRUE,
   .tls_try_verify_hosts =      US"*",
   .tls_verify_cert_hostnames = US"*",
   .tls_tempfail_tryclear =     TRUE,
   .tls_try_verify_hosts =      US"*",
   .tls_verify_cert_hostnames = US"*",
+# ifndef DISABLE_TLS_RESUME
+  .host_name_extract =         US"${if and {{match{$host}{.outlook.com\\$}} {match{$item}{\\N^250-([\\w.]+)\\s\\N}}} {$1}}",
+# endif
 #endif
 #ifdef SUPPORT_I18N
   .utf8_downconvert =          US"-1",
 #endif
 #ifdef SUPPORT_I18N
   .utf8_downconvert =          US"-1",
@@ -269,7 +276,7 @@ struct list
 
 for (struct list * l = list; l < list + nelem(list); l++)
   if (!*l->re)
 
 for (struct list * l = list; l < list + nelem(list); l++)
   if (!*l->re)
-    *l->re = regex_must_compile(l->string, FALSE, TRUE);
+    *l->re = regex_must_compile(l->string, MCS_NOFLAGS, TRUE);
 }
 
 
 }
 
 
@@ -758,6 +765,28 @@ return TRUE;
 }
 
 
 }
 
 
+/* 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, smtp_transport_options_block * ob)
+{
+#if !defined(DISABLE_TLS) && !defined(DISABLE_TLS_RESUME)
+const uschar * s;
+uschar * save_item = iterate_item;
+
+if (sx->conn_args.have_lbserver)
+  return;
+iterate_item = sx->buffer;
+s = expand_cstring(ob->host_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
 /******************************************************************************/
 
 #ifdef EXPERIMENTAL_ESMTP_LIMITS
@@ -827,10 +856,12 @@ ehlo_limits_apply(sx, sx->peer_limit_mail, sx->peer_limit_rcpt,
 static void
 ehlo_cache_limits_apply(smtp_context * sx)
 {
 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);
 ehlo_limits_apply(sx, sx->ehlo_resp.limit_mail, sx->ehlo_resp.limit_rcpt,
   sx->ehlo_resp.limit_rcptdom);
+# endif
 }
 }
-#endif
+#endif /*EXPERIMENTAL_ESMTP_LIMITS*/
 
 /******************************************************************************/
 
 
 /******************************************************************************/
 
@@ -861,11 +892,11 @@ write_ehlo_cache_entry(smtp_context * sx)
 {
 open_db dbblock, * dbm_file;
 
 {
 open_db dbblock, * dbm_file;
 
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+# 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;
 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
+# endif
 
 if ((dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE, TRUE)))
   {
 
 if ((dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE, TRUE)))
   {
@@ -873,7 +904,7 @@ if ((dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE, TRUE)))
   dbdata_ehlo_resp er = { .data = sx->ehlo_resp };
 
   HDEBUG(D_transport)
   dbdata_ehlo_resp er = { .data = sx->ehlo_resp };
 
   HDEBUG(D_transport)
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+# 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,
     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,
@@ -881,7 +912,7 @@ if ((dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE, TRUE)))
        sx->ehlo_resp.limit_mail, sx->ehlo_resp.limit_rcpt,
        sx->ehlo_resp.limit_rcptdom);
     else
        sx->ehlo_resp.limit_mail, sx->ehlo_resp.limit_rcpt,
        sx->ehlo_resp.limit_rcptdom);
     else
-#endif
+# 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);
       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);
@@ -930,7 +961,7 @@ else
   else
     {
     DEBUG(D_transport)
   else
     {
     DEBUG(D_transport)
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+# 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",
       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",
@@ -938,16 +969,16 @@ else
          er->data.crypted_features, er->data.crypted_auths,
          er->data.limit_mail, er->data.limit_rcpt, er->data.limit_rcptdom);
       else
          er->data.crypted_features, er->data.crypted_auths,
          er->data.limit_mail, er->data.limit_rcpt, er->data.limit_rcptdom);
       else
-#endif
+# 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;
        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;
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+# ifdef EXPERIMENTAL_ESMTP_LIMITS
     ehlo_cache_limits_apply(sx);
     ehlo_cache_limits_apply(sx);
-#endif
+# endif
     dbfn_close(dbm_file);
     return TRUE;
     }
     dbfn_close(dbm_file);
     return TRUE;
     }
@@ -959,7 +990,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)
@@ -970,7 +1001,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]);
@@ -985,7 +1016,7 @@ for (au = auths, authnum = 0; au; au = au->next, authnum++) if (au->client)
   }
 
 DEBUG(D_transport)
   }
 
 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)
@@ -1034,6 +1065,8 @@ 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 ? */
+  ehlo_response_lbserver(sx, sx->conn_args.ob);
   }
 
 if (pending_EHLO)
   }
 
 if (pending_EHLO)
@@ -1062,10 +1095,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
+# ifdef EXPERIMENTAL_ESMTP_LIMITS
   if (tls_out.active.sock >= 0 || !(peer_offered & OPTION_TLS))
     ehlo_response_limits_read(sx);
   if (tls_out.active.sock >= 0 || !(peer_offered & OPTION_TLS))
     ehlo_response_limits_read(sx);
-#endif
+# 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)
     {
@@ -1086,7 +1119,7 @@ if (pending_EHLO)
 
     return OK;         /* just carry on */
     }
 
     return OK;         /* just carry on */
     }
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+# 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 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. */
@@ -1110,7 +1143,7 @@ if (pending_EHLO)
       invalidate_ehlo_cache_entry(sx);
       }
     }
       invalidate_ehlo_cache_entry(sx);
       }
     }
-#endif
+# endif
   }
 return OK;
 
   }
 return OK;
 
@@ -1444,7 +1477,7 @@ rc = (au->info->clientcode)(au, sx, ob->command_timeout,
                            sx->buffer, sizeof(sx->buffer));
 sx->outblock.authenticating = FALSE;
 driver_srcfile = authenticator_name = NULL; driver_srcline = 0;
                            sx->buffer, sizeof(sx->buffer));
 sx->outblock.authenticating = FALSE;
 driver_srcfile = authenticator_name = NULL; driver_srcline = 0;
-DEBUG(D_transport) debug_printf("%s authenticator yielded %d\n", au->name, rc);
+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
@@ -1468,10 +1501,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
@@ -1531,7 +1579,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? */
 
@@ -1817,57 +1865,57 @@ pcre2_match_data * md = pcre2_match_data_create(1, pcre_gen_ctx);
 #ifndef DISABLE_TLS
 if (  checks & OPTION_TLS
    && pcre2_match(regex_STARTTLS,
 #ifndef DISABLE_TLS
 if (  checks & OPTION_TLS
    && pcre2_match(regex_STARTTLS,
-                 (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0)
+                 (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_gen_mtc_ctx) < 0)
 #endif
   checks &= ~OPTION_TLS;
 
 if (  checks & OPTION_IGNQ
    && pcre2_match(regex_IGNOREQUOTA,
 #endif
   checks &= ~OPTION_TLS;
 
 if (  checks & OPTION_IGNQ
    && pcre2_match(regex_IGNOREQUOTA,
-                 (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0)
+                 (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_gen_mtc_ctx) < 0)
   checks &= ~OPTION_IGNQ;
 
 if (  checks & OPTION_CHUNKING
    && pcre2_match(regex_CHUNKING,
   checks &= ~OPTION_IGNQ;
 
 if (  checks & OPTION_CHUNKING
    && pcre2_match(regex_CHUNKING,
-                 (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0)
+                 (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_gen_mtc_ctx) < 0)
   checks &= ~OPTION_CHUNKING;
 
 #ifndef DISABLE_PRDR
 if (  checks & OPTION_PRDR
    && pcre2_match(regex_PRDR,
   checks &= ~OPTION_CHUNKING;
 
 #ifndef DISABLE_PRDR
 if (  checks & OPTION_PRDR
    && pcre2_match(regex_PRDR,
-                 (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0)
+                 (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_gen_mtc_ctx) < 0)
 #endif
   checks &= ~OPTION_PRDR;
 
 #ifdef SUPPORT_I18N
 if (  checks & OPTION_UTF8
    && pcre2_match(regex_UTF8,
 #endif
   checks &= ~OPTION_PRDR;
 
 #ifdef SUPPORT_I18N
 if (  checks & OPTION_UTF8
    && pcre2_match(regex_UTF8,
-                 (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0)
+                 (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_gen_mtc_ctx) < 0)
 #endif
   checks &= ~OPTION_UTF8;
 
 if (  checks & OPTION_DSN
    && pcre2_match(regex_DSN,
 #endif
   checks &= ~OPTION_UTF8;
 
 if (  checks & OPTION_DSN
    && pcre2_match(regex_DSN,
-                 (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0)
+                 (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_gen_mtc_ctx) < 0)
   checks &= ~OPTION_DSN;
 
 if (  checks & OPTION_PIPE
    && pcre2_match(regex_PIPELINING,
   checks &= ~OPTION_DSN;
 
 if (  checks & OPTION_PIPE
    && pcre2_match(regex_PIPELINING,
-                 (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0)
+                 (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_gen_mtc_ctx) < 0)
   checks &= ~OPTION_PIPE;
 
 if (  checks & OPTION_SIZE
    && pcre2_match(regex_SIZE,
   checks &= ~OPTION_PIPE;
 
 if (  checks & OPTION_SIZE
    && pcre2_match(regex_SIZE,
-                 (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0)
+                 (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
    && pcre2_match(regex_EARLY_PIPE,
   checks &= ~OPTION_SIZE;
 
 #ifndef DISABLE_PIPE_CONNECT
 if (  checks & OPTION_EARLY_PIPE
    && pcre2_match(regex_EARLY_PIPE,
-                 (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0)
+                 (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);
+/* 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;
 }
@@ -1992,6 +2040,38 @@ 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
+
+
 /*************************************************
 *       Make connection for given message        *
 *************************************************/
 /*************************************************
 *       Make connection for given message        *
 *************************************************/
@@ -2101,33 +2181,14 @@ if (continue_hostname && continue_proxy_cipher)
   const uschar * sni = US"";
 
 # ifdef SUPPORT_DANE
   const uschar * sni = US"";
 
 # ifdef SUPPORT_DANE
-  /* Check if the message will be DANE-verified; if so force its SNI */
+  /* 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
 
   tls_out.dane_verified = FALSE;
   smtp_port_for_connect(sx->conn_args.host, sx->port);
   if (  sx->conn_args.host->dnssec == DS_YES
-     && (  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;
-      }
+     && (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
 # endif
 
   /* If the SNI or the DANE status required for the new message differs from the
@@ -2155,7 +2216,7 @@ if (continue_hostname && continue_proxy_cipher)
     DEBUG(D_transport)
       debug_printf("Closing proxied-TLS connection due to SNI mismatch\n");
 
     DEBUG(D_transport)
       debug_printf("Closing proxied-TLS connection due to SNI mismatch\n");
 
-    HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP>> QUIT\n");
+    smtp_debug_cmd(US"QUIT", 0);
     write(0, "QUIT\r\n", 6);
     close(0);
     continue_hostname = continue_proxy_cipher = NULL;
     write(0, "QUIT\r\n", 6);
     close(0);
     continue_hostname = continue_proxy_cipher = NULL;
@@ -2194,28 +2255,8 @@ 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;      /* 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;
-         }
+      if ((rc = check_force_dane_conn(sx, ob)) != OK)
+       return rc;
       }
     else if (sx->dane_required)
       {
       }
     else if (sx->dane_required)
       {
@@ -2239,6 +2280,9 @@ if (!continue_hostname)
   sx->peer_limit_mail = sx->peer_limit_rcpt = sx->peer_limit_rcptdom =
 #endif
   sx->avoid_option = sx->peer_offered = smtp_peer_options = 0;
   sx->peer_limit_mail = sx->peer_limit_rcpt = sx->peer_limit_rcptdom =
 #endif
   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,
@@ -2248,6 +2292,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")
        )
@@ -2263,11 +2308,14 @@ if (!continue_hostname)
        }
       }
     else DEBUG(D_transport)
        }
       }
     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)
 
 PIPE_CONNECT_RETRY:
   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
     {
@@ -2292,9 +2340,10 @@ PIPE_CONNECT_RETRY:
     }
   /* 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)))
@@ -2531,12 +2580,13 @@ 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);
          }
        }
 #endif
          sx->ehlo_resp.cleartext_auths = study_ehlo_auths(sx);
          write_ehlo_cache_entry(sx);
          }
        }
 #endif
+      ehlo_response_lbserver(sx, ob);
       }
 
   /* Set tls_offered if the response to EHLO specifies support for STARTTLS. */
       }
 
   /* Set tls_offered if the response to EHLO specifies support for STARTTLS. */
@@ -2628,14 +2678,17 @@ if (  smtp_peer_options & OPTION_TLS
   the response for the STARTTLS we just sent alone.  On fail, assume wrong
   cached capability and retry with the pipelining disabled. */
 
   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");
-    close(sx->cctx.sock);
-    sx->cctx.sock = -1;
-    sx->early_pipe_active = FALSE;
-    goto PIPE_CONNECT_RETRY;
+    if (sync_responses(sx, 2, 0) != 0)
+      {
+      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
 
@@ -2664,6 +2717,7 @@ 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
     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
@@ -2931,18 +2985,18 @@ if (   !continue_hostname
       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
 
@@ -3170,6 +3224,7 @@ sx->cctx.sock = -1;
 (void) event_raise(sx->conn_args.tblock->event_action, US"tcp:close", NULL, NULL);
 #endif
 
 (void) event_raise(sx->conn_args.tblock->event_action, US"tcp:close", NULL, NULL);
 #endif
 
+smtp_debug_cmd_report();
 continue_transport = NULL;
 continue_hostname = NULL;
 return yield;
 continue_transport = NULL;
 continue_hostname = NULL;
 return yield;
@@ -3212,7 +3267,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)
       {
@@ -3537,13 +3592,14 @@ 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)
 {
 struct pollfd p[2] = {{.fd = tls_out.active.sock, .events = POLLIN},
                      {.fd = pfd[0], .events = POLLIN}};
 {
 struct pollfd p[2] = {{.fd = tls_out.active.sock, .events = POLLIN},
                      {.fd = pfd[0], .events = POLLIN}};
@@ -3554,7 +3610,7 @@ close(pfd[1]);
 if ((rc = exim_fork(US"tls-proxy")))
   _exit(rc < 0 ? EXIT_FAILURE : EXIT_SUCCESS);
 
 if ((rc = exim_fork(US"tls-proxy")))
   _exit(rc < 0 ? EXIT_FAILURE : EXIT_SUCCESS);
 
-set_process_info("proxying TLS connection for continued transport");
+set_process_info("proxying TLS connection for continued transport to %s\n", host);
 
 do
   {
 
 do
   {
@@ -3712,6 +3768,7 @@ sx->port = defport;
 sx->conn_args.interface = interface;
 sx->helo_data = NULL;
 sx->conn_args.tblock = tblock;
 sx->conn_args.interface = interface;
 sx->helo_data = NULL;
 sx->conn_args.tblock = tblock;
+sx->conn_args.sock = -1;
 gettimeofday(&sx->delivery_start, NULL);
 sx->sync_addr = sx->first_addr = addrlist;
 
 gettimeofday(&sx->delivery_start, NULL);
 sx->sync_addr = sx->first_addr = addrlist;
 
@@ -3761,8 +3818,8 @@ 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, TRUE, DEFER, addrlist, FALSE,
+       string_sprintf("%.50s transport filter", tblock->name), NULL))
     {
     set_errno_nohost(addrlist->next, addrlist->basic_errno, addrlist->message, DEFER,
       FALSE, &sx->delivery_start);
     {
     set_errno_nohost(addrlist->next, addrlist->basic_errno, addrlist->message, DEFER,
       FALSE, &sx->delivery_start);
@@ -3773,7 +3830,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)
@@ -3781,7 +3838,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");
     }
   }
@@ -3858,7 +3915,7 @@ 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");
    && (sx->ok || (pipelining_active && !mua_wrapper)))
   {
   int count = smtp_write_command(sx, SCMD_FLUSH, "DATA\r\n");
@@ -3895,7 +3952,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;
@@ -3924,7 +3981,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;
@@ -3949,10 +4006,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
@@ -4001,7 +4058,7 @@ else
   If we can, we want the message-write to not flush (the tail end of) its data out.  */
 
   if (  sx->pipelining_used
   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 || sx->peer_offered & OPTION_CHUNKING)
+     && (sx->ok && sx->completed_addr || smtp_peer_options & OPTION_CHUNKING)
      && sx->send_quit
      && !(sx->first_addr || f.continue_more)
      && f.deliver_firsttime
      && sx->send_quit
      && !(sx->first_addr || f.continue_more)
      && f.deliver_firsttime
@@ -4085,7 +4142,7 @@ else
       sx->send_quit = FALSE;   /* avoid sending it later */
 
 #ifndef DISABLE_TLS
       sx->send_quit = FALSE;   /* avoid sending it later */
 
 #ifndef DISABLE_TLS
-      if (sx->cctx.tls_ctx)    /* need to send TLS Close Notify */
+      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));
        {
 # 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));
@@ -4099,7 +4156,7 @@ else
       }
     }
 
       }
     }
 
-  if (sx->peer_offered & OPTION_CHUNKING && sx->cmd_count > 1)
+  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))
@@ -4272,7 +4329,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
@@ -4429,7 +4486,8 @@ if (!sx->ok)
 # ifndef DISABLE_TLS
        if (sx->cctx.tls_ctx)
          {
 # ifndef DISABLE_TLS
        if (sx->cctx.tls_ctx)
          {
-         tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_WAIT);
+         tls_close(sx->cctx.tls_ctx,
+                   sx->send_tlsclose ? TLS_SHUTDOWN_WAIT : TLS_SHUTDOWN_WONLY);
          sx->cctx.tls_ctx = NULL;
          }
 # endif
          sx->cctx.tls_ctx = NULL;
          }
 # endif
@@ -4638,9 +4696,13 @@ if (sx->completed_addr && sx->ok && sx->send_quit)
            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
            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. */
+           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, TLS_SHUTDOWN_WAIT);
+         tls_close(sx->cctx.tls_ctx,
+           sx->send_tlsclose ? TLS_SHUTDOWN_WAIT : TLS_SHUTDOWN_WONLY);
          sx->send_tlsclose = FALSE;
          sx->cctx.tls_ctx = NULL;
          tls_out.active.sock = -1;
          sx->send_tlsclose = FALSE;
          sx->cctx.tls_ctx = NULL;
          tls_out.active.sock = -1;
@@ -4702,7 +4764,7 @@ if (sx->completed_addr && sx->ok && sx->send_quit)
              {
              /* does not return */
              smtp_proxy_tls(sx->cctx.tls_ctx, sx->buffer, sizeof(sx->buffer), pfd,
              {
              /* does not return */
              smtp_proxy_tls(sx->cctx.tls_ctx, sx->buffer, sizeof(sx->buffer), pfd,
-                             ob->command_timeout);
+                             ob->command_timeout, host->name);
              }
 
            if (pid > 0)                /* parent */
              }
 
            if (pid > 0)                /* parent */
@@ -4742,7 +4804,7 @@ 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
   {                    /* 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)
+  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));
     {
 # 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));
@@ -4797,10 +4859,15 @@ if (sx->send_quit || tcw_done && !tcw)
     while (!sigalrm_seen && n > 0);
     ALARM_CLR(0);
 
     while (!sigalrm_seen && n > 0);
     ALARM_CLR(0);
 
+    if (sx->send_tlsclose)
+      {
 # ifdef EXIM_TCP_CORK
 # ifdef EXIM_TCP_CORK
-    (void) setsockopt(sx->cctx.sock, IPPROTO_TCP, EXIM_TCP_CORK, US &on, sizeof(on));
+      (void) setsockopt(sx->cctx.sock, IPPROTO_TCP, EXIM_TCP_CORK, US &on, sizeof(on));
 # endif
 # endif
-    tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_WAIT);
+      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
     sx->cctx.tls_ctx = NULL;
     }
 #endif
@@ -4824,6 +4891,7 @@ HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP(close)>>\n");
 sx->cctx.sock = -1;
 continue_transport = NULL;
 continue_hostname = NULL;
 sx->cctx.sock = -1;
 continue_transport = NULL;
 continue_hostname = NULL;
+smtp_debug_cmd_report();
 
 #ifndef DISABLE_EVENT
 (void) event_raise(tblock->event_action, US"tcp:close", NULL, NULL);
 
 #ifndef DISABLE_EVENT
 (void) event_raise(tblock->event_action, US"tcp:close", NULL, NULL);
@@ -5248,6 +5316,17 @@ retry_non_continued:
     uschar *retry_message_key = NULL;
     uschar *serialize_key = NULL;
 
     uschar *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
     address is looked up here (in case the host was multihomed). */
     /* 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
     address is looked up here (in case the host was multihomed). */