DANE: Fix 2 messages from queue case
[exim.git] / src / src / transports / smtp.c
index 2216ff853302ae5021ffe00bc6fa82ef8c0e2e82..dfc1c767c1aea7e5731c0e88c1a69230d302273b 100644 (file)
@@ -3,6 +3,7 @@
 *************************************************/
 
 /* Copyright (c) University of Cambridge 1995 - 2018 */
+/* Copyright (c) The Exim Maintainers 2020 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "../exim.h"
@@ -110,6 +111,7 @@ optionlist smtp_transport_options[] = {
   { "lmtp_ignore_quota",    opt_bool,     LOFF(lmtp_ignore_quota) },
   { "max_rcpt",             opt_int | opt_public,
       OPT_OFF(transport_instance, max_addresses) },
+  { "message_linelength_limit", opt_int,   LOFF(message_linelength_limit) },
   { "multi_domain",         opt_expand_bool | opt_public,
       OPT_OFF(transport_instance, multi_domain) },
   { "port",                 opt_stringptr, LOFF(port) },
@@ -126,7 +128,7 @@ optionlist smtp_transport_options[] = {
   { "tls_dh_min_bits",      opt_int,      LOFF(tls_dh_min_bits) },
   { "tls_privatekey",       opt_stringptr, LOFF(tls_privatekey) },
   { "tls_require_ciphers",  opt_stringptr, LOFF(tls_require_ciphers) },
-# ifdef EXPERIMENTAL_TLS_RESUME
+# ifndef DISABLE_TLS_RESUME
   { "tls_resumption_hosts", opt_stringptr, LOFF(tls_resumption_hosts) },
 # endif
   { "tls_sni",              opt_stringptr, LOFF(tls_sni) },
@@ -206,6 +208,7 @@ smtp_transport_options_block smtp_transport_option_defaults = {
   .size_addition =             1024,
   .hosts_max_try =             5,
   .hosts_max_try_hardlimit =   50,
+  .message_linelength_limit =  998,
   .address_retry_include_sender = TRUE,
   .allow_localhost =           FALSE,
   .authenticated_sender_force =        FALSE,
@@ -232,7 +235,7 @@ 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
+# ifndef DISABLE_TLS_RESUME
   .tls_resumption_hosts =      NULL,
 # endif
   .tls_verify_hosts =          NULL,
@@ -240,7 +243,7 @@ smtp_transport_options_block smtp_transport_option_defaults = {
   .tls_verify_cert_hostnames = US"*",
 #endif
 #ifdef SUPPORT_I18N
-  .utf8_downconvert =          NULL,
+  .utf8_downconvert =          US"-1",
 #endif
 #ifndef DISABLE_DKIM
  .dkim =
@@ -361,10 +364,6 @@ smtp_transport_setup(transport_instance *tblock, address_item *addrlist,
 {
 smtp_transport_options_block *ob = SOB tblock->options_block;
 
-errmsg = errmsg;    /* Keep picky compilers happy */
-uid = uid;
-gid = gid;
-
 /* Pass back options if required. This interface is getting very messy. */
 
 if (tf)
@@ -911,11 +910,9 @@ names = string_copyn(expand_nstring[1], expand_nlength[1]);
 for (au = auths, authnum = 0; au; au = au->next, authnum++) if (au->client)
   {
   const uschar * list = names;
-  int sep = ' ';
-  uschar name[32];
-
-  while (string_nextinlist(&list, &sep, name, sizeof(name)))
-    if (strcmpic(au->public_name, name) == 0)
+  uschar * s;
+  for (int sep = ' '; s = string_nextinlist(&list, &sep, NULL, 0); )
+    if (strcmpic(au->public_name, s) == 0)
       { authbits |= BIT(authnum); break; }
   }
 
@@ -1550,6 +1547,7 @@ if (  sx->esmtp
 
 if (require_auth == OK && !f.smtp_authenticated)
   {
+  invalidate_ehlo_cache_entry(sx);
   set_errno_nohost(sx->addrlist, ERRNO_AUTHFAIL,
     string_sprintf("authentication required but %s", fail_reason), DEFER,
     FALSE, &sx->delivery_start);
@@ -1620,8 +1618,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;
 
 
@@ -1970,7 +1968,7 @@ tls_out.peerdn = NULL;
 tls_out.sni = NULL;
 #endif
 tls_out.ocsp = OCSP_NOT_REQ;
-#ifdef EXPERIMENTAL_TLS_RESUME
+#ifndef DISABLE_TLS_RESUME
 tls_out.resumption = 0;
 #endif
 tls_out.ver = NULL;
@@ -1991,6 +1989,75 @@ if (sx->smtps)
   }
 #endif
 
+#ifdef SUPPORT_DANE
+/*XXX new */
+/* If we have a proxied TLS connection, check usability for this message */
+
+if (continue_hostname && continue_proxy_cipher)
+  {
+  int rc;
+  const uschar * sni = US"";
+
+  /* Check if the message will be DANE-verified; if so force its SNI */
+
+  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;
+      }
+
+  /* If the SNI 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);
+
+  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
+    {
+    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);
+    tls_out.dane_verified = FALSE;
+    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
+
+
 /* 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
 specially so they can be identified for retries. */
@@ -2019,7 +2086,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,
@@ -2463,9 +2531,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
@@ -2487,7 +2553,23 @@ if (  smtp_peer_options & OPTION_TLS
       goto TLS_FAILED;
       }
 
-    /* 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)
@@ -2592,7 +2674,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;
@@ -3284,11 +3366,8 @@ int max_fd = MAX(pfd[0], tls_out.active.sock) + 1;
 int rc, i;
 
 close(pfd[1]);
-if ((rc = exim_fork(US"tls proxy")))
-  {
-  DEBUG(D_transport) debug_printf("proxy-proc final-pid %d\n", rc);
+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);
@@ -3364,7 +3443,7 @@ for (int fd_bits = 3; fd_bits; )
 
 done:
   testharness_pause_ms(100);   /* let logging complete */
-  exim_exit(0, US"TLS proxy");
+  exim_exit(EXIT_SUCCESS);
 }
 #endif
 
@@ -3432,8 +3511,10 @@ 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
 
-suppress_tls = suppress_tls;  /* stop compiler warning when no TLS support */
 *message_defer = FALSE;
 
 memset(sx, 0, sizeof(*sx));
@@ -3448,15 +3529,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. */
 
@@ -4144,6 +4251,16 @@ connection to a new process. However, not all servers can handle this (Exim
 can), so we do not pass such a connection on if the host matches
 hosts_nopass_tls. */
 
+/*XXX do we have to veto all passing of DANE'd connections?
+Can we be any more intelligent?
+
+I could see that unpleasantly impacting high-vol mailinglist.
+Where many messages are queued for a single dest MX.
+
+But the wait-DB used by transport_check_waiting only records hosts, not domains.
+So we cannot look for a domain mismatch.
+*/
+
 DEBUG(D_transport)
   debug_printf("ok=%d send_quit=%d send_rset=%d continue_more=%d "
     "yield=%d first_address is %sNULL\n", sx->ok, sx->send_quit,
@@ -4151,14 +4268,13 @@ DEBUG(D_transport)
 
 if (sx->completed_addr && sx->ok && sx->send_quit)
   {
-  BOOL more;
   smtp_compare_t t_compare;
 
   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
@@ -4167,7 +4283,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)
      )  )
     {
@@ -4205,7 +4321,7 @@ if (sx->completed_addr && sx->ok && sx->send_quit)
 
 
       if (sx->first_addr != NULL)      /* More addresses still to be sent */
-        {                              /*   in this run of the transport */
+        {                              /*   for this message              */
         continue_sequence++;           /* Causes * in logging */
        pipelining_active = sx->pipelining_used;    /* was cleared at DATA */
         goto SEND_MESSAGE;
@@ -4229,6 +4345,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)
@@ -4237,7 +4354,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
          {
@@ -4257,7 +4374,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. */
@@ -4278,7 +4395,7 @@ propagate it from the initial
 #ifndef DISABLE_TLS
        if (tls_out.active.sock >= 0)
          {
-         int pid = exim_fork(US"tls proxy interproc");
+         int pid = exim_fork(US"tls-proxy-interproc");
          if (pid == 0)         /* child; fork again to disconnect totally */
            {
            /* does not return */
@@ -4288,7 +4405,6 @@ propagate it from the initial
 
          if (pid > 0)          /* parent */
            {
-           DEBUG(D_transport) debug_printf("proxy-proc inter-pid %d\n", pid);
            close(pfd[0]);
            /* tidy the inter-proc to disconn the proxy proc */
            waitpid(pid, NULL, 0);
@@ -4298,7 +4414,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");
          }
@@ -4373,9 +4489,38 @@ if (sx->send_quit)
 (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;
+       DEBUG(D_transport) debug_printf("DANE: go-around for %s\n", a->domain);
+       }
+      }
+  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;
 }
 
 
@@ -4528,6 +4673,22 @@ DEBUG(D_transport)
       cutthrough.cctx.sock >= 0 ? cutthrough.cctx.sock : 0);
   }
 
+/* Check the restrictions on line length */
+
+if (max_received_linelength > ob->message_linelength_limit)
+  {
+  struct timeval now;
+  gettimeofday(&now, NULL);
+
+  for (address_item * addr = addrlist; addr; addr = addr->next)
+    if (addr->transport_return == DEFER)
+      addr->transport_return = PENDING_DEFER;
+
+  set_errno_nohost(addrlist, ERRNO_SMTPFORMAT,
+    US"message has lines too long for transport", FAIL, TRUE, &now);
+  goto END_TRANSPORT;
+  }
+
 /* Set the flag requesting that these hosts be added to the waiting
 database if the delivery fails temporarily or if we are running with
 queue_smtp or a 2-stage queue run. This gets unset for certain
@@ -5168,7 +5329,12 @@ retry_non_continued:
 #ifndef DISABLE_EVENT
       /* If the last host gave a defer raise a per-message event */
 
-      if (!nexthost && (message_defer || rc == DEFER))
+      if (  !(  nexthost
+            && unexpired_hosts_tried < ob->hosts_max_try
+            && total_hosts_tried < ob->hosts_max_try_hardlimit
+            )
+         && (message_defer || rc == DEFER)
+        )
        deferred_event_raise(first_addr, host, US"msg:defer");
 #endif
       }