Fix gnutls library version check, for system certs feature. Bug 1691
[exim.git] / src / src / deliver.c
index 78b669ad2cdbddd9ddb5acc852d79ef1c294fd6f..3f22dc91ff6c5aa7301483a2f8230ac2a1885110 100644 (file)
@@ -2,13 +2,14 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* The main code for delivering a message. */
 
 
 #include "exim.h"
+#include <assert.h>
 
 
 /* Data block for keeping track of subprocesses for parallel remote
@@ -63,10 +64,8 @@ static address_item *addr_new = NULL;
 static address_item *addr_remote = NULL;
 static address_item *addr_route = NULL;
 static address_item *addr_succeed = NULL;
-#ifdef EXPERIMENTAL_DSN
 static address_item *addr_dsntmp = NULL;
 static address_item *addr_senddsn = NULL;
-#endif
 
 static FILE *message_log = NULL;
 static BOOL update_spool;
@@ -129,7 +128,7 @@ deliver_set_expansions(address_item *addr)
 {
 if (addr == NULL)
   {
-  uschar ***p = address_expansions;
+  const uschar ***p = address_expansions;
   while (*p != NULL) **p++ = NULL;
   return;
   }
@@ -151,9 +150,9 @@ else
   }
 
 deliver_recipients = addr;
-deliver_address_data = addr->p.address_data;
-deliver_domain_data = addr->p.domain_data;
-deliver_localpart_data = addr->p.localpart_data;
+deliver_address_data = addr->prop.address_data;
+deliver_domain_data = addr->prop.domain_data;
+deliver_localpart_data = addr->prop.localpart_data;
 
 /* These may be unset for multiple addresses */
 
@@ -678,67 +677,105 @@ while (addr->parent != NULL)
 
 
 
+/*************************************************
+*      Delivery logging support functions        *
+*************************************************/
+
+/* The LOGGING() checks in d_log_interface() are complicated for backwards
+compatibility. When outgoing interface logging was originally added, it was
+conditional on just incoming_interface (which is off by default). The
+outgoing_interface option is on by default to preserve this behaviour, but
+you can enable incoming_interface and disable outgoing_interface to get I=
+fields on incoming lines only.
+
+Arguments:
+  s         The log line buffer
+  sizep     Pointer to the buffer size
+  ptrp      Pointer to current index into buffer
+  addr      The address to be logged
+
+Returns:    New value for s
+*/
+
+static uschar *
+d_log_interface(uschar *s, int *sizep, int *ptrp)
+{
+if (LOGGING(incoming_interface) && LOGGING(outgoing_interface)
+    && sending_ip_address != NULL)
+  {
+  s = string_append(s, sizep, ptrp, 2, US" I=[", sending_ip_address);
+  if (LOGGING(outgoing_port))
+    s = string_append(s, sizep, ptrp, 2, US"]:",
+      string_sprintf("%d", sending_port));
+  else
+    s = string_cat(s, sizep, ptrp, "]", 1);
+  }
+return s;
+}
+
+
 
 static uschar *
-d_hostlog(uschar * s, int * sizep, int * ptrp, address_item * addr)
+d_hostlog(uschar *s, int *sizep, int *ptrp, address_item *addr)
 {
-  s = string_append(s, sizep, ptrp, 5, US" H=", addr->host_used->name,
-    US" [", addr->host_used->address, US"]");
-  if ((log_extra_selector & LX_outgoing_port) != 0)
-    s = string_append(s, sizep, ptrp, 2, US":", string_sprintf("%d",
-      addr->host_used->port));
-  return s;
+s = string_append(s, sizep, ptrp, 5, US" H=", addr->host_used->name,
+  US" [", addr->host_used->address, US"]");
+if (LOGGING(outgoing_port))
+  s = string_append(s, sizep, ptrp, 2, US":", string_sprintf("%d",
+    addr->host_used->port));
+return d_log_interface(s, sizep, ptrp);
 }
 
+
+
 #ifdef SUPPORT_TLS
 static uschar *
 d_tlslog(uschar * s, int * sizep, int * ptrp, address_item * addr)
 {
-  if ((log_extra_selector & LX_tls_cipher) != 0 && addr->cipher != NULL)
-    s = string_append(s, sizep, ptrp, 2, US" X=", addr->cipher);
-  if ((log_extra_selector & LX_tls_certificate_verified) != 0 &&
-       addr->cipher != NULL)
-    s = string_append(s, sizep, ptrp, 2, US" CV=",
-      testflag(addr, af_cert_verified)
-      ?
+if (LOGGING(tls_cipher) && addr->cipher != NULL)
+  s = string_append(s, sizep, ptrp, 2, US" X=", addr->cipher);
+if (LOGGING(tls_certificate_verified) && addr->cipher != NULL)
+  s = string_append(s, sizep, ptrp, 2, US" CV=",
+    testflag(addr, af_cert_verified)
+    ?
 #ifdef EXPERIMENTAL_DANE
-        testflag(addr, af_dane_verified)
-      ? "dane"
-      :
+      testflag(addr, af_dane_verified)
+    ? "dane"
+    :
 #endif
-        "yes"
-      : "no");
-  if ((log_extra_selector & LX_tls_peerdn) != 0 && addr->peerdn != NULL)
-    s = string_append(s, sizep, ptrp, 3, US" DN=\"",
-      string_printing(addr->peerdn), US"\"");
-  return s;
+      "yes"
+    : "no");
+if (LOGGING(tls_peerdn) && addr->peerdn != NULL)
+  s = string_append(s, sizep, ptrp, 3, US" DN=\"",
+    string_printing(addr->peerdn), US"\"");
+return s;
 }
 #endif
 
 
 
 
-#ifdef EXPERIMENTAL_TPDA
-int
-tpda_raise_event(uschar * action, uschar * event, uschar * ev_data)
+#ifdef EXPERIMENTAL_EVENT
+uschar *
+event_raise(uschar * action, const uschar * event, uschar * ev_data)
 {
 uschar * s;
 if (action)
   {
   DEBUG(D_deliver)
-    debug_printf("TPDA(%s): tpda_event_action=|%s| tpda_delivery_IP=%s\n",
+    debug_printf("Event(%s): event_action=|%s| delivery_IP=%s\n",
       event,
       action, deliver_host_address);
 
-  tpda_event = event;
-  tpda_data =  ev_data;
+  event_name = event;
+  event_data = ev_data;
 
   if (!(s = expand_string(action)) && *expand_string_message)
     log_write(0, LOG_MAIN|LOG_PANIC,
-      "failed to expand tpda_event_action %s in %s: %s\n",
+      "failed to expand event_action %s in %s: %s\n",
       event, transport_name, expand_string_message);
 
-  tpda_event = tpda_data = NULL;
+  event_name = event_data = NULL;
 
   /* If the expansion returns anything but an empty string, flag for
   the caller to modify his normal processing
@@ -746,18 +783,19 @@ if (action)
   if (s && *s)
     {
     DEBUG(D_deliver)
-      debug_printf("TPDA(%s): event_action returned \"%s\"\n", s);
-    return DEFER;
+      debug_printf("Event(%s): event_action returned \"%s\"\n", event, s);
+    return s;
     }
   }
-return OK;
+return NULL;
 }
 
 static void
-tpda_msg_event(uschar * event, address_item * addr)
+msg_event_raise(const uschar * event, const address_item * addr)
 {
-uschar * save_domain = deliver_domain;
+const uschar * save_domain = deliver_domain;
 uschar * save_local =  deliver_localpart;
+const uschar * save_host = deliver_host;
 
 if (!addr->transport)
   return;
@@ -766,16 +804,18 @@ router_name =    addr->router ? addr->router->name : NULL;
 transport_name = addr->transport->name;
 deliver_domain = addr->domain;
 deliver_localpart = addr->local_part;
+deliver_host =   addr->host_used ? addr->host_used->name : NULL;
 
-(void) tpda_raise_event(addr->transport->tpda_event_action, event,
+(void) event_raise(addr->transport->event_action, event,
          addr->host_used || Ustrcmp(addr->transport->driver_name, "lmtp") == 0
          ? addr->message : NULL);
 
+deliver_host =      save_host;
 deliver_localpart = save_local;
 deliver_domain =    save_domain;
 router_name = transport_name = NULL;
 }
-#endif /*EXPERIMENTAL_TPDA*/
+#endif /*EXPERIMENTAL_EVENT*/
 
 
 
@@ -795,20 +835,19 @@ int ptr = 0;            /* expanding buffer, for */
 uschar *s;              /* building log lines;   */
 void *reset_point;      /* released afterwards.  */
 
-
 /* Log the delivery on the main log. We use an extensible string to build up
 the log line, and reset the store afterwards. Remote deliveries should always
 have a pointer to the host item that succeeded; local deliveries can have a
 pointer to a single host item in their host list, for use by the transport. */
 
-#ifdef EXPERIMENTAL_TPDA
+#ifdef EXPERIMENTAL_EVENT
   /* presume no successful remote delivery */
   lookup_dnssec_authenticated = NULL;
 #endif
 
 s = reset_point = store_get(size);
 
-log_address = string_log_address(addr, (log_write_selector & L_all_parents) != 0, TRUE);
+log_address = string_log_address(addr, LOGGING(all_parents), TRUE);
 if (msg)
   s = string_append(s, &size, &ptr, 3, host_and_ident(TRUE), US" ", log_address);
 else
@@ -817,12 +856,19 @@ else
   s = string_append(s, &size, &ptr, 2, US"> ", log_address);
   }
 
-if ((log_extra_selector & LX_sender_on_delivery) != 0  ||  msg)
-  s = string_append(s, &size, &ptr, 3, US" F=<", sender_address, US">");
+if (LOGGING(sender_on_delivery) || msg)
+  s = string_append(s, &size, &ptr, 3, US" F=<",
+#ifdef EXPERIMENTAL_INTERNATIONAL
+    testflag(addr, af_utf8_downcvt)
+    ? string_address_utf8_to_alabel(sender_address, NULL)
+    :
+#endif
+      sender_address,
+  US">");
 
 #ifdef EXPERIMENTAL_SRS
-if(addr->p.srs_sender)
-  s = string_append(s, &size, &ptr, 3, US" SRS=<", addr->p.srs_sender, US">");
+if(addr->prop.srs_sender)
+  s = string_append(s, &size, &ptr, 3, US" SRS=<", addr->prop.srs_sender, US">");
 #endif
 
 /* You might think that the return path must always be set for a successful
@@ -830,8 +876,7 @@ delivery; indeed, I did for some time, until this statement crashed. The case
 when it is not set is for a delivery to /dev/null which is optimised by not
 being run at all. */
 
-if (used_return_path != NULL &&
-      (log_extra_selector & LX_return_path_on_delivery) != 0)
+if (used_return_path != NULL && LOGGING(return_path_on_delivery))
   s = string_append(s, &size, &ptr, 3, US" P=<", used_return_path, US">");
 
 if (msg)
@@ -843,7 +888,7 @@ if (addr->router != NULL)
 
 s = string_append(s, &size, &ptr, 2, US" T=", addr->transport->name);
 
-if ((log_extra_selector & LX_delivery_size) != 0)
+if (LOGGING(delivery_size))
   s = string_append(s, &size, &ptr, 2, US" S=",
     string_sprintf("%d", transport_count));
 
@@ -853,6 +898,7 @@ if (addr->transport->info->local)
   {
   if (addr->host_list)
     s = string_append(s, &size, &ptr, 2, US" H=", addr->host_list->name);
+  s = d_log_interface(s, &size, &ptr);
   if (addr->shadow_message != NULL)
     s = string_cat(s, &size, &ptr, addr->shadow_message,
       Ustrlen(addr->shadow_message));
@@ -868,9 +914,10 @@ else
     if (continue_sequence > 1)
       s = string_cat(s, &size, &ptr, US"*", 1);
 
-#ifdef EXPERIMENTAL_TPDA
+#ifdef EXPERIMENTAL_EVENT
     deliver_host_address = addr->host_used->address;
     deliver_host_port =    addr->host_used->port;
+    deliver_host =         addr->host_used->name;
 
     /* DNS lookup status */
     lookup_dnssec_authenticated = addr->host_used->dnssec==DS_YES ? US"yes"
@@ -889,7 +936,7 @@ else
     if (addr->auth_id)
       {
       s = string_append(s, &size, &ptr, 2, US":", addr->auth_id);
-      if (log_extra_selector & LX_smtp_mailauth  &&  addr->auth_sndr)
+      if (LOGGING(smtp_mailauth) && addr->auth_sndr)
         s = string_append(s, &size, &ptr, 2, US":", addr->auth_sndr);
       }
     }
@@ -902,15 +949,15 @@ else
 
 /* confirmation message (SMTP (host_used) and LMTP (driver_name)) */
 
-if (log_extra_selector & LX_smtp_confirmation &&
-    addr->message &&
+if (LOGGING(smtp_confirmation) && addr->message &&
     (addr->host_used || Ustrcmp(addr->transport->driver_name, "lmtp") == 0))
   {
-  int i;
+  unsigned i;
+  unsigned lim = big_buffer_size < 1024 ? big_buffer_size : 1024;
   uschar *p = big_buffer;
   uschar *ss = addr->message;
   *p++ = '\"';
-  for (i = 0; i < 256 && ss[i] != 0; i++)      /* limit logged amount */
+  for (i = 0; i < lim && ss[i] != 0; i++)      /* limit logged amount */
     {
     if (ss[i] == '\"' || ss[i] == '\\') *p++ = '\\'; /* quote \ and " */
     *p++ = ss[i];
@@ -922,11 +969,11 @@ if (log_extra_selector & LX_smtp_confirmation &&
 
 /* Time on queue and actual time taken to deliver */
 
-if ((log_extra_selector & LX_queue_time) != 0)
+if (LOGGING(queue_time))
   s = string_append(s, &size, &ptr, 2, US" QT=",
     readconf_printtime( (int) ((long)time(NULL) - (long)received_time)) );
 
-if ((log_extra_selector & LX_deliver_time) != 0)
+if (LOGGING(deliver_time))
   s = string_append(s, &size, &ptr, 2, US" DT=",
     readconf_printtime(addr->more_errno));
 
@@ -936,9 +983,8 @@ store we used to build the line after writing it. */
 s[ptr] = 0;
 log_write(0, flags, "%s", s);
 
-#ifdef EXPERIMENTAL_TPDA
-/*XXX cutthrough calls this also for non-delivery...*/
-tpda_msg_event(US"msg:delivery", addr);
+#ifdef EXPERIMENTAL_EVENT
+if (!msg) msg_event_raise(US"msg:delivery", addr);
 #endif
 
 store_reset(reset_point);
@@ -979,7 +1025,6 @@ int ptr = 0;            /* expanding buffer, for */
 uschar *s;              /* building log lines;   */
 void *reset_point;      /* released afterwards.  */
 
-
 DEBUG(D_deliver) debug_printf("post-process %s (%d)\n", addr->address, result);
 
 /* Set up driver kind and name for logging. Disable logging if the router or
@@ -1015,7 +1060,10 @@ malformed, it won't ever have gone near LDAP.) */
 
 if (addr->message != NULL)
   {
-  addr->message = string_printing(addr->message);
+  const uschar * s = string_printing(addr->message);
+  if (s != addr->message)
+    addr->message = US s;
+    /* deconst cast ok as string_printing known to have alloc'n'copied */
   if (((Ustrstr(addr->message, "failed to expand") != NULL) || (Ustrstr(addr->message, "expansion of ") != NULL)) &&
       (Ustrstr(addr->message, "mysql") != NULL ||
        Ustrstr(addr->message, "pgsql") != NULL ||
@@ -1072,11 +1120,12 @@ if (addr->return_file >= 0 && addr->return_filename != NULL)
         if (s != NULL)
           {
           uschar *p = big_buffer + Ustrlen(big_buffer);
+         const uschar * sp;
           while (p > big_buffer && isspace(p[-1])) p--;
           *p = 0;
-          s = string_printing(big_buffer);
+          sp = string_printing(big_buffer);
           log_write(0, LOG_MAIN, "<%s>: %s transport output: %s",
-            addr->address, tb->name, s);
+            addr->address, tb->name, sp);
           }
         (void)fclose(f);
         }
@@ -1085,7 +1134,7 @@ if (addr->return_file >= 0 && addr->return_filename != NULL)
     /* Handle returning options, but only if there is an address to return
     the text to. */
 
-    if (sender_address[0] != 0 || addr->p.errors_address != NULL)
+    if (sender_address[0] != 0 || addr->prop.errors_address != NULL)
       {
       if (tb->return_output)
         {
@@ -1137,7 +1186,7 @@ if (result == OK)
     child_done(addr, now);
     }
 
-  /* Certificates for logging (via TPDA) */
+  /* Certificates for logging (via events) */
 #ifdef SUPPORT_TLS
   tls_out.ourcert = addr->ourcert;
   addr->ourcert = NULL;
@@ -1155,16 +1204,8 @@ if (result == OK)
   delivery_log(LOG_MAIN, addr, logchar, NULL);
 
 #ifdef SUPPORT_TLS
-  if (tls_out.ourcert)
-    {
-    tls_free_cert(tls_out.ourcert);
-    tls_out.ourcert = NULL;
-    }
-  if (tls_out.peercert)
-    {
-    tls_free_cert(tls_out.peercert);
-    tls_out.peercert = NULL;
-    }
+  tls_free_cert(&tls_out.ourcert);
+  tls_free_cert(&tls_out.peercert);
   tls_out.cipher = NULL;
   tls_out.peerdn = NULL;
   tls_out.ocsp = OCSP_NOT_REQ;
@@ -1223,8 +1264,7 @@ else if (result == DEFER || result == PANIC)
     /* Create the address string for logging. Must not do this earlier, because
     an OK result may be changed to FAIL when a pipe returns text. */
 
-    log_address = string_log_address(addr,
-      (log_write_selector & L_all_parents) != 0, result == OK);
+    log_address = string_log_address(addr, LOGGING(all_parents), result == OK);
 
     s = string_cat(s, &size, &ptr, log_address, Ustrlen(log_address));
 
@@ -1255,6 +1295,11 @@ else if (result == DEFER || result == PANIC)
       s = string_append(s, &size, &ptr, 2, US": ",
         US strerror(addr->basic_errno));
 
+    if (addr->host_used)
+      s = string_append(s, &size, &ptr, 5,
+                       US" H=", addr->host_used->name,
+                       US" [",  addr->host_used->address, US"]");
+
     if (addr->message != NULL)
       s = string_append(s, &size, &ptr, 2, US": ", addr->message);
 
@@ -1297,7 +1342,7 @@ else
 
   if (!testflag(addr, af_ignore_error) &&
       (addr->special_action == SPECIAL_FREEZE ||
-        (sender_address[0] == 0 && addr->p.errors_address == NULL)
+        (sender_address[0] == 0 && addr->prop.errors_address == NULL)
       ))
     {
     frozen_info = (addr->special_action == SPECIAL_FREEZE)? US"" :
@@ -1330,18 +1375,16 @@ else
   /* Create the address string for logging. Must not do this earlier, because
   an OK result may be changed to FAIL when a pipe returns text. */
 
-  log_address = string_log_address(addr,
-    (log_write_selector & L_all_parents) != 0, result == OK);
+  log_address = string_log_address(addr, LOGGING(all_parents), result == OK);
 
   s = string_cat(s, &size, &ptr, log_address, Ustrlen(log_address));
 
-  if ((log_extra_selector & LX_sender_on_delivery) != 0)
+  if (LOGGING(sender_on_delivery))
     s = string_append(s, &size, &ptr, 3, US" F=<", sender_address, US">");
 
   /* Return path may not be set if no delivery actually happened */
 
-  if (used_return_path != NULL &&
-      (log_extra_selector & LX_return_path_on_delivery) != 0)
+  if (used_return_path != NULL && LOGGING(return_path_on_delivery))
     s = string_append(s, &size, &ptr, 3, US" P=<", used_return_path, US">");
 
   if (addr->router != NULL)
@@ -1375,8 +1418,8 @@ else
 
   log_write(0, LOG_MAIN, "** %s", s);
 
-#ifdef EXPERIMENTAL_TPDA
-  tpda_msg_event(US"msg:fail:delivery", addr);
+#ifdef EXPERIMENTAL_EVENT
+  msg_event_raise(US"msg:fail:delivery", addr);
 #endif
 
   store_reset(reset_point);
@@ -1797,11 +1840,11 @@ transport_instance *tp = addr->transport;
 /* Set up the return path from the errors or sender address. If the transport
 has its own return path setting, expand it and replace the existing value. */
 
-if(addr->p.errors_address != NULL)
-  return_path = addr->p.errors_address;
+if(addr->prop.errors_address != NULL)
+  return_path = addr->prop.errors_address;
 #ifdef EXPERIMENTAL_SRS
-else if(addr->p.srs_sender != NULL)
-  return_path = addr->p.srs_sender;
+else if(addr->prop.srs_sender != NULL)
+  return_path = addr->prop.srs_sender;
 #endif
 else
   return_path = sender_address;
@@ -2413,9 +2456,9 @@ while (addr_local != NULL)
         (addr->flags & (af_pfr|af_file)) == (next->flags & (af_pfr|af_file)) &&
         (!uses_lp  || Ustrcmp(next->local_part, addr->local_part) == 0) &&
         (!uses_dom || Ustrcmp(next->domain, addr->domain) == 0) &&
-        same_strings(next->p.errors_address, addr->p.errors_address) &&
-        same_headers(next->p.extra_headers, addr->p.extra_headers) &&
-        same_strings(next->p.remove_headers, addr->p.remove_headers) &&
+        same_strings(next->prop.errors_address, addr->prop.errors_address) &&
+        same_headers(next->prop.extra_headers, addr->prop.extra_headers) &&
+        same_strings(next->prop.remove_headers, addr->prop.remove_headers) &&
         same_ugid(tp, addr, next) &&
         ((addr->host_list == NULL && next->host_list == NULL) ||
          (addr->host_list != NULL && next->host_list != NULL &&
@@ -2771,7 +2814,7 @@ sort_remote_deliveries(void)
 {
 int sep = 0;
 address_item **aptr = &addr_remote;
-uschar *listptr = remote_sort_domains;
+const uschar *listptr = remote_sort_domains;
 uschar *pattern;
 uschar patbuf[256];
 
@@ -2786,7 +2829,7 @@ while (*aptr != NULL &&
     {
     address_item **next;
     deliver_domain = (*aptr)->domain;   /* set $domain */
-    if (match_isinlist(deliver_domain, &pattern, UCHAR_MAX+1,
+    if (match_isinlist(deliver_domain, (const uschar **)&pattern, UCHAR_MAX+1,
           &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL) == OK)
       {
       aptr = &((*aptr)->next);
@@ -2796,7 +2839,7 @@ while (*aptr != NULL &&
     next = &((*aptr)->next);
     while (*next != NULL &&
            (deliver_domain = (*next)->domain,  /* Set $domain */
-            match_isinlist(deliver_domain, &pattern, UCHAR_MAX+1,
+            match_isinlist(deliver_domain, (const uschar **)&pattern, UCHAR_MAX+1,
               &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL)) != OK)
       next = &((*next)->next);
 
@@ -2883,6 +2926,8 @@ uschar *ptr = endptr;
 uschar *msg = p->msg;
 BOOL done = p->done;
 BOOL unfinished = TRUE;
+/* minimum size to read is header size including id, subid and length */
+int required = PIPE_HEADER_SIZE;
 
 /* Loop through all items, reading from the pipe when necessary. The pipe
 is set up to be non-blocking, but there are two different Unix mechanisms in
@@ -2905,12 +2950,15 @@ while (!done)
   {
   retry_item *r, **rp;
   int remaining = endptr - ptr;
+  uschar header[PIPE_HEADER_SIZE + 1];
+  uschar id, subid;
+  uschar *endc;
 
   /* Read (first time) or top up the chars in the buffer if necessary.
   There will be only one read if we get all the available data (i.e. don't
   fill the buffer completely). */
 
-  if (remaining < 2500 && unfinished)
+  if (remaining < required && unfinished)
     {
     int len;
     int available = big_buffer_size - remaining;
@@ -2943,17 +2991,63 @@ while (!done)
     won't read any more, as "unfinished" will get set FALSE. */
 
     endptr += len;
+    remaining += len;
     unfinished = len == available;
     }
 
   /* If we are at the end of the available data, exit the loop. */
-
   if (ptr >= endptr) break;
 
+  /* copy and read header */
+  memcpy(header, ptr, PIPE_HEADER_SIZE);
+  header[PIPE_HEADER_SIZE] = '\0'; 
+  id = header[0];
+  subid = header[1];
+  required = Ustrtol(header + 2, &endc, 10) + PIPE_HEADER_SIZE;     /* header + data */
+  if (*endc)
+    {
+    msg = string_sprintf("failed to read pipe from transport process "
+      "%d for transport %s: error reading size from header", pid, addr->transport->driver_name);
+    done = TRUE;
+    break;
+    }
+
+  DEBUG(D_deliver)
+    debug_printf("header read  id:%c,subid:%c,size:%s,required:%d,remaining:%d,unfinished:%d\n", 
+                    id, subid, header+2, required, remaining, unfinished);
+
+  /* is there room for the dataset we want to read ? */
+  if (required > big_buffer_size - PIPE_HEADER_SIZE)
+    {
+    msg = string_sprintf("failed to read pipe from transport process "
+      "%d for transport %s: big_buffer too small! required size=%d buffer size=%d", pid, addr->transport->driver_name,
+      required, big_buffer_size - PIPE_HEADER_SIZE);
+    done = TRUE;
+    break;
+    }
+
+  /* we wrote all datasets with atomic write() calls 
+     remaining < required only happens if big_buffer was too small
+     to get all available data from pipe. unfinished has to be true 
+     as well. */
+  if (remaining < required)
+    {
+    if (unfinished)
+      continue;
+    msg = string_sprintf("failed to read pipe from transport process "
+      "%d for transport %s: required size=%d > remaining size=%d and unfinished=false", 
+      pid, addr->transport->driver_name, required, remaining);
+    done = TRUE;
+    break;
+    }
+
+  /* step behind the header */
+  ptr += PIPE_HEADER_SIZE;
   /* Handle each possible type of item, assuming the complete item is
   available in store. */
 
-  switch (*ptr++)
+  switch (id)
     {
     /* Host items exist only if any hosts were marked unusable. Match
     up by checking the IP address. */
@@ -3050,7 +3144,7 @@ while (!done)
 #ifdef SUPPORT_TLS
     case 'X':
     if (addr == NULL) goto ADDR_MISMATCH;          /* Below, in 'A' handler */
-    switch (*ptr++)
+    switch (subid)
       {
       case '1':
       addr->cipher = NULL;
@@ -3064,15 +3158,17 @@ while (!done)
       break;
 
       case '2':
-      addr->peercert = NULL;
       if (*ptr)
        (void) tls_import_cert(ptr, &addr->peercert);
+      else
+       addr->peercert = NULL;
       break;
 
       case '3':
-      addr->ourcert = NULL;
       if (*ptr)
        (void) tls_import_cert(ptr, &addr->ourcert);
+      else
+       addr->ourcert = NULL;
       break;
 
 # ifndef DISABLE_OCSP
@@ -3088,7 +3184,7 @@ while (!done)
 #endif /*SUPPORT_TLS*/
 
     case 'C':  /* client authenticator information */
-    switch (*ptr++)
+    switch (subid)
       {
       case '1':
        addr->authenticator = (*ptr)? string_copy(ptr) : NULL;
@@ -3109,14 +3205,12 @@ while (!done)
     break;
 #endif
 
-#ifdef EXPERIMENTAL_DSN
     case 'D':
-    if (addr == NULL) break;
+    if (addr == NULL) goto ADDR_MISMATCH;
     memcpy(&(addr->dsn_aware), ptr, sizeof(addr->dsn_aware));
     ptr += sizeof(addr->dsn_aware);
     DEBUG(D_deliver) debug_printf("DSN read: addr->dsn_aware = %d\n", addr->dsn_aware);
     break;
-#endif
 
     case 'A':
     if (addr == NULL)
@@ -3129,41 +3223,64 @@ while (!done)
       break;
       }
 
-    addr->transport_return = *ptr++;
-    addr->special_action = *ptr++;
-    memcpy(&(addr->basic_errno), ptr, sizeof(addr->basic_errno));
-    ptr += sizeof(addr->basic_errno);
-    memcpy(&(addr->more_errno), ptr, sizeof(addr->more_errno));
-    ptr += sizeof(addr->more_errno);
-    memcpy(&(addr->flags), ptr, sizeof(addr->flags));
-    ptr += sizeof(addr->flags);
-    addr->message = (*ptr)? string_copy(ptr) : NULL;
-    while(*ptr++);
-    addr->user_message = (*ptr)? string_copy(ptr) : NULL;
-    while(*ptr++);
+    switch (subid)
+      {
+#ifdef EXPERIMENTAL_DSN_INFO
+      case '1':        /* must arrive before A0, and applies to that addr */
+               /* Two strings: smtp_greeting and helo_response */
+       addr->smtp_greeting = string_copy(ptr);
+       while(*ptr++);
+       addr->helo_response = string_copy(ptr);
+       while(*ptr++);
+       break;
+#endif
+
+      case '0':
+       addr->transport_return = *ptr++;
+       addr->special_action = *ptr++;
+       memcpy(&(addr->basic_errno), ptr, sizeof(addr->basic_errno));
+       ptr += sizeof(addr->basic_errno);
+       memcpy(&(addr->more_errno), ptr, sizeof(addr->more_errno));
+       ptr += sizeof(addr->more_errno);
+       memcpy(&(addr->flags), ptr, sizeof(addr->flags));
+       ptr += sizeof(addr->flags);
+       addr->message = (*ptr)? string_copy(ptr) : NULL;
+       while(*ptr++);
+       addr->user_message = (*ptr)? string_copy(ptr) : NULL;
+       while(*ptr++);
 
-    /* Always two strings for host information, followed by the port number and DNSSEC mark */
+       /* Always two strings for host information, followed by the port number and DNSSEC mark */
 
-    if (*ptr != 0)
-      {
-      h = store_get(sizeof(host_item));
-      h->name = string_copy(ptr);
-      while (*ptr++);
-      h->address = string_copy(ptr);
-      while(*ptr++);
-      memcpy(&(h->port), ptr, sizeof(h->port));
-      ptr += sizeof(h->port);
-      h->dnssec = *ptr == '2' ? DS_YES
-               : *ptr == '1' ? DS_NO
-               : DS_UNK;
-      ptr++;
-      addr->host_used = h;
+       if (*ptr != 0)
+         {
+         h = store_get(sizeof(host_item));
+         h->name = string_copy(ptr);
+         while (*ptr++);
+         h->address = string_copy(ptr);
+         while(*ptr++);
+         memcpy(&(h->port), ptr, sizeof(h->port));
+         ptr += sizeof(h->port);
+         h->dnssec = *ptr == '2' ? DS_YES
+                   : *ptr == '1' ? DS_NO
+                   : DS_UNK;
+         ptr++;
+         addr->host_used = h;
+         }
+       else ptr++;
+
+       /* Finished with this address */
+
+       addr = addr->next;
+       break;
       }
-    else ptr++;
-
-    /* Finished with this address */
+    break;
 
-    addr = addr->next;
+    /* Local interface address/port */
+    case 'I':
+    if (*ptr) sending_ip_address = string_copy(ptr);
+    while (*ptr++) ;
+    if (*ptr) sending_port = atoi(CS ptr);
+    while (*ptr++) ;
     break;
 
     /* Z marks the logical end of the data. It is followed by '0' if
@@ -3179,7 +3296,7 @@ while (!done)
       continue_hostname = NULL;
       }
     done = TRUE;
-    DEBUG(D_deliver) debug_printf("Z%c item read\n", *ptr);
+    DEBUG(D_deliver) debug_printf("Z0%c item read\n", *ptr);
     break;
 
     /* Anything else is a disaster. */
@@ -3632,9 +3749,40 @@ while (parcount > max)
 
 
 static void
-rmt_dlv_checked_write(int fd, void * buf, int size)
+rmt_dlv_checked_write(int fd, char id, char subid, void * buf, int size)
 {
-int ret = write(fd, buf, size);
+uschar writebuffer[PIPE_HEADER_SIZE + BIG_BUFFER_SIZE];
+int     header_length;
+
+/* we assume that size can't get larger then BIG_BUFFER_SIZE which currently is set to 16k */
+/* complain to log if someone tries with buffer sizes we can't handle*/
+
+if (size > 99999)
+{
+  log_write(0, LOG_MAIN|LOG_PANIC_DIE, 
+    "Failed writing transport result to pipe: can't handle buffers > 99999 bytes. truncating!\n");
+  size = 99999;
+}
+
+/* to keep the write() atomic we build header in writebuffer and copy buf behind */
+/* two write() calls would increase the complexity of reading from pipe */
+
+/* convert size to human readable string prepended by id and subid */
+header_length = snprintf(CS writebuffer, PIPE_HEADER_SIZE+1, "%c%c%05d", id, subid, size);
+if (header_length != PIPE_HEADER_SIZE)
+{
+  log_write(0, LOG_MAIN|LOG_PANIC_DIE, "header snprintf failed\n");
+  writebuffer[0] = '\0';
+}
+
+DEBUG(D_deliver) debug_printf("header write id:%c,subid:%c,size:%d,final:%s\n", 
+                                 id, subid, size, writebuffer);
+
+if (buf && size > 0)
+  memcpy(writebuffer + PIPE_HEADER_SIZE, buf, size);
+
+size += PIPE_HEADER_SIZE;
+int ret = write(fd, writebuffer, size);
 if(ret != size)
   log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Failed writing transport result to pipe: %s\n",
     ret == -1 ? strerror(errno) : "short write");
@@ -3756,9 +3904,20 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
     }
 
   /* Get the flag which specifies whether the transport can handle different
-  domains that nevertheless resolve to the same set of hosts. */
-
-  multi_domain = tp->multi_domain;
+  domains that nevertheless resolve to the same set of hosts. If it needs
+  expanding, get variables set: $address_data, $domain_data, $localpart_data,
+  $host, $host_address, $host_port. */
+  if (tp->expand_multi_domain)
+    deliver_set_expansions(addr);
+
+  if (exp_bool(addr, US"transport", tp->name, D_transport,
+               US"multi_domain", tp->multi_domain, tp->expand_multi_domain,
+               &multi_domain) != OK)
+    {
+    deliver_set_expansions(NULL);
+    remote_post_process(addr, LOG_MAIN|LOG_PANIC, addr->message, fallback);
+    continue;
+    }
 
   /* Get the maximum it can handle in one envelope, with zero meaning
   unlimited, which is forced for the MUA wrapper case. */
@@ -3827,26 +3986,35 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
   entirely different domains. The host list pointers can be NULL in the case
   where the hosts are defined in the transport. There is also a configured
   maximum limit of addresses that can be handled at once (see comments above
-  for how it is computed). */
+  for how it is computed).
+  If the transport does not handle multiple domains, enforce that also,
+  and if it might need a per-address check for this, re-evaluate it.
+  */
 
   while ((next = *anchor) != NULL && address_count < address_count_max)
     {
-    if ((multi_domain || Ustrcmp(next->domain, addr->domain) == 0)
-        &&
-        tp == next->transport
-        &&
-        same_hosts(next->host_list, addr->host_list)
-        &&
-        same_strings(next->p.errors_address, addr->p.errors_address)
-        &&
-        same_headers(next->p.extra_headers, addr->p.extra_headers)
-        &&
-        same_ugid(tp, next, addr)
-        &&
-        (next->p.remove_headers == addr->p.remove_headers ||
-          (next->p.remove_headers != NULL &&
-           addr->p.remove_headers != NULL &&
-           Ustrcmp(next->p.remove_headers, addr->p.remove_headers) == 0)))
+    BOOL md;
+    if (  (multi_domain || Ustrcmp(next->domain, addr->domain) == 0)
+       && tp == next->transport
+       && same_hosts(next->host_list, addr->host_list)
+       && same_strings(next->prop.errors_address, addr->prop.errors_address)
+       && same_headers(next->prop.extra_headers, addr->prop.extra_headers)
+       && same_ugid(tp, next, addr)
+       && (  next->prop.remove_headers == addr->prop.remove_headers
+         || (  next->prop.remove_headers
+            && addr->prop.remove_headers
+            && Ustrcmp(next->prop.remove_headers, addr->prop.remove_headers) == 0
+         )  )
+       && (  !multi_domain
+         || (  (
+               !tp->expand_multi_domain || (deliver_set_expansions(next), 1),
+               exp_bool(addr,
+                   US"transport", next->transport->name, D_transport,
+                   US"multi_domain", next->transport->multi_domain,
+                   next->transport->expand_multi_domain, &md) == OK
+               )
+            && md
+       )  )  )
       {
       *anchor = next->next;
       next->next = NULL;
@@ -3856,6 +4024,7 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
       address_count++;
       }
     else anchor = &(next->next);
+    deliver_set_expansions(NULL);
     }
 
   /* If we are acting as an MUA wrapper, all addresses must go in a single
@@ -3878,11 +4047,11 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
   /* Compute the return path, expanding a new one if required. The old one
   must be set first, as it might be referred to in the expansion. */
 
-  if(addr->p.errors_address != NULL)
-    return_path = addr->p.errors_address;
+  if(addr->prop.errors_address != NULL)
+    return_path = addr->prop.errors_address;
 #ifdef EXPERIMENTAL_SRS
-  else if(addr->p.srs_sender != NULL)
-    return_path = addr->p.srs_sender;
+  else if(addr->prop.srs_sender != NULL)
+    return_path = addr->prop.srs_sender;
 #endif
   else
     return_path = sender_address;
@@ -4160,8 +4329,8 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
     for (h = addr->host_list; h != NULL; h = h->next)
       {
       if (h->address == NULL || h->status < hstatus_unusable) continue;
-      sprintf(CS big_buffer, "H%c%c%s", h->status, h->why, h->address);
-      rmt_dlv_checked_write(fd, big_buffer, Ustrlen(big_buffer+3) + 4);
+      sprintf(CS big_buffer, "%c%c%s", h->status, h->why, h->address);
+      rmt_dlv_checked_write(fd, 'H', '0', big_buffer, Ustrlen(big_buffer+2) + 3);
       }
 
     /* The number of bytes written. This is the same for each address. Even
@@ -4169,9 +4338,8 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
     size of each one is the same, and it's that value we have got because
     transport_count gets reset before calling transport_write_message(). */
 
-    big_buffer[0] = 'S';
-    memcpy(big_buffer+1, &transport_count, sizeof(transport_count));
-    rmt_dlv_checked_write(fd, big_buffer, sizeof(transport_count) + 1);
+    memcpy(big_buffer, &transport_count, sizeof(transport_count));
+    rmt_dlv_checked_write(fd, 'S', '0', big_buffer, sizeof(transport_count));
 
     /* Information about what happened to each address. Four item types are
     used: an optional 'X' item first, for TLS information, then an optional "C"
@@ -4194,7 +4362,7 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
       if (addr->cipher)
         {
         ptr = big_buffer;
-        sprintf(CS ptr, "X1%.128s", addr->cipher);
+        sprintf(CS ptr, "%.128s", addr->cipher);
         while(*ptr++);
         if (!addr->peerdn)
          *ptr++ = 0;
@@ -4204,35 +4372,33 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
           while(*ptr++);
           }
 
-        rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer);
+        rmt_dlv_checked_write(fd, 'X', '1', big_buffer, ptr - big_buffer);
         }
       if (addr->peercert)
        {
         ptr = big_buffer;
-       *ptr++ = 'X'; *ptr++ = '2';
        if (!tls_export_cert(ptr, big_buffer_size-2, addr->peercert))
          while(*ptr++);
        else
          *ptr++ = 0;
-        rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer);
+        rmt_dlv_checked_write(fd, 'X', '2', big_buffer, ptr - big_buffer);
        }
       if (addr->ourcert)
        {
         ptr = big_buffer;
-       *ptr++ = 'X'; *ptr++ = '3';
        if (!tls_export_cert(ptr, big_buffer_size-2, addr->ourcert))
          while(*ptr++);
        else
          *ptr++ = 0;
-        rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer);
+        rmt_dlv_checked_write(fd, 'X', '3', big_buffer, ptr - big_buffer);
        }
 # ifndef DISABLE_OCSP
       if (addr->ocsp > OCSP_NOT_REQ)
        {
        ptr = big_buffer;
-       sprintf(CS ptr, "X4%c", addr->ocsp + '0');
+       sprintf(CS ptr, "%c", addr->ocsp + '0');
        while(*ptr++);
-        rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer);
+        rmt_dlv_checked_write(fd, 'X', '4', big_buffer, ptr - big_buffer);
        }
 # endif
 #endif /*SUPPORT_TLS*/
@@ -4240,43 +4406,39 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
       if (client_authenticator)
         {
         ptr = big_buffer;
-       sprintf(CS big_buffer, "C1%.64s", client_authenticator);
+       sprintf(CS big_buffer, "%.64s", client_authenticator);
         while(*ptr++);
-        rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer);
+        rmt_dlv_checked_write(fd, 'C', '1', big_buffer, ptr - big_buffer);
        }
       if (client_authenticated_id)
         {
         ptr = big_buffer;
-       sprintf(CS big_buffer, "C2%.64s", client_authenticated_id);
+       sprintf(CS big_buffer, "%.64s", client_authenticated_id);
         while(*ptr++);
-        rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer);
+        rmt_dlv_checked_write(fd, 'C', '2', big_buffer, ptr - big_buffer);
        }
       if (client_authenticated_sender)
         {
         ptr = big_buffer;
-       sprintf(CS big_buffer, "C3%.64s", client_authenticated_sender);
+       sprintf(CS big_buffer, "%.64s", client_authenticated_sender);
         while(*ptr++);
-        rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer);
+        rmt_dlv_checked_write(fd, 'C', '3', big_buffer, ptr - big_buffer);
        }
 
 #ifndef DISABLE_PRDR
       if (addr->flags & af_prdr_used)
-       rmt_dlv_checked_write(fd, "P", 1);
+       rmt_dlv_checked_write(fd, 'P', '0', NULL, 0);
 #endif
 
-#ifdef EXPERIMENTAL_DSN
-      big_buffer[0] = 'D';
-      memcpy(big_buffer+1, &addr->dsn_aware, sizeof(addr->dsn_aware));
-      rmt_dlv_checked_write(fd, big_buffer, sizeof(addr->dsn_aware) + 1);
+      memcpy(big_buffer, &addr->dsn_aware, sizeof(addr->dsn_aware));
+      rmt_dlv_checked_write(fd, 'D', '0', big_buffer, sizeof(addr->dsn_aware));
       DEBUG(D_deliver) debug_printf("DSN write: addr->dsn_aware = %d\n", addr->dsn_aware);
-#endif
 
       /* Retry information: for most success cases this will be null. */
 
       for (r = addr->retries; r != NULL; r = r->next)
         {
-        uschar *ptr;
-        sprintf(CS big_buffer, "R%c%.500s", r->flags, r->key);
+        sprintf(CS big_buffer, "%c%.500s", r->flags, r->key);
         ptr = big_buffer + Ustrlen(big_buffer+2) + 3;
         memcpy(ptr, &(r->basic_errno), sizeof(r->basic_errno));
         ptr += sizeof(r->basic_errno);
@@ -4287,14 +4449,34 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
           sprintf(CS ptr, "%.512s", r->message);
           while(*ptr++);
           }
-        rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer);
+        rmt_dlv_checked_write(fd, 'R', '0', big_buffer, ptr - big_buffer);
         }
 
-      /* The rest of the information goes in an 'A' item. */
+#ifdef EXPERIMENTAL_DSN_INFO
+/*um, are they really per-addr?  Other per-conn stuff is not (auth, tls).  But host_used is! */
+      if (addr->smtp_greeting)
+       {
+       ptr = big_buffer;
+       DEBUG(D_deliver) debug_printf("smtp_greeting '%s'\n", addr->smtp_greeting);
+        sprintf(CS ptr, "%.128s", addr->smtp_greeting);
+        while(*ptr++);
+       if (addr->helo_response)
+         {
+         DEBUG(D_deliver) debug_printf("helo_response '%s'\n", addr->helo_response);
+         sprintf(CS ptr, "%.128s", addr->helo_response);
+         while(*ptr++);
+         }
+       else
+         *ptr++ = '\0';
+        rmt_dlv_checked_write(fd, 'A', '1', big_buffer, ptr - big_buffer);
+       }
+#endif
+
+      /* The rest of the information goes in an 'A0' item. */
 
-      ptr = big_buffer + 3;
-      sprintf(CS big_buffer, "A%c%c", addr->transport_return,
+      sprintf(CS big_buffer, "%c%c", addr->transport_return,
         addr->special_action);
+      ptr = big_buffer + 2;
       memcpy(ptr, &(addr->basic_errno), sizeof(addr->basic_errno));
       ptr += sizeof(addr->basic_errno);
       memcpy(ptr, &(addr->more_errno), sizeof(addr->more_errno));
@@ -4328,7 +4510,23 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
               : addr->host_used->dnssec==DS_NO ? '1' : '0';
 
         }
-      rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer);
+      rmt_dlv_checked_write(fd, 'A', '0', big_buffer, ptr - big_buffer);
+      }
+
+    /* Local interface address/port */
+#ifdef EXPERIMENTAL_DSN_INFO
+    if (sending_ip_address)
+#else
+    if (LOGGING(incoming_interface) && sending_ip_address)
+#endif
+      {
+      uschar * ptr = big_buffer;
+      sprintf(CS ptr, "%.128s", sending_ip_address);
+      while(*ptr++);
+      sprintf(CS ptr, "%d", sending_port);
+      while(*ptr++);
+
+      rmt_dlv_checked_write(fd, 'I', '0', big_buffer, ptr - big_buffer);
       }
 
     /* Add termination flag, close the pipe, and that's it. The character
@@ -4336,9 +4534,8 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
     A change from non-NULL to NULL indicates a problem with a continuing
     connection. */
 
-    big_buffer[0] = 'Z';
-    big_buffer[1] = (continue_transport == NULL)? '0' : '1';
-    rmt_dlv_checked_write(fd, big_buffer, 2);
+    big_buffer[0] = (continue_transport == NULL)? '0' : '1';
+    rmt_dlv_checked_write(fd, 'Z', '0', big_buffer, 1);
     (void)close(fd);
     exit(EXIT_SUCCESS);
     }
@@ -4458,7 +4655,7 @@ if (percent_hack_domains != NULL)
 
   deliver_domain = addr->domain;  /* set $domain */
 
-  while ((rc = match_isinlist(deliver_domain, &percent_hack_domains, 0,
+  while ((rc = match_isinlist(deliver_domain, (const uschar **)&percent_hack_domains, 0,
            &domainlist_anchor, addr->domain_cache, MCL_DOMAIN, TRUE, NULL))
              == OK &&
          (t = Ustrrchr(local_part, '%')) != NULL)
@@ -4640,6 +4837,10 @@ if (ancestor != addr)
       string_printing(original));
   }
 
+if (addr->host_used)
+  fprintf(f, "\n    host %s [%s]",
+         addr->host_used->name, addr->host_used->address);
+
 fprintf(f, "%s", CS se);
 return yield;
 }
@@ -4677,15 +4878,12 @@ print_address_error(address_item *addr, FILE *f, uschar *t)
 int count = Ustrlen(t);
 uschar *s = testflag(addr, af_pass_message)? addr->message : NULL;
 
-if (s == NULL)
-  {
-  if (addr->user_message != NULL) s = addr->user_message; else return;
-  }
+if (!s && !(s = addr->user_message))
+  return;
 
 fprintf(f, "\n    %s", t);
 
-while (*s != 0)
-  {
+while (*s)
   if (*s == '\\' && s[1] == 'n')
     {
     fprintf(f, "\n    ");
@@ -4702,12 +4900,59 @@ while (*s != 0)
       count = 0;
       }
     }
-  }
 }
 
 
+/***********************************************************
+*         Print Diagnostic-Code for an address             *
+************************************************************/
+
+/* This function is called to print the error information out of an address for
+a bounce or a warning message. It tries to format the message reasonably as
+required by RFC 3461 by adding a space after each newline
+
+it uses the same logic as print_address_error() above. if af_pass_message is true
+and addr->message is set it uses the remote host answer. if not addr->user_message
+is used instead if available.
+
+Arguments:
+  addr         the address
+  f            the FILE to print on
+
+Returns:       nothing
+*/
+
+static void
+print_dsn_diagnostic_code(const address_item *addr, FILE *f)
+{
+uschar *s = testflag(addr, af_pass_message) ? addr->message : NULL;
+
+/* af_pass_message and addr->message set ? print remote host answer */
+if (s)
+  {
+  DEBUG(D_deliver)
+    debug_printf("DSN Diagnostic-Code: addr->message = %s\n", addr->message);
+
+  /* search first ": ". we assume to find the remote-MTA answer there */
+  if (!(s = Ustrstr(addr->message, ": ")))
+    return;                            /* not found, bail out */
+  s += 2;  /* skip ": " */
+  fprintf(f, "Diagnostic-Code: smtp; ");
+  }
+/* no message available. do nothing */
+else return;
 
+while (*s)
+  if (*s == '\\' && s[1] == 'n')
+    {
+    fputs("\n ", f);    /* as defined in RFC 3461 */
+    s += 2;
+    }
+  else
+    fputc(*s++, f);
 
+fputc('\n', f);
+}
 
 
 /*************************************************
@@ -5416,18 +5661,28 @@ if (process_recipients != RECIP_IGNORE)
       {
       recipient_item *r = recipients_list + i;
       address_item *new = deliver_make_addr(r->address, FALSE);
-      new->p.errors_address = r->errors_to;
+      new->prop.errors_address = r->errors_to;
+#ifdef EXPERIMENTAL_INTERNATIONAL
+      if ((new->prop.utf8_msg = message_smtputf8))
+       {
+       new->prop.utf8_downcvt =       message_utf8_downconvert == 1;
+       new->prop.utf8_downcvt_maybe = message_utf8_downconvert == -1;
+       DEBUG(D_deliver) debug_printf("utf8, downconvert %s\n",
+         new->prop.utf8_downcvt ? "yes"
+         : new->prop.utf8_downcvt_maybe ? "ifneeded"
+         : "no");
+       }
+#endif
 
       if (r->pno >= 0)
         new->onetime_parent = recipients_list[r->pno].address;
 
-#ifdef EXPERIMENTAL_DSN
       /* If DSN support is enabled, set the dsn flags and the original receipt 
          to be passed on to other DSN enabled MTAs */
       new->dsn_flags = r->dsn_flags & rf_dsnflags;
       new->dsn_orcpt = r->orcpt;
-      DEBUG(D_deliver) debug_printf("DSN: set orcpt: %s  flags: %d\n", new->dsn_orcpt, new->dsn_flags);
-#endif
+      DEBUG(D_deliver) debug_printf("DSN: set orcpt: %s  flags: %d\n",
+       new->dsn_orcpt, new->dsn_flags);
 
       switch (process_recipients)
         {
@@ -5498,18 +5753,18 @@ if (process_recipients != RECIP_IGNORE)
         break;
         }
 
-#ifdef EXPERIMENTAL_TPDA
+#ifdef EXPERIMENTAL_EVENT
       if (process_recipients != RECIP_ACCEPT)
        {
        uschar * save_local =  deliver_localpart;
-       uschar * save_domain = deliver_domain;
+       const uschar * save_domain = deliver_domain;
 
        deliver_localpart = expand_string(
                      string_sprintf("${local_part:%s}", new->address));
        deliver_domain =    expand_string(
                      string_sprintf("${domain:%s}", new->address));
 
-       (void) tpda_raise_event(delivery_event_action,
+       (void) event_raise(event_action,
                      US"msg:fail:internal", new->message);
 
        deliver_localpart = save_local;
@@ -5767,7 +6022,7 @@ while (addr_new != NULL)           /* Loop until all addresses dealt with */
 
     deliver_domain = addr->domain;  /* set $domain */
     if (!forced && hold_domains != NULL &&
-         (rc = match_isinlist(addr->domain, &hold_domains, 0,
+         (rc = match_isinlist(addr->domain, (const uschar **)&hold_domains, 0,
            &domainlist_anchor, addr->domain_cache, MCL_DOMAIN, TRUE,
            NULL)) != FAIL)
       {
@@ -5974,7 +6229,7 @@ while (addr_new != NULL)           /* Loop until all addresses dealt with */
       addr_route = addr->next;
 
       deliver_domain = addr->domain;  /* set $domain */
-      if ((rc = match_isinlist(addr->domain, &queue_domains, 0,
+      if ((rc = match_isinlist(addr->domain, (const uschar **)&queue_domains, 0,
             &domainlist_anchor, addr->domain_cache, MCL_DOMAIN, TRUE, NULL))
               != OK)
         {
@@ -6007,15 +6262,15 @@ while (addr_new != NULL)           /* Loop until all addresses dealt with */
     {
     int rc;
     address_item *addr = addr_route;
-    uschar *old_domain = addr->domain;
+    const uschar *old_domain = addr->domain;
     uschar *old_unique = addr->unique;
     addr_route = addr->next;
     addr->next = NULL;
 
     /* Just in case some router parameter refers to it. */
 
-    return_path = (addr->p.errors_address != NULL)?
-      addr->p.errors_address : sender_address;
+    return_path = (addr->prop.errors_address != NULL)?
+      addr->prop.errors_address : sender_address;
 
     /* If a router defers an address, add a retry item. Whether or not to
     use the local part in the key is a property of the router. */
@@ -6085,8 +6340,8 @@ while (addr_new != NULL)           /* Loop until all addresses dealt with */
 
     if (addr_remote == addr &&
         addr->router->same_domain_copy_routing &&
-        addr->p.extra_headers == NULL &&
-        addr->p.remove_headers == NULL &&
+        addr->prop.extra_headers == NULL &&
+        addr->prop.remove_headers == NULL &&
         old_domain == addr->domain)
       {
       address_item **chain = &addr_route;
@@ -6113,7 +6368,7 @@ while (addr_new != NULL)           /* Loop until all addresses dealt with */
         addr2->transport = addr->transport;
         addr2->host_list = addr->host_list;
         addr2->fallback_hosts = addr->fallback_hosts;
-        addr2->p.errors_address = addr->p.errors_address;
+        addr2->prop.errors_address = addr->prop.errors_address;
         copyflag(addr2, addr, af_hide_child | af_local_host_removed);
 
         DEBUG(D_deliver|D_route)
@@ -6372,31 +6627,7 @@ if (addr_remote != NULL)
   /* Precompile some regex that are used to recognize parameters in response
   to an EHLO command, if they aren't already compiled. */
 
-  if (regex_PIPELINING == NULL) regex_PIPELINING =
-    regex_must_compile(US"\\n250[\\s\\-]PIPELINING(\\s|\\n|$)", FALSE, TRUE);
-
-  if (regex_SIZE == NULL) regex_SIZE =
-    regex_must_compile(US"\\n250[\\s\\-]SIZE(\\s|\\n|$)", FALSE, TRUE);
-
-  if (regex_AUTH == NULL) regex_AUTH =
-    regex_must_compile(US"\\n250[\\s\\-]AUTH\\s+([\\-\\w\\s]+)(?:\\n|$)",
-      FALSE, TRUE);
-
-#ifdef SUPPORT_TLS
-  if (regex_STARTTLS == NULL) regex_STARTTLS =
-    regex_must_compile(US"\\n250[\\s\\-]STARTTLS(\\s|\\n|$)", FALSE, TRUE);
-#endif
-
-#ifndef DISABLE_PRDR
-  if (regex_PRDR == NULL) regex_PRDR =
-    regex_must_compile(US"\\n250[\\s\\-]PRDR(\\s|\\n|$)", FALSE, TRUE);
-#endif
-
-#ifdef EXPERIMENTAL_DSN
-  /* Set the regex to check for DSN support on remote MTA */
-  if (regex_DSN == NULL) regex_DSN  =
-    regex_must_compile(US"\\n250[\\s\\-]DSN(\\s|\\n|$)", FALSE, TRUE);
-#endif
+  deliver_init();
 
   /* Now sort the addresses if required, and do the deliveries. The yield of
   do_remote_deliveries is FALSE when mua_wrapper is set and all addresses
@@ -6472,6 +6703,7 @@ if (mua_wrapper)
     {
     uschar *s = (addr_failed->user_message != NULL)?
       addr_failed->user_message : addr_failed->message;
+    host_item * host;
 
     fprintf(stderr, "Delivery failed: ");
     if (addr_failed->basic_errno > 0)
@@ -6479,6 +6711,8 @@ if (mua_wrapper)
       fprintf(stderr, "%s", strerror(addr_failed->basic_errno));
       if (s != NULL) fprintf(stderr, ": ");
       }
+    if ((host = addr_failed->host_used))
+      fprintf(stderr, "H=%s [%s]: ", host->name, host->address);
     if (s == NULL)
       {
       if (addr_failed->basic_errno <= 0) fprintf(stderr, "unknown error");
@@ -6502,34 +6736,40 @@ prevents actual delivery. */
 
 else if (!dont_deliver) retry_update(&addr_defer, &addr_failed, &addr_succeed);
 
-#ifdef EXPERIMENTAL_DSN
 /* Send DSN for successful messages */
 addr_dsntmp = addr_succeed;
 addr_senddsn = NULL;
 
-while(addr_dsntmp != NULL)
+while(addr_dsntmp)
   {
-  DEBUG(D_deliver)
-    debug_printf("DSN: processing router : %s\n", addr_dsntmp->router->name);
-
-  DEBUG(D_deliver)
-    debug_printf("DSN: processing successful delivery address: %s\n", addr_dsntmp->address);
-
   /* af_ignore_error not honored here. it's not an error */
-
-  DEBUG(D_deliver) debug_printf("DSN: Sender_address: %s\n", sender_address);
-  DEBUG(D_deliver) debug_printf("DSN: orcpt: %s  flags: %d\n", addr_dsntmp->dsn_orcpt, addr_dsntmp->dsn_flags);
-  DEBUG(D_deliver) debug_printf("DSN: envid: %s  ret: %d\n", dsn_envid, dsn_ret);
-  DEBUG(D_deliver) debug_printf("DSN: Final recipient: %s\n", addr_dsntmp->address);
-  DEBUG(D_deliver) debug_printf("DSN: Remote SMTP server supports DSN: %d\n", addr_dsntmp->dsn_aware);
+  DEBUG(D_deliver)
+    {
+    debug_printf("DSN: processing router : %s\n"
+      "DSN: processing successful delivery address: %s\n"
+      "DSN: Sender_address: %s\n"
+      "DSN: orcpt: %s  flags: %d\n"
+      "DSN: envid: %s  ret: %d\n"
+      "DSN: Final recipient: %s\n"
+      "DSN: Remote SMTP server supports DSN: %d\n",
+      addr_dsntmp->router->name,
+      addr_dsntmp->address,
+      sender_address,
+      addr_dsntmp->dsn_orcpt, addr_dsntmp->dsn_flags,
+      dsn_envid, dsn_ret,
+      addr_dsntmp->address,
+      addr_dsntmp->dsn_aware
+      );
+    }
 
   /* send report if next hop not DSN aware or a router flagged "last DSN hop"
      and a report was requested */
-  if (((addr_dsntmp->dsn_aware != dsn_support_yes) ||
-       ((addr_dsntmp->dsn_flags & rf_dsnlasthop) != 0))
-      &&
-      (((addr_dsntmp->dsn_flags & rf_dsnflags) != 0) &&
-        ((addr_dsntmp->dsn_flags & rf_notify_success) != 0)))
+  if (  (  addr_dsntmp->dsn_aware != dsn_support_yes
+       || addr_dsntmp->dsn_flags & rf_dsnlasthop
+        )
+     && addr_dsntmp->dsn_flags & rf_dsnflags
+     && addr_dsntmp->dsn_flags & rf_notify_success
+     )
     {
     /* copy and relink address_item and send report with all of them at once later */
     address_item *addr_next;
@@ -6539,14 +6779,12 @@ while(addr_dsntmp != NULL)
     addr_senddsn->next = addr_next;
     }
   else
-    {
-      DEBUG(D_deliver) debug_printf("DSN: *** NOT SENDING DSN SUCCESS Message ***\n"); 
-    }
+    DEBUG(D_deliver) debug_printf("DSN: not sending DSN success message\n"); 
 
   addr_dsntmp = addr_dsntmp->next;
   }
 
-if (addr_senddsn != NULL)
+if (addr_senddsn)
   {
   pid_t pid;
   int fd;
@@ -6562,22 +6800,21 @@ if (addr_senddsn != NULL)
       "create child process to send failure message: %s", getpid(),
       getppid(), strerror(errno));
 
-      DEBUG(D_deliver) debug_printf("DSN: child_open_exim failed\n");
-
+    DEBUG(D_deliver) debug_printf("DSN: child_open_exim failed\n");
     }    
   else  /* Creation of child succeeded */
     {
     FILE *f = fdopen(fd, "wb");
     /* header only as required by RFC. only failure DSN needs to honor RET=FULL */
     int topt = topt_add_return_path | topt_no_body;
-    uschar boundaryStr[64];
+    uschar * bound;
      
-    DEBUG(D_deliver) debug_printf("sending error message to: %s\n", sender_address);
+    DEBUG(D_deliver)
+      debug_printf("sending error message to: %s\n", sender_address);
   
     /* build unique id for MIME boundary */
-    snprintf(boundaryStr, sizeof(boundaryStr)-1, TIME_T_FMT "-eximdsn-%d",
-      time(NULL), rand());
-    DEBUG(D_deliver) debug_printf("DSN: MIME boundary: %s\n", boundaryStr);
+    bound = string_sprintf(TIME_T_FMT "-eximdsn-%d", time(NULL), rand());
+    DEBUG(D_deliver) debug_printf("DSN: MIME boundary: %s\n", bound);
   
     if (errors_reply_to)
       fprintf(f, "Reply-To: %s\n", errors_reply_to);
@@ -6594,11 +6831,10 @@ if (addr_senddsn != NULL)
    
        "This message was created automatically by mail delivery software.\n"
        " ----- The following addresses had successful delivery notifications -----\n",
-      qualify_domain_sender, sender_address, boundaryStr, boundaryStr);
+      qualify_domain_sender, sender_address, bound, bound);
 
-    addr_dsntmp = addr_senddsn;
-    while(addr_dsntmp)
-      {
+    for (addr_dsntmp = addr_senddsn; addr_dsntmp;
+        addr_dsntmp = addr_dsntmp->next)
       fprintf(f, "<%s> (relayed %s)\n\n",
        addr_dsntmp->address,
        (addr_dsntmp->dsn_flags & rf_dsnlasthop) == 1
@@ -6607,15 +6843,14 @@ if (addr_senddsn != NULL)
          ? "to non-DSN-aware mailer"
          : "via non \"Remote SMTP\" router"
        );
-      addr_dsntmp = addr_dsntmp->next;
-      }
+
     fprintf(f, "--%s\n"
        "Content-type: message/delivery-status\n\n"
        "Reporting-MTA: dns; %s\n",
-      boundaryStr, smtp_active_hostname);
+      bound, smtp_active_hostname);
 
-    if (dsn_envid != NULL) {
-      /* must be decoded from xtext: see RFC 3461:6.3a */
+    if (dsn_envid)
+      {                        /* must be decoded from xtext: see RFC 3461:6.3a */
       uschar *xdec_envid;
       if (auth_xtextdecode(dsn_envid, &xdec_envid) > 0)
         fprintf(f, "Original-Envelope-ID: %s\n", dsn_envid);
@@ -6637,15 +6872,14 @@ if (addr_senddsn != NULL)
        addr_dsntmp->address);
 
       if (addr_dsntmp->host_used && addr_dsntmp->host_used->name)
-        fprintf(f, "Remote-MTA: dns; %s\nDiagnostic-Code: smtp; 250 Ok\n",
+        fprintf(f, "Remote-MTA: dns; %s\nDiagnostic-Code: smtp; 250 Ok\n\n",
          addr_dsntmp->host_used->name);
       else
-       fprintf(f,"Diagnostic-Code: X-Exim; relayed via non %s router\n",
+       fprintf(f, "Diagnostic-Code: X-Exim; relayed via non %s router\n\n",
          (addr_dsntmp->dsn_flags & rf_dsnlasthop) == 1 ? "DSN" : "SMTP");
-      fputc('\n', f);
       }
 
-    fprintf(f, "--%s\nContent-type: text/rfc822-headers\n\n", boundaryStr);
+    fprintf(f, "--%s\nContent-type: text/rfc822-headers\n\n", bound);
            
     fflush(f);
     transport_filter_argv = NULL;   /* Just in case */
@@ -6655,15 +6889,13 @@ if (addr_senddsn != NULL)
     transport_write_message(NULL, fileno(f), topt, 0, NULL, NULL, NULL, NULL, NULL, 0);
     fflush(f);
 
-    fprintf(f,"\n");       
-    fprintf(f,"--%s--\n", boundaryStr);
+    fprintf(f,"\n--%s--\n", bound);
 
     fflush(f);
     fclose(f);
     rc = child_close(pid, 0);     /* Waits for child to close, no timeout */
     }
   }
-#endif /*EXPERIMENTAL_DSN*/
 
 /* If any addresses failed, we must send a message to somebody, unless
 af_ignore_error is set, in which case no action is taken. It is possible for
@@ -6707,10 +6939,10 @@ while (addr_failed != NULL)
   If neither of these cases obtains, something has gone wrong. Log the
   incident, but then ignore the error. */
 
-  if (sender_address[0] == 0 && addr_failed->p.errors_address == NULL)
+  if (sender_address[0] == 0 && addr_failed->prop.errors_address == NULL)
     {
-    if (!testflag(addr_failed, af_retry_timedout) &&
-        !testflag(addr_failed, af_ignore_error))
+    if (  !testflag(addr_failed, af_retry_timedout)
+       && !testflag(addr_failed, af_ignore_error))
       {
       log_write(0, LOG_MAIN|LOG_PANIC, "internal error: bounce message "
         "failure is neither frozen nor ignored (it's been ignored)");
@@ -6722,22 +6954,20 @@ while (addr_failed != NULL)
   it from the list, throw away any saved message file, log it, and
   mark the recipient done. */
 
-  if (testflag(addr_failed, af_ignore_error)
-#ifdef EXPERIMENTAL_DSN
-      || (((addr_failed->dsn_flags & rf_dsnflags) != 0)
-         && ((addr_failed->dsn_flags & rf_notify_failure) != rf_notify_failure))
-#endif
+  if (  testflag(addr_failed, af_ignore_error)
+     || (  addr_failed->dsn_flags & rf_dsnflags
+        && (addr_failed->dsn_flags & rf_notify_failure) != rf_notify_failure)
      )
-  {
+    {
     addr = addr_failed;
     addr_failed = addr->next;
     if (addr->return_filename != NULL) Uunlink(addr->return_filename);
 
     log_write(0, LOG_MAIN, "%s%s%s%s: error ignored",
       addr->address,
-      (addr->parent == NULL)? US"" : US" <",
-      (addr->parent == NULL)? US"" : addr->parent->address,
-      (addr->parent == NULL)? US"" : US">");
+      !addr->parent ? US"" : US" <",
+      !addr->parent ? US"" : addr->parent->address,
+      !addr->parent ? US"" : US">");
 
     address_done(addr, logtod);
     child_done(addr, logtod);
@@ -6753,16 +6983,12 @@ while (addr_failed != NULL)
 
   else
     {
-    bounce_recipient = (addr_failed->p.errors_address == NULL)?
-      sender_address : addr_failed->p.errors_address;
+    bounce_recipient = addr_failed->prop.errors_address
+      ? addr_failed->prop.errors_address : sender_address;
 
     /* Make a subprocess to send a message */
 
-    pid = child_open_exim(&fd);
-
-    /* Creation of child failed */
-
-    if (pid < 0)
+    if ((pid = child_open_exim(&fd)) < 0)
       log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Process %d (parent %d) failed to "
         "create child process to send failure message: %s", getpid(),
         getppid(), strerror(errno));
@@ -6780,12 +7006,10 @@ while (addr_failed != NULL)
       BOOL to_sender = strcmpic(sender_address, bounce_recipient) == 0;
       int max = (bounce_return_size_limit/DELIVER_IN_BUFFER_SIZE + 1) *
         DELIVER_IN_BUFFER_SIZE;
-#ifdef EXPERIMENTAL_DSN
-      uschar boundaryStr[64];
+      uschar * bound;
       uschar *dsnlimitmsg;
       uschar *dsnnotifyhdr;
       int topt;
-#endif
 
       DEBUG(D_deliver)
         debug_printf("sending error message to: %s\n", bounce_recipient);
@@ -6795,20 +7019,16 @@ while (addr_failed != NULL)
 
       paddr = &addr_failed;
       for (addr = addr_failed; addr != NULL; addr = *paddr)
-        {
-        if (Ustrcmp(bounce_recipient, (addr->p.errors_address == NULL)?
-              sender_address : addr->p.errors_address) != 0)
-          {
-          paddr = &(addr->next);      /* Not the same; skip */
-          }
-        else                          /* The same - dechain */
-          {
+        if (Ustrcmp(bounce_recipient, addr->prop.errors_address
+             ? addr->prop.errors_address : sender_address) == 0)
+          {                          /* The same - dechain */
           *paddr = addr->next;
           *pmsgchain = addr;
           addr->next = NULL;
           pmsgchain = &(addr->next);
           }
-        }
+        else
+          paddr = &addr->next;        /* Not the same; skip */
 
       /* Include X-Failed-Recipients: for automatic interpretation, but do
       not let any one header line get too long. We do this by starting a
@@ -6833,22 +7053,19 @@ while (addr_failed != NULL)
 
       /* Output the standard headers */
 
-      if (errors_reply_to != NULL)
+      if (errors_reply_to)
         fprintf(f, "Reply-To: %s\n", errors_reply_to);
       fprintf(f, "Auto-Submitted: auto-replied\n");
       moan_write_from(f);
       fprintf(f, "To: %s\n", bounce_recipient);
 
-#ifdef EXPERIMENTAL_DSN
       /* generate boundary string and output MIME-Headers */
-      snprintf(boundaryStr, sizeof(boundaryStr)-1, TIME_T_FMT "-eximdsn-%d",
-       time(NULL), rand());
+      bound = string_sprintf(TIME_T_FMT "-eximdsn-%d", time(NULL), rand());
 
       fprintf(f, "Content-Type: multipart/report;"
            " report-type=delivery-status; boundary=%s\n"
          "MIME-Version: 1.0\n",
-       boundaryStr);
-#endif
+       bound);
 
       /* Open a template file if one is provided. Log failure to open, but
       carry on - default texts will be used. */
@@ -6873,12 +7090,10 @@ while (addr_failed != NULL)
         fprintf(f, "Subject: Mail delivery failed%s\n\n",
           to_sender? ": returning message to sender" : "");
 
-#ifdef EXPERIMENTAL_DSN
       /* output human readable part as text/plain section */
       fprintf(f, "--%s\n"
          "Content-type: text/plain; charset=us-ascii\n\n",
-       boundaryStr);
-#endif
+       bound);
 
       if ((emf_text = next_emf(emf, US"intro")))
        fprintf(f, "%s", CS emf_text);
@@ -7005,12 +7220,19 @@ wording. */
        fputc('\n', f);
         }
 
-#ifdef EXPERIMENTAL_DSN
       /* output machine readable part */
-      fprintf(f, "--%s\n"
-         "Content-type: message/delivery-status\n\n"
-         "Reporting-MTA: dns; %s\n",
-       boundaryStr, smtp_active_hostname);
+#ifdef EXPERIMENTAL_INTERNATIONAL
+      if (message_smtputf8)
+       fprintf(f, "--%s\n"
+           "Content-type: message/global-delivery-status\n\n"
+           "Reporting-MTA: dns; %s\n",
+         bound, smtp_active_hostname);
+      else
+#endif
+       fprintf(f, "--%s\n"
+           "Content-type: message/delivery-status\n\n"
+           "Reporting-MTA: dns; %s\n",
+         bound, smtp_active_hostname);
 
       if (dsn_envid)
        {
@@ -7025,15 +7247,34 @@ wording. */
  
       for (addr = handled_addr; addr; addr = addr->next)
         {
+       host_item * hu;
         fprintf(f, "Action: failed\n"
            "Final-Recipient: rfc822;%s\n"
            "Status: 5.0.0\n",
            addr->address);
-        if (addr->host_used && addr->host_used->name)
-          fprintf(f, "Remote-MTA: dns; %s\nDiagnostic-Code: smtp; %d\n",
-           addr->host_used->name, addr->basic_errno);
-        }
+        if ((hu = addr->host_used) && hu->name)
+         {
+         const uschar * s;
+         fprintf(f, "Remote-MTA: dns; %s\n",
+           hu->name);
+#ifdef EXPERIMENTAL_DSN_INFO
+         if (hu->address)
+           {
+           uschar * p = hu->port == 25
+             ? US"" : string_sprintf(":%d", hu->port);
+           fprintf(f, "Remote-MTA: X-ip; [%s]%s\n", hu->address, p);
+           }
+         if ((s = addr->smtp_greeting) && *s)
+           fprintf(f, "X-Remote-MTA-smtp-greeting: X-str; %s\n", s);
+         if ((s = addr->helo_response) && *s)
+           fprintf(f, "X-Remote-MTA-helo-response: X-str; %s\n", s);
+         if ((s = addr->message) && *s)
+           fprintf(f, "X-Exim-Diagnostic: X-str; %s\n", s);
 #endif
+         print_dsn_diagnostic_code(addr, f);
+         }
+       fputc('\n', f);
+        }
 
       /* Now copy the message, trying to give an intelligible comment if
       it is too long for it all to be copied. The limit isn't strictly
@@ -7042,65 +7283,6 @@ wording. */
 
       emf_text = next_emf(emf, US"copy");
 
-#ifndef EXPERIMENTAL_DSN
-      if (bounce_return_message)
-        {
-        int topt = topt_add_return_path;
-        if (!bounce_return_body) topt |= topt_no_body;
-
-        if (emf_text)
-         fprintf(f, "%s", CS emf_text);
-       else
-          {
-          if (bounce_return_body) fprintf(f,
-"------ This is a copy of the message, including all the headers. ------\n");
-          else fprintf(f,
-"------ This is a copy of the message's headers. ------\n");
-          }
-
-        /* While reading the "truncated" message, set return_size_limit to
-        the actual max testing value, rounded. We need to read the message
-        whether we are going to use it or not. */
-
-          {
-          int temp = bounce_return_size_limit;
-          bounce_return_size_limit = (max/1000)*1000;
-          emf_text = next_emf(emf, US"truncated");
-          bounce_return_size_limit = temp;
-          }
-
-        if (bounce_return_body && bounce_return_size_limit > 0)
-          {
-          struct stat statbuf;
-          if (fstat(deliver_datafile, &statbuf) == 0 && statbuf.st_size > max)
-           {
-            if (emf_text)
-             fprintf(f, "%s", CS emf_text);
-           else
-              fprintf(f,
-"------ The body of the message is " OFF_T_FMT " characters long; only the first\n"
-"------ %d or so are included here.\n", statbuf.st_size, max);
-           }
-          }
-
-       fputc('\n', f);
-        fflush(f);
-
-        transport_filter_argv = NULL;   /* Just in case */
-        return_path = sender_address;   /* In case not previously set */
-        transport_write_message(NULL, fileno(f), topt,
-          bounce_return_size_limit, NULL, NULL, NULL, NULL, NULL, 0);
-        }
-
-      /* Write final text and close the template file if one is open */
-
-      if (emf)
-        {
-        if ((emf_text = next_emf(emf, US"final")))
-         fprintf(f, "%s", CS emf_text);
-        (void)fclose(emf);
-        }
-#else
       /* add message body
          we ignore the intro text from template and add 
          the text for bounce_return_size_limit at the end.
@@ -7112,7 +7294,7 @@ wording. */
          bounce_return_size_limit is always honored.
       */
   
-      fprintf(f, "\n--%s\n", boundaryStr);
+      fprintf(f, "--%s\n", bound);
 
       dsnlimitmsg = US"X-Exim-DSN-Information: Due to administrative limits only headers are returned";
       dsnnotifyhdr = NULL;
@@ -7141,10 +7323,16 @@ wording. */
             }
           }
   
-      if (topt & topt_no_body)
-        fprintf(f,"Content-type: text/rfc822-headers\n\n");
+#ifdef EXPERIMENTAL_INTERNATIONAL
+      if (message_smtputf8)
+       fputs(topt & topt_no_body ? "Content-type: message/global-headers\n\n"
+                                 : "Content-type: message/global\n\n",
+             f);
       else
-        fprintf(f,"Content-type: message/rfc822\n\n");
+#endif
+       fputs(topt & topt_no_body ? "Content-type: text/rfc822-headers\n\n"
+                                 : "Content-type: message/rfc822\n\n",
+             f);
 
       fflush(f);
       transport_filter_argv = NULL;   /* Just in case */
@@ -7157,8 +7345,7 @@ wording. */
       if (emf)
         (void)fclose(emf);
  
-      fprintf(f, "\n--%s--\n", boundaryStr);
-#endif /*EXPERIMENTAL_DSN*/
+      fprintf(f, "\n--%s--\n", bound);
 
       /* Close the file, which should send an EOF to the child process
       that is receiving the message. Wait for it to finish. */
@@ -7245,11 +7432,9 @@ if (addr_defer == NULL)
           "msglog.OLD directory", spoolname);
       }
     else
-      {
       if (Uunlink(spoolname) < 0)
         log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to unlink %s: %s",
                  spoolname, strerror(errno));
-      }
     }
 
   /* Remove the two message files. */
@@ -7265,7 +7450,7 @@ if (addr_defer == NULL)
 
   /* Log the end of this message, with queue time if requested. */
 
-  if ((log_extra_selector & LX_queue_time_overall) != 0)
+  if (LOGGING(queue_time_overall))
     log_write(0, LOG_MAIN, "Completed QT=%s",
       readconf_printtime( (int) ((long)time(NULL) - (long)received_time)) );
   else
@@ -7274,8 +7459,8 @@ if (addr_defer == NULL)
   /* Unset deliver_freeze so that we won't try to move the spool files further down */
   deliver_freeze = FALSE;
 
-#ifdef EXPERIMENTAL_TPDA
-  (void) tpda_raise_event(delivery_event_action, US"msg:complete", NULL);
+#ifdef EXPERIMENTAL_EVENT
+  (void) event_raise(event_action, US"msg:complete", NULL);
 #endif
 }
 
@@ -7323,7 +7508,8 @@ else if (addr_defer != (address_item *)(+1))
 
     if (deliver_domain != NULL)
       {
-      uschar *d = (testflag(addr, af_pfr))? addr->parent->domain : addr->domain;
+      const uschar *d = testflag(addr, af_pfr)
+       ? addr->parent->domain : addr->domain;
 
       /* The domain may be unset for an address that has never been routed
       because the system filter froze the message. */
@@ -7361,7 +7547,7 @@ else if (addr_defer != (address_item *)(+1))
         DEBUG(D_deliver) debug_printf("one_time: adding %s in place of %s\n",
           otaddr->address, otaddr->parent->address);
         receive_add_recipient(otaddr->address, t);
-        recipients_list[recipients_count-1].errors_to = otaddr->p.errors_address;
+        recipients_list[recipients_count-1].errors_to = otaddr->prop.errors_address;
         tree_add_nonrecipient(otaddr->parent->address);
         update_spool = TRUE;
         }
@@ -7373,7 +7559,7 @@ else if (addr_defer != (address_item *)(+1))
 
     if (sender_address[0] != 0)
       {
-      if (addr->p.errors_address == NULL)
+      if (addr->prop.errors_address == NULL)
         {
         if (Ustrstr(recipients, sender_address) == NULL)
           recipients = string_sprintf("%s%s%s", recipients,
@@ -7381,9 +7567,9 @@ else if (addr_defer != (address_item *)(+1))
         }
       else
         {
-        if (Ustrstr(recipients, addr->p.errors_address) == NULL)
+        if (Ustrstr(recipients, addr->prop.errors_address) == NULL)
           recipients = string_sprintf("%s%s%s", recipients,
-            (recipients[0] == 0)? "" : ",", addr->p.errors_address);
+            (recipients[0] == 0)? "" : ",", addr->prop.errors_address);
         }
       }
     }
@@ -7393,15 +7579,18 @@ else if (addr_defer != (address_item *)(+1))
   is not sent. Another attempt will be made at the next delivery attempt (if
   it also defers). */
 
-  if (!queue_2stage && delivery_attempted &&
-#ifdef EXPERIMENTAL_DSN
-      (((addr_defer->dsn_flags & rf_dsnflags) == 0) ||
-       (addr_defer->dsn_flags & rf_notify_delay) == rf_notify_delay) &&
-#endif
-      delay_warning[1] > 0 && sender_address[0] != 0 &&
-       (delay_warning_condition == NULL ||
-          expand_check_condition(delay_warning_condition,
-            US"delay_warning", US"option")))
+  if (  !queue_2stage
+     && delivery_attempted
+     && (  ((addr_defer->dsn_flags & rf_dsnflags) == 0)
+        || (addr_defer->dsn_flags & rf_notify_delay) == rf_notify_delay
+       )
+     && delay_warning[1] > 0
+     && sender_address[0] != 0
+     && (  delay_warning_condition == NULL
+        || expand_check_condition(delay_warning_condition,
+            US"delay_warning", US"option")
+       )
+     )
     {
     int count;
     int show_time;
@@ -7462,9 +7651,7 @@ else if (addr_defer != (address_item *)(+1))
         uschar *wmf_text;
         FILE *wmf = NULL;
         FILE *f = fdopen(fd, "wb");
-#ifdef EXPERIMENTAL_DSN
-       uschar boundaryStr[64];
-#endif
+       uschar * bound;
 
         if (warn_message_file)
           {
@@ -7485,16 +7672,13 @@ else if (addr_defer != (address_item *)(+1))
         moan_write_from(f);
         fprintf(f, "To: %s\n", recipients);
 
-#ifdef EXPERIMENTAL_DSN
         /* generated boundary string and output MIME-Headers */
-        snprintf(boundaryStr, sizeof(boundaryStr)-1,
-         TIME_T_FMT "-eximdsn-%d", time(NULL), rand());
+        bound = string_sprintf(TIME_T_FMT "-eximdsn-%d", time(NULL), rand());
 
         fprintf(f, "Content-Type: multipart/report;"
            " report-type=delivery-status; boundary=%s\n"
            "MIME-Version: 1.0\n",
-         boundaryStr);
-#endif
+         bound);
 
         if ((wmf_text = next_emf(wmf, US"header")))
           fprintf(f, "%s\n", wmf_text);
@@ -7502,12 +7686,10 @@ else if (addr_defer != (address_item *)(+1))
           fprintf(f, "Subject: Warning: message %s delayed %s\n\n",
             message_id, warnmsg_delay);
 
-#ifdef EXPERIMENTAL_DSN
         /* output human readable part as text/plain section */
         fprintf(f, "--%s\n"
            "Content-type: text/plain; charset=us-ascii\n\n",
-         boundaryStr);
-#endif
+         bound);
 
         if ((wmf_text = next_emf(wmf, US"intro")))
          fprintf(f, "%s", CS wmf_text);
@@ -7546,10 +7728,8 @@ else if (addr_defer != (address_item *)(+1))
 
         /* List the addresses, with error information if allowed */
 
-#ifdef EXPERIMENTAL_DSN
         /* store addr_defer for machine readable part */
         address_item *addr_dsndefer = addr_defer;
-#endif
         fputc('\n', f);
         while (addr_defer)
           {
@@ -7578,12 +7758,11 @@ else if (addr_defer != (address_item *)(+1))
 "and when that happens, the message will be returned to you.\n");
           }
 
-#ifdef EXPERIMENTAL_DSN
         /* output machine readable part */
         fprintf(f, "\n--%s\n"
            "Content-type: message/delivery-status\n\n"
            "Reporting-MTA: dns; %s\n",
-         boundaryStr,
+         bound,
          smtp_active_hostname);
  
 
@@ -7598,23 +7777,27 @@ else if (addr_defer != (address_item *)(+1))
           }
         fputc('\n', f);
 
-        while (addr_dsndefer)
+        for ( ; addr_dsndefer; addr_dsndefer = addr_dsndefer->next)
           {
           if (addr_dsndefer->dsn_orcpt)
-            fprintf(f,"Original-Recipient: %s\n", addr_dsndefer->dsn_orcpt);
+            fprintf(f, "Original-Recipient: %s\n", addr_dsndefer->dsn_orcpt);
 
-          fprintf(f,"Action: delayed\n");
-          fprintf(f,"Final-Recipient: rfc822;%s\n", addr_dsndefer->address);
-          fprintf(f,"Status: 4.0.0\n");
+          fprintf(f, "Action: delayed\n"
+             "Final-Recipient: rfc822;%s\n"
+             "Status: 4.0.0\n",
+           addr_dsndefer->address);
           if (addr_dsndefer->host_used && addr_dsndefer->host_used->name)
-            fprintf(f,"Remote-MTA: dns; %s\nDiagnostic-Code: smtp; %d\n", 
-                   addr_dsndefer->host_used->name, addr_dsndefer->basic_errno);
-          addr_dsndefer = addr_dsndefer->next;
+            {
+            fprintf(f, "Remote-MTA: dns; %s\n", 
+                   addr_dsndefer->host_used->name);
+            print_dsn_diagnostic_code(addr_dsndefer, f);
+            }
+         fputc('\n', f);
           }
 
-        fprintf(f, "\n--%s\n"
+        fprintf(f, "--%s\n"
            "Content-type: text/rfc822-headers\n\n",
-         boundaryStr);
+         bound);
 
         fflush(f);
         /* header only as required by RFC. only failure DSN needs to honor RET=FULL */
@@ -7625,10 +7808,9 @@ else if (addr_defer != (address_item *)(+1))
         transport_write_message(NULL, fileno(f), topt, 0, NULL, NULL, NULL, NULL, NULL, 0);
         fflush(f);
 
-        fprintf(f,"\n--%s--\n", boundaryStr);
+        fprintf(f,"\n--%s--\n", bound);
 
         fflush(f);
-#endif /*EXPERIMENTAL_DSN*/
 
         /* Close and wait for child process to complete, without a timeout.
         If there's an error, don't update the count. */
@@ -7766,6 +7948,79 @@ acl_where = ACL_WHERE_UNKNOWN;
 return final_yield;
 }
 
+
+
+void
+deliver_init(void)
+{
+if (!regex_PIPELINING) regex_PIPELINING =
+  regex_must_compile(US"\\n250[\\s\\-]PIPELINING(\\s|\\n|$)", FALSE, TRUE);
+
+if (!regex_SIZE) regex_SIZE =
+  regex_must_compile(US"\\n250[\\s\\-]SIZE(\\s|\\n|$)", FALSE, TRUE);
+
+if (!regex_AUTH) regex_AUTH =
+  regex_must_compile(US"\\n250[\\s\\-]AUTH\\s+([\\-\\w\\s]+)(?:\\n|$)",
+    FALSE, TRUE);
+
+#ifdef SUPPORT_TLS
+if (!regex_STARTTLS) regex_STARTTLS =
+  regex_must_compile(US"\\n250[\\s\\-]STARTTLS(\\s|\\n|$)", FALSE, TRUE);
+#endif
+
+#ifndef DISABLE_PRDR
+if (!regex_PRDR) regex_PRDR =
+  regex_must_compile(US"\\n250[\\s\\-]PRDR(\\s|\\n|$)", FALSE, TRUE);
+#endif
+
+#ifdef EXPERIMENTAL_INTERNATIONAL
+if (!regex_UTF8) regex_UTF8 =
+  regex_must_compile(US"\\n250[\\s\\-]SMTPUTF8(\\s|\\n|$)", FALSE, TRUE);
+#endif
+
+if (!regex_DSN) regex_DSN  =
+  regex_must_compile(US"\\n250[\\s\\-]DSN(\\s|\\n|$)", FALSE, TRUE);
+
+if (!regex_IGNOREQUOTA) regex_IGNOREQUOTA =
+  regex_must_compile(US"\\n250[\\s\\-]IGNOREQUOTA(\\s|\\n|$)", FALSE, TRUE);
+}
+
+
+uschar *
+deliver_get_sender_address (uschar * id)
+{
+int rc;
+uschar * new_sender_address,
+       * save_sender_address;
+
+if (!spool_open_datafile(id))
+  return NULL;
+
+/* Save and restore the global sender_address.  I'm not sure if we should
+not save/restore all the other global variables too, because
+spool_read_header() may change all of them. But OTOH, when this
+deliver_get_sender_address() gets called, the current message is done
+already and nobody needs the globals anymore. (HS12, 2015-08-21) */
+
+sprintf(CS spoolname, "%s-H", id);
+save_sender_address = sender_address;
+
+rc = spool_read_header(spoolname, TRUE, TRUE);
+
+new_sender_address = sender_address;
+sender_address = save_sender_address;
+
+if (rc != spool_read_OK)
+  return NULL;
+
+assert(new_sender_address);
+
+(void)close(deliver_datafile);
+deliver_datafile = -1;
+
+return new_sender_address;
+}
+
 /* vi: aw ai sw=2
 */
 /* End of deliver.c */