LibreSSL: fix build for 3.5.0+ Bug 3074
[exim.git] / src / src / transports / smtp.c
index af2e1f2dd4679716a1b0ddd150fb56d8f219ff01..73659235216fea2c8b21678bb2b4e9e89fb5bffe 100644 (file)
@@ -203,9 +203,6 @@ smtp_transport_options_block smtp_transport_option_defaults = {
   .tls_tempfail_tryclear =     TRUE,
   .tls_try_verify_hosts =      US"*",
   .tls_verify_cert_hostnames = US"*",
-# ifndef DISABLE_TLS_RESUME
-  .host_name_extract =         US"${if and {{match{$host}{.outlook.com\\$}} {match{$item}{\\N^250-([\\w.]+)\\s\\N}}} {$1}}",
-# endif
 #endif
 #ifdef SUPPORT_I18N
   .utf8_downconvert =          US"-1",
@@ -238,6 +235,17 @@ static BOOL    pipelining_active;  /* current transaction is in pipe mode */
 
 static unsigned ehlo_response(uschar * buf, unsigned checks);
 
+/* sync_responses() return codes */
+
+#define RESP_BIT_HAD_5XX       BIT(1)
+#define RESP_BIT_HAD_2XX       BIT(0)
+#define RESP_HAD_2_AND_5       (RESP_BIT_HAD_2XX | RESP_BIT_HAD_5XX)
+#define RESP_NOERROR           0
+#define RESP_RCPT_TIMEO                -1
+#define RESP_RCPT_ERROR                -2
+#define RESP_MAIL_OR_DATA_ERROR        -3
+#define RESP_EPIPE_EHLO_ERR    -4
+#define RESP_EHLO_ERR_TLS      -5
 
 /******************************************************************************/
 
@@ -269,7 +277,7 @@ struct list
 #ifndef DISABLE_PIPE_CONNECT
     { &regex_EARLY_PIPE,       US"\\n250[\\s\\-]" EARLY_PIPE_FEATURE_NAME "(\\s|\\n|$)" },
 #endif
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
     { &regex_LIMITS,           US"\\n250[\\s\\-]LIMITS\\s" },
 #endif
   };
@@ -352,7 +360,7 @@ Returns:    nothing
 void
 smtp_transport_init(transport_instance *tblock)
 {
-smtp_transport_options_block *ob = SOB tblock->options_block;
+smtp_transport_options_block * ob = SOB tblock->options_block;
 int old_pool = store_pool;
 
 /* Retry_use_local_part defaults FALSE if unset */
@@ -659,8 +667,7 @@ static void
 deferred_event_raise(address_item * addr, host_item * host, uschar * evstr)
 {
 uschar * action = addr->transport->event_action;
-const uschar * save_domain;
-uschar * save_local;
+const uschar * save_domain, * save_local;
 
 if (!action)
   return;
@@ -765,31 +772,32 @@ return TRUE;
 }
 
 
+#if !defined(DISABLE_TLS) && !defined(DISABLE_TLS_RESUME)
+
 /* Grab a string differentiating server behind a loadbalancer, for TLS
 resumption when such servers do not share a session-cache */
 
 static void
-ehlo_response_lbserver(smtp_context * sx, smtp_transport_options_block * ob)
+ehlo_response_lbserver(smtp_context * sx, const uschar * name_extract)
 {
-#if !defined(DISABLE_TLS) && !defined(DISABLE_TLS_RESUME)
 const uschar * s;
 uschar * save_item = iterate_item;
 
 if (sx->conn_args.have_lbserver)
   return;
 iterate_item = sx->buffer;
-s = expand_cstring(ob->host_name_extract);
+s = expand_cstring(name_extract);
 iterate_item = save_item;
 sx->conn_args.host_lbserver = s && !*s ? NULL : s;
 sx->conn_args.have_lbserver = TRUE;
-#endif
 }
+#endif
 
 
 
 /******************************************************************************/
 
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
 /* If TLS, or TLS not offered, called with the EHLO response in the buffer.
 Check it for a LIMITS keyword and parse values into the smtp context structure.
 
@@ -892,7 +900,7 @@ write_ehlo_cache_entry(smtp_context * sx)
 {
 open_db dbblock, * dbm_file;
 
-# ifdef EXPERIMENTAL_ESMTP_LIMITS
+# ifndef DISABLE_ESMTP_LIMITS
 sx->ehlo_resp.limit_mail = sx->peer_limit_mail;
 sx->ehlo_resp.limit_rcpt = sx->peer_limit_rcpt;
 sx->ehlo_resp.limit_rcptdom = sx->peer_limit_rcptdom;
@@ -904,7 +912,7 @@ if ((dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE, TRUE)))
   dbdata_ehlo_resp er = { .data = sx->ehlo_resp };
 
   HDEBUG(D_transport)
-# ifdef EXPERIMENTAL_ESMTP_LIMITS
+# ifndef DISABLE_ESMTP_LIMITS
     if (sx->ehlo_resp.limit_mail || sx->ehlo_resp.limit_rcpt || sx->ehlo_resp.limit_rcptdom)
       debug_printf("writing clr %04x/%04x cry %04x/%04x lim %05d/%05d/%05d\n",
        sx->ehlo_resp.cleartext_features, sx->ehlo_resp.cleartext_auths,
@@ -971,7 +979,7 @@ else
   else
     {
     DEBUG(D_transport)
-# ifdef EXPERIMENTAL_ESMTP_LIMITS
+# ifndef DISABLE_ESMTP_LIMITS
       if (er->data.limit_mail || er->data.limit_rcpt || er->data.limit_rcptdom)
        debug_printf("EHLO response bits from cache:"
          " cleartext 0x%04x/0x%04x crypted 0x%04x/0x%04x lim %05d/%05d/%05d\n",
@@ -986,7 +994,7 @@ else
          er->data.crypted_features, er->data.crypted_auths);
 
     sx->ehlo_resp = er->data;
-# ifdef EXPERIMENTAL_ESMTP_LIMITS
+# ifndef DISABLE_ESMTP_LIMITS
     ehlo_cache_limits_apply(sx);
 # endif
     dbfn_close(dbm_file);
@@ -1067,6 +1075,8 @@ sx->pending_EHLO = FALSE;
 
 if (pending_BANNER)
   {
+  const uschar * s;
+
   DEBUG(D_transport) debug_printf("%s expect banner\n", __FUNCTION__);
   (*countp)--;
   if (!smtp_reap_banner(sx))
@@ -1076,7 +1086,13 @@ if (pending_BANNER)
     goto fail;
     }
   /*XXX EXPERIMENTAL_ESMTP_LIMITS ? */
-  ehlo_response_lbserver(sx, sx->conn_args.ob);
+
+# ifndef DISABLE_TLS_RESUME
+  GET_OPTION("host_name_extract");
+  s = ((smtp_transport_options_block *)sx->conn_args.ob)->host_name_extract;
+  if (!s) s = HNE_DEFAULT;
+  ehlo_response_lbserver(sx, s);
+# endif
   }
 
 if (pending_EHLO)
@@ -1105,7 +1121,7 @@ if (pending_EHLO)
        | OPTION_CHUNKING | OPTION_PRDR | OPTION_DSN | OPTION_PIPE | OPTION_SIZE
        | OPTION_UTF8 | OPTION_EARLY_PIPE
        );
-# ifdef EXPERIMENTAL_ESMTP_LIMITS
+# ifndef DISABLE_ESMTP_LIMITS
   if (tls_out.active.sock >= 0 || !(peer_offered & OPTION_TLS))
     ehlo_response_limits_read(sx);
 # endif
@@ -1132,7 +1148,7 @@ if (pending_EHLO)
 
     return OK;         /* just carry on */
     }
-# ifdef EXPERIMENTAL_ESMTP_LIMITS
+# ifndef DISABLE_ESMTP_LIMITS
     /* If we are handling LIMITS, compare the actual EHLO LIMITS values with the
     cached values and invalidate cache if different.  OK to carry on with
     connect since values are advisory. */
@@ -1221,7 +1237,7 @@ int yield = 0;
 #ifndef DISABLE_PIPE_CONNECT
 int rc;
 if ((rc = smtp_reap_early_pipe(sx, &count)) != OK)
-  return rc == FAIL ? -4 : -5;
+  return rc == FAIL ? RESP_EPIPE_EHLO_ERR : RESP_EHLO_ERR_TLS;
 #endif
 
 /* Handle the response for a MAIL command. On error, reinstate the original
@@ -1239,7 +1255,7 @@ if (sx->pending_MAIL)
     DEBUG(D_transport) debug_printf("bad response for MAIL\n");
     Ustrcpy(big_buffer, mail_command);  /* Fits, because it came from there! */
     if (errno == ERRNO_TLSFAILURE)
-      return -5;
+      return RESP_EHLO_ERR_TLS;
     if (errno == 0 && sx->buffer[0] != 0)
       {
       int save_errno = 0;
@@ -1259,7 +1275,7 @@ if (sx->pending_MAIL)
       addr->host_used = sx->conn_args.host;
       addr = addr->next;
       }
-    return -3;
+    return RESP_MAIL_OR_DATA_ERROR;
     }
   }
 
@@ -1273,7 +1289,7 @@ while (count-- > 0)
   {
   while (addr->transport_return != PENDING_DEFER)
     if (!(addr = addr->next))
-      return -2;
+      return RESP_RCPT_ERROR;
 
   /* The address was accepted */
   addr->host_used = sx->conn_args.host;
@@ -1282,7 +1298,7 @@ while (count-- > 0)
   if (smtp_read_response(sx, sx->buffer, sizeof(sx->buffer),
                          '2', ob->command_timeout))
     {
-    yield |= 1;
+    yield |= RESP_BIT_HAD_2XX;
     addr->transport_return = PENDING_OK;
 
     /* If af_dr_retry_exists is set, there was a routing delay on this address;
@@ -1301,7 +1317,7 @@ while (count-- > 0)
   /* Error on first TLS read */
 
   else if (errno == ERRNO_TLSFAILURE)
-    return -5;
+    return RESP_EHLO_ERR_TLS;
 
   /* Timeout while reading the response */
 
@@ -1312,7 +1328,7 @@ while (count-- > 0)
     set_errno_nohost(sx->first_addr, ETIMEDOUT, message, DEFER, FALSE, &sx->delivery_start);
     retry_add_item(addr, addr->address_retry_key, 0);
     update_waiting = FALSE;
-    return -1;
+    return RESP_RCPT_TIMEO;
     }
 
   /* Handle other errors in obtaining an SMTP response by returning -1. This
@@ -1330,7 +1346,7 @@ while (count-- > 0)
     g = string_fmt_append_f(g, SVFMT_TAINT_NOCHK, "RCPT TO:<%s>",
       transport_rcpt_address(addr, sx->conn_args.tblock->rcpt_include_affixes));
     string_from_gstring(g);
-    return -2;
+    return RESP_RCPT_ERROR;
     }
 
   /* Handle SMTP permanent and temporary response codes. */
@@ -1350,7 +1366,7 @@ while (count-- > 0)
     if (sx->buffer[0] == '5')
       {
       addr->transport_return = FAIL;
-      yield |= 2;
+      yield |= RESP_BIT_HAD_5XX;
       }
 
     /* The response was 4xx */
@@ -1370,7 +1386,7 @@ while (count-- > 0)
        /* If a 452 and we've had at least one 2xx or 5xx, set next_addr to the
        start point for another MAIL command. */
 
-       if (addr->more_errno >> 8 == 52  &&  yield & 3)
+       if (addr->more_errno >> 8 == 52  &&  yield > 0)
          {
          if (!sx->RCPT_452)            /* initialised at MAIL-ack above */
            {
@@ -1420,7 +1436,7 @@ while (count-- > 0)
       }
     }
   if (count && !(addr = addr->next))
-    return -2;
+    return RESP_RCPT_ERROR;
   }       /* Loop for next RCPT response */
 
 /* Update where to start at for the next block of responses, unless we
@@ -1442,16 +1458,16 @@ if (pending_DATA != 0)
     BOOL pass_message;
 
     if (errno == ERRNO_TLSFAILURE)     /* Error on first TLS read */
-      return -5;
+      return RESP_EHLO_ERR_TLS;
 
-    if (pending_DATA > 0 || (yield & 1) != 0)
+    if (pending_DATA > 0 || yield & RESP_BIT_HAD_2XX)
       {
       if (errno == 0 && sx->buffer[0] == '4')
        {
        errno = ERRNO_DATA4XX;
        sx->first_addr->more_errno |= ((sx->buffer[1] - '0')*10 + sx->buffer[2] - '0') << 8;
        }
-      return -3;
+      return RESP_MAIL_OR_DATA_ERROR;
       }
     (void)check_response(sx->conn_args.host, &errno, 0, sx->buffer, &code, &msg, &pass_message);
     DEBUG(D_transport) debug_printf("%s\nerror for DATA ignored: pipelining "
@@ -1754,6 +1770,7 @@ uschar * local_authenticated_sender = authenticated_sender;
     authenticated_sender, ob->authenticated_sender, f.smtp_authenticated?"Y":"N");
 #endif
 
+GET_OPTION("authenticated_sender");
 if (ob->authenticated_sender)
   {
   uschar * new = expand_string(ob->authenticated_sender);
@@ -1791,7 +1808,7 @@ return FALSE;
 
 typedef struct smtp_compare_s
 {
-    uschar *                   current_sender_address;
+    const uschar *             current_sender_address;
     struct transport_instance *        tblock;
 } smtp_compare_t;
 
@@ -1801,7 +1818,7 @@ sender_address, helo_data and tls_certificate if enabled.
 */
 
 static uschar *
-smtp_local_identity(uschar * sender, struct transport_instance * tblock)
+smtp_local_identity(const uschar * sender, struct transport_instance * tblock)
 {
 address_item * addr1;
 uschar * if1 = US"";
@@ -1809,7 +1826,7 @@ uschar * helo1 = US"";
 #ifndef DISABLE_TLS
 uschar * tlsc1 = US"";
 #endif
-uschar * save_sender_address = sender_address;
+const uschar * save_sender_address = sender_address;
 uschar * local_identity = NULL;
 smtp_transport_options_block * ob = SOB tblock->options_block;
 
@@ -2004,18 +2021,18 @@ if (flags & tc_reap_prev  &&  prev_cmd_count > 0)
 
   switch(sync_responses(sx, prev_cmd_count, 0))
     {
-    case 1:                            /* 2xx (only) => OK */
-    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 RESP_BIT_HAD_2XX:                             /* OK */
+    case RESP_HAD_2_AND_5: sx->good_RCPT = TRUE;       /* OK & progress made */
+    case RESP_BIT_HAD_5XX: sx->completed_addr = TRUE;  /* progress made */
+    case RESP_NOERROR:    break;       /* No 2xx or 5xx, but no probs */
 
-    case -5: errno = ERRNO_TLSFAILURE;
-            return DEFER;
+    case RESP_EHLO_ERR_TLS:errno = ERRNO_TLSFAILURE;
+                          return DEFER;
 #ifndef DISABLE_PIPE_CONNECT
-    case -4:                           /* non-2xx for pipelined banner or EHLO */
+    case RESP_EPIPE_EHLO_ERR:          /* non-2xx for pipelined banner or EHLO */
 #endif
-    case -1:                           /* Timeout on RCPT */
-    default: return ERROR;             /* I/O error, or any MAIL/DATA error */
+    case RESP_RCPT_TIMEO:              /* Timeout on RCPT */
+    default:              return ERROR;/* I/O error, or any MAIL/DATA error */
     }
   cmd_count = 1;
   if (!sx->pending_BDAT)
@@ -2290,7 +2307,7 @@ if (!continue_hostname)
 
   sx->cctx.tls_ctx = NULL;
   sx->inblock.cctx = sx->outblock.cctx = &sx->cctx;
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
   sx->peer_limit_mail = sx->peer_limit_rcpt = sx->peer_limit_rcptdom =
 #endif
   sx->avoid_option = sx->peer_offered = smtp_peer_options = 0;
@@ -2359,6 +2376,7 @@ PIPE_CONNECT_RETRY:
   For early-pipe, we are ok if binding to a local interface; otherwise (if
   $sending_ip_address is seen in helo_data) we disabled early-pipe above. */
 
+  GET_OPTION("helo_data");
   if (sx->helo_data)
     if (!(sx->helo_data = expand_string(sx->helo_data)))
       if (sx->verify)
@@ -2474,10 +2492,22 @@ goto SEND_QUIT;
 #ifndef DISABLE_TLS
   if (sx->smtps)
     {
+    const uschar * s;
+
     smtp_peer_options |= OPTION_TLS;
     suppress_tls = FALSE;
     ob->tls_tempfail_tryclear = FALSE;
     smtp_command = US"SSL-on-connect";
+
+# ifndef DISABLE_TLS_RESUME
+    /* Having no EHLO response yet, cannot peek there for a servername to detect
+    an LB.  Call this anyway, so that a dummy host_name_extract option value can
+    force resumption attempts. */
+
+    GET_OPTION("host_name_extract");
+    if (!(s = ob->host_name_extract)) s = US"never-LB";
+    ehlo_response_lbserver(sx, s);
+# endif
     goto TLS_NEGOTIATE;
     }
 #endif
@@ -2508,7 +2538,7 @@ goto SEND_QUIT;
        DEBUG(D_transport) debug_printf("may need to auth, so pipeline no further\n");
        if (smtp_write_command(sx, SCMD_FLUSH, NULL) < 0)
          goto SEND_FAILED;
-       if (sync_responses(sx, 2, 0) != 0)
+       if (sync_responses(sx, 2, 0) != RESP_NOERROR)
          {
          HDEBUG(D_transport)
            debug_printf("failed reaping pipelined cmd responses\n");
@@ -2565,6 +2595,8 @@ goto SEND_QUIT;
     if (!sx->early_pipe_active)
 #endif
       {
+      const uschar * s;
+
       sx->peer_offered = ehlo_response(sx->buffer,
        OPTION_TLS      /* others checked later */
 #ifndef DISABLE_PIPE_CONNECT
@@ -2579,7 +2611,7 @@ goto SEND_QUIT;
          )
 #endif
        );
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
       if (tls_out.active.sock >= 0 || !(sx->peer_offered & OPTION_TLS))
        {
        ehlo_response_limits_read(sx);
@@ -2600,7 +2632,11 @@ goto SEND_QUIT;
          }
        }
 #endif
-      ehlo_response_lbserver(sx, ob);
+#ifndef DISABLE_TLS_RESUME
+      GET_OPTION("host_name_extract");
+      if (!(s = ob->host_name_extract)) s = HNE_DEFAULT;
+      ehlo_response_lbserver(sx, s);
+#endif
       }
 
   /* Set tls_offered if the response to EHLO specifies support for STARTTLS. */
@@ -2641,7 +2677,7 @@ else
   sx->inblock.cctx = sx->outblock.cctx = &sx->cctx;
   smtp_command = big_buffer;
   sx->peer_offered = smtp_peer_options;
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
   /* Limits passed by cmdline over exec. */
   ehlo_limits_apply(sx,
                    sx->peer_limit_mail = continue_limit_mail,
@@ -2694,7 +2730,7 @@ if (  smtp_peer_options & OPTION_TLS
 
   if (sx->early_pipe_active)
     {
-    if (sync_responses(sx, 2, 0) != 0)
+    if (sync_responses(sx, 2, 0) != RESP_NOERROR)
       {
       HDEBUG(D_transport)
        debug_printf("failed reaping pipelined cmd responses\n");
@@ -2806,13 +2842,17 @@ if (tls_out.active.sock >= 0)
   {
   uschar * greeting_cmd;
 
-  if (!sx->helo_data && !(sx->helo_data = expand_string(ob->helo_data)))
+  if (!sx->helo_data)
     {
-    uschar *message = string_sprintf("failed to expand helo_data: %s",
-      expand_string_message);
-    set_errno_nohost(sx->addrlist, ERRNO_EXPANDFAIL, message, DEFER, FALSE, &sx->delivery_start);
-    yield = DEFER;
-    goto SEND_QUIT;
+    GET_OPTION("helo_data");
+    if (!(sx->helo_data = expand_string(ob->helo_data)))
+      {
+      uschar *message = string_sprintf("failed to expand helo_data: %s",
+       expand_string_message);
+      set_errno_nohost(sx->addrlist, ERRNO_EXPANDFAIL, message, DEFER, FALSE, &sx->delivery_start);
+      yield = DEFER;
+      goto SEND_QUIT;
+      }
     }
 
 #ifndef DISABLE_PIPE_CONNECT
@@ -2969,7 +3009,7 @@ if (   !continue_hostname
       sx->ehlo_resp.crypted_features = sx->peer_offered;
 #endif
 
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
     if (tls_out.active.sock >= 0 || !(sx->peer_offered & OPTION_TLS))
       {
       ehlo_response_limits_read(sx);
@@ -3063,6 +3103,7 @@ if (sx->addrlist->prop.utf8_msg)
   /* If the transport sets a downconversion mode it overrides any set by ACL
   for the message. */
 
+  GET_OPTION("utf8_downconvert");
   if ((s = ob->utf8_downconvert))
     {
     if (!(s = expand_string(s)))
@@ -3384,21 +3425,15 @@ if (sx->peer_offered & OPTION_DSN && !(addr->dsn_flags & rf_dsnlasthop))
 
 
 
-/*
-Return:
- 0     good, rcpt results in addr->transport_return (PENDING_OK, DEFER, FAIL)
- -1    MAIL response error
- -2    any non-MAIL read i/o error
- -3    non-MAIL response timeout
- -4    internal error; channel still usable
- -5    transmit failed
+/* Send MAIL FROM and RCPT TO commands.
+See sw_mrc_t definition for return codes.
  */
 
-int
+sw_mrc_t
 smtp_write_mail_and_rcpt_cmds(smtp_context * sx, int * yield)
 {
 address_item * addr;
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
 address_item * restart_addr = NULL;
 #endif
 int address_count, pipe_limit;
@@ -3407,7 +3442,7 @@ int rc;
 if (build_mailcmd_options(sx, sx->first_addr) != OK)
   {
   *yield = ERROR;
-  return -4;
+  return sw_mrc_bad_internal;
   }
 
 /* From here until we send the DATA command, we can make use of PIPELINING
@@ -3419,7 +3454,7 @@ buffer. */
 sx->pending_MAIL = TRUE;     /* The block starts with MAIL */
 
   {
-  uschar * s = sx->from_addr;
+  const uschar * s = sx->from_addr;
 #ifdef SUPPORT_I18N
   uschar * errstr = NULL;
 
@@ -3435,7 +3470,7 @@ sx->pending_MAIL = TRUE;     /* The block starts with MAIL */
       {
       set_errno_nohost(sx->addrlist, ERRNO_EXPANDFAIL, errstr, DEFER, FALSE, &sx->delivery_start);
       *yield = ERROR;
-      return -4;
+      return sw_mrc_bad_internal;
       }
     setflag(sx->addrlist, af_utf8_downcvt);
     }
@@ -3450,7 +3485,7 @@ mail_command = string_copy(big_buffer);  /* Save for later error message */
 switch(rc)
   {
   case -1:                /* Transmission error */
-    return -5;
+    return sw_mrc_bad_mail;
 
   case +1:                /* Cmd was sent */
     if (!smtp_read_response(sx, sx->buffer, sizeof(sx->buffer), '2',
@@ -3461,7 +3496,7 @@ switch(rc)
        errno = ERRNO_MAIL4XX;
        sx->addrlist->more_errno |= ((sx->buffer[1] - '0')*10 + sx->buffer[2] - '0') << 8;
        }
-      return -1;
+      return sw_mrc_bad_mail;
       }
     sx->pending_MAIL = FALSE;
     break;
@@ -3490,9 +3525,9 @@ for (addr = sx->first_addr, address_count = 0, pipe_limit = 100;
   {
   int cmds_sent;
   BOOL no_flush;
-  uschar * rcpt_addr;
+  const uschar * rcpt_addr;
 
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
   if (  sx->single_rcpt_domain                                 /* restriction on domains */
      && address_count > 0                                      /* not first being sent */
      && Ustrcmp(addr->domain, sx->first_addr->domain) != 0     /* dom diff from first */
@@ -3536,26 +3571,26 @@ for (addr = sx->first_addr, address_count = 0, pipe_limit = 100;
     {
     /*XXX could we use a per-address errstr here? Not fail the whole send? */
     errno = ERRNO_EXPANDFAIL;
-    return -5;         /*XXX too harsh? */
+    return sw_mrc_tx_fail;             /*XXX too harsh? */
     }
 #endif
 
   cmds_sent = smtp_write_command(sx, no_flush ? SCMD_BUFFER : SCMD_FLUSH,
     "RCPT TO:<%s>%s%s\r\n", rcpt_addr, sx->igquotstr, sx->buffer);
 
-  if (cmds_sent < 0) return -5;
+  if (cmds_sent < 0) return sw_mrc_tx_fail;
   if (cmds_sent > 0)
     {
     switch(sync_responses(sx, cmds_sent, 0))
       {
-      case 3: sx->ok = TRUE;                   /* 2xx & 5xx => OK & progress made */
-      case 2: sx->completed_addr = TRUE;       /* 5xx (only) => progress made */
+      case RESP_HAD_2_AND_5: sx->ok = TRUE;    /* OK & progress made */
+      case RESP_BIT_HAD_5XX: sx->completed_addr = TRUE;        /* progress made */
              break;
 
-      case 1: sx->ok = TRUE;                   /* 2xx (only) => OK, but if LMTP, */
+      case RESP_BIT_HAD_2XX: sx->ok = TRUE;    /* 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 */
+      case RESP_NOERROR:                       /* No 2xx or 5xx, but no probs */
              /* If any RCPT got a 452 response then next_addr has been updated
              for restarting with a new MAIL on the same connection.  Send no more
              RCPTs for this MAIL. */
@@ -3565,28 +3600,28 @@ for (addr = sx->first_addr, address_count = 0, pipe_limit = 100;
                DEBUG(D_transport) debug_printf("seen 452 too-many-rcpts\n");
                sx->RCPT_452 = FALSE;
                /* sx->next_addr has been reset for fast_retry */
-               return 0;
+               return sw_mrc_ok;
                }
              break;
 
-      case -1: return -3;                      /* Timeout on RCPT */
-      case -2: return -2;                      /* non-MAIL read i/o error */
-      default: return -1;                      /* any MAIL error */
+      case RESP_RCPT_TIMEO:        return sw_mrc_nonmail_read_timeo;
+      case RESP_RCPT_ERROR:        return sw_mrc_bad_read;
+      default:                     return sw_mrc_bad_mail;     /* any MAIL error */
 
 #ifndef DISABLE_PIPE_CONNECT
-      case -4: return -1;                      /* non-2xx for pipelined banner or EHLO */
-      case -5: return -1;                      /* TLS first-read error */
+      case RESP_EPIPE_EHLO_ERR:            return sw_mrc_bad_mail;     /* non-2xx for pipelined banner or EHLO */
+      case RESP_EHLO_ERR_TLS:      return sw_mrc_bad_mail;     /* TLS first-read error */
 #endif
       }
     }
   }      /* Loop for next address */
 
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
 sx->next_addr = restart_addr ? restart_addr : addr;
 #else
 sx->next_addr = addr;
 #endif
-return 0;
+return sw_mrc_ok;
 }
 
 
@@ -3765,7 +3800,7 @@ uschar *message = NULL;
 uschar new_message_id[MESSAGE_ID_LENGTH + 1];
 smtp_context * sx = store_get(sizeof(*sx), GET_TAINTED);       /* tainted, for the data buffers */
 BOOL pass_message = FALSE;
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
 BOOL mail_limit = FALSE;
 #endif
 #ifdef SUPPORT_DANE
@@ -3894,11 +3929,12 @@ else
 
   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;
+    case sw_mrc_ok:                    break;
+    case sw_mrc_bad_mail:              goto RESPONSE_FAILED;
+    case sw_mrc_bad_read:              goto RESPONSE_FAILED;
+    case sw_mrc_nonmail_read_timeo:    goto END_OFF;
+    case sw_mrc_bad_internal:          goto SEND_QUIT;
+    default:                           goto SEND_FAILED;
     }
 
   /* If we are an MUA wrapper, abort if any RCPTs were rejected, either
@@ -3936,17 +3972,19 @@ if (  !(smtp_peer_options & OPTION_CHUNKING)
   int count = smtp_write_command(sx, SCMD_FLUSH, "DATA\r\n");
 
   if (count < 0) goto SEND_FAILED;
+
   switch(sync_responses(sx, count, sx->ok ? +1 : -1))
     {
-    case 3: sx->ok = TRUE;            /* 2xx & 5xx => OK & progress made */
-    case 2: sx->completed_addr = TRUE;    /* 5xx (only) => progress made */
-    break;
+    case RESP_HAD_2_AND_5: sx->ok = TRUE;      /* OK & progress made */
+    case RESP_BIT_HAD_5XX: sx->completed_addr = TRUE; /* progress made */
+                          break;
 
-    case 1: sx->ok = TRUE;            /* 2xx (only) => OK, but if LMTP, */
-    if (!sx->lmtp) sx->completed_addr = TRUE; /* can't tell about progress yet */
-    case 0: break;                     /* No 2xx or 5xx, but no probs */
+    case RESP_BIT_HAD_2XX: sx->ok = TRUE;      /* OK, but if LMTP, */
+                          if (!sx->lmtp)       /* can't tell about progress yet */
+                           sx->completed_addr = TRUE;
+    case RESP_NOERROR:    break;               /* No 2xx or 5xx, but no probs */
 
-    case -1: goto END_OFF;             /* Timeout on RCPT */
+    case RESP_RCPT_TIMEO:  goto END_OFF;
 
 #ifndef DISABLE_PIPE_CONNECT
     case -5:                           /* TLS first-read error */
@@ -4176,22 +4214,23 @@ else
     /* Reap any outstanding MAIL & RCPT commands, but not a DATA-go-ahead */
     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 */
-             break;
+      case RESP_HAD_2_AND_5: sx->ok = TRUE;            /* OK & progress made */
+      case RESP_BIT_HAD_5XX: sx->completed_addr = TRUE;        /* progress made */
+                            break;
 
-      case 1: sx->ok = TRUE;           /* 2xx (only) => OK, but if LMTP, */
-      if (!sx->lmtp) sx->completed_addr = TRUE; /* can't tell about progress yet */
-      case 0: break;                   /* No 2xx or 5xx, but no probs */
+      case RESP_BIT_HAD_2XX: sx->ok = TRUE;            /*  OK, but if LMTP, */
+                            if (!sx->lmtp)             /* can't tell about progress yet */
+                              sx->completed_addr = TRUE;
+      case RESP_NOERROR:     break;                    /* No 2xx or 5xx, but no probs */
 
-      case -1: goto END_OFF;           /* Timeout on RCPT */
+      case RESP_RCPT_TIMEO: goto END_OFF;              /* Timeout on RCPT */
 
 #ifndef DISABLE_PIPE_CONNECT
-      case -5:                         /* TLS first-read error */
-      case -4:  HDEBUG(D_transport)
+      case RESP_EHLO_ERR_TLS:                          /* TLS first-read error */
+      case RESP_EPIPE_EHLO_ERR:  HDEBUG(D_transport)
                  debug_printf("failed reaping pipelined cmd responses\n");
 #endif
-      default: goto RESPONSE_FAILED;   /* I/O error, or any MAIL/DATA error */
+      default:              goto RESPONSE_FAILED;      /* I/O error, or any MAIL/DATA error */
       }
     }
 
@@ -4489,7 +4528,26 @@ if (!sx->ok)
        break;
 
       case ERRNO_SMTPCLOSED:
-       message_error = Ustrncmp(smtp_command,"end ",4) == 0;
+       /* If the peer closed the TCP connection after end-of-data, but before
+       we could send QUIT, do TLS close, etc - call it a message error.
+       Otherwise, if all the recipients have been dealt with, call a close no
+       error at all; each address_item should have a suitable result already
+       (2xx: PENDING_OK, 4xx: DEFER, 5xx: FAIL) */
+
+       if (!(message_error = Ustrncmp(smtp_command,"end ",4) == 0))
+         {
+         address_item * addr;
+         for (addr = sx->addrlist; addr; addr = addr->next)
+           if (addr->transport_return == PENDING_DEFER)
+             break;
+         if (!addr)    /* all rcpts fates determined */
+           {
+           log_write(0, LOG_MAIN, "peer close after all rcpt responses;"
+             " converting i/o-error to no-error");
+           sx->ok = TRUE;
+           goto happy;
+           }
+         }
        break;
 
 #ifndef DISABLE_DKIM
@@ -4513,7 +4571,8 @@ if (!sx->ok)
        break;
       }
 
-    /* Handle the cases that are treated as message errors. These are:
+    /* Handle the cases that are treated as message errors (as opposed to
+    host-errors). These are:
 
       (a) negative response or timeout after MAIL
       (b) negative response after DATA
@@ -4617,13 +4676,15 @@ connection to a new process. However, not all servers can handle this (Exim
 can), so we do not pass such a connection on if the host matches
 hosts_nopass_tls. */
 
+happy:
+
 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, f.continue_more, yield, sx->first_addr ? "not " : "");
 
 if (sx->completed_addr && sx->ok && sx->send_quit)
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
   if (mail_limit = continue_sequence >= sx->max_mail)
     {
     DEBUG(D_transport)
@@ -4684,7 +4745,7 @@ if (sx->completed_addr && sx->ok && sx->send_quit)
 
        if (sx->first_addr)             /* More addresses still to be sent */
          {                             /*   for this message              */
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
          /* Any that we marked as skipped, reset to do now */
          for (address_item * a = sx->first_addr; a; a = a->next)
            if (a->transport_return == SKIP)
@@ -4759,7 +4820,7 @@ if (sx->completed_addr && sx->ok && sx->send_quit)
   */
        if (sx->ok && transport_pass_socket(tblock->name, host->name,
              host->address, new_message_id, socket_fd
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
              , sx->peer_limit_mail, sx->peer_limit_rcpt, sx->peer_limit_rcptdom
 #endif
              ))
@@ -4936,7 +4997,7 @@ if (dane_held)
   }
 #endif
 
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
 if (mail_limit && sx->first_addr)
   {
   /* Reset the sequence count since we closed the connection.  This is flagged
@@ -5034,16 +5095,16 @@ Returns:       the first address for this delivery
 */
 
 static address_item *
-prepare_addresses(address_item *addrlist, host_item *host)
+prepare_addresses(address_item * addrlist, host_item * host)
 {
-address_item *first_addr = NULL;
+address_item * first_addr = NULL;
 for (address_item * addr = addrlist; addr; addr = addr->next)
   if (addr->transport_return == DEFER)
     {
     if (!first_addr) first_addr = addr;
     addr->transport_return = PENDING_DEFER;
     addr->basic_errno = 0;
-    addr->more_errno = (host->mx >= 0)? 'M' : 'A';
+    addr->more_errno = host->mx >= 0 ? 'M' : 'A';
     addr->message = NULL;
 #ifndef DISABLE_TLS
     addr->cipher = NULL;
@@ -5075,24 +5136,17 @@ FALSE. */
 
 BOOL
 smtp_transport_entry(
-  transport_instance *tblock,      /* data for this instantiation */
-  address_item *addrlist)          /* addresses we are working on */
+  transport_instance * tblock,      /* data for this instantiation */
+  address_item * addrlist)          /* addresses we are working on */
 {
 int defport;
-int hosts_defer = 0;
-int hosts_fail  = 0;
-int hosts_looked_up = 0;
-int hosts_retry = 0;
-int hosts_serial = 0;
-int hosts_total = 0;
-int total_hosts_tried = 0;
+int hosts_defer = 0, hosts_fail  = 0, hosts_looked_up = 0;
+int hosts_retry = 0, hosts_serial = 0, hosts_total = 0, total_hosts_tried = 0;
 BOOL expired = TRUE;
-uschar *expanded_hosts = NULL;
-uschar *pistring;
-uschar *tid = string_sprintf("%s transport", tblock->name);
-smtp_transport_options_block *ob = SOB tblock->options_block;
-host_item *hostlist = addrlist->host_list;
-host_item *host = NULL;
+uschar * expanded_hosts = NULL, * pistring;
+uschar * tid = string_sprintf("%s transport", tblock->name);
+smtp_transport_options_block * ob = SOB tblock->options_block;
+host_item * hostlist = addrlist->host_list, * host = NULL;
 
 DEBUG(D_transport)
   {
@@ -5132,7 +5186,7 @@ database if the delivery fails temporarily or if we are running with
 queue_smtp or a 2-stage queue run. This gets unset for certain
 kinds of error, typically those that are specific to the message. */
 
-update_waiting =  TRUE;
+update_waiting = TRUE;
 
 /* If a host list is not defined for the addresses - they must all have the
 same one in order to be passed to a single transport - or if the transport has
@@ -5320,16 +5374,12 @@ retry_non_continued:
        && total_hosts_tried < ob->hosts_max_try_hardlimit;
        host = nexthost)
     {
-    int rc;
-    int host_af;
-    BOOL host_is_expired = FALSE;
-    BOOL message_defer = FALSE;
-    BOOL some_deferred = FALSE;
-    address_item *first_addr = NULL;
-    uschar *interface = NULL;
-    uschar *retry_host_key = NULL;
-    uschar *retry_message_key = NULL;
-    uschar *serialize_key = NULL;
+    int rc, host_af;
+    BOOL host_is_expired = FALSE, message_defer = FALSE, some_deferred = FALSE;
+    address_item * first_addr = NULL;
+    uschar * interface = NULL;
+    uschar * retry_host_key = NULL, * retry_message_key = NULL;
+    uschar * serialize_key = NULL;
 
     /* Deal slightly better with a possible Linux kernel bug that results
     in intermittent TFO-conn fails deep into the TCP flow.  Bug 2907 tracks.
@@ -5525,8 +5575,9 @@ retry_non_continued:
 
     host_af = Ustrchr(host->address, ':') ? AF_INET6 : AF_INET;
       {
-      uschar * s = ob->interface;
-      if (s && *s)
+      uschar * s;
+      GET_OPTION("interface");
+      if ((s = ob->interface) && *s)
        {
        if (!smtp_get_interface(s, host_af, addrlist, &interface, tid))
          return FALSE;
@@ -5636,7 +5687,14 @@ retry_non_continued:
     out the result of previous attempts, and finding the first address that
     is still to be delivered. */
 
-    first_addr = prepare_addresses(addrlist, host);
+    if (!(first_addr = prepare_addresses(addrlist, host)))
+      {
+      /* Obscure situation; at least one case (bug 3059, fixed) where
+      a previous host try returned DEFER, but having moved all
+      recipients away from DEFER (the waiting-to-be-done state). */
+      DEBUG(D_transport) debug_printf("no pending recipients\n");
+      goto END_TRANSPORT;
+      }
 
     DEBUG(D_transport) debug_printf("delivering %s to %s [%s] (%s%s)\n",
       message_id, host->name, host->address, addrlist->address,
@@ -5688,7 +5746,7 @@ retry_non_continued:
       {
       host_item * thost;
       /* Make a copy of the host if it is local to this invocation
-       of the transport. */
+      of the transport. */
 
       if (expanded_hosts)
        {
@@ -5882,20 +5940,14 @@ retry_non_continued:
     if (rc == OK)
       for (address_item * addr = addrlist; addr; addr = addr->next)
         if (addr->transport_return == DEFER)
-          {
-          some_deferred = TRUE;
-          break;
-          }
+          { some_deferred = TRUE; break; }
 
     /* If no addresses deferred or the result was ERROR, return. We do this for
     ERROR because a failing filter set-up or add_headers expansion is likely to
     fail for any host we try. */
 
     if (rc == ERROR || (rc == OK && !some_deferred))
-      {
-      DEBUG(D_transport) debug_printf("Leaving %s transport\n", tblock->name);
-      return TRUE;    /* Each address has its status */
-      }
+      goto END_TRANSPORT;
 
     /* If the result was DEFER or some individual addresses deferred, let
     the loop run to try other hosts with the deferred addresses, except for the
@@ -5915,7 +5967,7 @@ retry_non_continued:
     if ((rc == DEFER || some_deferred) && nexthost)
       {
       BOOL timedout;
-      retry_config *retry = retry_find_config(host->name, NULL, 0, 0);
+      retry_config * retry = retry_find_config(host->name, NULL, 0, 0);
 
       if (retry && retry->rules)
         {