use smtp_context struct for sync_responses()
[exim.git] / src / src / transports / smtp.c
index f3247245d3f99c20ef3d8d4f3fabb789dbdae26e..af463d66a4986958115fa670c87d330356594b8c 100644 (file)
@@ -8,12 +8,6 @@
 #include "../exim.h"
 #include "smtp.h"
 
-#define PENDING          256
-#define PENDING_DEFER   (PENDING + DEFER)
-#define PENDING_OK      (PENDING + OK)
-
-#define DELIVER_BUFFER_SIZE 4096
-
 
 /* Options specific to the smtp transport. This transport also supports LMTP
 over TCP/IP. The options must be in alphabetic order (note that "_" comes
@@ -327,7 +321,7 @@ gid = gid;
 
 /* Pass back options if required. This interface is getting very messy. */
 
-if (tf != NULL)
+if (tf)
   {
   tf->interface = ob->interface;
   tf->port = ob->port;
@@ -346,11 +340,8 @@ host lists, provided that the local host wasn't present in the original host
 list. */
 
 if (!testflag(addrlist, af_local_host_removed))
-  {
-  for (; addrlist != NULL; addrlist = addrlist->next)
-    if (addrlist->fallback_hosts == NULL)
-      addrlist->fallback_hosts = ob->fallback_hostlist;
-  }
+  for (; addrlist; addrlist = addrlist->next)
+    if (!addrlist->fallback_hosts) addrlist->fallback_hosts = ob->fallback_hostlist;
 
 return OK;
 }
@@ -458,7 +449,7 @@ for (addr = addrlist; addr; addr = addr->next)
     {
     addr->basic_errno = errno_value;
     addr->more_errno |= orvalue;
-    if (msg != NULL)
+    if (msg)
       {
       addr->message = msg;
       if (pass_message) setflag(addr, af_pass_message);
@@ -610,7 +601,7 @@ if (*errno_value == 0 || *errno_value == ECONNRESET)
   {
   *errno_value = ERRNO_SMTPCLOSED;
   *message = US string_sprintf("Remote host closed connection "
-    "in response to %s%s",  pl, smtp_command);
+    "in response to %s%s", pl, smtp_command);
   }
 else *message = US string_sprintf("%s [%s]", host->name, host->address);
 
@@ -744,21 +735,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
@@ -770,39 +751,35 @@ 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;
 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', ((smtp_transport_options_block *)sx->tblock->options_block)->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', ((smtp_transport_options_block *)sx->tblock->options_block)->command_timeout)
             && (errno != 0 || flushbuffer[0] == 0))
           break;
         }
@@ -813,7 +790,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;
@@ -831,9 +808,9 @@ while (count-- > 0)
   while (addr->transport_return != PENDING_DEFER) addr = addr->next;
 
   /* 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', ((smtp_transport_options_block *)sx->tblock->options_block)->command_timeout))
     {
     yield |= 1;
     addr->transport_return = PENDING_OK;
@@ -856,8 +833,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;
@@ -868,10 +845,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;
     }
 
@@ -881,14 +858,14 @@ 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);
+    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;
@@ -900,7 +877,7 @@ 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;
 
 #ifndef DISABLE_EVENT
       event_defer_errno = addr->more_errno;
@@ -910,8 +887,8 @@ while (count-- > 0)
       /* 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
@@ -927,7 +904,7 @@ while (count-- > 0)
       too soon. If address_retry_include_sender is true, add the sender address
       to the retry key. */
 
-      if (address_retry_include_sender)
+      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);
@@ -941,27 +918,27 @@ 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 != NULL) 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', ((smtp_transport_options_block *)sx->tblock->options_block)->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);
   }
@@ -1387,6 +1364,7 @@ smtp_transport_options_block * ob =
 int cmd_count = 0;
 int prev_cmd_count;
 uschar * buffer = tctx->buffer;
+smtp_context sx;
 
 
 /* Write SMTP chunk header command */
@@ -1412,16 +1390,17 @@ if (tctx->pending_BDAT)
 
 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(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 */
@@ -1431,6 +1410,7 @@ if (flags & tc_reap_prev  &&  prev_cmd_count > 0)
     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)
     pipelining_active = FALSE;
@@ -1442,14 +1422,14 @@ if (tctx->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(tctx->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.buffer[1] - '0')*10 + sx.buffer[2] - '0') << 8;
       }
     return ERROR;
     }
@@ -1471,61 +1451,9 @@ return OK;
 *       Make connection for given message        *
 *************************************************/
 
-typedef struct {
-  address_item *       addrlist;
-  host_item *          host;
-  int                  host_af;
-  int                  port;
-  uschar *             interface;
-
-  BOOL lmtp:1;
-  BOOL smtps:1;
-  BOOL ok:1;
-  BOOL send_rset:1;
-  BOOL send_quit:1;
-  BOOL setting_up:1;
-  BOOL esmtp:1;
-  BOOL esmtp_sent:1;
-  BOOL pending_MAIL:1;
-#ifndef DISABLE_PRDR
-  BOOL prdr_active:1;
-#endif
-#ifdef SUPPORT_I18N
-  BOOL utf8_needed:1;
-#endif
-  BOOL dsn_all_lasthop:1;
-#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_DANE)
-  BOOL dane:1;
-  BOOL dane_required:1;
-#endif
-
-  int          max_rcpt;
-
-  uschar       peer_offered;
-  uschar *     igquotstr;
-  uschar *     helo_data;
-#ifdef EXPERIMENTAL_DSN_INFO
-  uschar *     smtp_greeting;
-  uschar *     helo_response;
-#endif
-
-  smtp_inblock  inblock;
-  smtp_outblock outblock;
-  uschar       buffer[DELIVER_BUFFER_SIZE];
-  uschar       inbuffer[4096];
-  uschar       outbuffer[4096];
-
-  transport_instance *                 tblock;
-  smtp_transport_options_block *       ob;
-} smtp_context;
-
 /*
 Arguments:
   ctx            connection context
-  message_defer   return set TRUE if yield is OK, but all addresses were deferred
-                    because of a non-recipient, non-host failure, that is, a
-                    4xx response to MAIL FROM, DATA, or ".". This is a defer
-                    that is specific to the message.
   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
@@ -1541,8 +1469,7 @@ Returns:          OK    - the connection was made and the delivery attempted;
                          to expand
 */
 int
-smtp_setup_conn(smtp_context * sx, BOOL * message_defer, BOOL suppress_tls,
-       BOOL verify)
+smtp_setup_conn(smtp_context * sx, BOOL suppress_tls, BOOL verify)
 {
 #if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_DANE)
 dns_answer tlsa_dnsa;
@@ -1554,7 +1481,7 @@ int save_errno;
 int yield = OK;
 int rc;
 
-sx->ob = (smtp_transport_options_block *)(sx->tblock->options_block);
+sx->ob = (smtp_transport_options_block *) sx->tblock->options_block;
 
 sx->lmtp = strcmpic(sx->ob->protocol, US"lmtp") == 0;
 sx->smtps = strcmpic(sx->ob->protocol, US"smtps") == 0;
@@ -1574,15 +1501,14 @@ sx->dane_required = verify_check_given_host(&sx->ob->hosts_require_dane, sx->hos
 #endif
 
 if ((sx->max_rcpt = sx->tblock->max_addresses) == 0) sx->max_rcpt = 999999;
-sx->helo_data = NULL;
 sx->peer_offered = 0;
 sx->igquotstr = US"";
+if (!sx->helo_data) sx->helo_data = sx->ob->helo_data;
 #ifdef EXPERIMENTAL_DSN_INFO
 sx->smtp_greeting = NULL;
 sx->helo_response = NULL;
 #endif
 
-*message_defer = FALSE;
 smtp_command = US"initial connection";
 sx->buffer[0] = '\0';
 
@@ -1615,7 +1541,8 @@ tls_out.ocsp = OCSP_NOT_REQ;
 
 /* Flip the legacy TLS-related variables over to the outbound set in case
 they're used in the context of the transport.  Don't bother resetting
-afterward as we're in a subprocess. */
+afterward (when being used by a transport) as we're in a subprocess.
+For verify, unflipped once the callout is dealt with */
 
 tls_modify_variables(&tls_out);
 
@@ -1634,6 +1561,9 @@ specially so they can be identified for retries. */
 
 if (continue_hostname == NULL)
   {
+  if (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,
@@ -1641,8 +1571,19 @@ if (continue_hostname == NULL)
 
   if (sx->inblock.sock < 0)
     {
-    set_errno_nohost(sx->addrlist, errno == ETIMEDOUT ? ERRNO_CONNECTTIMEOUT : errno,
-      NULL, DEFER, FALSE);
+    uschar * msg = NULL;
+    int save_errno = errno;
+    if (verify)
+      {
+      msg = 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)
+            : NULL,
+      DEFER, FALSE);
+    sx->send_quit = FALSE;
     return DEFER;
     }
 
@@ -1684,18 +1625,26 @@ if (continue_hostname == NULL)
   sense if helo_data contains ${lookup dnsdb ...} stuff). The expansion is
   delayed till here so that $sending_interface and $sending_port are set. */
 
-  sx->helo_data = expand_string(sx->ob->helo_data);
+  if (sx->helo_data)
+    if (!(sx->helo_data = expand_string(sx->helo_data)))
+      if (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);
+
 #ifdef SUPPORT_I18N
   if (sx->helo_data)
     {
-    uschar * errstr = NULL;
-    if ((sx->helo_data = string_domain_utf8_to_alabel(sx->helo_data, &errstr)), errstr)
-      {
-      errstr = string_sprintf("failed to expand helo_data: %s", errstr);
-      set_errno_nohost(sx->addrlist, ERRNO_EXPANDFAIL, errstr, DEFER, FALSE);
-      yield = DEFER;
-      goto SEND_QUIT;
-      }
+    expand_string_message = NULL;
+    if ((sx->helo_data = string_domain_utf8_to_alabel(sx->helo_data,
+                                             &expand_string_message)),
+       expand_string_message)
+      if (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);
+      else
+       sx->helo_data = NULL;
     }
 #endif
 
@@ -1892,6 +1841,7 @@ else
   sx->inblock.sock = sx->outblock.sock = fileno(stdin);
   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 */
   }
 
 /* If TLS is available on this connection, whether continued or not, attempt to
@@ -1905,7 +1855,10 @@ for error analysis. */
 #ifdef SUPPORT_TLS
 if (  smtp_peer_options & PEER_OFFERED_TLS
    && !suppress_tls
-   && verify_check_given_host(&sx->ob->hosts_avoid_tls, sx->host) != OK)
+   && verify_check_given_host(&sx->ob->hosts_avoid_tls, sx->host) != OK
+   && (  !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)
@@ -2226,8 +2179,6 @@ SEND_QUIT:
 if (sx->send_quit)
   (void)smtp_write_command(&sx->outblock, FALSE, "QUIT\r\n");
 
-/*END_OFF:*/
-
 #ifdef SUPPORT_TLS
 tls_close(FALSE, TRUE);
 #endif
@@ -2249,8 +2200,10 @@ if (sx->send_quit)
   if (fcntl(sx->inblock.sock, F_SETFL, O_NONBLOCK) == 0)
     for (rc = 16; read(sx->inblock.sock, sx->inbuffer, sizeof(sx->inbuffer)) > 0 && rc > 0;)
       rc--;                            /* drain socket */
+  sx->send_quit = FALSE;
   }
 (void)close(sx->inblock.sock);
+sx->inblock.sock = sx->outblock.sock = -1;
 
 #ifndef DISABLE_EVENT
 (void) event_raise(sx->tblock->event_action, US"tcp:close", NULL);
@@ -2399,6 +2352,163 @@ 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
+ */
+
+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 -3;
+  }
+
+/* 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 -3;
+      }
+    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 -4;
+
+  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;
+  }
+
+/* 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 (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 && (!mua_wrapper || addr->next);
+
+  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 -4;         /*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 -4;
+  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 -2;                      /* Timeout on RCPT */
+      default: return -1;                      /* I/O error, or any MAIL error */
+      }
+    sx->pending_MAIL = FALSE;            /* Dealt with MAIL */
+    }
+  }      /* Loop for next address */
+
+sx->next_addr = addr;
+return 0;
+}
+
+
 /*************************************************
 *       Deliver address list to given host       *
 *************************************************/
@@ -2449,17 +2559,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];
@@ -2468,18 +2572,20 @@ uschar *p;
 smtp_context sx;
 
 suppress_tls = suppress_tls;  /* stop compiler warning when no TLS support */
+*message_defer = FALSE;
 
 sx.addrlist = addrlist;
 sx.host = host;
 sx.host_af = host_af,
 sx.port = port;
 sx.interface = interface;
+sx.helo_data = NULL;
 sx.tblock = tblock;
 
 /* Get the channel set up ready for a message (MAIL FROM being the next
 SMTP command to send */
 
-if ((rc = smtp_setup_conn(&sx, message_defer, suppress_tls, FALSE)) != OK)
+if ((rc = smtp_setup_conn(&sx, suppress_tls, FALSE)) != OK)
   return rc;
 
 /* If there is a filter command specified for this transport, we can now
@@ -2526,154 +2632,24 @@ 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.first_addr = sx.sync_addr = addrlist;
 sx.ok = FALSE;
 sx.send_rset = TRUE;
-completed_address = FALSE;
+sx.completed_addr = FALSE;
 
 
 /* Initiate a message transfer. */
 
-if (build_mailcmd_options(&sx, first_addr) != OK)
-  {
-  yield = ERROR;
-  goto SEND_QUIT;
-  }
-
-/* 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 */
-
+switch(smtp_write_mail_and_rcpt_cmds(&sx, &yield))
   {
-  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);
+  case 0:      break;
+  case -1:     goto RESPONSE_FAILED;
+  case -2:     goto END_OFF;
+  case -3:     goto SEND_QUIT;
+  default:     goto SEND_FAILED;
   }
 
-mail_command = string_copy(big_buffer);  /* Save for later error message */
-
-switch(rc)
-  {
-  case -1:                /* Transmission error */
-    goto SEND_FAILED;
-
-  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;
-  }
-
-/* 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)
-  {
-  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);
-
-  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))
-     )
-    {
-    /*XXX could we use a per-address errstr here? Not fail the whole send? */
-    errno = ERRNO_EXPANDFAIL;
-    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 (count < 0) goto SEND_FAILED;
-  if (count > 0)
-    {
-    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;
-
-      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 */
-    }
-  }      /* 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. */
@@ -2681,7 +2657,7 @@ RCPT. */
 if (mua_wrapper)
   {
   address_item *badaddr;
-  for (badaddr = first_addr; badaddr; badaddr = badaddr->next)
+  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? */
@@ -2706,16 +2682,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 */
@@ -2735,7 +2709,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;
   }
@@ -2766,12 +2740,12 @@ else
     tctx.inblock = &sx.inblock;
     tctx.outblock = &sx.outblock;
     tctx.host = host;
-    tctx.first_addr = first_addr;
-    tctx.sync_addr = &sync_addr;
+    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 = &completed_address;
+    tctx.completed_address = &sx.completed_addr;
     tctx.cmd_count = 0;
     tctx.buffer = sx.buffer;
     }
@@ -2779,7 +2753,7 @@ 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. */
@@ -2827,17 +2801,14 @@ else
   if (sx.peer_offered & PEER_OFFERED_CHUNKING && tctx.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, tctx.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 */
@@ -2921,7 +2892,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;
 
@@ -2962,7 +2933,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);
@@ -3021,14 +2992,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))
@@ -3212,9 +3183,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;
@@ -3222,7 +3193,7 @@ 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
            || verify_check_given_host(&sx.ob->hosts_nopass_tls, host) != OK
@@ -3260,7 +3231,7 @@ if (completed_address && sx.ok && sx.send_quit)
 
     if (sx.ok)
       {
-      if (first_addr != NULL)            /* More addresses still to be sent */
+      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;
@@ -3298,7 +3269,7 @@ propagate it from the initial
 
     /* 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
@@ -3386,7 +3357,7 @@ void
 smtp_transport_closedown(transport_instance *tblock)
 {
 smtp_transport_options_block *ob =
-  (smtp_transport_options_block *)(tblock->options_block);
+  (smtp_transport_options_block *)tblock->options_block;
 smtp_inblock inblock;
 smtp_outblock outblock;
 uschar buffer[256];