TLS: Session resumption, under the EXPERIMENTAL_TLS_RESUME build option.
[exim.git] / src / src / transports / smtp.c
index c1cba598c5db5ea9fede20a98edc7903dfa76259..f76b4f730cd0ba5de83cdd529880cd83ea3f4557 100644 (file)
@@ -183,6 +183,10 @@ optionlist smtp_transport_options[] = {
       (void *)offsetof(smtp_transport_options_block, tls_privatekey) },
   { "tls_require_ciphers",  opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, tls_require_ciphers) },
+# ifdef EXPERIMENTAL_TLS_RESUME
+  { "tls_resumption_hosts", opt_stringptr,
+      (void *)offsetof(smtp_transport_options_block, tls_resumption_hosts) },
+# endif
   { "tls_sni",              opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, tls_sni) },
   { "tls_tempfail_tryclear", opt_bool,
@@ -293,6 +297,9 @@ smtp_transport_options_block smtp_transport_option_defaults = {
   .tls_verify_certificates =   US"system",
   .tls_dh_min_bits =           EXIM_CLIENT_DH_DEFAULT_MIN_BITS,
   .tls_tempfail_tryclear =     TRUE,
+# ifdef EXPERIMENTAL_TLS_RESUME
+  .tls_resumption_hosts =      NULL,
+# endif
   .tls_verify_hosts =          NULL,
   .tls_try_verify_hosts =      US"*",
   .tls_verify_cert_hostnames = US"*",
@@ -593,6 +600,11 @@ switch(*errno_value)
          pl, smtp_command, s);
     return FALSE;
 
+  case ERRNO_TLSFAILURE:       /* Handle bad first read; can happen with
+                               GnuTLS and TLS1.3 */
+    *message = US"bad first read from TLS conn";
+    return TRUE;
+
   case ERRNO_FILTER_FAIL:      /* Handle a failed filter process error;
                          can't send QUIT as we mustn't end the DATA. */
     *message = string_sprintf("transport filter process failed (%d)%s",
@@ -820,7 +832,7 @@ write_ehlo_cache_entry(const smtp_context * sx)
 {
 open_db dbblock, * dbm_file;
 
-if ((dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE)))
+if ((dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE, TRUE)))
   {
   uschar * ehlo_resp_key = ehlo_cache_key(sx);
   dbdata_ehlo_resp er = { .data = sx->ehlo_resp };
@@ -840,7 +852,7 @@ invalidate_ehlo_cache_entry(smtp_context * sx)
 open_db dbblock, * dbm_file;
 
 if (  sx->early_pipe_active
-   && (dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE)))
+   && (dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE, TRUE)))
   {
   uschar * ehlo_resp_key = ehlo_cache_key(sx);
   dbfn_delete(dbm_file, ehlo_resp_key);
@@ -854,7 +866,7 @@ read_ehlo_cache_entry(smtp_context * sx)
 open_db dbblock;
 open_db * dbm_file;
 
-if (!(dbm_file = dbfn_open(US"misc", O_RDONLY, &dbblock, FALSE)))
+if (!(dbm_file = dbfn_open(US"misc", O_RDONLY, &dbblock, FALSE, TRUE)))
   { DEBUG(D_transport) debug_printf("ehlo-cache: no misc DB\n"); }
 else
   {
@@ -867,7 +879,7 @@ else
     {
     DEBUG(D_transport) debug_printf("ehlo-resp record too old\n");
     dbfn_close(dbm_file);
-    if ((dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE)))
+    if ((dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE, TRUE)))
       dbfn_delete(dbm_file, ehlo_resp_key);
     }
   else
@@ -941,6 +953,7 @@ Arguments:
 
 Return:
  OK    all well
+ DEFER error on first read of TLS'd conn
  FAIL  SMTP error in response
 */
 int
@@ -948,6 +961,7 @@ smtp_reap_early_pipe(smtp_context * sx, int * countp)
 {
 BOOL pending_BANNER = sx->pending_BANNER;
 BOOL pending_EHLO = sx->pending_EHLO;
+int rc = FAIL;
 
 sx->pending_BANNER = FALSE;    /* clear early to avoid recursion */
 sx->pending_EHLO = FALSE;
@@ -959,6 +973,7 @@ if (pending_BANNER)
   if (!smtp_reap_banner(sx))
     {
     DEBUG(D_transport) debug_printf("bad banner\n");
+    if (tls_out.active.sock >= 0) rc = DEFER;
     goto fail;
     }
   }
@@ -973,6 +988,7 @@ if (pending_EHLO)
   if (!smtp_reap_ehlo(sx))
     {
     DEBUG(D_transport) debug_printf("bad response for EHLO\n");
+    if (tls_out.active.sock >= 0) rc = DEFER;
     goto fail;
     }
 
@@ -983,7 +999,7 @@ if (pending_EHLO)
       ? &sx->ehlo_resp.cleartext_auths : &sx->ehlo_resp.crypted_auths;
 
   peer_offered = ehlo_response(sx->buffer,
-         (tls_out.active.sock < 0 ?  OPTION_TLS : OPTION_REQUIRETLS)
+         (tls_out.active.sock < 0 ?  OPTION_TLS : 0)
        | OPTION_CHUNKING | OPTION_PRDR | OPTION_DSN | OPTION_PIPE | OPTION_SIZE
        | OPTION_UTF8 | OPTION_EARLY_PIPE
        );
@@ -1010,7 +1026,7 @@ return OK;
 fail:
   invalidate_ehlo_cache_entry(sx);
   (void) smtp_discard_responses(sx, sx->conn_args.ob, *countp);
-  return FAIL;
+  return rc;
 }
 #endif
 
@@ -1055,6 +1071,7 @@ Returns:      3 if at least one address had 2xx and one had 5xx
              -2 I/O or other non-response error for RCPT
              -3 DATA or MAIL failed - errno and buffer set
             -4 banner or EHLO failed (early-pipelining)
+            -5 banner or EHLO failed (early-pipelining, TLS)
 */
 
 static int
@@ -1065,8 +1082,9 @@ smtp_transport_options_block * ob = sx->conn_args.ob;
 int yield = 0;
 
 #ifdef EXPERIMENTAL_PIPE_CONNECT
-if (smtp_reap_early_pipe(sx, &count) != OK)
-  return -4;
+int rc;
+if ((rc = smtp_reap_early_pipe(sx, &count)) != OK)
+  return rc == FAIL ? -4 : -5;
 #endif
 
 /* Handle the response for a MAIL command. On error, reinstate the original
@@ -1082,6 +1100,8 @@ 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;
     if (errno == 0 && sx->buffer[0] != 0)
       {
       int save_errno = 0;
@@ -1140,6 +1160,11 @@ while (count-- > 0)
       }
     }
 
+  /* Error on first TLS read */
+
+  else if (errno == ERRNO_TLSFAILURE)
+    return -5;
+
   /* Timeout while reading the response */
 
   else if (errno == ETIMEDOUT)
@@ -1252,6 +1277,10 @@ if (pending_DATA != 0)
     int code;
     uschar *msg;
     BOOL pass_message;
+
+    if (errno == ERRNO_TLSFAILURE)     /* Error on first TLS read */
+      return -5;
+
     if (pending_DATA > 0 || (yield & 1) != 0)
       {
       if (errno == 0 && sx->buffer[0] == '4')
@@ -1405,8 +1434,6 @@ if (  sx->esmtp
   if (  require_auth == OK
      || verify_check_given_host(CUSS &ob->hosts_try_auth, host) == OK)
     {
-    auth_instance * au;
-
     DEBUG(D_transport) debug_printf("scanning authentication mechanisms\n");
     fail_reason = US"no common mechanisms were found";
 
@@ -1419,6 +1446,7 @@ if (  sx->esmtp
       client function.  We are limited to supporting up to 16 authenticator
       public-names by the number of bits in a short. */
 
+      auth_instance * au;
       uschar bitnum;
       int rc;
 
@@ -1727,12 +1755,6 @@ size_t bsize = Ustrlen(buf);
 /* debug_printf("%s: check for 0x%04x\n", __FUNCTION__, checks); */
 
 #ifdef SUPPORT_TLS
-# ifdef EXPERIMENTAL_REQUIRETLS
-if (  checks & OPTION_REQUIRETLS
-   && pcre_exec(regex_REQUIRETLS, NULL, CS buf,bsize, 0, PCRE_EOPT, NULL,0) < 0)
-# endif
-  checks &= ~OPTION_REQUIRETLS;
-
 if (  checks & OPTION_TLS
    && pcre_exec(regex_STARTTLS, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
 #endif
@@ -1800,7 +1822,9 @@ Args:
    tc_chunk_last       add LAST option to SMTP BDAT command
    tc_reap_prev                reap response to previous SMTP commands
 
-Returns:       OK or ERROR
+Returns:
+  OK or ERROR
+  DEFER                        TLS error on first read (EHLO-resp); errno set
 */
 
 static int
@@ -1857,10 +1881,12 @@ if (flags & tc_reap_prev  &&  prev_cmd_count > 0)
     case 2: sx->completed_addr = TRUE; /* 5xx (only) => progress made */
     case 0: break;                     /* No 2xx or 5xx, but no probs */
 
-    case -1:                           /* Timeout on RCPT */
+    case -5: errno = ERRNO_TLSFAILURE;
+            return DEFER;
 #ifdef EXPERIMENTAL_PIPE_CONNECT
     case -4:                           /* non-2xx for pipelined banner or EHLO */
 #endif
+    case -1:                           /* Timeout on RCPT */
     default: return ERROR;             /* I/O error, or any MAIL/DATA error */
     }
   cmd_count = 1;
@@ -1923,14 +1949,14 @@ Returns:          OK    - the connection was made and the delivery attempted;
 int
 smtp_setup_conn(smtp_context * sx, BOOL suppress_tls)
 {
-#if defined(SUPPORT_TLS) && defined(SUPPORT_DANE)
-dns_answer tlsa_dnsa;
-#endif
 smtp_transport_options_block * ob = sx->conn_args.tblock->options_block;
 BOOL pass_message = FALSE;
 uschar * message = NULL;
 int yield = OK;
 int rc;
+#ifdef SUPPORT_TLS
+uschar * tls_errstr;
+#endif
 
 sx->conn_args.ob = ob;
 
@@ -1947,7 +1973,7 @@ sx->utf8_needed = FALSE;
 #endif
 sx->dsn_all_lasthop = TRUE;
 #if defined(SUPPORT_TLS) && defined(SUPPORT_DANE)
-sx->dane = FALSE;
+sx->conn_args.dane = FALSE;
 sx->dane_required =
   verify_check_given_host(CUSS &ob->hosts_require_dane, sx->conn_args.host) == OK;
 #endif
@@ -1997,6 +2023,9 @@ tls_out.peerdn = NULL;
 tls_out.sni = NULL;
 #endif
 tls_out.ocsp = OCSP_NOT_REQ;
+#ifdef EXPERIMENTAL_TLS_RESUME
+tls_out.resumption = 0;
+#endif
 
 /* 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
@@ -2038,15 +2067,15 @@ if (!continue_hostname)
       if(  sx->dane_required
        || verify_check_given_host(CUSS &ob->hosts_try_dane, sx->conn_args.host) == OK
        )
-       switch (rc = tlsa_lookup(sx->conn_args.host, &tlsa_dnsa, sx->dane_required))
+       switch (rc = tlsa_lookup(sx->conn_args.host, &sx->conn_args.tlsa_dnsa, sx->dane_required))
          {
-         case OK:              sx->dane = TRUE;
+         case OK:              sx->conn_args.dane = TRUE;
                                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",
-                                   rc == DEFER ? "DEFER" : "FAIL"),
+                                   rc_to_string(rc)),
                                  rc, FALSE);
 # ifndef DISABLE_EVENT
                                (void) event_raise(sx->conn_args.tblock->event_action,
@@ -2471,28 +2500,22 @@ if (  smtp_peer_options & OPTION_TLS
   else
   TLS_NEGOTIATE:
     {
-    address_item * addr;
-    uschar * errstr;
-    sx->cctx.tls_ctx = tls_client_start(sx->cctx.sock, sx->conn_args.host,
-                           sx->addrlist, sx->conn_args.tblock,
-# ifdef SUPPORT_DANE
-                            sx->dane ? &tlsa_dnsa : NULL,
-# endif
-                            &tls_out, &errstr);
-
-    if (!sx->cctx.tls_ctx)
+    if (!tls_client_start(&sx->cctx, &sx->conn_args, sx->addrlist, &tls_out, &tls_errstr))
       {
       /* TLS negotiation failed; give an error. From outside, this function may
       be called again to try in clear on a new connection, if the options permit
       it for this host. */
-      DEBUG(D_tls) debug_printf("TLS session fail: %s\n", errstr);
+#ifdef USE_GNUTLS
+  GNUTLS_CONN_FAILED:
+#endif
+      DEBUG(D_tls) debug_printf("TLS session fail: %s\n", tls_errstr);
 
 # ifdef SUPPORT_DANE
-      if (sx->dane)
+      if (sx->conn_args.dane)
         {
        log_write(0, LOG_MAIN,
          "DANE attempt failed; TLS connection to %s [%s]: %s",
-         sx->conn_args.host->name, sx->conn_args.host->address, errstr);
+         sx->conn_args.host->name, sx->conn_args.host->address, tls_errstr);
 #  ifndef DISABLE_EVENT
        (void) event_raise(sx->conn_args.tblock->event_action,
          US"dane:fail", US"validation-failure");       /* could do with better detail */
@@ -2501,7 +2524,7 @@ if (  smtp_peer_options & OPTION_TLS
 # endif
 
       errno = ERRNO_TLSFAILURE;
-      message = string_sprintf("TLS session: %s", errstr);
+      message = string_sprintf("TLS session: %s", tls_errstr);
       sx->send_quit = FALSE;
       goto TLS_FAILED;
       }
@@ -2599,7 +2622,22 @@ if (tls_out.active.sock >= 0)
 #endif
     {
     if (!smtp_reap_ehlo(sx))
+#ifdef USE_GNUTLS
+      {
+      /* The GnuTLS layer in Exim only spots a server-rejection of a client
+      cert late, under TLS1.3 - which means here; the first time we try to
+      receive crypted data.  Treat it as if it was a connect-time failure.
+      See also the early-pipe equivalent... which will be hard; every call
+      to sync_responses will need to check the result.
+      It would be nicer to have GnuTLS check the cert during the handshake.
+      Can it do that, with all the flexibility we need? */
+
+      tls_errstr = US"error on first read";
+      goto GNUTLS_CONN_FAILED;
+      }
+#else
       goto RESPONSE_FAILED;
+#endif
     smtp_peer_options = 0;
     }
   }
@@ -2609,24 +2647,17 @@ have one. */
 
 else if (  sx->smtps
 # ifdef SUPPORT_DANE
-       || sx->dane
-# endif
-# ifdef EXPERIMENTAL_REQUIRETLS
-       || tls_requiretls & REQUIRETLS_MSG
+       || sx->conn_args.dane
 # endif
        || verify_check_given_host(CUSS &ob->hosts_require_tls, sx->conn_args.host) == OK
        )
   {
-  errno =
-# ifdef EXPERIMENTAL_REQUIRETLS
-      tls_requiretls & REQUIRETLS_MSG ? ERRNO_REQUIRETLS :
-# endif
-      ERRNO_TLSREQUIRED;
+  errno = ERRNO_TLSREQUIRED;
   message = string_sprintf("a TLS session is required, but %s",
     smtp_peer_options & OPTION_TLS
     ? "an attempt to start TLS failed" : "the server did not offer TLS support");
 # if defined(SUPPORT_DANE) && !defined(DISABLE_EVENT)
-  if (sx->dane)
+  if (sx->conn_args.dane)
     (void) event_raise(sx->conn_args.tblock->event_action, US"dane:fail",
       smtp_peer_options & OPTION_TLS
       ? US"validation-failure"         /* could do with better detail */
@@ -2658,7 +2689,7 @@ if (continue_hostname == NULL
 #ifdef EXPERIMENTAL_PIPE_CONNECT
        | (sx->lmtp && ob->lmtp_ignore_quota ? OPTION_IGNQ : 0)
        | OPTION_DSN | OPTION_PIPE | OPTION_SIZE
-       | OPTION_CHUNKING | OPTION_PRDR | OPTION_UTF8 | OPTION_REQUIRETLS
+       | OPTION_CHUNKING | OPTION_PRDR | OPTION_UTF8
        | (tls_out.active.sock >= 0 ? OPTION_EARLY_PIPE : 0) /* not for lmtp */
 
 #else
@@ -2674,9 +2705,6 @@ if (continue_hostname == NULL
        | OPTION_DSN
        | OPTION_PIPE
        | (ob->size_addition >= 0 ? OPTION_SIZE : 0)
-# if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
-       | (tls_requiretls & REQUIRETLS_MSG ? OPTION_REQUIRETLS : 0)
-# endif
 #endif
       );
 #ifdef EXPERIMENTAL_PIPE_CONNECT
@@ -2727,16 +2755,6 @@ if (continue_hostname == NULL
     DEBUG(D_transport) debug_printf("%susing DSN\n",
                        sx->peer_offered & OPTION_DSN ? "" : "not ");
 
-#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
-    if (sx->peer_offered & OPTION_REQUIRETLS)
-      {
-      smtp_peer_options |= OPTION_REQUIRETLS;
-      DEBUG(D_transport) debug_printf(
-       tls_requiretls & REQUIRETLS_MSG
-       ? "using REQUIRETLS\n" : "REQUIRETLS offered\n");
-      }
-#endif
-
 #ifdef EXPERIMENTAL_PIPE_CONNECT
     if (  sx->early_pipe_ok
        && !sx->early_pipe_active
@@ -2822,22 +2840,6 @@ if (sx->utf8_needed && !(sx->peer_offered & OPTION_UTF8))
   }
 #endif /*SUPPORT_I18N*/
 
-#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
-  /*XXX should tls_requiretls actually be per-addr? */
-
-if (  tls_requiretls & REQUIRETLS_MSG
-   && !(sx->peer_offered & OPTION_REQUIRETLS)
-   )
-  {
-  sx->setting_up = TRUE;
-  errno = ERRNO_REQUIRETLS;
-  message = US"REQUIRETLS support is required from the server"
-    " but it was not offered";
-  DEBUG(D_transport) debug_printf("%s\n", message);
-  goto TLS_FAILED;
-  }
-#endif
-
 return OK;
 
 
@@ -2874,13 +2876,7 @@ return OK;
 
 #ifdef SUPPORT_TLS
   TLS_FAILED:
-# ifdef EXPERIMENTAL_REQUIRETLS
-    if (errno == ERRNO_REQUIRETLS)
-      code = '5', yield = FAIL;
-      /*XXX DSN will be labelled 500; prefer 530 5.7.4 */
-    else
-# endif
-      code = '4', yield = DEFER;
+    code = '4', yield = DEFER;
     goto FAILED;
 #endif
 
@@ -3018,11 +3014,6 @@ if (  sx->peer_offered & OPTION_UTF8
   Ustrcpy(p, " SMTPUTF8"), p += 9;
 #endif
 
-#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
-if (tls_requiretls & REQUIRETLS_MSG)
-  Ustrcpy(p, " REQUIRETLS") , p += 11;
-#endif
-
 /* check if all addresses have DSN-lasthop flag; do not send RET and ENVID if so */
 for (sx->dsn_all_lasthop = TRUE, addr = addrlist, address_count = 0;
      addr && address_count < sx->max_rcpt;
@@ -3258,6 +3249,7 @@ for (addr = sx->first_addr, address_count = 0;
 
 #ifdef EXPERIMENTAL_PIPE_CONNECT
       case -4: return -1;                      /* non-2xx for pipelined banner or EHLO */
+      case -5: return -1;                      /* TLS first-read error */
 #endif
       }
     sx->pending_MAIL = FALSE;            /* Dealt with MAIL */
@@ -3433,7 +3425,6 @@ smtp_deliver(address_item *addrlist, host_item *host, int host_af, int defport,
   uschar *interface, transport_instance *tblock,
   BOOL *message_defer, BOOL suppress_tls)
 {
-address_item *addr;
 smtp_transport_options_block * ob = SOB tblock->options_block;
 int yield = OK;
 int save_errno;
@@ -3584,11 +3575,12 @@ if (  !(sx.peer_offered & OPTION_CHUNKING)
 
     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 0: break;                     /* No 2xx or 5xx, but no probs */
 
-    case -1: goto END_OFF;               /* Timeout on RCPT */
+    case -1: goto END_OFF;             /* Timeout on RCPT */
 
 #ifdef EXPERIMENTAL_PIPE_CONNECT
+    case -5:                           /* TLS first-read error */
     case -4:  HDEBUG(D_transport)
                debug_printf("failed reaping pipelined cmd responses\n");
 #endif
@@ -3725,19 +3717,20 @@ else
       {
       case 3: sx.ok = TRUE;            /* 2xx & 5xx => OK & progress made */
       case 2: sx.completed_addr = TRUE;    /* 5xx (only) => progress made */
-      break;
+             break;
 
-      case 1: sx.ok = TRUE;            /* 2xx (only) => OK, but if LMTP, */
+      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 0: break;                   /* No 2xx or 5xx, but no probs */
 
-      case -1: goto END_OFF;               /* Timeout on RCPT */
+      case -1: goto END_OFF;           /* Timeout on RCPT */
 
 #ifdef EXPERIMENTAL_PIPE_CONNECT
+      case -5:                         /* TLS first-read error */
       case -4:  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 */
       }
     }
 
@@ -4496,14 +4489,13 @@ int hosts_retry = 0;
 int hosts_serial = 0;
 int hosts_total = 0;
 int total_hosts_tried = 0;
-address_item *addr;
 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;
+host_item *host = NULL;
 
 DEBUG(D_transport)
   {
@@ -4534,12 +4526,6 @@ same one in order to be passed to a single transport - or if the transport has
 a host list with hosts_override set, use the host list supplied with the
 transport. It is an error for this not to exist. */
 
-#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
-if (tls_requiretls & REQUIRETLS_MSG)
-  ob->tls_tempfail_tryclear = FALSE;   /*XXX surely we should have a local for this
-                                       rather than modifying the transport? */
-#endif
-
 if (!hostlist || (ob->hosts_override && ob->hosts))
   {
   if (!ob->hosts)
@@ -4712,7 +4698,6 @@ retry_non_continued:
     {
     int rc;
     int host_af;
-    uschar *rs;
     BOOL host_is_expired = FALSE;
     BOOL message_defer = FALSE;
     BOOL some_deferred = FALSE;
@@ -4900,11 +4885,14 @@ retry_non_continued:
     treated separately. */
 
     host_af = Ustrchr(host->address, ':') == NULL ? AF_INET : AF_INET6;
-    if ((rs = ob->interface) && *rs)
       {
-      if (!smtp_get_interface(rs, host_af, addrlist, &interface, tid))
-       return FALSE;
-      pistring = string_sprintf("%s/%s", pistring, interface);
+      uschar * s = ob->interface;
+      if (s && *s)
+       {
+       if (!smtp_get_interface(s, host_af, addrlist, &interface, tid))
+         return FALSE;
+       pistring = string_sprintf("%s/%s", pistring, interface);
+       }
       }
 
     /* The first time round the outer loop, check the status of the host by
@@ -5149,14 +5137,9 @@ retry_non_continued:
 
     /* Delivery attempt finished */
 
-    rs = rc == OK ? US"OK"
-       : rc == DEFER ? US"DEFER"
-       : rc == ERROR ? US"ERROR"
-       : US"?";
-
     set_process_info("delivering %s: just tried %s [%s]%s for %s%s: result %s",
       message_id, host->name, host->address, pistring, addrlist->address,
-      addrlist->next ? " (& others)" : "", rs);
+      addrlist->next ? " (& others)" : "", rc_to_string(rc));
 
     /* Release serialization if set up */