RFC3461 support - MIME DSN messages. Bug 118
[exim.git] / src / src / transports / smtp.c
index 25cc5490a8273ef394849da4b8435327b4894728..38dcfa08032c24d0eaf63f285ff3a004592eee3f 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2012 */
+/* Copyright (c) University of Cambridge 1995 - 2014 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "../exim.h"
@@ -55,6 +55,10 @@ 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) },
+  { "dnssec_request_domains", opt_stringptr,
+      (void *)offsetof(smtp_transport_options_block, dnssec_request_domains) },
+  { "dnssec_require_domains", opt_stringptr,
+      (void *)offsetof(smtp_transport_options_block, dnssec_require_domains) },
   { "dscp",                 opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, dscp) },
   { "fallback_hosts",       opt_stringptr,
@@ -98,6 +102,10 @@ optionlist smtp_transport_options[] = {
       (void *)offsetof(smtp_transport_options_block, hosts_override) },
   { "hosts_randomize",      opt_bool,
       (void *)offsetof(smtp_transport_options_block, hosts_randomize) },
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_OCSP)
+  { "hosts_request_ocsp",   opt_stringptr,
+      (void *)offsetof(smtp_transport_options_block, hosts_request_ocsp) },
+#endif
   { "hosts_require_auth",   opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, hosts_require_auth) },
 #ifdef SUPPORT_TLS
@@ -110,7 +118,7 @@ optionlist smtp_transport_options[] = {
 #endif
   { "hosts_try_auth",       opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, hosts_try_auth) },
-#ifdef EXPERIMENTAL_PRDR
+#ifndef DISABLE_PRDR
   { "hosts_try_prdr",       opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, hosts_try_prdr) },
 #endif
@@ -153,8 +161,20 @@ optionlist smtp_transport_options[] = {
       (void *)offsetof(smtp_transport_options_block, tls_sni) },
   { "tls_tempfail_tryclear", opt_bool,
       (void *)offsetof(smtp_transport_options_block, tls_tempfail_tryclear) },
+  { "tls_try_verify_hosts", opt_stringptr,
+      (void *)offsetof(smtp_transport_options_block, tls_try_verify_hosts) },
+#ifdef EXPERIMENTAL_CERTNAMES
+  { "tls_verify_cert_hostnames", opt_stringptr,
+      (void *)offsetof(smtp_transport_options_block,tls_verify_cert_hostnames)},
+#endif
   { "tls_verify_certificates", opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, tls_verify_certificates) }
+      (void *)offsetof(smtp_transport_options_block, tls_verify_certificates) },
+  { "tls_verify_hosts",     opt_stringptr,
+      (void *)offsetof(smtp_transport_options_block, tls_verify_hosts) }
+#endif
+#ifdef EXPERIMENTAL_TPDA
+ ,{ "tpda_host_defer_action", opt_stringptr,
+      (void *)offsetof(smtp_transport_options_block, tpda_host_defer_action) },
 #endif
 };
 
@@ -180,10 +200,11 @@ smtp_transport_options_block smtp_transport_option_defaults = {
   NULL,                /* serialize_hosts */
   NULL,                /* hosts_try_auth */
   NULL,                /* hosts_require_auth */
-#ifdef EXPERIMENTAL_PRDR
+#ifndef DISABLE_PRDR
   NULL,                /* hosts_try_prdr */
 #endif
 #ifdef EXPERIMENTAL_OCSP
+  US"*",               /* hosts_request_ocsp */
   NULL,                /* hosts_require_ocsp */
 #endif
   NULL,                /* hosts_require_tls */
@@ -205,6 +226,8 @@ smtp_transport_options_block smtp_transport_option_defaults = {
   FALSE,               /* gethostbyname */
   TRUE,                /* dns_qualify_single */
   FALSE,               /* dns_search_parents */
+  NULL,                /* dnssec_request_domains */
+  NULL,                /* dnssec_require_domains */
   TRUE,                /* delay_after_cutoff */
   FALSE,               /* hosts_override */
   FALSE,               /* hosts_randomize */
@@ -223,7 +246,12 @@ smtp_transport_options_block smtp_transport_option_defaults = {
   NULL,                /* tls_verify_certificates */
   EXIM_CLIENT_DH_DEFAULT_MIN_BITS,
                        /* tls_dh_min_bits */
-  TRUE                 /* tls_tempfail_tryclear */
+  TRUE,                /* tls_tempfail_tryclear */
+  NULL,                /* tls_verify_hosts */
+  NULL                 /* tls_try_verify_hosts */
+# ifdef EXPERIMENTAL_CERTNAMES
+ ,NULL                 /* tls_verify_cert_hostnames */
+# endif
 #endif
 #ifndef DISABLE_DKIM
  ,NULL,                /* dkim_canon */
@@ -233,8 +261,21 @@ smtp_transport_options_block smtp_transport_option_defaults = {
   NULL,                /* dkim_sign_headers */
   NULL                 /* dkim_strict */
 #endif
+#ifdef EXPERIMENTAL_TPDA
+ ,NULL                 /* tpda_host_defer_action */
+#endif
 };
 
+#ifdef EXPERIMENTAL_DSN
+/* some DSN flags for use later */
+
+static int     rf_list[] = {rf_notify_never, rf_notify_success,
+                            rf_notify_failure, rf_notify_delay };
+
+static uschar *rf_names[] = { "NEVER", "SUCCESS", "FAILURE", "DELAY" };
+#endif
+
+
 
 /* Local statics */
 
@@ -577,6 +618,59 @@ else
 
 
 
+#ifdef EXPERIMENTAL_TPDA
+/*************************************************
+*   Post-defer action                            *
+*************************************************/
+
+/* This expands an arbitrary per-transport string.
+   It might, for example, be used to write to the database log.
+
+Arguments:
+  ob                    transport options block
+  addr                  the address item containing error information
+  host                  the current host
+
+Returns:   nothing
+*/
+
+static void
+tpda_deferred(smtp_transport_options_block *ob, address_item *addr, host_item *host)
+{
+uschar *action = ob->tpda_host_defer_action;
+if (!action)
+       return;
+
+tpda_delivery_ip =         string_copy(host->address);
+tpda_delivery_port =       (host->port == PORT_NONE)? 25 : host->port;
+tpda_delivery_fqdn =       string_copy(host->name);
+tpda_delivery_local_part = string_copy(addr->local_part);
+tpda_delivery_domain =     string_copy(addr->domain);
+tpda_defer_errno =         addr->basic_errno;
+
+tpda_defer_errstr = addr->message
+  ? addr->basic_errno > 0
+    ? string_sprintf("%s: %s", addr->message, strerror(addr->basic_errno))
+    : string_copy(addr->message)
+  : addr->basic_errno > 0
+    ? string_copy(US strerror(addr->basic_errno))
+    : NULL;
+
+DEBUG(D_transport)
+  debug_printf("  TPDA(host defer): tpda_host_defer_action=|%s| tpda_delivery_IP=%s\n",
+    action, tpda_delivery_ip);
+
+router_name =    addr->router->name;
+transport_name = addr->transport->name;
+if (!expand_string(action) && *expand_string_message)
+  log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand tpda_defer_action in %s: %s\n",
+    transport_name, expand_string_message);
+router_name = transport_name = NULL;
+}
+#endif
+
+
+
 /*************************************************
 *           Synchronize SMTP responses           *
 *************************************************/
@@ -976,7 +1070,7 @@ smtp_auth(uschar *buffer, unsigned bufsize, address_item *addrlist, host_item *h
       FALSE);
     return DEFER;
     }
-  
+
   return OK;
 }
 
@@ -1108,10 +1202,13 @@ BOOL completed_address = FALSE;
 BOOL esmtp = TRUE;
 BOOL pending_MAIL;
 BOOL pass_message = FALSE;
-#ifdef EXPERIMENTAL_PRDR
+#ifndef DISABLE_PRDR
 BOOL prdr_offered = FALSE;
 BOOL prdr_active;
 #endif
+#ifdef EXPERIMENTAL_DSN
+BOOL dsn_all_lasthop = TRUE;
+#endif
 smtp_inblock inblock;
 smtp_outblock outblock;
 int max_rcpt = tblock->max_addresses;
@@ -1147,25 +1244,27 @@ outblock.authenticating = FALSE;
 
 /* Reset the parameters of a TLS session. */
 
-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
-
 tls_out.bits = 0;
 tls_out.cipher = NULL; /* the one we may use for this transport */
+tls_out.ourcert = NULL;
+tls_out.peercert = NULL;
 tls_out.peerdn = NULL;
 #if defined(SUPPORT_TLS) && !defined(USE_GNUTLS)
 tls_out.sni = NULL;
 #endif
+tls_out.ocsp = OCSP_NOT_REQ;
+
+/* Flip the legacy TLS-related variables over to the outbound set in case
+they're used in the context of the transport.  Don't bother resetting
+afterward as we're in a subprocess. */
+
+tls_modify_variables(&tls_out);
 
 #ifndef SUPPORT_TLS
 if (smtps)
   {
-    set_errno(addrlist, 0, US"TLS support not available", DEFER, FALSE);
-    return ERROR;
+  set_errno(addrlist, 0, US"TLS support not available", DEFER, FALSE);
+  return ERROR;
   }
 #endif
 
@@ -1307,7 +1406,7 @@ goto SEND_QUIT;
       PCRE_EOPT, NULL, 0) >= 0;
   #endif
 
-  #ifdef EXPERIMENTAL_PRDR
+  #ifndef DISABLE_PRDR
   prdr_offered = esmtp &&
     (pcre_exec(regex_PRDR, NULL, CS buffer, Ustrlen(buffer), 0,
       PCRE_EOPT, NULL, 0) >= 0) &&
@@ -1373,20 +1472,7 @@ if (tls_offered && !suppress_tls &&
   else
   TLS_NEGOTIATE:
     {
-    int rc = tls_client_start(inblock.sock,
-      host,
-      addrlist,
-      ob->tls_certificate,
-      ob->tls_privatekey,
-      ob->tls_sni,
-      ob->tls_verify_certificates,
-      ob->tls_crl,
-      ob->tls_require_ciphers,
-#ifdef EXPERIMENTAL_OCSP
-      ob->hosts_require_ocsp,
-#endif
-      ob->tls_dh_min_bits,
-      ob->command_timeout);
+    int rc = tls_client_start(inblock.sock, host, addrlist, ob);
 
     /* 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
@@ -1407,7 +1493,10 @@ if (tls_offered && !suppress_tls &&
       if (addr->transport_return == PENDING_DEFER)
         {
         addr->cipher = tls_out.cipher;
+        addr->ourcert = tls_out.ourcert;
+        addr->peercert = tls_out.peercert;
         addr->peerdn = tls_out.peerdn;
+       addr->ocsp = tls_out.ocsp;
         }
       }
     }
@@ -1516,7 +1605,7 @@ if (continue_hostname == NULL
   DEBUG(D_transport) debug_printf("%susing PIPELINING\n",
     smtp_use_pipelining? "" : "not ");
 
-#ifdef EXPERIMENTAL_PRDR
+#ifndef DISABLE_PRDR
   prdr_offered = esmtp &&
     pcre_exec(regex_PRDR, NULL, CS buffer, Ustrlen(CS buffer), 0,
       PCRE_EOPT, NULL, 0) >= 0 &&
@@ -1527,6 +1616,13 @@ if (continue_hostname == NULL
     {DEBUG(D_transport) debug_printf("PRDR usable\n");}
 #endif
 
+#ifdef EXPERIMENTAL_DSN
+  /* Note if the server supports DSN */
+  smtp_use_dsn = esmtp && pcre_exec(regex_DSN, NULL, CS buffer, (int)Ustrlen(CS buffer), 0,
+       PCRE_EOPT, NULL, 0) >= 0;
+  DEBUG(D_transport) debug_printf("use_dsn=%d\n", smtp_use_dsn);
+#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
@@ -1604,7 +1700,7 @@ if (smtp_use_size)
   while (*p) p++;
   }
 
-#ifdef EXPERIMENTAL_PRDR
+#ifndef DISABLE_PRDR
 prdr_active = FALSE;
 if (prdr_offered)
   {
@@ -1624,6 +1720,38 @@ if (prdr_offered)
 prdr_is_active:
 #endif
 
+#ifdef EXPERIMENTAL_DSN
+/* check if all addresses have lasthop flag */
+/* do not send RET and ENVID if true */
+dsn_all_lasthop = TRUE;
+for (addr = first_addr;
+     address_count < max_rcpt && addr != NULL;
+     addr = addr->next)
+  if ((addr->dsn_flags & rf_dsnlasthop) != 1)
+    dsn_all_lasthop = FALSE;
+
+/* Add any DSN flags to the mail command */
+
+if ((smtp_use_dsn) && (dsn_all_lasthop == FALSE))
+  {
+  if (dsn_ret == dsn_ret_hdrs)
+    {
+    strcpy(p, " RET=HDRS");
+    while (*p) p++;
+    }
+  else if (dsn_ret == dsn_ret_full)
+    {
+    strcpy(p, " RET=FULL");
+    while (*p) p++;
+    }
+  if (dsn_envid != NULL)
+    {
+    string_format(p, sizeof(buffer) - (p-buffer), " ENVID=%s", dsn_envid);
+    while (*p) p++;
+    }
+  }
+#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.
@@ -1686,18 +1814,66 @@ for (addr = first_addr;
   int count;
   BOOL no_flush;
 
+  #ifdef EXPERIMENTAL_DSN
+  if(smtp_use_dsn)
+    addr->dsn_aware = dsn_support_yes;
+  else
+    addr->dsn_aware = dsn_support_no;
+  #endif
+
   if (addr->transport_return != PENDING_DEFER) continue;
 
   address_count++;
   no_flush = smtp_use_pipelining && (!mua_wrapper || addr->next != NULL);
 
+  #ifdef EXPERIMENTAL_DSN
+  /* Add any DSN flags to the rcpt command and add to the sent string */
+
+  p = buffer;
+  *p = 0;
+
+  if ((smtp_use_dsn) && ((addr->dsn_flags & rf_dsnlasthop) != 1))
+    {
+    if ((addr->dsn_flags & rf_dsnflags) != 0)
+      {
+      int i;
+      BOOL first = TRUE;
+      strcpy(p, " NOTIFY=");
+      while (*p) p++;
+      for (i = 0; i < 4; i++)
+        {
+        if ((addr->dsn_flags & rf_list[i]) != 0)
+          {
+          if (!first) *p++ = ',';
+          first = FALSE;
+          strcpy(p, rf_names[i]);
+          while (*p) p++;
+          }
+        }
+      }
+
+    if (addr->dsn_orcpt != NULL) {
+      string_format(p, sizeof(buffer) - (p-buffer), " ORCPT=%s",
+        addr->dsn_orcpt);
+      while (*p) p++;
+      }
+    }
+  #endif
+
+
   /* Now send the RCPT command, and process outstanding responses when
   necessary. After a timeout on RCPT, we just end the function, leaving the
   yield as OK, because this error can often mean that there is a problem with
   just one address, so we don't want to delay the host. */
 
+  #ifdef EXPERIMENTAL_DSN
+  count = smtp_write_command(&outblock, no_flush, "RCPT TO:<%s>%s%s\r\n",
+    transport_rcpt_address(addr, tblock->rcpt_include_affixes), igquotstr, buffer);
+  #else
   count = smtp_write_command(&outblock, no_flush, "RCPT TO:<%s>%s\r\n",
     transport_rcpt_address(addr, tblock->rcpt_include_affixes), igquotstr);
+  #endif
+
   if (count < 0) goto SEND_FAILED;
   if (count > 0)
     {
@@ -1840,7 +2016,7 @@ if (!ok) ok = TRUE; else
 
   smtp_command = US"end of data";
 
-#ifdef EXPERIMENTAL_PRDR
+#ifndef DISABLE_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
@@ -1912,7 +2088,12 @@ if (!ok) ok = TRUE; else
 
     /* Set up confirmation if needed - applies only to SMTP */
 
-    if ((log_extra_selector & LX_smtp_confirmation) != 0 && !lmtp)
+    if (
+        #ifndef EXPERIMENTAL_TPDA
+          (log_extra_selector & LX_smtp_confirmation) != 0 &&
+        #endif
+          !lmtp
+       )
       {
       uschar *s = string_printing(buffer);
       conf = (s == buffer)? (uschar *)string_copy(s) : s;
@@ -1930,7 +2111,7 @@ 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
+#ifndef DISABLE_PRDR
       if (lmtp || prdr_active)
 #else
       if (lmtp)
@@ -1941,7 +2122,7 @@ if (!ok) ok = TRUE; else
           {
           if (errno != 0 || buffer[0] == 0) goto RESPONSE_FAILED;
           addr->message = string_sprintf(
-#ifdef EXPERIMENTAL_PRDR
+#ifndef DISABLE_PRDR
            "%s error after %s: %s", prdr_active ? "PRDR":"LMTP",
 #else
            "LMTP error after %s: %s",
@@ -1955,7 +2136,7 @@ if (!ok) ok = TRUE; else
             errno = ERRNO_DATA4XX;
             addr->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
             addr->transport_return = DEFER;
-#ifdef EXPERIMENTAL_PRDR
+#ifndef DISABLE_PRDR
             if (!prdr_active)
 #endif
               retry_add_item(addr, addr->address_retry_key, 0);
@@ -1978,12 +2159,12 @@ if (!ok) ok = TRUE; else
       addr->host_used = thost;
       addr->special_action = flag;
       addr->message = conf;
-#ifdef EXPERIMENTAL_PRDR
+#ifndef DISABLE_PRDR
       if (prdr_active) addr->flags |= af_prdr_used;
 #endif
       flag = '-';
 
-#ifdef EXPERIMENTAL_PRDR
+#ifndef DISABLE_PRDR
       if (!prdr_active)
 #endif
         {
@@ -1991,12 +2172,12 @@ if (!ok) ok = TRUE; else
         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)
@@ -2005,7 +2186,7 @@ if (!ok) ok = TRUE; else
         }
       }
 
-#ifdef EXPERIMENTAL_PRDR
+#ifndef DISABLE_PRDR
       if (prdr_active)
         {
        /* PRDR - get the final, overall response.  For any non-success
@@ -2033,7 +2214,7 @@ if (!ok) ok = TRUE; else
             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)
@@ -2339,8 +2520,6 @@ tls_close(FALSE, TRUE);
 #endif
 
 /* Close the socket, and return the appropriate value, first setting
-continue_transport and continue_hostname NULL to prevent any other addresses
-that may include the host from trying to re-use a continuation socket. This
 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,
 
@@ -2441,7 +2620,10 @@ for (addr = addrlist; addr != NULL; addr = addr->next)
   addr->message = NULL;
   #ifdef SUPPORT_TLS
   addr->cipher = NULL;
+  addr->ourcert = NULL;
+  addr->peercert = NULL;
   addr->peerdn = NULL;
+  addr->ocsp = OCSP_NOT_REQ;
   #endif
   }
 return first_addr;
@@ -2744,6 +2926,7 @@ for (cutoff_retry = 0; expired &&
         rc = host_find_byname(host, NULL, flags, &canonical_name, TRUE);
       else
         rc = host_find_bydns(host, NULL, flags, NULL, NULL, NULL,
+         ob->dnssec_request_domains, ob->dnssec_require_domains,
           &canonical_name, NULL);
 
       /* Update the host (and any additional blocks, resulting from
@@ -2843,6 +3026,9 @@ for (cutoff_retry = 0; expired &&
 
     deliver_host = host->name;
     deliver_host_address = host->address;
+    lookup_dnssec_authenticated = host->dnssec == DS_YES ? US"yes"
+                               : host->dnssec == DS_NO ? US"no"
+                               : US"";
 
     /* Set up a string for adding to the retry key if the port number is not
     the standard SMTP port. A host may have its own port setting that overrides
@@ -3051,6 +3237,11 @@ for (cutoff_retry = 0; expired &&
                          first_addr->basic_errno != ERRNO_TLSFAILURE)
         write_logs(first_addr, host);
 
+      #ifdef EXPERIMENTAL_TPDA
+      if (rc == DEFER)
+        tpda_deferred(ob, first_addr, host);
+      #endif
+
       /* If STARTTLS was accepted, but there was a failure in setting up the
       TLS session (usually a certificate screwup), and the host is not in
       hosts_require_tls, and tls_tempfail_tryclear is true, try again, with
@@ -3073,6 +3264,10 @@ for (cutoff_retry = 0; expired &&
           expanded_hosts != NULL, &message_defer, TRUE);
         if (rc == DEFER && first_addr->basic_errno != ERRNO_AUTHFAIL)
           write_logs(first_addr, host);
+        #ifdef EXPERIMENTAL_TPDA
+        if (rc == DEFER)
+          tpda_deferred(ob, first_addr, host);
+        #endif
         }
       #endif
       }
@@ -3347,4 +3542,6 @@ DEBUG(D_transport) debug_printf("Leaving %s transport\n", tblock->name);
 return TRUE;   /* Each address has its status */
 }
 
+/* vi: aw ai sw=2
+*/
 /* End of transport/smtp.c */