Prebuild the data structure for builtin macros
[exim.git] / src / src / transports / smtp.c
index 02d1e671edfa2a376ca242498a189892e04c8aae..477cdac4da9b71c4fd1f5b6d3ecedc96c7161246 100644 (file)
@@ -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) },
@@ -184,6 +186,18 @@ address can appear in the tables drtables.c. */
 int smtp_transport_options_count =
   sizeof(smtp_transport_options)/sizeof(optionlist);
 
+
+#ifdef MACRO_PREDEF
+
+/* Dummy values */
+smtp_transport_options_block smtp_transport_option_defaults = {0};
+void smtp_transport_init(transport_instance *tblock) {}
+BOOL smtp_transport_entry(transport_instance *tblock, address_item *addr) {return FALSE;}
+void smtp_transport_closedown(transport_instance *tblock) {}
+
+#else   /*!MACRO_PREDEF*/
+
+
 /* Default private options block for the smtp transport. */
 
 smtp_transport_options_block smtp_transport_option_defaults = {
@@ -218,7 +232,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 +300,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 */
 
 
@@ -789,7 +808,9 @@ 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;
@@ -1177,20 +1198,24 @@ tlsa_lookup(const host_item * host, dns_answer * dnsa, BOOL dane_required)
 /* move this out to host.c given the similarity to dns_lookup() ? */
 uschar buffer[300];
 const uschar * fullname = buffer;
+int rc;
+BOOL sec;
 
 /* TLSA lookup string */
 (void)sprintf(CS buffer, "_%d._tcp.%.256s", host->port, host->name);
 
-switch (dns_lookup(dnsa, buffer, T_TLSA, &fullname))
+rc = dns_lookup(dnsa, buffer, T_TLSA, &fullname);
+sec = dns_is_secure(dnsa);
+DEBUG(D_transport)
+  debug_printf("TLSA lookup ret %d %sDNSSEC\n", rc, sec ? "" : "not ");
+
+switch (rc)
   {
   case DNS_SUCCEED:
-    if (!dns_is_secure(dnsa))
-      {
-      log_write(0, LOG_MAIN, "DANE error: TLSA lookup not DNSSEC");
-      return DEFER;
-      }
-    return OK;
+    if (sec) return OK;
 
+    log_write(0, LOG_MAIN, "DANE error: TLSA lookup not DNSSEC");
+    /*FALLTHROUGH*/
   case DNS_AGAIN:
     return DEFER; /* just defer this TLS'd conn */
 
@@ -1340,15 +1365,22 @@ 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);
@@ -1356,13 +1388,14 @@ smtp_context * sx = tctx->smtp_context;
 int cmd_count = 0;
 int prev_cmd_count;
 
-/* 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(&sx->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 */
@@ -1536,34 +1569,17 @@ if (sx->smtps)
 the initial interaction and HELO/EHLO/LHLO. Connect timeout errors are handled
 specially so they can be identified for retries. */
 
-if (continue_hostname == NULL)
+if (!continue_hostname)
   {
   if (sx->verify)
     HDEBUG(D_verify) debug_printf("interface=%s port=%d\n", sx->interface, sx->port);
 
-  /* This puts port into host->port */
-  sx->inblock.sock = sx->outblock.sock =
-    smtp_connect(sx->host, sx->host_af, sx->port, sx->interface,
-                 sx->ob->connect_timeout, sx->tblock);
+  /* Get the actual port the connection will use, into sx->host */
 
-  if (sx->inblock.sock < 0)
-    {
-    uschar * msg = NULL;
-    if (sx->verify)
-      {
-      msg = US strerror(errno);
-      HDEBUG(D_verify) debug_printf("connect: %s\n", msg);
-      }
-    set_errno_nohost(sx->addrlist,
-      errno == ETIMEDOUT ? ERRNO_CONNECTTIMEOUT : errno,
-      sx->verify ? string_sprintf("could not connect: %s", msg)
-            : NULL,
-      DEFER, FALSE);
-    sx->send_quit = FALSE;
-    return DEFER;
-    }
+  smtp_port_for_connect(sx->host, sx->port);
 
 #if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_DANE)
+    /* Do TLSA lookup for DANE */
     {
     tls_out.dane_verified = FALSE;
     tls_out.tlsa_usage = 0;
@@ -1575,7 +1591,9 @@ if (continue_hostname == NULL)
        )
        switch (rc = tlsa_lookup(sx->host, &tlsa_dnsa, sx->dane_required))
          {
-         case OK:              sx->dane = TRUE; break;
+         case OK:              sx->dane = TRUE;
+                               sx->ob->tls_tempfail_tryclear = FALSE;
+                               break;
          case FAIL_FORCED:     break;
          default:              set_errno_nohost(sx->addrlist, ERRNO_DNSDEFER,
                                  string_sprintf("DANE error: tlsa lookup %s",
@@ -1591,12 +1609,32 @@ if (continue_hostname == NULL)
        FAIL, FALSE);
       return FAIL;
       }
-
-    if (sx->dane)
-      sx->ob->tls_tempfail_tryclear = FALSE;
     }
 #endif /*DANE*/
 
+  /* Make the TCP connection */
+
+  sx->inblock.sock = sx->outblock.sock =
+    smtp_connect(sx->host, sx->host_af, sx->interface,
+                 sx->ob->connect_timeout, sx->tblock);
+
+  if (sx->inblock.sock < 0)
+    {
+    uschar * msg = NULL;
+    if (sx->verify)
+      {
+      msg = US strerror(errno);
+      HDEBUG(D_verify) debug_printf("connect: %s\n", msg);
+      }
+    set_errno_nohost(sx->addrlist,
+      errno == ETIMEDOUT ? ERRNO_CONNECTTIMEOUT : errno,
+      sx->verify ? string_sprintf("could not connect: %s", msg)
+            : NULL,
+      DEFER, FALSE);
+    sx->send_quit = FALSE;
+    return DEFER;
+    }
+
   /* Expand the greeting message while waiting for the initial response. (Makes
   sense if helo_data contains ${lookup dnsdb ...} stuff). The expansion is
   delayed till here so that $sending_interface and $sending_port are set. */
@@ -1725,7 +1763,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;
@@ -1758,7 +1796,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,9 +1834,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. */
@@ -1810,10 +1850,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 */
+    smtp_port_for_connect(sx->host, 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
@@ -1833,7 +1896,7 @@ if (  smtp_peer_options & PEER_OFFERED_TLS
    )  )
   {
   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
@@ -1947,7 +2010,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),
@@ -2126,32 +2189,41 @@ return OK;
 
   /* The failure happened while setting up the call; see if the failure was
   a 5xx response (this will either be on connection, or following HELO - a 5xx
-  after EHLO causes it to try HELO). If so, fail all addresses, as this host is
-  never going to accept them. For other errors during setting up (timeouts or
-  whatever), defer all addresses, and yield DEFER, so that the host is not
-  tried again for a while. */
+  after EHLO causes it to try HELO). If so, and there are no more hosts to try,
+  fail all addresses, as this host is never going to accept them. For other
+  errors during setting up (timeouts or whatever), defer all addresses, and
+  yield DEFER, so that the host is not tried again for a while.
+
+  XXX This peeking for another host feels like a layering violation. We want
+  to note the host as unusable, but down here we shouldn't know if this was
+  the last host to try for the addr(list).  Perhaps the upper layer should be
+  the one to do set_errno() ?  The problem is that currently the addr is where
+  errno etc. are stashed, but until we run out of hosts to try the errors are
+  host-specific.  Maybe we should enhance the host_item definition? */
 
 FAILED:
   sx->ok = FALSE;                /* For when reached by GOTO */
-
-  yield = code == '5'
+  set_errno(sx->addrlist, errno, message,
+           sx->host->next
+           ? DEFER
+           : code == '5'
 #ifdef SUPPORT_I18N
-         || errno == ERRNO_UTF8_FWD
+                       || errno == ERRNO_UTF8_FWD
 #endif
-    ? FAIL : DEFER;
-
-  set_errno(sx->addrlist, errno, message, yield, pass_message, sx->host
+           ? FAIL : DEFER,
+           pass_message, sx->host
 #ifdef EXPERIMENTAL_DSN_INFO
            , sx->smtp_greeting, sx->helo_response
 #endif
            );
+  yield = DEFER;
   }
 
 
 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);
@@ -2160,12 +2232,7 @@ 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_indent("  SMTP(close)>>\n");
 if (sx->send_quit)
@@ -2212,6 +2279,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++;
   }
@@ -2381,7 +2451,7 @@ sx->pending_MAIL = TRUE;     /* The block starts with MAIL */
     }
 #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);
   }
 
@@ -2436,7 +2506,8 @@ for (addr = sx->first_addr, address_count = 0;
     ? dsn_support_yes : dsn_support_no;
 
   address_count++;
-  no_flush = pipelining_active && !sx->verify && (!mua_wrapper || addr->next);
+  no_flush = pipelining_active && !sx->verify
+         && (!mua_wrapper || addr->next && address_count < sx->max_rcpt);
 
   build_rcptcmd_options(sx, addr);
 
@@ -2458,8 +2529,8 @@ for (addr = sx->first_addr, address_count = 0;
     }
 #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 -5;
   if (count > 0)
@@ -2489,6 +2560,104 @@ 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 rfds, efds;
+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(&rfds);
+FD_SET(tls_out.active, &rfds);
+FD_SET(proxy_fd, &rfds);
+
+for (fd_bits = 3; fd_bits; )
+  {
+  time_t time_left = timeout;
+  time_t time_start = time(NULL);
+
+  /* wait for data */
+  efds = rfds;
+  do
+    {
+    struct timeval tv = { time_left, 0 };
+
+    rc = select(max_fd,
+      (SELECT_ARG2_TYPE *)&rfds, NULL, (SELECT_ARG2_TYPE *)&efds, &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;
+      }
+
+    if (FD_ISSET(tls_out.active, &efds) || FD_ISSET(proxy_fd, &efds))
+      {
+      DEBUG(D_transport) debug_printf("select: exceptional cond on %s fd\n",
+       FD_ISSET(proxy_fd, &efds) ? "proxy" : "tls");
+      return;
+      }
+    }
+  while (rc < 0 || !(FD_ISSET(tls_out.active, &rfds) || FD_ISSET(proxy_fd, &rfds)));
+
+  /* handle inbound data */
+  if (FD_ISSET(tls_out.active, &rfds))
+    if ((rc = tls_read(FALSE, buf, bsize)) <= 0)
+      {
+      fd_bits &= ~1;
+      FD_CLR(tls_out.active, &rfds);
+      shutdown(proxy_fd, SHUT_WR);
+      timeout = 5;
+      }
+    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, &rfds);
+
+  /* handle outbound data */
+  if (FD_ISSET(proxy_fd, &rfds))
+    if ((rc = read(proxy_fd, buf, bsize)) <= 0)
+      {
+      fd_bits = 0;
+      tls_close(FALSE, TRUE);
+      }
+    else
+      {
+      for (nbytes = 0; rc - nbytes > 0; nbytes += i)
+       if ((i = tls_write(FALSE, buf + nbytes, rc - nbytes, FALSE)) < 0)
+         return;
+      }
+  else if (fd_bits & 2)
+    FD_SET(proxy_fd, &rfds);
+  }
+}
+#endif
+
+
 /*************************************************
 *       Deliver address list to given host       *
 *************************************************/
@@ -2512,7 +2681,8 @@ Arguments:
                   failed by one of them.
   host            host to deliver to
   host_af         AF_INET or AF_INET6
-  port            default TCP/IP port to use, in host byte order
+  defport         default TCP/IP port to use if host does not specify, in host
+                 byte order
   interface       interface to bind to, or NULL
   tblock          transport instance block
   message_defer   set TRUE if yield is OK, but all addresses were deferred
@@ -2534,7 +2704,7 @@ Returns:          OK    - the connection was made and the delivery attempted;
 */
 
 static int
-smtp_deliver(address_item *addrlist, host_item *host, int host_af, int port,
+smtp_deliver(address_item *addrlist, host_item *host, int host_af, int defport,
   uschar *interface, transport_instance *tblock,
   BOOL *message_defer, BOOL suppress_tls)
 {
@@ -2557,7 +2727,7 @@ suppress_tls = suppress_tls;  /* stop compiler warning when no TLS support */
 sx.addrlist = addrlist;
 sx.host = host;
 sx.host_af = host_af,
-sx.port = port;
+sx.port = defport;
 sx.interface = interface;
 sx.helo_data = NULL;
 sx.tblock = tblock;
@@ -2574,17 +2744,14 @@ set it up. This cannot be done until the identify of the host is known. */
 
 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);
@@ -2603,6 +2770,7 @@ if (tblock->filter_command)
     }
   }
 
+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
@@ -2614,39 +2782,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: case -2:    goto RESPONSE_FAILED;
-  case -3:             goto END_OFF;
-  case -4:             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
@@ -2660,7 +2850,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))
@@ -2697,6 +2887,7 @@ if (!(sx.peer_offered & PEER_OFFERED_CHUNKING) && !sx.ok)
 else
   {
   transport_ctx tctx = {
+    sx.inblock.sock,
     tblock,
     addrlist,
     US".", US"..",    /* Escaping strings */
@@ -2745,9 +2936,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
@@ -2762,7 +2953,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
@@ -2944,7 +3136,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 "
@@ -3027,8 +3219,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;
     }
@@ -3169,10 +3361,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)
@@ -3182,7 +3377,7 @@ 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(errno));
@@ -3205,30 +3400,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. */
@@ -3237,13 +3464,48 @@ 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);
+           close(pfd[0]);
+           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 */
+           {
+           close(pfd[1]);
+           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
@@ -3270,7 +3532,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:
 
@@ -3351,7 +3613,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);
@@ -3423,7 +3685,7 @@ smtp_transport_entry(
   address_item *addrlist)          /* addresses we are working on */
 {
 int cutoff_retry;
-int port;
+int defport;
 int hosts_defer = 0;
 int hosts_fail  = 0;
 int hosts_looked_up = 0;
@@ -3452,8 +3714,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
@@ -3576,7 +3840,7 @@ else if (ob->hosts_randomize && hostlist->mx == MX_NONE && !continue_hostname)
 
 /* Sort out the default port.  */
 
-if (!smtp_get_port(ob->port, addrlist, &port, tid)) return FALSE;
+if (!smtp_get_port(ob->port, addrlist, &defport, tid)) return FALSE;
 
 /* For each host-plus-IP-address on the list:
 
@@ -3815,7 +4079,7 @@ for (cutoff_retry = 0;
     the default. */
 
     pistring = string_sprintf(":%d", host->port == PORT_NONE
-      ? port : host->port);
+      ? defport : host->port);
     if (Ustrcmp(pistring, ":25") == 0) pistring = US"";
 
     /* Select IPv4 or IPv6, and choose an outgoing interface. If the interface
@@ -3853,7 +4117,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" :
@@ -4015,7 +4279,7 @@ for (cutoff_retry = 0;
       /* Attempt the delivery. */
 
       total_hosts_tried++;
-      rc = smtp_deliver(addrlist, thost, host_af, port, interface, tblock,
+      rc = smtp_deliver(addrlist, thost, host_af, defport, interface, tblock,
         &message_defer, FALSE);
 
       /* Yield is one of:
@@ -4062,7 +4326,7 @@ for (cutoff_retry = 0;
          "%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,
+        rc = smtp_deliver(addrlist, thost, host_af, defport, interface, tblock,
           &message_defer, TRUE);
         if (rc == DEFER && first_addr->basic_errno != ERRNO_AUTHFAIL)
           write_logs(first_addr, host);
@@ -4357,6 +4621,7 @@ DEBUG(D_transport) debug_printf("Leaving %s transport\n", tblock->name);
 return TRUE;   /* Each address has its status */
 }
 
+#endif /*!MACRO_PREDEF*/
 /* vi: aw ai sw=2
 */
 /* End of transport/smtp.c */