Transport: fix smtp under combo of mua_wrapper and limited max_rcpt
[exim.git] / src / src / transports / smtp.c
index 1ce0cbe1144ce8c954190c79d46b01c3b43bc182..997281901b7b90d546e6899a1304074bdd18a00e 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2017 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "../exim.h"
@@ -87,6 +87,8 @@ optionlist smtp_transport_options[] = {
 #ifdef SUPPORT_TLS
   { "hosts_nopass_tls",     opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, hosts_nopass_tls) },
+  { "hosts_noproxy_tls",    opt_stringptr,
+      (void *)offsetof(smtp_transport_options_block, hosts_noproxy_tls) },
 #endif
   { "hosts_override",       opt_bool,
       (void *)offsetof(smtp_transport_options_block, hosts_override) },
@@ -218,7 +220,10 @@ smtp_transport_options_block smtp_transport_option_defaults = {
   NULL,                /* hosts_verify_avoid_tls */
   NULL,                /* hosts_avoid_pipelining */
   NULL,                /* hosts_avoid_esmtp */
+#ifdef SUPPORT_TLS
   NULL,                /* hosts_nopass_tls */
+  US"*",              /* hosts_noproxy_tls */
+#endif
   5*60,                /* command_timeout */
   5*60,                /* connect_timeout; shorter system default overrides */
   5*60,                /* data timeout */
@@ -283,6 +288,8 @@ static uschar *smtp_command;                /* Points to last cmd for error messages */
 static uschar *mail_command;           /* Points to MAIL cmd for error messages */
 static uschar *data_command = US"";    /* Points to DATA cmd for error messages */
 static BOOL    update_waiting;         /* TRUE to update the "wait" database */
+
+/*XXX move to smtp_context */
 static BOOL    pipelining_active;      /* current transaction is in pipe mode */
 
 
@@ -510,82 +517,62 @@ check_response(host_item *host, int *errno_value, int more_errno,
   uschar *buffer, int *yield, uschar **message, BOOL *pass_message)
 {
 uschar * pl = pipelining_active ? US"pipelined " : US"";
+const uschar * s;
 
 *yield = '4';    /* Default setting is to give a temporary error */
 
-/* Handle response timeout */
-
-if (*errno_value == ETIMEDOUT)
-  {
-  *message = US string_sprintf("SMTP timeout after %s%s",
-      pl, smtp_command);
-  if (transport_count > 0)
-    *message = US string_sprintf("%s (%d bytes written)", *message,
-      transport_count);
-  return FALSE;
-  }
-
-/* Handle malformed SMTP response */
-
-if (*errno_value == ERRNO_SMTPFORMAT)
-  {
-  const uschar *malfresp = string_printing(buffer);
-  while (isspace(*malfresp)) malfresp++;
-  *message = *malfresp == 0
-    ? string_sprintf("Malformed SMTP reply (an empty line) "
-       "in response to %s%s", pl, smtp_command)
-    : string_sprintf("Malformed SMTP reply in response to %s%s: %s",
-       pl, smtp_command, malfresp);
-  return FALSE;
-  }
-
-/* Handle a failed filter process error; can't send QUIT as we mustn't
-end the DATA. */
-
-if (*errno_value == ERRNO_FILTER_FAIL)
-  {
-  *message = US string_sprintf("transport filter process failed (%d)%s",
-    more_errno,
-    (more_errno == EX_EXECFAILED)? ": unable to execute command" : "");
-  return FALSE;
-  }
-
-/* Handle a failed add_headers expansion; can't send QUIT as we mustn't
-end the DATA. */
-
-if (*errno_value == ERRNO_CHHEADER_FAIL)
-  {
-  *message =
-    US string_sprintf("failed to expand headers_add or headers_remove: %s",
-      expand_string_message);
-  return FALSE;
-  }
-
-/* Handle failure to write a complete data block */
-
-if (*errno_value == ERRNO_WRITEINCOMPLETE)
+switch(*errno_value)
   {
-  *message = US string_sprintf("failed to write a data block");
-  return FALSE;
-  }
+  case ETIMEDOUT:              /* Handle response timeout */
+    *message = US string_sprintf("SMTP timeout after %s%s",
+       pl, smtp_command);
+    if (transport_count > 0)
+      *message = US string_sprintf("%s (%d bytes written)", *message,
+       transport_count);
+    return FALSE;
+
+  case ERRNO_SMTPFORMAT:       /* Handle malformed SMTP response */
+    s = string_printing(buffer);
+    while (isspace(*s)) s++;
+    *message = *s == 0
+      ? string_sprintf("Malformed SMTP reply (an empty line) "
+         "in response to %s%s", pl, smtp_command)
+      : string_sprintf("Malformed SMTP reply in response to %s%s: %s",
+         pl, smtp_command, s);
+    return FALSE;
+
+  case ERRNO_FILTER_FAIL:      /* Handle a failed filter process error;
+                         can't send QUIT as we mustn't end the DATA. */
+    *message = string_sprintf("transport filter process failed (%d)%s",
+      more_errno,
+      more_errno == EX_EXECFAILED ? ": unable to execute command" : "");
+    return FALSE;
+
+  case ERRNO_CHHEADER_FAIL:    /* Handle a failed add_headers expansion;
+                           can't send QUIT as we mustn't end the DATA. */
+    *message =
+      string_sprintf("failed to expand headers_add or headers_remove: %s",
+       expand_string_message);
+    return FALSE;
+
+  case ERRNO_WRITEINCOMPLETE:  /* failure to write a complete data block */
+    *message = string_sprintf("failed to write a data block");
+    return FALSE;
 
 #ifdef SUPPORT_I18N
-/* Handle lack of advertised SMTPUTF8, for international message */
-if (*errno_value == ERRNO_UTF8_FWD)
-  {
-  *message = US"utf8 support required but not offered for forwarding";
-  DEBUG(D_deliver|D_transport) debug_printf("%s\n", *message);
-  return TRUE;
-  }
+  case ERRNO_UTF8_FWD: /* no advertised SMTPUTF8, for international message */
+    *message = US"utf8 support required but not offered for forwarding";
+    DEBUG(D_deliver|D_transport) debug_printf("%s\n", *message);
+    return TRUE;
 #endif
+  }
 
 /* Handle error responses from the remote mailer. */
 
 if (buffer[0] != 0)
   {
-  const uschar *s = string_printing(buffer);
-  *message = US string_sprintf("SMTP error from remote mail server after %s%s: "
-    "%s", pl, smtp_command, s);
+  *message = string_sprintf("SMTP error from remote mail server after %s%s: "
+    "%s", pl, smtp_command, s = string_printing(buffer));
   *pass_message = TRUE;
   *yield = buffer[0];
   return TRUE;
@@ -603,7 +590,8 @@ if (*errno_value == 0 || *errno_value == ECONNRESET)
   *message = US string_sprintf("Remote host closed connection "
     "in response to %s%s", pl, smtp_command);
   }
-else *message = US string_sprintf("%s [%s]", host->name, host->address);
+else
+  *message = US string_sprintf("%s [%s]", host->name, host->address);
 
 return FALSE;
 }
@@ -735,21 +723,11 @@ subsequent general error, it will get reset accordingly. If not, it will get
 converted to OK at the end.
 
 Arguments:
-  addrlist          the complete address list
-  include_affixes   TRUE if affixes include in RCPT
-  sync_addr         ptr to the ptr of the one to start scanning at (updated)
-  host              the host we are connected to
+  sx               smtp connection context
   count             the number of responses to read
-  address_retry_
-    include_sender  true if 4xx retry is to include the sender it its key
-  pending_MAIL      true if the first response is for MAIL
   pending_DATA      0 if last command sent was not DATA
                    +1 if previously had a good recipient
                    -1 if not previously had a good recipient
-  inblock           incoming SMTP block
-  timeout           timeout value
-  buffer            buffer for reading response
-  buffsize          size of buffer
 
 Returns:      3 if at least one address had 2xx and one had 5xx
               2 if at least one address had 5xx but none had 2xx
@@ -761,39 +739,38 @@ Returns:      3 if at least one address had 2xx and one had 5xx
 */
 
 static int
-sync_responses(address_item *addrlist, BOOL include_affixes,
-  address_item **sync_addr, host_item *host, int count,
-  BOOL address_retry_include_sender, BOOL pending_MAIL,
-  int pending_DATA, smtp_inblock *inblock, int timeout, uschar *buffer,
-  int buffsize)
+sync_responses(smtp_context * sx, int count, int pending_DATA)
 {
-address_item *addr = *sync_addr;
+address_item *addr = sx->sync_addr;
+smtp_transport_options_block *ob =
+  (smtp_transport_options_block *)sx->tblock->options_block;
 int yield = 0;
 
 /* Handle the response for a MAIL command. On error, reinstate the original
 command in big_buffer for error message use, and flush any further pending
 responses before returning, except after I/O errors and timeouts. */
 
-if (pending_MAIL)
+if (sx->pending_MAIL)
   {
   count--;
-  if (!smtp_read_response(inblock, buffer, buffsize, '2', timeout))
+  if (!smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer),
+                         '2', ob->command_timeout))
     {
     DEBUG(D_transport) debug_printf("bad response for MAIL\n");
     Ustrcpy(big_buffer, mail_command);  /* Fits, because it came from there! */
-    if (errno == 0 && buffer[0] != 0)
+    if (errno == 0 && sx->buffer[0] != 0)
       {
       uschar flushbuffer[4096];
       int save_errno = 0;
-      if (buffer[0] == '4')
+      if (sx->buffer[0] == '4')
         {
         save_errno = ERRNO_MAIL4XX;
-        addr->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
+        addr->more_errno |= ((sx->buffer[1] - '0')*10 + sx->buffer[2] - '0') << 8;
         }
       while (count-- > 0)
         {
-        if (!smtp_read_response(inblock, flushbuffer, sizeof(flushbuffer),
-                   '2', timeout)
+        if (!smtp_read_response(&sx->inblock, flushbuffer, sizeof(flushbuffer),
+                   '2', ob->command_timeout)
             && (errno != 0 || flushbuffer[0] == 0))
           break;
         }
@@ -804,7 +781,7 @@ if (pending_MAIL)
     while (count-- > 0)                /* Mark any pending addrs with the host used */
       {
       while (addr->transport_return != PENDING_DEFER) addr = addr->next;
-      addr->host_used = host;
+      addr->host_used = sx->host;
       addr = addr->next;
       }
     return -3;
@@ -819,12 +796,15 @@ with an address by scanning for the next address whose status is PENDING_DEFER.
 
 while (count-- > 0)
   {
-  while (addr->transport_return != PENDING_DEFER) addr = addr->next;
+  while (addr->transport_return != PENDING_DEFER)
+    if (!(addr = addr->next))
+      return -2;
 
   /* The address was accepted */
-  addr->host_used = host;
+  addr->host_used = sx->host;
 
-  if (smtp_read_response(inblock, buffer, buffsize, '2', timeout))
+  if (smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer),
+                         '2', ob->command_timeout))
     {
     yield |= 1;
     addr->transport_return = PENDING_OK;
@@ -847,8 +827,8 @@ while (count-- > 0)
   else if (errno == ETIMEDOUT)
     {
     uschar *message = string_sprintf("SMTP timeout after RCPT TO:<%s>",
-                         transport_rcpt_address(addr, include_affixes));
-    set_errno_nohost(addrlist, ETIMEDOUT, message, DEFER, FALSE);
+               transport_rcpt_address(addr, sx->tblock->rcpt_include_affixes));
+    set_errno_nohost(sx->first_addr, ETIMEDOUT, message, DEFER, FALSE);
     retry_add_item(addr, addr->address_retry_key, 0);
     update_waiting = FALSE;
     return -1;
@@ -859,10 +839,10 @@ while (count-- > 0)
   big_buffer for which we are checking the response, so the error message
   makes sense. */
 
-  else if (errno != 0 || buffer[0] == 0)
+  else if (errno != 0 || sx->buffer[0] == 0)
     {
     string_format(big_buffer, big_buffer_size, "RCPT TO:<%s>",
-      transport_rcpt_address(addr, include_affixes));
+      transport_rcpt_address(addr, sx->tblock->rcpt_include_affixes));
     return -2;
     }
 
@@ -872,14 +852,15 @@ while (count-- > 0)
     {
     addr->message =
       string_sprintf("SMTP error from remote mail server after RCPT TO:<%s>: "
-       "%s", transport_rcpt_address(addr, include_affixes),
-       string_printing(buffer));
+       "%s", transport_rcpt_address(addr, sx->tblock->rcpt_include_affixes),
+       string_printing(sx->buffer));
     setflag(addr, af_pass_message);
-    msglog_line(host, addr->message);
+    if (!sx->verify)
+      msglog_line(sx->host, addr->message);
 
     /* The response was 5xx */
 
-    if (buffer[0] == '5')
+    if (sx->buffer[0] == '5')
       {
       addr->transport_return = FAIL;
       yield |= 2;
@@ -891,40 +872,42 @@ while (count-- > 0)
       {
       addr->transport_return = DEFER;
       addr->basic_errno = ERRNO_RCPT4XX;
-      addr->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
+      addr->more_errno |= ((sx->buffer[1] - '0')*10 + sx->buffer[2] - '0') << 8;
 
+      if (!sx->verify)
+       {
 #ifndef DISABLE_EVENT
-      event_defer_errno = addr->more_errno;
-      msg_event_raise(US"msg:rcpt:host:defer", addr);
+       event_defer_errno = addr->more_errno;
+       msg_event_raise(US"msg:rcpt:host:defer", addr);
 #endif
 
-      /* Log temporary errors if there are more hosts to be tried.
-      If not, log this last one in the == line. */
+       /* Log temporary errors if there are more hosts to be tried.
+       If not, log this last one in the == line. */
 
-      if (host->next)
-       log_write(0, LOG_MAIN, "H=%s [%s]: %s", host->name, host->address, addr->message);
+       if (sx->host->next)
+         log_write(0, LOG_MAIN, "H=%s [%s]: %s",
+           sx->host->name, sx->host->address, addr->message);
 
 #ifndef DISABLE_EVENT
-      else
-       msg_event_raise(US"msg:rcpt:defer", addr);
+       else
+         msg_event_raise(US"msg:rcpt:defer", addr);
 #endif
 
-      /* Do not put this message on the list of those waiting for specific
-      hosts, as otherwise it is likely to be tried too often. */
+       /* Do not put this message on the list of those waiting for specific
+       hosts, as otherwise it is likely to be tried too often. */
 
-      update_waiting = FALSE;
+       update_waiting = FALSE;
 
-      /* Add a retry item for the address so that it doesn't get tried again
-      too soon. If address_retry_include_sender is true, add the sender address
-      to the retry key. */
+       /* Add a retry item for the address so that it doesn't get tried again
+       too soon. If address_retry_include_sender is true, add the sender address
+       to the retry key. */
 
-      if (address_retry_include_sender)
-        {
-        uschar *altkey = string_sprintf("%s:<%s>", addr->address_retry_key,
-          sender_address);
-        retry_add_item(addr, altkey, 0);
-        }
-      else retry_add_item(addr, addr->address_retry_key, 0);
+       retry_add_item(addr,
+         ob->address_retry_include_sender
+           ? string_sprintf("%s:<%s>", addr->address_retry_key, sender_address)
+           : addr->address_retry_key,
+         0);
+       }
       }
     }
   }       /* Loop for next RCPT response */
@@ -932,27 +915,28 @@ while (count-- > 0)
 /* Update where to start at for the next block of responses, unless we
 have already handled all the addresses. */
 
-if (addr != NULL) *sync_addr = addr->next;
+if (addr) sx->sync_addr = addr->next;
 
 /* Handle a response to DATA. If we have not had any good recipients, either
 previously or in this block, the response is ignored. */
 
 if (pending_DATA != 0 &&
-    !smtp_read_response(inblock, buffer, buffsize, '3', timeout))
+    !smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer),
+                       '3', ob->command_timeout))
   {
   int code;
   uschar *msg;
   BOOL pass_message;
   if (pending_DATA > 0 || (yield & 1) != 0)
     {
-    if (errno == 0 && buffer[0] == '4')
+    if (errno == 0 && sx->buffer[0] == '4')
       {
       errno = ERRNO_DATA4XX;
-      addrlist->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
+      sx->first_addr->more_errno |= ((sx->buffer[1] - '0')*10 + sx->buffer[2] - '0') << 8;
       }
     return -3;
     }
-  (void)check_response(host, &errno, 0, buffer, &code, &msg, &pass_message);
+  (void)check_response(sx->host, &errno, 0, sx->buffer, &code, &msg, &pass_message);
   DEBUG(D_transport) debug_printf("%s\nerror for DATA ignored: pipelining "
     "is in use and there were no good recipients\n", msg);
   }
@@ -1310,9 +1294,11 @@ return Ustrcmp(current_local_identity, message_local_identity) == 0;
 
 
 
-uschar
-ehlo_response(uschar * buf, size_t bsize, uschar checks)
+static uschar
+ehlo_response(uschar * buf, uschar checks)
 {
+size_t bsize = Ustrlen(buf);
+
 #ifdef SUPPORT_TLS
 if (  checks & PEER_OFFERED_TLS
    && pcre_exec(regex_STARTTLS, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
@@ -1363,28 +1349,34 @@ return checks;
 If given a nonzero size, first flush any buffered SMTP commands
 then emit the command.
 
-Reap previous SMTP command responses if requested.
-Reap one SMTP command response if requested.
+Reap previous SMTP command responses if requested, and always reap
+the response from a previous BDAT command.
+
+Args:
+ tctx          transport context
+ chunk_size    value for SMTP BDAT command
+ flags
+   tc_chunk_last       add LAST option to SMTP BDAT command
+   tc_reap_prev                reap response to previous SMTP commands
 
 Returns:       OK or ERROR
 */
 
 static int
-smtp_chunk_cmd_callback(int fd, transport_ctx * tctx,
-  unsigned chunk_size, unsigned flags)
+smtp_chunk_cmd_callback(transport_ctx * tctx, unsigned chunk_size,
+  unsigned flags)
 {
 smtp_transport_options_block * ob =
   (smtp_transport_options_block *)(tctx->tblock->options_block);
+smtp_context * sx = tctx->smtp_context;
 int cmd_count = 0;
 int prev_cmd_count;
-uschar * buffer = tctx->buffer;
-
 
 /* Write SMTP chunk header command */
 
 if (chunk_size > 0)
   {
-  if((cmd_count = smtp_write_command(tctx->outblock, FALSE, "BDAT %u%s\r\n",
+  if((cmd_count = smtp_write_command(&sx->outblock, FALSE, "BDAT %u%s\r\n",
                              chunk_size,
                              flags & tc_chunk_last ? " LAST" : "")
      ) < 0) return ERROR;
@@ -1392,13 +1384,13 @@ if (chunk_size > 0)
     data_command = string_copy(big_buffer);  /* Save for later error message */
   }
 
-prev_cmd_count = cmd_count += tctx->cmd_count;
+prev_cmd_count = cmd_count += sx->cmd_count;
 
 /* Reap responses for any previous, but not one we just emitted */
 
 if (chunk_size > 0)
   prev_cmd_count--;
-if (tctx->pending_BDAT)
+if (sx->pending_BDAT)
   prev_cmd_count--;
 
 if (flags & tc_reap_prev  &&  prev_cmd_count > 0)
@@ -1406,53 +1398,47 @@ if (flags & tc_reap_prev  &&  prev_cmd_count > 0)
   DEBUG(D_transport) debug_printf("look for %d responses"
     " for previous pipelined cmds\n", prev_cmd_count);
 
-  switch(sync_responses(tctx->first_addr, tctx->tblock->rcpt_include_affixes,
-         tctx->sync_addr, tctx->host, prev_cmd_count,
-         ob->address_retry_include_sender,
-         tctx->pending_MAIL, 0,
-         tctx->inblock,
-         ob->command_timeout,
-         buffer, DELIVER_BUFFER_SIZE))
+  switch(sync_responses(sx, prev_cmd_count, 0))
     {
     case 1:                            /* 2xx (only) => OK */
-    case 3: tctx->good_RCPT = TRUE;    /* 2xx & 5xx => OK & progress made */
-    case 2: *tctx->completed_address = TRUE; /* 5xx (only) => progress made */
+    case 3: sx->good_RCPT = TRUE;      /* 2xx & 5xx => OK & progress made */
+    case 2: sx->completed_addr = TRUE; /* 5xx (only) => progress made */
     case 0: break;                     /* No 2xx or 5xx, but no probs */
 
     case -1:                           /* Timeout on RCPT */
     default: return ERROR;             /* I/O error, or any MAIL/DATA error */
     }
   cmd_count = 1;
-  if (!tctx->pending_BDAT)
+  if (!sx->pending_BDAT)
     pipelining_active = FALSE;
   }
 
 /* Reap response for an outstanding BDAT */
 
-if (tctx->pending_BDAT)
+if (sx->pending_BDAT)
   {
   DEBUG(D_transport) debug_printf("look for one response for BDAT\n");
 
-  if (!smtp_read_response(tctx->inblock, buffer, DELIVER_BUFFER_SIZE, '2',
+  if (!smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer), '2',
        ob->command_timeout))
     {
-    if (errno == 0 && buffer[0] == '4')
+    if (errno == 0 && sx->buffer[0] == '4')
       {
       errno = ERRNO_DATA4XX;   /*XXX does this actually get used? */
-      tctx->first_addr->more_errno |=
-       ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
+      sx->addrlist->more_errno |=
+       ((sx->buffer[1] - '0')*10 + sx->buffer[2] - '0') << 8;
       }
     return ERROR;
     }
   cmd_count--;
-  tctx->pending_BDAT = FALSE;
+  sx->pending_BDAT = FALSE;
   pipelining_active = FALSE;
   }
 else if (chunk_size > 0)
-  tctx->pending_BDAT = TRUE;
+  sx->pending_BDAT = TRUE;
 
 
-tctx->cmd_count = cmd_count;
+sx->cmd_count = cmd_count;
 return OK;
 }
 
@@ -1467,8 +1453,6 @@ Arguments:
   ctx            connection context
   suppress_tls    if TRUE, don't attempt a TLS connection - this is set for
                     a second attempt after TLS initialization fails
-  verify         TRUE if connection is for a verify callout, FALSE for
-                 a delivery attempt
 
 Returns:          OK    - the connection was made and the delivery attempted;
                           fd is set in the conn context, tls_out set up.
@@ -1480,15 +1464,13 @@ Returns:          OK    - the connection was made and the delivery attempted;
                          to expand
 */
 int
-smtp_setup_conn(smtp_context * sx, BOOL suppress_tls, BOOL verify)
+smtp_setup_conn(smtp_context * sx, BOOL suppress_tls)
 {
 #if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_DANE)
 dns_answer tlsa_dnsa;
 #endif
 BOOL pass_message = FALSE;
-
 uschar * message = NULL;
-int save_errno;
 int yield = OK;
 int rc;
 
@@ -1572,7 +1554,7 @@ specially so they can be identified for retries. */
 
 if (continue_hostname == NULL)
   {
-  if (verify)
+  if (sx->verify)
     HDEBUG(D_verify) debug_printf("interface=%s port=%d\n", sx->interface, sx->port);
 
   /* This puts port into host->port */
@@ -1583,15 +1565,14 @@ if (continue_hostname == NULL)
   if (sx->inblock.sock < 0)
     {
     uschar * msg = NULL;
-    int save_errno = errno;
-    if (verify)
+    if (sx->verify)
       {
-      msg = strerror(errno);
+      msg = US strerror(errno);
       HDEBUG(D_verify) debug_printf("connect: %s\n", msg);
       }
     set_errno_nohost(sx->addrlist,
-      save_errno == ETIMEDOUT ? ERRNO_CONNECTTIMEOUT : save_errno,
-      verify ? string_sprintf("could not connect: %s", msg)
+      errno == ETIMEDOUT ? ERRNO_CONNECTTIMEOUT : errno,
+      sx->verify ? string_sprintf("could not connect: %s", msg)
             : NULL,
       DEFER, FALSE);
     sx->send_quit = FALSE;
@@ -1638,7 +1619,7 @@ if (continue_hostname == NULL)
 
   if (sx->helo_data)
     if (!(sx->helo_data = expand_string(sx->helo_data)))
-      if (verify)
+      if (sx->verify)
        log_write(0, LOG_MAIN|LOG_PANIC,
          "<%s>: failed to expand transport's helo_data value for callout: %s",
          sx->addrlist->address, expand_string_message);
@@ -1650,7 +1631,7 @@ if (continue_hostname == NULL)
     if ((sx->helo_data = string_domain_utf8_to_alabel(sx->helo_data,
                                              &expand_string_message)),
        expand_string_message)
-      if (verify)
+      if (sx->verify)
        log_write(0, LOG_MAIN|LOG_PANIC,
          "<%s>: failed to expand transport's helo_data value for callout: %s",
          sx->addrlist->address, expand_string_message);
@@ -1807,12 +1788,8 @@ goto SEND_QUIT;
 
       if (rsp != sx->buffer && rsp[0] == 0 && (errno == 0 || errno == ECONNRESET))
        {
-       sx->send_quit = FALSE;
-       save_errno = ERRNO_SMTPCLOSED;
-       message = string_sprintf("Remote host closed connection "
-             "in response to %s (EHLO response was: %s)",
-             smtp_command, sx->buffer);
-       goto FAILED;
+       errno = ERRNO_SMTPCLOSED;
+       goto EHLOHELO_FAILED;
        }
       Ustrncpy(sx->buffer, rsp, sizeof(sx->buffer)/2);
       goto RESPONSE_FAILED;
@@ -1823,7 +1800,7 @@ goto SEND_QUIT;
 
   if (sx->esmtp || sx->lmtp)
     {
-    sx->peer_offered = ehlo_response(sx->buffer, Ustrlen(sx->buffer),
+    sx->peer_offered = ehlo_response(sx->buffer,
       PEER_OFFERED_TLS /* others checked later */
       );
 
@@ -1835,9 +1812,11 @@ goto SEND_QUIT;
     }
   }
 
-/* For continuing deliveries down the same channel, the socket is the standard
-input, and 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
+/* For continuing deliveries down the same channel, having re-exec'd  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. */
@@ -1849,10 +1828,33 @@ separate - we could match up by host ip+port as a bodge. */
 
 else
   {
-  sx->inblock.sock = sx->outblock.sock = fileno(stdin);
+  if (cutthrough.fd >= 0 && cutthrough.callout_hold_only)
+    {
+    sx->inblock.sock = sx->outblock.sock = cutthrough.fd;
+    sx->host->port = sx->port = cutthrough.host.port;
+    }
+  else
+    {
+    sx->inblock.sock = sx->outblock.sock = 0;  /* stdin */
+    sx->host->port = sx->port;    /* Record the port that was used */
+    }
   smtp_command = big_buffer;
-  sx->host->port = sx->port;    /* Record the port that was used */
   sx->helo_data = NULL;                /* ensure we re-expand ob->helo_data */
+
+  /* For a continued connection with TLS being proxied for us, or a
+  held-open verify connection with TLS, nothing more to do. */
+
+  if (  continue_proxy_cipher
+     || (cutthrough.fd >= 0 && cutthrough.callout_hold_only && cutthrough.is_tls)
+     )
+    {
+    sx->peer_offered = smtp_peer_options;
+    pipelining_active = !!(smtp_peer_options & PEER_OFFERED_PIPE);
+    HDEBUG(D_transport) debug_printf("continued connection, %s TLS\n",
+      continue_proxy_cipher ? "proxied" : "verify conn with");
+    return OK;
+    }
+  HDEBUG(D_transport) debug_printf("continued connection, no TLS\n");
   }
 
 /* If TLS is available on this connection, whether continued or not, attempt to
@@ -1867,7 +1869,7 @@ for error analysis. */
 if (  smtp_peer_options & PEER_OFFERED_TLS
    && !suppress_tls
    && verify_check_given_host(&sx->ob->hosts_avoid_tls, sx->host) != OK
-   && (  !verify
+   && (  !sx->verify
       || verify_check_given_host(&sx->ob->hosts_verify_avoid_tls, sx->host) != OK
    )  )
   {
@@ -1891,6 +1893,7 @@ if (  smtp_peer_options & PEER_OFFERED_TLS
        )
       {
       Ustrncpy(sx->buffer, buffer2, sizeof(sx->buffer));
+      sx->buffer[sizeof(sx->buffer)-1] = '\0';
       goto RESPONSE_FAILED;
       }
     }
@@ -1901,11 +1904,12 @@ if (  smtp_peer_options & PEER_OFFERED_TLS
   TLS_NEGOTIATE:
     {
     address_item * addr;
-    int rc = tls_client_start(sx->inblock.sock, sx->host, sx->addrlist, sx->tblock
+    uschar * errstr;
+    int rc = tls_client_start(sx->inblock.sock, sx->host, sx->addrlist, sx->tblock,
 # ifdef EXPERIMENTAL_DANE
-                            , sx->dane ? &tlsa_dnsa : NULL
+                            sx->dane ? &tlsa_dnsa : NULL,
 # endif
-                            );
+                            &errstr);
 
     /* TLS negotiation failed; give an error. From outside, this function may
     be called again to try in clear on a new connection, if the options permit
@@ -1915,12 +1919,12 @@ if (  smtp_peer_options & PEER_OFFERED_TLS
       {
 # ifdef EXPERIMENTAL_DANE
       if (sx->dane) log_write(0, LOG_MAIN,
-         "DANE attempt failed; no TLS connection to %s [%s]",
-         sx->host->name, sx->host->address);
+         "DANE attempt failed; TLS connection to %s [%s]: %s",
+         sx->host->name, sx->host->address, errstr);
 # endif
 
-      save_errno = ERRNO_TLSFAILURE;
-      message = US"failure while setting up TLS session";
+      errno = ERRNO_TLSFAILURE;
+      message = string_sprintf("TLS session: %s", errstr);
       sx->send_quit = FALSE;
       goto TLS_FAILED;
       }
@@ -2006,7 +2010,7 @@ else if (  sx->smtps
        || verify_check_given_host(&sx->ob->hosts_require_tls, sx->host) == OK
        )
   {
-  save_errno = ERRNO_TLSREQUIRED;
+  errno = ERRNO_TLSREQUIRED;
   message = string_sprintf("a TLS session is required, but %s",
     smtp_peer_options & PEER_OFFERED_TLS
     ? "an attempt to start TLS failed" : "the server did not offer TLS support");
@@ -2027,7 +2031,7 @@ if (continue_hostname == NULL
   {
   if (sx->esmtp || sx->lmtp)
     {
-    sx->peer_offered = ehlo_response(sx->buffer, Ustrlen(sx->buffer),
+    sx->peer_offered = ehlo_response(sx->buffer,
        0 /* no TLS */
        | (sx->lmtp && sx->ob->lmtp_ignore_quota ? PEER_OFFERED_IGNQ : 0)
        | PEER_OFFERED_CHUNKING
@@ -2129,35 +2133,36 @@ return OK;
 
   {
   int code;
-  uschar * set_message;
 
   RESPONSE_FAILED:
-    {
-    save_errno = errno;
     message = NULL;
-    sx->send_quit = check_response(sx->host, &save_errno, sx->addrlist->more_errno,
+    sx->send_quit = check_response(sx->host, &errno, sx->addrlist->more_errno,
       sx->buffer, &code, &message, &pass_message);
     goto FAILED;
-    }
 
   SEND_FAILED:
-    {
-    save_errno = errno;
     code = '4';
     message = US string_sprintf("send() to %s [%s] failed: %s",
-      sx->host->name, sx->host->address, strerror(save_errno));
+      sx->host->name, sx->host->address, strerror(errno));
     sx->send_quit = FALSE;
     goto FAILED;
-    }
 
   /* This label is jumped to directly when a TLS negotiation has failed,
   or was not done for a host for which it is required. Values will be set
-  in message and save_errno, and setting_up will always be true. Treat as
+  in message and errno, and setting_up will always be true. Treat as
   a temporary error. */
 
+  EHLOHELO_FAILED:
+    code = '4';
+    message = string_sprintf("Remote host closed connection in response to %s"
+      " (EHLO response was: %s)", smtp_command, sx->buffer);
+    sx->send_quit = FALSE;
+    goto FAILED;
+
 #ifdef SUPPORT_TLS
   TLS_FAILED:
-  code = '4';
+    code = '4';
+    goto FAILED;
 #endif
 
   /* The failure happened while setting up the call; see if the failure was
@@ -2167,9 +2172,8 @@ return OK;
   whatever), defer all addresses, and yield DEFER, so that the host is not
   tried again for a while. */
 
-  FAILED:
+FAILED:
   sx->ok = FALSE;                /* For when reached by GOTO */
-  set_message = message;
 
   yield = code == '5'
 #ifdef SUPPORT_I18N
@@ -2177,7 +2181,7 @@ return OK;
 #endif
     ? FAIL : DEFER;
 
-  set_errno(sx->addrlist, save_errno, set_message, yield, pass_message, sx->host
+  set_errno(sx->addrlist, errno, message, yield, pass_message, sx->host
 #ifdef EXPERIMENTAL_DSN_INFO
            , sx->smtp_greeting, sx->helo_response
 #endif
@@ -2204,7 +2208,7 @@ writing RSET might have failed, or there may be other addresses whose hosts are
 specified in the transports, and therefore not visible at top level, in which
 case continue_more won't get set. */
 
-HDEBUG(D_transport|D_acl|D_v) debug_printf("  SMTP(close)>>\n");
+HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP(close)>>\n");
 if (sx->send_quit)
   {
   shutdown(sx->outblock.sock, SHUT_WR);
@@ -2363,6 +2367,258 @@ if (sx->peer_offered & PEER_OFFERED_DSN && !(addr->dsn_flags & rf_dsnlasthop))
 }
 
 
+
+/*
+Return:
+ 0     good, rcpt results in addr->transport_return (PENDING_OK, DEFER, FAIL)
+ -1    MAIL response error
+ -2    any non-MAIL read i/o error
+ -3    non-MAIL response timeout
+ -4    internal error; channel still usable
+ -5    transmit failed
+ */
+
+int
+smtp_write_mail_and_rcpt_cmds(smtp_context * sx, int * yield)
+{
+address_item * addr;
+int address_count;
+int rc;
+
+if (build_mailcmd_options(sx, sx->first_addr) != OK)
+  {
+  *yield = ERROR;
+  return -4;
+  }
+
+/* From here until we send the DATA command, we can make use of PIPELINING
+if the server host supports it. The code has to be able to check the responses
+at any point, for when the buffer fills up, so we write it totally generally.
+When PIPELINING is off, each command written reports that it has flushed the
+buffer. */
+
+sx->pending_MAIL = TRUE;     /* The block starts with MAIL */
+
+  {
+  uschar * s = sx->from_addr;
+#ifdef SUPPORT_I18N
+  uschar * errstr = NULL;
+
+  /* If we must downconvert, do the from-address here.  Remember we had to
+  for the to-addresses (done below), and also (ugly) for re-doing when building
+  the delivery log line. */
+
+  if (  sx->addrlist->prop.utf8_msg
+     && (sx->addrlist->prop.utf8_downcvt || !(sx->peer_offered & PEER_OFFERED_UTF8))
+     )
+    {
+    if (s = string_address_utf8_to_alabel(s, &errstr), errstr)
+      {
+      set_errno_nohost(sx->addrlist, ERRNO_EXPANDFAIL, errstr, DEFER, FALSE);
+      *yield = ERROR;
+      return -4;
+      }
+    setflag(sx->addrlist, af_utf8_downcvt);
+    }
+#endif
+
+  rc = smtp_write_command(&sx->outblock, pipelining_active,
+         "MAIL FROM:<%s>%s\r\n", s, sx->buffer);
+  }
+
+mail_command = string_copy(big_buffer);  /* Save for later error message */
+
+switch(rc)
+  {
+  case -1:                /* Transmission error */
+    return -5;
+
+  case +1:                /* Cmd was sent */
+    if (!smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer), '2',
+       sx->ob->command_timeout))
+      {
+      if (errno == 0 && sx->buffer[0] == '4')
+       {
+       errno = ERRNO_MAIL4XX;
+       sx->addrlist->more_errno |= ((sx->buffer[1] - '0')*10 + sx->buffer[2] - '0') << 8;
+       }
+      return -1;
+      }
+    sx->pending_MAIL = FALSE;
+    break;
+
+  /* otherwise zero: command queued for pipeline */
+  }
+
+/* Pass over all the relevant recipient addresses for this host, which are the
+ones that have status PENDING_DEFER. If we are using PIPELINING, we can send
+several before we have to read the responses for those seen so far. This
+checking is done by a subroutine because it also needs to be done at the end.
+Send only up to max_rcpt addresses at a time, leaving next_addr pointing to
+the next one if not all are sent.
+
+In the MUA wrapper situation, we want to flush the PIPELINING buffer for the
+last address because we want to abort if any recipients have any kind of
+problem, temporary or permanent. We know that all recipient addresses will have
+the PENDING_DEFER status, because only one attempt is ever made, and we know
+that max_rcpt will be large, so all addresses will be done at once.
+
+For verify we flush the pipeline after any (the only) rcpt address. */
+
+for (addr = sx->first_addr, address_count = 0;
+     addr  &&  address_count < sx->max_rcpt;
+     addr = addr->next) if (addr->transport_return == PENDING_DEFER)
+  {
+  int count;
+  BOOL no_flush;
+  uschar * rcpt_addr;
+
+  addr->dsn_aware = sx->peer_offered & PEER_OFFERED_DSN
+    ? dsn_support_yes : dsn_support_no;
+
+  address_count++;
+  no_flush = pipelining_active && !sx->verify
+         && (!mua_wrapper || addr->next && address_count < sx->max_rcpt);
+
+  build_rcptcmd_options(sx, addr);
+
+  /* Now send the RCPT command, and process outstanding responses when
+  necessary. After a timeout on RCPT, we just end the function, leaving the
+  yield as OK, because this error can often mean that there is a problem with
+  just one address, so we don't want to delay the host. */
+
+  rcpt_addr = transport_rcpt_address(addr, sx->tblock->rcpt_include_affixes);
+
+#ifdef SUPPORT_I18N
+  if (  testflag(sx->addrlist, af_utf8_downcvt)
+     && !(rcpt_addr = string_address_utf8_to_alabel(rcpt_addr, NULL))
+     )
+    {
+    /*XXX could we use a per-address errstr here? Not fail the whole send? */
+    errno = ERRNO_EXPANDFAIL;
+    return -5;         /*XXX too harsh? */
+    }
+#endif
+
+  count = smtp_write_command(&sx->outblock, no_flush, "RCPT TO:<%s>%s%s\r\n",
+    rcpt_addr, sx->igquotstr, sx->buffer);
+
+  if (count < 0) return -5;
+  if (count > 0)
+    {
+    switch(sync_responses(sx, count, 0))
+      {
+      case 3: sx->ok = TRUE;                   /* 2xx & 5xx => OK & progress made */
+      case 2: sx->completed_addr = TRUE;       /* 5xx (only) => progress made */
+             break;
+
+      case 1: sx->ok = TRUE;                   /* 2xx (only) => OK, but if LMTP, */
+             if (!sx->lmtp)                    /*  can't tell about progress yet */
+               sx->completed_addr = TRUE;
+      case 0:                                  /* No 2xx or 5xx, but no probs */
+             break;
+
+      case -1: return -3;                      /* Timeout on RCPT */
+      case -2: return -2;                      /* non-MAIL read i/o error */
+      default: return -1;                      /* any MAIL error */
+      }
+    sx->pending_MAIL = FALSE;            /* Dealt with MAIL */
+    }
+  }      /* Loop for next address */
+
+sx->next_addr = addr;
+return 0;
+}
+
+
+#ifdef SUPPORT_TLS
+/*****************************************************
+* Proxy TLS connection for another transport process *
+******************************************************/
+/*
+Use the given buffer as a staging area, and select on both the given fd
+and the TLS'd client-fd for data to read (per the coding in ip_recv() and
+fd_ready() this is legitimate).  Do blocking full-size writes, and reads
+under a timeout.
+
+Arguments:
+  buf          space to use for buffering
+  bufsiz       size of buffer
+  proxy_fd     comms to proxied process
+  timeout      per-read timeout, seconds
+*/
+
+void
+smtp_proxy_tls(uschar * buf, size_t bsize, int proxy_fd, int timeout)
+{
+fd_set fds;
+int max_fd = MAX(proxy_fd, tls_out.active) + 1;
+int rc, i, fd_bits, nbytes;
+
+set_process_info("proxying TLS connection for continued transport");
+FD_ZERO(&fds);
+FD_SET(tls_out.active, &fds);
+FD_SET(proxy_fd, &fds);
+
+for (fd_bits = 3; fd_bits; )
+  {
+  time_t time_left = timeout;
+  time_t time_start = time(NULL);
+
+  /* wait for data */
+  do
+    {
+    struct timeval tv = { time_left, 0 };
+
+    rc = select(max_fd, (SELECT_ARG2_TYPE *)&fds, NULL, NULL, &tv);
+
+    if (rc < 0 && errno == EINTR)
+      if ((time_left -= time(NULL) - time_start) > 0) continue;
+
+    if (rc <= 0)
+      {
+      DEBUG(D_transport) if (rc == 0) debug_printf("%s: timed out\n", __FUNCTION__);
+      return;
+      }
+    }
+  while (rc < 0 || !(FD_ISSET(tls_out.active, &fds) || FD_ISSET(proxy_fd, &fds)));
+
+  /* handle inbound data */
+  if (FD_ISSET(tls_out.active, &fds))
+    if ((rc = tls_read(FALSE, buf, bsize)) <= 0)
+      {
+      fd_bits &= ~1;
+      FD_CLR(tls_out.active, &fds);
+      shutdown(proxy_fd, SHUT_WR);
+      }
+    else
+      {
+      for (nbytes = 0; rc - nbytes > 0; nbytes += i)
+       if ((i = write(proxy_fd, buf + nbytes, rc - nbytes)) < 0) return;
+      }
+  else if (fd_bits & 1)
+    FD_SET(tls_out.active, &fds);
+
+  /* handle outbound data */
+  if (FD_ISSET(proxy_fd, &fds))
+    if ((rc = read(proxy_fd, buf, bsize)) <= 0)
+      {
+      fd_bits &= ~2;
+      FD_CLR(proxy_fd, &fds);
+      shutdown(tls_out.active, SHUT_WR);
+      }
+    else
+      {
+      for (nbytes = 0; rc - nbytes > 0; nbytes += i)
+       if ((i = tls_write(FALSE, buf + nbytes, rc - nbytes)) < 0) return;
+      }
+  else if (fd_bits & 2)
+    FD_SET(proxy_fd, &fds);
+  }
+}
+#endif
+
+
 /*************************************************
 *       Deliver address list to given host       *
 *************************************************/
@@ -2413,17 +2669,11 @@ smtp_deliver(address_item *addrlist, host_item *host, int host_af, int port,
   BOOL *message_defer, BOOL suppress_tls)
 {
 address_item *addr;
-address_item *sync_addr;
-address_item *first_addr = addrlist;
 int yield = OK;
-int address_count;
 int save_errno;
 int rc;
 time_t start_delivery_time = time(NULL);
 
-BOOL completed_address = FALSE;
-
-
 BOOL pass_message = FALSE;
 uschar *message = NULL;
 uschar new_message_id[MESSAGE_ID_LENGTH + 1];
@@ -2441,29 +2691,27 @@ sx.port = port;
 sx.interface = interface;
 sx.helo_data = NULL;
 sx.tblock = tblock;
+sx.verify = FALSE;
 
 /* Get the channel set up ready for a message (MAIL FROM being the next
 SMTP command to send */
 
-if ((rc = smtp_setup_conn(&sx, suppress_tls, FALSE)) != OK)
+if ((rc = smtp_setup_conn(&sx, suppress_tls)) != OK)
   return rc;
 
 /* If there is a filter command specified for this transport, we can now
 set it up. This cannot be done until the identify of the host is known. */
 
-if (tblock->filter_command != NULL)
+if (tblock->filter_command)
   {
-  BOOL rc;
-  uschar fbuf[64];
-  sprintf(CS fbuf, "%.50s transport", tblock->name);
-  rc = transport_set_up_command(&transport_filter_argv, tblock->filter_command,
-    TRUE, DEFER, addrlist, fbuf, NULL);
   transport_filter_timeout = tblock->filter_timeout;
 
   /* On failure, copy the error to all addresses, abandon the SMTP call, and
   yield ERROR. */
 
-  if (!rc)
+  if (!transport_set_up_command(&transport_filter_argv,
+       tblock->filter_command, TRUE, DEFER, addrlist,
+       string_sprintf("%.50s transport", tblock->name), NULL))
     {
     set_errno_nohost(addrlist->next, addrlist->basic_errno, addrlist->message, DEFER,
       FALSE);
@@ -2482,6 +2730,7 @@ if (tblock->filter_command != NULL)
     }
   }
 
+sx.first_addr = addrlist;
 
 /* For messages that have more than the maximum number of envelope recipients,
 we want to send several transactions down the same SMTP connection. (See
@@ -2492,170 +2741,62 @@ code was to use a goto to jump back to this point when there is another
 transaction to handle. */
 
 SEND_MESSAGE:
-sync_addr = first_addr;
+sx.from_addr = return_path;
+sx.sync_addr = sx.first_addr;
 sx.ok = FALSE;
 sx.send_rset = TRUE;
-completed_address = FALSE;
+sx.completed_addr = FALSE;
 
 
-/* Initiate a message transfer. */
+/* If we are a continued-connection-after-verify the MAIL and RCPT
+commands were already sent; do not re-send but do mark the addrs as
+having been accepted up to RCPT stage.  A traditional cont-conn
+always has a sequence number greater than one. */
 
-if (build_mailcmd_options(&sx, first_addr) != OK)
+if (continue_hostname && continue_sequence == 1)
   {
-  yield = ERROR;
-  goto SEND_QUIT;
-  }
+  address_item * addr;
 
-/* From here until we send the DATA command, we can make use of PIPELINING
-if the server host supports it. The code has to be able to check the responses
-at any point, for when the buffer fills up, so we write it totally generally.
-When PIPELINING is off, each command written reports that it has flushed the
-buffer. */
-
-sx.pending_MAIL = TRUE;     /* The block starts with MAIL */
-
-  {
-  uschar * s = return_path;
-#ifdef SUPPORT_I18N
-  uschar * errstr = NULL;
-
-  /* If we must downconvert, do the from-address here.  Remember we had to
-  for the to-addresses (done below), and also (ugly) for re-doing when building
-  the delivery log line. */
-
-  if (  addrlist->prop.utf8_msg
-     && (addrlist->prop.utf8_downcvt || !(sx.peer_offered & PEER_OFFERED_UTF8))
-     )
-    {
-    if (s = string_address_utf8_to_alabel(return_path, &errstr), errstr)
-      {
-      set_errno_nohost(addrlist, ERRNO_EXPANDFAIL, errstr, DEFER, FALSE);
-      yield = ERROR;
-      goto SEND_QUIT;
-      }
-    setflag(addrlist, af_utf8_downcvt);
-    }
-#endif
-
-  rc = smtp_write_command(&sx.outblock, pipelining_active,
-         "MAIL FROM:<%s>%s\r\n", s, sx.buffer);
-  }
-
-mail_command = string_copy(big_buffer);  /* Save for later error message */
-
-switch(rc)
-  {
-  case -1:                /* Transmission error */
-    goto SEND_FAILED;
+  sx.peer_offered = smtp_peer_options;
+  sx.pending_MAIL = FALSE;
+  sx.ok = TRUE;
+  sx.next_addr = NULL;
 
-  case +1:                /* Block was sent */
-    if (!smtp_read_response(&sx.inblock, sx.buffer, sizeof(sx.buffer), '2',
-       sx.ob->command_timeout))
-      {
-      if (errno == 0 && sx.buffer[0] == '4')
-       {
-       errno = ERRNO_MAIL4XX;
-       addrlist->more_errno |= ((sx.buffer[1] - '0')*10 + sx.buffer[2] - '0') << 8;
-       }
-      goto RESPONSE_FAILED;
-      }
-    sx.pending_MAIL = FALSE;
-    break;
+  for (addr = addrlist; addr; addr = addr->next)
+    addr->transport_return = PENDING_OK;
   }
-
-/* Pass over all the relevant recipient addresses for this host, which are the
-ones that have status PENDING_DEFER. If we are using PIPELINING, we can send
-several before we have to read the responses for those seen so far. This
-checking is done by a subroutine because it also needs to be done at the end.
-Send only up to max_rcpt addresses at a time, leaving first_addr pointing to
-the next one if not all are sent.
-
-In the MUA wrapper situation, we want to flush the PIPELINING buffer for the
-last address because we want to abort if any recipients have any kind of
-problem, temporary or permanent. We know that all recipient addresses will have
-the PENDING_DEFER status, because only one attempt is ever made, and we know
-that max_rcpt will be large, so all addresses will be done at once. */
-
-for (addr = first_addr, address_count = 0;
-     addr  &&  address_count < sx.max_rcpt;
-     addr = addr->next) if (addr->transport_return == PENDING_DEFER)
+else
   {
-  int count;
-  BOOL no_flush;
-  uschar * rcpt_addr;
-
-  addr->dsn_aware = sx.peer_offered & PEER_OFFERED_DSN
-    ? dsn_support_yes : dsn_support_no;
-
-  address_count++;
-  no_flush = pipelining_active && (!mua_wrapper || addr->next);
+  /* Initiate a message transfer. */
 
-  build_rcptcmd_options(&sx, addr);
-
-  /* Now send the RCPT command, and process outstanding responses when
-  necessary. After a timeout on RCPT, we just end the function, leaving the
-  yield as OK, because this error can often mean that there is a problem with
-  just one address, so we don't want to delay the host. */
-
-  rcpt_addr = transport_rcpt_address(addr, tblock->rcpt_include_affixes);
-
-#ifdef SUPPORT_I18N
-  if (  testflag(addrlist, af_utf8_downcvt)
-     && !(rcpt_addr = string_address_utf8_to_alabel(rcpt_addr, NULL))
-     )
+  switch(smtp_write_mail_and_rcpt_cmds(&sx, &yield))
     {
-    /*XXX could we use a per-address errstr here? Not fail the whole send? */
-    errno = ERRNO_EXPANDFAIL;
-    goto SEND_FAILED;
+    case 0:            break;
+    case -1: case -2:  goto RESPONSE_FAILED;
+    case -3:           goto END_OFF;
+    case -4:           goto SEND_QUIT;
+    default:           goto SEND_FAILED;
     }
-#endif
 
-  count = smtp_write_command(&sx.outblock, no_flush, "RCPT TO:<%s>%s%s\r\n",
-    rcpt_addr, sx.igquotstr, sx.buffer);
+  /* If we are an MUA wrapper, abort if any RCPTs were rejected, either
+  permanently or temporarily. We should have flushed and synced after the last
+  RCPT. */
 
-  if (count < 0) goto SEND_FAILED;
-  if (count > 0)
+  if (mua_wrapper)
     {
-    switch(sync_responses(first_addr, tblock->rcpt_include_affixes,
-             &sync_addr, host, count, sx.ob->address_retry_include_sender,
-             sx.pending_MAIL, 0, &sx.inblock, sx.ob->command_timeout, sx.buffer,
-             sizeof(sx.buffer)))
-      {
-      case 3: sx.ok = TRUE;            /* 2xx & 5xx => OK & progress made */
-      case 2: completed_address = TRUE;    /* 5xx (only) => progress made */
-      break;
-
-      case 1: sx.ok = TRUE;            /* 2xx (only) => OK, but if LMTP, */
-      if (!sx.lmtp) completed_address = TRUE; /* can't tell about progress yet */
-      case 0:                              /* No 2xx or 5xx, but no probs */
-      break;
+    address_item * a;
+    unsigned cnt;
 
-      case -1: goto END_OFF;               /* Timeout on RCPT */
-      default: goto RESPONSE_FAILED;       /* I/O error, or any MAIL error */
-      }
-    sx.pending_MAIL = FALSE;            /* Dealt with MAIL */
+    for (a = sx.first_addr, cnt = 0; a && cnt < sx.max_rcpt; a = a->next, cnt++)
+      if (a->transport_return != PENDING_OK)
+       {
+       /*XXX could we find a better errno than 0 here? */
+       set_errno_nohost(addrlist, 0, a->message, FAIL,
+         testflag(a, af_pass_message));
+       sx.ok = FALSE;
+       break;
+       }
     }
-  }      /* Loop for next address */
-
-/*XXX potential break point for verify-callouts here.  The MAIL and
-RCPT handling is relevant there */
-
-/* If we are an MUA wrapper, abort if any RCPTs were rejected, either
-permanently or temporarily. We should have flushed and synced after the last
-RCPT. */
-
-if (mua_wrapper)
-  {
-  address_item *badaddr;
-  for (badaddr = first_addr; badaddr; badaddr = badaddr->next)
-    if (badaddr->transport_return != PENDING_OK)
-      {
-      /*XXX could we find a better errno than 0 here? */
-      set_errno_nohost(addrlist, 0, badaddr->message, FAIL,
-       testflag(badaddr, af_pass_message));
-      sx.ok = FALSE;
-      break;
-      }
   }
 
 /* If ok is TRUE, we know we have got at least one good recipient, and must now
@@ -2672,16 +2813,14 @@ if (  !(sx.peer_offered & PEER_OFFERED_CHUNKING)
   int count = smtp_write_command(&sx.outblock, FALSE, "DATA\r\n");
 
   if (count < 0) goto SEND_FAILED;
-  switch(sync_responses(first_addr, tblock->rcpt_include_affixes, &sync_addr,
-           host, count, sx.ob->address_retry_include_sender, sx.pending_MAIL,
-           sx.ok ? +1 : -1, &sx.inblock, sx.ob->command_timeout, sx.buffer, sizeof(sx.buffer)))
+  switch(sync_responses(&sx, count, sx.ok ? +1 : -1))
     {
     case 3: sx.ok = TRUE;            /* 2xx & 5xx => OK & progress made */
-    case 2: completed_address = TRUE;    /* 5xx (only) => progress made */
+    case 2: sx.completed_addr = TRUE;    /* 5xx (only) => progress made */
     break;
 
     case 1: sx.ok = TRUE;            /* 2xx (only) => OK, but if LMTP, */
-    if (!sx.lmtp) completed_address = TRUE; /* can't tell about progress yet */
+    if (!sx.lmtp) sx.completed_addr = TRUE; /* can't tell about progress yet */
     case 0: break;                       /* No 2xx or 5xx, but no probs */
 
     case -1: goto END_OFF;               /* Timeout on RCPT */
@@ -2701,7 +2840,7 @@ well as body. Set the appropriate timeout value to be used for each chunk.
 if (!(sx.peer_offered & PEER_OFFERED_CHUNKING) && !sx.ok)
   {
   /* Save the first address of the next batch. */
-  first_addr = addr;
+  sx.first_addr = sx.next_addr;
 
   sx.ok = TRUE;
   }
@@ -2729,23 +2868,16 @@ else
     tctx.check_string = tctx.escape_string = NULL;
     tctx.options |= topt_use_bdat;
     tctx.chunk_cb = smtp_chunk_cmd_callback;
-    tctx.inblock = &sx.inblock;
-    tctx.outblock = &sx.outblock;
-    tctx.host = host;
-    tctx.first_addr = first_addr;
-    tctx.sync_addr = &sync_addr;
-    tctx.pending_MAIL = sx.pending_MAIL;
-    tctx.pending_BDAT = FALSE;
-    tctx.good_RCPT = sx.ok;
-    tctx.completed_address = &completed_address;
-    tctx.cmd_count = 0;
-    tctx.buffer = sx.buffer;
+    sx.pending_BDAT = FALSE;
+    sx.good_RCPT = sx.ok;
+    sx.cmd_count = 0;
+    tctx.smtp_context = &sx;
     }
   else
     tctx.options |= topt_end_dot;
 
   /* Save the first address of the next batch. */
-  first_addr = addr;
+  sx.first_addr = sx.next_addr;
 
   /* Responses from CHUNKING commands go in buffer.  Otherwise,
   there has not been a response. */
@@ -2763,7 +2895,8 @@ else
   transport_count = 0;
 
 #ifndef DISABLE_DKIM
-  sx.ok = dkim_transport_write_message(sx.inblock.sock, &tctx, &sx.ob->dkim);
+  sx.ok = dkim_transport_write_message(sx.inblock.sock, &tctx, &sx.ob->dkim,
+           CUSS &message);
 #else
   sx.ok = transport_write_message(sx.inblock.sock, &tctx, 0);
 #endif
@@ -2780,7 +2913,8 @@ else
   Or, when CHUNKING, it can be a protocol-detected failure. */
 
   if (!sx.ok)
-    goto RESPONSE_FAILED;
+    if (message) goto SEND_FAILED;
+    else         goto RESPONSE_FAILED;
 
   /* We used to send the terminating "." explicitly here, but because of
   buffering effects at both ends of TCP/IP connections, you don't gain
@@ -2790,20 +2924,17 @@ else
 
   smtp_command = US"end of data";
 
-  if (sx.peer_offered & PEER_OFFERED_CHUNKING && tctx.cmd_count > 1)
+  if (sx.peer_offered & PEER_OFFERED_CHUNKING && sx.cmd_count > 1)
     {
     /* Reap any outstanding MAIL & RCPT commands, but not a DATA-go-ahead */
-    switch(sync_responses(first_addr, tblock->rcpt_include_affixes, &sync_addr,
-            host, tctx.cmd_count-1, sx.ob->address_retry_include_sender,
-            sx.pending_MAIL, 0,
-            &sx.inblock, sx.ob->command_timeout, sx.buffer, sizeof(sx.buffer)))
+    switch(sync_responses(&sx, sx.cmd_count-1, 0))
       {
       case 3: sx.ok = TRUE;            /* 2xx & 5xx => OK & progress made */
-      case 2: completed_address = TRUE;    /* 5xx (only) => progress made */
+      case 2: sx.completed_addr = TRUE;    /* 5xx (only) => progress made */
       break;
 
       case 1: sx.ok = TRUE;            /* 2xx (only) => OK, but if LMTP, */
-      if (!sx.lmtp) completed_address = TRUE; /* can't tell about progress yet */
+      if (!sx.lmtp) sx.completed_addr = TRUE; /* can't tell about progress yet */
       case 0: break;                       /* No 2xx or 5xx, but no probs */
 
       case -1: goto END_OFF;               /* Timeout on RCPT */
@@ -2887,7 +3018,7 @@ else
     /* Process all transported addresses - for LMTP or PRDR, read a status for
     each one. */
 
-    for (addr = addrlist; addr != first_addr; addr = addr->next)
+    for (addr = addrlist; addr != sx.first_addr; addr = addr->next)
       {
       if (addr->transport_return != PENDING_OK) continue;
 
@@ -2928,7 +3059,7 @@ else
             }
           continue;
           }
-        completed_address = TRUE;   /* NOW we can set this flag */
+        sx.completed_addr = TRUE;   /* NOW we can set this flag */
         if (LOGGING(smtp_confirmation))
           {
           const uschar *s = string_printing(sx.buffer);
@@ -2965,7 +3096,7 @@ else
         else
           sprintf(CS sx.buffer, "%.500s\n", addr->unique);
 
-        DEBUG(D_deliver) debug_printf("journalling %s\n", sx.buffer);
+        DEBUG(D_deliver) debug_printf("S:journalling %s\n", sx.buffer);
         len = Ustrlen(CS sx.buffer);
         if (write(journal_fd, sx.buffer, len) != len)
           log_write(0, LOG_MAIN|LOG_PANIC, "failed to write journal for "
@@ -2987,14 +3118,14 @@ else
             errno = ERRNO_DATA4XX;
             addrlist->more_errno |= ((sx.buffer[1] - '0')*10 + sx.buffer[2] - '0') << 8;
             }
-         for (addr = addrlist; addr != first_addr; addr = addr->next)
+         for (addr = addrlist; addr != sx.first_addr; addr = addr->next)
             if (sx.buffer[0] == '5' || addr->transport_return == OK)
               addr->transport_return = PENDING_OK; /* allow set_errno action */
          goto RESPONSE_FAILED;
          }
 
        /* Update the journal, or setup retry. */
-        for (addr = addrlist; addr != first_addr; addr = addr->next)
+        for (addr = addrlist; addr != sx.first_addr; addr = addr->next)
          if (addr->transport_return == OK)
            {
            if (testflag(addr, af_homonym))
@@ -3048,8 +3179,8 @@ if (!sx.ok)
     {
     save_errno = errno;
     code = '4';
-    message = US string_sprintf("send() to %s [%s] failed: %s",
-      host->name, host->address, strerror(save_errno));
+    message = string_sprintf("send() to %s [%s] failed: %s",
+      host->name, host->address, message ? message : US strerror(save_errno));
     sx.send_quit = FALSE;
     goto FAILED;
     }
@@ -3178,9 +3309,9 @@ hosts_nopass_tls. */
 DEBUG(D_transport)
   debug_printf("ok=%d send_quit=%d send_rset=%d continue_more=%d "
     "yield=%d first_address is %sNULL\n", sx.ok, sx.send_quit,
-    sx.send_rset, continue_more, yield, first_addr ? "not " : "");
+    sx.send_rset, continue_more, yield, sx.first_addr ? "not " : "");
 
-if (completed_address && sx.ok && sx.send_quit)
+if (sx.completed_addr && sx.ok && sx.send_quit)
   {
   BOOL more;
   smtp_compare_t t_compare;
@@ -3188,12 +3319,15 @@ if (completed_address && sx.ok && sx.send_quit)
   t_compare.tblock = tblock;
   t_compare.current_sender_address = sender_address;
 
-  if (  first_addr != NULL
+  if (  sx.first_addr != NULL
      || continue_more
-     || (  (  tls_out.active < 0
+     || (
+#ifdef SUPPORT_TLS
+          (  tls_out.active < 0  &&  !continue_proxy_cipher
            || verify_check_given_host(&sx.ob->hosts_nopass_tls, host) != OK
           )
         &&
+#endif
            transport_check_waiting(tblock->name, host->name,
              tblock->connection_max_messages, new_message_id, &more,
             (oicf)smtp_are_same_identities, (void*)&t_compare)
@@ -3206,7 +3340,7 @@ if (completed_address && sx.ok && sx.send_quit)
       if (! (sx.ok = smtp_write_command(&sx.outblock, FALSE, "RSET\r\n") >= 0))
         {
         msg = US string_sprintf("send() to %s [%s] failed: %s", host->name,
-          host->address, strerror(save_errno));
+          host->address, strerror(errno));
         sx.send_quit = FALSE;
         }
       else if (! (sx.ok = smtp_read_response(&sx.inblock, sx.buffer,
@@ -3226,29 +3360,51 @@ if (completed_address && sx.ok && sx.send_quit)
 
     if (sx.ok)
       {
-      if (first_addr != NULL)            /* More addresses still to be sent */
+      int pfd[2];
+      int socket_fd = sx.inblock.sock;
+
+
+      if (sx.first_addr != NULL)         /* More addresses still to be sent */
         {                                /*   in this run of the transport */
         continue_sequence++;             /* Causes * in logging */
         goto SEND_MESSAGE;
         }
       if (continue_more) return yield;   /* More addresses for another run */
 
-      /* Pass the socket to a new Exim process. Before doing so, 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. */
-
+      /* Pass the connection on to a new Exim process. */
 #ifdef SUPPORT_TLS
       if (tls_out.active >= 0)
-        {
-        tls_close(FALSE, TRUE);
-       smtp_peer_options = smtp_peer_options_wrap;
-       sx.ok = !sx.smtps
-          && smtp_write_command(&sx.outblock, FALSE,
-                                   "EHLO %s\r\n", sx.helo_data) >= 0
-         && smtp_read_response(&sx.inblock, sx.buffer, sizeof(sx.buffer),
-                                   '2', sx.ob->command_timeout);
-        }
+       if (verify_check_given_host(&sx.ob->hosts_noproxy_tls, host) == OK)
+         {
+         /* Pass the socket, for direct use, to a new Exim process. Before
+         doing so, 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. */
+
+         tls_close(FALSE, TRUE);
+         smtp_peer_options = smtp_peer_options_wrap;
+         sx.ok = !sx.smtps
+           && smtp_write_command(&sx.outblock, FALSE,
+                                     "EHLO %s\r\n", sx.helo_data) >= 0
+           && smtp_read_response(&sx.inblock, sx.buffer, sizeof(sx.buffer),
+                                     '2', sx.ob->command_timeout);
+         }
+       else
+         {
+         /* Set up a pipe for proxying TLS for the new transport process */
+
+         smtp_peer_options |= PEER_OFFERED_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
+                   );
+         }
 #endif
 
       /* If the socket is successfully passed, we mustn't send QUIT (or
@@ -3258,13 +3414,42 @@ if (completed_address && sx.ok && sx.send_quit)
 propagate it from the initial
 */
       if (sx.ok && transport_pass_socket(tblock->name, host->name,
-           host->address, new_message_id, sx.inblock.sock))
+           host->address, new_message_id, socket_fd))
+       {
         sx.send_quit = FALSE;
+
+       /* 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. */
+#ifdef SUPPORT_TLS
+       if (tls_out.active >= 0)
+         {
+         int pid = fork();
+         if (pid > 0)          /* parent */
+           {
+           waitpid(pid, NULL, 0);
+           tls_close(FALSE, FALSE);
+           (void)close(sx.inblock.sock);
+           continue_transport = NULL;
+           continue_hostname = NULL;
+           return yield;
+           }
+         else if (pid == 0)    /* child; fork again to disconnect totally */
+           {
+           if ((pid = fork()))
+             _exit(pid ? EXIT_FAILURE : EXIT_SUCCESS);
+           smtp_proxy_tls(sx.buffer, sizeof(sx.buffer), pfd[0], sx.ob->command_timeout);
+           exim_exit(0);
+           }
+         }
+#endif
+       }
       }
 
     /* If RSET failed and there are addresses left, they get deferred. */
-
-    else set_errno(first_addr, errno, msg, DEFER, FALSE, host
+    else
+      set_errno(sx.first_addr, errno, msg, DEFER, FALSE, host
 #ifdef EXPERIMENTAL_DSN_INFO
                  , sx.smtp_greeting, sx.helo_response
 #endif
@@ -3309,7 +3494,7 @@ writing RSET might have failed, or there may be other addresses whose hosts are
 specified in the transports, and therefore not visible at top level, in which
 case continue_more won't get set. */
 
-HDEBUG(D_transport|D_acl|D_v) debug_printf("  SMTP(close)>>\n");
+HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP(close)>>\n");
 if (sx.send_quit)
   {
   shutdown(sx.outblock.sock, SHUT_WR);
@@ -3473,8 +3658,10 @@ DEBUG(D_transport)
     for (host = hostlist; host; host = host->next)
       debug_printf("  %s:%d\n", host->name, host->port);
     }
-  if (continue_hostname) debug_printf("already connected to %s [%s]\n",
-      continue_hostname, continue_host_address);
+  if (continue_hostname)
+    debug_printf("already connected to %s [%s] (on fd %d)\n",
+      continue_hostname, continue_host_address,
+      cutthrough.fd >= 0 ? cutthrough.fd : 0);
   }
 
 /* Set the flag requesting that these hosts be added to the waiting
@@ -3739,7 +3926,7 @@ for (cutoff_retry = 0;
       commonly points to a configuration error, but the best action is still
       to carry on for the next host. */
 
-      if (rc == HOST_FIND_AGAIN || rc == HOST_FIND_FAILED)
+      if (rc == HOST_FIND_AGAIN || rc == HOST_FIND_SECURITY || rc == HOST_FIND_FAILED)
         {
         retry_add_item(addrlist, string_sprintf("R:%s", host->name), 0);
         expired = FALSE;
@@ -3752,8 +3939,11 @@ for (cutoff_retry = 0;
           {
           if (addr->transport_return != DEFER) continue;
           addr->basic_errno = ERRNO_UNKNOWNHOST;
-          addr->message =
-            string_sprintf("failed to lookup IP address for %s", host->name);
+          addr->message = string_sprintf(
+           rc == HOST_FIND_SECURITY
+             ? "lookup of IP address for %s was insecure"
+             : "failed to lookup IP address for %s",
+           host->name);
           }
         continue;
         }
@@ -3871,7 +4061,7 @@ for (cutoff_retry = 0;
       host_is_expired = retry_check_address(addrlist->domain, host, pistring,
         incl_ip, &retry_host_key, &retry_message_key);
 
-      DEBUG(D_transport) debug_printf("%s [%s]%s status = %s\n", host->name,
+      DEBUG(D_transport) debug_printf("%s [%s]%s retry-status = %s\n", host->name,
         (host->address == NULL)? US"" : host->address, pistring,
         (host->status == hstatus_usable)? "usable" :
         (host->status == hstatus_unusable)? "unusable" :
@@ -3892,6 +4082,7 @@ for (cutoff_retry = 0;
            {
            case hwhy_retry: hosts_retry++; break;
            case hwhy_failed:  hosts_fail++; break;
+           case hwhy_insecure:
            case hwhy_deferred: hosts_defer++; break;
            }
 
@@ -4075,8 +4266,9 @@ for (cutoff_retry = 0;
         && verify_check_given_host(&ob->hosts_require_tls, host) != OK
         )
         {
-        log_write(0, LOG_MAIN, "TLS session failure: delivering unencrypted "
-          "to %s [%s] (not in hosts_require_tls)", host->name, host->address);
+        log_write(0, LOG_MAIN,
+         "%s: delivering unencrypted to H=%s [%s] (not in hosts_require_tls)",
+         first_addr->message, host->name, host->address);
         first_addr = prepare_addresses(addrlist, host);
         rc = smtp_deliver(addrlist, thost, host_af, port, interface, tblock,
           &message_defer, TRUE);