Expanded EXPERIMENTAL_TPDA feature
[exim.git] / src / src / verify.c
index 964bdf7141eabcf66f0e247af178f1b5da412921..8564aacc2c5bb6bbef7391f9d8b95da3c62d97ef 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2014 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions concerned with verifying things. The original code for callout
@@ -154,7 +154,6 @@ do_callout(address_item *addr, host_item *host_list, transport_feedback *tf,
   int callout, int callout_overall, int callout_connect, int options,
   uschar *se_mailfrom, uschar *pm_mailfrom)
 {
-smtp_transport_options_block *ob = (smtp_transport_options_block *)(addr->transport->options_block);
 BOOL is_recipient = (options & vopt_is_recipient) != 0;
 BOOL callout_no_cache = (options & vopt_callout_no_cache) != 0;
 BOOL callout_random = (options & vopt_callout_random) != 0;
@@ -370,596 +369,664 @@ if (dbm_file != NULL)
   dbm_file = NULL;
   }
 
-/* The information wasn't available in the cache, so we have to do a real
-callout and save the result in the cache for next time, unless no_cache is set,
-or unless we have a previously cached negative random result. If we are to test
-with a random local part, ensure that such a local part is available. If not,
-log the fact, but carry on without randomming. */
-
-if (callout_random && callout_random_local_part != NULL)
+if (!addr->transport)
   {
-  random_local_part = expand_string(callout_random_local_part);
-  if (random_local_part == NULL)
-    log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
-      "callout_random_local_part: %s", expand_string_message);
+  HDEBUG(D_verify) debug_printf("cannot callout via null transport\n");
   }
+else if (Ustrcmp(addr->transport->driver_name, "smtp") != 0)
+  log_write(0, LOG_MAIN|LOG_PANIC|LOG_CONFIG_FOR, "callout transport '%s': %s is non-smtp",
+    addr->transport->name, addr->transport->driver_name);
+else
+  {
+  smtp_transport_options_block *ob =
+    (smtp_transport_options_block *)addr->transport->options_block;
 
-/* Default the connect and overall callout timeouts if not set, and record the
-time we are starting so that we can enforce it. */
-
-if (callout_overall < 0) callout_overall = 4 * callout;
-if (callout_connect < 0) callout_connect = callout;
-callout_start_time = time(NULL);
-
-/* Before doing a real callout, if this is an SMTP connection, flush the SMTP
-output because a callout might take some time. When PIPELINING is active and
-there are many recipients, the total time for doing lots of callouts can add up
-and cause the client to time out. So in this case we forgo the PIPELINING
-optimization. */
-
-if (smtp_out != NULL && !disable_callout_flush) mac_smtp_fflush();
-
-/* Precompile some regex that are used to recognize parameters in response
-to an EHLO command, if they aren't already compiled. */
-#ifdef SUPPORT_TLS
-if (regex_STARTTLS == NULL) regex_STARTTLS =
-  regex_must_compile(US"\\n250[\\s\\-]STARTTLS(\\s|\\n|$)", FALSE, TRUE);
-#endif
-
-/* Now make connections to the hosts and do real callouts. The list of hosts
-is passed in as an argument. */
+  /* The information wasn't available in the cache, so we have to do a real
+  callout and save the result in the cache for next time, unless no_cache is set,
+  or unless we have a previously cached negative random result. If we are to test
+  with a random local part, ensure that such a local part is available. If not,
+  log the fact, but carry on without randomming. */
 
-for (host = host_list; host != NULL && !done; host = host->next)
-  {
-  smtp_inblock inblock;
-  smtp_outblock outblock;
-  int host_af;
-  int port = 25;
-  BOOL send_quit = TRUE;
-  uschar *active_hostname = smtp_active_hostname;
-  BOOL lmtp;
-  BOOL smtps;
-  BOOL esmtp;
-  BOOL suppress_tls = FALSE;
-  uschar *interface = NULL;  /* Outgoing interface to use; NULL => any */
-  uschar inbuffer[4096];
-  uschar outbuffer[1024];
-  uschar responsebuffer[4096];
-
-  clearflag(addr, af_verify_pmfail);  /* postmaster callout flag */
-  clearflag(addr, af_verify_nsfail);  /* null sender callout flag */
-
-  /* Skip this host if we don't have an IP address for it. */
-
-  if (host->address == NULL)
+  if (callout_random && callout_random_local_part != NULL)
     {
-    DEBUG(D_verify) debug_printf("no IP address for host name %s: skipping\n",
-      host->name);
-    continue;
+    random_local_part = expand_string(callout_random_local_part);
+    if (random_local_part == NULL)
+      log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
+        "callout_random_local_part: %s", expand_string_message);
     }
 
-  /* Check the overall callout timeout */
+  /* Default the connect and overall callout timeouts if not set, and record the
+  time we are starting so that we can enforce it. */
 
-  if (time(NULL) - callout_start_time >= callout_overall)
-    {
-    HDEBUG(D_verify) debug_printf("overall timeout for callout exceeded\n");
-    break;
-    }
+  if (callout_overall < 0) callout_overall = 4 * callout;
+  if (callout_connect < 0) callout_connect = callout;
+  callout_start_time = time(NULL);
 
-  /* Set IPv4 or IPv6 */
+  /* Before doing a real callout, if this is an SMTP connection, flush the SMTP
+  output because a callout might take some time. When PIPELINING is active and
+  there are many recipients, the total time for doing lots of callouts can add up
+  and cause the client to time out. So in this case we forgo the PIPELINING
+  optimization. */
 
-  host_af = (Ustrchr(host->address, ':') == NULL)? AF_INET:AF_INET6;
+  if (smtp_out != NULL && !disable_callout_flush) mac_smtp_fflush();
 
-  /* Expand and interpret the interface and port strings. The latter will not
-  be used if there is a host-specific port (e.g. from a manualroute router).
-  This has to be delayed till now, because they may expand differently for
-  different hosts. If there's a failure, log it, but carry on with the
-  defaults. */
+  /* Now make connections to the hosts and do real callouts. The list of hosts
+  is passed in as an argument. */
 
-  deliver_host = host->name;
-  deliver_host_address = host->address;
-  deliver_domain = addr->domain;
+  for (host = host_list; host != NULL && !done; host = host->next)
+    {
+    smtp_inblock inblock;
+    smtp_outblock outblock;
+    int host_af;
+    int port = 25;
+    BOOL send_quit = TRUE;
+    uschar *active_hostname = smtp_active_hostname;
+    BOOL lmtp;
+    BOOL smtps;
+    BOOL esmtp;
+    BOOL suppress_tls = FALSE;
+    uschar *interface = NULL;  /* Outgoing interface to use; NULL => any */
+    uschar inbuffer[4096];
+    uschar outbuffer[1024];
+    uschar responsebuffer[4096];
+
+    clearflag(addr, af_verify_pmfail);  /* postmaster callout flag */
+    clearflag(addr, af_verify_nsfail);  /* null sender callout flag */
+
+    /* Skip this host if we don't have an IP address for it. */
+
+    if (host->address == NULL)
+      {
+      DEBUG(D_verify) debug_printf("no IP address for host name %s: skipping\n",
+        host->name);
+      continue;
+      }
 
-  if (!smtp_get_interface(tf->interface, host_af, addr, NULL, &interface,
-          US"callout") ||
-      !smtp_get_port(tf->port, addr, &port, US"callout"))
-    log_write(0, LOG_MAIN|LOG_PANIC, "<%s>: %s", addr->address,
-      addr->message);
+    /* Check the overall callout timeout */
 
-  /* Set HELO string according to the protocol */
-  lmtp= Ustrcmp(tf->protocol, "lmtp") == 0;
-  smtps= Ustrcmp(tf->protocol, "smtps") == 0;
+    if (time(NULL) - callout_start_time >= callout_overall)
+      {
+      HDEBUG(D_verify) debug_printf("overall timeout for callout exceeded\n");
+      break;
+      }
 
+    /* Set IPv4 or IPv6 */
 
-  HDEBUG(D_verify) debug_printf("interface=%s port=%d\n", interface, port);
+    host_af = (Ustrchr(host->address, ':') == NULL)? AF_INET:AF_INET6;
 
-  /* Set up the buffer for reading SMTP response packets. */
+    /* Expand and interpret the interface and port strings. The latter will not
+    be used if there is a host-specific port (e.g. from a manualroute router).
+    This has to be delayed till now, because they may expand differently for
+    different hosts. If there's a failure, log it, but carry on with the
+    defaults. */
 
-  inblock.buffer = inbuffer;
-  inblock.buffersize = sizeof(inbuffer);
-  inblock.ptr = inbuffer;
-  inblock.ptrend = inbuffer;
+    deliver_host = host->name;
+    deliver_host_address = host->address;
+    deliver_host_port = host->port;
+    deliver_domain = addr->domain;
 
-  /* Set up the buffer for holding SMTP commands while pipelining */
+    if (!smtp_get_interface(tf->interface, host_af, addr, NULL, &interface,
+            US"callout") ||
+        !smtp_get_port(tf->port, addr, &port, US"callout"))
+      log_write(0, LOG_MAIN|LOG_PANIC, "<%s>: %s", addr->address,
+        addr->message);
 
-  outblock.buffer = outbuffer;
-  outblock.buffersize = sizeof(outbuffer);
-  outblock.ptr = outbuffer;
-  outblock.cmd_count = 0;
-  outblock.authenticating = FALSE;
+    /* Set HELO string according to the protocol */
+    lmtp= Ustrcmp(tf->protocol, "lmtp") == 0;
+    smtps= Ustrcmp(tf->protocol, "smtps") == 0;
 
-  /* Reset the parameters of a TLS session */
-  tls_out.cipher = tls_out.peerdn = NULL;
 
-  /* Connect to the host; on failure, just loop for the next one, but we
-  set the error for the last one. Use the callout_connect timeout. */
+    HDEBUG(D_verify) debug_printf("interface=%s port=%d\n", interface, port);
 
-  tls_retry_connection:
+    /* Set up the buffer for reading SMTP response packets. */
 
-  inblock.sock = outblock.sock =
-    smtp_connect(host, host_af, port, interface, callout_connect, TRUE, NULL);
-  /* reconsider DSCP here */
-  if (inblock.sock < 0)
-    {
-    addr->message = string_sprintf("could not connect to %s [%s]: %s",
-        host->name, host->address, strerror(errno));
-    deliver_host = deliver_host_address = NULL;
-    deliver_domain = save_deliver_domain;
-    continue;
-    }
+    inblock.buffer = inbuffer;
+    inblock.buffersize = sizeof(inbuffer);
+    inblock.ptr = inbuffer;
+    inblock.ptrend = inbuffer;
 
-  /* Expand the helo_data string to find the host name to use. */
+    /* Set up the buffer for holding SMTP commands while pipelining */
 
-  if (tf->helo_data != NULL)
-    {
-    uschar *s = expand_string(tf->helo_data);
-    if (s == NULL)
-      log_write(0, LOG_MAIN|LOG_PANIC, "<%s>: failed to expand transport's "
-        "helo_data value for callout: %s", addr->address,
-        expand_string_message);
-    else active_hostname = s;
-    }
+    outblock.buffer = outbuffer;
+    outblock.buffersize = sizeof(outbuffer);
+    outblock.ptr = outbuffer;
+    outblock.cmd_count = 0;
+    outblock.authenticating = FALSE;
 
-  deliver_host = deliver_host_address = NULL;
-  deliver_domain = save_deliver_domain;
+    /* Reset the parameters of a TLS session */
+    tls_out.cipher = tls_out.peerdn = NULL;
 
-  /* Wait for initial response, and send HELO. The smtp_write_command()
-  function leaves its command in big_buffer. This is used in error responses.
-  Initialize it in case the connection is rejected. */
+    /* Connect to the host; on failure, just loop for the next one, but we
+    set the error for the last one. Use the callout_connect timeout. */
 
-  Ustrcpy(big_buffer, "initial connection");
+    tls_retry_connection:
 
-  /* Unless ssl-on-connect, wait for the initial greeting */
-  smtps_redo_greeting:
+    inblock.sock = outblock.sock =
+      smtp_connect(host, host_af, port, interface, callout_connect, TRUE, NULL
+#ifdef EXPERIMENTAL_TPDA
+    /*XXX tpda action? NULL for now. */
+                 , NULL
+#endif
+                 );
+    /* reconsider DSCP here */
+    if (inblock.sock < 0)
+      {
+      addr->message = string_sprintf("could not connect to %s [%s]: %s",
+          host->name, host->address, strerror(errno));
+      deliver_host = deliver_host_address = NULL;
+      deliver_domain = save_deliver_domain;
+      continue;
+      }
 
-  #ifdef SUPPORT_TLS
-  if (!smtps || (smtps && tls_out.active >= 0))
-  #endif
-    if (!(done= smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), '2', callout)))
-      goto RESPONSE_FAILED;
-  
-  /* Not worth checking greeting line for ESMTP support */
-  if (!(esmtp = verify_check_this_host(&(ob->hosts_avoid_esmtp), NULL,
-    host->name, host->address, NULL) != OK))
-    DEBUG(D_transport)
-      debug_printf("not sending EHLO (host matches hosts_avoid_esmtp)\n");
+    /* Expand the helo_data string to find the host name to use. */
 
-  tls_redo_helo:
+    if (tf->helo_data != NULL)
+      {
+      uschar *s = expand_string(tf->helo_data);
+      if (s == NULL)
+        log_write(0, LOG_MAIN|LOG_PANIC, "<%s>: failed to expand transport's "
+          "helo_data value for callout: %s", addr->address,
+          expand_string_message);
+      else active_hostname = s;
+      }
 
-  #ifdef SUPPORT_TLS
-  if (smtps  &&  tls_out.active < 0)   /* ssl-on-connect, first pass */
-    {
-    tls_offered = TRUE;
-    ob->tls_tempfail_tryclear = FALSE;
-    }
-    else                               /* all other cases */
-  #endif
+    /* Wait for initial response, and send HELO. The smtp_write_command()
+    function leaves its command in big_buffer. This is used in error responses.
+    Initialize it in case the connection is rejected. */
 
-    { esmtp_retry:
+    Ustrcpy(big_buffer, "initial connection");
 
-    if (!(done= smtp_write_command(&outblock, FALSE, "%s %s\r\n",
-      !esmtp? "HELO" : lmtp? "LHLO" : "EHLO", active_hostname) >= 0))
-      goto SEND_FAILED;
-    if (!smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), '2', callout))
+    /* Unless ssl-on-connect, wait for the initial greeting */
+    smtps_redo_greeting:
+
+#ifdef SUPPORT_TLS
+    if (!smtps || (smtps && tls_out.active >= 0))
+#endif
       {
-      if (errno != 0 || responsebuffer[0] == 0 || lmtp || !esmtp || tls_out.active >= 0)
-       {
-       done= FALSE;
+      if (!(done= smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), '2', callout)))
         goto RESPONSE_FAILED;
+
+#ifdef EXPERIMENTAL_TPDA
+      if (tpda_raise_event(addr->transport->tpda_event_action,
+                           US"smtp:connect", responsebuffer) == DEFER)
+       {
+       /* Logging?  Debug? */
+       goto RESPONSE_FAILED;
        }
-      #ifdef SUPPORT_TLS
-      tls_offered = FALSE;
-      #endif
-      esmtp = FALSE;
-      goto esmtp_retry;                        /* fallback to HELO */
+#endif
       }
 
-    /* Set tls_offered if the response to EHLO specifies support for STARTTLS. */
-    #ifdef SUPPORT_TLS
-    tls_offered = esmtp && !suppress_tls &&  tls_out.active < 0  &&
-      pcre_exec(regex_STARTTLS, NULL, CS responsebuffer, Ustrlen(responsebuffer), 0,
-        PCRE_EOPT, NULL, 0) >= 0;
-    #endif
-    }
+    /* Not worth checking greeting line for ESMTP support */
+    if (!(esmtp = verify_check_this_host(&(ob->hosts_avoid_esmtp), NULL,
+      host->name, host->address, NULL) != OK))
+      DEBUG(D_transport)
+        debug_printf("not sending EHLO (host matches hosts_avoid_esmtp)\n");
 
-  /* If TLS is available on this connection attempt to
-  start up a TLS session, unless the host is in hosts_avoid_tls. If successful,
-  send another EHLO - the server may give a different answer in secure mode. We
-  use a separate buffer for reading the response to STARTTLS so that if it is
-  negative, the original EHLO data is available for subsequent analysis, should
-  the client not be required to use TLS. If the response is bad, copy the buffer
-  for error analysis. */
+    tls_redo_helo:
 
-  #ifdef SUPPORT_TLS
-  if (tls_offered &&
-       verify_check_this_host(&(ob->hosts_avoid_tls), NULL, host->name,
-         host->address, NULL) != OK)
-    {
-    uschar buffer2[4096];
-    if (  !smtps
-       && !(done= smtp_write_command(&outblock, FALSE, "STARTTLS\r\n") >= 0))
-      goto SEND_FAILED;
-
-    /* If there is an I/O error, transmission of this message is deferred. If
-    there is a temporary rejection of STARRTLS and tls_tempfail_tryclear is
-    false, we also defer. However, if there is a temporary rejection of STARTTLS
-    and tls_tempfail_tryclear is true, or if there is an outright rejection of
-    STARTTLS, we carry on. This means we will try to send the message in clear,
-    unless the host is in hosts_require_tls (tested below). */
-
-    if (!smtps && !smtp_read_response(&inblock, buffer2, sizeof(buffer2), '2',
-                       ob->command_timeout))
+#ifdef SUPPORT_TLS
+    if (smtps  &&  tls_out.active < 0) /* ssl-on-connect, first pass */
       {
-      if (errno != 0 || buffer2[0] == 0 ||
-       (buffer2[0] == '4' && !ob->tls_tempfail_tryclear))
+      tls_offered = TRUE;
+      ob->tls_tempfail_tryclear = FALSE;
+      }
+    else                               /* all other cases */
+#endif
+
+      { esmtp_retry:
+
+      if (!(done= smtp_write_command(&outblock, FALSE, "%s %s\r\n",
+        !esmtp? "HELO" : lmtp? "LHLO" : "EHLO", active_hostname) >= 0))
+        goto SEND_FAILED;
+      if (!smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), '2', callout))
+        {
+       if (errno != 0 || responsebuffer[0] == 0 || lmtp || !esmtp || tls_out.active >= 0)
+         {
+         done= FALSE;
+         goto RESPONSE_FAILED;
+         }
+#ifdef SUPPORT_TLS
+        tls_offered = FALSE;
+#endif
+        esmtp = FALSE;
+        goto esmtp_retry;                      /* fallback to HELO */
+        }
+
+      /* Set tls_offered if the response to EHLO specifies support for STARTTLS. */
+#ifdef SUPPORT_TLS
+      if (esmtp && !suppress_tls &&  tls_out.active < 0)
        {
-       Ustrncpy(responsebuffer, buffer2, sizeof(responsebuffer));
-       done= FALSE;
-       goto RESPONSE_FAILED;
+       if (regex_STARTTLS == NULL) regex_STARTTLS =
+         regex_must_compile(US"\\n250[\\s\\-]STARTTLS(\\s|\\n|$)", FALSE, TRUE);
+
+       tls_offered = pcre_exec(regex_STARTTLS, NULL, CS responsebuffer,
+                     Ustrlen(responsebuffer), 0, PCRE_EOPT, NULL, 0) >= 0;
        }
+      else
+        tls_offered = FALSE;
+#endif
       }
 
-     /* STARTTLS accepted or ssl-on-connect: try to negotiate a TLS session. */
-    else
+    /* If TLS is available on this connection attempt to
+    start up a TLS session, unless the host is in hosts_avoid_tls. If successful,
+    send another EHLO - the server may give a different answer in secure mode. We
+    use a separate buffer for reading the response to STARTTLS so that if it is
+    negative, the original EHLO data is available for subsequent analysis, should
+    the client not be required to use TLS. If the response is bad, copy the buffer
+    for error analysis. */
+
+#ifdef SUPPORT_TLS
+    if (tls_offered &&
+       verify_check_this_host(&(ob->hosts_avoid_tls), NULL, host->name,
+         host->address, NULL) != OK &&
+       verify_check_this_host(&(ob->hosts_verify_avoid_tls), NULL, host->name,
+         host->address, NULL) != OK
+       )
       {
-      int rc = tls_client_start(inblock.sock, host, addr,
-        NULL,                    /* No DH param */
-        ob->tls_certificate, ob->tls_privatekey,
-        ob->tls_sni,
-        ob->tls_verify_certificates, ob->tls_crl,
-        ob->tls_require_ciphers,
-        ob->gnutls_require_mac, ob->gnutls_require_kx, ob->gnutls_require_proto,
-        callout);
-
-      /* TLS negotiation failed; give an error.  Try in clear on a new connection,
-         if the options permit it for this host. */
-      if (rc != OK)
+      uschar buffer2[4096];
+      if (  !smtps
+         && !(done= smtp_write_command(&outblock, FALSE, "STARTTLS\r\n") >= 0))
+        goto SEND_FAILED;
+
+      /* If there is an I/O error, transmission of this message is deferred. If
+      there is a temporary rejection of STARRTLS and tls_tempfail_tryclear is
+      false, we also defer. However, if there is a temporary rejection of STARTTLS
+      and tls_tempfail_tryclear is true, or if there is an outright rejection of
+      STARTTLS, we carry on. This means we will try to send the message in clear,
+      unless the host is in hosts_require_tls (tested below). */
+
+      if (!smtps && !smtp_read_response(&inblock, buffer2, sizeof(buffer2), '2',
+                       ob->command_timeout))
         {
-       if (rc == DEFER && ob->tls_tempfail_tryclear && !smtps &&
-          verify_check_this_host(&(ob->hosts_require_tls), NULL, host->name,
-            host->address, NULL) != OK)
+        if (errno != 0 || buffer2[0] == 0 ||
+               (buffer2[0] == '4' && !ob->tls_tempfail_tryclear))
          {
-          (void)close(inblock.sock);
-         log_write(0, LOG_MAIN, "TLS session failure: delivering unencrypted "
-           "to %s [%s] (not in hosts_require_tls)", host->name, host->address);
-         suppress_tls = TRUE;
-         goto tls_retry_connection;
+         Ustrncpy(responsebuffer, buffer2, sizeof(responsebuffer));
+         done= FALSE;
+         goto RESPONSE_FAILED;
+         }
+        }
+
+       /* STARTTLS accepted or ssl-on-connect: try to negotiate a TLS session. */
+      else
+        {
+       int oldtimeout = ob->command_timeout;
+       int rc;
+
+       ob->command_timeout = callout;
+        rc = tls_client_start(inblock.sock, host, addr, addr->transport);
+       ob->command_timeout = oldtimeout;
+
+        /* TLS negotiation failed; give an error.  Try in clear on a new connection,
+           if the options permit it for this host. */
+        if (rc != OK)
+          {
+         if (rc == DEFER && ob->tls_tempfail_tryclear && !smtps &&
+            verify_check_this_host(&(ob->hosts_require_tls), NULL, host->name,
+              host->address, NULL) != OK)
+           {
+           (void)close(inblock.sock);
+#ifdef EXPERIMENTAL_TPDA
+           (void) tpda_raise_event(addr->transport->tpda_event_action,
+                                   US"tcp:close", NULL);
+#endif
+           log_write(0, LOG_MAIN, "TLS session failure: delivering unencrypted "
+             "to %s [%s] (not in hosts_require_tls)", host->name, host->address);
+           suppress_tls = TRUE;
+           goto tls_retry_connection;
+           }
+         /*save_errno = ERRNO_TLSFAILURE;*/
+         /*message = US"failure while setting up TLS session";*/
+         send_quit = FALSE;
+         done= FALSE;
+         goto TLS_FAILED;
          }
-       /*save_errno = ERRNO_TLSFAILURE;*/
-       /*message = US"failure while setting up TLS session";*/
-       send_quit = FALSE;
-       done= FALSE;
-       goto TLS_FAILED;
-       }
 
-      /* TLS session is set up.  Copy info for logging. */
-      addr->cipher = tls_out.cipher;
-      addr->peerdn = tls_out.peerdn;
+        /* TLS session is set up.  Copy info for logging. */
+        addr->cipher = tls_out.cipher;
+        addr->peerdn = tls_out.peerdn;
 
-      /* For SMTPS we need to wait for the initial OK response, then do HELO. */
-      if (smtps)
-        goto smtps_redo_greeting;
+        /* For SMTPS we need to wait for the initial OK response, then do HELO. */
+        if (smtps)
+         goto smtps_redo_greeting;
 
-      /* For STARTTLS we need to redo EHLO */
-      goto tls_redo_helo;
+        /* For STARTTLS we need to redo EHLO */
+        goto tls_redo_helo;
+        }
       }
-    }
 
-  /* If the host is required to use a secure channel, ensure that we have one. */
-  if (tls_out.active < 0)
-    if (verify_check_this_host(&(ob->hosts_require_tls), NULL, host->name,
-       host->address, NULL) == OK)
+    /* If the host is required to use a secure channel, ensure that we have one. */
+    if (tls_out.active < 0)
+      if (verify_check_this_host(&(ob->hosts_require_tls), NULL, host->name,
+       host->address, NULL) == OK)
+        {
+        /*save_errno = ERRNO_TLSREQUIRED;*/
+        log_write(0, LOG_MAIN, "a TLS session is required for %s [%s], but %s",
+          host->name, host->address,
+       tls_offered? "an attempt to start TLS failed" : "the server did not offer TLS support");
+        done= FALSE;
+        goto TLS_FAILED;
+        }
+
+    #endif /*SUPPORT_TLS*/
+
+    done = TRUE; /* so far so good; have response to HELO */
+
+    /*XXX the EHLO response would be analyzed here for IGNOREQUOTA, SIZE, PIPELINING */
+
+    /* For now, transport_filter by cutthrough-delivery is not supported */
+    /* Need proper integration with the proper transport mechanism. */
+    if (cutthrough_delivery)
       {
-      /*save_errno = ERRNO_TLSREQUIRED;*/
-      log_write(0, LOG_MAIN, "a TLS session is required for %s [%s], but %s",
-        host->name, host->address,
-       tls_offered? "an attempt to start TLS failed" : "the server did not offer TLS support");
-      done= FALSE;
-      goto TLS_FAILED;
+      if (addr->transport->filter_command)
+        {
+        cutthrough_delivery= FALSE;
+        HDEBUG(D_acl|D_v) debug_printf("Cutthrough cancelled by presence of transport filter\n");
+        }
+#ifndef DISABLE_DKIM
+      if (ob->dkim_domain)
+        {
+        cutthrough_delivery= FALSE;
+        HDEBUG(D_acl|D_v) debug_printf("Cutthrough cancelled by presence of DKIM signing\n");
+        }
+#endif
       }
 
-  #endif /*SUPPORT_TLS*/
+    SEND_FAILED:
+    RESPONSE_FAILED:
+    TLS_FAILED:
+    ;
+    /* Clear down of the TLS, SMTP and TCP layers on error is handled below.  */
 
-  done = TRUE; /* so far so good; have response to HELO */
+    /* Failure to accept HELO is cached; this blocks the whole domain for all
+    senders. I/O errors and defer responses are not cached. */
 
-  /*XXX the EHLO response would be analyzed here for IGNOREQUOTA, SIZE, PIPELINING, AUTH */
-  /* If we haven't authenticated, but are required to, give up. */
+    if (!done)
+      {
+      *failure_ptr = US"mail";     /* At or before MAIL */
+      if (errno == 0 && responsebuffer[0] == '5')
+        {
+        setflag(addr, af_verify_nsfail);
+        new_domain_record.result = ccache_reject;
+        }
+      }
 
-  /*XXX "filter command specified for this transport" ??? */
-  /* for now, transport_filter by cutthrough-delivery is not supported */
-  /* Need proper integration with the proper transport mechanism. */
+    /* If we haven't authenticated, but are required to, give up. */
+    /* Try to AUTH */
 
+    else done = smtp_auth(responsebuffer, sizeof(responsebuffer),
+       addr, host, ob, esmtp, &inblock, &outblock) == OK  &&
 
-  SEND_FAILED:
-  RESPONSE_FAILED:
-  TLS_FAILED:
-  ;
-  /* Clear down of the TLS, SMTP and TCP layers on error is handled below.  */
+               /* Copy AUTH info for logging */
+      ( (addr->authenticator = client_authenticator),
+        (addr->auth_id = client_authenticated_id),
 
+    /* Build a mail-AUTH string (re-using responsebuffer for convenience */
+        !smtp_mail_auth_str(responsebuffer, sizeof(responsebuffer), addr, ob)
+      )  &&
 
-  /* Failure to accept HELO is cached; this blocks the whole domain for all
-  senders. I/O errors and defer responses are not cached. */
+      ( (addr->auth_sndr = client_authenticated_sender),
 
-  if (!done)
-    {
-    *failure_ptr = US"mail";     /* At or before MAIL */
-    if (errno == 0 && responsebuffer[0] == '5')
-      {
-      setflag(addr, af_verify_nsfail);
-      new_domain_record.result = ccache_reject;
-      }
-    }
+    /* Send the MAIL command */
+        (smtp_write_command(&outblock, FALSE, "MAIL FROM:<%s>%s\r\n",
+          from_address, responsebuffer) >= 0)
+      )  &&
 
-  /* Send the MAIL command */
+      smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer),
+        '2', callout);
 
-  else done =
-    smtp_write_command(&outblock, FALSE, "MAIL FROM:<%s>\r\n",
-      from_address) >= 0 &&
-    smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer),
-      '2', callout);
+    deliver_host = deliver_host_address = NULL;
+    deliver_domain = save_deliver_domain;
 
-  /* If the host does not accept MAIL FROM:<>, arrange to cache this
-  information, but again, don't record anything for an I/O error or a defer. Do
-  not cache rejections of MAIL when a non-empty sender has been used, because
-  that blocks the whole domain for all senders. */
+    /* If the host does not accept MAIL FROM:<>, arrange to cache this
+    information, but again, don't record anything for an I/O error or a defer. Do
+    not cache rejections of MAIL when a non-empty sender has been used, because
+    that blocks the whole domain for all senders. */
 
-  if (!done)
-    {
-    *failure_ptr = US"mail";     /* At or before MAIL */
-    if (errno == 0 && responsebuffer[0] == '5')
+    if (!done)
       {
-      setflag(addr, af_verify_nsfail);
-      if (from_address[0] == 0)
-        new_domain_record.result = ccache_reject_mfnull;
+      *failure_ptr = US"mail";     /* At or before MAIL */
+      if (errno == 0 && responsebuffer[0] == '5')
+        {
+        setflag(addr, af_verify_nsfail);
+        if (from_address[0] == 0)
+          new_domain_record.result = ccache_reject_mfnull;
+        }
       }
-    }
 
-  /* Otherwise, proceed to check a "random" address (if required), then the
-  given address, and the postmaster address (if required). Between each check,
-  issue RSET, because some servers accept only one recipient after MAIL
-  FROM:<>.
+    /* Otherwise, proceed to check a "random" address (if required), then the
+    given address, and the postmaster address (if required). Between each check,
+    issue RSET, because some servers accept only one recipient after MAIL
+    FROM:<>.
 
-  Before doing this, set the result in the domain cache record to "accept",
-  unless its previous value was ccache_reject_mfnull. In that case, the domain
-  rejects MAIL FROM:<> and we want to continue to remember that. When that is
-  the case, we have got here only in the case of a recipient verification with
-  a non-null sender. */
+    Before doing this, set the result in the domain cache record to "accept",
+    unless its previous value was ccache_reject_mfnull. In that case, the domain
+    rejects MAIL FROM:<> and we want to continue to remember that. When that is
+    the case, we have got here only in the case of a recipient verification with
+    a non-null sender. */
 
-  else
-    {
-    new_domain_record.result =
-      (old_domain_cache_result == ccache_reject_mfnull)?
-        ccache_reject_mfnull: ccache_accept;
+    else
+      {
+      new_domain_record.result =
+        (old_domain_cache_result == ccache_reject_mfnull)?
+          ccache_reject_mfnull: ccache_accept;
 
-    /* Do the random local part check first */
+      /* Do the random local part check first */
 
-    if (random_local_part != NULL)
-      {
-      uschar randombuffer[1024];
-      BOOL random_ok =
-        smtp_write_command(&outblock, FALSE,
-          "RCPT TO:<%.1000s@%.1000s>\r\n", random_local_part,
-          addr->domain) >= 0 &&
-        smtp_read_response(&inblock, randombuffer,
-          sizeof(randombuffer), '2', callout);
+      if (random_local_part != NULL)
+        {
+        uschar randombuffer[1024];
+        BOOL random_ok =
+          smtp_write_command(&outblock, FALSE,
+            "RCPT TO:<%.1000s@%.1000s>\r\n", random_local_part,
+            addr->domain) >= 0 &&
+          smtp_read_response(&inblock, randombuffer,
+            sizeof(randombuffer), '2', callout);
 
-      /* Remember when we last did a random test */
+        /* Remember when we last did a random test */
 
-      new_domain_record.random_stamp = time(NULL);
+        new_domain_record.random_stamp = time(NULL);
 
-      /* If accepted, we aren't going to do any further tests below. */
+        /* If accepted, we aren't going to do any further tests below. */
 
-      if (random_ok)
-        {
-        new_domain_record.random_result = ccache_accept;
-        }
+        if (random_ok)
+          {
+          new_domain_record.random_result = ccache_accept;
+          }
 
-      /* Otherwise, cache a real negative response, and get back to the right
-      state to send RCPT. Unless there's some problem such as a dropped
-      connection, we expect to succeed, because the commands succeeded above. */
+        /* Otherwise, cache a real negative response, and get back to the right
+        state to send RCPT. Unless there's some problem such as a dropped
+        connection, we expect to succeed, because the commands succeeded above. */
 
-      else if (errno == 0)
+        else if (errno == 0)
+          {
+          if (randombuffer[0] == '5')
+            new_domain_record.random_result = ccache_reject;
+
+          done =
+            smtp_write_command(&outblock, FALSE, "RSET\r\n") >= 0 &&
+            smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer),
+              '2', callout) &&
+
+            smtp_write_command(&outblock, FALSE, "MAIL FROM:<%s>\r\n",
+              from_address) >= 0 &&
+            smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer),
+              '2', callout);
+          }
+        else done = FALSE;    /* Some timeout/connection problem */
+        }                     /* Random check */
+
+      /* If the host is accepting all local parts, as determined by the "random"
+      check, we don't need to waste time doing any further checking. */
+
+      if (new_domain_record.random_result != ccache_accept && done)
         {
-        if (randombuffer[0] == '5')
-          new_domain_record.random_result = ccache_reject;
+        /* Get the rcpt_include_affixes flag from the transport if there is one,
+        but assume FALSE if there is not. */
 
         done =
-          smtp_write_command(&outblock, FALSE, "RSET\r\n") >= 0 &&
-          smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer),
-            '2', callout) &&
-
-          smtp_write_command(&outblock, FALSE, "MAIL FROM:<%s>\r\n",
-            from_address) >= 0 &&
+          smtp_write_command(&outblock, FALSE, "RCPT TO:<%.1000s>\r\n",
+            transport_rcpt_address(addr,
+              (addr->transport == NULL)? FALSE :
+               addr->transport->rcpt_include_affixes)) >= 0 &&
           smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer),
             '2', callout);
-        }
-      else done = FALSE;    /* Some timeout/connection problem */
-      }                     /* Random check */
 
-    /* If the host is accepting all local parts, as determined by the "random"
-    check, we don't need to waste time doing any further checking. */
+        if (done)
+          new_address_record.result = ccache_accept;
+        else if (errno == 0 && responsebuffer[0] == '5')
+          {
+          *failure_ptr = US"recipient";
+          new_address_record.result = ccache_reject;
+          }
 
-    if (new_domain_record.random_result != ccache_accept && done)
-      {
-      /* Get the rcpt_include_affixes flag from the transport if there is one,
-      but assume FALSE if there is not. */
-
-      done =
-        smtp_write_command(&outblock, FALSE, "RCPT TO:<%.1000s>\r\n",
-          transport_rcpt_address(addr,
-            (addr->transport == NULL)? FALSE :
-             addr->transport->rcpt_include_affixes)) >= 0 &&
-        smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer),
-          '2', callout);
-
-      if (done)
-        new_address_record.result = ccache_accept;
-      else if (errno == 0 && responsebuffer[0] == '5')
-        {
-        *failure_ptr = US"recipient";
-        new_address_record.result = ccache_reject;
-        }
+        /* Do postmaster check if requested; if a full check is required, we
+        check for RCPT TO:<postmaster> (no domain) in accordance with RFC 821. */
 
-      /* Do postmaster check if requested; if a full check is required, we
-      check for RCPT TO:<postmaster> (no domain) in accordance with RFC 821. */
+        if (done && pm_mailfrom != NULL)
+          {
+          /*XXX not suitable for cutthrough - sequencing problems */
+       cutthrough_delivery= FALSE;
+       HDEBUG(D_acl|D_v) debug_printf("Cutthrough cancelled by presence of postmaster verify\n");
 
-      if (done && pm_mailfrom != NULL)
-        {
-        /*XXX not suitable for cutthrough - sequencing problems */
-       cutthrough_delivery= FALSE;
-       HDEBUG(D_acl|D_v) debug_printf("Cutthrough cancelled by presence of postmaster verify\n");
+          done =
+            smtp_write_command(&outblock, FALSE, "RSET\r\n") >= 0 &&
+            smtp_read_response(&inblock, responsebuffer,
+              sizeof(responsebuffer), '2', callout) &&
 
-        done =
-          smtp_write_command(&outblock, FALSE, "RSET\r\n") >= 0 &&
-          smtp_read_response(&inblock, responsebuffer,
-            sizeof(responsebuffer), '2', callout) &&
+            smtp_write_command(&outblock, FALSE,
+              "MAIL FROM:<%s>\r\n", pm_mailfrom) >= 0 &&
+            smtp_read_response(&inblock, responsebuffer,
+              sizeof(responsebuffer), '2', callout) &&
 
-          smtp_write_command(&outblock, FALSE,
-            "MAIL FROM:<%s>\r\n", pm_mailfrom) >= 0 &&
-          smtp_read_response(&inblock, responsebuffer,
-            sizeof(responsebuffer), '2', callout) &&
+            /* First try using the current domain */
 
-          /* First try using the current domain */
+            ((
+            smtp_write_command(&outblock, FALSE,
+              "RCPT TO:<postmaster@%.1000s>\r\n", addr->domain) >= 0 &&
+            smtp_read_response(&inblock, responsebuffer,
+              sizeof(responsebuffer), '2', callout)
+            )
 
-          ((
-          smtp_write_command(&outblock, FALSE,
-            "RCPT TO:<postmaster@%.1000s>\r\n", addr->domain) >= 0 &&
-          smtp_read_response(&inblock, responsebuffer,
-            sizeof(responsebuffer), '2', callout)
-          )
+            ||
 
-          ||
+            /* If that doesn't work, and a full check is requested,
+            try without the domain. */
 
-          /* If that doesn't work, and a full check is requested,
-          try without the domain. */
+            (
+            (options & vopt_callout_fullpm) != 0 &&
+            smtp_write_command(&outblock, FALSE,
+              "RCPT TO:<postmaster>\r\n") >= 0 &&
+            smtp_read_response(&inblock, responsebuffer,
+              sizeof(responsebuffer), '2', callout)
+            ));
 
-          (
-          (options & vopt_callout_fullpm) != 0 &&
-          smtp_write_command(&outblock, FALSE,
-            "RCPT TO:<postmaster>\r\n") >= 0 &&
-          smtp_read_response(&inblock, responsebuffer,
-            sizeof(responsebuffer), '2', callout)
-          ));
+          /* Sort out the cache record */
 
-        /* Sort out the cache record */
+          new_domain_record.postmaster_stamp = time(NULL);
 
-        new_domain_record.postmaster_stamp = time(NULL);
-
-        if (done)
-          new_domain_record.postmaster_result = ccache_accept;
-        else if (errno == 0 && responsebuffer[0] == '5')
-          {
-          *failure_ptr = US"postmaster";
-          setflag(addr, af_verify_pmfail);
-          new_domain_record.postmaster_result = ccache_reject;
+          if (done)
+            new_domain_record.postmaster_result = ccache_accept;
+          else if (errno == 0 && responsebuffer[0] == '5')
+            {
+            *failure_ptr = US"postmaster";
+            setflag(addr, af_verify_pmfail);
+            new_domain_record.postmaster_result = ccache_reject;
+            }
           }
-        }
-      }           /* Random not accepted */
-    }             /* MAIL FROM: accepted */
+        }           /* Random not accepted */
+      }             /* MAIL FROM: accepted */
 
-  /* For any failure of the main check, other than a negative response, we just
-  close the connection and carry on. We can identify a negative response by the
-  fact that errno is zero. For I/O errors it will be non-zero
+    /* For any failure of the main check, other than a negative response, we just
+    close the connection and carry on. We can identify a negative response by the
+    fact that errno is zero. For I/O errors it will be non-zero
 
-  Set up different error texts for logging and for sending back to the caller
-  as an SMTP response. Log in all cases, using a one-line format. For sender
-  callouts, give a full response to the caller, but for recipient callouts,
-  don't give the IP address because this may be an internal host whose identity
-  is not to be widely broadcast. */
+    Set up different error texts for logging and for sending back to the caller
+    as an SMTP response. Log in all cases, using a one-line format. For sender
+    callouts, give a full response to the caller, but for recipient callouts,
+    don't give the IP address because this may be an internal host whose identity
+    is not to be widely broadcast. */
 
-  if (!done)
-    {
-    if (errno == ETIMEDOUT)
-      {
-      HDEBUG(D_verify) debug_printf("SMTP timeout\n");
-      send_quit = FALSE;
-      }
-    else if (errno == 0)
+    if (!done)
       {
-      if (*responsebuffer == 0) Ustrcpy(responsebuffer, US"connection dropped");
+      if (errno == ETIMEDOUT)
+        {
+        HDEBUG(D_verify) debug_printf("SMTP timeout\n");
+        send_quit = FALSE;
+        }
+      else if (errno == 0)
+        {
+        if (*responsebuffer == 0) Ustrcpy(responsebuffer, US"connection dropped");
 
-      addr->message =
-        string_sprintf("response to \"%s\" from %s [%s] was: %s",
-          big_buffer, host->name, host->address,
-          string_printing(responsebuffer));
+        addr->message =
+          string_sprintf("response to \"%s\" from %s [%s] was: %s",
+            big_buffer, host->name, host->address,
+            string_printing(responsebuffer));
 
-      addr->user_message = is_recipient?
-        string_sprintf("Callout verification failed:\n%s", responsebuffer)
-        :
-        string_sprintf("Called:   %s\nSent:     %s\nResponse: %s",
-          host->address, big_buffer, responsebuffer);
+        addr->user_message = is_recipient?
+          string_sprintf("Callout verification failed:\n%s", responsebuffer)
+          :
+          string_sprintf("Called:   %s\nSent:     %s\nResponse: %s",
+            host->address, big_buffer, responsebuffer);
 
-      /* Hard rejection ends the process */
+        /* Hard rejection ends the process */
 
-      if (responsebuffer[0] == '5')   /* Address rejected */
-        {
-        yield = FAIL;
-        done = TRUE;
+        if (responsebuffer[0] == '5')   /* Address rejected */
+          {
+          yield = FAIL;
+          done = TRUE;
+          }
         }
       }
-    }
 
-  /* End the SMTP conversation and close the connection. */
-
-  /* Cutthrough - on a successfull connect and recipient-verify with use-sender
-  and we have no cutthrough conn so far
-  here is where we want to leave the conn open */
-  if (  cutthrough_delivery
-     && done
-     && yield == OK
-     && (options & (vopt_callout_recipsender|vopt_callout_recippmaster)) == vopt_callout_recipsender
-     && !random_local_part
-     && !pm_mailfrom
-     && cutthrough_fd < 0
-     )
-    {
-    cutthrough_fd= outblock.sock;      /* We assume no buffer in use in the outblock */
-    cutthrough_addr = *addr;           /* Save the address_item for later logging */
-    cutthrough_addr.host_used = store_get(sizeof(host_item));
-    cutthrough_addr.host_used->name =    host->name;
-    cutthrough_addr.host_used->address = host->address;
-    cutthrough_addr.host_used->port =    port;
-    if (addr->parent)
-      *(cutthrough_addr.parent = store_get(sizeof(address_item)))= *addr->parent;
-    ctblock.buffer = ctbuffer;
-    ctblock.buffersize = sizeof(ctbuffer);
-    ctblock.ptr = ctbuffer;
-    /* ctblock.cmd_count = 0; ctblock.authenticating = FALSE; */
-    ctblock.sock = cutthrough_fd;
-    }
-  else
-    {
-    if (options & vopt_callout_recipsender)
-      cancel_cutthrough_connection();  /* Ensure no cutthrough on multiple address verifies */
-    if (send_quit) (void)smtp_write_command(&outblock, FALSE, "QUIT\r\n");
-
-    #ifdef SUPPORT_TLS
-    tls_close(FALSE, TRUE);
-    #endif
-    (void)close(inblock.sock);
-    }
+    /* End the SMTP conversation and close the connection. */
+
+    /* Cutthrough - on a successfull connect and recipient-verify with use-sender
+    and we have no cutthrough conn so far
+    here is where we want to leave the conn open */
+    if (  cutthrough_delivery
+       && done
+       && yield == OK
+       && (options & (vopt_callout_recipsender|vopt_callout_recippmaster)) == vopt_callout_recipsender
+       && !random_local_part
+       && !pm_mailfrom
+       && cutthrough_fd < 0
+       )
+      {
+      cutthrough_fd= outblock.sock;    /* We assume no buffer in use in the outblock */
+      cutthrough_addr = *addr;         /* Save the address_item for later logging */
+      cutthrough_addr.next =     NULL;
+      cutthrough_addr.host_used = store_get(sizeof(host_item));
+      cutthrough_addr.host_used->name =    host->name;
+      cutthrough_addr.host_used->address = host->address;
+      cutthrough_addr.host_used->port =    port;
+      if (addr->parent)
+        *(cutthrough_addr.parent = store_get(sizeof(address_item)))= *addr->parent;
+      ctblock.buffer = ctbuffer;
+      ctblock.buffersize = sizeof(ctbuffer);
+      ctblock.ptr = ctbuffer;
+      /* ctblock.cmd_count = 0; ctblock.authenticating = FALSE; */
+      ctblock.sock = cutthrough_fd;
+      }
+    else
+      {
+      /* Ensure no cutthrough on multiple address verifies */
+      if (options & vopt_callout_recipsender)
+        cancel_cutthrough_connection("multiple verify calls");
+      if (send_quit) (void)smtp_write_command(&outblock, FALSE, "QUIT\r\n");
 
-  }    /* Loop through all hosts, while !done */
+#ifdef SUPPORT_TLS
+      tls_close(FALSE, TRUE);
+#endif
+      (void)close(inblock.sock);
+#ifdef EXPERIMENTAL_TPDA
+      (void) tpda_raise_event(addr->transport->tpda_event_action,
+                             US"tcp:close", NULL);
+#endif
+      }
+
+    }    /* Loop through all hosts, while !done */
+  }
 
 /* If we get here with done == TRUE, a successful callout happened, and yield
 will be set OK or FAIL according to the response to the RCPT command.
@@ -1120,7 +1187,7 @@ cutthrough_puts(uschar * cp, int n)
 {
 if (cutthrough_fd < 0)       return TRUE;
 if (_cutthrough_puts(cp, n)) return TRUE;
-cancel_cutthrough_connection();
+cancel_cutthrough_connection("transmit failed");
 return FALSE;
 }
 
@@ -1142,7 +1209,7 @@ BOOL
 cutthrough_flush_send( void )
 {
 if (_cutthrough_flush_send()) return TRUE;
-cancel_cutthrough_connection();
+cancel_cutthrough_connection("transmit failed");
 return FALSE;
 }
 
@@ -1169,7 +1236,7 @@ inblock.ptrend = inbuffer;
 inblock.sock = cutthrough_fd;
 /* this relies on (inblock.sock == tls_out.active) */
 if(!smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), expect, CUTTHROUGH_DATA_TIMEOUT))
-  cancel_cutthrough_connection();
+  cancel_cutthrough_connection("target timeout on read");
 
 if(copy != NULL)
   {
@@ -1201,32 +1268,48 @@ return cutthrough_response('3', NULL) == '3';
 }
 
 
+/* fd and use_crlf args only to match write_chunk() */
+static BOOL
+cutthrough_write_chunk(int fd, uschar * s, int len, BOOL use_crlf)
+{
+uschar * s2;
+while(s && (s2 = Ustrchr(s, '\n')))
+ {
+ if(!cutthrough_puts(s, s2-s) || !cutthrough_put_nl())
+  return FALSE;
+ s = s2+1;
+ }
+return TRUE;
+}
+
+
 /* Buffered send of headers.  Return success boolean. */
 /* Expands newlines to wire format (CR,NL).           */
 /* Also sends header-terminating blank line.          */
 BOOL
 cutthrough_headers_send( void )
 {
-header_line * h;
-uschar * cp1, * cp2;
-
 if(cutthrough_fd < 0)
   return FALSE;
 
-for(h= header_list; h != NULL; h= h->next)
-  if(h->type != htype_old  &&  h->text != NULL)
-    for (cp1 = h->text; *cp1 && (cp2 = Ustrchr(cp1, '\n')); cp1 = cp2+1)
-      if(  !cutthrough_puts(cp1, cp2-cp1)
-        || !cutthrough_put_nl())
-        return FALSE;
+/* We share a routine with the mainline transport to handle header add/remove/rewrites,
+   but having a separate buffered-output function (for now)
+*/
+HDEBUG(D_acl) debug_printf("----------- start cutthrough headers send -----------\n");
+
+if (!transport_headers_send(&cutthrough_addr, cutthrough_fd,
+       cutthrough_addr.transport->add_headers, cutthrough_addr.transport->remove_headers,
+       &cutthrough_write_chunk, TRUE,
+       cutthrough_addr.transport->rewrite_rules, cutthrough_addr.transport->rewrite_existflags))
+  return FALSE;
 
-HDEBUG(D_transport|D_acl|D_v) debug_printf("  SMTP>>(nl)\n");
-return cutthrough_put_nl();
+HDEBUG(D_acl) debug_printf("----------- done cutthrough headers send ------------\n");
+return TRUE;
 }
 
 
 static void
-close_cutthrough_connection( void )
+close_cutthrough_connection( const char * why )
 {
 if(cutthrough_fd >= 0)
   {
@@ -1245,15 +1328,15 @@ if(cutthrough_fd >= 0)
   #endif
   (void)close(cutthrough_fd);
   cutthrough_fd= -1;
-  HDEBUG(D_acl) debug_printf("----------- cutthrough shutdown ------------\n");
+  HDEBUG(D_acl) debug_printf("----------- cutthrough shutdown (%s) ------------\n", why);
   }
 ctblock.ptr = ctbuffer;
 }
 
 void
-cancel_cutthrough_connection( void )
+cancel_cutthrough_connection( const char * why )
 {
-close_cutthrough_connection();
+close_cutthrough_connection(why);
 cutthrough_delivery= FALSE;
 }
 
@@ -1278,7 +1361,7 @@ switch(cutthrough_response('2', &cutthrough_addr.message))
   {
   case '2':
     delivery_log(LOG_MAIN, &cutthrough_addr, (int)'>', NULL);
-    close_cutthrough_connection();
+    close_cutthrough_connection("delivered");
     break;
 
   case '4':
@@ -1509,6 +1592,12 @@ addresses, such rewriting fails. */
 
 if (address[0] == 0) return OK;
 
+/* Flip the legacy TLS-related variables over to the outbound set in case
+they're used in the context of a transport used by verification. Reset them
+at exit from this routine. */
+
+tls_modify_variables(&tls_out);
+
 /* Save a copy of the sender address for re-instating if we change it to <>
 while verifying a sender address (a nice bit of self-reference there). */
 
@@ -1680,8 +1769,20 @@ while (addr_new != NULL)
                   string_is_ip_address(host->name, NULL) != 0)
                 (void)host_find_byname(host, NULL, flags, &canonical_name, TRUE);
               else
+               {
+               uschar * d_request = NULL, * d_require = NULL;
+               if (Ustrcmp(addr->transport->driver_name, "smtp") == 0)
+                 {
+                 smtp_transport_options_block * ob =
+                     (smtp_transport_options_block *)
+                       addr->transport->options_block;
+                 d_request = ob->dnssec_request_domains;
+                 d_require = ob->dnssec_require_domains;
+                 }
+
                 (void)host_find_bydns(host, NULL, flags, NULL, NULL, NULL,
-                  &canonical_name, NULL);
+                 d_request, d_require, &canonical_name, NULL);
+               }
               }
             }
           }
@@ -1701,6 +1802,9 @@ while (addr_new != NULL)
           }
         else
           {
+#ifdef SUPPORT_TLS
+         deliver_set_expansions(addr);
+#endif
           rc = do_callout(addr, host_list, &tf, callout, callout_overall,
             callout_connect, options, se_mailfrom, pm_mailfrom);
           }
@@ -1752,10 +1856,14 @@ while (addr_new != NULL)
         }
       respond_printf(f, "%s\n", cr);
       }
-    cancel_cutthrough_connection();
+    cancel_cutthrough_connection("routing hard fail");
 
-    if (!full_info) return copy_error(vaddr, addr, FAIL);
-      else yield = FAIL;
+    if (!full_info)
+    {
+      yield = copy_error(vaddr, addr, FAIL);
+      goto out;
+    }
+    else yield = FAIL;
     }
 
   /* Soft failure */
@@ -1787,10 +1895,14 @@ while (addr_new != NULL)
         }
       respond_printf(f, "%s\n", cr);
       }
-    cancel_cutthrough_connection();
+    cancel_cutthrough_connection("routing soft fail");
 
-    if (!full_info) return copy_error(vaddr, addr, DEFER);
-      else if (yield == OK) yield = DEFER;
+    if (!full_info)
+      {
+      yield = copy_error(vaddr, addr, DEFER);
+      goto out;
+      }
+    else if (yield == OK) yield = DEFER;
     }
 
   /* If we are handling EXPN, we do not want to continue to route beyond
@@ -1813,7 +1925,8 @@ while (addr_new != NULL)
       if (addr_new == NULL) ok_prefix = US"250 ";
       respond_printf(f, "%s<%s>\r\n", ok_prefix, addr2->address);
       }
-    return OK;
+    yield = OK;
+    goto out;
     }
 
   /* Successful routing other than EXPN. */
@@ -1848,7 +1961,8 @@ while (addr_new != NULL)
       of $address_data to be that of the child */
 
       vaddr->p.address_data = addr->p.address_data;
-      return OK;
+      yield = OK;
+      goto out;
       }
     }
   }     /* Loop for generated addresses */
@@ -1865,7 +1979,7 @@ discarded, usually because of the use of :blackhole: in an alias file. */
 if (allok && addr_local == NULL && addr_remote == NULL)
   {
   fprintf(f, "mail to %s is discarded\n", address);
-  return yield;
+  goto out;
   }
 
 for (addr_list = addr_local, i = 0; i < 2; addr_list = addr_remote, i++)
@@ -1949,9 +2063,12 @@ for (addr_list = addr_local, i = 0; i < 2; addr_list = addr_remote, i++)
     }
   }
 
-/* Will be DEFER or FAIL if any one address has, only for full_info (which is
+/* Yield will be DEFER or FAIL if any one address has, only for full_info (which is
 the -bv or -bt case). */
 
+out:
+tls_modify_variables(&tls_in);
+
 return yield;
 }
 
@@ -2080,6 +2197,41 @@ return yield;
 }
 
 
+/*************************************************
+*      Check header names for 8-bit characters   *
+*************************************************/
+
+/* This function checks for invalid charcters in header names. See
+RFC 5322, 2.2. and RFC 6532, 3.
+
+Arguments:
+  msgptr     where to put an error message
+
+Returns:     OK
+             FAIL
+*/
+
+int
+verify_check_header_names_ascii(uschar **msgptr)
+{
+header_line *h;
+uschar *colon, *s;
+
+for (h = header_list; h != NULL; h = h->next)
+  {
+   colon = Ustrchr(h->text, ':');
+   for(s = h->text; s < colon; s++)
+     {
+        if ((*s < 33) || (*s > 126))
+        {
+                *msgptr = string_sprintf("Invalid character in header \"%.*s\" found",
+                                         colon - h->text, h->text);
+                return FAIL;
+        }
+     }
+  }
+return OK;
+}
 
 /*************************************************
 *          Check for blind recipients            *
@@ -3426,7 +3578,7 @@ revadd[0] = 0;
 
 /* In case this is the first time the DNS resolver is being used. */
 
-dns_init(FALSE, FALSE);
+dns_init(FALSE, FALSE, FALSE); /*XXX dnssec? */
 
 /* Loop through all the domains supplied, until something matches */
 
@@ -3597,4 +3749,6 @@ while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL
 return FAIL;
 }
 
+/* vi: aw ai sw=2
+*/
 /* End of verify.c */