Logging: better tracking of continued-connection use
[exim.git] / src / src / transports / smtp.c
index 0f3ade9fd58e91b6c0c39e867b14548c4e2bb331..2a2928c46b32e7f53e61bf5b6ed24e5c34f7350c 100644 (file)
@@ -43,7 +43,7 @@ optionlist smtp_transport_options[] = {
   { "dane_require_tls_ciphers", opt_stringptr, LOFF(dane_require_tls_ciphers) },
 # endif
   { "data_timeout",         opt_time,     LOFF(data_timeout) },
-  { "delay_after_cutoff", opt_bool,       LOFF(delay_after_cutoff) },
+  { "delay_after_cutoff",   opt_bool,     LOFF(delay_after_cutoff) },
 #ifndef DISABLE_DKIM
   { "dkim_canon", opt_stringptr,          LOFF(dkim.dkim_canon) },
   { "dkim_domain", opt_stringptr,         LOFF(dkim.dkim_domain) },
@@ -163,23 +163,12 @@ void smtp_transport_closedown(transport_instance *tblock) {}
 /* Default private options block for the smtp transport. */
 
 smtp_transport_options_block smtp_transport_option_defaults = {
-  .hosts =                     NULL,
-  .fallback_hosts =            NULL,
-  .hostlist =                  NULL,
-  .fallback_hostlist =         NULL,
+  /* All non-mentioned elements 0/NULL/FALSE */
   .helo_data =                 US"$primary_hostname",
-  .interface =                 NULL,
-  .port =                      NULL,
   .protocol =                  US"smtp",
-  .dscp =                      NULL,
-  .serialize_hosts =           NULL,
-  .hosts_try_auth =            NULL,
-  .hosts_require_auth =                NULL,
   .hosts_try_chunking =                US"*",
 #ifdef SUPPORT_DANE
   .hosts_try_dane =            US"*",
-  .hosts_require_dane =                NULL,
-  .dane_require_tls_ciphers =  NULL,
 #endif
   .hosts_try_fastopen =                US"*",
 #ifndef DISABLE_PRDR
@@ -187,19 +176,6 @@ smtp_transport_options_block smtp_transport_option_defaults = {
 #endif
 #ifndef DISABLE_OCSP
   .hosts_request_ocsp =                US"*",               /* hosts_request_ocsp (except under DANE; tls_client_start()) */
-  .hosts_require_ocsp =                NULL,
-#endif
-  .hosts_require_tls =         NULL,
-  .hosts_avoid_tls =           NULL,
-  .hosts_verify_avoid_tls =    NULL,
-  .hosts_avoid_pipelining =    NULL,
-#ifndef DISABLE_PIPE_CONNECT
-  .hosts_pipe_connect =                NULL,
-#endif
-  .hosts_avoid_esmtp =         NULL,
-#ifndef DISABLE_TLS
-  .hosts_nopass_tls =          NULL,
-  .hosts_noproxy_tls =         NULL,
 #endif
   .command_timeout =           5*60,
   .connect_timeout =           5*60,
@@ -210,35 +186,17 @@ smtp_transport_options_block smtp_transport_option_defaults = {
   .hosts_max_try_hardlimit =   50,
   .message_linelength_limit =  998,
   .address_retry_include_sender = TRUE,
-  .allow_localhost =           FALSE,
-  .authenticated_sender_force =        FALSE,
-  .gethostbyname =             FALSE,
   .dns_qualify_single =                TRUE,
-  .dns_search_parents =                FALSE,
   .dnssec = { .request= US"*", .require=NULL },
   .delay_after_cutoff =                TRUE,
-  .hosts_override =            FALSE,
-  .hosts_randomize =           FALSE,
   .keepalive =                 TRUE,
-  .lmtp_ignore_quota =         FALSE,
-  .expand_retry_include_ip_address =   NULL,
   .retry_include_ip_address =  TRUE,
-#ifdef SUPPORT_SOCKS
-  .socks_proxy =               NULL,
-#endif
 #ifndef DISABLE_TLS
-  .tls_certificate =           NULL,
-  .tls_crl =                   NULL,
-  .tls_privatekey =            NULL,
-  .tls_require_ciphers =       NULL,
-  .tls_sni =                   NULL,
+# 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,
-# ifndef DISABLE_TLS_RESUME
-  .tls_resumption_hosts =      NULL,
-# endif
-  .tls_verify_hosts =          NULL,
   .tls_try_verify_hosts =      US"*",
   .tls_verify_cert_hostnames = US"*",
 #endif
@@ -247,24 +205,7 @@ smtp_transport_options_block smtp_transport_option_defaults = {
 #endif
 #ifndef DISABLE_DKIM
  .dkim =
-   {.dkim_domain =             NULL,
-    .dkim_identity =           NULL,
-    .dkim_private_key =                NULL,
-    .dkim_selector =           NULL,
-    .dkim_canon =              NULL,
-    .dkim_sign_headers =       NULL,
-    .dkim_strict =             NULL,
-    .dkim_hash =               US"sha256",
-    .dkim_timestamps =         NULL,
-    .dot_stuffed =             FALSE,
-    .force_bodyhash =          FALSE,
-# ifdef EXPERIMENTAL_ARC
-    .arc_signspec =            NULL,
-# endif
-    },
-# ifdef EXPERIMENTAL_ARC
-  .arc_sign =                  NULL,
-# endif
+   { .dkim_hash =              US"sha256", },
 #endif
 };
 
@@ -439,7 +380,7 @@ if (ob->command_timeout <= 0 || ob->data_timeout <= 0 ||
 /* If hosts_override is set and there are local hosts, set the global
 flag that stops verify from showing router hosts. */
 
-if (ob->hosts_override && ob->hosts != NULL) tblock->overrides_hosts = TRUE;
+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. */
@@ -771,7 +712,17 @@ return count;
 static BOOL
 smtp_reap_banner(smtp_context * sx)
 {
-BOOL good_response = smtp_read_response(sx, sx->buffer, sizeof(sx->buffer),
+BOOL good_response;
+#if defined(__linux__) && defined(TCP_QUICKACK)
+  {    /* 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));
+  }
+#endif
+good_response = smtp_read_response(sx, sx->buffer, sizeof(sx->buffer),
   '2', (SOB sx->conn_args.ob)->command_timeout);
 #ifdef EXPERIMENTAL_DSN_INFO
 sx->smtp_greeting = string_copy(sx->buffer);
@@ -864,7 +815,7 @@ else
   uschar * ehlo_resp_key = ehlo_cache_key(sx);
   dbdata_ehlo_resp * er;
 
-  if (!(er = dbfn_read(dbm_file, ehlo_resp_key)))
+  if (!(er = dbfn_read_enforce_length(dbm_file, ehlo_resp_key, sizeof(dbdata_ehlo_resp))))
     { DEBUG(D_transport) debug_printf("no ehlo-resp record\n"); }
   else if (time(NULL) - er->time_stamp > retry_data_expire)
     {
@@ -1018,7 +969,7 @@ fail:
   (void) smtp_discard_responses(sx, sx->conn_args.ob, *countp);
   return rc;
 }
-#endif
+#endif /*!DISABLE_PIPE_CONNECT*/
 
 
 /*************************************************
@@ -1085,7 +1036,7 @@ if (sx->pending_MAIL)
   {
   DEBUG(D_transport) debug_printf("%s expect mail\n", __FUNCTION__);
   count--;
-  sx->pending_MAIL = FALSE;
+  sx->pending_MAIL = sx->RCPT_452 = FALSE;
   if (!smtp_read_response(sx, sx->buffer, sizeof(sx->buffer),
                          '2', ob->command_timeout))
     {
@@ -1131,7 +1082,7 @@ while (count-- > 0)
   /* The address was accepted */
   addr->host_used = sx->conn_args.host;
 
-  DEBUG(D_transport) debug_printf("%s expect rcpt\n", __FUNCTION__);
+  DEBUG(D_transport) debug_printf("%s expect rcpt for %s\n", __FUNCTION__, addr->address);
   if (smtp_read_response(sx, sx->buffer, sizeof(sx->buffer),
                          '2', ob->command_timeout))
     {
@@ -1225,7 +1176,7 @@ while (count-- > 0)
 
        if (addr->more_errno >> 8 == 52  &&  yield & 3)
          {
-         if (!sx->RCPT_452)
+         if (!sx->RCPT_452)            /* initialised at MAIL-ack above */
            {
            DEBUG(D_transport)
              debug_printf("%s: seen first 452 too-many-rcpts\n", __FUNCTION__);
@@ -1272,6 +1223,8 @@ while (count-- > 0)
        }
       }
     }
+  if (count && !(addr = addr->next))
+    return -2;
   }       /* Loop for next RCPT response */
 
 /* Update where to start at for the next block of responses, unless we
@@ -1547,7 +1500,9 @@ if (  sx->esmtp
 
 if (require_auth == OK && !f.smtp_authenticated)
   {
+#ifndef DISABLE_PIPE_CONNECT
   invalidate_ehlo_cache_entry(sx);
+#endif
   set_errno_nohost(sx->addrlist, ERRNO_AUTHFAIL,
     string_sprintf("authentication required but %s", fail_reason), DEFER,
     FALSE, &sx->delivery_start);
@@ -1618,8 +1573,8 @@ return FALSE;
 
 typedef struct smtp_compare_s
 {
-    uschar                          *current_sender_address;
-    struct transport_instance       *tblock;
+    uschar *                   current_sender_address;
+    struct transport_instance *        tblock;
 } smtp_compare_t;
 
 
@@ -1682,8 +1637,9 @@ uschar * message_local_identity,
 current_local_identity =
   smtp_local_identity(s_compare->current_sender_address, s_compare->tblock);
 
-if (!(new_sender_address = deliver_get_sender_address(message_id)))
-    return FALSE;
+if (!(new_sender_address = spool_sender_from_msgid(message_id)))
+  return FALSE;
+
 
 message_local_identity =
   smtp_local_identity(new_sender_address, s_compare->tblock);
@@ -1871,15 +1827,13 @@ return OK;
 
 
 
-
-
 /*************************************************
 *       Make connection for given message        *
 *************************************************/
 
 /*
 Arguments:
-  ctx            connection context
+  sx             connection context
   suppress_tls    if TRUE, don't attempt a TLS connection - this is set for
                     a second attempt after TLS initialization fails
 
@@ -1987,7 +1941,80 @@ if (sx->smtps)
            DEFER, FALSE, &sx->delivery_start);
   return ERROR;
   }
-#endif
+#else
+
+/* If we have a proxied TLS connection, check usability for this message */
+
+if (continue_hostname && continue_proxy_cipher)
+  {
+  int rc;
+  const uschar * sni = US"";
+
+# ifdef SUPPORT_DANE
+  /* Check if the message will be DANE-verified; if so force its SNI */
+
+  tls_out.dane_verified = FALSE;
+  smtp_port_for_connect(sx->conn_args.host, sx->port);
+  if (  sx->conn_args.host->dnssec == DS_YES
+     && (  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, &sx->conn_args.tlsa_dnsa, sx->dane_required))
+      {
+      case OK:         sx->conn_args.dane = TRUE;
+                       ob->tls_tempfail_tryclear = FALSE;      /* force TLS */
+                       ob->tls_sni = sx->first_addr->domain;   /* force SNI */
+                       break;
+      case FAIL_FORCED:        break;
+      default:         set_errno_nohost(sx->addrlist, ERRNO_DNSDEFER,
+                             string_sprintf("DANE error: tlsa lookup %s",
+                               rc_to_string(rc)),
+                             rc, FALSE, &sx->delivery_start);
+#  ifndef DISABLE_EVENT
+                           (void) event_raise(sx->conn_args.tblock->event_action,
+                             US"dane:fail", sx->dane_required
+                               ?  US"dane-required" : US"dnssec-invalid");
+#  endif
+                           return rc;
+      }
+# endif
+
+  /* If the SNI or the DANE status required for the new message differs from the
+  existing conn drop the connection to force a new one. */
+
+  if (ob->tls_sni && !(sni = expand_cstring(ob->tls_sni)))
+    log_write(0, LOG_MAIN|LOG_PANIC,
+      "<%s>: failed to expand transport's tls_sni value: %s",
+      sx->addrlist->address, expand_string_message);
+
+# ifdef SUPPORT_DANE
+  if (  (continue_proxy_sni ? (Ustrcmp(continue_proxy_sni, sni) == 0) : !*sni)
+     && continue_proxy_dane == sx->conn_args.dane)
+    {
+    tls_out.sni = US sni;
+    if ((tls_out.dane_verified = continue_proxy_dane))
+      sx->conn_args.host->dnssec = DS_YES;
+    }
+# else
+  if ((continue_proxy_sni ? (Ustrcmp(continue_proxy_sni, sni) == 0) : !*sni))
+    tls_out.sni = US sni;
+# endif
+  else
+    {
+    DEBUG(D_transport)
+      debug_printf("Closing proxied-TLS connection due to SNI mismatch\n");
+
+    HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP>> QUIT\n");
+    write(0, "QUIT\r\n", 6);
+    close(0);
+    continue_hostname = continue_proxy_cipher = NULL;
+    f.continue_more = FALSE;
+    continue_sequence = 1;     /* Unfortunately, this process cannot affect success log
+                               which is done by delivery proc.  Would have to pass this
+                               back through reporting pipe. */
+    }
+  }
+#endif /*!DISABLE_TLS*/
 
 /* Make a connection to the host if this isn't a continued delivery, and handle
 the initial interaction and HELO/EHLO/LHLO. Connect timeout errors are handled
@@ -1998,6 +2025,11 @@ if (!continue_hostname)
   if (sx->verify)
     HDEBUG(D_verify) debug_printf("interface=%s port=%d\n", sx->conn_args.interface, sx->port);
 
+  /* Arrange to report to calling process this is a new connection */
+
+  clearflag(sx->first_addr, af_cont_conn);
+  setflag(sx->first_addr, af_new_conn);
+
   /* Get the actual port the connection will use, into sx->conn_args.host */
 
   smtp_port_for_connect(sx->conn_args.host, sx->port);
@@ -2017,7 +2049,8 @@ if (!continue_hostname)
        switch (rc = tlsa_lookup(sx->conn_args.host, &sx->conn_args.tlsa_dnsa, sx->dane_required))
          {
          case OK:              sx->conn_args.dane = TRUE;
-                               ob->tls_tempfail_tryclear = FALSE;
+                               ob->tls_tempfail_tryclear = FALSE;      /* force TLS */
+                               ob->tls_sni = sx->first_addr->domain;   /* force SNI */
                                break;
          case FAIL_FORCED:     break;
          default:              set_errno_nohost(sx->addrlist, ERRNO_DNSDEFER,
@@ -2083,7 +2116,12 @@ PIPE_CONNECT_RETRY:
   else
 #endif
     {
-    if ((sx->cctx.sock = smtp_connect(&sx->conn_args, NULL)) < 0)
+    blob lazy_conn = {.data = NULL};
+    /* For TLS-connect, a TFO lazy-connect is useful since the Client Hello
+    can go on the TCP SYN. */
+
+    if ((sx->cctx.sock = smtp_connect(&sx->conn_args,
+                           sx->smtps ? &lazy_conn : NULL)) < 0)
       {
       set_errno_nohost(sx->addrlist,
        errno == ETIMEDOUT ? ERRNO_CONNECTTIMEOUT : errno,
@@ -2092,6 +2130,10 @@ PIPE_CONNECT_RETRY:
       sx->send_quit = FALSE;
       return DEFER;
       }
+#ifdef TCP_QUICKACK
+    (void) setsockopt(sx->cctx.sock, IPPROTO_TCP, TCP_QUICKACK, US &off,
+                       sizeof(off));
+#endif
     }
   /* Expand the greeting message while waiting for the initial response. (Makes
   sense if helo_data contains ${lookup dnsdb ...} stuff). The expansion is
@@ -2137,10 +2179,6 @@ will be?  Somehow I doubt it. */
     else
 #endif
       {
-#ifdef TCP_QUICKACK
-      (void) setsockopt(sx->cctx.sock, IPPROTO_TCP, TCP_QUICKACK, US &off,
-                       sizeof(off));
-#endif
       if (!smtp_reap_banner(sx))
        goto RESPONSE_FAILED;
       }
@@ -2461,9 +2499,7 @@ if (  smtp_peer_options & OPTION_TLS
       /* 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. */
-#ifdef USE_GNUTLS
-  GNUTLS_CONN_FAILED:
-#endif
+  TLS_CONN_FAILED:
       DEBUG(D_tls) debug_printf("TLS session fail: %s\n", tls_errstr);
 
 # ifdef SUPPORT_DANE
@@ -2484,8 +2520,25 @@ if (  smtp_peer_options & OPTION_TLS
       sx->send_quit = FALSE;
       goto TLS_FAILED;
       }
+    sx->send_tlsclose = TRUE;
 
-    /* TLS session is set up */
+    /* TLS session is set up.  Check the inblock fill level.  If there is
+    content then as we have not yet done a tls read it must have arrived before
+    the TLS handshake, in-clear.  That violates the sync requirement of the
+    STARTTLS RFC, so fail. */
+
+    if (sx->inblock.ptr != sx->inblock.ptrend)
+      {
+      DEBUG(D_tls)
+       {
+       int i = sx->inblock.ptrend - sx->inblock.ptr;
+       debug_printf("unused data in input buffer after ack for STARTTLS:\n"
+         "'%.*s'%s\n",
+         i > 100 ? 100 : i, sx->inblock.ptr, i > 100 ? "..." : "");
+       }
+      tls_errstr = US"synch error before connect";
+      goto TLS_CONN_FAILED;
+      }
 
     smtp_peer_options_wrap = smtp_peer_options;
     for (address_item * addr = sx->addrlist; addr; addr = addr->next)
@@ -2590,7 +2643,7 @@ if (tls_out.active.sock >= 0)
       Can it do that, with all the flexibility we need? */
 
       tls_errstr = US"error on first read";
-      goto GNUTLS_CONN_FAILED;
+      goto TLS_CONN_FAILED;
       }
 #else
       goto RESPONSE_FAILED;
@@ -2629,7 +2682,7 @@ so its response needs to be analyzed. If TLS is not active and this is a
 continued session down a previously-used socket, we haven't just done EHLO, so
 we skip this. */
 
-if (continue_hostname == NULL
+if (   !continue_hostname
 #ifndef DISABLE_TLS
     || tls_out.active.sock >= 0
 #endif
@@ -2901,7 +2954,11 @@ if (sx->send_quit)
 #ifndef DISABLE_TLS
 if (sx->cctx.tls_ctx)
   {
-  tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT);
+  if (sx->send_tlsclose)
+    {
+    tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT);
+    sx->send_tlsclose = FALSE;
+    }
   sx->cctx.tls_ctx = NULL;
   }
 #endif
@@ -3169,7 +3226,7 @@ that max_rcpt will be large, so all addresses will be done at once.
 For verify we flush the pipeline after any (the only) rcpt address. */
 
 for (addr = sx->first_addr, address_count = 0, pipe_limit = 100;
-     addr  &&  address_count < sx->max_rcpt;
+     addr &&  address_count < sx->max_rcpt;
      addr = addr->next) if (addr->transport_return == PENDING_DEFER)
   {
   int cmds_sent;
@@ -3280,6 +3337,7 @@ smtp_proxy_tls(void * ct_ctx, uschar * buf, size_t bsize, int * pfd,
 fd_set rfds, efds;
 int max_fd = MAX(pfd[0], tls_out.active.sock) + 1;
 int rc, i;
+BOOL send_tls_shutdown = TRUE;
 
 close(pfd[1]);
 if ((rc = exim_fork(US"tls-proxy")))
@@ -3313,19 +3371,23 @@ for (int fd_bits = 3; fd_bits; )
       goto done;
       }
 
+    /* For errors where not readable, bomb out */
+
     if (FD_ISSET(tls_out.active.sock, &efds) || FD_ISSET(pfd[0], &efds))
       {
       DEBUG(D_transport) debug_printf("select: exceptional cond on %s fd\n",
        FD_ISSET(pfd[0], &efds) ? "proxy" : "tls");
-      goto done;
+      if (!(FD_ISSET(tls_out.active.sock, &rfds) || FD_ISSET(pfd[0], &rfds)))
+       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)));
 
   /* handle inbound data */
   if (FD_ISSET(tls_out.active.sock, &rfds))
-    if ((rc = tls_read(ct_ctx, buf, bsize)) <= 0)
-      {
+    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);
       shutdown(pfd[0], SHUT_WR);
@@ -3336,16 +3398,21 @@ for (int fd_bits = 3; fd_bits; )
       for (int nbytes = 0; rc - nbytes > 0; nbytes += i)
        if ((i = write(pfd[0], buf + nbytes, rc - nbytes)) < 0) goto done;
       }
-  else if (fd_bits & 1)
-    FD_SET(tls_out.active.sock, &rfds);
 
-  /* handle outbound data */
+  /* Handle outbound data.  We cannot combine payload and the TLS-close
+  due to the limitations of the (pipe) channel feeding us. */
   if (FD_ISSET(pfd[0], &rfds))
     if ((rc = read(pfd[0], buf, bsize)) <= 0)
       {
-      fd_bits = 0;
-      tls_close(ct_ctx, TLS_SHUTDOWN_NOWAIT);
-      ct_ctx = NULL;
+      fd_bits &= ~2;
+      FD_CLR(pfd[0], &rfds);
+
+# 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));
+# endif
+      tls_shutdown_wr(ct_ctx);
+      send_tls_shutdown = FALSE;
+      shutdown(tls_out.active.sock, SHUT_WR);
       }
     else
       {
@@ -3353,11 +3420,14 @@ for (int fd_bits = 3; fd_bits; )
        if ((i = tls_write(ct_ctx, buf + nbytes, rc - nbytes, FALSE)) < 0)
          goto done;
       }
-  else if (fd_bits & 2)
-    FD_SET(pfd[0], &rfds);
+
+  if (fd_bits & 1) FD_SET(tls_out.active.sock, &rfds);
+  if (fd_bits & 2) FD_SET(pfd[0], &rfds);
   }
 
 done:
+  if (send_tls_shutdown) tls_close(ct_ctx, TLS_SHUTDOWN_NOWAIT);
+  ct_ctx = NULL;
   testharness_pause_ms(100);   /* let logging complete */
   exim_exit(EXIT_SUCCESS);
 }
@@ -3427,8 +3497,11 @@ BOOL pass_message = FALSE;
 uschar *message = NULL;
 uschar new_message_id[MESSAGE_ID_LENGTH + 1];
 smtp_context * sx = store_get(sizeof(*sx), TRUE);      /* tainted, for the data buffers */
+#ifdef SUPPORT_DANE
+BOOL dane_held;
+#endif
+BOOL tcw_done = FALSE, tcw = FALSE;
 
-suppress_tls = suppress_tls;  /* stop compiler warning when no TLS support */
 *message_defer = FALSE;
 
 memset(sx, 0, sizeof(*sx));
@@ -3443,15 +3516,41 @@ sx->conn_args.tblock = tblock;
 gettimeofday(&sx->delivery_start, NULL);
 sx->sync_addr = sx->first_addr = addrlist;
 
-/* Get the channel set up ready for a message (MAIL FROM being the next
-SMTP command to send */
+#ifdef SUPPORT_DANE
+DANE_DOMAINS:
+dane_held = FALSE;
+#endif
+
+/* Get the channel set up ready for a message, MAIL FROM being the next
+SMTP command to send. */
 
 if ((rc = smtp_setup_conn(sx, suppress_tls)) != OK)
   {
   timesince(&addrlist->delivery_time, &sx->delivery_start);
-  return rc;
+  yield = rc;
+  goto TIDYUP;
   }
 
+#ifdef SUPPORT_DANE
+/* If the connection used DANE, ignore for now any addresses with incompatible
+domains.  The SNI has to be the domain.  Arrange a whole new TCP conn later,
+just in case only TLS isn't enough. */
+
+if (sx->conn_args.dane)
+  {
+  const uschar * dane_domain = sx->first_addr->domain;
+
+  for (address_item * a = sx->first_addr->next; a; a = a->next)
+    if (  a->transport_return == PENDING_DEFER
+       && Ustrcmp(dane_domain, a->domain) != 0)
+      {
+      DEBUG(D_transport) debug_printf("DANE: holding %s for later\n", a->domain);
+      dane_held = TRUE;
+      a->transport_return = DANE;
+      }
+  }
+#endif
+
 /* If there is a filter command specified for this transport, we can now
 set it up. This cannot be done until the identity of the host is known. */
 
@@ -3691,6 +3790,46 @@ else
   report_time_since(&t0, US"dkim_exim_sign_init (delta)");
 # endif
   }
+#endif
+
+  /* See if we can pipeline QUIT.  Reasons not to are
+  - pipelining not active
+  - not ok to send quit
+  - errors in amtp transation responses
+  - more addrs to send for this message or this host
+  - this message was being retried
+  - more messages for this host
+  If we can, we want the message-write to not flush (the tail end of) its data out.  */
+
+  if (  sx->pipelining_used
+     && (sx->ok && sx->completed_addr || sx->peer_offered & OPTION_CHUNKING)
+     && sx->send_quit
+     && !(sx->first_addr || f.continue_more)
+     && f.deliver_firsttime
+     )
+    {
+    smtp_compare_t t_compare =
+      {.tblock = tblock, .current_sender_address = sender_address};
+
+    tcw_done = TRUE;
+    tcw =
+#ifndef DISABLE_TLS
+          (  tls_out.active.sock < 0  &&  !continue_proxy_cipher
+           || verify_check_given_host(CUSS &ob->hosts_nopass_tls, host) != OK
+          )
+        &&
+#endif
+           transport_check_waiting(tblock->name, host->name,
+             tblock->connection_max_messages, new_message_id,
+            (oicf)smtp_are_same_identities, (void*)&t_compare);
+    if (!tcw)
+      {
+      HDEBUG(D_transport) debug_printf("will pipeline QUIT\n");
+      tctx.options |= topt_no_flush;
+      }
+    }
+
+#ifndef DISABLE_DKIM
   sx->ok = dkim_transport_write_message(&tctx, &ob->dkim, CUSS &message);
 #else
   sx->ok = transport_write_message(&tctx, 0);
@@ -3719,6 +3858,48 @@ else
 
   smtp_command = US"end of data";
 
+  /* If we can pipeline a QUIT with the data them send it now.  If a new message
+  for this host appeared in the queue while data was being sent, we will not see
+  it and it will have to wait for a queue run.  If there was one but another
+  thread took it, we might attempt to send it - but locking of spoolfiles will
+  detect that. Use _MORE to get QUIT in FIN segment. */
+
+  if (tcw_done && !tcw)
+    {
+    /*XXX jgh 2021/03/10 google et. al screwup.  G, at least, sends TCP FIN in response to TLS
+    close-notify.  Under TLS 1.3, violating RFC.
+    However, TLS 1.2 does not have half-close semantics. */
+
+    if (     sx->cctx.tls_ctx
+#if 0 && !defined(DISABLE_TLS)
+          && Ustrcmp(tls_out.ver, "TLS1.3") != 0
+#endif
+       || !f.deliver_firsttime
+       )
+      {                                /* Send QUIT now and not later */
+      (void)smtp_write_command(sx, SCMD_FLUSH, "QUIT\r\n");
+      sx->send_quit = FALSE;
+      }
+    else
+      {                                /* add QUIT to the output buffer */
+      (void)smtp_write_command(sx, SCMD_MORE, "QUIT\r\n");
+      sx->send_quit = FALSE;   /* avoid sending it later */
+
+#ifndef DISABLE_TLS
+      if (sx->cctx.tls_ctx)    /* need to send TLS Cloe 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));
+# endif
+       tls_shutdown_wr(sx->cctx.tls_ctx);
+       sx->send_tlsclose = FALSE;      /* avoid later repeat */
+       }
+#endif
+      HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP(shutdown)>>\n");
+      shutdown(sx->cctx.sock, SHUT_WR);        /* flush output buffer, with TCP FIN */
+      }
+    }
+
   if (sx->peer_offered & OPTION_CHUNKING && sx->cmd_count > 1)
     {
     /* Reap any outstanding MAIL & RCPT commands, but not a DATA-go-ahead */
@@ -3811,15 +3992,16 @@ else
           !sx->lmtp
        )
       {
-      const uschar *s = string_printing(sx->buffer);
+      const uschar * s = string_printing(sx->buffer);
       /* deconst cast ok here as string_printing was checked to have alloc'n'copied */
-      conf = (s == sx->buffer)? US string_copy(s) : US s;
+      conf = s == sx->buffer ? US string_copy(s) : US s;
       }
 
     /* Process all transported addresses - for LMTP or PRDR, read a status for
-    each one. */
+    each one. We used to drop out at first_addr, until someone returned a 452
+    followed by a 250... and we screwed up the accepted addresses. */
 
-    for (address_item * addr = addrlist; addr != sx->first_addr; addr = addr->next)
+    for (address_item * addr = addrlist; addr; addr = addr->next)
       {
       if (addr->transport_return != PENDING_OK) continue;
 
@@ -3992,7 +4174,8 @@ if (!sx->ok)
     {
     save_errno = errno;
     message = NULL;
-    sx->send_quit = check_response(host, &save_errno, addrlist->more_errno,
+    /* Clear send_quit flag if needed.  Do not set. */
+    sx->send_quit &= check_response(host, &save_errno, addrlist->more_errno,
       sx->buffer, &code, &message, &pass_message);
     goto FAILED;
     }
@@ -4077,8 +4260,15 @@ if (!sx->ok)
 
         *message_defer = TRUE;
         }
+#ifdef TIOCOUTQ
+      DEBUG(D_transport) if (sx->cctx.sock >= 0)
+       {
+       int n;
+       if (ioctl(sx->cctx.sock, TIOCOUTQ, &n) == 0)
+         debug_printf("%d bytes remain in socket output buffer\n", n);
+       }
+#endif
       }
-
     /* Otherwise, we have an I/O error or a timeout other than after MAIL or
     ".", or some other transportation error. We defer all addresses and yield
     DEFER, except for the case of failed add_headers expansion, or a transport
@@ -4146,14 +4336,12 @@ DEBUG(D_transport)
 
 if (sx->completed_addr && sx->ok && sx->send_quit)
   {
-  BOOL more;
-  smtp_compare_t t_compare;
+  smtp_compare_t t_compare =
+    {.tblock = tblock, .current_sender_address = sender_address};
 
-  t_compare.tblock = tblock;
-  t_compare.current_sender_address = sender_address;
-
-  if (  sx->first_addr != NULL
-     || f.continue_more
+  if (  sx->first_addr                 /* more addrs for this message */
+     || f.continue_more                        /* more addrs for continued-host */
+     || tcw_done && tcw                        /* more messages for host */
      || (
 #ifndef DISABLE_TLS
           (  tls_out.active.sock < 0  &&  !continue_proxy_cipher
@@ -4162,7 +4350,7 @@ if (sx->completed_addr && sx->ok && sx->send_quit)
         &&
 #endif
            transport_check_waiting(tblock->name, host->name,
-             tblock->connection_max_messages, new_message_id, &more,
+             tblock->connection_max_messages, new_message_id,
             (oicf)smtp_are_same_identities, (void*)&t_compare)
      )  )
     {
@@ -4198,10 +4386,11 @@ if (sx->completed_addr && sx->ok && sx->send_quit)
 #endif
       int socket_fd = sx->cctx.sock;
 
-
-      if (sx->first_addr != NULL)      /* More addresses still to be sent */
-        {                              /*   in this run of the transport */
-        continue_sequence++;           /* Causes * in logging */
+      if (sx->first_addr)              /* More addresses still to be sent */
+        {                              /*   for this message              */
+        continue_sequence++;                   /* for consistency */
+        clearflag(sx->first_addr, af_new_conn);
+        setflag(sx->first_addr, af_cont_conn); /* Causes * in logging */
        pipelining_active = sx->pipelining_used;    /* was cleared at DATA */
         goto SEND_MESSAGE;
         }
@@ -4223,7 +4412,9 @@ if (sx->completed_addr && sx->ok && sx->send_quit)
          the socket on. */
 
          tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_WAIT);
+         sx->send_tlsclose = FALSE;
          sx->cctx.tls_ctx = NULL;
+         tls_out.active.sock = -1;
          smtp_peer_options = smtp_peer_options_wrap;
          sx->ok = !sx->smtps
            && smtp_write_command(sx, SCMD_FLUSH, "EHLO %s\r\n", sx->helo_data)
@@ -4232,7 +4423,7 @@ if (sx->completed_addr && sx->ok && sx->send_quit)
                                      '2', ob->command_timeout);
 
          if (sx->ok && f.continue_more)
-           return yield;               /* More addresses for another run */
+           goto TIDYUP;                /* More addresses for another run */
          }
        else
          {
@@ -4252,7 +4443,7 @@ if (sx->completed_addr && sx->ok && sx->send_quit)
       else
 #endif
        if (f.continue_more)
-         return yield;                 /* More addresses for another run */
+         goto TIDYUP;                  /* More addresses for another run */
 
       /* If the socket is successfully passed, we mustn't send QUIT (or
       indeed anything!) from here. */
@@ -4292,7 +4483,7 @@ propagate it from the initial
            sx->cctx.sock = -1;
            continue_transport = NULL;
            continue_hostname = NULL;
-           return yield;
+           goto TIDYUP;
            }
          log_write(0, LOG_PANIC_DIE, "fork failed");
          }
@@ -4311,36 +4502,26 @@ propagate it from the initial
   }
 
 /* End off tidily with QUIT unless the connection has died or the socket has
-been passed to another process. There has been discussion on the net about what
-to do after sending QUIT. The wording of the RFC suggests that it is necessary
-to wait for a response, but on the other hand, there isn't anything one can do
-with an error response, other than log it. Exim used to do that. However,
-further discussion suggested that it is positively advantageous not to wait for
-the response, but to close the session immediately. This is supposed to move
-the TCP/IP TIME_WAIT state from the server to the client, thereby removing some
-load from the server. (Hosts that are both servers and clients may not see much
-difference, of course.) Further discussion indicated that this was safe to do
-on Unix systems which have decent implementations of TCP/IP that leave the
-connection around for a while (TIME_WAIT) after the application has gone away.
-This enables the response sent by the server to be properly ACKed rather than
-timed out, as can happen on broken TCP/IP implementations on other OS.
-
-This change is being made on 31-Jul-98. After over a year of trouble-free
-operation, the old commented-out code was removed on 17-Sep-99. */
+been passed to another process. */
 
 SEND_QUIT:
-#ifdef TCP_CORK
-(void) setsockopt(sx->cctx.sock, IPPROTO_TCP, TCP_CORK, US &on, sizeof(on));
+if (sx->send_quit)
+  {                    /* Use _MORE to get QUIT in FIN segment */
+  (void)smtp_write_command(sx, SCMD_MORE, "QUIT\r\n");
+#ifndef DISABLE_TLS
+  if (sx->cctx.tls_ctx)
+    {
+# ifdef EXIM_TCP_CORK  /* Use _CORK to get TLS Close Notify in FIN segment */
+    (void) setsockopt(sx->cctx.sock, IPPROTO_TCP, EXIM_TCP_CORK, US &on, sizeof(on));
+# endif
+    tls_shutdown_wr(sx->cctx.tls_ctx);
+    sx->send_tlsclose = FALSE;
+    }
 #endif
-if (sx->send_quit) (void)smtp_write_command(sx, SCMD_FLUSH, "QUIT\r\n");
+  }
 
 END_OFF:
 
-#ifndef DISABLE_TLS
-tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT);
-sx->cctx.tls_ctx = NULL;
-#endif
-
 /* Close the socket, and return the appropriate value, first setting
 works because the NULL setting is passed back to the calling process, and
 remote_max_parallel is forced to 1 when delivering over an existing connection,
@@ -4351,25 +4532,102 @@ writing RSET might have failed, or there may be other addresses whose hosts are
 specified in the transports, and therefore not visible at top level, in which
 case continue_more won't get set. */
 
-HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP(close)>>\n");
 if (sx->send_quit)
   {
+  /* This flushes data queued in the socket, being the QUIT and any TLS Close,
+  sending them along with the client FIN flag.  Us (we hope) sending FIN first
+  means we (client) take the TIME_WAIT state, so the server (which likely has a
+  higher connection rate) does not have to. */
+
+  HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP(shutdown)>>\n");
   shutdown(sx->cctx.sock, SHUT_WR);
+  }
+
+if (sx->send_quit || tcw_done && !tcw)
+  {
+  /* Wait for (we hope) ack of our QUIT, and a server FIN.  Discard any data
+  received, then discard the socket.  Any packet received after then, or receive
+  data still in the socket, will get a RST - hence the pause/drain. */
+
+  /* Reap the response to QUIT, timing out after one second */
+  (void) smtp_read_response(sx, sx->buffer, sizeof(sx->buffer), '2', 1);
+#ifndef DISABLE_TLS
+  if (sx->cctx.tls_ctx)
+    {
+    int n;
+
+    /* Reap the TLS Close Notify from the server, timing out after one second */
+    sigalrm_seen = FALSE;
+    ALARM(1);
+    do
+      n = tls_read(sx->cctx.tls_ctx, sx->inbuffer, sizeof(sx->inbuffer));
+    while (!sigalrm_seen && n > 0);
+    ALARM_CLR(0);
+
+# ifdef EXIM_TCP_CORK
+    (void) setsockopt(sx->cctx.sock, IPPROTO_TCP, EXIM_TCP_CORK, US &on, sizeof(on));
+# endif
+    tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_WAIT);
+    sx->cctx.tls_ctx = NULL;
+    }
+#endif
   millisleep(20);
-  testharness_pause_ms(200);
   if (fcntl(sx->cctx.sock, F_SETFL, O_NONBLOCK) == 0)
-    for (int i = 16; read(sx->cctx.sock, sx->inbuffer, sizeof(sx->inbuffer)) > 0 && i > 0;)
-      i--;                             /* drain socket */
+    for (int i = 16, n;                                                /* drain socket */
+        (n = read(sx->cctx.sock, sx->inbuffer, sizeof(sx->inbuffer))) > 0 && i > 0;
+        i--) HDEBUG(D_transport|D_acl|D_v)
+      {
+      int m = MIN(n, 64);
+      debug_printf_indent("  SMTP(drain %d bytes)<< %.*s\n", n, m, sx->inbuffer);
+      for (m = 0; m < n; m++)
+       debug_printf("0x%02x\n", sx->inbuffer[m]);
+      }
   }
+HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP(close)>>\n");
 (void)close(sx->cctx.sock);
+sx->cctx.sock = -1;
+continue_transport = NULL;
+continue_hostname = NULL;
 
 #ifndef DISABLE_EVENT
 (void) event_raise(tblock->event_action, US"tcp:close", NULL);
 #endif
 
+#ifdef SUPPORT_DANE
+if (dane_held)
+  {
+  sx->first_addr = NULL;
+  for (address_item * a = sx->addrlist->next; a; a = a->next)
+    if (a->transport_return == DANE)
+      {
+      a->transport_return = PENDING_DEFER;
+      if (!sx->first_addr)
+       {
+       /* Remember the new start-point in the addrlist, for smtp_setup_conn()
+       to get the domain string for SNI */
+
+       sx->first_addr = a;
+       clearflag(a, af_cont_conn);
+       setflag(a, af_new_conn);                /* clear * from logging */
+       DEBUG(D_transport) debug_printf("DANE: go-around for %s\n", a->domain);
+       }
+      }
+  continue_sequence = 1;                       /* for consistency */
+  goto DANE_DOMAINS;
+  }
+#endif
+
 continue_transport = NULL;
 continue_hostname = NULL;
 return yield;
+
+TIDYUP:
+#ifdef SUPPORT_DANE
+if (dane_held) for (address_item * a = sx->addrlist->next; a; a = a->next)
+  if (a->transport_return == DANE)
+    a->transport_return = PENDING_DEFER;
+#endif
+return yield;
 }
 
 
@@ -4575,7 +4833,7 @@ if (!hostlist || (ob->hosts_override && ob->hosts))
     {
     uschar *s = ob->hosts;
 
-    if (Ustrchr(s, '$') != NULL)
+    if (Ustrchr(s, '$'))
       {
       if (!(expanded_hosts = expand_string(s)))
         {
@@ -4923,7 +5181,7 @@ retry_non_continued:
     because connections to the same host from a different interface should be
     treated separately. */
 
-    host_af = Ustrchr(host->address, ':') == NULL ? AF_INET : AF_INET6;
+    host_af = Ustrchr(host->address, ':') ? AF_INET6 : AF_INET;
       {
       uschar * s = ob->interface;
       if (s && *s)