Fix continue_more on TLS connection. Bug 2104
[exim.git] / src / src / transports / smtp.c
index af463d66a4986958115fa670c87d330356594b8c..758f1143aeb28480eddeb09d4b4d4e7fb13dd5cb 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)
+switch(*errno_value)
   {
-  *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)
-  {
-  *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;
 }
@@ -754,6 +742,8 @@ static int
 sync_responses(smtp_context * sx, int count, int pending_DATA)
 {
 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
@@ -763,7 +753,8 @@ responses before returning, except after I/O errors and timeouts. */
 if (sx->pending_MAIL)
   {
   count--;
-  if (!smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer), '2', ((smtp_transport_options_block *)sx->tblock->options_block)->command_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! */
@@ -779,7 +770,7 @@ if (sx->pending_MAIL)
       while (count-- > 0)
         {
         if (!smtp_read_response(&sx->inblock, flushbuffer, sizeof(flushbuffer),
-                   '2', ((smtp_transport_options_block *)sx->tblock->options_block)->command_timeout)
+                   '2', ob->command_timeout)
             && (errno != 0 || flushbuffer[0] == 0))
           break;
         }
@@ -805,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 = sx->host;
 
-  if (smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer), '2', ((smtp_transport_options_block *)sx->tblock->options_block)->command_timeout))
+  if (smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer),
+                         '2', ob->command_timeout))
     {
     yield |= 1;
     addr->transport_return = PENDING_OK;
@@ -833,7 +827,7 @@ while (count-- > 0)
   else if (errno == ETIMEDOUT)
     {
     uschar *message = string_sprintf("SMTP timeout after RCPT TO:<%s>",
-                         transport_rcpt_address(addr, sx->tblock->rcpt_include_affixes));
+               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;
@@ -861,7 +855,8 @@ while (count-- > 0)
        "%s", transport_rcpt_address(addr, sx->tblock->rcpt_include_affixes),
        string_printing(sx->buffer));
     setflag(addr, af_pass_message);
-    msglog_line(sx->host, addr->message);
+    if (!sx->verify)
+      msglog_line(sx->host, addr->message);
 
     /* The response was 5xx */
 
@@ -879,38 +874,40 @@ while (count-- > 0)
       addr->basic_errno = ERRNO_RCPT4XX;
       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 (sx->host->next)
-       log_write(0, LOG_MAIN, "H=%s [%s]: %s", sx->host->name, sx->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 (((smtp_transport_options_block *)sx->tblock->options_block)->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 */
@@ -918,13 +915,14 @@ 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) sx->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(&sx->inblock, sx->buffer, sizeof(sx->buffer), '3', ((smtp_transport_options_block *)sx->tblock->options_block)->command_timeout))
+    !smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer),
+                       '3', ob->command_timeout))
   {
   int code;
   uschar *msg;
@@ -1296,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)
@@ -1349,99 +1349,97 @@ 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;
-smtp_context sx;
-
 
-/* Write SMTP chunk header command */
+/* Write SMTP chunk header command.  If not reaping responses, note that
+there may be more writes (like, the chunk data) done soon. */
 
 if (chunk_size > 0)
   {
-  if((cmd_count = smtp_write_command(tctx->outblock, FALSE, "BDAT %u%s\r\n",
-                             chunk_size,
-                             flags & tc_chunk_last ? " LAST" : "")
+  if((cmd_count = smtp_write_command(&sx->outblock,
+             flags & tc_reap_prev ? SCMD_FLUSH : SCMD_MORE,
+             "BDAT %u%s\r\n", chunk_size, flags & tc_chunk_last ? " LAST" : "")
      ) < 0) return ERROR;
   if (flags & tc_chunk_last)
     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)
   {
-  sx.first_addr = tctx->first_addr;
-  sx.tblock = tctx->tblock;
-  sx.sync_addr = *tctx->sync_addr;
-  sx.host = tctx->host;
-  sx.pending_MAIL = tctx->pending_MAIL;
-  sx.inblock = *tctx->inblock;
-
   DEBUG(D_transport) debug_printf("look for %d responses"
     " for previous pipelined cmds\n", prev_cmd_count);
 
-  switch(sync_responses(&sx, prev_cmd_count, 0))
+  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 */
     }
-  *tctx->sync_addr = sx.sync_addr;
   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, sx.buffer, sizeof(sx.buffer), '2',
+  if (!smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer), '2',
        ob->command_timeout))
     {
-    if (errno == 0 && sx.buffer[0] == '4')
+    if (errno == 0 && sx->buffer[0] == '4')
       {
       errno = ERRNO_DATA4XX;   /*XXX does this actually get used? */
-      tctx->first_addr->more_errno |=
-       ((sx.buffer[1] - '0')*10 + sx.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;
 }
 
@@ -1456,8 +1454,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.
@@ -1469,15 +1465,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;
 
@@ -1561,7 +1555,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 */
@@ -1572,15 +1566,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;
@@ -1627,7 +1620,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);
@@ -1639,7 +1632,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);
@@ -1749,7 +1742,7 @@ goto SEND_QUIT;
 
   if (sx->esmtp)
     {
-    if (smtp_write_command(&sx->outblock, FALSE, "%s %s\r\n",
+    if (smtp_write_command(&sx->outblock, SCMD_FLUSH, "%s %s\r\n",
          sx->lmtp ? "LHLO" : "EHLO", sx->helo_data) < 0)
       goto SEND_FAILED;
     sx->esmtp_sent = TRUE;
@@ -1782,7 +1775,7 @@ goto SEND_QUIT;
     if (sx->esmtp_sent && (n = Ustrlen(sx->buffer)) < sizeof(sx->buffer)/2)
       { rsp = sx->buffer + n + 1; n = sizeof(sx->buffer) - n; }
 
-    if (smtp_write_command(&sx->outblock, FALSE, "HELO %s\r\n", sx->helo_data) < 0)
+    if (smtp_write_command(&sx->outblock, SCMD_FLUSH, "HELO %s\r\n", sx->helo_data) < 0)
       goto SEND_FAILED;
     good_response = smtp_read_response(&sx->inblock, rsp, n,
       '2', sx->ob->command_timeout);
@@ -1796,12 +1789,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;
@@ -1812,7 +1801,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 */
       );
 
@@ -1824,9 +1813,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. */
@@ -1838,10 +1829,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
@@ -1856,12 +1870,12 @@ 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
    )  )
   {
   uschar buffer2[4096];
-  if (smtp_write_command(&sx->outblock, FALSE, "STARTTLS\r\n") < 0)
+  if (smtp_write_command(&sx->outblock, SCMD_FLUSH, "STARTTLS\r\n") < 0)
     goto SEND_FAILED;
 
   /* If there is an I/O error, transmission of this message is deferred. If
@@ -1880,6 +1894,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;
       }
     }
@@ -1890,11 +1905,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
@@ -1904,12 +1920,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;
       }
@@ -1973,7 +1989,7 @@ if (tls_out.active >= 0)
       debug_printf("not sending EHLO (host matches hosts_avoid_esmtp)\n");
     }
 
-  if (smtp_write_command(&sx->outblock, FALSE, "%s %s\r\n",
+  if (smtp_write_command(&sx->outblock, SCMD_FLUSH, "%s %s\r\n",
         sx->lmtp ? "LHLO" : greeting_cmd, sx->helo_data) < 0)
     goto SEND_FAILED;
   good_response = smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer),
@@ -1995,7 +2011,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");
@@ -2016,7 +2032,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
@@ -2118,35 +2134,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
@@ -2156,9 +2173,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
@@ -2166,7 +2182,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
@@ -2177,7 +2193,7 @@ return OK;
 SEND_QUIT:
 
 if (sx->send_quit)
-  (void)smtp_write_command(&sx->outblock, FALSE, "QUIT\r\n");
+  (void)smtp_write_command(&sx->outblock, SCMD_FLUSH, "QUIT\r\n");
 
 #ifdef SUPPORT_TLS
 tls_close(FALSE, TRUE);
@@ -2186,14 +2202,9 @@ tls_close(FALSE, TRUE);
 /* Close the socket, and return the appropriate value, first setting
 works because the NULL setting is passed back to the calling process, and
 remote_max_parallel is forced to 1 when delivering over an existing connection,
+*/
 
-If all went well and continue_more is set, we shouldn't actually get here if
-there are further addresses, as the return above will be taken. However,
-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);
@@ -2238,6 +2249,9 @@ included in the count.) */
 
 if (sx->peer_offered & PEER_OFFERED_SIZE)
   {
+/*XXX problem here under spool_files_wireformat?
+Or just forget about lines?  Or inflate by a fixed proportion? */
+
   sprintf(CS p, " SIZE=%d", message_size+message_linecount+sx->ob->size_addition);
   while (*p) p++;
   }
@@ -2355,11 +2369,12 @@ if (sx->peer_offered & PEER_OFFERED_DSN && !(addr->dsn_flags & rf_dsnlasthop))
 
 /*
 Return:
- 0     good
- -1    MAIL response error, any read i/o error
- -2    non-MAIL response timeout
- -3    internal error; channel still usable
- -4    transmit failed
+ 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
@@ -2372,7 +2387,7 @@ int rc;
 if (build_mailcmd_options(sx, sx->first_addr) != OK)
   {
   *yield = ERROR;
-  return -3;
+  return -4;
   }
 
 /* From here until we send the DATA command, we can make use of PIPELINING
@@ -2400,13 +2415,13 @@ sx->pending_MAIL = TRUE;     /* The block starts with MAIL */
       {
       set_errno_nohost(sx->addrlist, ERRNO_EXPANDFAIL, errstr, DEFER, FALSE);
       *yield = ERROR;
-      return -3;
+      return -4;
       }
     setflag(sx->addrlist, af_utf8_downcvt);
     }
 #endif
 
-  rc = smtp_write_command(&sx->outblock, pipelining_active,
+  rc = smtp_write_command(&sx->outblock, pipelining_active ? SCMD_BUFFER : SCMD_FLUSH,
          "MAIL FROM:<%s>%s\r\n", s, sx->buffer);
   }
 
@@ -2415,7 +2430,7 @@ mail_command = string_copy(big_buffer);  /* Save for later error message */
 switch(rc)
   {
   case -1:                /* Transmission error */
-    return -4;
+    return -5;
 
   case +1:                /* Cmd was sent */
     if (!smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer), '2',
@@ -2430,6 +2445,8 @@ switch(rc)
       }
     sx->pending_MAIL = FALSE;
     break;
+
+  /* otherwise zero: command queued for pipeline */
   }
 
 /* Pass over all the relevant recipient addresses for this host, which are the
@@ -2443,7 +2460,9 @@ 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. */
+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;
@@ -2457,7 +2476,8 @@ for (addr = sx->first_addr, address_count = 0;
     ? dsn_support_yes : dsn_support_no;
 
   address_count++;
-  no_flush = pipelining_active && (!mua_wrapper || addr->next);
+  no_flush = pipelining_active && !sx->verify
+         && (!mua_wrapper || addr->next && address_count < sx->max_rcpt);
 
   build_rcptcmd_options(sx, addr);
 
@@ -2475,21 +2495,21 @@ for (addr = sx->first_addr, address_count = 0;
     {
     /*XXX could we use a per-address errstr here? Not fail the whole send? */
     errno = ERRNO_EXPANDFAIL;
-    return -4;         /*XXX too harsh? */
+    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);
+  count = smtp_write_command(&sx->outblock, no_flush ? SCMD_BUFFER : SCMD_FLUSH,
+    "RCPT TO:<%s>%s%s\r\n", rcpt_addr, sx->igquotstr, sx->buffer);
 
-  if (count < 0) return -4;
+  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;
+             break;
 
       case 1: sx->ok = TRUE;                   /* 2xx (only) => OK, but if LMTP, */
              if (!sx->lmtp)                    /*  can't tell about progress yet */
@@ -2497,8 +2517,9 @@ for (addr = sx->first_addr, address_count = 0;
       case 0:                                  /* No 2xx or 5xx, but no probs */
              break;
 
-      case -1: return -2;                      /* Timeout on RCPT */
-      default: return -1;                      /* I/O error, or any MAIL error */
+      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 */
     }
@@ -2509,6 +2530,94 @@ 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       *
 *************************************************/
@@ -2581,29 +2690,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);
@@ -2622,6 +2729,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
@@ -2633,39 +2741,61 @@ transaction to handle. */
 
 SEND_MESSAGE:
 sx.from_addr = return_path;
-sx.first_addr = sx.sync_addr = addrlist;
+sx.sync_addr = sx.first_addr;
 sx.ok = FALSE;
 sx.send_rset = TRUE;
 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. */
 
-switch(smtp_write_mail_and_rcpt_cmds(&sx, &yield))
+if (continue_hostname && continue_sequence == 1)
   {
-  case 0:      break;
-  case -1:     goto RESPONSE_FAILED;
-  case -2:     goto END_OFF;
-  case -3:     goto SEND_QUIT;
-  default:     goto SEND_FAILED;
-  }
+  address_item * addr;
 
-/* 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. */
+  sx.peer_offered = smtp_peer_options;
+  sx.pending_MAIL = FALSE;
+  sx.ok = TRUE;
+  sx.next_addr = NULL;
 
-if (mua_wrapper)
+  for (addr = addrlist; addr; addr = addr->next)
+    addr->transport_return = PENDING_OK;
+  }
+else
   {
-  address_item *badaddr;
-  for (badaddr = sx.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;
-      }
+  /* Initiate a message transfer. */
+
+  switch(smtp_write_mail_and_rcpt_cmds(&sx, &yield))
+    {
+    case 0:            break;
+    case -1: case -2:  goto RESPONSE_FAILED;
+    case -3:           goto END_OFF;
+    case -4:           goto SEND_QUIT;
+    default:           goto SEND_FAILED;
+    }
+
+  /* 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 * a;
+    unsigned cnt;
+
+    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;
+       }
+    }
   }
 
 /* If ok is TRUE, we know we have got at least one good recipient, and must now
@@ -2679,7 +2809,7 @@ to send is. */
 if (  !(sx.peer_offered & PEER_OFFERED_CHUNKING)
    && (sx.ok || (pipelining_active && !mua_wrapper)))
   {
-  int count = smtp_write_command(&sx.outblock, FALSE, "DATA\r\n");
+  int count = smtp_write_command(&sx.outblock, SCMD_FLUSH, "DATA\r\n");
 
   if (count < 0) goto SEND_FAILED;
   switch(sync_responses(&sx, count, sx.ok ? +1 : -1))
@@ -2716,6 +2846,7 @@ if (!(sx.peer_offered & PEER_OFFERED_CHUNKING) && !sx.ok)
 else
   {
   transport_ctx tctx = {
+    sx.inblock.sock,
     tblock,
     addrlist,
     US".", US"..",    /* Escaping strings */
@@ -2737,17 +2868,10 @@ 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 = sx.first_addr;
-    tctx.sync_addr = &sx.sync_addr;
-    tctx.pending_MAIL = sx.pending_MAIL;
-    tctx.pending_BDAT = FALSE;
-    tctx.good_RCPT = sx.ok;
-    tctx.completed_address = &sx.completed_addr;
-    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;
@@ -2771,9 +2895,9 @@ 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(&tctx, &sx.ob->dkim, CUSS &message);
 #else
-  sx.ok = transport_write_message(sx.inblock.sock, &tctx, 0);
+  sx.ok = transport_write_message(&tctx, 0);
 #endif
 
   /* transport_write_message() uses write() because it is called from other
@@ -2788,7 +2912,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
@@ -2798,10 +2923,10 @@ 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(&sx, tctx.cmd_count-1, 0))
+    switch(sync_responses(&sx, sx.cmd_count-1, 0))
       {
       case 3: sx.ok = TRUE;            /* 2xx & 5xx => OK & progress made */
       case 2: sx.completed_addr = TRUE;    /* 5xx (only) => progress made */
@@ -2970,7 +3095,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 "
@@ -3053,8 +3178,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;
     }
@@ -3195,10 +3320,13 @@ if (sx.completed_addr && sx.ok && sx.send_quit)
 
   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)
@@ -3208,10 +3336,10 @@ if (sx.completed_addr && sx.ok && sx.send_quit)
     BOOL pass_message;
 
     if (sx.send_rset)
-      if (! (sx.ok = smtp_write_command(&sx.outblock, FALSE, "RSET\r\n") >= 0))
+      if (! (sx.ok = smtp_write_command(&sx.outblock, SCMD_FLUSH, "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,
@@ -3231,30 +3359,62 @@ if (sx.completed_addr && sx.ok && sx.send_quit)
 
     if (sx.ok)
       {
-      if (sx.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. */
+      /* Unless caller said it already has more messages listed for this host,
+      pass the connection on to a new Exim process (below, the call to
+      transport_pass_socket).  If the caller has more ready, just return with
+      the connection still open. */
 
 #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 (  continue_more
+          || verify_check_given_host(&sx.ob->hosts_noproxy_tls, host) == OK)
+         {
+         /* Before passing the socket on, or returning to caller with it still
+         open, we must shut down TLS.  Not all MTAs allow for the continuation
+         of the SMTP session when TLS is shut down. We test for this by sending
+         a new EHLO. If we don't get a good response, we don't attempt to pass
+         the socket on. */
+
+         tls_close(FALSE, TRUE);
+         smtp_peer_options = smtp_peer_options_wrap;
+         sx.ok = !sx.smtps
+           && smtp_write_command(&sx.outblock, SCMD_FLUSH,
+                                     "EHLO %s\r\n", sx.helo_data) >= 0
+           && smtp_read_response(&sx.inblock, sx.buffer, sizeof(sx.buffer),
+                                     '2', sx.ob->command_timeout);
+
+         if (sx.ok && continue_more)
+           return yield;               /* More addresses for another run */
+         }
+       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
+                   );
+         }
+      else
 #endif
+       if (continue_more)
+         return yield;                 /* More addresses for another run */
 
       /* If the socket is successfully passed, we mustn't send QUIT (or
       indeed anything!) from here. */
@@ -3263,13 +3423,46 @@ if (sx.completed_addr && 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 */
+           {
+           DEBUG(D_transport) debug_printf("proxy-proc inter-pid %d\n", pid);
+           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()))
+             {
+             DEBUG(D_transport) debug_printf("proxy-prox final-pid %d\n", pid);
+             _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(sx.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
@@ -3296,7 +3489,7 @@ This change is being made on 31-Jul-98. After over a year of trouble-free
 operation, the old commented-out code was removed on 17-Sep-99. */
 
 SEND_QUIT:
-if (sx.send_quit) (void)smtp_write_command(&sx.outblock, FALSE, "QUIT\r\n");
+if (sx.send_quit) (void)smtp_write_command(&sx.outblock, SCMD_FLUSH, "QUIT\r\n");
 
 END_OFF:
 
@@ -3314,7 +3507,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);
@@ -3377,7 +3570,7 @@ outblock.ptr = outbuffer;
 outblock.cmd_count = 0;
 outblock.authenticating = FALSE;
 
-(void)smtp_write_command(&outblock, FALSE, "QUIT\r\n");
+(void)smtp_write_command(&outblock, SCMD_FLUSH, "QUIT\r\n");
 (void)smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
   ob->command_timeout);
 (void)close(inblock.sock);
@@ -3478,8 +3671,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
@@ -3744,7 +3939,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;
@@ -3757,8 +3952,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;
         }
@@ -3876,7 +4074,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" :
@@ -3897,6 +4095,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;
            }
 
@@ -4080,8 +4279,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);