Merge remote-tracking branch 'exim_github/pr/16'
[exim.git] / src / src / transports / smtp.c
index 537f32aa512373ea78366b0d1e5e3e55479e6fb7..40eebe8b68c9bad080dff10f6e54322dfb12cd67 100644 (file)
@@ -1,10 +1,8 @@
-/* $Cambridge: exim/src/src/transports/smtp.c,v 1.37 2007/06/18 13:57:50 ph10 Exp $ */
-
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2007 */
+/* Copyright (c) University of Cambridge 1995 - 2014 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "../exim.h"
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "../exim.h"
@@ -39,38 +37,47 @@ optionlist smtp_transport_options[] = {
       (void *)offsetof(smtp_transport_options_block, data_timeout) },
   { "delay_after_cutoff", opt_bool,
       (void *)offsetof(smtp_transport_options_block, delay_after_cutoff) },
       (void *)offsetof(smtp_transport_options_block, data_timeout) },
   { "delay_after_cutoff", opt_bool,
       (void *)offsetof(smtp_transport_options_block, delay_after_cutoff) },
-#ifdef EXPERIMENTAL_DOMAINKEYS
-  { "dk_canon", opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, dk_canon) },
-  { "dk_domain", opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, dk_domain) },
-  { "dk_headers", opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, dk_headers) },
-  { "dk_private_key", opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, dk_private_key) },
-  { "dk_selector", opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, dk_selector) },
-  { "dk_strict", opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, dk_strict) },
+#ifndef DISABLE_DKIM
+  { "dkim_canon", opt_stringptr,
+      (void *)offsetof(smtp_transport_options_block, dkim_canon) },
+  { "dkim_domain", opt_stringptr,
+      (void *)offsetof(smtp_transport_options_block, dkim_domain) },
+  { "dkim_private_key", opt_stringptr,
+      (void *)offsetof(smtp_transport_options_block, dkim_private_key) },
+  { "dkim_selector", opt_stringptr,
+      (void *)offsetof(smtp_transport_options_block, dkim_selector) },
+  { "dkim_sign_headers", opt_stringptr,
+      (void *)offsetof(smtp_transport_options_block, dkim_sign_headers) },
+  { "dkim_strict", opt_stringptr,
+      (void *)offsetof(smtp_transport_options_block, dkim_strict) },
 #endif
   { "dns_qualify_single",   opt_bool,
       (void *)offsetof(smtp_transport_options_block, dns_qualify_single) },
   { "dns_search_parents",   opt_bool,
       (void *)offsetof(smtp_transport_options_block, dns_search_parents) },
 #endif
   { "dns_qualify_single",   opt_bool,
       (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,
       (void *)offsetof(smtp_transport_options_block, fallback_hosts) },
   { "final_timeout",        opt_time,
       (void *)offsetof(smtp_transport_options_block, final_timeout) },
   { "gethostbyname",        opt_bool,
       (void *)offsetof(smtp_transport_options_block, gethostbyname) },
   { "fallback_hosts",       opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, fallback_hosts) },
   { "final_timeout",        opt_time,
       (void *)offsetof(smtp_transport_options_block, final_timeout) },
   { "gethostbyname",        opt_bool,
       (void *)offsetof(smtp_transport_options_block, gethostbyname) },
-  #ifdef SUPPORT_TLS
+#ifdef SUPPORT_TLS
+  /* These are no longer honoured, as of Exim 4.80; for now, we silently
+  ignore; 4.83 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,
       (void *)offsetof(smtp_transport_options_block, gnutls_require_mac) },
   { "gnutls_require_protocols", opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, gnutls_require_proto) },
   { "gnutls_require_kx",    opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, gnutls_require_kx) },
   { "gnutls_require_mac",   opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, gnutls_require_mac) },
   { "gnutls_require_protocols", opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, gnutls_require_proto) },
-  #endif
+#endif
   { "helo_data",            opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, helo_data) },
   { "hosts",                opt_stringptr,
   { "helo_data",            opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, helo_data) },
   { "hosts",                opt_stringptr,
@@ -79,30 +86,46 @@ optionlist smtp_transport_options[] = {
       (void *)offsetof(smtp_transport_options_block, hosts_avoid_esmtp) },
   { "hosts_avoid_pipelining", opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, hosts_avoid_pipelining) },
       (void *)offsetof(smtp_transport_options_block, hosts_avoid_esmtp) },
   { "hosts_avoid_pipelining", opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, hosts_avoid_pipelining) },
-  #ifdef SUPPORT_TLS
+#ifdef SUPPORT_TLS
   { "hosts_avoid_tls",      opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, hosts_avoid_tls) },
   { "hosts_avoid_tls",      opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, hosts_avoid_tls) },
-  #endif
+#endif
   { "hosts_max_try",        opt_int,
       (void *)offsetof(smtp_transport_options_block, hosts_max_try) },
   { "hosts_max_try_hardlimit", opt_int,
       (void *)offsetof(smtp_transport_options_block, hosts_max_try_hardlimit) },
   { "hosts_max_try",        opt_int,
       (void *)offsetof(smtp_transport_options_block, hosts_max_try) },
   { "hosts_max_try_hardlimit", opt_int,
       (void *)offsetof(smtp_transport_options_block, hosts_max_try_hardlimit) },
-  #ifdef SUPPORT_TLS
+#ifdef SUPPORT_TLS
   { "hosts_nopass_tls",     opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, hosts_nopass_tls) },
   { "hosts_nopass_tls",     opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, hosts_nopass_tls) },
-  #endif
+#endif
   { "hosts_override",       opt_bool,
       (void *)offsetof(smtp_transport_options_block, hosts_override) },
   { "hosts_randomize",      opt_bool,
       (void *)offsetof(smtp_transport_options_block, hosts_randomize) },
   { "hosts_override",       opt_bool,
       (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(DISABLE_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) },
   { "hosts_require_auth",   opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, hosts_require_auth) },
-  #ifdef SUPPORT_TLS
+#ifdef SUPPORT_TLS
+# ifndef DISABLE_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) },
   { "hosts_require_tls",    opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, hosts_require_tls) },
-  #endif
+#endif
   { "hosts_try_auth",       opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, hosts_try_auth) },
   { "hosts_try_auth",       opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, hosts_try_auth) },
+#ifndef DISABLE_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,
   { "interface",            opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, interface) },
   { "keepalive",            opt_bool,
@@ -123,20 +146,36 @@ optionlist smtp_transport_options[] = {
       (void *)offsetof(smtp_transport_options_block, serialize_hosts) },
   { "size_addition",        opt_int,
       (void *)offsetof(smtp_transport_options_block, size_addition) }
       (void *)offsetof(smtp_transport_options_block, serialize_hosts) },
   { "size_addition",        opt_int,
       (void *)offsetof(smtp_transport_options_block, size_addition) }
-  #ifdef SUPPORT_TLS
+#ifdef SUPPORT_TLS
  ,{ "tls_certificate",      opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, tls_certificate) },
   { "tls_crl",              opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, tls_crl) },
  ,{ "tls_certificate",      opt_stringptr,
       (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_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) },
       (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_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,
   { "tls_verify_certificates", opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, tls_verify_certificates) }
-  #endif
+      (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
 };
 
 /* Size of the options list. An extern variable has to be used so that its
 };
 
 /* Size of the options list. An extern variable has to be used so that its
@@ -157,11 +196,20 @@ smtp_transport_options_block smtp_transport_option_defaults = {
   NULL,                /* interface */
   NULL,                /* port */
   US"smtp",            /* protocol */
   NULL,                /* interface */
   NULL,                /* port */
   US"smtp",            /* protocol */
+  NULL,                /* DSCP */
   NULL,                /* serialize_hosts */
   NULL,                /* hosts_try_auth */
   NULL,                /* hosts_require_auth */
   NULL,                /* serialize_hosts */
   NULL,                /* hosts_try_auth */
   NULL,                /* hosts_require_auth */
+#ifndef DISABLE_PRDR
+  NULL,                /* hosts_try_prdr */
+#endif
+#ifndef DISABLE_OCSP
+  US"*",               /* hosts_request_ocsp */
+  NULL,                /* hosts_require_ocsp */
+#endif
   NULL,                /* hosts_require_tls */
   NULL,                /* hosts_avoid_tls */
   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 */
   NULL,                /* hosts_avoid_pipelining */
   NULL,                /* hosts_avoid_esmtp */
   NULL,                /* hosts_nopass_tls */
@@ -178,13 +226,15 @@ smtp_transport_options_block smtp_transport_option_defaults = {
   FALSE,               /* gethostbyname */
   TRUE,                /* dns_qualify_single */
   FALSE,               /* dns_search_parents */
   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 */
   TRUE,                /* keepalive */
   FALSE,               /* lmtp_ignore_quota */
   TRUE                 /* retry_include_ip_address */
   TRUE,                /* delay_after_cutoff */
   FALSE,               /* hosts_override */
   FALSE,               /* hosts_randomize */
   TRUE,                /* keepalive */
   FALSE,               /* lmtp_ignore_quota */
   TRUE                 /* retry_include_ip_address */
-  #ifdef SUPPORT_TLS
+#ifdef SUPPORT_TLS
  ,NULL,                /* tls_certificate */
   NULL,                /* tls_crl */
   NULL,                /* tls_privatekey */
  ,NULL,                /* tls_certificate */
   NULL,                /* tls_crl */
   NULL,                /* tls_privatekey */
@@ -192,19 +242,40 @@ smtp_transport_options_block smtp_transport_option_defaults = {
   NULL,                /* gnutls_require_kx */
   NULL,                /* gnutls_require_mac */
   NULL,                /* gnutls_require_proto */
   NULL,                /* gnutls_require_kx */
   NULL,                /* gnutls_require_mac */
   NULL,                /* gnutls_require_proto */
+  NULL,                /* tls_sni */
   NULL,                /* tls_verify_certificates */
   NULL,                /* tls_verify_certificates */
-  TRUE                 /* tls_tempfail_tryclear */
-  #endif
-  #ifdef EXPERIMENTAL_DOMAINKEYS
- ,NULL,                /* dk_canon */
-  NULL,                /* dk_domain */
-  NULL,                /* dk_headers */
-  NULL,                /* dk_private_key */
-  NULL,                /* dk_selector */
-  NULL                 /* dk_strict */
-  #endif
+  EXIM_CLIENT_DH_DEFAULT_MIN_BITS,
+                       /* tls_dh_min_bits */
+  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 */
+  NULL,                /* dkim_domain */
+  NULL,                /* dkim_private_key */
+  NULL,                /* dkim_selector */
+  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 */
 
 
 /* Local statics */
 
@@ -304,7 +375,8 @@ if (tblock->retry_use_local_part == TRUE_UNSET)
 /* Set the default port according to the protocol */
 
 if (ob->port == NULL)
 /* Set the default port according to the protocol */
 
 if (ob->port == NULL)
-  ob->port = (strcmpic(ob->protocol, US"lmtp") == 0)? US"lmtp" : US"smtp";
+  ob->port = (strcmpic(ob->protocol, US"lmtp") == 0)? US"lmtp" :
+    (strcmpic(ob->protocol, US"smtps") == 0)? US"smtps" : US"smtp";
 
 /* Set up the setup entry point, to be called before subprocesses for this
 transport. */
 
 /* Set up the setup entry point, to be called before subprocesses for this
 transport. */
@@ -328,6 +400,15 @@ if (ob->hosts_override && ob->hosts != NULL) tblock->overrides_hosts = TRUE;
 for them, but do not do any lookups at this time. */
 
 host_build_hostlist(&(ob->fallback_hostlist), ob->fallback_hosts, FALSE);
 for them, but do not do any lookups at this time. */
 
 host_build_hostlist(&(ob->fallback_hostlist), ob->fallback_hosts, FALSE);
+
+#ifdef SUPPORT_TLS
+if (  ob->gnutls_require_kx
+   || ob->gnutls_require_mac
+   || ob->gnutls_require_proto)
+  log_write(0, LOG_MAIN, "WARNING: smtp transport options"
+    " gnutls_require_kx, gnutls_require_mac and gnutls_require_protocols"
+    " are obsolete\n");
+#endif
 }
 
 
 }
 
 
@@ -546,6 +627,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           *
 *************************************************/
 /*************************************************
 *           Synchronize SMTP responses           *
 *************************************************/
@@ -783,6 +917,229 @@ return yield;
 
 
 
 
 
 
+/* Do the client side of smtp-level authentication */
+/*
+Arguments:
+  buffer       EHLO response from server (gets overwritten)
+  addrlist      chain of potential addresses to deliver
+  host          host to deliver to
+  ob           transport options
+  ibp, obp     comms channel control blocks
+
+Returns:
+  OK                   Success, or failed (but not required): global "smtp_authenticated" set
+  DEFER                        Failed authentication (and was required)
+  ERROR                        Internal problem
+
+  FAIL_SEND            Failed communications - transmit
+  FAIL                 - response
+*/
+
+int
+smtp_auth(uschar *buffer, unsigned bufsize, address_item *addrlist, host_item *host,
+    smtp_transport_options_block *ob, BOOL is_esmtp,
+    smtp_inblock *ibp, smtp_outblock *obp)
+{
+  int require_auth;
+  uschar *fail_reason = US"server did not advertise AUTH support";
+
+  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);
+
+  if (is_esmtp && !regex_AUTH) regex_AUTH =
+      regex_must_compile(US"\\n250[\\s\\-]AUTH\\s+([\\-\\w\\s]+)(?:\\n|$)",
+            FALSE, TRUE);
+
+  if (is_esmtp && regex_match_and_setup(regex_AUTH, buffer, 0, -1))
+    {
+    uschar *names = string_copyn(expand_nstring[1], expand_nlength[1]);
+    expand_nmax = -1;                          /* reset */
+
+    /* Must not do this check until after we have saved the result of the
+    regex match above. */
+
+    if (require_auth == OK ||
+        verify_check_this_host(&(ob->hosts_try_auth), NULL, host->name,
+          host->address, NULL) == OK)
+      {
+      auth_instance *au;
+      fail_reason = US"no common mechanisms were found";
+
+      DEBUG(D_transport) debug_printf("scanning authentication mechanisms\n");
+
+      /* Scan the configured authenticators looking for one which is configured
+      for use as a client, which is not suppressed by client_condition, and
+      whose name matches an authentication mechanism supported by the server.
+      If one is found, attempt to authenticate by calling its client function.
+      */
+
+      for (au = auths; !smtp_authenticated && au != NULL; au = au->next)
+        {
+        uschar *p = names;
+        if (!au->client ||
+            (au->client_condition != NULL &&
+             !expand_check_condition(au->client_condition, au->name,
+               US"client authenticator")))
+          {
+          DEBUG(D_transport) debug_printf("skipping %s authenticator: %s\n",
+            au->name,
+            (au->client)? "client_condition is false" :
+                          "not configured as a client");
+          continue;
+          }
+
+        /* Loop to scan supported server mechanisms */
+
+        while (*p != 0)
+          {
+          int rc;
+          int len = Ustrlen(au->public_name);
+          while (isspace(*p)) p++;
+
+          if (strncmpic(au->public_name, p, len) != 0 ||
+              (p[len] != 0 && !isspace(p[len])))
+            {
+            while (*p != 0 && !isspace(*p)) p++;
+            continue;
+            }
+
+          /* Found data for a listed mechanism. Call its client entry. Set
+          a flag in the outblock so that data is overwritten after sending so
+          that reflections don't show it. */
+
+          fail_reason = US"authentication attempt(s) failed";
+          obp->authenticating = TRUE;
+          rc = (au->info->clientcode)(au, ibp, obp,
+            ob->command_timeout, buffer, bufsize);
+          obp->authenticating = FALSE;
+          DEBUG(D_transport) debug_printf("%s authenticator yielded %d\n",
+            au->name, rc);
+
+          /* A temporary authentication failure must hold up delivery to
+          this host. After a permanent authentication failure, we carry on
+          to try other authentication methods. If all fail hard, try to
+          deliver the message unauthenticated unless require_auth was set. */
+
+          switch(rc)
+            {
+            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 */
+
+            case FAIL_SEND:
+            return FAIL_SEND;
+
+            /* Failure after reading a response */
+
+            case FAIL:
+            if (errno != 0 || buffer[0] != '5') return FAIL;
+            log_write(0, LOG_MAIN, "%s authenticator failed H=%s [%s] %s",
+              au->name, host->name, host->address, buffer);
+            break;
+
+            /* Failure by some other means. In effect, the authenticator
+            decided it wasn't prepared to handle this case. Typically this
+            is the result of "fail" in an expansion string. Do we need to
+            log anything here? Feb 2006: a message is now put in the buffer
+            if logging is required. */
+
+            case CANCELLED:
+            if (*buffer != 0)
+              log_write(0, LOG_MAIN, "%s authenticator cancelled "
+                "authentication H=%s [%s] %s", au->name, host->name,
+                host->address, buffer);
+            break;
+
+            /* Internal problem, message in buffer. */
+
+            case ERROR:
+            set_errno(addrlist, 0, string_copy(buffer), DEFER, FALSE);
+            return ERROR;
+            }
+
+          break;  /* If not authenticated, try next authenticator */
+          }       /* Loop for scanning supported server mechanisms */
+        }         /* Loop for further authenticators */
+      }
+    }
+
+  /* If we haven't authenticated, but are required to, give up. */
+
+  if (require_auth == OK && !smtp_authenticated)
+    {
+    set_errno(addrlist, ERRNO_AUTHFAIL,
+      string_sprintf("authentication required but %s", fail_reason), DEFER,
+      FALSE);
+    return DEFER;
+    }
+
+  return OK;
+}
+
+
+/* Construct AUTH appendix string for MAIL TO */
+/*
+Arguments
+  buffer       to build string
+  addrlist      chain of potential addresses to deliver
+  ob           transport options
+
+Globals                smtp_authenticated
+               client_authenticated_sender
+Return True on error, otherwise buffer has (possibly empty) terminated string
+*/
+
+BOOL
+smtp_mail_auth_str(uschar *buffer, unsigned bufsize, address_item *addrlist,
+                   smtp_transport_options_block *ob)
+{
+uschar *local_authenticated_sender = authenticated_sender;
+
+#ifdef notdef
+  debug_printf("smtp_mail_auth_str: as<%s> os<%s> SA<%s>\n", authenticated_sender, ob->authenticated_sender, smtp_authenticated?"Y":"N");
+#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 TRUE;
+      }
+    }
+  else if (new[0] != 0) local_authenticated_sender = new;
+  }
+
+/* Add the authenticated sender address if present */
+
+if ((smtp_authenticated || ob->authenticated_sender_force) &&
+    local_authenticated_sender != NULL)
+  {
+  string_format(buffer, bufsize, " AUTH=%s",
+    auth_xtextencode(local_authenticated_sender,
+    Ustrlen(local_authenticated_sender)));
+  client_authenticated_sender = string_copy(local_authenticated_sender);
+  }
+else
+  *buffer= 0;
+
+return FALSE;
+}
+
+
+
 /*************************************************
 *       Deliver address list to given host       *
 *************************************************/
 /*************************************************
 *       Deliver address list to given host       *
 *************************************************/
@@ -845,6 +1202,7 @@ time_t start_delivery_time = time(NULL);
 smtp_transport_options_block *ob =
   (smtp_transport_options_block *)(tblock->options_block);
 BOOL lmtp = strcmpic(ob->protocol, US"lmtp") == 0;
 smtp_transport_options_block *ob =
   (smtp_transport_options_block *)(tblock->options_block);
 BOOL lmtp = strcmpic(ob->protocol, US"lmtp") == 0;
+BOOL smtps = strcmpic(ob->protocol, US"smtps") == 0;
 BOOL ok = FALSE;
 BOOL send_rset = TRUE;
 BOOL send_quit = TRUE;
 BOOL ok = FALSE;
 BOOL send_rset = TRUE;
 BOOL send_quit = TRUE;
@@ -853,18 +1211,24 @@ BOOL completed_address = FALSE;
 BOOL esmtp = TRUE;
 BOOL pending_MAIL;
 BOOL pass_message = FALSE;
 BOOL esmtp = TRUE;
 BOOL pending_MAIL;
 BOOL pass_message = FALSE;
+#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;
 uschar *igquotstr = US"";
 smtp_inblock inblock;
 smtp_outblock outblock;
 int max_rcpt = tblock->max_addresses;
 uschar *igquotstr = US"";
-uschar *local_authenticated_sender = authenticated_sender;
 uschar *helo_data = NULL;
 uschar *message = NULL;
 uschar new_message_id[MESSAGE_ID_LENGTH + 1];
 uschar *p;
 uschar buffer[4096];
 uschar inbuffer[4096];
 uschar *helo_data = NULL;
 uschar *message = NULL;
 uschar new_message_id[MESSAGE_ID_LENGTH + 1];
 uschar *p;
 uschar buffer[4096];
 uschar inbuffer[4096];
-uschar outbuffer[1024];
+uschar outbuffer[4096];
 
 suppress_tls = suppress_tls;  /* stop compiler warning when no TLS support */
 
 
 suppress_tls = suppress_tls;  /* stop compiler warning when no TLS support */
 
@@ -889,31 +1253,29 @@ outblock.authenticating = FALSE;
 
 /* Reset the parameters of a TLS session. */
 
 
 /* Reset the parameters of a TLS session. */
 
-tls_cipher = NULL;
-tls_peerdn = NULL;
+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;
 
 
-/* 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. */
+/* 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. */
 
 
-if (ob->authenticated_sender != NULL)
+tls_modify_variables(&tls_out);
+
+#ifndef SUPPORT_TLS
+if (smtps)
   {
   {
-  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;
+  set_errno(addrlist, 0, US"TLS support not available", DEFER, FALSE);
+  return ERROR;
   }
   }
+#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
 
 /* 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
@@ -923,7 +1285,7 @@ if (continue_hostname == NULL)
   {
   inblock.sock = outblock.sock =
     smtp_connect(host, host_af, port, interface, ob->connect_timeout,
   {
   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)
     {
 
   if (inblock.sock < 0)
     {
@@ -942,19 +1304,22 @@ if (continue_hostname == NULL)
   is nevertheless a reasonably clean way of programming this kind of logic,
   where you want to escape on any error. */
 
   is nevertheless a reasonably clean way of programming this kind of logic,
   where you want to escape on any error. */
 
-  if (!smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
-    ob->command_timeout)) goto RESPONSE_FAILED;
-
-  /* Now check if the helo_data expansion went well, and sign off cleanly if it
-  didn't. */
-
-  if (helo_data == NULL)
+  if (!smtps)
     {
     {
-    uschar *message = string_sprintf("failed to expand helo_data: %s",
-      expand_string_message);
-    set_errno(addrlist, 0, message, DEFER, FALSE);
-    yield = DEFER;
-    goto SEND_QUIT;
+    if (!smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
+      ob->command_timeout)) goto RESPONSE_FAILED;
+
+    /* Now check if the helo_data expansion went well, and sign off cleanly if
+    it didn't. */
+
+    if (helo_data == NULL)
+      {
+      uschar *message = string_sprintf("failed to expand helo_data: %s",
+        expand_string_message);
+      set_errno(addrlist, 0, message, DEFER, FALSE);
+      yield = DEFER;
+      goto SEND_QUIT;
+      }
     }
 
 /** Debugging without sending a message
     }
 
 /** Debugging without sending a message
@@ -995,6 +1360,20 @@ goto SEND_QUIT;
   esmtp = verify_check_this_host(&(ob->hosts_avoid_esmtp), NULL,
      host->name, host->address, NULL) != OK;
 
   esmtp = verify_check_this_host(&(ob->hosts_avoid_esmtp), NULL,
      host->name, host->address, NULL) != OK;
 
+  /* Alas; be careful, since this goto is not an error-out, so conceivably
+  we might set data between here and the target which we assume to exist
+  and be usable.  I can see this coming back to bite us. */
+  #ifdef SUPPORT_TLS
+  if (smtps)
+    {
+    tls_offered = TRUE;
+    suppress_tls = FALSE;
+    ob->tls_tempfail_tryclear = FALSE;
+    smtp_command = US"SSL-on-connect";
+    goto TLS_NEGOTIATE;
+    }
+  #endif
+
   if (esmtp)
     {
     if (smtp_write_command(&outblock, FALSE, "%s %s\r\n",
   if (esmtp)
     {
     if (smtp_write_command(&outblock, FALSE, "%s %s\r\n",
@@ -1035,6 +1414,17 @@ goto SEND_QUIT;
     pcre_exec(regex_STARTTLS, NULL, CS buffer, Ustrlen(buffer), 0,
       PCRE_EOPT, NULL, 0) >= 0;
   #endif
     pcre_exec(regex_STARTTLS, NULL, CS buffer, Ustrlen(buffer), 0,
       PCRE_EOPT, NULL, 0) >= 0;
   #endif
+
+  #ifndef DISABLE_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
   }
 
 /* For continuing deliveries down the same channel, the socket is the standard
@@ -1078,29 +1468,20 @@ if (tls_offered && !suppress_tls &&
   if (!smtp_read_response(&inblock, buffer2, sizeof(buffer2), '2',
       ob->command_timeout))
     {
   if (!smtp_read_response(&inblock, buffer2, sizeof(buffer2), '2',
       ob->command_timeout))
     {
-    Ustrncpy(buffer, buffer2, sizeof(buffer));
     if (errno != 0 || buffer2[0] == 0 ||
          (buffer2[0] == '4' && !ob->tls_tempfail_tryclear))
     if (errno != 0 || buffer2[0] == 0 ||
          (buffer2[0] == '4' && !ob->tls_tempfail_tryclear))
+      {
+      Ustrncpy(buffer, buffer2, sizeof(buffer));
       goto RESPONSE_FAILED;
       goto RESPONSE_FAILED;
+      }
     }
 
   /* STARTTLS accepted: try to negotiate a TLS session. */
 
   else
     }
 
   /* STARTTLS accepted: try to negotiate a TLS session. */
 
   else
+  TLS_NEGOTIATE:
     {
     {
-    int rc = tls_client_start(inblock.sock,
-      host,
-      addrlist,
-      NULL,                    /* No DH param */
-      ob->tls_certificate,
-      ob->tls_privatekey,
-      ob->tls_verify_certificates,
-      ob->tls_crl,
-      ob->tls_require_ciphers,
-      ob->gnutls_require_mac,
-      ob->gnutls_require_kx,
-      ob->gnutls_require_proto,
-      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
 
     /* 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
@@ -1120,21 +1501,29 @@ if (tls_offered && !suppress_tls &&
       {
       if (addr->transport_return == PENDING_DEFER)
         {
       {
       if (addr->transport_return == PENDING_DEFER)
         {
-        addr->cipher = tls_cipher;
-        addr->peerdn = tls_peerdn;
+        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;
         }
       }
     }
   }
 
         }
       }
     }
   }
 
+/* if smtps, we'll have smtp_command set to something else; always safe to
+reset it here. */
+smtp_command = big_buffer;
+
 /* If we started TLS, redo the EHLO/LHLO exchange over the secure channel. If
 helo_data is null, we are dealing with a connection that was passed from
 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 we started TLS, redo the EHLO/LHLO exchange over the secure channel. If
 helo_data is null, we are dealing with a connection that was passed from
 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)
     {
     helo_data = expand_string(ob->helo_data);
   if (helo_data == NULL)
     {
     helo_data = expand_string(ob->helo_data);
@@ -1148,8 +1537,24 @@ if (tls_active >= 0)
       }
     }
 
       }
     }
 
-  if (smtp_write_command(&outblock, FALSE, "%s %s\r\n", lmtp? "LHLO" : "EHLO",
-        helo_data) < 0)
+  /* 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 (smtp_write_command(&outblock, FALSE, "%s %s\r\n",
+        lmtp? "LHLO" : greeting_cmd, helo_data) < 0)
     goto SEND_FAILED;
   if (!smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
        ob->command_timeout))
     goto SEND_FAILED;
   if (!smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
        ob->command_timeout))
@@ -1178,13 +1583,10 @@ we skip this. */
 
 if (continue_hostname == NULL
     #ifdef SUPPORT_TLS
 
 if (continue_hostname == NULL
     #ifdef SUPPORT_TLS
-    || tls_active >= 0
+    || tls_out.active >= 0
     #endif
     )
   {
     #endif
     )
   {
-  int require_auth;
-  uschar *fail_reason = US"server did not advertise AUTH support";
-
   /* Set for IGNOREQUOTA if the response to LHLO specifies support and the
   lmtp_ignore_quota option was set. */
 
   /* Set for IGNOREQUOTA if the response to LHLO specifies support and the
   lmtp_ignore_quota option was set. */
 
@@ -1212,140 +1614,36 @@ if (continue_hostname == NULL
   DEBUG(D_transport) debug_printf("%susing PIPELINING\n",
     smtp_use_pipelining? "" : "not ");
 
   DEBUG(D_transport) debug_printf("%susing PIPELINING\n",
     smtp_use_pipelining? "" : "not ");
 
+#ifndef DISABLE_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
+
+#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
   authenticator's client driver is running. */
 
   /* 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;
-  require_auth = verify_check_this_host(&(ob->hosts_require_auth), NULL,
-    host->name, host->address, NULL);
-
-  if (esmtp && regex_match_and_setup(regex_AUTH, buffer, 0, -1))
-    {
-    uschar *names = string_copyn(expand_nstring[1], expand_nlength[1]);
-    expand_nmax = -1;                          /* reset */
-
-    /* Must not do this check until after we have saved the result of the
-    regex match above. */
-
-    if (require_auth == OK ||
-        verify_check_this_host(&(ob->hosts_try_auth), NULL, host->name,
-          host->address, NULL) == OK)
-      {
-      auth_instance *au;
-      fail_reason = US"no common mechanisms were found";
-
-      DEBUG(D_transport) debug_printf("scanning authentication mechanisms\n");
-
-      /* Scan the configured authenticators looking for one which is configured
-      for use as a client, which is not suppressed by client_condition, and
-      whose name matches an authentication mechanism supported by the server.
-      If one is found, attempt to authenticate by calling its client function.
-      */
-
-      for (au = auths; !smtp_authenticated && au != NULL; au = au->next)
-        {
-        uschar *p = names;
-        if (!au->client ||
-            (au->client_condition != NULL &&
-             !expand_check_condition(au->client_condition, au->name,
-               US"client authenticator")))
-          {
-          DEBUG(D_transport) debug_printf("skipping %s authenticator: %s\n",
-            au->name,
-            (au->client)? "client_condition is false" :
-                          "not configured as a client");
-          continue;
-          }
-
-        /* Loop to scan supported server mechanisms */
-
-        while (*p != 0)
-          {
-          int rc;
-          int len = Ustrlen(au->public_name);
-          while (isspace(*p)) p++;
-
-          if (strncmpic(au->public_name, p, len) != 0 ||
-              (p[len] != 0 && !isspace(p[len])))
-            {
-            while (*p != 0 && !isspace(*p)) p++;
-            continue;
-            }
-
-          /* Found data for a listed mechanism. Call its client entry. Set
-          a flag in the outblock so that data is overwritten after sending so
-          that reflections don't show it. */
-
-          fail_reason = US"authentication attempt(s) failed";
-          outblock.authenticating = TRUE;
-          rc = (au->info->clientcode)(au, &inblock, &outblock,
-            ob->command_timeout, buffer, sizeof(buffer));
-          outblock.authenticating = FALSE;
-          DEBUG(D_transport) debug_printf("%s authenticator yielded %d\n",
-            au->name, rc);
-
-          /* A temporary authentication failure must hold up delivery to
-          this host. After a permanent authentication failure, we carry on
-          to try other authentication methods. If all fail hard, try to
-          deliver the message unauthenticated unless require_auth was set. */
-
-          switch(rc)
-            {
-            case OK:
-            smtp_authenticated = TRUE;   /* stops the outer loop */
-            break;
-
-            /* Failure after writing a command */
-
-            case FAIL_SEND:
-            goto SEND_FAILED;
-
-            /* Failure after reading a response */
-
-            case FAIL:
-            if (errno != 0 || buffer[0] != '5') goto RESPONSE_FAILED;
-            log_write(0, LOG_MAIN, "%s authenticator failed H=%s [%s] %s",
-              au->name, host->name, host->address, buffer);
-            break;
-
-            /* Failure by some other means. In effect, the authenticator
-            decided it wasn't prepared to handle this case. Typically this
-            is the result of "fail" in an expansion string. Do we need to
-            log anything here? Feb 2006: a message is now put in the buffer
-            if logging is required. */
-
-            case CANCELLED:
-            if (*buffer != 0)
-              log_write(0, LOG_MAIN, "%s authenticator cancelled "
-                "authentication H=%s [%s] %s", au->name, host->name,
-                host->address, buffer);
-            break;
-
-            /* Internal problem, message in buffer. */
-
-            case ERROR:
-            yield = ERROR;
-            set_errno(addrlist, 0, string_copy(buffer), DEFER, FALSE);
-            goto SEND_QUIT;
-            }
-
-          break;  /* If not authenticated, try next authenticator */
-          }       /* Loop for scanning supported server mechanisms */
-        }         /* Loop for further authenticators */
-      }
-    }
-
-  /* If we haven't authenticated, but are required to, give up. */
-
-  if (require_auth == OK && !smtp_authenticated)
+  switch (yield = smtp_auth(buffer, sizeof(buffer), addrlist, host,
+                           ob, esmtp, &inblock, &outblock))
     {
     {
-    yield = DEFER;
-    set_errno(addrlist, ERRNO_AUTHFAIL,
-      string_sprintf("authentication required but %s", fail_reason), DEFER,
-      FALSE);
-    goto SEND_QUIT;
+    default:           goto SEND_QUIT;
+    case OK:           break;
+    case FAIL_SEND:    goto SEND_FAILED;
+    case FAIL:         goto RESPONSE_FAILED;
     }
   }
 
     }
   }
 
@@ -1364,6 +1662,7 @@ if (tblock->filter_command != NULL)
   sprintf(CS buffer, "%.50s transport", tblock->name);
   rc = transport_set_up_command(&transport_filter_argv, tblock->filter_command,
     TRUE, DEFER, addrlist, buffer, NULL);
   sprintf(CS buffer, "%.50s transport", tblock->name);
   rc = transport_set_up_command(&transport_filter_argv, tblock->filter_command,
     TRUE, DEFER, addrlist, buffer, NULL);
+  transport_filter_timeout = tblock->filter_timeout;
 
   /* On failure, copy the error to all addresses, abandon the SMTP call, and
   yield ERROR. */
 
   /* On failure, copy the error to all addresses, abandon the SMTP call, and
   yield ERROR. */
@@ -1410,15 +1709,67 @@ if (smtp_use_size)
   while (*p) p++;
   }
 
   while (*p) p++;
   }
 
-/* Add the authenticated sender address if present */
+#ifndef DISABLE_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 ((smtp_authenticated || ob->authenticated_sender_force) &&
-    local_authenticated_sender != NULL)
+#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))
   {
   {
-  string_format(p, sizeof(buffer) - (p-buffer), " AUTH=%s",
-    auth_xtextencode(local_authenticated_sender,
-    Ustrlen(local_authenticated_sender)));
+  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.
+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 (smtp_mail_auth_str(p, sizeof(buffer) - (p-buffer), addrlist, ob))
+    return ERROR;
 
 /* From here until we send the DATA command, we can make use of PIPELINING
 if the server host supports it. The code has to be able to check the responses
 
 /* From here until we send the DATA command, we can make use of PIPELINING
 if the server host supports it. The code has to be able to check the responses
@@ -1472,18 +1823,66 @@ for (addr = first_addr;
   int count;
   BOOL no_flush;
 
   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);
 
   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. */
 
   /* 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);
   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)
     {
   if (count < 0) goto SEND_FAILED;
   if (count > 0)
     {
@@ -1573,23 +1972,22 @@ if (!ok) ok = TRUE; else
   DEBUG(D_transport|D_v)
     debug_printf("  SMTP>> writing message and terminating \".\"\n");
   transport_count = 0;
   DEBUG(D_transport|D_v)
     debug_printf("  SMTP>> writing message and terminating \".\"\n");
   transport_count = 0;
-#ifdef EXPERIMENTAL_DOMAINKEYS
-  if ( (ob->dk_private_key != NULL) && (ob->dk_selector != NULL) )
-    ok = dk_transport_write_message(addrlist, inblock.sock,
-      topt_use_crlf | topt_end_dot | topt_escape_headers |
-        (tblock->body_only? topt_no_headers : 0) |
-        (tblock->headers_only? topt_no_body : 0) |
-        (tblock->return_path_add? topt_add_return_path : 0) |
-        (tblock->delivery_date_add? topt_add_delivery_date : 0) |
-        (tblock->envelope_to_add? topt_add_envelope_to : 0),
-      0,            /* No size limit */
-      tblock->add_headers, tblock->remove_headers,
-      US".", US"..",    /* Escaping strings */
-      tblock->rewrite_rules, tblock->rewrite_existflags,
-      ob->dk_private_key, ob->dk_domain, ob->dk_selector,
-      ob->dk_canon, ob->dk_headers, ob->dk_strict);
-  else
-#endif
+#ifndef DISABLE_DKIM
+  ok = dkim_transport_write_message(addrlist, inblock.sock,
+    topt_use_crlf | topt_end_dot | topt_escape_headers |
+      (tblock->body_only? topt_no_headers : 0) |
+      (tblock->headers_only? topt_no_body : 0) |
+      (tblock->return_path_add? topt_add_return_path : 0) |
+      (tblock->delivery_date_add? topt_add_delivery_date : 0) |
+      (tblock->envelope_to_add? topt_add_envelope_to : 0),
+    0,            /* No size limit */
+    tblock->add_headers, tblock->remove_headers,
+    US".", US"..",    /* Escaping strings */
+    tblock->rewrite_rules, tblock->rewrite_existflags,
+    ob->dkim_private_key, ob->dkim_domain, ob->dkim_selector,
+    ob->dkim_canon, ob->dkim_strict, ob->dkim_sign_headers
+    );
+#else
   ok = transport_write_message(addrlist, inblock.sock,
     topt_use_crlf | topt_end_dot | topt_escape_headers |
       (tblock->body_only? topt_no_headers : 0) |
   ok = transport_write_message(addrlist, inblock.sock,
     topt_use_crlf | topt_end_dot | topt_escape_headers |
       (tblock->body_only? topt_no_headers : 0) |
@@ -1601,6 +1999,7 @@ if (!ok) ok = TRUE; else
     tblock->add_headers, tblock->remove_headers,
     US".", US"..",    /* Escaping strings */
     tblock->rewrite_rules, tblock->rewrite_existflags);
     tblock->add_headers, tblock->remove_headers,
     US".", US"..",    /* Escaping strings */
     tblock->rewrite_rules, tblock->rewrite_existflags);
+#endif
 
   /* transport_write_message() uses write() because it is called from other
   places to write to non-sockets. This means that under some OS (e.g. Solaris)
 
   /* transport_write_message() uses write() because it is called from other
   places to write to non-sockets. This means that under some OS (e.g. Solaris)
@@ -1626,8 +2025,31 @@ if (!ok) ok = TRUE; else
 
   smtp_command = US"end of data";
 
 
   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. */
+#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
+   * 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)
     {
 
   if (!lmtp)
     {
@@ -1675,13 +2097,18 @@ if (!ok) ok = TRUE; else
 
     /* Set up confirmation if needed - applies only to SMTP */
 
 
     /* 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;
       }
 
       {
       uschar *s = string_printing(buffer);
       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)
     each one. */
 
     for (addr = addrlist; addr != first_addr; addr = addr->next)
@@ -1693,13 +2120,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. */
 
       address. For temporary errors, add a retry item for the address so that
       it doesn't get tried again too soon. */
 
+#ifndef DISABLE_PRDR
+      if (lmtp || prdr_active)
+#else
       if (lmtp)
       if (lmtp)
+#endif
         {
         if (!smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
             ob->final_timeout))
           {
           if (errno != 0 || buffer[0] == 0) goto RESPONSE_FAILED;
         {
         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(
+#ifndef DISABLE_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')
             big_buffer, string_printing(buffer));
           setflag(addr, af_pass_message);   /* Allow message to go to user */
           if (buffer[0] == '5')
@@ -1709,11 +2145,19 @@ if (!ok) ok = TRUE; else
             errno = ERRNO_DATA4XX;
             addr->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
             addr->transport_return = DEFER;
             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);
+#ifndef DISABLE_PRDR
+            if (!prdr_active)
+#endif
+              retry_add_item(addr, addr->address_retry_key, 0);
             }
           continue;
           }
         completed_address = TRUE;   /* NOW we can set this flag */
             }
           continue;
           }
         completed_address = TRUE;   /* NOW we can set this flag */
+        if ((log_extra_selector & LX_smtp_confirmation) != 0)
+          {
+          uschar *s = string_printing(buffer);
+          conf = (s == buffer)? (uschar *)string_copy(s) : s;
+          }
         }
 
       /* SMTP, or success return from LMTP for this address. Pass back the
         }
 
       /* SMTP, or success return from LMTP for this address. Pass back the
@@ -1724,25 +2168,73 @@ if (!ok) ok = TRUE; else
       addr->host_used = thost;
       addr->special_action = flag;
       addr->message = conf;
       addr->host_used = thost;
       addr->special_action = flag;
       addr->message = conf;
+#ifndef DISABLE_PRDR
+      if (prdr_active) addr->flags |= af_prdr_used;
+#endif
       flag = '-';
 
       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));
+#ifndef DISABLE_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));
+        }
       }
 
       }
 
+#ifndef DISABLE_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)
     /* Ensure the journal file is pushed out to disk. */
 
     if (EXIMfsync(journal_fd) < 0)
@@ -1932,7 +2424,7 @@ if (completed_address && ok && send_quit)
   BOOL more;
   if (first_addr != NULL || continue_more ||
         (
   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)
         &&
            verify_check_this_host(&(ob->hosts_nopass_tls), NULL, host->name,
              host->address, NULL) != OK)
         &&
@@ -1981,12 +2473,15 @@ if (completed_address && ok && send_quit)
       don't get a good response, we don't attempt to pass the socket on. */
 
       #ifdef SUPPORT_TLS
       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);
-        ok = smtp_write_command(&outblock,FALSE,"EHLO %s\r\n",helo_data) >= 0 &&
-             smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
-               ob->command_timeout);
+        tls_close(FALSE, TRUE);
+        if (smtps)
+          ok = FALSE;
+        else
+          ok = smtp_write_command(&outblock,FALSE,"EHLO %s\r\n",helo_data) >= 0 &&
+               smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
+                 ob->command_timeout);
         }
       #endif
 
         }
       #endif
 
@@ -2030,12 +2525,10 @@ if (send_quit) (void)smtp_write_command(&outblock, FALSE, "QUIT\r\n");
 END_OFF:
 
 #ifdef SUPPORT_TLS
 END_OFF:
 
 #ifdef SUPPORT_TLS
-tls_close(TRUE);
+tls_close(FALSE, TRUE);
 #endif
 
 /* Close the socket, and return the appropriate value, first setting
 #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,
 
 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,
 
@@ -2136,7 +2629,10 @@ for (addr = addrlist; addr != NULL; addr = addr->next)
   addr->message = NULL;
   #ifdef SUPPORT_TLS
   addr->cipher = NULL;
   addr->message = NULL;
   #ifdef SUPPORT_TLS
   addr->cipher = NULL;
+  addr->ourcert = NULL;
+  addr->peercert = NULL;
   addr->peerdn = NULL;
   addr->peerdn = NULL;
+  addr->ocsp = OCSP_NOT_REQ;
   #endif
   }
 return first_addr;
   #endif
   }
 return first_addr;
@@ -2244,6 +2740,15 @@ if (hostlist == NULL || (ob->hosts_override && ob->hosts != NULL))
 
     host_build_hostlist(&hostlist, s, ob->hosts_randomize);
 
 
     host_build_hostlist(&hostlist, s, ob->hosts_randomize);
 
+    /* Check that the expansion yielded something useful. */
+    if (hostlist == NULL)
+      {
+      addrlist->message =
+        string_sprintf("%s transport has empty hosts setting", tblock->name);
+      addrlist->transport_return = PANIC;
+      return FALSE;   /* Only top address has status */
+      }
+
     /* If there was no expansion of hosts, save the host list for
     next time. */
 
     /* If there was no expansion of hosts, save the host list for
     next time. */
 
@@ -2430,6 +2935,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,
         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
           &canonical_name, NULL);
 
       /* Update the host (and any additional blocks, resulting from
@@ -2529,6 +3035,9 @@ for (cutoff_retry = 0; expired &&
 
     deliver_host = host->name;
     deliver_host_address = host->address;
 
     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
 
     /* 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
@@ -2737,6 +3246,11 @@ for (cutoff_retry = 0; expired &&
                          first_addr->basic_errno != ERRNO_TLSFAILURE)
         write_logs(first_addr, host);
 
                          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
       /* 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
@@ -2759,6 +3273,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);
           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
       }
         }
       #endif
       }
@@ -3019,9 +3537,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
 /* 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:
 
 
 END_TRANSPORT:
 
@@ -3030,4 +3551,6 @@ DEBUG(D_transport) debug_printf("Leaving %s transport\n", tblock->name);
 return TRUE;   /* Each address has its status */
 }
 
 return TRUE;   /* Each address has its status */
 }
 
+/* vi: aw ai sw=2
+*/
 /* End of transport/smtp.c */
 /* End of transport/smtp.c */