OCSP-stapling enhancement and testing.
[exim.git] / src / src / transports / smtp.c
index 53012eced0c4715858ef4223307ff060d6b16f48..4b5529fd8643cc5c6f1c0814a2a9934f9baad43a 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2012 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "../exim.h"
@@ -55,6 +55,8 @@ optionlist smtp_transport_options[] = {
       (void *)offsetof(smtp_transport_options_block, dns_qualify_single) },
   { "dns_search_parents",   opt_bool,
       (void *)offsetof(smtp_transport_options_block, dns_search_parents) },
+  { "dscp",                 opt_stringptr,
+      (void *)offsetof(smtp_transport_options_block, dscp) },
   { "fallback_hosts",       opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, fallback_hosts) },
   { "final_timeout",        opt_time,
@@ -62,6 +64,9 @@ optionlist smtp_transport_options[] = {
   { "gethostbyname",        opt_bool,
       (void *)offsetof(smtp_transport_options_block, gethostbyname) },
 #ifdef SUPPORT_TLS
+  /* These are no longer honoured, as of Exim 4.80; for now, we silently
+  ignore; a later release will warn, and a later-still release will remove
+  these options, so that using them becomes an error. */
   { "gnutls_require_kx",    opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, gnutls_require_kx) },
   { "gnutls_require_mac",   opt_stringptr,
@@ -96,11 +101,23 @@ optionlist smtp_transport_options[] = {
   { "hosts_require_auth",   opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, hosts_require_auth) },
 #ifdef SUPPORT_TLS
+# if defined EXPERIMENTAL_OCSP
+  { "hosts_require_ocsp",   opt_stringptr,
+      (void *)offsetof(smtp_transport_options_block, hosts_require_ocsp) },
+# endif
   { "hosts_require_tls",    opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, hosts_require_tls) },
 #endif
   { "hosts_try_auth",       opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, hosts_try_auth) },
+#ifdef EXPERIMENTAL_PRDR
+  { "hosts_try_prdr",       opt_stringptr,
+      (void *)offsetof(smtp_transport_options_block, hosts_try_prdr) },
+#endif
+#ifdef SUPPORT_TLS
+  { "hosts_verify_avoid_tls", opt_stringptr,
+      (void *)offsetof(smtp_transport_options_block, hosts_verify_avoid_tls) },
+#endif
   { "interface",            opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, interface) },
   { "keepalive",            opt_bool,
@@ -126,10 +143,14 @@ optionlist smtp_transport_options[] = {
       (void *)offsetof(smtp_transport_options_block, tls_certificate) },
   { "tls_crl",              opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, tls_crl) },
+  { "tls_dh_min_bits",      opt_int,
+      (void *)offsetof(smtp_transport_options_block, tls_dh_min_bits) },
   { "tls_privatekey",       opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, tls_privatekey) },
-  { "tls_require_ciphers",   opt_stringptr,
+  { "tls_require_ciphers",  opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, tls_require_ciphers) },
+  { "tls_sni",              opt_stringptr,
+      (void *)offsetof(smtp_transport_options_block, tls_sni) },
   { "tls_tempfail_tryclear", opt_bool,
       (void *)offsetof(smtp_transport_options_block, tls_tempfail_tryclear) },
   { "tls_verify_certificates", opt_stringptr,
@@ -155,11 +176,19 @@ smtp_transport_options_block smtp_transport_option_defaults = {
   NULL,                /* interface */
   NULL,                /* port */
   US"smtp",            /* protocol */
+  NULL,                /* DSCP */
   NULL,                /* serialize_hosts */
   NULL,                /* hosts_try_auth */
   NULL,                /* hosts_require_auth */
+#ifdef EXPERIMENTAL_PRDR
+  NULL,                /* hosts_try_prdr */
+#endif
+#ifdef EXPERIMENTAL_OCSP
+  NULL,                /* hosts_require_ocsp */
+#endif
   NULL,                /* hosts_require_tls */
   NULL,                /* hosts_avoid_tls */
+  US"*",               /* hosts_verify_avoid_tls */
   NULL,                /* hosts_avoid_pipelining */
   NULL,                /* hosts_avoid_esmtp */
   NULL,                /* hosts_nopass_tls */
@@ -190,7 +219,10 @@ smtp_transport_options_block smtp_transport_option_defaults = {
   NULL,                /* gnutls_require_kx */
   NULL,                /* gnutls_require_mac */
   NULL,                /* gnutls_require_proto */
+  NULL,                /* tls_sni */
   NULL,                /* tls_verify_certificates */
+  EXIM_CLIENT_DH_DEFAULT_MIN_BITS,
+                       /* tls_dh_min_bits */
   TRUE                 /* tls_tempfail_tryclear */
 #endif
 #ifndef DISABLE_DKIM
@@ -853,6 +885,10 @@ BOOL completed_address = FALSE;
 BOOL esmtp = TRUE;
 BOOL pending_MAIL;
 BOOL pass_message = FALSE;
+#ifdef EXPERIMENTAL_PRDR
+BOOL prdr_offered = FALSE;
+BOOL prdr_active;
+#endif
 smtp_inblock inblock;
 smtp_outblock outblock;
 int max_rcpt = tblock->max_addresses;
@@ -889,31 +925,19 @@ outblock.authenticating = FALSE;
 
 /* Reset the parameters of a TLS session. */
 
-tls_cipher = NULL;
-tls_peerdn = NULL;
-
-/* If an authenticated_sender override has been specified for this transport
-instance, expand it. If the expansion is forced to fail, and there was already
-an authenticated_sender for this message, the original value will be used.
-Other expansion failures are serious. An empty result is ignored, but there is
-otherwise no check - this feature is expected to be used with LMTP and other
-cases where non-standard addresses (e.g. without domains) might be required. */
+tls_in.bits = 0;
+tls_in.cipher = NULL;  /* for back-compatible behaviour */
+tls_in.peerdn = NULL;
+#if defined(SUPPORT_TLS) && !defined(USE_GNUTLS)
+tls_in.sni = NULL;
+#endif
 
-if (ob->authenticated_sender != NULL)
-  {
-  uschar *new = expand_string(ob->authenticated_sender);
-  if (new == NULL)
-    {
-    if (!expand_string_forcedfail)
-      {
-      uschar *message = string_sprintf("failed to expand "
-        "authenticated_sender: %s", expand_string_message);
-      set_errno(addrlist, 0, message, DEFER, FALSE);
-      return ERROR;
-      }
-    }
-  else if (new[0] != 0) local_authenticated_sender = new;
-  }
+tls_out.bits = 0;
+tls_out.cipher = NULL; /* the one we may use for this transport */
+tls_out.peerdn = NULL;
+#if defined(SUPPORT_TLS) && !defined(USE_GNUTLS)
+tls_out.sni = NULL;
+#endif
 
 #ifndef SUPPORT_TLS
 if (smtps)
@@ -931,7 +955,7 @@ if (continue_hostname == NULL)
   {
   inblock.sock = outblock.sock =
     smtp_connect(host, host_af, port, interface, ob->connect_timeout,
-      ob->keepalive);   /* This puts port into host->port */
+      ob->keepalive, ob->dscp);   /* This puts port into host->port */
 
   if (inblock.sock < 0)
     {
@@ -1060,6 +1084,17 @@ goto SEND_QUIT;
     pcre_exec(regex_STARTTLS, NULL, CS buffer, Ustrlen(buffer), 0,
       PCRE_EOPT, NULL, 0) >= 0;
   #endif
+
+  #ifdef EXPERIMENTAL_PRDR
+  prdr_offered = esmtp &&
+    (pcre_exec(regex_PRDR, NULL, CS buffer, Ustrlen(buffer), 0,
+      PCRE_EOPT, NULL, 0) >= 0) &&
+    (verify_check_this_host(&(ob->hosts_try_prdr), NULL, host->name,
+      host->address, NULL) == OK);
+
+  if (prdr_offered)
+    {DEBUG(D_transport) debug_printf("PRDR usable\n");}
+  #endif
   }
 
 /* For continuing deliveries down the same channel, the socket is the standard
@@ -1119,15 +1154,16 @@ if (tls_offered && !suppress_tls &&
     int rc = tls_client_start(inblock.sock,
       host,
       addrlist,
-      NULL,                    /* No DH param */
       ob->tls_certificate,
       ob->tls_privatekey,
+      ob->tls_sni,
       ob->tls_verify_certificates,
       ob->tls_crl,
       ob->tls_require_ciphers,
-      ob->gnutls_require_mac,
-      ob->gnutls_require_kx,
-      ob->gnutls_require_proto,
+#ifdef EXPERIMENTAL_OCSP
+      ob->hosts_require_ocsp,
+#endif
+      ob->tls_dh_min_bits,
       ob->command_timeout);
 
     /* TLS negotiation failed; give an error. From outside, this function may
@@ -1148,8 +1184,8 @@ if (tls_offered && !suppress_tls &&
       {
       if (addr->transport_return == PENDING_DEFER)
         {
-        addr->cipher = tls_cipher;
-        addr->peerdn = tls_peerdn;
+        addr->cipher = tls_out.cipher;
+        addr->peerdn = tls_out.peerdn;
         }
       }
     }
@@ -1165,7 +1201,7 @@ another process, and so we won't have expanded helo_data above. We have to
 expand it here. $sending_ip_address and $sending_port are set up right at the
 start of the Exim process (in exim.c). */
 
-if (tls_active >= 0)
+if (tls_out.active >= 0)
   {
   char *greeting_cmd;
   if (helo_data == NULL)
@@ -1181,21 +1217,20 @@ if (tls_active >= 0)
       }
     }
 
-  /* For SMTPS we need to wait for the initial OK response.
-  Also, it seems likely that a server not supporting STARTTLS is broken
-  enough to perhaps not support EHLO. */
+  /* For SMTPS we need to wait for the initial OK response. */
   if (smtps)
     {
     if (!smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
       ob->command_timeout)) goto RESPONSE_FAILED;
-    if (esmtp)
-      greeting_cmd = "EHLO";
-    else
-      {
-      greeting_cmd = "HELO";
-      DEBUG(D_transport)
-        debug_printf("not sending EHLO (host matches hosts_avoid_esmtp)\n");
-      }
+    }
+
+  if (esmtp)
+    greeting_cmd = "EHLO";
+  else
+    {
+    greeting_cmd = "HELO";
+    DEBUG(D_transport)
+      debug_printf("not sending EHLO (host matches hosts_avoid_esmtp)\n");
     }
 
   if (smtp_write_command(&outblock, FALSE, "%s %s\r\n",
@@ -1228,7 +1263,7 @@ we skip this. */
 
 if (continue_hostname == NULL
     #ifdef SUPPORT_TLS
-    || tls_active >= 0
+    || tls_out.active >= 0
     #endif
     )
   {
@@ -1262,12 +1297,24 @@ if (continue_hostname == NULL
   DEBUG(D_transport) debug_printf("%susing PIPELINING\n",
     smtp_use_pipelining? "" : "not ");
 
+#ifdef EXPERIMENTAL_PRDR
+  prdr_offered = esmtp &&
+    pcre_exec(regex_PRDR, NULL, CS buffer, Ustrlen(CS buffer), 0,
+      PCRE_EOPT, NULL, 0) >= 0 &&
+    verify_check_this_host(&(ob->hosts_try_prdr), NULL, host->name,
+      host->address, NULL) == OK;
+
+  if (prdr_offered)
+    {DEBUG(D_transport) debug_printf("PRDR usable\n");}
+#endif
+
   /* Note if the response to EHLO specifies support for the AUTH extension.
   If it has, check that this host is one we want to authenticate to, and do
   the business. The host name and address must be available when the
   authenticator's client driver is running. */
 
   smtp_authenticated = FALSE;
+  client_authenticator = client_authenticated_id = client_authenticated_sender = NULL;
   require_auth = verify_check_this_host(&(ob->hosts_require_auth), NULL,
     host->name, host->address, NULL);
 
@@ -1345,6 +1392,9 @@ if (continue_hostname == NULL
             {
             case OK:
             smtp_authenticated = TRUE;   /* stops the outer loop */
+           client_authenticator = au->name;
+           if (au->set_client_id != NULL)
+             client_authenticated_id = expand_string(au->set_client_id);
             break;
 
             /* Failure after writing a command */
@@ -1461,6 +1511,49 @@ if (smtp_use_size)
   while (*p) p++;
   }
 
+#ifdef EXPERIMENTAL_PRDR
+prdr_active = FALSE;
+if (prdr_offered)
+  {
+  for (addr = first_addr; addr; addr = addr->next)
+    if (addr->transport_return == PENDING_DEFER)
+      {
+      for (addr = addr->next; addr; addr = addr->next)
+        if (addr->transport_return == PENDING_DEFER)
+         {                     /* at least two recipients to send */
+         prdr_active = TRUE;
+         sprintf(CS p, " PRDR"); p += 5;
+         goto prdr_is_active;
+         }
+      break;
+      }
+  }
+prdr_is_active:
+#endif
+
+/* If an authenticated_sender override has been specified for this transport
+instance, expand it. If the expansion is forced to fail, and there was already
+an authenticated_sender for this message, the original value will be used.
+Other expansion failures are serious. An empty result is ignored, but there is
+otherwise no check - this feature is expected to be used with LMTP and other
+cases where non-standard addresses (e.g. without domains) might be required. */
+
+if (ob->authenticated_sender != NULL)
+  {
+  uschar *new = expand_string(ob->authenticated_sender);
+  if (new == NULL)
+    {
+    if (!expand_string_forcedfail)
+      {
+      uschar *message = string_sprintf("failed to expand "
+        "authenticated_sender: %s", expand_string_message);
+      set_errno(addrlist, 0, message, DEFER, FALSE);
+      return ERROR;
+      }
+    }
+  else if (new[0] != 0) local_authenticated_sender = new;
+  }
+
 /* Add the authenticated sender address if present */
 
 if ((smtp_authenticated || ob->authenticated_sender_force) &&
@@ -1469,6 +1562,7 @@ if ((smtp_authenticated || ob->authenticated_sender_force) &&
   string_format(p, sizeof(buffer) - (p-buffer), " AUTH=%s",
     auth_xtextencode(local_authenticated_sender,
     Ustrlen(local_authenticated_sender)));
+  client_authenticated_sender = string_copy(local_authenticated_sender);
   }
 
 /* From here until we send the DATA command, we can make use of PIPELINING
@@ -1677,8 +1771,31 @@ if (!ok) ok = TRUE; else
 
   smtp_command = US"end of data";
 
-  /* For SMTP, we now read a single response that applies to the whole message.
-  If it is OK, then all the addresses have been delivered. */
+#ifdef EXPERIMENTAL_PRDR
+  /* For PRDR we optionally get a partial-responses warning
+   * followed by the individual responses, before going on with
+   * the overall response.  If we don't get the warning then deal
+   * with per non-PRDR. */
+  if(prdr_active)
+    {
+    ok = smtp_read_response(&inblock, buffer, sizeof(buffer), '3',
+      ob->final_timeout);
+    if (!ok && errno == 0)
+      switch(buffer[0])
+        {
+       case '2': prdr_active = FALSE;
+                 ok = TRUE;
+                 break;
+       case '4': errno = ERRNO_DATA4XX;
+                  addrlist->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
+                 break;
+        }
+    }
+  else
+#endif
+
+  /* For non-PRDR SMTP, we now read a single response that applies to the
+  whole message.  If it is OK, then all the addresses have been delivered. */
 
   if (!lmtp)
     {
@@ -1732,7 +1849,7 @@ if (!ok) ok = TRUE; else
       conf = (s == buffer)? (uschar *)string_copy(s) : s;
       }
 
-    /* Process all transported addresses - for LMTP, read a status for
+    /* Process all transported addresses - for LMTP or PRDR, read a status for
     each one. */
 
     for (addr = addrlist; addr != first_addr; addr = addr->next)
@@ -1744,13 +1861,22 @@ if (!ok) ok = TRUE; else
       address. For temporary errors, add a retry item for the address so that
       it doesn't get tried again too soon. */
 
+#ifdef EXPERIMENTAL_PRDR
+      if (lmtp || prdr_active)
+#else
       if (lmtp)
+#endif
         {
         if (!smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
             ob->final_timeout))
           {
           if (errno != 0 || buffer[0] == 0) goto RESPONSE_FAILED;
-          addr->message = string_sprintf("LMTP error after %s: %s",
+          addr->message = string_sprintf(
+#ifdef EXPERIMENTAL_PRDR
+           "%s error after %s: %s", prdr_active ? "PRDR":"LMTP",
+#else
+           "LMTP error after %s: %s",
+#endif
             big_buffer, string_printing(buffer));
           setflag(addr, af_pass_message);   /* Allow message to go to user */
           if (buffer[0] == '5')
@@ -1760,7 +1886,10 @@ if (!ok) ok = TRUE; else
             errno = ERRNO_DATA4XX;
             addr->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
             addr->transport_return = DEFER;
-            retry_add_item(addr, addr->address_retry_key, 0);
+#ifdef EXPERIMENTAL_PRDR
+            if (!prdr_active)
+#endif
+              retry_add_item(addr, addr->address_retry_key, 0);
             }
           continue;
           }
@@ -1780,25 +1909,73 @@ if (!ok) ok = TRUE; else
       addr->host_used = thost;
       addr->special_action = flag;
       addr->message = conf;
+#ifdef EXPERIMENTAL_PRDR
+      if (prdr_active) addr->flags |= af_prdr_used;
+#endif
       flag = '-';
 
-      /* Update the journal. For homonymic addresses, use the base address plus
-      the transport name. See lots of comments in deliver.c about the reasons
-      for the complications when homonyms are involved. Just carry on after
-      write error, as it may prove possible to update the spool file later. */
-
-      if (testflag(addr, af_homonym))
-        sprintf(CS buffer, "%.500s/%s\n", addr->unique + 3, tblock->name);
-      else
-        sprintf(CS buffer, "%.500s\n", addr->unique);
-
-      DEBUG(D_deliver) debug_printf("journalling %s", buffer);
-      len = Ustrlen(CS buffer);
-      if (write(journal_fd, buffer, len) != len)
-        log_write(0, LOG_MAIN|LOG_PANIC, "failed to write journal for "
-          "%s: %s", buffer, strerror(errno));
+#ifdef EXPERIMENTAL_PRDR
+      if (!prdr_active)
+#endif
+        {
+        /* Update the journal. For homonymic addresses, use the base address plus
+        the transport name. See lots of comments in deliver.c about the reasons
+        for the complications when homonyms are involved. Just carry on after
+        write error, as it may prove possible to update the spool file later. */
+  
+        if (testflag(addr, af_homonym))
+          sprintf(CS buffer, "%.500s/%s\n", addr->unique + 3, tblock->name);
+        else
+          sprintf(CS buffer, "%.500s\n", addr->unique);
+  
+        DEBUG(D_deliver) debug_printf("journalling %s", buffer);
+        len = Ustrlen(CS buffer);
+        if (write(journal_fd, buffer, len) != len)
+          log_write(0, LOG_MAIN|LOG_PANIC, "failed to write journal for "
+            "%s: %s", buffer, strerror(errno));
+        }
       }
 
+#ifdef EXPERIMENTAL_PRDR
+      if (prdr_active)
+        {
+       /* PRDR - get the final, overall response.  For any non-success
+       upgrade all the address statuses. */
+        ok = smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
+          ob->final_timeout);
+        if (!ok)
+         {
+         if(errno == 0 && buffer[0] == '4')
+            {
+            errno = ERRNO_DATA4XX;
+            addrlist->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
+            }
+         for (addr = addrlist; addr != first_addr; addr = addr->next)
+            if (buffer[0] == '5' || addr->transport_return == OK)
+              addr->transport_return = PENDING_OK; /* allow set_errno action */
+         goto RESPONSE_FAILED;
+         }
+
+       /* Update the journal, or setup retry. */
+        for (addr = addrlist; addr != first_addr; addr = addr->next)
+         if (addr->transport_return == OK)
+         {
+          if (testflag(addr, af_homonym))
+            sprintf(CS buffer, "%.500s/%s\n", addr->unique + 3, tblock->name);
+          else
+            sprintf(CS buffer, "%.500s\n", addr->unique);
+  
+          DEBUG(D_deliver) debug_printf("journalling(PRDR) %s", buffer);
+          len = Ustrlen(CS buffer);
+          if (write(journal_fd, buffer, len) != len)
+            log_write(0, LOG_MAIN|LOG_PANIC, "failed to write journal for "
+              "%s: %s", buffer, strerror(errno));
+         }
+       else if (addr->transport_return == DEFER)
+          retry_add_item(addr, addr->address_retry_key, -2);
+       }
+#endif
+
     /* Ensure the journal file is pushed out to disk. */
 
     if (EXIMfsync(journal_fd) < 0)
@@ -1988,7 +2165,7 @@ if (completed_address && ok && send_quit)
   BOOL more;
   if (first_addr != NULL || continue_more ||
         (
-           (tls_active < 0 ||
+           (tls_out.active < 0 ||
            verify_check_this_host(&(ob->hosts_nopass_tls), NULL, host->name,
              host->address, NULL) != OK)
         &&
@@ -2037,9 +2214,9 @@ if (completed_address && ok && send_quit)
       don't get a good response, we don't attempt to pass the socket on. */
 
       #ifdef SUPPORT_TLS
-      if (tls_active >= 0)
+      if (tls_out.active >= 0)
         {
-        tls_close(TRUE);
+        tls_close(FALSE, TRUE);
         if (smtps)
           ok = FALSE;
         else
@@ -2089,7 +2266,7 @@ if (send_quit) (void)smtp_write_command(&outblock, FALSE, "QUIT\r\n");
 END_OFF:
 
 #ifdef SUPPORT_TLS
-tls_close(TRUE);
+tls_close(FALSE, TRUE);
 #endif
 
 /* Close the socket, and return the appropriate value, first setting
@@ -3087,9 +3264,12 @@ for (addr = addrlist; addr != NULL; addr = addr->next)
 /* Update the database which keeps information about which messages are waiting
 for which hosts to become available. For some message-specific errors, the
 update_waiting flag is turned off because we don't want follow-on deliveries in
-those cases. */
+those cases.  If this transport instance is explicitly limited to one message
+per connection then follow-on deliveries are not possible and there's no need
+to create/update the per-transport wait-<transport_name> database. */
 
-if (update_waiting) transport_update_waiting(hostlist, tblock->name);
+if (update_waiting && tblock->connection_max_messages != 1)
+  transport_update_waiting(hostlist, tblock->name);
 
 END_TRANSPORT: