Tidying: defines for sync_responses() retcodes
[exim.git] / src / src / transports / smtp.c
index 4d6135be71f0d024609c7fc4560eac3d03c9aefe..194671272523fce8dd1a56484aa69295678f3f13 100644 (file)
@@ -2,9 +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 */
 /* 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) },
@@ -110,7 +114,7 @@ optionlist smtp_transport_options[] = {
   { "interface",            opt_stringptr, LOFF(interface) },
   { "keepalive",            opt_bool,     LOFF(keepalive) },
   { "lmtp_ignore_quota",    opt_bool,     LOFF(lmtp_ignore_quota) },
   { "interface",            opt_stringptr, LOFF(interface) },
   { "keepalive",            opt_bool,     LOFF(keepalive) },
   { "lmtp_ignore_quota",    opt_bool,     LOFF(lmtp_ignore_quota) },
-  { "max_rcpt",             opt_int | opt_public,
+  { "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,
       OPT_OFF(transport_instance, max_addresses) },
   { "message_linelength_limit", opt_int,   LOFF(message_linelength_limit) },
   { "multi_domain",         opt_expand_bool | opt_public,
@@ -231,6 +235,17 @@ 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
 
 /******************************************************************************/
 
 
 /******************************************************************************/
 
@@ -269,7 +284,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);
 }
 
 
 }
 
 
@@ -345,7 +360,7 @@ 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 */
 int old_pool = store_pool;
 
 /* Retry_use_local_part defaults FALSE if unset */
@@ -619,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
@@ -652,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;
@@ -758,6 +772,29 @@ 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
 /******************************************************************************/
 
 #ifdef EXPERIMENTAL_ESMTP_LIMITS
@@ -827,10 +864,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 +900,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 +912,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 +920,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);
@@ -900,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);
   }
@@ -930,7 +979,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 +987,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 +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)
@@ -970,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]);
@@ -985,7 +1034,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)
@@ -1026,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))
@@ -1034,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)
@@ -1062,10 +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
+# 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)
     {
@@ -1082,11 +1140,14 @@ if (pending_EHLO)
       write_ehlo_cache_entry(sx);
       }
     else
       write_ehlo_cache_entry(sx);
       }
     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
+# 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 +1171,7 @@ if (pending_EHLO)
       invalidate_ehlo_cache_entry(sx);
       }
     }
       invalidate_ehlo_cache_entry(sx);
       }
     }
-#endif
+# endif
   }
 return OK;
 
   }
 return OK;
 
@@ -1175,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
@@ -1193,7 +1254,7 @@ if (sx->pending_MAIL)
     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)
     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;
@@ -1213,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;
     }
   }
 
     }
   }
 
@@ -1227,7 +1288,7 @@ 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;
@@ -1236,7 +1297,7 @@ while (count-- > 0)
   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;
@@ -1255,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 */
 
@@ -1266,7 +1327,7 @@ while (count-- > 0)
     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;
     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;
-    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
@@ -1284,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. */
@@ -1304,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 */
@@ -1324,7 +1385,7 @@ 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)            /* initialised at MAIL-ack above */
            {
          {
          if (!sx->RCPT_452)            /* initialised at MAIL-ack above */
            {
@@ -1374,7 +1435,7 @@ while (count-- > 0)
       }
     }
   if (count && !(addr = addr->next))
       }
     }
   if (count && !(addr = addr->next))
-    return -2;
+    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
@@ -1396,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 "
@@ -1444,7 +1505,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 +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
@@ -1531,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? */
 
@@ -1730,7 +1806,7 @@ return FALSE;
 
 typedef struct smtp_compare_s
 {
 
 typedef struct smtp_compare_s
 {
-    uschar *                   current_sender_address;
+    const uschar *             current_sender_address;
     struct transport_instance *        tblock;
 } smtp_compare_t;
 
     struct transport_instance *        tblock;
 } smtp_compare_t;
 
@@ -1740,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"";
@@ -1748,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;
 
@@ -1817,57 +1893,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;
 }
@@ -1943,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)
@@ -1992,6 +2068,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        *
 *************************************************/
@@ -2041,8 +2149,9 @@ sx->dane_required =
   verify_check_given_host(CUSS &ob->hosts_require_dane, sx->conn_args.host) == OK;
 #endif
 
   verify_check_given_host(CUSS &ob->hosts_require_dane, sx->conn_args.host) == OK;
 #endif
 
-if ((sx->max_mail = sx->conn_args.tblock->connection_max_messages) == 0) sx->max_mail = 999999;
-if ((sx->max_rcpt = sx->conn_args.tblock->max_addresses) == 0)           sx->max_rcpt = 999999;
+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;
 
@@ -2101,33 +2210,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
@@ -2194,28 +2284,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)
       {
@@ -2267,7 +2337,7 @@ 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)
@@ -2419,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
@@ -2453,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");
@@ -2510,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
@@ -2539,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
       }
 
@@ -2636,14 +2723,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) != 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
 
@@ -2672,6 +2762,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
@@ -2773,8 +2864,9 @@ if (tls_out.active.sock >= 0)
 #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;
 #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 = 999999;
-  if ((sx->max_rcpt = sx->conn_args.tblock->max_addresses) == 0)          sx->max_rcpt = 999999;
+  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
 
   sx->single_rcpt_domain = FALSE;
 #endif
 
@@ -3358,7 +3450,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;
 
@@ -3429,7 +3521,7 @@ for (addr = sx->first_addr, address_count = 0, pipe_limit = 100;
   {
   int cmds_sent;
   BOOL no_flush;
   {
   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 */
 
 #ifdef EXPERIMENTAL_ESMTP_LIMITS
   if (  sx->single_rcpt_domain                                 /* restriction on domains */
@@ -3487,14 +3579,14 @@ for (addr = sx->first_addr, address_count = 0, pipe_limit = 100;
     {
     switch(sync_responses(sx, 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. */
@@ -3508,13 +3600,13 @@ for (addr = sx->first_addr, address_count = 0, pipe_limit = 100;
                }
              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 -3;  /* Timeout on RCPT */
+      case RESP_RCPT_ERROR:        return -2;  /* non-MAIL read i/o error */
+      default:                     return -1;  /* 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 -1;  /* non-2xx for pipelined banner or EHLO */
+      case RESP_EHLO_ERR_TLS:      return -1;  /* TLS first-read error */
 #endif
       }
     }
 #endif
       }
     }
@@ -3546,13 +3638,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}};
@@ -3563,7 +3656,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
   {
@@ -3771,7 +3864,7 @@ 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, FALSE,
+       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,
        string_sprintf("%.50s transport filter", tblock->name), NULL))
     {
     set_errno_nohost(addrlist->next, addrlist->basic_errno, addrlist->message, DEFER,
@@ -3874,17 +3967,19 @@ if (  !(smtp_peer_options & OPTION_CHUNKING)
   int count = smtp_write_command(sx, SCMD_FLUSH, "DATA\r\n");
 
   if (count < 0) goto SEND_FAILED;
   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 */
@@ -3962,7 +4057,7 @@ else
     if (smtp_peer_options & OPTION_CHUNKING)
       debug_printf("         will write message using CHUNKING\n");
     else
     if (smtp_peer_options & OPTION_CHUNKING)
       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
@@ -4114,22 +4209,23 @@ else
     /* 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 */
       }
     }
 
       }
     }
 
@@ -4649,7 +4745,10 @@ 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,
            sx->send_tlsclose ? TLS_SHUTDOWN_WAIT : TLS_SHUTDOWN_WONLY);
 
          tls_close(sx->cctx.tls_ctx,
            sx->send_tlsclose ? TLS_SHUTDOWN_WAIT : TLS_SHUTDOWN_WONLY);
@@ -4714,7 +4813,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 */
@@ -4921,7 +5020,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];
@@ -5266,6 +5365,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). */