Tidy input buffer handling
[exim.git] / src / src / transports / smtp.c
index e7e03213e1685f0a4cc4590bc5d7311d94182075..ee07bcfe87dbd2155045c70c7d58559cac69443a 100644 (file)
@@ -3,7 +3,7 @@
 *************************************************/
 
 /* Copyright (c) University of Cambridge 1995 - 2018 */
-/* Copyright (c) The Exim Maintainers 2020 */
+/* Copyright (c) The Exim Maintainers 2020 - 2021 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "../exim.h"
@@ -84,6 +84,7 @@ optionlist smtp_transport_options[] = {
 #if !defined(DISABLE_TLS) && !defined(DISABLE_OCSP)
   { "hosts_request_ocsp",   opt_stringptr, LOFF(hosts_request_ocsp) },
 #endif
+  { "hosts_require_alpn",   opt_stringptr, LOFF(hosts_require_alpn) },
   { "hosts_require_auth",   opt_stringptr, LOFF(hosts_require_auth) },
 #ifndef DISABLE_TLS
 # ifdef SUPPORT_DANE
@@ -123,6 +124,7 @@ optionlist smtp_transport_options[] = {
   { "socks_proxy",          opt_stringptr, LOFF(socks_proxy) },
 #endif
 #ifndef DISABLE_TLS
+  { "tls_alpn",             opt_stringptr, LOFF(tls_alpn) },
   { "tls_certificate",      opt_stringptr, LOFF(tls_certificate) },
   { "tls_crl",              opt_stringptr, LOFF(tls_crl) },
   { "tls_dh_min_bits",      opt_int,      LOFF(tls_dh_min_bits) },
@@ -192,9 +194,7 @@ smtp_transport_options_block smtp_transport_option_defaults = {
   .keepalive =                 TRUE,
   .retry_include_ip_address =  TRUE,
 #ifndef DISABLE_TLS
-# if defined(SUPPORT_SYSDEFAULT_CABUNDLE) || !defined(USE_GNUTLS)
   .tls_verify_certificates =   US"system",
-# endif
   .tls_dh_min_bits =           EXIM_CLIENT_DH_DEFAULT_MIN_BITS,
   .tls_tempfail_tryclear =     TRUE,
   .tls_try_verify_hosts =      US"*",
@@ -237,48 +237,39 @@ static unsigned ehlo_response(uschar * buf, unsigned checks);
 void
 smtp_deliver_init(void)
 {
-if (!regex_PIPELINING) regex_PIPELINING =
-  regex_must_compile(US"\\n250[\\s\\-]PIPELINING(\\s|\\n|$)", FALSE, TRUE);
-
-if (!regex_SIZE) regex_SIZE =
-  regex_must_compile(US"\\n250[\\s\\-]SIZE(\\s|\\n|$)", FALSE, TRUE);
-
-if (!regex_AUTH) regex_AUTH =
-  regex_must_compile(AUTHS_REGEX, FALSE, TRUE);
+struct list
+  {
+  const pcre2_code **  re;
+  const uschar *       string;
+  } list[] =
+  {
+    { &regex_AUTH,             AUTHS_REGEX },
+    { &regex_CHUNKING,         US"\\n250[\\s\\-]CHUNKING(\\s|\\n|$)" },
+    { &regex_DSN,              US"\\n250[\\s\\-]DSN(\\s|\\n|$)" },
+    { &regex_IGNOREQUOTA,      US"\\n250[\\s\\-]IGNOREQUOTA(\\s|\\n|$)" },
+    { &regex_PIPELINING,       US"\\n250[\\s\\-]PIPELINING(\\s|\\n|$)" },
+    { &regex_SIZE,             US"\\n250[\\s\\-]SIZE(\\s|\\n|$)" },
 
 #ifndef DISABLE_TLS
-if (!regex_STARTTLS) regex_STARTTLS =
-  regex_must_compile(US"\\n250[\\s\\-]STARTTLS(\\s|\\n|$)", FALSE, TRUE);
+    { &regex_STARTTLS,         US"\\n250[\\s\\-]STARTTLS(\\s|\\n|$)" },
 #endif
-
-if (!regex_CHUNKING) regex_CHUNKING =
-  regex_must_compile(US"\\n250[\\s\\-]CHUNKING(\\s|\\n|$)", FALSE, TRUE);
-
 #ifndef DISABLE_PRDR
-if (!regex_PRDR) regex_PRDR =
-  regex_must_compile(US"\\n250[\\s\\-]PRDR(\\s|\\n|$)", FALSE, TRUE);
+    { &regex_PRDR,             US"\\n250[\\s\\-]PRDR(\\s|\\n|$)" },
 #endif
-
 #ifdef SUPPORT_I18N
-if (!regex_UTF8) regex_UTF8 =
-  regex_must_compile(US"\\n250[\\s\\-]SMTPUTF8(\\s|\\n|$)", FALSE, TRUE);
+    { &regex_UTF8,             US"\\n250[\\s\\-]SMTPUTF8(\\s|\\n|$)" },
 #endif
-
-if (!regex_DSN) regex_DSN  =
-  regex_must_compile(US"\\n250[\\s\\-]DSN(\\s|\\n|$)", FALSE, TRUE);
-
-if (!regex_IGNOREQUOTA) regex_IGNOREQUOTA =
-  regex_must_compile(US"\\n250[\\s\\-]IGNOREQUOTA(\\s|\\n|$)", FALSE, TRUE);
-
 #ifndef DISABLE_PIPE_CONNECT
-if (!regex_EARLY_PIPE) regex_EARLY_PIPE =
-  regex_must_compile(US"\\n250[\\s\\-]" EARLY_PIPE_FEATURE_NAME "(\\s|\\n|$)", FALSE, TRUE);
+    { &regex_EARLY_PIPE,       US"\\n250[\\s\\-]" EARLY_PIPE_FEATURE_NAME "(\\s|\\n|$)" },
 #endif
-
 #ifdef EXPERIMENTAL_ESMTP_LIMITS
-if (!regex_LIMITS) regex_LIMITS =
-  regex_must_compile(US"\\n250[\\s\\-]LIMITS\\s", FALSE, TRUE);
+    { &regex_LIMITS,           US"\\n250[\\s\\-]LIMITS\\s" },
 #endif
+  };
+
+for (struct list * l = list; l < list + nelem(list); l++)
+  if (!*l->re)
+    *l->re = regex_must_compile(l->string, FALSE, TRUE);
 }
 
 
@@ -355,6 +346,7 @@ void
 smtp_transport_init(transport_instance *tblock)
 {
 smtp_transport_options_block *ob = SOB tblock->options_block;
+int old_pool = store_pool;
 
 /* Retry_use_local_part defaults FALSE if unset */
 
@@ -390,7 +382,9 @@ if (ob->hosts_override && ob->hosts) tblock->overrides_hosts = TRUE;
 /* If there are any fallback hosts listed, build a chain of host items
 for them, but do not do any lookups at this time. */
 
-host_build_hostlist(&(ob->fallback_hostlist), ob->fallback_hosts, FALSE);
+store_pool = POOL_PERM;
+host_build_hostlist(&ob->fallback_hostlist, ob->fallback_hosts, FALSE);
+store_pool = old_pool;
 }
 
 
@@ -722,9 +716,11 @@ BOOL good_response;
   {    /* Hack to get QUICKACK disabled; has to be right after 3whs, and has to on->off */
   int sock = sx->cctx.sock;
   struct pollfd p = {.fd = sock, .events = POLLOUT};
-  int rc = poll(&p, 1, 1000);
-  (void) setsockopt(sock, IPPROTO_TCP, TCP_QUICKACK, US &on, sizeof(on));
-  (void) setsockopt(sock, IPPROTO_TCP, TCP_QUICKACK, US &off, sizeof(off));
+  if (poll(&p, 1, 1000) >= 0)  /* retval test solely for compiler quitening */
+    {
+    (void) setsockopt(sock, IPPROTO_TCP, TCP_QUICKACK, US &on, sizeof(on));
+    (void) setsockopt(sock, IPPROTO_TCP, TCP_QUICKACK, US &off, sizeof(off));
+    }
   }
 #endif
 good_response = smtp_read_response(sx, sx->buffer, sizeof(sx->buffer),
@@ -774,13 +770,12 @@ This saves us dealing with a duplicate set of values. */
 static void
 ehlo_response_limits_read(smtp_context * sx)
 {
-int ovec[3];   /* results vector for a main-match only */
+uschar * match;
 
 /* matches up to just after the first space after the keyword */
 
-if (pcre_exec(regex_LIMITS, NULL, CS sx->buffer, Ustrlen(sx->buffer),
-             0, PCRE_EOPT, ovec, nelem(ovec)) >= 0)
-  for (const uschar * s = sx->buffer + ovec[1]; *s; )
+if (regex_match(regex_LIMITS, sx->buffer, -1, &match))
+  for (const uschar * s = sx->buffer + Ustrlen(match); *s; )
     {
     while (isspace(*s)) s++;
     if (*s == '\n') break;
@@ -853,9 +848,9 @@ return Ustrchr(host->address, ':')
 /* Cache EHLO-response info for use by early-pipe.
 Called
 - During a normal flow on EHLO response (either cleartext or under TLS),
-  when we are willing to do PIPE_CONNECT and it is offered
+  when we are willing to do PIPECONNECT and it is offered
 - During an early-pipe flow on receiving the actual EHLO response and noting
-  disparity versus the cached info used, when PIPE_CONNECT is still being offered
+  disparity versus the cached info used, when PIPECONNECT is still being offered
 
 We assume that suitable values have been set in the sx.ehlo_resp structure for
 features and auths; we handle the copy of limits. */
@@ -1806,57 +1801,65 @@ return Ustrcmp(current_local_identity, message_local_identity) == 0;
 static unsigned
 ehlo_response(uschar * buf, unsigned checks)
 {
-size_t bsize = Ustrlen(buf);
+PCRE2_SIZE bsize = Ustrlen(buf);
+pcre2_match_data * md = pcre2_match_data_create(1, pcre_gen_ctx);
 
 /* debug_printf("%s: check for 0x%04x\n", __FUNCTION__, checks); */
 
 #ifndef DISABLE_TLS
 if (  checks & OPTION_TLS
-   && pcre_exec(regex_STARTTLS, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
+   && pcre2_match(regex_STARTTLS,
+                 (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0)
 #endif
   checks &= ~OPTION_TLS;
 
 if (  checks & OPTION_IGNQ
-   && pcre_exec(regex_IGNOREQUOTA, NULL, CS buf, bsize, 0,
-               PCRE_EOPT, NULL, 0) < 0)
+   && pcre2_match(regex_IGNOREQUOTA,
+                 (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0)
   checks &= ~OPTION_IGNQ;
 
 if (  checks & OPTION_CHUNKING
-   && pcre_exec(regex_CHUNKING, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
+   && pcre2_match(regex_CHUNKING,
+                 (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0)
   checks &= ~OPTION_CHUNKING;
 
 #ifndef DISABLE_PRDR
 if (  checks & OPTION_PRDR
-   && pcre_exec(regex_PRDR, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
+   && pcre2_match(regex_PRDR,
+                 (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0)
 #endif
   checks &= ~OPTION_PRDR;
 
 #ifdef SUPPORT_I18N
 if (  checks & OPTION_UTF8
-   && pcre_exec(regex_UTF8, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
+   && pcre2_match(regex_UTF8,
+                 (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0)
 #endif
   checks &= ~OPTION_UTF8;
 
 if (  checks & OPTION_DSN
-   && pcre_exec(regex_DSN, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
+   && pcre2_match(regex_DSN,
+                 (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0)
   checks &= ~OPTION_DSN;
 
 if (  checks & OPTION_PIPE
-   && pcre_exec(regex_PIPELINING, NULL, CS buf, bsize, 0,
-               PCRE_EOPT, NULL, 0) < 0)
+   && pcre2_match(regex_PIPELINING,
+                 (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0)
   checks &= ~OPTION_PIPE;
 
 if (  checks & OPTION_SIZE
-   && pcre_exec(regex_SIZE, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
+   && pcre2_match(regex_SIZE,
+                 (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0)
   checks &= ~OPTION_SIZE;
 
 #ifndef DISABLE_PIPE_CONNECT
 if (  checks & OPTION_EARLY_PIPE
-   && pcre_exec(regex_EARLY_PIPE, NULL, CS buf, bsize, 0,
-               PCRE_EOPT, NULL, 0) < 0)
+   && pcre2_match(regex_EARLY_PIPE,
+                 (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0)
 #endif
   checks &= ~OPTION_EARLY_PIPE;
 
+pcre2_match_data_free(md);
 /* debug_printf("%s: found     0x%04x\n", __FUNCTION__, checks); */
 return checks;
 }
@@ -2118,7 +2121,7 @@ if (continue_hostname && continue_proxy_cipher)
       {
       case OK:         sx->conn_args.dane = TRUE;
                        ob->tls_tempfail_tryclear = FALSE;      /* force TLS */
-                       ob->tls_sni = sx->first_addr->domain;   /* force SNI */
+                        ob->tls_sni = sx->conn_args.host->name; /* force SNI */
                        break;
       case FAIL_FORCED:        break;
       default:         set_errno_nohost(sx->addrlist, ERRNO_DNSDEFER,
@@ -2205,7 +2208,7 @@ if (!continue_hostname)
          {
          case OK:              sx->conn_args.dane = TRUE;
                                ob->tls_tempfail_tryclear = FALSE;      /* force TLS */
-                               ob->tls_sni = sx->first_addr->domain;   /* force SNI */
+                               ob->tls_sni = sx->conn_args.host->name; /* force SNI */
                                break;
          case FAIL_FORCED:     break;
          default:              set_errno_nohost(sx->addrlist, ERRNO_DNSDEFER,
@@ -2260,7 +2263,7 @@ if (!continue_hostname)
         && sx->ehlo_resp.cleartext_features & OPTION_EARLY_PIPE)
        {
        DEBUG(D_transport)
-         debug_printf("Using cached cleartext PIPE_CONNECT\n");
+         debug_printf("Using cached cleartext PIPECONNECT\n");
        sx->early_pipe_active = TRUE;
        sx->peer_offered = sx->ehlo_resp.cleartext_features;
        }
@@ -2475,8 +2478,8 @@ goto SEND_QUIT;
       int n = sizeof(sx->buffer);
       uschar * rsp = sx->buffer;
 
-      if (sx->esmtp_sent && (n = Ustrlen(sx->buffer)) < sizeof(sx->buffer)/2)
-       { rsp = sx->buffer + n + 1; n = sizeof(sx->buffer) - n; }
+      if (sx->esmtp_sent && (n = Ustrlen(sx->buffer) + 1) < sizeof(sx->buffer)/2)
+       { rsp = sx->buffer + n; n = sizeof(sx->buffer) - n; }
 
       if (smtp_write_command(sx, SCMD_FLUSH, "HELO %s\r\n", sx->helo_data) < 0)
        goto SEND_FAILED;
@@ -2762,7 +2765,7 @@ if (tls_out.active.sock >= 0)
     sx->peer_offered = sx->ehlo_resp.crypted_features;
     if ((sx->early_pipe_active =
         !!(sx->ehlo_resp.crypted_features & OPTION_EARLY_PIPE)))
-      DEBUG(D_transport) debug_printf("Using cached crypted PIPE_CONNECT\n");
+      DEBUG(D_transport) debug_printf("Using cached crypted PIPECONNECT\n");
     }
 #endif
 #ifdef EXPERIMMENTAL_ESMTP_LIMITS
@@ -2961,7 +2964,7 @@ if (   !continue_hostname
        && ( sx->ehlo_resp.cleartext_features | sx->ehlo_resp.crypted_features)
          & OPTION_EARLY_PIPE)
       {
-      DEBUG(D_transport) debug_printf("PIPE_CONNECT usable in future for this IP\n");
+      DEBUG(D_transport) debug_printf("PIPECONNECT usable in future for this IP\n");
       sx->ehlo_resp.crypted_auths = study_ehlo_auths(sx);
       write_ehlo_cache_entry(sx);
       }
@@ -3076,7 +3079,7 @@ return OK;
 
   SEND_FAILED:
     code = '4';
-    message = US string_sprintf("send() to %s [%s] failed: %s",
+    message = US string_sprintf("smtp send to %s [%s] failed: %s",
       sx->conn_args.host->name, sx->conn_args.host->address, strerror(errno));
     sx->send_quit = FALSE;
     yield = DEFER;
@@ -3547,8 +3550,8 @@ void
 smtp_proxy_tls(void * ct_ctx, uschar * buf, size_t bsize, int * pfd,
   int timeout)
 {
-fd_set rfds, efds;
-int max_fd = MAX(pfd[0], tls_out.active.sock) + 1;
+struct pollfd p[2] = {{.fd = tls_out.active.sock, .events = POLLIN},
+                     {.fd = pfd[0], .events = POLLIN}};
 int rc, i;
 BOOL send_tls_shutdown = TRUE;
 
@@ -3557,23 +3560,16 @@ if ((rc = exim_fork(US"tls-proxy")))
   _exit(rc < 0 ? EXIT_FAILURE : EXIT_SUCCESS);
 
 set_process_info("proxying TLS connection for continued transport");
-FD_ZERO(&rfds);
-FD_SET(tls_out.active.sock, &rfds);
-FD_SET(pfd[0], &rfds);
 
-for (int fd_bits = 3; fd_bits; )
+do
   {
   time_t time_left = timeout;
   time_t time_start = time(NULL);
 
   /* wait for data */
-  efds = rfds;
   do
     {
-    struct timeval tv = { time_left, 0 };
-
-    rc = select(max_fd,
-      (SELECT_ARG2_TYPE *)&rfds, NULL, (SELECT_ARG2_TYPE *)&efds, &tv);
+    rc = poll(p, 2, time_left * 1000);
 
     if (rc < 0 && errno == EINTR)
       if ((time_left -= time(NULL) - time_start) > 0) continue;
@@ -3586,23 +3582,22 @@ for (int fd_bits = 3; fd_bits; )
 
     /* For errors where not readable, bomb out */
 
-    if (FD_ISSET(tls_out.active.sock, &efds) || FD_ISSET(pfd[0], &efds))
+    if (p[0].revents & POLLERR || p[1].revents & POLLERR)
       {
       DEBUG(D_transport) debug_printf("select: exceptional cond on %s fd\n",
-       FD_ISSET(pfd[0], &efds) ? "proxy" : "tls");
-      if (!(FD_ISSET(tls_out.active.sock, &rfds) || FD_ISSET(pfd[0], &rfds)))
+       p[0].revents & POLLERR ? "tls" : "proxy");
+      if (!(p[0].revents & POLLIN || p[1].events & POLLIN))
        goto done;
       DEBUG(D_transport) debug_printf("- but also readable; no exit yet\n");
       }
     }
-  while (rc < 0 || !(FD_ISSET(tls_out.active.sock, &rfds) || FD_ISSET(pfd[0], &rfds)));
+  while (rc < 0 || !(p[0].revents & POLLIN || p[1].revents & POLLIN));
 
   /* handle inbound data */
-  if (FD_ISSET(tls_out.active.sock, &rfds))
+  if (p[0].revents & POLLIN)
     if ((rc = tls_read(ct_ctx, buf, bsize)) <= 0)      /* Expect -1 for EOF; */
     {                              /* that reaps the TLS Close Notify record */
-      fd_bits &= ~1;
-      FD_CLR(tls_out.active.sock, &rfds);
+      p[0].fd = -1;
       shutdown(pfd[0], SHUT_WR);
       timeout = 5;
       }
@@ -3613,11 +3608,10 @@ for (int fd_bits = 3; fd_bits; )
   /* Handle outbound data.  We cannot combine payload and the TLS-close
   due to the limitations of the (pipe) channel feeding us.  Maybe use a unix-domain
   socket? */
-  if (FD_ISSET(pfd[0], &rfds))
+  if (p[1].revents & POLLIN)
     if ((rc = read(pfd[0], buf, bsize)) <= 0)
       {
-      fd_bits &= ~2;
-      FD_CLR(pfd[0], &rfds);
+      p[1].fd = -1;
 
 # ifdef EXIM_TCP_CORK  /* Use _CORK to get TLS Close Notify in FIN segment */
       (void) setsockopt(tls_out.active.sock, IPPROTO_TCP, EXIM_TCP_CORK, US &on, sizeof(on));
@@ -3630,10 +3624,8 @@ for (int fd_bits = 3; fd_bits; )
       for (int nbytes = 0; rc - nbytes > 0; nbytes += i)
        if ((i = tls_write(ct_ctx, buf + nbytes, rc - nbytes, FALSE)) < 0)
          goto done;
-
-  if (fd_bits & 1) FD_SET(tls_out.active.sock, &rfds);
-  if (fd_bits & 2) FD_SET(pfd[0], &rfds);
   }
+while (p[0].fd >= 0 || p[1].fd >= 0);
 
 done:
   if (send_tls_shutdown) tls_close(ct_ctx, TLS_SHUTDOWN_NOWAIT);
@@ -4099,7 +4091,7 @@ else
       sx->send_quit = FALSE;   /* avoid sending it later */
 
 #ifndef DISABLE_TLS
-      if (sx->cctx.tls_ctx)    /* need to send TLS Cloe Notify */
+      if (sx->cctx.tls_ctx)    /* need to send TLS Close Notify */
        {
 # ifdef EXIM_TCP_CORK          /* Use _CORK to get Close Notify in FIN segment */
        (void) setsockopt(sx->cctx.sock, IPPROTO_TCP, EXIM_TCP_CORK, US &on, sizeof(on));
@@ -4357,6 +4349,7 @@ else
              "%s: %s", sx->buffer, strerror(errno));
          }
        else if (addr->transport_return == DEFER)
+         /*XXX magic value -2 ? maybe host+message ? */
          retry_add_item(addr, addr->address_retry_key, -2);
       }
 #endif
@@ -4397,7 +4390,7 @@ if (!sx->ok)
     {
     save_errno = errno;
     code = '4';
-    message = string_sprintf("send() to %s [%s] failed: %s",
+    message = string_sprintf("smtp send to %s [%s] failed: %s",
       host->name, host->address, message ? message : US strerror(save_errno));
     sx->send_quit = FALSE;
     goto FAILED;
@@ -4433,6 +4426,21 @@ if (!sx->ok)
        message_error = Ustrncmp(smtp_command,"end ",4) == 0;
        break;
 
+#ifndef DISABLE_DKIM
+      case EACCES:
+       /* DKIM signing failure: avoid thinking we pipelined quit,
+       just abandon the message and close the socket. */
+
+       message_error = FALSE;
+# ifndef DISABLE_TLS
+       if (sx->cctx.tls_ctx)
+         {
+         tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_WAIT);
+         sx->cctx.tls_ctx = NULL;
+         }
+# endif
+       break;
+#endif
       default:
        message_error = FALSE;
        break;
@@ -4581,7 +4589,7 @@ if (sx->completed_addr && sx->ok && sx->send_quit)
       if (sx->send_rset)
        if (! (sx->ok = smtp_write_command(sx, SCMD_FLUSH, "RSET\r\n") >= 0))
          {
-         msg = US string_sprintf("send() to %s [%s] failed: %s", host->name,
+         msg = US string_sprintf("smtp send to %s [%s] failed: %s", host->name,
            host->address, strerror(errno));
          sx->send_quit = FALSE;
          }
@@ -5092,11 +5100,8 @@ if (!hostlist || (ob->hosts_override && ob->hosts))
     else
       if (ob->hosts_randomize) s = expanded_hosts = string_copy(s);
 
-    if (is_tainted(s))
+    if (is_tainted2(s, LOG_MAIN|LOG_PANIC, "Tainted host list '%s' from '%s' in transport %s", s, ob->hosts, tblock->name))
       {
-      log_write(0, LOG_MAIN|LOG_PANIC,
-       "attempt to use tainted host list '%s' from '%s' in transport %s",
-       s, ob->hosts, tblock->name);
       /* Avoid leaking info to an attacker */
       addrlist->message = US"internal configuration error";
       addrlist->transport_return = PANIC;