smtp
[exim.git] / src / src / transports / smtp.c
index 28dd8ff246460edc6bce4b6d0cb69fedf4305301..8fecf7eef444cbb2261d6fbff57bd2e1c8b5c49a 100644 (file)
@@ -1020,7 +1020,7 @@ fail:
   (void) smtp_discard_responses(sx, sx->conn_args.ob, *countp);
   return rc;
 }
-#endif
+#endif /*!DISABLE_PIPE_CONNECT*/
 
 
 /*************************************************
@@ -1087,7 +1087,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))
     {
@@ -1227,7 +1227,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__);
@@ -1274,6 +1274,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
@@ -1549,7 +1551,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);
@@ -1620,8 +1624,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;
 
 
@@ -1989,7 +1993,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
@@ -2019,8 +2096,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_sni = sx->addrlist->domain;
+                               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,
@@ -3430,6 +3507,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
 
 suppress_tls = suppress_tls;  /* stop compiler warning when no TLS support */
 *message_defer = FALSE;
@@ -3446,14 +3526,40 @@ 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. */
@@ -3820,9 +3926,10 @@ else
       }
 
     /* 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;
 
@@ -4155,8 +4262,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
@@ -4203,7 +4310,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;
@@ -4227,6 +4334,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)
@@ -4235,7 +4343,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
          {
@@ -4255,7 +4363,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. */
@@ -4295,7 +4403,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");
          }
@@ -4370,9 +4478,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;
 }
 
 
@@ -4578,11 +4715,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;