build: use pkg-config for i18n
[exim.git] / src / src / transports / smtp.c
index ddd6ad7e13e0d0aef2e516bee623e0f4767bc7a2..79bacfc31d56614138d309314d20fab3359f80eb 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) The Exim Maintainers 2020 - 2023 */
+/* Copyright (c) The Exim Maintainers 2020 - 2024 */
 /* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 /* SPDX-License-Identifier: GPL-2.0-or-later */
@@ -32,7 +32,7 @@ optionlist smtp_transport_options[] = {
       LOFF(address_retry_include_sender) },
   { "allow_localhost",      opt_bool,     LOFF(allow_localhost) },
 #ifdef EXPERIMENTAL_ARC
-  { "arc_sign", opt_stringptr,            LOFF(arc_sign) },
+  { "arc_sign",                    opt_stringptr, LOFF(arc_sign) },
 #endif
   { "authenticated_sender", opt_stringptr, LOFF(authenticated_sender) },
   { "authenticated_sender_force", opt_bool, LOFF(authenticated_sender_force) },
@@ -46,6 +46,7 @@ optionlist smtp_transport_options[] = {
   { "data_timeout",         opt_time,     LOFF(data_timeout) },
   { "delay_after_cutoff",   opt_bool,     LOFF(delay_after_cutoff) },
 #ifndef DISABLE_DKIM
+  /*XXX dkim module */
   { "dkim_canon", opt_stringptr,          LOFF(dkim.dkim_canon) },
   { "dkim_domain", opt_stringptr,         LOFF(dkim.dkim_domain) },
   { "dkim_hash", opt_stringptr,                   LOFF(dkim.dkim_hash) },
@@ -159,7 +160,7 @@ int smtp_transport_options_count = nelem(smtp_transport_options);
 
 /* Dummy values */
 smtp_transport_options_block smtp_transport_option_defaults = {0};
-void smtp_transport_init(transport_instance *tblock) {}
+void smtp_transport_init(driver_instance *tblock) {}
 BOOL smtp_transport_entry(transport_instance *tblock, address_item *addr) {return FALSE;}
 void smtp_transport_closedown(transport_instance *tblock) {}
 
@@ -314,7 +315,7 @@ static int
 smtp_transport_setup(transport_instance *tblock, address_item *addrlist,
   transport_feedback *tf, uid_t uid, gid_t gid, uschar **errmsg)
 {
-smtp_transport_options_block *ob = SOB tblock->options_block;
+smtp_transport_options_block * ob = tblock->drinst.options_block;
 
 /* Pass back options if required. This interface is getting very messy. */
 
@@ -358,9 +359,10 @@ Returns:    nothing
 */
 
 void
-smtp_transport_init(transport_instance *tblock)
+smtp_transport_init(driver_instance * t)
 {
-smtp_transport_options_block * ob = SOB tblock->options_block;
+transport_instance * tblock = (transport_instance *)t;
+smtp_transport_options_block * ob = t->options_block;
 int old_pool = store_pool;
 
 /* Retry_use_local_part defaults FALSE if unset */
@@ -387,7 +389,7 @@ if (ob->command_timeout <= 0 || ob->data_timeout <= 0 ||
     ob->final_timeout <= 0)
   log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
     "command, data, or final timeout value is zero for %s transport",
-      tblock->name);
+      t->name);
 
 /* If hosts_override is set and there are local hosts, set the global
 flag that stops verify from showing router hosts. */
@@ -465,6 +467,10 @@ for (address_item * addr = addrlist; addr; addr = addr->next)
     if (host)
       {
       addr->host_used = host;
+      if (continue_sequence > 1)
+       { clearflag(addr, af_new_conn); setflag(addr, af_cont_conn); }
+      else
+       { clearflag(addr, af_cont_conn); setflag(addr, af_new_conn); }
 #ifdef EXPERIMENTAL_DSN_INFO
       if (smtp_greeting)
        {uschar * s = Ustrchr(smtp_greeting, '\n'); if (s) *s = '\0';}
@@ -668,20 +674,23 @@ deferred_event_raise(address_item * addr, host_item * host, uschar * evstr)
 {
 uschar * action = addr->transport->event_action;
 const uschar * save_domain, * save_local;
+const uschar * save_rn, * save_tn;
 
 if (!action)
   return;
 
 save_domain = deliver_domain;
 save_local = deliver_localpart;
+save_rn = router_name;
+save_tn = transport_name;
 
 /*XXX would ip & port already be set up? */
 deliver_host_address = string_copy(host->address);
 deliver_host_port =    host->port == PORT_NONE ? 25 : host->port;
 event_defer_errno =    addr->basic_errno;
 
-router_name =    addr->router->name;
-transport_name = addr->transport->name;
+router_name =    addr->router->drinst.name;
+transport_name = addr->transport->drinst.name;
 deliver_domain = addr->domain;
 deliver_localpart = addr->local_part;
 
@@ -697,7 +706,8 @@ deliver_localpart = addr->local_part;
 
 deliver_localpart = save_local;
 deliver_domain =    save_domain;
-router_name = transport_name = NULL;
+router_name = save_rn;
+router_name = save_tn;
 }
 #endif
 
@@ -819,17 +829,17 @@ if (regex_match(regex_LIMITS, sx->buffer, -1, &match))
 
     if (strncmpic(s, US"MAILMAX=", 8) == 0)
       {
-      sx->peer_limit_mail = atoi(CS (s += 8));
+      continue_limit_mail = sx->peer_limit_mail = atoi(CS (s += 8));
       while (isdigit(*s)) s++;
       }
     else if (strncmpic(s, US"RCPTMAX=", 8) == 0)
       {
-      sx->peer_limit_rcpt = atoi(CS (s += 8));
+      continue_limit_rcpt = sx->peer_limit_rcpt = atoi(CS (s += 8));
       while (isdigit(*s)) s++;
       }
     else if (strncmpic(s, US"RCPTDOMAINMAX=", 14) == 0)
       {
-      sx->peer_limit_rcptdom = atoi(CS (s += 14));
+      continue_limit_rcptdom = sx->peer_limit_rcptdom = atoi(CS (s += 14));
       while (isdigit(*s)) s++;
       }
     else
@@ -859,16 +869,16 @@ ehlo_limits_apply(sx, sx->peer_limit_mail, sx->peer_limit_rcpt,
   sx->peer_limit_rcptdom);
 }
 
+# ifndef DISABLE_PIPE_CONNECT
 /* Apply values read from cache to the current connection */
 static void
 ehlo_cache_limits_apply(smtp_context * sx)
 {
-# ifndef DISABLE_PIPE_CONNECT
 ehlo_limits_apply(sx, sx->ehlo_resp.limit_mail, sx->ehlo_resp.limit_rcpt,
   sx->ehlo_resp.limit_rcptdom);
-# endif
 }
-#endif /*EXPERIMENTAL_ESMTP_LIMITS*/
+# endif
+#endif /*DISABLE_ESMTP_LIMITS*/
 
 /******************************************************************************/
 
@@ -905,7 +915,7 @@ sx->ehlo_resp.limit_rcpt = sx->peer_limit_rcpt;
 sx->ehlo_resp.limit_rcptdom = sx->peer_limit_rcptdom;
 # endif
 
-if ((dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE, TRUE)))
+if ((dbm_file = dbfn_open(US"misc", O_RDWR|O_CREAT, &dbblock, TRUE, TRUE)))
   {
   uschar * ehlo_resp_key = ehlo_cache_key(sx);
   dbdata_ehlo_resp er = { .data = sx->ehlo_resp };
@@ -935,7 +945,7 @@ invalidate_ehlo_cache_entry(smtp_context * sx)
 open_db dbblock, * dbm_file;
 
 if (  sx->early_pipe_active
-   && (dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE, TRUE)))
+   && (dbm_file = dbfn_open(US"misc", O_RDWR|O_CREAT, &dbblock, TRUE, TRUE)))
   {
   uschar * ehlo_resp_key = ehlo_cache_key(sx);
   HDEBUG(D_transport)
@@ -973,7 +983,7 @@ else
     {
     DEBUG(D_transport) debug_printf("ehlo-resp record too old\n");
     dbfn_close(dbm_file);
-    if ((dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE, TRUE)))
+    if ((dbm_file = dbfn_open(US"misc", O_RDWR|O_CREAT, &dbblock, TRUE, TRUE)))
       dbfn_delete(dbm_file, ehlo_resp_key);
     }
   else
@@ -1024,14 +1034,15 @@ if (!regex_match_and_setup(regex_AUTH, sx->buffer, 0, -1)) return 0;
 expand_nmax = -1;                                              /* reset */
 names = string_copyn(expand_nstring[1], expand_nlength[1]);
 
-for (au = auths, authnum = 0; au; au = au->next, authnum++) if (au->client)
-  {
-  const uschar * list = names;
-  uschar * s;
-  for (int sep = ' '; s = string_nextinlist(&list, &sep, NULL, 0); )
-    if (strcmpic(au->public_name, s) == 0)
-      { authbits |= BIT(authnum); break; }
-  }
+for (au = auths, authnum = 0; au; au = au->drinst.next, authnum++)
+  if (au->client)
+    {
+    const uschar * list = names;
+    uschar * s;
+    for (int sep = ' '; s = string_nextinlist(&list, &sep, NULL, 0); )
+      if (strcmpic(au->public_name, s) == 0)
+       { authbits |= BIT(authnum); break; }
+    }
 
 DEBUG(D_transport)
   debug_printf("server offers %s AUTH, methods '%s', usable-bitmap 0x%04x\n",
@@ -1085,9 +1096,9 @@ if (pending_BANNER)
     if (tls_out.active.sock >= 0) rc = DEFER;
     goto fail;
     }
-  /*XXX EXPERIMENTAL_ESMTP_LIMITS ? */
+  /*XXX DISABLE_ESMTP_LIMITS ? */
 
-# ifndef DISABLE_TLS_RESUME
+# if !defined(DISABLE_TLS) && !defined(DISABLE_TLS_RESUME)
   GET_OPTION("host_name_extract");
   s = ((smtp_transport_options_block *)sx->conn_args.ob)->host_name_extract;
   if (!s) s = HNE_DEFAULT;
@@ -1273,6 +1284,10 @@ if (sx->pending_MAIL)
       {
       while (addr->transport_return != PENDING_DEFER) addr = addr->next;
       addr->host_used = sx->conn_args.host;
+      if (continue_sequence > 1)
+       { clearflag(addr, af_new_conn); setflag(addr, af_cont_conn); }
+      else
+       { clearflag(addr, af_cont_conn); setflag(addr, af_new_conn); }
       addr = addr->next;
       }
     return RESP_MAIL_OR_DATA_ERROR;
@@ -1293,6 +1308,10 @@ while (count-- > 0)
 
   /* The address was accepted */
   addr->host_used = sx->conn_args.host;
+  if (continue_sequence > 1)
+    { clearflag(addr, af_new_conn); setflag(addr, af_cont_conn); }
+  else
+    { clearflag(addr, af_cont_conn); setflag(addr, af_new_conn); }
 
   DEBUG(D_transport) debug_printf("%s expect rcpt for %s\n", __FUNCTION__, addr->address);
   if (smtp_read_response(sx, sx->buffer, sizeof(sx->buffer),
@@ -1497,16 +1516,20 @@ int rc;
 
 /* Set up globals for error messages */
 
-authenticator_name = au->name;
-driver_srcfile = au->srcfile;
-driver_srcline = au->srcline;
+authenticator_name = au->drinst.name;
+driver_srcfile = au->drinst.srcfile;
+driver_srcline = au->drinst.srcline;
 
-sx->outblock.authenticating = TRUE;
-rc = (au->info->clientcode)(au, sx, ob->command_timeout,
-                           sx->buffer, sizeof(sx->buffer));
-sx->outblock.authenticating = FALSE;
+  {
+  auth_info * ai = au->drinst.info;
+  sx->outblock.authenticating = TRUE;
+  rc = (ai->clientcode)(au, sx, ob->command_timeout,
+                             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 %s\n", au->name, rc_names[rc]);
+DEBUG(D_transport) debug_printf("%s authenticator yielded %s\n",
+  au->drinst.name, rc_names[rc]);
 
 /* A temporary authentication failure must hold up delivery to
 this host. After a permanent authentication failure, we carry on
@@ -1517,7 +1540,7 @@ switch(rc)
   {
   case OK:
     f.smtp_authenticated = TRUE;   /* stops the outer loop */
-    client_authenticator = au->name;
+    client_authenticator = au->drinst.name;
     if (au->set_client_id)
       client_authenticated_id = expand_string(au->set_client_id);
     break;
@@ -1537,16 +1560,16 @@ switch(rc)
 #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)))
+      sender_host_authenticated = au->drinst.name;
+      if ((logmsg = event_raise(sx->conn_args.tblock->event_action,
+                               US"auth:fail", sx->buffer, NULL)))
        log_write(0, LOG_MAIN, "%s", logmsg);
       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);
+       au->drinst.name, host->name, host->address, sx->buffer);
     break;
     }
 
@@ -1559,7 +1582,7 @@ switch(rc)
   case CANCELLED:
     if (*sx->buffer != 0)
       log_write(0, LOG_MAIN, "%s authenticator cancelled "
-       "authentication H=%s [%s] %s", au->name, host->name,
+       "authentication H=%s [%s] %s", au->drinst.name, host->name,
        host->address, sx->buffer);
     break;
 
@@ -1653,26 +1676,27 @@ if (  sx->esmtp
 
       for (bitnum = 0, au = auths;
           !f.smtp_authenticated && au && bitnum < 16;
-          bitnum++, au = au->next) if (authbits & BIT(bitnum))
-       {
-       if (  au->client_condition
-          && !expand_check_condition(au->client_condition, au->name,
-                   US"client authenticator"))
+          bitnum++, au = au->drinst.next)
+       if (authbits & BIT(bitnum))
          {
-         DEBUG(D_transport) debug_printf("skipping %s authenticator: %s\n",
-           au->name, "client_condition is false");
-         continue;
-         }
+         if (  au->client_condition
+            && !expand_check_condition(au->client_condition, au->drinst.name,
+                    US"client authenticator"))
+           {
+           DEBUG(D_transport) debug_printf("skipping %s authenticator: %s\n",
+             au->drinst.name, "client_condition is false");
+           continue;
+           }
 
-       /* Found data for a listed mechanism. Call its client entry. Set
-       a flag in the outblock so that data is overwritten after sending so
-       that reflections don't show it. */
+         /* Found data for a listed mechanism. Call its client entry. Set
+         a flag in the outblock so that data is overwritten after sending so
+         that reflections don't show it. */
 
-       fail_reason = US"authentication attempt(s) failed";
+         fail_reason = US"authentication attempt(s) failed";
 
-       if ((rc = try_authenticator(sx, au)) != OK)
-         return rc;
-       }
+         if ((rc = try_authenticator(sx, au)) != OK)
+           return rc;
+         }
       }
     else
 #endif
@@ -1683,17 +1707,18 @@ if (  sx->esmtp
     If one is found, attempt to authenticate by calling its client function.
     */
 
-    for (auth_instance * au = auths; !f.smtp_authenticated && au; au = au->next)
+    for (auth_instance * au = auths; !f.smtp_authenticated && au;
+       au = au->drinst.next)
       {
-      uschar *p = names;
+      uschar * p = names;
 
       if (  !au->client
          || (   au->client_condition
-           &&  !expand_check_condition(au->client_condition, au->name,
+           &&  !expand_check_condition(au->client_condition, au->drinst.name,
                   US"client authenticator")))
        {
        DEBUG(D_transport) debug_printf("skipping %s authenticator: %s\n",
-         au->name,
+         au->drinst.name,
          au->client ? "client_condition is false"
                    : "not configured as a client");
        continue;
@@ -1793,7 +1818,7 @@ if (  (f.smtp_authenticated || ob->authenticated_sender_force)
    && local_authenticated_sender)
   {
   string_format_nt(p, sizeof(sx->buffer) - (p-sx->buffer), " AUTH=%s",
-    auth_xtextencode(local_authenticated_sender,
+    xtextencode(local_authenticated_sender,
       Ustrlen(local_authenticated_sender)));
   client_authenticated_sender = string_copy(local_authenticated_sender);
   }
@@ -1819,6 +1844,7 @@ sender_address, helo_data and tls_certificate if enabled.
 static uschar *
 smtp_local_identity(const uschar * sender, struct transport_instance * tblock)
 {
+smtp_transport_options_block * ob = tblock->drinst.options_block;
 address_item * addr1;
 uschar * if1 = US"";
 uschar * helo1 = US"";
@@ -1827,7 +1853,6 @@ uschar * tlsc1 = US"";
 #endif
 const uschar * save_sender_address = sender_address;
 uschar * local_identity = NULL;
-smtp_transport_options_block * ob = SOB tblock->options_block;
 
 sender_address = sender;
 
@@ -1975,7 +2000,7 @@ static int
 smtp_chunk_cmd_callback(transport_ctx * tctx, unsigned chunk_size,
   unsigned flags)
 {
-smtp_transport_options_block * ob = SOB tctx->tblock->options_block;
+smtp_transport_options_block * ob = tctx->tblock->drinst.options_block;
 smtp_context * sx = tctx->smtp_context;
 int cmd_count = 0;
 int prev_cmd_count;
@@ -2123,7 +2148,7 @@ Returns:          OK    - the connection was made and the delivery attempted;
 int
 smtp_setup_conn(smtp_context * sx, BOOL suppress_tls)
 {
-smtp_transport_options_block * ob = sx->conn_args.tblock->options_block;
+smtp_transport_options_block * ob = sx->conn_args.tblock->drinst.options_block;
 BOOL pass_message = FALSE;
 uschar * message = NULL;
 int yield = OK;
@@ -2244,16 +2269,22 @@ if (continue_hostname && continue_proxy_cipher)
   else
     {
     DEBUG(D_transport)
-      debug_printf("Closing proxied-TLS connection due to SNI mismatch\n");
+# ifdef SUPPORT_DANE
+      if (continue_proxy_dane != sx->conn_args.dane)
+       debug_printf(
+         "Closing proxied-TLS connection due to dane requirement mismatch\n");
+      else
+# endif
+       debug_printf("Closing proxied-TLS connection (SNI '%s') "
+                   "due to SNI mismatch (transport requirement '%s')\n",
+                   continue_proxy_sni, sni);
 
     smtp_debug_cmd(US"QUIT", 0);
     write(0, "QUIT\r\n", 6);
     close(0);
     continue_hostname = continue_proxy_cipher = NULL;
     f.continue_more = FALSE;
-    continue_sequence = 1;     /* Unfortunately, this process cannot affect success log
-                               which is done by delivery proc.  Would have to pass this
-                               back through reporting pipe. */
+    continue_sequence = 1;     /* Ensure proper logging of non-cont-conn */
     }
   }
 #endif /*!DISABLE_TLS*/
@@ -2264,13 +2295,8 @@ specially so they can be identified for retries. */
 
 if (!continue_hostname)
   {
-  if (sx->verify)
-    HDEBUG(D_verify) debug_printf("interface=%s port=%d\n", sx->conn_args.interface, sx->port);
-
-  /* Arrange to report to calling process this is a new connection */
-
-  clearflag(sx->first_addr, af_cont_conn);
-  setflag(sx->first_addr, af_new_conn);
+  if (sx->verify) HDEBUG(D_verify)
+    debug_printf("interface=%s port=%d\n", sx->conn_args.interface, sx->port);
 
   /* Get the actual port the connection will use, into sx->conn_args.host */
 
@@ -2299,6 +2325,8 @@ if (!continue_hostname)
 # endif
       return FAIL;
       }
+      else DEBUG(D_transport)
+       debug_printf("lack of DNSSEC traceability precludes DANE\n");
     }
 #endif /*DANE*/
 
@@ -2310,9 +2338,7 @@ 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;
-#ifndef DISABLE_CLIENT_CMD_LOG
-  client_cmd_log = NULL;
-#endif
+  smtp_debug_cmd_log_init();
 
 #ifndef DISABLE_PIPE_CONNECT
   if (  verify_check_given_host(CUSS &ob->hosts_pipe_connect,
@@ -2534,7 +2560,8 @@ goto SEND_QUIT;
       if (  (ob->hosts_require_auth || ob->hosts_try_auth)
         && f.smtp_in_early_pipe_no_auth)
        {
-       DEBUG(D_transport) debug_printf("may need to auth, so pipeline no further\n");
+       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) != RESP_NOERROR)
@@ -2625,13 +2652,14 @@ goto SEND_QUIT;
        if (  (sx->peer_offered & (OPTION_PIPE | OPTION_EARLY_PIPE))
           == (OPTION_PIPE | OPTION_EARLY_PIPE))
          {
-         DEBUG(D_transport) debug_printf("PIPECONNECT 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
-#ifndef DISABLE_TLS_RESUME
+#if !defined(DISABLE_TLS) && !defined(DISABLE_TLS_RESUME)
       GET_OPTION("host_name_extract");
       if (!(s = ob->host_name_extract)) s = HNE_DEFAULT;
       ehlo_response_lbserver(sx, s);
@@ -2646,17 +2674,15 @@ goto SEND_QUIT;
     }
   }
 
-/* For continuing deliveries down the same channel, having re-exec'd  the socket
+/* For continuing deliveries down the same channel, the socket
 is the standard input; for a socket held open from verify it is recorded
 in the cutthrough context block.  Either way we don't need to redo EHLO here
 (but may need to do so for TLS - see below).
-Set up the pointer to where subsequent commands will be left, for
-error messages. Note that smtp_peer_options will have been
-set from the command line if they were set in the process that passed the
-connection on. */
+Set up the pointer "smtp_command" to where subsequent commands will be left,
+for error messages. Other stuff was set up for us by the delivery process. */
 
 /*XXX continue case needs to propagate DSN_INFO, prob. in deliver.c
-as the continue goes via transport_pass_socket() and doublefork and exec.
+as the continue goes via pass-fd to the delivery process.
 It does not wait.  Unclear how we keep separate host's responses
 separate - we could match up by host ip+port as a bodge. */
 
@@ -2671,10 +2697,10 @@ else
     {
     sx->cctx.sock = 0;                         /* stdin */
     sx->cctx.tls_ctx = NULL;
-    smtp_port_for_connect(sx->conn_args.host, sx->port);       /* Record the port that was used */
+    smtp_port_for_connect(sx->conn_args.host, sx->port); /* Record the port that was used */
     }
-  sx->inblock.cctx = sx->outblock.cctx = &sx->cctx;
   smtp_command = big_buffer;
+  sx->inblock.cctx = sx->outblock.cctx = &sx->cctx;
   sx->peer_offered = smtp_peer_options;
 #ifndef DISABLE_ESMTP_LIMITS
   /* Limits passed by cmdline over exec. */
@@ -2696,6 +2722,14 @@ else
     sx->pipelining_used = pipelining_active = !!(smtp_peer_options & OPTION_PIPE);
     HDEBUG(D_transport) debug_printf("continued connection, %s TLS\n",
       continue_proxy_cipher ? "proxied" : "verify conn with");
+
+    tls_out.certificate_verified = !!(continue_flags & CTF_CV);
+#ifdef SUPPORT_DANE
+    tls_out.dane_verified = !!(continue_flags & CTF_DV);
+#endif
+#ifndef DISABLE_TLS_RESUME
+    if (continue_flags & CTF_TR) tls_out.resumption |= RESUME_USED;
+#endif
     return OK;
     }
   HDEBUG(D_transport) debug_printf("continued connection, no TLS\n");
@@ -3279,7 +3313,6 @@ sx->cctx.sock = -1;
 (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;
@@ -3426,7 +3459,7 @@ if (sx->peer_offered & OPTION_DSN && !(addr->dsn_flags & rf_dsnlasthop))
 
 /* Send MAIL FROM and RCPT TO commands.
 See sw_mrc_t definition for return codes.
- */
+*/
 
 sw_mrc_t
 smtp_write_mail_and_rcpt_cmds(smtp_context * sx, int * yield)
@@ -3655,6 +3688,7 @@ struct pollfd p[2] = {{.fd = tls_out.active.sock, .events = POLLIN},
 int rc, i;
 BOOL send_tls_shutdown = TRUE;
 
+acl_level++;
 close(pfd[1]);
 if ((rc = exim_fork(US"tls-proxy")))
   _exit(rc < 0 ? EXIT_FAILURE : EXIT_SUCCESS);
@@ -3705,9 +3739,12 @@ do
       for (int nbytes = 0; rc - nbytes > 0; nbytes += i)
        if ((i = write(pfd[0], buf + nbytes, rc - nbytes)) < 0) goto done;
 
-  /* Handle outbound data.  We cannot combine payload and the TLS-close
-  due to the limitations of the (pipe) channel feeding us.  Maybe use a unix-domain
-  socket? */
+  /* Handle outbound data.  We cannot yet combine payload and the TLS-close
+  due to the limitations of the (pipe) channel feeding us. Could we use
+  a poll/POLLRDHUP?  Would that need an extra poll call after every read
+  (likely not worth it), or (best case) could we get POLLIN+POLLRDHUP for
+  the final data blob? */
+
   if (p[1].revents & POLLIN)
     if ((rc = read(pfd[0], buf, bsize)) <= 0)
       {
@@ -3790,13 +3827,13 @@ smtp_deliver(address_item *addrlist, host_item *host, int host_af, int defport,
   uschar *interface, transport_instance *tblock,
   BOOL *message_defer, BOOL suppress_tls)
 {
-smtp_transport_options_block * ob = SOB tblock->options_block;
+smtp_transport_options_block * ob = tblock->drinst.options_block;
+const uschar * trname = tblock->drinst.name;
 int yield = OK;
 int save_errno;
 int rc;
 
 uschar *message = NULL;
-uschar new_message_id[MESSAGE_ID_LENGTH + 1];
 smtp_context * sx = store_get(sizeof(*sx), GET_TAINTED);       /* tainted, for the data buffers */
 BOOL pass_message = FALSE;
 #ifndef DISABLE_ESMTP_LIMITS
@@ -3805,9 +3842,10 @@ BOOL mail_limit = FALSE;
 #ifdef SUPPORT_DANE
 BOOL dane_held;
 #endif
-BOOL tcw_done = FALSE, tcw = FALSE;
+BOOL tcw_done = FALSE, tcw = FALSE, passback_conn = FALSE;
 
 *message_defer = FALSE;
+continue_next_id[0] = '\0';
 
 memset(sx, 0, sizeof(*sx));
 sx->addrlist = addrlist;
@@ -3868,7 +3906,7 @@ if (tblock->filter_command)
 
   if (!transport_set_up_command(&transport_filter_argv,
        tblock->filter_command, TSUC_EXPAND_ARGS, DEFER, addrlist,
-       string_sprintf("%.50s transport filter", tblock->name), NULL))
+       string_sprintf("%.50s transport filter", trname), NULL))
     {
     set_errno_nohost(addrlist->next, addrlist->basic_errno, addrlist->message, DEFER,
       FALSE, &sx->delivery_start);
@@ -4066,34 +4104,42 @@ else
 
 #ifndef DISABLE_DKIM
   {
+  misc_module_info * mi;
+
 # ifdef MEASURE_TIMING
   struct timeval t0;
   gettimeofday(&t0, NULL);
 # endif
-  dkim_exim_sign_init();
-# ifdef EXPERIMENTAL_ARC
+
+  if ((mi = misc_mod_find(US"dkim", NULL)))
     {
-    uschar * s = ob->arc_sign;
-    if (s)
+    typedef void (*fn_t)(void);
+    (((fn_t *) mi->functions)[DKIM_TRANSPORT_INIT]) ();
+
+# ifdef EXPERIMENTAL_ARC
       {
-      if (!(ob->dkim.arc_signspec = s = expand_string(s)))
-       {
-       if (!f.expand_string_forcedfail)
+      uschar * s = ob->arc_sign;
+      if (s)
+       if (!(ob->dkim.arc_signspec = s = expand_string(s)))
          {
-         message = US"failed to expand arc_sign";
-         sx->ok = FALSE;
-         goto SEND_FAILED;
+         if (!f.expand_string_forcedfail)
+           {
+           message = US"failed to expand arc_sign";
+           sx->ok = FALSE;
+           goto SEND_FAILED;
+           }
+         }
+       else if (*s && (mi = misc_mod_find(US"arc", NULL)))
+         {
+         typedef void (*fn_t)(void);
+         (((fn_t *) mi->functions)[ARC_SIGN_INIT]) ();
+
+         /* Ask dkim code to hash the body for ARC */
+         ob->dkim.force_bodyhash = TRUE;
          }
-       }
-      else if (*s)
-       {
-       /* Ask dkim code to hash the body for ARC */
-       (void) arc_ams_setup_sign_bodyhash();
-       ob->dkim.force_bodyhash = TRUE;
-       }
       }
+# endif        /*ARC*/
     }
-# endif
 # ifdef MEASURE_TIMING
   report_time_since(&t0, US"dkim_exim_sign_init (delta)");
 # endif
@@ -4127,8 +4173,8 @@ else
           )
         &&
 #endif
-           transport_check_waiting(tblock->name, host->name,
-             tblock->connection_max_messages, new_message_id,
+           transport_check_waiting(trname, host->name,
+             tblock->connection_max_messages, continue_next_id,
             (oicf)smtp_are_same_identities, (void*)&t_compare);
     if (!tcw)
       {
@@ -4138,7 +4184,15 @@ else
     }
 
 #ifndef DISABLE_DKIM
-  sx->ok = dkim_transport_write_message(&tctx, &ob->dkim, CUSS &message);
+    {
+    misc_module_info * mi = misc_mod_find(US"dkim", NULL);
+    typedef BOOL (*fn_t)(transport_ctx *, struct ob_dkim *, const uschar **);
+
+    sx->ok = mi
+      ? (((fn_t *) mi->functions)[DKIM_TRANSPORT_WRITE])
+                                     (&tctx, &ob->dkim, CUSS &message)
+      : transport_write_message(&tctx, 0);
+    }
 #else
   sx->ok = transport_write_message(&tctx, 0);
 #endif
@@ -4368,6 +4422,10 @@ else
       addr->delivery_time = delivery_time;
       addr->special_action = flag;
       addr->message = conf;
+      if (continue_sequence > 1)
+       { clearflag(addr, af_new_conn); setflag(addr, af_cont_conn); }
+      else
+       { clearflag(addr, af_cont_conn); setflag(addr, af_new_conn); }
 
       if (tcp_out_fastopen)
        {
@@ -4395,7 +4453,7 @@ else
         write error, as it may prove possible to update the spool file later. */
 
         if (testflag(addr, af_homonym))
-          sprintf(CS sx->buffer, "%.500s/%s\n", addr->unique + 3, tblock->name);
+          sprintf(CS sx->buffer, "%.500s/%s\n", addr->unique + 3, trname);
         else
           sprintf(CS sx->buffer, "%.500s\n", addr->unique);
 
@@ -4442,7 +4500,7 @@ else
        if (addr->transport_return == OK)
          {
          if (testflag(addr, af_homonym))
-           sprintf(CS sx->buffer, "%.500s/%s\n", addr->unique + 3, tblock->name);
+           sprintf(CS sx->buffer, "%.500s/%s\n", addr->unique + 3, trname);
          else
            sprintf(CS sx->buffer, "%.500s\n", addr->unique);
 
@@ -4528,10 +4586,11 @@ if (!sx->ok)
 
       case ERRNO_SMTPCLOSED:
        /* If the peer closed the TCP connection after end-of-data, but before
-       we could send QUIT, do TLS close, etc - call it a message error.
-       Otherwise, if all the recipients have been dealt with, call a close no
-       error at all; each address_item should have a suitable result already
-       (2xx: PENDING_OK, 4xx: DEFER, 5xx: FAIL) */
+       we could send QUIT, do TLS close, etc - it is a message error.
+       If not, and all the recipients have been dealt with, call such a close
+       no error at all; each address_item should have a suitable result already
+       (2xx: PENDING_OK, 4xx: DEFER, 5xx: FAIL).
+       Otherwise, it is a non-message error. */
 
        if (!(message_error = Ustrncmp(smtp_command,"end ",4) == 0))
          {
@@ -4653,9 +4712,9 @@ message (indicated by first_addr being non-NULL) we want to carry on with the
 rest of them. Also, it is desirable to send more than one message down the SMTP
 connection if there are several waiting, provided we haven't already sent so
 many as to hit the configured limit. The function transport_check_waiting looks
-for a waiting message and returns its id. Then transport_pass_socket tries to
-set up a continued delivery by passing the socket on to another process. The
-variable send_rset is FALSE if a message has just been successfully transferred.
+for a waiting message and returns its id. We pass it back to the delivery
+process via the reporting pipe. The variable send_rset is FALSE if a message has
+just been successfully transferred.
 
 If we are already sending down a continued channel, there may be further
 addresses not yet delivered that are aimed at the same host, but which have not
@@ -4688,6 +4747,12 @@ if (sx->completed_addr && sx->ok && sx->send_quit)
     {
     DEBUG(D_transport)
       debug_printf("reached limit %u for MAILs per conn\n", sx->max_mail);
+    /* We will close the smtp session and connection, and clear
+    continue_hostname.  Then if there are further addrs for the message we will
+    loop to the top of this function and make a fresh connection.  Any further
+    message found in the wait-tpt hintsdb would then do a pass-fd over the
+    transport reporting pipe to get the connection fd back to the delivery
+    process. */
     }
   else
 #endif
@@ -4695,9 +4760,9 @@ if (sx->completed_addr && sx->ok && sx->send_quit)
     smtp_compare_t t_compare =
       {.tblock = tblock, .current_sender_address = sender_address};
 
-    if (  sx->first_addr                       /* more addrs for this message */
-       || f.continue_more                      /* more addrs for continued-host */
-       || tcw_done && tcw                      /* more messages for host */
+    if (  sx->first_addr               /* more addrs for this message */
+       || f.continue_more              /* more addrs for continued-host */
+       || tcw_done && tcw              /* more messages for host */
        || (
 #ifndef DISABLE_TLS
             (  tls_out.active.sock < 0  &&  !continue_proxy_cipher
@@ -4705,8 +4770,8 @@ if (sx->completed_addr && sx->ok && sx->send_quit)
             )
          &&
 #endif
-            transport_check_waiting(tblock->name, host->name,
-              sx->max_mail, new_message_id,
+            transport_check_waiting(trname, host->name,
+              sx->max_mail, continue_next_id,
               (oicf)smtp_are_same_identities, (void*)&t_compare)
        )  )
       {
@@ -4740,8 +4805,7 @@ if (sx->completed_addr && sx->ok && sx->send_quit)
 #ifndef DISABLE_TLS
        int pfd[2];
 #endif
-       int socket_fd = sx->cctx.sock;
-
+       continue_fd = sx->cctx.sock;
        if (sx->first_addr)             /* More addresses still to be sent */
          {                             /*   for this message              */
 #ifndef DISABLE_ESMTP_LIMITS
@@ -4751,117 +4815,116 @@ if (sx->completed_addr && sx->ok && sx->send_quit)
              a->transport_return = PENDING_DEFER;
 #endif
          continue_sequence++;                          /* for consistency */
-         clearflag(sx->first_addr, af_new_conn);
-         setflag(sx->first_addr, af_cont_conn);        /* Causes * in logging */
          pipelining_active = sx->pipelining_used;      /* was cleared at DATA */
          goto SEND_MESSAGE;
          }
 
-       /* Unless caller said it already has more messages listed for this host,
-       pass the connection on to a new Exim process (below, the call to
-       transport_pass_socket).  If the caller has more ready, just return with
-       the connection still open. */
+       /* If there is a next-message-id from the wait-transport hintsdb,
+       pretend caller said it has further message for us.  Note that we lose
+       the TLS session (below), and that our caller will pass back the id to
+       the delivery process. */
+
+       if (f.continue_more)
+         {
+         passback_conn = TRUE;
+         continue_next_id[0] = '\0';
+         }
+       else if (*continue_next_id)
+         passback_conn = f.continue_more = TRUE;
 
 #ifndef DISABLE_TLS
+       /* If we will be returning with the connection still open and have a TLS
+       endpoint, shut down TLS if we must, or if this is a first-time passback
+       fork a proxy process with the TLS state. */
+
        if (tls_out.active.sock >= 0)
-         if (  f.continue_more
-            || verify_check_given_host(CUSS &ob->hosts_noproxy_tls, host) == OK)
+         {
+         if (  (continue_hostname || passback_conn)
+            && verify_check_given_host(CUSS &ob->hosts_noproxy_tls, host) == OK
+            )
            {
-           /* Before passing the socket on, or returning to caller with it still
-           open, we must shut down TLS.  Not all MTAs allow for the continuation
-           of the SMTP session when TLS is shut down. We test for this by sending
-           a new EHLO. If we don't get a good response, we don't attempt to pass
-           the socket on.
+           /* Not all MTAs allow for the continuation of the SMTP session when
+           TLS is shut down. We test for this by sending a new EHLO. If we
+           don't get a good response, we don't attempt to pass the socket on.
            NB: TLS close is *required* per RFC 9266 when tls-exporter info has
            been used, which we do under TLSv1.3 for the gsasl SCRAM*PLUS methods.
-           But we were always doing it anyway. */
-
-         tls_close(sx->cctx.tls_ctx,
-           sx->send_tlsclose ? TLS_SHUTDOWN_WAIT : TLS_SHUTDOWN_WONLY);
-         sx->send_tlsclose = FALSE;
-         sx->cctx.tls_ctx = NULL;
-         tls_out.active.sock = -1;
-         smtp_peer_options = smtp_peer_options_wrap;
-         sx->ok = !sx->smtps
-           && smtp_write_command(sx, SCMD_FLUSH, "EHLO %s\r\n", sx->helo_data)
-               >= 0
-           && smtp_read_response(sx, sx->buffer, sizeof(sx->buffer),
-                                     '2', ob->command_timeout);
-
-           if (sx->ok && f.continue_more)
-             goto TIDYUP;              /* More addresses for another run */
+           XXX TODO */
+
+           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;
+           smtp_peer_options = smtp_peer_options_wrap;
+           sx->ok = !sx->smtps
+             && smtp_write_command(sx, SCMD_FLUSH, "EHLO %s\r\n",sx->helo_data)
+                 >= 0
+             && smtp_read_response(sx, sx->buffer, sizeof(sx->buffer),
+                                       '2', ob->command_timeout);
            }
-         else
+         else if (passback_conn)
            {
            /* Set up a pipe for proxying TLS for the new transport process */
 
            smtp_peer_options |= OPTION_TLS;
            if ((sx->ok = socketpair(AF_UNIX, SOCK_STREAM, 0, pfd) == 0))
-             socket_fd = pfd[1];
-           else
-             set_errno(sx->first_addr, errno, US"internal allocation problem",
-                     DEFER, FALSE, host,
-# ifdef EXPERIMENTAL_DSN_INFO
-                     sx->smtp_greeting, sx->helo_response,
-# endif
-                     &sx->delivery_start);
-           }
-       else
-#endif
-         if (f.continue_more)
-           goto TIDYUP;                        /* More addresses for another run */
-
-       /* If the socket is successfully passed, we mustn't send QUIT (or
-       indeed anything!) from here. */
-
-  /*XXX DSN_INFO: assume likely to do new HELO; but for greet we'll want to
-  propagate it from the initial
-  */
-       if (sx->ok && transport_pass_socket(tblock->name, host->name,
-             host->address, new_message_id, socket_fd
-#ifndef DISABLE_ESMTP_LIMITS
-             , sx->peer_limit_mail, sx->peer_limit_rcpt, sx->peer_limit_rcptdom
-#endif
-             ))
-         {
-         sx->send_quit = FALSE;
-
-         /* We have passed the client socket to a fresh transport process.
-         If TLS is still active, we need to proxy it for the transport we
-         just passed the baton to.  Fork a child to to do it, and return to
-         get logging done asap.  Which way to place the work makes assumptions
-         about post-fork prioritisation which may not hold on all platforms. */
-#ifndef DISABLE_TLS
-         if (tls_out.active.sock >= 0)
-           {
-           int pid = exim_fork(US"tls-proxy-interproc");
-           if (pid == 0)               /* child; fork again to disconnect totally */
              {
-             /* does not return */
-             smtp_proxy_tls(sx->cctx.tls_ctx, sx->buffer, sizeof(sx->buffer), pfd,
-                             ob->command_timeout, host->name);
-             }
+             int pid = exim_fork(US"tls-proxy-interproc");
+             if (pid == 0)     /* child; fork again to disconnect totally */
+               {
+               /* does not return */
+               smtp_proxy_tls(sx->cctx.tls_ctx, sx->buffer, sizeof(sx->buffer),
+                             pfd, ob->command_timeout, host->name);
+               }
+
+             if (pid < 0)
+               log_write(0, LOG_PANIC_DIE, "fork failed");
 
-           if (pid > 0)                /* parent */
-             {
              close(pfd[0]);
+             continue_fd = pfd[1];
              /* tidy the inter-proc to disconn the proxy proc */
              waitpid(pid, NULL, 0);
              tls_close(sx->cctx.tls_ctx, TLS_NO_SHUTDOWN);
              sx->cctx.tls_ctx = NULL;
              (void)close(sx->cctx.sock);
              sx->cctx.sock = -1;
-             continue_transport = NULL;
-             continue_hostname = NULL;
-             goto TIDYUP;
+
+             continue_proxy_cipher = tls_out.cipher;
+             continue_proxy_sni = tls_out.sni;
+# ifdef SUPPORT_DANE
+             continue_proxy_dane = tls_out.sni && tls_out.dane_verified;
+# endif
              }
-           log_write(0, LOG_PANIC_DIE, "fork failed");
+           else
+             set_errno(sx->first_addr, errno, US"internal allocation problem",
+                     DEFER, FALSE, host,
+# ifdef EXPERIMENTAL_DSN_INFO
+                     sx->smtp_greeting, sx->helo_response,
+# endif
+                     &sx->delivery_start);
            }
-#endif
          }
+#endif /*DISABLE_TLS*/
+
+       /* If a connection re-use is possible, arrange to pass back all the info
+       about it so that further forks of the delivery process see it. */
+
+       if (passback_conn)
+         {
+         continue_transport = transport_name;
+         continue_hostname = host->name;
+         continue_host_address = host->address;
+         }
+       else
+         continue_hostname = NULL;
+
+       if (sx->ok && f.continue_more)  /* More addresses for another run; */
+         goto TIDYUP;                  /* skip the channel closedown */
        }
 
-      /* If RSET failed and there are addresses left, they get deferred. */
+      /* If RSET failed and there are addresses left, they get deferred.
+      Do not pass back a next-id or conn info. */
+
       else
        set_errno(sx->first_addr, errno, msg, DEFER, FALSE, host,
 #ifdef EXPERIMENTAL_DSN_INFO
@@ -4964,9 +5027,8 @@ if (sx->send_quit || tcw_done && !tcw)
 HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP(close)>>\n");
 (void)close(sx->cctx.sock);
 sx->cctx.sock = -1;
-continue_transport = NULL;
 continue_hostname = NULL;
-smtp_debug_cmd_report();
+continue_next_id[0] = '\0';
 
 #ifndef DISABLE_EVENT
 (void) event_raise(tblock->event_action, US"tcp:close", NULL, NULL);
@@ -4986,8 +5048,6 @@ if (dane_held)
        to get the domain string for SNI */
 
        sx->first_addr = a;
-       clearflag(a, af_cont_conn);
-       setflag(a, af_new_conn);                /* clear * from logging */
        DEBUG(D_transport) debug_printf("DANE: go-around for %s\n", a->domain);
        }
       }
@@ -5000,25 +5060,25 @@ if (dane_held)
 if (mail_limit && sx->first_addr)
   {
   /* Reset the sequence count since we closed the connection.  This is flagged
-  on the pipe back to the delivery process so that a non-continued-conn delivery
-  is logged. */
+  on the pipe back to the delivery process so that it can reset it's count.
+  Also set flags on the addr so that a non-continued-conn delivery is logged. */
 
   continue_sequence = 1;                       /* for consistency */
-  clearflag(sx->first_addr, af_cont_conn);
-  setflag(sx->first_addr, af_new_conn);                /* clear  * from logging */
-  goto REPEAT_CONN;
+  goto REPEAT_CONN;                            /* open a fresh connection */
   }
 #endif
 
-return yield;
+OUT:
+  smtp_debug_cmd_report();
+  return yield;
 
 TIDYUP:
 #ifdef SUPPORT_DANE
-if (dane_held) for (address_item * a = sx->addrlist->next; a; a = a->next)
-  if (a->transport_return == DANE)
-    a->transport_return = PENDING_DEFER;
+  if (dane_held) for (address_item * a = sx->addrlist->next; a; a = a->next)
+    if (a->transport_return == DANE)
+      a->transport_return = PENDING_DEFER;
 #endif
-return yield;
+  goto OUT;
 }
 
 
@@ -5042,9 +5102,9 @@ Returns:    nothing
 */
 
 void
-smtp_transport_closedown(transport_instance *tblock)
+smtp_transport_closedown(transport_instance * tblock)
 {
-smtp_transport_options_block * ob = SOB tblock->options_block;
+smtp_transport_options_block * ob = tblock->drinst.options_block;
 client_conn_ctx cctx;
 smtp_context sx = {0};
 uschar buffer[256];
@@ -5138,18 +5198,19 @@ smtp_transport_entry(
   transport_instance * tblock,      /* data for this instantiation */
   address_item * addrlist)          /* addresses we are working on */
 {
+smtp_transport_options_block * ob = tblock->drinst.options_block;
+const uschar * trname = tblock->drinst.name;
 int defport;
 int hosts_defer = 0, hosts_fail  = 0, hosts_looked_up = 0;
 int hosts_retry = 0, hosts_serial = 0, hosts_total = 0, total_hosts_tried = 0;
 BOOL expired = TRUE;
 uschar * expanded_hosts = NULL, * pistring;
-uschar * tid = string_sprintf("%s transport", tblock->name);
-smtp_transport_options_block * ob = SOB tblock->options_block;
+uschar * tid = string_sprintf("%s transport", trname);
 host_item * hostlist = addrlist->host_list, * host = NULL;
 
 DEBUG(D_transport)
   {
-  debug_printf("%s transport entered\n", tblock->name);
+  debug_printf("%s transport entered\n", trname);
   for (address_item * addr = addrlist; addr; addr = addr->next)
     debug_printf("  %s\n", addr->address);
   if (hostlist)
@@ -5176,7 +5237,10 @@ if (max_received_linelength > ob->message_linelength_limit)
       addr->transport_return = PENDING_DEFER;
 
   set_errno_nohost(addrlist, ERRNO_SMTPFORMAT,
-    US"message has lines too long for transport", FAIL, TRUE, &now);
+    string_sprintf("message has lines too long for transport "
+                   "(received %d, limit %d)",
+                   max_received_linelength, ob->message_linelength_limit),
+                 FAIL, TRUE, &now);
   goto END_TRANSPORT;
   }
 
@@ -5197,7 +5261,7 @@ if (!hostlist || (ob->hosts_override && ob->hosts))
   if (!ob->hosts)
     {
     addrlist->message = string_sprintf("%s transport called with no hosts set",
-      tblock->name);
+      trname);
     addrlist->transport_return = PANIC;
     return FALSE;   /* Only top address has status */
     }
@@ -5222,7 +5286,7 @@ if (!hostlist || (ob->hosts_override && ob->hosts))
       if (!(expanded_hosts = expand_string(s)))
         {
         addrlist->message = string_sprintf("failed to expand list of hosts "
-          "\"%s\" in %s transport: %s", s, tblock->name, expand_string_message);
+          "\"%s\" in %s transport: %s", s, trname, expand_string_message);
         addrlist->transport_return = f.search_find_defer ? DEFER : PANIC;
         return FALSE;     /* Only top address has status */
         }
@@ -5237,7 +5301,7 @@ if (!hostlist || (ob->hosts_override && ob->hosts))
       {
       log_write(0, LOG_MAIN|LOG_PANIC,
        "attempt to use tainted host list '%s' from '%s' in transport %s",
-       s, ob->hosts, tblock->name);
+       s, ob->hosts, trname);
       /* Avoid leaking info to an attacker */
       addrlist->message = US"internal configuration error";
       addrlist->transport_return = PANIC;
@@ -5250,7 +5314,7 @@ if (!hostlist || (ob->hosts_override && ob->hosts))
     if (!hostlist)
       {
       addrlist->message =
-        string_sprintf("%s transport has empty hosts setting", tblock->name);
+        string_sprintf("%s transport has empty hosts setting", trname);
       addrlist->transport_return = PANIC;
       return FALSE;   /* Only top address has status */
       }
@@ -5492,7 +5556,7 @@ retry_non_continued:
           {
           addr->basic_errno = ERRNO_HOST_IS_LOCAL;
           addr->message = string_sprintf("%s transport found host %s to be "
-            "local", tblock->name, host->name);
+            "local", trname, host->name);
           }
         goto END_TRANSPORT;
         }
@@ -5504,7 +5568,7 @@ retry_non_continued:
     result of the lookup. Set expired FALSE, to save the outer loop executing
     twice. */
 
-    if (continue_hostname)
+    if (continue_sequence > 1)
       if (  Ustrcmp(continue_hostname, host->name) != 0
          || Ustrcmp(continue_host_address, host->address) != 0
         )
@@ -5580,6 +5644,31 @@ retry_non_continued:
        {
        if (!smtp_get_interface(s, host_af, addrlist, &interface, tid))
          return FALSE;
+
+       if (continue_sequence > 1)
+         {
+         union sockaddr_46 interface_sock;
+         EXIM_SOCKLEN_T size = sizeof(interface_sock);
+         const uschar * local_ip_addr;
+
+         /* Assume the connection is on fd 0 */
+         if (getsockname(0, (struct sockaddr *) &interface_sock, &size) < 0)
+           {
+           DEBUG(D_transport)
+             debug_printf_indent("failed getsockname: %s\n", strerror(errno));
+           return FALSE;
+           }
+         local_ip_addr = host_ntoa(-1, &interface_sock, NULL, &sending_port);
+         if (Ustrcmp(interface, local_ip_addr) != 0)
+           {
+           DEBUG(D_transport) debug_printf_indent(
+             "tpt interface option mismatch with continued-connection\n");
+           /* Close the conn and recheck retry info */
+           continue_host_tried = FALSE;
+           break;
+           }
+         }
+
        pistring = string_sprintf("%s/%s", pistring, interface);
        }
       }
@@ -5597,7 +5686,7 @@ retry_non_continued:
       If either of these retry records are actually read, the keys used are
       returned to save recomputing them later. */
 
-      if (exp_bool(addrlist, US"transport", tblock->name, D_transport,
+      if (exp_bool(addrlist, US"transport", trname, D_transport,
                US"retry_include_ip_address", ob->retry_include_ip_address,
                ob->expand_retry_include_ip_address, &incl_ip) != OK)
        continue;       /* with next host */
@@ -5720,7 +5809,7 @@ retry_non_continued:
       DEBUG(D_transport)
         {
         debug_printf("*** delivery by %s transport bypassed by -N option\n"
-                     "*** host and remaining hosts:\n", tblock->name);
+                     "*** host and remaining hosts:\n", trname);
         for (host_item * host2 = host; host2; host2 = host2->next)
           debug_printf("    %s [%s]\n", host2->name,
             host2->address ? host2->address : US"unset");
@@ -5869,7 +5958,7 @@ retry_non_continued:
       if (!retry_host_key)
         {
        BOOL incl_ip;
-       if (exp_bool(addrlist, US"transport", tblock->name, D_transport,
+       if (exp_bool(addrlist, US"transport", trname, D_transport,
                  US"retry_include_ip_address", ob->retry_include_ip_address,
                  ob->expand_retry_include_ip_address, &incl_ip) != OK)
          incl_ip = TRUE;       /* error; use most-specific retry record */
@@ -5915,7 +6004,7 @@ retry_non_continued:
       if (!retry_message_key)
         {
        BOOL incl_ip;
-       if (exp_bool(addrlist, US"transport", tblock->name, D_transport,
+       if (exp_bool(addrlist, US"transport", trname, D_transport,
                  US"retry_include_ip_address", ob->retry_include_ip_address,
                  ob->expand_retry_include_ip_address, &incl_ip) != OK)
          incl_ip = TRUE;       /* error; use most-specific retry record */
@@ -5949,7 +6038,7 @@ retry_non_continued:
     case when we were trying to deliver down an existing channel and failed.
     Don't try any other hosts in this case. */
 
-    if (continue_hostname) break;
+    if (continue_sequence > 1) break;
 
     /* If the whole delivery, or some individual addresses, were deferred and
     there are more hosts that could be tried, do not count this host towards
@@ -6000,11 +6089,12 @@ retry_non_continued:
   for routing that changes from run to run, or big multi-IP sites with
   round-robin DNS. */
 
-  if (continue_hostname && !continue_host_tried)
+  if (continue_sequence > 1 && !continue_host_tried)
     {
     int fd = cutthrough.cctx.sock >= 0 ? cutthrough.cctx.sock : 0;
 
     DEBUG(D_transport) debug_printf("no hosts match already-open connection\n");
+    DEBUG(D_transport) debug_printf("  SMTP>>QUIT\n");
 #ifndef DISABLE_TLS
     /* A TLS conn could be open for a cutthrough, but not for a plain continued-
     transport */
@@ -6021,9 +6111,12 @@ retry_non_continued:
 #else
       (void) write(fd, US"QUIT\r\n", 6);
 #endif
+
+    DEBUG(D_transport) debug_printf("  SMTP(close)>>\n");
     (void) close(fd);
     cutthrough.cctx.sock = -1;
     continue_hostname = NULL;
+    continue_sequence = 1;
     goto retry_non_continued;
     }
 
@@ -6148,15 +6241,40 @@ per connection then follow-on deliveries are not possible and there's no need
 to create/update the per-transport wait-<transport_name> database. */
 
 if (update_waiting && tblock->connection_max_messages != 1)
-  transport_update_waiting(hostlist, tblock->name);
+  transport_update_waiting(hostlist, trname);
 
 END_TRANSPORT:
 
-DEBUG(D_transport) debug_printf("Leaving %s transport\n", tblock->name);
+DEBUG(D_transport) debug_printf("Leaving %s transport\n", trname);
 
 return TRUE;   /* Each address has its status */
 }
 
+
+
+
+# ifdef DYNLOOKUP
+#  define smtp_transport_info _transport_info
+# endif
+
+transport_info smtp_transport_info = {
+.drinfo = {
+  .driver_name =       US"smtp",
+  .options =           smtp_transport_options,
+  .options_count =     &smtp_transport_options_count,
+  .options_block =     &smtp_transport_option_defaults,
+  .options_len =       sizeof(smtp_transport_options_block),
+  .init =              smtp_transport_init,
+# ifdef DYNLOOKUP
+  .dyn_magic =         TRANSPORT_MAGIC,
+# endif
+  },
+.code =                smtp_transport_entry,
+.tidyup =      NULL,
+.closedown =   smtp_transport_closedown,
+.local =       FALSE
+};
+
 #endif /*!MACRO_PREDEF*/
 /* vi: aw ai sw=2
 */