Fix handling of server which follows a RCPT 452 with a 250. Bug 26092
[exim.git] / src / src / transports / smtp.c
index 447f76a9b5c9dddcbbb9c387b793e7218fcecba9..33051a5e2bac25cde2a8fead4e0aa5801311170f 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
 };
 
@@ -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)
     {
@@ -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
@@ -1618,8 +1571,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;
 
 
@@ -1987,7 +1940,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
@@ -2021,8 +2047,7 @@ if (!continue_hostname)
                                ob->tls_sni = sx->first_addr->domain;   /* force SNI */
                                break;
          case FAIL_FORCED:     break;
-         default:
-         set_errno_nohost(sx->addrlist, ERRNO_DNSDEFER,
+         default:              set_errno_nohost(sx->addrlist, ERRNO_DNSDEFER,
                                  string_sprintf("DANE error: tlsa lookup %s",
                                    rc_to_string(rc)),
                                  rc, FALSE, &sx->delivery_start);
@@ -2085,7 +2110,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,
@@ -2094,6 +2124,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
@@ -2139,10 +2173,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;
       }
@@ -3443,7 +3473,9 @@ 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
 
 *message_defer = FALSE;
 
@@ -3459,8 +3491,10 @@ sx->conn_args.tblock = tblock;
 gettimeofday(&sx->delivery_start, NULL);
 sx->sync_addr = sx->first_addr = addrlist;
 
+#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. */
@@ -3472,7 +3506,7 @@ if ((rc = smtp_setup_conn(sx, suppress_tls)) != OK)
   goto TIDYUP;
   }
 
-/*XXX*/
+#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. */
@@ -3490,6 +3524,7 @@ if (sx->conn_args.dane)
       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. */
@@ -3850,15 +3885,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;
 
@@ -4116,8 +4152,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
@@ -4190,8 +4233,8 @@ if (sx->completed_addr && sx->ok && sx->send_quit)
   t_compare.tblock = tblock;
   t_compare.current_sender_address = sender_address;
 
-  if (  sx->first_addr != NULL
-     || f.continue_more
+  if (  sx->first_addr != NULL         /* more addrs for this message */
+     || f.continue_more                        /* more addrs for coninued-host */
      || (
 #ifndef DISABLE_TLS
           (  tls_out.active.sock < 0  &&  !continue_proxy_cipher
@@ -4238,7 +4281,7 @@ if (sx->completed_addr && sx->ok && sx->send_quit)
 
 
       if (sx->first_addr != NULL)      /* More addresses still to be sent */
-        {                              /*   on this connection            */
+        {                              /*   for this message              */
         continue_sequence++;           /* Causes * in logging */
        pipelining_active = sx->pipelining_used;    /* was cleared at DATA */
         goto SEND_MESSAGE;
@@ -4253,10 +4296,6 @@ if (sx->completed_addr && sx->ok && sx->send_quit)
       if (tls_out.active.sock >= 0)
        if (  f.continue_more
           || verify_check_given_host(CUSS &ob->hosts_noproxy_tls, host) == OK)
-
-/*XXX*/
-/*        || sx->conn_args.dane && Ustrcmp( , ob->tls_sni) != 0 */
-/*XXX*/
          {
          /* Before passing the socket on, or returning to caller with it still
          open, we must shut down TLS.  Not all MTAs allow for the continuation
@@ -4266,6 +4305,7 @@ if (sx->completed_addr && sx->ok && sx->send_quit)
 
          tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_WAIT);
          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)
@@ -4353,32 +4393,21 @@ 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));
-#endif
-if (sx->send_quit) (void)smtp_write_command(sx, SCMD_FLUSH, "QUIT\r\n");
+if (sx->send_quit)
+                       /* Use _MORE to get QUIT in FIN segment */
+  (void)smtp_write_command(sx, SCMD_MORE, "QUIT\r\n");
 
 END_OFF:
 
 #ifndef DISABLE_TLS
+# ifdef EXIM_TCP_CORK
+if (sx->cctx.tls_ctx)  /* 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_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT);
 sx->cctx.tls_ctx = NULL;
 #endif
@@ -4396,7 +4425,17 @@ 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 no have to. */
+
   shutdown(sx->cctx.sock, SHUT_WR);
+
+  /* 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. */
+
   millisleep(20);
   testharness_pause_ms(200);
   if (fcntl(sx->cctx.sock, F_SETFL, O_NONBLOCK) == 0)
@@ -4409,7 +4448,7 @@ if (sx->send_quit)
 (void) event_raise(tblock->event_action, US"tcp:close", NULL);
 #endif
 
-/*XXX*/
+#ifdef SUPPORT_DANE
 if (dane_held)
   {
   sx->first_addr = NULL;
@@ -4428,15 +4467,18 @@ if (dane_held)
       }
   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;
 }