For connects and certificate-verifies denied by event actions, log
[exim.git] / src / src / deliver.c
index 741d7b79cc31baec27ec1ba1a7d9aa64e9c8df50..27a4344c50ce0aa751c2a8cdb63895018b614381 100644 (file)
@@ -1,10 +1,8 @@
-/* $Cambridge: exim/src/src/deliver.c,v 1.1 2004/10/07 10:39:01 ph10 Exp $ */
-
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2004 */
+/* Copyright (c) University of Cambridge 1995 - 2014 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* The main code for delivering a message. */
@@ -65,6 +63,10 @@ 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;
@@ -139,11 +141,13 @@ the first address. */
 if (addr->host_list == NULL)
   {
   deliver_host = deliver_host_address = US"";
+  deliver_host_port = 0;
   }
 else
   {
   deliver_host = addr->host_list->name;
   deliver_host_address = addr->host_list->address;
+  deliver_host_port = addr->host_list->port;
   }
 
 deliver_recipients = addr;
@@ -156,6 +160,13 @@ deliver_localpart_data = addr->p.localpart_data;
 deliver_domain = addr->domain;
 self_hostname = addr->self_hostname;
 
+#ifdef EXPERIMENTAL_BRIGHTMAIL
+bmi_deliver = 1;    /* deliver by default */
+bmi_alt_location = NULL;
+bmi_base64_verdict = NULL;
+bmi_base64_tracker_verdict = NULL;
+#endif
+
 /* If there's only one address we can set everything. */
 
 if (addr->next == NULL)
@@ -205,14 +216,34 @@ if (addr->next == NULL)
       deliver_localpart_suffix = addr->parent->suffix;
       }
     }
+
+#ifdef EXPERIMENTAL_BRIGHTMAIL
+    /* Set expansion variables related to Brightmail AntiSpam */
+    bmi_base64_verdict = bmi_get_base64_verdict(deliver_localpart_orig, deliver_domain_orig);
+    bmi_base64_tracker_verdict = bmi_get_base64_tracker_verdict(bmi_base64_verdict);
+    /* get message delivery status (0 - don't deliver | 1 - deliver) */
+    bmi_deliver = bmi_get_delivery_status(bmi_base64_verdict);
+    /* if message is to be delivered, get eventual alternate location */
+    if (bmi_deliver == 1) {
+      bmi_alt_location = bmi_get_alt_location(bmi_base64_verdict);
+    };
+#endif
+
   }
 
 /* For multiple addresses, don't set local part, and leave the domain and
-self_hostname set only if it is the same for all of them. */
+self_hostname set only if it is the same for all of them. It is possible to
+have multiple pipe and file addresses, but only when all addresses have routed
+to the same pipe or file. */
 
 else
   {
   address_item *addr2;
+  if (testflag(addr, af_pfr))
+    {
+    if (testflag(addr, af_file)) address_file = addr->local_part;
+      else if (addr->local_part[0] == '|') address_pipe = addr->local_part;
+    }
   for (addr2 = addr->next; addr2 != NULL; addr2 = addr2->next)
     {
     if (deliver_domain != NULL &&
@@ -266,7 +297,7 @@ doesn't always get set automatically. */
 
 if (fd >= 0)
   {
-  fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
+  (void)fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
   if (fchown(fd, exim_uid, exim_gid) < 0)
     {
     *error = US"chown";
@@ -597,7 +628,7 @@ for (dup = addr_duplicate; dup != NULL; dup = dup->next)
   {
   if (Ustrcmp(addr->unique, dup->unique) == 0)
     {
-    tree_add_nonrecipient(dup->address);
+    tree_add_nonrecipient(dup->unique);
     child_done(dup, now);
     }
   }
@@ -648,6 +679,276 @@ while (addr->parent != NULL)
 
 
 
+static uschar *
+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;
+}
+
+#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)
+      ?
+#ifdef EXPERIMENTAL_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;
+}
+#endif
+
+
+
+
+#ifdef EXPERIMENTAL_EVENT
+uschar *
+event_raise(uschar * action, uschar * event, uschar * ev_data)
+{
+uschar * s;
+if (action)
+  {
+  DEBUG(D_deliver)
+    debug_printf("Event(%s): event_action=|%s| delivery_IP=%s\n",
+      event,
+      action, deliver_host_address);
+
+  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 event_action %s in %s: %s\n",
+      event, transport_name, expand_string_message);
+
+  event_name = event_data = NULL;
+
+  /* If the expansion returns anything but an empty string, flag for
+  the caller to modify his normal processing
+  */
+  if (s && *s)
+    {
+    DEBUG(D_deliver)
+      debug_printf("Event(%s): event_action returned \"%s\"\n", event, s);
+    return s;
+    }
+  }
+return NULL;
+}
+
+static void
+msg_event_raise(uschar * event, address_item * addr)
+{
+uschar * save_domain = deliver_domain;
+uschar * save_local =  deliver_localpart;
+uschar * save_host =   deliver_host;
+
+if (!addr->transport)
+  return;
+
+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) 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_EVENT*/
+
+
+
+/* If msg is NULL this is a delivery log and logchar is used. Otherwise
+this is a nonstandard call; no two-character delivery flag is written
+but sender-host and sender are prefixed and "msg" is inserted in the log line.
+
+Arguments:
+  flags                passed to log_write()
+*/
+void
+delivery_log(int flags, address_item * addr, int logchar, uschar * msg)
+{
+uschar *log_address;
+int size = 256;         /* Used for a temporary, */
+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_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);
+if (msg)
+  s = string_append(s, &size, &ptr, 3, host_and_ident(TRUE), US" ", log_address);
+else
+  {
+  s[ptr++] = logchar;
+  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">");
+
+#ifdef EXPERIMENTAL_SRS
+if(addr->p.srs_sender)
+  s = string_append(s, &size, &ptr, 3, US" SRS=<", addr->p.srs_sender, US">");
+#endif
+
+/* You might think that the return path must always be set for a successful
+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)
+  s = string_append(s, &size, &ptr, 3, US" P=<", used_return_path, US">");
+
+if (msg)
+  s = string_append(s, &size, &ptr, 2, US" ", msg);
+
+/* For a delivery from a system filter, there may not be a router */
+if (addr->router != NULL)
+  s = string_append(s, &size, &ptr, 2, US" R=", addr->router->name);
+
+s = string_append(s, &size, &ptr, 2, US" T=", addr->transport->name);
+
+if ((log_extra_selector & LX_delivery_size) != 0)
+  s = string_append(s, &size, &ptr, 2, US" S=",
+    string_sprintf("%d", transport_count));
+
+/* Local delivery */
+
+if (addr->transport->info->local)
+  {
+  if (addr->host_list)
+    s = string_append(s, &size, &ptr, 2, US" H=", addr->host_list->name);
+  if (addr->shadow_message != NULL)
+    s = string_cat(s, &size, &ptr, addr->shadow_message,
+      Ustrlen(addr->shadow_message));
+  }
+
+/* Remote delivery */
+
+else
+  {
+  if (addr->host_used)
+    {
+    s = d_hostlog(s, &size, &ptr, addr);
+    if (continue_sequence > 1)
+      s = string_cat(s, &size, &ptr, US"*", 1);
+
+#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"
+                             : addr->host_used->dnssec==DS_NO ? US"no"
+                             : NULL;
+#endif
+    }
+
+#ifdef SUPPORT_TLS
+  s = d_tlslog(s, &size, &ptr, addr);
+#endif
+
+  if (addr->authenticator)
+    {
+    s = string_append(s, &size, &ptr, 2, US" A=", addr->authenticator);
+    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)
+        s = string_append(s, &size, &ptr, 2, US":", addr->auth_sndr);
+      }
+    }
+
+#ifndef DISABLE_PRDR
+  if (addr->flags & af_prdr_used)
+    s = string_append(s, &size, &ptr, 1, US" PRDR");
+#endif
+  }
+
+/* confirmation message (SMTP (host_used) and LMTP (driver_name)) */
+
+if (log_extra_selector & LX_smtp_confirmation &&
+    addr->message &&
+    (addr->host_used || Ustrcmp(addr->transport->driver_name, "lmtp") == 0))
+  {
+  int i;
+  uschar *p = big_buffer;
+  uschar *ss = addr->message;
+  *p++ = '\"';
+  for (i = 0; i < 256 && ss[i] != 0; i++)      /* limit logged amount */
+    {
+    if (ss[i] == '\"' || ss[i] == '\\') *p++ = '\\'; /* quote \ and " */
+    *p++ = ss[i];
+    }
+  *p++ = '\"';
+  *p = 0;
+  s = string_append(s, &size, &ptr, 2, US" C=", big_buffer);
+  }
+
+/* Time on queue and actual time taken to deliver */
+
+if ((log_extra_selector & LX_queue_time) != 0)
+  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)
+  s = string_append(s, &size, &ptr, 2, US" DT=",
+    readconf_printtime(addr->more_errno));
+
+/* string_cat() always leaves room for the terminator. Release the
+store we used to build the line after writing it. */
+
+s[ptr] = 0;
+log_write(0, flags, "%s", s);
+
+#ifdef EXPERIMENTAL_EVENT
+if (!msg) msg_event_raise(US"msg:delivery", addr);
+#endif
+
+store_reset(reset_point);
+return;
+}
+
+
+
 /*************************************************
 *    Actions at the end of handling an address   *
 *************************************************/
@@ -680,7 +981,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
@@ -709,9 +1009,28 @@ else if (driver_type == DTYPE_ROUTER)
 
 /* If there's an error message set, ensure that it contains only printing
 characters - it should, but occasionally things slip in and this at least
-stops the log format from getting wrecked. */
+stops the log format from getting wrecked. We also scan the message for an LDAP
+expansion item that has a password setting, and flatten the password. This is a
+fudge, but I don't know a cleaner way of doing this. (If the item is badly
+malformed, it won't ever have gone near LDAP.) */
 
-if (addr->message != NULL) addr->message = string_printing(addr->message);
+if (addr->message != NULL)
+  {
+  addr->message = string_printing(addr->message);
+  if (((Ustrstr(addr->message, "failed to expand") != NULL) || (Ustrstr(addr->message, "expansion of ") != NULL)) &&
+      (Ustrstr(addr->message, "mysql") != NULL ||
+       Ustrstr(addr->message, "pgsql") != NULL ||
+#ifdef EXPERIMENTAL_REDIS
+       Ustrstr(addr->message, "redis") != NULL ||
+#endif
+       Ustrstr(addr->message, "sqlite") != NULL ||
+       Ustrstr(addr->message, "ldap:") != NULL ||
+       Ustrstr(addr->message, "ldapdn:") != NULL ||
+       Ustrstr(addr->message, "ldapm:") != NULL))
+    {
+      addr->message = string_sprintf("Temporary internal error");
+    }
+  }
 
 /* If we used a transport that has one of the "return_output" options set, and
 if it did in fact generate some output, then for return_output we treat the
@@ -729,7 +1048,7 @@ if (addr->return_file >= 0 && addr->return_filename != NULL)
   {
   BOOL return_output = FALSE;
   struct stat statbuf;
-  fsync(addr->return_file);
+  (void)EXIMfsync(addr->return_file);
 
   /* If there is no output, do nothing. */
 
@@ -760,7 +1079,7 @@ if (addr->return_file >= 0 && addr->return_filename != NULL)
           log_write(0, LOG_MAIN, "<%s>: %s transport output: %s",
             addr->address, tb->name, s);
           }
-        fclose(f);
+        (void)fclose(f);
         }
       }
 
@@ -791,16 +1110,10 @@ if (addr->return_file >= 0 && addr->return_filename != NULL)
     addr->return_file = -1;
     }
 
-  close(addr->return_file);
+  (void)close(addr->return_file);
   }
 
-/* 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);
-
-/* The sucess case happens only after delivery by a transport. */
+/* The success case happens only after delivery by a transport. */
 
 if (result == OK)
   {
@@ -816,10 +1129,8 @@ if (result == OK)
   DEBUG(D_deliver) debug_printf("%s delivered\n", addr->address);
 
   if (addr->parent == NULL)
-    {
     deliver_msglog("%s %s: %s%s succeeded\n", now, addr->address,
       driver_name, driver_kind);
-    }
   else
     {
     deliver_msglog("%s %s <%s>: %s%s succeeded\n", now, addr->address,
@@ -827,114 +1138,41 @@ if (result == OK)
     child_done(addr, now);
     }
 
-  /* 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. */
-
-  s = reset_point = store_get(size);
-  s[ptr++] = logchar;
-
-  s = string_append(s, &size, &ptr, 2, US"> ", log_address);
-
-  if ((log_extra_selector & LX_sender_on_delivery) != 0)
-    s = string_append(s, &size, &ptr, 3, US" F=<", sender_address, US">");
-
-  /* You might think that the return path must always be set for a successful
-  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)
-    s = string_append(s, &size, &ptr, 3, US" P=<", used_return_path, US">");
-
-  /* For a delivery from a system filter, there may not be a router */
-
-  if (addr->router != NULL)
-    s = string_append(s, &size, &ptr, 2, US" R=", addr->router->name);
-
-  s = string_append(s, &size, &ptr, 2, US" T=", addr->transport->name);
-
-  if ((log_extra_selector & LX_delivery_size) != 0)
-    s = string_append(s, &size, &ptr, 2, US" S=",
-      string_sprintf("%d", transport_count));
-
-  /* Local delivery */
-
-  if (addr->transport->info->local)
-    {
-    if (addr->host_list != NULL)
-      s = string_append(s, &size, &ptr, 2, US" H=", addr->host_list->name);
-    if (addr->shadow_message != NULL)
-      s = string_cat(s, &size, &ptr, addr->shadow_message,
-        Ustrlen(addr->shadow_message));
-    }
-
-  /* Remote delivery */
-
-  else
-    {
-    if (addr->host_used != NULL)
-      {
-      s = string_append(s, &size, &ptr, 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, &size, &ptr, 2, US":", string_sprintf("%d",
-          addr->host_used->port));
-      if (continue_sequence > 1)
-        s = string_cat(s, &size, &ptr, US"*", 1);
-      }
-
-    #ifdef SUPPORT_TLS
-    if ((log_extra_selector & LX_tls_cipher) != 0 && addr->cipher != NULL)
-      s = string_append(s, &size, &ptr, 2, US" X=", addr->cipher);
-    if ((log_extra_selector & LX_tls_certificate_verified) != 0 &&
-         addr->cipher != NULL)
-      s = string_append(s, &size, &ptr, 2, US" CV=",
-        testflag(addr, af_cert_verified)? "yes":"no");
-    if ((log_extra_selector & LX_tls_peerdn) != 0 && addr->peerdn != NULL)
-      s = string_append(s, &size, &ptr, 3, US" DN=\"", addr->peerdn, US"\"");
-    #endif
-
-    if ((log_extra_selector & LX_smtp_confirmation) != 0 &&
-        addr->message != NULL)
-      {
-      int i;
-      uschar *p = big_buffer;
-      uschar *ss = addr->message;
-      *p++ = '\"';
-      for (i = 0; i < 100 && ss[i] != 0; i++)
-        {
-        if (ss[i] == '\"' || ss[i] == '\\') *p++ = '\\';
-        *p++ = ss[i];
-        }
-      *p++ = '\"';
-      *p = 0;
-      s = string_append(s, &size, &ptr, 2, US" C=", big_buffer);
-      }
-    }
+  /* Certificates for logging (via events) */
+#ifdef SUPPORT_TLS
+  tls_out.ourcert = addr->ourcert;
+  addr->ourcert = NULL;
+  tls_out.peercert = addr->peercert;
+  addr->peercert = NULL;
+
+  tls_out.cipher = addr->cipher;
+  tls_out.peerdn = addr->peerdn;
+  tls_out.ocsp = addr->ocsp;
+# ifdef EXPERIMENTAL_DANE
+  tls_out.dane_verified = testflag(addr, af_dane_verified);
+# endif
+#endif
 
-  /* Time on queue and actual time taken to deliver */
+  delivery_log(LOG_MAIN, addr, logchar, NULL);
 
-  if ((log_extra_selector & LX_queue_time) != 0)
+#ifdef SUPPORT_TLS
+  if (tls_out.ourcert)
     {
-    s = string_append(s, &size, &ptr, 2, US" QT=",
-      readconf_printtime(time(NULL) - received_time));
+    tls_free_cert(tls_out.ourcert);
+    tls_out.ourcert = NULL;
     }
-
-  if ((log_extra_selector & LX_deliver_time) != 0)
+  if (tls_out.peercert)
     {
-    s = string_append(s, &size, &ptr, 2, US" DT=",
-      readconf_printtime(addr->more_errno));
+    tls_free_cert(tls_out.peercert);
+    tls_out.peercert = NULL;
     }
-
-  /* string_cat() always leaves room for the terminator. Release the
-  store we used to build the line after writing it. */
-
-  s[ptr] = 0;
-  log_write(0, LOG_MAIN, "%s", s);
-  store_reset(reset_point);
+  tls_out.cipher = NULL;
+  tls_out.peerdn = NULL;
+  tls_out.ocsp = OCSP_NOT_REQ;
+# ifdef EXPERIMENTAL_DANE
+  tls_out.dane_verified = FALSE;
+# endif
+#endif
   }
 
 
@@ -982,6 +1220,13 @@ else if (result == DEFER || result == PANIC)
     log. */
 
     s = reset_point = store_get(size);
+
+    /* 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);
+
     s = string_cat(s, &size, &ptr, log_address, Ustrlen(log_address));
 
     /* Either driver_name contains something and driver_kind contains
@@ -1011,6 +1256,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);
 
@@ -1046,12 +1296,15 @@ else
     setflag(addr, af_ignore_error);
 
   /* Freeze the message if requested, or if this is a bounce message (or other
-  message with null sender). However, don't freeze if errors are being ignored.
-  The actual code to ignore occurs later, instead of sending a message. Logging
-  of freezing occurs later, just before writing the -H file. */
+  message with null sender) and this address does not have its own errors
+  address. However, don't freeze if errors are being ignored. The actual code
+  to ignore occurs later, instead of sending a message. Logging of freezing
+  occurs later, just before writing the -H file. */
 
   if (!testflag(addr, af_ignore_error) &&
-      (addr->special_action == SPECIAL_FREEZE || sender_address[0] == 0))
+      (addr->special_action == SPECIAL_FREEZE ||
+        (sender_address[0] == 0 && addr->p.errors_address == NULL)
+      ))
     {
     frozen_info = (addr->special_action == SPECIAL_FREEZE)? US"" :
       (sender_local && !local_error_message)?
@@ -1079,6 +1332,13 @@ else
   /* Build up the log line for the message and main logs */
 
   s = reset_point = store_get(size);
+
+  /* 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);
+
   s = string_cat(s, &size, &ptr, log_address, Ustrlen(log_address));
 
   if ((log_extra_selector & LX_sender_on_delivery) != 0)
@@ -1088,9 +1348,7 @@ else
 
   if (used_return_path != NULL &&
       (log_extra_selector & LX_return_path_on_delivery) != 0)
-    {
     s = string_append(s, &size, &ptr, 3, US" P=<", used_return_path, US">");
-    }
 
   if (addr->router != NULL)
     s = string_append(s, &size, &ptr, 2, US" R=", addr->router->name);
@@ -1098,8 +1356,11 @@ else
     s = string_append(s, &size, &ptr, 2, US" T=", addr->transport->name);
 
   if (addr->host_used != NULL)
-    s = string_append(s, &size, &ptr, 5, US" H=", addr->host_used->name,
-      US" [", addr->host_used->address, US"]");
+    s = d_hostlog(s, &size, &ptr, addr);
+
+#ifdef SUPPORT_TLS
+  s = d_tlslog(s, &size, &ptr, addr);
+#endif
 
   if (addr->basic_errno > 0)
     s = string_append(s, &size, &ptr, 2, US": ",
@@ -1119,6 +1380,11 @@ else
     deliver_msglog("%s %s\n", now, s);
 
   log_write(0, LOG_MAIN, "** %s", s);
+
+#ifdef EXPERIMENTAL_EVENT
+  msg_event_raise(US"msg:fail:delivery", addr);
+#endif
+
   store_reset(reset_point);
   }
 
@@ -1163,7 +1429,7 @@ if (format != NULL)
   va_start(ap, format);
   if (!string_vformat(buffer, sizeof(buffer), CS format, ap))
     log_write(0, LOG_MAIN|LOG_PANIC_DIE,
-      "common_error expansion was longer than %d", sizeof(buffer));
+      "common_error expansion was longer than " SIZE_T_FMT, sizeof(buffer));
   va_end(ap);
   addr->message = string_copy(buffer);
   }
@@ -1258,6 +1524,14 @@ else if (tp->expand_gid != NULL)
     }
   }
 
+/* If the transport did not set a group, see if the router did. */
+
+if (!gid_set && testflag(addr, af_gid_set))
+  {
+  *gidp = addr->gid;
+  gid_set = TRUE;
+  }
+
 /* Pick up a uid from the transport if one is set. */
 
 if (tp->uid_set) *uidp = tp->uid;
@@ -1293,20 +1567,13 @@ else if (tp->deliver_as_creator)
     }
   }
 
-/* Otherwise see if the address specifies the uid and if so, take its
-initgroups flag. The gid from the address is taken only if the transport hasn't
-set it. In other words, a gid on the transport overrides the gid on the
-address. */
+/* Otherwise see if the address specifies the uid and if so, take it and its
+initgroups flag. */
 
 else if (testflag(addr, af_uid_set))
   {
   *uidp = addr->uid;
   *igfp = testflag(addr, af_initgroups);
-  if (!gid_set)
-    {
-    *gidp = addr->gid;
-    gid_set = TRUE;
-    }
   }
 
 /* Nothing has specified the uid - default to the Exim user, and group if the
@@ -1322,7 +1589,9 @@ else
     }
   }
 
-/* If no gid is set, it is a disaster. */
+/* If no gid is set, it is a disaster. We default to the Exim gid only if
+defaulting to the Exim uid. In other words, if the configuration has specified
+a uid, it must also provide a gid. */
 
 if (!gid_set)
   {
@@ -1377,10 +1646,10 @@ int rc = OK;
 int size_limit;
 
 deliver_set_expansions(addr);
-size_limit = expand_string_integer(tp->message_size_limit);
+size_limit = expand_string_integer(tp->message_size_limit, TRUE);
 deliver_set_expansions(NULL);
 
-if (size_limit < 0)
+if (expand_string_message != NULL)
   {
   rc = DEFER;
   if (size_limit == -1)
@@ -1408,18 +1677,21 @@ return rc;
 *************************************************/
 
 /* Check that this base address hasn't previously been delivered to its routed
-transport. The check is necessary at delivery time in order to handle homonymic
-addresses correctly in cases where the pattern of redirection changes between
-delivery attempts (so the unique fields change). Non-homonymic previous
-delivery is detected earlier, at routing time (which saves unnecessary
-routing).
+transport. If it has been delivered, mark it done. The check is necessary at
+delivery time in order to handle homonymic addresses correctly in cases where
+the pattern of redirection changes between delivery attempts (so the unique
+fields change). Non-homonymic previous delivery is detected earlier, at routing
+time (which saves unnecessary routing).
+
+Arguments:
+  addr      the address item
+  testing   TRUE if testing wanted only, without side effects
 
-Argument:   the address item
 Returns:    TRUE if previously delivered by the transport
 */
 
 static BOOL
-previously_transported(address_item *addr)
+previously_transported(address_item *addr, BOOL testing)
 {
 (void)string_format(big_buffer, big_buffer_size, "%s/%s",
   addr->unique + (testflag(addr, af_homonym)? 3:0), addr->transport->name);
@@ -1429,7 +1701,7 @@ if (tree_search(tree_nonrecipients, big_buffer) != 0)
   DEBUG(D_deliver|D_route|D_transport)
     debug_printf("%s was previously delivered (%s transport): discarded\n",
     addr->address, addr->transport->name);
-  child_done(addr, tod_stamp(tod_log));
+  if (!testing) child_done(addr, tod_stamp(tod_log));
   return TRUE;
   }
 
@@ -1438,6 +1710,44 @@ return FALSE;
 
 
 
+/******************************************************
+*      Check for a given header in a header string    *
+******************************************************/
+
+/* This function is used when generating quota warnings. The configuration may
+specify any header lines it likes in quota_warn_message. If certain of them are
+missing, defaults are inserted, so we need to be able to test for the presence
+of a given header.
+
+Arguments:
+  hdr         the required header name
+  hstring     the header string
+
+Returns:      TRUE  the header is in the string
+              FALSE the header is not in the string
+*/
+
+static BOOL
+contains_header(uschar *hdr, uschar *hstring)
+{
+int len = Ustrlen(hdr);
+uschar *p = hstring;
+while (*p != 0)
+  {
+  if (strncmpic(p, hdr, len) == 0)
+    {
+    p += len;
+    while (*p == ' ' || *p == '\t') p++;
+    if (*p == ':') return TRUE;
+    }
+  while (*p != 0 && *p != '\n') p++;
+  if (*p == '\n') p++;
+  }
+return FALSE;
+}
+
+
+
 
 /*************************************************
 *           Perform a local delivery             *
@@ -1493,8 +1803,14 @@ 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. */
 
-return_path = (addr->p.errors_address != NULL)?
-  addr->p.errors_address : sender_address;
+if(addr->p.errors_address != NULL)
+  return_path = addr->p.errors_address;
+#ifdef EXPERIMENTAL_SRS
+else if(addr->p.srs_sender != NULL)
+  return_path = addr->p.srs_sender;
+#endif
+else
+  return_path = sender_address;
 
 if (tp->return_path != NULL)
   {
@@ -1523,17 +1839,13 @@ return. */
 
 if (!findugid(addr, tp, &uid, &gid, &use_initgroups)) return;
 
-/* See if either the transport or the address specifies a home and/or a current
-working directory. Expand it if necessary. If nothing is set, use "/", for the
-working directory, which is assumed to be a directory to which all users have
-access. It is necessary to be in a visible directory for some operating systems
-when running pipes, as some commands (e.g. "rm" under Solaris 2.5) require
-this. */
-
-deliver_home = (tp->home_dir != NULL)? tp->home_dir :
-               (addr->home_dir != NULL)? addr->home_dir : NULL;
+/* See if either the transport or the address specifies a home directory. A
+home directory set in the address may already be expanded; a flag is set to
+indicate that. In other cases we must expand it. */
 
-if (deliver_home != NULL && !testflag(addr, af_home_expanded))
+if ((deliver_home = tp->home_dir) != NULL ||       /* Set in transport, or */
+     ((deliver_home = addr->home_dir) != NULL &&   /* Set in address and */
+       !testflag(addr, af_home_expanded)))         /*   not expanded */
   {
   uschar *rawhome = deliver_home;
   deliver_home = NULL;                      /* in case it contains $home */
@@ -1553,8 +1865,15 @@ if (deliver_home != NULL && !testflag(addr, af_home_expanded))
     }
   }
 
-working_directory = (tp->current_dir != NULL)? tp->current_dir :
-                    (addr->current_dir != NULL)? addr->current_dir : NULL;
+/* See if either the transport or the address specifies a current directory,
+and if so, expand it. If nothing is set, use the home directory, unless it is
+also unset in which case use "/", which is assumed to be a directory to which
+all users have access. It is necessary to be in a visible directory for some
+operating systems when running pipes, as some commands (e.g. "rm" under Solaris
+2.5) require this. */
+
+working_directory = (tp->current_dir != NULL)?
+  tp->current_dir : addr->current_dir;
 
 if (working_directory != NULL)
   {
@@ -1620,21 +1939,34 @@ if ((pid = fork()) == 0)
   HP-UX doesn't have RLIMIT_CORE; I don't know how to do this in that
   system. Some experimental/developing systems (e.g. GNU/Hurd) may define
   RLIMIT_CORE but not support it in setrlimit(). For such systems, do not
-  complain if the error is "not supported". */
-
-  #ifdef RLIMIT_CORE
+  complain if the error is "not supported".
+
+  There are two scenarios where changing the max limit has an effect.  In one,
+  the user is using a .forward and invoking a command of their choice via pipe;
+  for these, we do need the max limit to be 0 unless the admin chooses to
+  permit an increased limit.  In the other, the command is invoked directly by
+  the transport and is under administrator control, thus being able to raise
+  the limit aids in debugging.  So there's no general always-right answer.
+
+  Thus we inhibit core-dumps completely but let individual transports, while
+  still root, re-raise the limits back up to aid debugging.  We make the
+  default be no core-dumps -- few enough people can use core dumps in
+  diagnosis that it's reasonable to make them something that has to be explicitly requested.
+  */
+
+#ifdef RLIMIT_CORE
   struct rlimit rl;
   rl.rlim_cur = 0;
   rl.rlim_max = 0;
   if (setrlimit(RLIMIT_CORE, &rl) < 0)
     {
-    #ifdef SETRLIMIT_NOT_SUPPORTED
+ifdef SETRLIMIT_NOT_SUPPORTED
     if (errno != ENOSYS && errno != ENOTSUP)
-    #endif
+endif
       log_write(0, LOG_MAIN|LOG_PANIC, "setrlimit(RLIMIT_CORE) failed: %s",
         strerror(errno));
     }
-  #endif
+#endif
 
   /* Reset the random number generator, so different processes don't all
   have the same sequence. */
@@ -1647,7 +1979,7 @@ if ((pid = fork()) == 0)
 
   if (addr->transport->setup != NULL)
     {
-    switch((addr->transport->setup)(addr->transport, addr, NULL,
+    switch((addr->transport->setup)(addr->transport, addr, NULL, uid, gid,
            &(addr->message)))
       {
       case DEFER:
@@ -1673,8 +2005,8 @@ if ((pid = fork()) == 0)
   half - for transports that exec things (e.g. pipe). Then set the required
   gid/uid. */
 
-  close(pfd[pipe_read]);
-  fcntl(pfd[pipe_write], F_SETFD, fcntl(pfd[pipe_write], F_GETFD) |
+  (void)close(pfd[pipe_read]);
+  (void)fcntl(pfd[pipe_write], F_SETFD, fcntl(pfd[pipe_write], F_GETFD) |
     FD_CLOEXEC);
   exim_setugid(uid, gid, use_initgroups,
     string_sprintf("local delivery to %s <%s> transport=%s", addr->local_part,
@@ -1705,6 +2037,9 @@ if ((pid = fork()) == 0)
     set_process_info("delivering %s to %s using %s", message_id,
      addr->local_part, addr->transport->name);
 
+    /* Setting this global in the subprocess means we need never clear it */
+    transport_name = addr->transport->name;
+
     /* If a transport filter has been specified, set up its argument list.
     Any errors will get put into the address, and FALSE yielded. */
 
@@ -1738,40 +2073,47 @@ if ((pid = fork()) == 0)
     int i;
     int local_part_length = Ustrlen(addr2->local_part);
     uschar *s;
+    int ret;
 
-    write(pfd[pipe_write], (void *)&(addr2->transport_return), sizeof(int));
-    write(pfd[pipe_write], (void *)&transport_count, sizeof(transport_count));
-    write(pfd[pipe_write], (void *)&(addr2->flags), sizeof(addr2->flags));
-    write(pfd[pipe_write], (void *)&(addr2->basic_errno), sizeof(int));
-    write(pfd[pipe_write], (void *)&(addr2->more_errno), sizeof(int));
-    write(pfd[pipe_write], (void *)&(addr2->special_action), sizeof(int));
-    write(pfd[pipe_write], (void *)&(addr2->transport),
-      sizeof(transport_instance *));
+    if(  (ret = write(pfd[pipe_write], (void *)&(addr2->transport_return), sizeof(int))) != sizeof(int)
+      || (ret = write(pfd[pipe_write], (void *)&transport_count, sizeof(transport_count))) != sizeof(transport_count)
+      || (ret = write(pfd[pipe_write], (void *)&(addr2->flags), sizeof(addr2->flags))) != sizeof(addr2->flags)
+      || (ret = write(pfd[pipe_write], (void *)&(addr2->basic_errno), sizeof(int))) != sizeof(int)
+      || (ret = write(pfd[pipe_write], (void *)&(addr2->more_errno), sizeof(int))) != sizeof(int)
+      || (ret = write(pfd[pipe_write], (void *)&(addr2->special_action), sizeof(int))) != sizeof(int)
+      || (ret = write(pfd[pipe_write], (void *)&(addr2->transport),
+        sizeof(transport_instance *))) != sizeof(transport_instance *)
 
     /* For a file delivery, pass back the local part, in case the original
     was only part of the final delivery path. This gives more complete
     logging. */
 
-    if (testflag(addr2, af_file))
-      {
-      write(pfd[pipe_write], (void *)&local_part_length, sizeof(int));
-      write(pfd[pipe_write], addr2->local_part, local_part_length);
-      }
+      || (testflag(addr2, af_file)
+          && (  (ret = write(pfd[pipe_write], (void *)&local_part_length, sizeof(int))) != sizeof(int)
+             || (ret = write(pfd[pipe_write], addr2->local_part, local_part_length)) != local_part_length
+            )
+        )
+      )
+      log_write(0, LOG_MAIN|LOG_PANIC, "Failed writing transport results to pipe: %s\n",
+       ret == -1 ? strerror(errno) : "short write");
 
     /* Now any messages */
 
     for (i = 0, s = addr2->message; i < 2; i++, s = addr2->user_message)
       {
       int message_length = (s == NULL)? 0 : Ustrlen(s) + 1;
-      write(pfd[pipe_write], (void *)&message_length, sizeof(int));
-      if (message_length > 0) write(pfd[pipe_write], s, message_length);
+      if(  (ret = write(pfd[pipe_write], (void *)&message_length, sizeof(int))) != sizeof(int)
+        || (message_length > 0  && (ret = write(pfd[pipe_write], s, message_length)) != message_length)
+       )
+        log_write(0, LOG_MAIN|LOG_PANIC, "Failed writing transport results to pipe: %s\n",
+         ret == -1 ? strerror(errno) : "short write");
       }
     }
 
   /* OK, this process is now done. Free any cached resources that it opened,
   and close the pipe we were writing down before exiting. */
 
-  close(pfd[pipe_write]);
+  (void)close(pfd[pipe_write]);
   search_tidyup();
   exit(EXIT_SUCCESS);
   }
@@ -1790,7 +2132,7 @@ on an empty pipe. We check that a status exists for each address before
 overwriting the address structure. If data is missing, the default DEFER status
 will remain. Afterwards, close the reading end. */
 
-close(pfd[pipe_write]);
+(void)close(pfd[pipe_write]);
 
 for (addr2 = addr; addr2 != NULL; addr2 = addr2->next)
   {
@@ -1840,7 +2182,7 @@ for (addr2 = addr; addr2 != NULL; addr2 = addr2->next)
     }
   }
 
-close(pfd[pipe_read]);
+(void)close(pfd[pipe_read]);
 
 /* Unless shadowing, write all successful addresses immediately to the journal
 file, to ensure they are recorded asap. For homonymic addresses, use the base
@@ -1873,7 +2215,7 @@ if (!shadowing)
 
   /* Ensure the journal file is pushed out to disk. */
 
-  if (fsync(journal_fd) < 0)
+  if (EXIMfsync(journal_fd) < 0)
     log_write(0, LOG_MAIN|LOG_PANIC, "failed to fsync journal: %s",
       strerror(errno));
   }
@@ -1933,17 +2275,16 @@ if (addr->special_action == SPECIAL_WARN &&
     if (pid > 0)
       {
       FILE *f = fdopen(fd, "wb");
-
-      if (errors_reply_to != NULL)
+      if (errors_reply_to != NULL &&
+          !contains_header(US"Reply-To", warn_message))
         fprintf(f, "Reply-To: %s\n", errors_reply_to);
-      fprintf(f, "Auto-Submitted: auto-generated\n");
-      fprintf(f, "From: Mail Delivery System <Mailer-Daemon@%s>\n",
-        qualify_domain_sender);
+      fprintf(f, "Auto-Submitted: auto-replied\n");
+      if (!contains_header(US"From", warn_message)) moan_write_from(f);
       fprintf(f, "%s", CS warn_message);
 
       /* Close and wait for child process to complete, without a timeout. */
 
-      fclose(f);
+      (void)fclose(f);
       (void)child_close(pid, 0);
       }
     }
@@ -2016,21 +2357,23 @@ while (addr_local != NULL)
   attempts. Non-homonymic previous delivery is detected earlier, at routing
   time. */
 
-  if (previously_transported(addr)) continue;
+  if (previously_transported(addr, FALSE)) continue;
 
   /* There are weird cases where logging is disabled */
 
   disable_logging = tp->disable_logging;
 
-  /* Check for batched addresses and possible amalgamation. File deliveries can
-  never be batched. Skip all the work if either batch_max <= 1 or there aren't
-  any other addresses for local delivery. */
+  /* Check for batched addresses and possible amalgamation. Skip all the work
+  if either batch_max <= 1 or there aren't any other addresses for local
+  delivery. */
 
-  if (!testflag(addr, af_file) && tp->batch_max > 1 && addr_local != NULL)
+  if (tp->batch_max > 1 && addr_local != NULL)
     {
     int batch_count = 1;
     BOOL uses_dom = readconf_depends((driver_instance *)tp, US"domain");
-    BOOL uses_lp = readconf_depends((driver_instance *)tp, US"local_part");
+    BOOL uses_lp = (testflag(addr, af_pfr) &&
+      (testflag(addr, af_file) || addr->local_part[0] == '|')) ||
+      readconf_depends((driver_instance *)tp, US"local_part");
     uschar *batch_id = NULL;
     address_item **anchor = &addr_local;
     address_item *last = addr;
@@ -2057,7 +2400,9 @@ while (addr_local != NULL)
     same characteristics. These are:
 
       same transport
+      not previously delivered (see comment about 50 lines above)
       same local part if the transport's configuration contains $local_part
+        or if this is a file or pipe delivery from a redirection
       same domain if the transport's configuration contains $domain
       same errors address
       same additional headers
@@ -2070,6 +2415,8 @@ while (addr_local != NULL)
       {
       BOOL ok =
         tp == next->transport &&
+        !previously_transported(next, TRUE) &&
+        (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) &&
@@ -2190,10 +2537,12 @@ while (addr_local != NULL)
 
         DEBUG(D_retry)
           {
-          debug_printf("retry record exists: age=%d (max=%d)\n",
-            (int)(now - retry_record->time_stamp), retry_data_expire);
-          debug_printf("  time to retry = %d expired = %d\n",
-            (int)(now - retry_record->next_try), retry_record->expired);
+          debug_printf("retry record exists: age=%s ",
+            readconf_printtime(now - retry_record->time_stamp));
+          debug_printf("(max %s)\n", readconf_printtime(retry_data_expire));
+          debug_printf("  time to retry = %s expired = %d\n",
+            readconf_printtime(retry_record->next_try - now),
+            retry_record->expired);
           }
 
         if (queue_running && !deliver_force)
@@ -2206,32 +2555,8 @@ while (addr_local != NULL)
           to do, which is for the ultimate address timeout. */
 
           if (!ok)
-            {
-            retry_config *retry =
-              retry_find_config(retry_key+2, addr2->domain,
-                retry_record->basic_errno,
-                retry_record->more_errno);
-
-            DEBUG(D_deliver|D_retry)
-              debug_printf("retry time not reached for %s: "
-                "checking ultimate address timeout\n", addr2->address);
-
-            if (retry != NULL && retry->rules != NULL)
-              {
-              retry_rule *last_rule;
-              for (last_rule = retry->rules;
-                   last_rule->next != NULL;
-                   last_rule = last_rule->next);
-              if (now - received_time > last_rule->timeout) ok = TRUE;
-              }
-            else ok = TRUE;    /* No rule => timed out */
-
-            DEBUG(D_deliver|D_retry)
-              {
-              if (ok) debug_printf("on queue longer than maximum retry for "
-                "address - allowing delivery\n");
-              }
-            }
+            ok = retry_ultimate_address_timeout(retry_key, addr2->domain,
+                retry_record, now);
           }
         }
       else DEBUG(D_retry) debug_printf("no retry record exists\n");
@@ -2533,13 +2858,13 @@ also by optional retry data.
 
 Read in large chunks into the big buffer and then scan through, interpreting
 the data therein. In most cases, only a single read will be necessary. No
-individual item will ever be anywhere near 500 bytes in length, so by ensuring
-that we read the next chunk when there is less than 500 bytes left in the
-non-final chunk, we can assume each item is complete in store before handling
-it. Actually, each item is written using a single write(), which is atomic for
-small items (less than PIPE_BUF, which seems to be at least 512 in any Unix) so
-even if we are reading while the subprocess is still going, we should never
-have only a partial item in the buffer.
+individual item will ever be anywhere near 2500 bytes in length, so by ensuring
+that we read the next chunk when there is less than 2500 bytes left in the
+non-final chunk, we can assume each item is complete in the buffer before
+handling it. Each item is written using a single write(), which is atomic for
+small items (less than PIPE_BUF, which seems to be at least 512 in any Unix and
+often bigger) so even if we are reading while the subprocess is still going, we
+should never have only a partial item in the buffer.
 
 Argument:
   poffset     the offset of the parlist item
@@ -2564,6 +2889,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
@@ -2575,7 +2902,9 @@ completed.
 
 Each separate item is written to the pipe in a single write(), and as they are
 all short items, the writes will all be atomic and we should never find
-ourselves in the position of having read an incomplete item. */
+ourselves in the position of having read an incomplete item. "Short" in this
+case can mean up to about 1K in the case when there is a long error message
+associated with an address. */
 
 DEBUG(D_deliver) debug_printf("reading pipe for subprocess %d (%s)\n",
   (int)p->pid, eop? "ended" : "not ended");
@@ -2584,12 +2913,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 < 500 && unfinished)
+  if (remaining < required && unfinished)
     {
     int len;
     int available = big_buffer_size - remaining;
@@ -2622,17 +2954,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. */
@@ -2726,15 +3104,76 @@ while (!done)
     it in with the other info, in order to keep each message short enough to
     guarantee it won't be split in the pipe. */
 
-    #ifdef SUPPORT_TLS
+#ifdef SUPPORT_TLS
     case 'X':
-    if (addr == NULL) goto ADDR_MISMATCH;            /* Below, in 'A' handler */
-    addr->cipher = (*ptr)? string_copy(ptr) : NULL;
+    if (addr == NULL) goto ADDR_MISMATCH;          /* Below, in 'A' handler */
+    switch (subid)
+      {
+      case '1':
+      addr->cipher = NULL;
+      addr->peerdn = NULL;
+
+      if (*ptr)
+       addr->cipher = string_copy(ptr);
+      while (*ptr++);
+      if (*ptr)
+       addr->peerdn = string_copy(ptr);
+      break;
+
+      case '2':
+      addr->peercert = NULL;
+      if (*ptr)
+       (void) tls_import_cert(ptr, &addr->peercert);
+      break;
+
+      case '3':
+      addr->ourcert = NULL;
+      if (*ptr)
+       (void) tls_import_cert(ptr, &addr->ourcert);
+      break;
+
+# ifndef DISABLE_OCSP
+      case '4':
+      addr->ocsp = OCSP_NOT_REQ;
+      if (*ptr)
+       addr->ocsp = *ptr - '0';
+      break;
+# endif
+      }
     while (*ptr++);
-    addr->peerdn = (*ptr)? string_copy(ptr) : NULL;
+    break;
+#endif /*SUPPORT_TLS*/
+
+    case 'C':  /* client authenticator information */
+    switch (subid)
+      {
+      case '1':
+       addr->authenticator = (*ptr)? string_copy(ptr) : NULL;
+       break;
+      case '2':
+       addr->auth_id = (*ptr)? string_copy(ptr) : NULL;
+       break;
+      case '3':
+       addr->auth_sndr = (*ptr)? string_copy(ptr) : NULL;
+       break;
+      }
     while (*ptr++);
     break;
-    #endif
+
+#ifndef DISABLE_PRDR
+    case 'P':
+    addr->flags |= af_prdr_used;
+    break;
+#endif
+
+#ifdef EXPERIMENTAL_DSN
+    case 'D':
+    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)
@@ -2760,7 +3199,7 @@ while (!done)
     addr->user_message = (*ptr)? string_copy(ptr) : NULL;
     while(*ptr++);
 
-    /* Always two strings for host information, followed by the port number */
+    /* Always two strings for host information, followed by the port number and DNSSEC mark */
 
     if (*ptr != 0)
       {
@@ -2771,6 +3210,10 @@ while (!done)
       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++;
@@ -2793,7 +3236,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. */
@@ -2826,7 +3269,7 @@ if (!eop && !done)
 /* Close our end of the pipe, to prevent deadlock if the far end is still
 pushing stuff into it. */
 
-close(fd);
+(void)close(fd);
 p->fd = -1;
 
 /* If we have finished without error, but haven't had data for every address,
@@ -3245,6 +3688,46 @@ while (parcount > max)
 
 
 
+static void
+rmt_dlv_checked_write(int fd, char id, char subid, void * buf, int 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");
+}
+
 /*************************************************
 *           Do remote deliveries                 *
 *************************************************/
@@ -3345,7 +3828,7 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
   attempts. Non-homonymic previous delivery is detected earlier, at routing
   time. */
 
-  if (previously_transported(addr)) continue;
+  if (previously_transported(addr, FALSE)) continue;
 
   /* Force failure if the message is too big. */
 
@@ -3477,11 +3960,20 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
 
   deliver_set_expansions(addr);
 
+  /* Ensure any transport-set auth info is fresh */
+  addr->authenticator = addr->auth_id = addr->auth_sndr = NULL;
+
   /* 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. */
 
-  return_path = (addr->p.errors_address != NULL)?
-    addr->p.errors_address : sender_address;
+  if(addr->p.errors_address != NULL)
+    return_path = addr->p.errors_address;
+#ifdef EXPERIMENTAL_SRS
+  else if(addr->p.srs_sender != NULL)
+    return_path = addr->p.srs_sender;
+#endif
+  else
+    return_path = sender_address;
 
   if (tp->return_path != NULL)
     {
@@ -3499,12 +3991,25 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
     else return_path = new_return_path;
     }
 
+  /* Find the uid, gid, and use_initgroups setting for this transport. Failure
+  logs and sets up error messages, so we just post-process and continue with
+  the next address. */
+
+  if (!findugid(addr, tp, &uid, &gid, &use_initgroups))
+    {
+    remote_post_process(addr, LOG_MAIN|LOG_PANIC, NULL, fallback);
+    continue;
+    }
+
   /* If this transport has a setup function, call it now so that it gets
   run in this process and not in any subprocess. That way, the results of
-  any setup that are retained by the transport can be reusable. */
+  any setup that are retained by the transport can be reusable. One of the
+  things the setup does is to set the fallback host lists in the addresses.
+  That is why it is called at this point, before the continue delivery
+  processing, because that might use the fallback hosts. */
 
   if (tp->setup != NULL)
-    (void)((tp->setup)(addr->transport, addr, NULL, NULL));
+    (void)((tp->setup)(addr->transport, addr, NULL, uid, gid, NULL));
 
   /* If this is a run to continue delivery down an already-established
   channel, check that this set of addresses matches the transport and
@@ -3580,16 +4085,6 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
 
   transport_filter_argv = NULL;
 
-  /* Find the uid, gid, and use_initgroups setting for this transport. Failure
-  logs and sets up error messages, so we just post-process and continue with
-  the next address. */
-
-  if (!findugid(addr, tp, &uid, &gid, &use_initgroups))
-    {
-    remote_post_process(addr, LOG_MAIN|LOG_PANIC, NULL, fallback);
-    continue;
-    }
-
   /* Create the pipe for inter-process communication. If pipe creation
   fails, it is probably because the value of remote_max_parallel is so
   large that too many file descriptors for pipes have been created. Arrange
@@ -3607,11 +4102,11 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
     that it can use either of them, though it prefers O_NONBLOCK, which
     distinguishes between EOF and no-more-data. */
 
-    #ifdef O_NONBLOCK
-    fcntl(pfd[pipe_read], F_SETFL, O_NONBLOCK);
-    #else
-    fcntl(pfd[pipe_read], F_SETFL, O_NDELAY);
-    #endif
+#ifdef O_NONBLOCK
+    (void)fcntl(pfd[pipe_read], F_SETFL, O_NONBLOCK);
+#else
+    (void)fcntl(pfd[pipe_read], F_SETFL, O_NDELAY);
+#endif
 
     /* If the maximum number of subprocesses already exist, wait for a process
     to finish. If we ran out of file descriptors, parmax will have been reduced
@@ -3642,8 +4137,8 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
 
   if (poffset >= remote_max_parallel)
     {
-    close(pfd[pipe_write]);
-    close(pfd[pipe_read]);
+    (void)close(pfd[pipe_write]);
+    (void)close(pfd[pipe_read]);
     remote_post_process(addr, LOG_MAIN|LOG_PANIC,
       US"Unexpectedly no free subprocess slot", fallback);
     continue;
@@ -3660,8 +4155,10 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
     int fd = pfd[pipe_write];
     host_item *h;
 
-    /* There are weird circumstances in which logging is disabled */
+    /* Setting this global in the subprocess means we need never clear it */
+    transport_name = tp->name;
 
+    /* There are weird circumstances in which logging is disabled */
     disable_logging = tp->disable_logging;
 
     /* Show pids on debug output if parallelism possible */
@@ -3683,13 +4180,13 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
     a new process that may be forked to do another delivery down the same
     SMTP connection. */
 
-    fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
+    (void)fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
 
     /* Close open file descriptors for the pipes of other processes
     that are running in parallel. */
 
     for (poffset = 0; poffset < remote_max_parallel; poffset++)
-      if (parlist[poffset].pid != 0) close(parlist[poffset].fd);
+      if (parlist[poffset].pid != 0) (void)close(parlist[poffset].fd);
 
     /* This process has inherited a copy of the file descriptor
     for the data file, but its file pointer is shared with all the
@@ -3699,7 +4196,7 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
     the parent process. There doesn't seem to be any way of doing
     a dup-with-new-file-pointer. */
 
-    close(deliver_datafile);
+    (void)close(deliver_datafile);
     sprintf(CS spoolname, "%s/input/%s/%s-D", spool_directory, message_subdir,
       message_id);
     deliver_datafile = Uopen(spoolname, O_RDWR | O_APPEND, 0);
@@ -3710,7 +4207,7 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
 
     /* Set the close-on-exec flag */
 
-    fcntl(deliver_datafile, F_SETFD, fcntl(deliver_datafile, F_GETFD) |
+    (void)fcntl(deliver_datafile, F_SETFD, fcntl(deliver_datafile, F_GETFD) |
       FD_CLOEXEC);
 
     /* Set the uid/gid of this process; bombs out on failure. */
@@ -3723,7 +4220,7 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
     and run the transport. Afterwards, transport_count will contain the number
     of bytes written. */
 
-    close(pfd[pipe_read]);
+    (void)close(pfd[pipe_read]);
     set_process_info("delivering %s using %s", message_id, tp->name);
     debug_print_string(tp->debug_string);
     if (!(tp->info->code)(addr->transport, addr)) replicate_status(addr);
@@ -3751,8 +4248,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);
-      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
@@ -3760,14 +4257,13 @@ 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));
-    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. Three item types are
-    used: an optional 'X' item first, for TLS information, followed by 'R'
-    items for any retry settings, and finally an 'A' item for the remaining
-    data. */
+    /* Information about what happened to each address. Four item types are
+    used: an optional 'X' item first, for TLS information, then an optional "C"
+    item for any client-auth info followed by 'R' items for any retry settings,
+    and finally an 'A' item for the remaining data. */
 
     for(; addr != NULL; addr = addr->next)
       {
@@ -3775,33 +4271,96 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
       retry_item *r;
 
       /* The certificate verification status goes into the flags */
-
-      if (tls_certificate_verified) setflag(addr, af_cert_verified);
+      if (tls_out.certificate_verified) setflag(addr, af_cert_verified);
+#ifdef EXPERIMENTAL_DANE
+      if (tls_out.dane_verified)        setflag(addr, af_dane_verified);
+#endif
 
       /* Use an X item only if there's something to send */
-
-      #ifdef SUPPORT_TLS
-      if (addr->cipher != NULL)
+#ifdef SUPPORT_TLS
+      if (addr->cipher)
         {
         ptr = big_buffer;
-        *ptr++ = 'X';
         sprintf(CS ptr, "%.128s", addr->cipher);
         while(*ptr++);
-        if (addr->peerdn == NULL) *ptr++ = 0; else
+        if (!addr->peerdn)
+         *ptr++ = 0;
+       else
           {
           sprintf(CS ptr, "%.512s", addr->peerdn);
           while(*ptr++);
           }
-        write(fd, big_buffer, ptr - big_buffer);
+
+        rmt_dlv_checked_write(fd, 'X', '1', big_buffer, ptr - big_buffer);
         }
-      #endif
+      if (addr->peercert)
+       {
+        ptr = big_buffer;
+       if (!tls_export_cert(ptr, big_buffer_size-2, addr->peercert))
+         while(*ptr++);
+       else
+         *ptr++ = 0;
+        rmt_dlv_checked_write(fd, 'X', '2', big_buffer, ptr - big_buffer);
+       }
+      if (addr->ourcert)
+       {
+        ptr = big_buffer;
+       if (!tls_export_cert(ptr, big_buffer_size-2, addr->ourcert))
+         while(*ptr++);
+       else
+         *ptr++ = 0;
+        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, "%c", addr->ocsp + '0');
+       while(*ptr++);
+        rmt_dlv_checked_write(fd, 'X', '4', big_buffer, ptr - big_buffer);
+       }
+# endif
+#endif /*SUPPORT_TLS*/
+
+      if (client_authenticator)
+        {
+        ptr = big_buffer;
+       sprintf(CS big_buffer, "%.64s", client_authenticator);
+        while(*ptr++);
+        rmt_dlv_checked_write(fd, 'C', '1', big_buffer, ptr - big_buffer);
+       }
+      if (client_authenticated_id)
+        {
+        ptr = big_buffer;
+       sprintf(CS big_buffer, "%.64s", client_authenticated_id);
+        while(*ptr++);
+        rmt_dlv_checked_write(fd, 'C', '2', big_buffer, ptr - big_buffer);
+       }
+      if (client_authenticated_sender)
+        {
+        ptr = big_buffer;
+       sprintf(CS big_buffer, "%.64s", client_authenticated_sender);
+        while(*ptr++);
+        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', '0', NULL, 0);
+#endif
+
+#ifdef EXPERIMENTAL_DSN
+      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);
@@ -3812,13 +4371,13 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
           sprintf(CS ptr, "%.512s", r->message);
           while(*ptr++);
           }
-        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. */
 
-      ptr = big_buffer + 3;
-      sprintf(CS big_buffer, "A%c%c", addr->transport_return,
+      ptr = big_buffer + 2;
+      sprintf(CS big_buffer, "%c%c", addr->transport_return,
         addr->special_action);
       memcpy(ptr, &(addr->basic_errno), sizeof(addr->basic_errno));
       ptr += sizeof(addr->basic_errno);
@@ -3847,8 +4406,13 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
         while(*ptr++);
         memcpy(ptr, &(addr->host_used->port), sizeof(addr->host_used->port));
         ptr += sizeof(addr->host_used->port);
+
+        /* DNS lookup status */
+       *ptr++ = addr->host_used->dnssec==DS_YES ? '2'
+              : addr->host_used->dnssec==DS_NO ? '1' : '0';
+
         }
-      write(fd, big_buffer, ptr - big_buffer);
+      rmt_dlv_checked_write(fd, 'A', '0', big_buffer, ptr - big_buffer);
       }
 
     /* Add termination flag, close the pipe, and that's it. The character
@@ -3856,22 +4420,21 @@ 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';
-    write(fd, big_buffer, 2);
-    close(fd);
+    big_buffer[0] = (continue_transport == NULL)? '0' : '1';
+    rmt_dlv_checked_write(fd, 'Z', '0', big_buffer, 1);
+    (void)close(fd);
     exit(EXIT_SUCCESS);
     }
 
   /* Back in the mainline: close the unwanted half of the pipe. */
 
-  close(pfd[pipe_write]);
+  (void)close(pfd[pipe_write]);
 
   /* Fork failed; defer with error message */
 
   if (pid < 0)
     {
-    close(pfd[pipe_read]);
+    (void)close(pfd[pipe_read]);
     remote_post_process(addr, LOG_MAIN|LOG_PANIC,
       string_sprintf("fork failed for remote delivery to %s: %s",
         addr->domain, strerror(errno)), fallback);
@@ -4132,7 +4695,6 @@ if (addr->parent != NULL && testflag(addr, af_hide_child))
   printed = US"an undisclosed address";
   yield = FALSE;
   }
-
 else if (!testflag(addr, af_pfr) || addr->parent == NULL)
   printed = addr->address;
 
@@ -4161,6 +4723,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;
 }
@@ -4169,7 +4735,6 @@ return yield;
 
 
 
-
 /*************************************************
 *         Print error for an address             *
 *************************************************/
@@ -4179,47 +4744,102 @@ a bounce or a warning message. It tries to format the message reasonably by
 introducing newlines. All lines are indented by 4; the initial printing
 position must be set before calling.
 
+This function used always to print the error. Nowadays we want to restrict it
+to cases such as LMTP/SMTP errors from a remote host, and errors from :fail:
+and filter "fail". We no longer pass other information willy-nilly in bounce
+and warning messages. Text in user_message is always output; text in message
+only if the af_pass_message flag is set.
+
 Arguments:
-  addr         points to the address
+  addr         the address
   f            the FILE to print on
+  t            some leading text
 
 Returns:       nothing
 */
 
 static void
-print_address_error(address_item *addr, FILE *f)
+print_address_error(address_item *addr, FILE *f, uschar *t)
 {
-uschar *s = (addr->user_message != NULL)? addr->user_message : addr->message;
-if (addr->basic_errno > 0)
-  {
-  fprintf(f, "%s%s", strerror(addr->basic_errno),
-    (s == NULL)? "" : ":\n    ");
-  }
+int count = Ustrlen(t);
+uschar *s = testflag(addr, af_pass_message)? addr->message : NULL;
+
 if (s == NULL)
   {
-  if (addr->basic_errno <= 0) fprintf(f, "unknown error");
+  if (addr->user_message != NULL) s = addr->user_message; else return;
   }
-else
+
+fprintf(f, "\n    %s", t);
+
+while (*s != 0)
   {
-  int count = 0;
-  while (*s != 0)
+  if (*s == '\\' && s[1] == 'n')
+    {
+    fprintf(f, "\n    ");
+    s += 2;
+    count = 0;
+    }
+  else
     {
-    if (*s == '\\' && s[1] == 'n')
+    fputc(*s, f);
+    count++;
+    if (*s++ == ':' && isspace(*s) && count > 45)
       {
-      fprintf(f, "\n    ");
-      s += 2;
+      fprintf(f, "\n   ");  /* sic (because space follows) */
       count = 0;
       }
-    else
-      {
-      fputc(*s, f);
-      count++;
-      if (*s++ == ':' && isspace(*s) && count > 45)
-        {
-        fprintf(f, "\n   ");  /* sic (because space follows) */
-        count = 0;
-        }
-      }
+    }
+  }
+}
+
+
+
+
+
+
+/*************************************************
+*     Check list of addresses for duplication    *
+*************************************************/
+
+/* This function was introduced when the test for duplicate addresses that are
+not pipes, files, or autoreplies was moved from the middle of routing to when
+routing was complete. That was to fix obscure cases when the routing history
+affects the subsequent routing of identical addresses. This function is called
+after routing, to check that the final routed addresses are not duplicates.
+
+If we detect a duplicate, we remember what it is a duplicate of. Note that
+pipe, file, and autoreply de-duplication is handled during routing, so we must
+leave such "addresses" alone here, as otherwise they will incorrectly be
+discarded.
+
+Argument:     address of list anchor
+Returns:      nothing
+*/
+
+static void
+do_duplicate_check(address_item **anchor)
+{
+address_item *addr;
+while ((addr = *anchor) != NULL)
+  {
+  tree_node *tnode;
+  if (testflag(addr, af_pfr))
+    {
+    anchor = &(addr->next);
+    }
+  else if ((tnode = tree_search(tree_duplicates, addr->unique)) != NULL)
+    {
+    DEBUG(D_deliver|D_route)
+      debug_printf("%s is a duplicate address: discarded\n", addr->unique);
+    *anchor = addr->next;
+    addr->dupof = tnode->data.ptr;
+    addr->next = addr_duplicate;
+    addr_duplicate = addr;
+    }
+  else
+    {
+    tree_add_duplicate(addr->unique, addr);
+    anchor = &(addr->next);
     }
   }
 }
@@ -4276,6 +4896,7 @@ FILE *jread;
 int process_recipients = RECIP_ACCEPT;
 open_db dbblock;
 open_db *dbm_file;
+extern int acl_where;
 
 uschar *info = (queue_run_pid == (pid_t)0)?
   string_sprintf("delivering %s", id) :
@@ -4326,6 +4947,9 @@ message_size = 0;
 update_spool = FALSE;
 remove_journal = TRUE;
 
+/* Set a known context for any ACLs we call via expansions */
+acl_where = ACL_WHERE_DELIVERY;
+
 /* Reset the random number generator, so that if several delivery processes are
 started from a queue runner that has already used random numbers (for sorting),
 they don't all get the same sequence. */
@@ -4358,11 +4982,8 @@ if ((rc = spool_read_header(spoolname, TRUE, TRUE)) != spool_read_OK)
     sprintf(CS big_buffer, "%s/input/%s/%s", spool_directory, message_subdir,
       spoolname);
     if (Ustat(big_buffer, &statbuf) == 0)
-      {
-      int size = statbuf.st_size;   /* Because might be a long */
-      log_write(0, LOG_MAIN, "Format error in spool file %s: size=%d",
-        spoolname, size);
-      }
+      log_write(0, LOG_MAIN, "Format error in spool file %s: "
+        "size=" OFF_T_FMT, spoolname, statbuf.st_size);
     else log_write(0, LOG_MAIN, "Format error in spool file %s", spoolname);
     }
   else
@@ -4396,7 +5017,7 @@ if ((rc = spool_read_header(spoolname, TRUE, TRUE)) != spool_read_OK)
       readconf_printtime(keep_malformed));
     }
 
-  close(deliver_datafile);
+  (void)close(deliver_datafile);
   deliver_datafile = -1;
   return continue_closedown();   /* yields DELIVER_NOT_ATTEMPTED */
   }
@@ -4422,7 +5043,7 @@ if (jread != NULL)
     DEBUG(D_deliver) debug_printf("Previously delivered address %s taken from "
       "journal file\n", big_buffer);
     }
-  fclose(jread);
+  (void)fclose(jread);
   /* Panic-dies on error */
   (void)spool_write_header(message_id, SW_DELIVERING, NULL);
   }
@@ -4437,7 +5058,7 @@ else if (errno != ENOENT)
 
 if (recipients_list == NULL)
   {
-  close(deliver_datafile);
+  (void)close(deliver_datafile);
   deliver_datafile = -1;
   log_write(0, LOG_MAIN, "Spool error: no recipients for %s", spoolname);
   return continue_closedown();   /* yields DELIVER_NOT_ATTEMPTED */
@@ -4450,7 +5071,7 @@ attempted. */
 
 if (deliver_freeze)
   {
-  #ifdef SUPPORT_MOVE_FROZEN_MESSAGES
+#ifdef SUPPORT_MOVE_FROZEN_MESSAGES
   /* Moving to another directory removes the message from Exim's view. Other
   tools must be used to deal with it. Logging of this action happens in
   spool_move_message() and its subfunctions. */
@@ -4458,7 +5079,7 @@ if (deliver_freeze)
   if (move_frozen_messages &&
       spool_move_message(id, message_subdir, US"", US"F"))
     return continue_closedown();   /* yields DELIVER_NOT_ATTEMPTED */
-  #endif
+#endif
 
   /* For all frozen messages (bounces or not), timeout_frozen_after sets the
   maximum time to keep messages that are frozen. Thaw if we reach it, with a
@@ -4480,19 +5101,24 @@ if (deliver_freeze)
     log_write(0, LOG_MAIN, "Unfrozen by errmsg timer");
     }
 
-  /* If there's no auto thaw, or we haven't reached the auto thaw time yet, and
-  this delivery is not forced by an admin user, do not attempt delivery of this
-  message. Note that forced is set for continuing messages down the same
-  channel, in order to skip load checking and ignore hold domains, but we
-  don't want unfreezing in that case. */
+  /* If this is a bounce message, or there's no auto thaw, or we haven't
+  reached the auto thaw time yet, and this delivery is not forced by an admin
+  user, do not attempt delivery of this message. Note that forced is set for
+  continuing messages down the same channel, in order to skip load checking and
+  ignore hold domains, but we don't want unfreezing in that case. */
 
   else
     {
-    if ((auto_thaw <= 0 || now <= deliver_frozen_at + auto_thaw) &&
-      (!forced || !deliver_force_thaw || !admin_user ||
-        continue_hostname != NULL))
+    if ((sender_address[0] == 0 ||
+         auto_thaw <= 0 ||
+         now <= deliver_frozen_at + auto_thaw
+        )
+        &&
+        (!forced || !deliver_force_thaw || !admin_user ||
+          continue_hostname != NULL
+        ))
       {
-      close(deliver_datafile);
+      (void)close(deliver_datafile);
       deliver_datafile = -1;
       log_write(L_skip_delivery, LOG_MAIN, "Message is frozen");
       return continue_closedown();   /* yields DELIVER_NOT_ATTEMPTED */
@@ -4614,6 +5240,9 @@ else if (system_filter != NULL && process_recipients != RECIP_FAIL_TIMEOUT)
       RDO_REWRITE,
     NULL,                   /* No :include: restriction (not used in filter) */
     NULL,                   /* No sieve vacation directory (not sieve!) */
+    NULL,                   /* No sieve enotify mailto owner (not sieve!) */
+    NULL,                   /* No sieve user address (not sieve!) */
+    NULL,                   /* No sieve subaddress (not sieve!) */
     &ugid,                  /* uid/gid data */
     &addr_new,              /* Where to hang generated addresses */
     &filter_message,        /* Where to put error message */
@@ -4625,7 +5254,7 @@ else if (system_filter != NULL && process_recipients != RECIP_FAIL_TIMEOUT)
 
   if (rc == FF_ERROR || rc == FF_NONEXIST)
     {
-    close(deliver_datafile);
+    (void)close(deliver_datafile);
     deliver_datafile = -1;
     log_write(0, LOG_MAIN|LOG_PANIC, "Error in system filter: %s",
       string_printing(filter_message));
@@ -4745,6 +5374,9 @@ else if (system_filter != NULL && process_recipients != RECIP_FAIL_TIMEOUT)
 
     while (p != NULL)
       {
+      if (parent->child_count == SHRT_MAX)
+        log_write(0, LOG_MAIN|LOG_PANIC_DIE, "system filter generated more "
+          "than %d delivery addresses", SHRT_MAX);
       parent->child_count++;
       p->parent = parent;
 
@@ -4876,6 +5508,14 @@ if (process_recipients != RECIP_IGNORE)
       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
+
       switch (process_recipients)
         {
         /* RECIP_DEFER is set when a system filter freezes a message. */
@@ -4892,6 +5532,7 @@ if (process_recipients != RECIP_IGNORE)
         case RECIP_FAIL_FILTER:
         new->message =
           (filter_message == NULL)? US"delivery cancelled" : filter_message;
+        setflag(new, af_pass_message);
         goto RECIP_QUEUE_FAILED;   /* below */
 
 
@@ -4943,6 +5584,25 @@ if (process_recipients != RECIP_IGNORE)
         addr_last = new;
         break;
         }
+
+#ifdef EXPERIMENTAL_EVENT
+      if (process_recipients != RECIP_ACCEPT)
+       {
+       uschar * save_local =  deliver_localpart;
+       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) event_raise(event_action,
+                     US"msg:fail:internal", new->message);
+
+       deliver_localpart = save_local;
+       deliver_domain =    save_domain;
+       }
+#endif
       }
     }
   }
@@ -5043,7 +5703,20 @@ while (addr_new != NULL)           /* Loop until all addresses dealt with */
 
     if (testflag(addr, af_pfr))
       {
-      int offset = testflag(addr->parent, af_homonym)? 3:0;
+      /* If an autoreply in a filter could not generate a syntactically valid
+      address, give up forthwith. Set af_ignore_error so that we don't try to
+      generate a bounce. */
+
+      if (testflag(addr, af_bad_reply))
+        {
+        addr->basic_errno = ERRNO_BADADDRESS2;
+        addr->local_part = addr->address;
+        addr->message =
+          US"filter autoreply generated syntactically invalid recipient";
+        setflag(addr, af_ignore_error);
+        (void)post_process_one(addr, FAIL, LOG_MAIN, DTYPE_ROUTER, 0);
+        continue;   /* with the next new address */
+        }
 
       /* If two different users specify delivery to the same pipe or file or
       autoreply, there should be two different deliveries, so build a unique
@@ -5051,7 +5724,8 @@ while (addr_new != NULL)           /* Loop until all addresses dealt with */
       duplicate testing and recording delivery, and also for retrying. */
 
       addr->unique =
-        string_sprintf("%s:%s", addr->address, addr->parent->unique + offset);
+        string_sprintf("%s:%s", addr->address, addr->parent->unique +
+          (testflag(addr->parent, af_homonym)? 3:0));
 
       addr->address_retry_key = addr->domain_retry_key =
         string_sprintf("T:%s", addr->unique);
@@ -5240,25 +5914,11 @@ while (addr_new != NULL)           /* Loop until all addresses dealt with */
       continue;
       }
 
-    /* If it's a duplicate, remember what it's a duplicate of */
-
-    if ((tnode = tree_search(tree_duplicates, addr->unique)) != NULL)
-      {
-      DEBUG(D_deliver|D_route)
-        debug_printf("%s is a duplicate address: discarded\n", addr->unique);
-      addr->dupof = tnode->data.ptr;
-      addr->next = addr_duplicate;
-      addr_duplicate = addr;
-      continue;
-      }
-
-    /* Record this address, so subsequent duplicates get picked up. */
-
-    tree_add_duplicate(addr->unique, addr);
-
     /* Get the routing retry status, saving the two retry keys (with and
-    without the local part) for subsequent use. Ignore retry records that
-    are too old. */
+    without the local part) for subsequent use. If there is no retry record for
+    the standard address routing retry key, we look for the same key with the
+    sender attached, because this form is used by the smtp transport after a
+    4xx response to RCPT when address_retry_include_sender is true. */
 
     addr->domain_retry_key = string_sprintf("R:%s", addr->domain);
     addr->address_retry_key = string_sprintf("R:%s@%s", addr->local_part,
@@ -5271,12 +5931,22 @@ while (addr_new != NULL)           /* Loop until all addresses dealt with */
       domain_retry_record = dbfn_read(dbm_file, addr->domain_retry_key);
       if (domain_retry_record != NULL &&
           now - domain_retry_record->time_stamp > retry_data_expire)
-        domain_retry_record = NULL;
+        domain_retry_record = NULL;    /* Ignore if too old */
 
       address_retry_record = dbfn_read(dbm_file, addr->address_retry_key);
       if (address_retry_record != NULL &&
           now - address_retry_record->time_stamp > retry_data_expire)
-        address_retry_record = NULL;
+        address_retry_record = NULL;   /* Ignore if too old */
+
+      if (address_retry_record == NULL)
+        {
+        uschar *altkey = string_sprintf("%s:<%s>", addr->address_retry_key,
+          sender_address);
+        address_retry_record = dbfn_read(dbm_file, altkey);
+        if (address_retry_record != NULL &&
+            now - address_retry_record->time_stamp > retry_data_expire)
+          address_retry_record = NULL;   /* Ignore if too old */
+        }
       }
 
     DEBUG(D_deliver|D_retry)
@@ -5308,26 +5978,51 @@ while (addr_new != NULL)           /* Loop until all addresses dealt with */
       (void)post_process_one(addr, DEFER, LOG_MAIN, DTYPE_ROUTER, 0);
       }
 
-    /* If queue_running, defer routing unless no retry data or we've
-    passed the next retry time, or this message is forced. However,
-    if the retry time has expired, allow the routing attempt.
-    If it fails again, the address will be failed. This ensures that
+    /* If we are in a queue run, defer routing unless there is no retry data or
+    we've passed the next retry time, or this message is forced. In other
+    words, ignore retry data when not in a queue run.
+
+    However, if the domain retry time has expired, always allow the routing
+    attempt. If it fails again, the address will be failed. This ensures that
     each address is routed at least once, even after long-term routing
     failures.
 
     If there is an address retry, check that too; just wait for the next
     retry time. This helps with the case when the temporary error on the
     address was really message-specific rather than address specific, since
-    it allows other messages through. */
-
-    else if (!deliver_force && queue_running &&
+    it allows other messages through.
+
+    We also wait for the next retry time if this is a message sent down an
+    existing SMTP connection (even though that will be forced). Otherwise there
+    will be far too many attempts for an address that gets a 4xx error. In
+    fact, after such an error, we should not get here because, the host should
+    not be remembered as one this message needs. However, there was a bug that
+    used to cause this to  happen, so it is best to be on the safe side.
+
+    Even if we haven't reached the retry time in the hints, there is one more
+    check to do, which is for the ultimate address timeout. We only do this
+    check if there is an address retry record and there is not a domain retry
+    record; this implies that previous attempts to handle the address had the
+    retry_use_local_parts option turned on. We use this as an approximation
+    for the destination being like a local delivery, for example delivery over
+    LMTP to an IMAP message store. In this situation users are liable to bump
+    into their quota and thereby have intermittently successful deliveries,
+    which keep the retry record fresh, which can lead to us perpetually
+    deferring messages. */
+
+    else if (((queue_running && !deliver_force) || continue_hostname != NULL)
+            &&
             ((domain_retry_record != NULL &&
               now < domain_retry_record->next_try &&
               !domain_retry_record->expired)
             ||
             (address_retry_record != NULL &&
               now < address_retry_record->next_try))
-            )
+            &&
+           (domain_retry_record != NULL ||
+            address_retry_record == NULL ||
+            !retry_ultimate_address_timeout(addr->address_retry_key,
+              addr->domain, address_retry_record, now)))
       {
       addr->message = US"retry time not reached";
       addr->basic_errno = ERRNO_RRETRY;
@@ -5419,12 +6114,16 @@ while (addr_new != NULL)           /* Loop until all addresses dealt with */
         string_sprintf("R:%s", addr->domain), 0);
 
     /* Otherwise, if there is an existing retry record in the database, add
-    retry items to delete both forms. Since the domain might have been
-    rewritten (expanded to fully qualified) as a result of routing, ensure
-    that the rewritten form is also deleted. */
+    retry items to delete both forms. We must also allow for the possibility
+    of a routing retry that includes the sender address. Since the domain might
+    have been rewritten (expanded to fully qualified) as a result of routing,
+    ensure that the rewritten form is also deleted. */
 
     else if (testflag(addr, af_dr_retry_exists))
       {
+      uschar *altkey = string_sprintf("%s:<%s>", addr->address_retry_key,
+        sender_address);
+      retry_add_item(addr, altkey, rf_delete);
       retry_add_item(addr, addr->address_retry_key, rf_delete);
       retry_add_item(addr, addr->domain_retry_key, rf_delete);
       if (Ustrcmp(addr->domain, old_domain) != 0)
@@ -5567,6 +6266,15 @@ Ensure they are not set in transports. */
 local_user_gid = (gid_t)(-1);
 local_user_uid = (uid_t)(-1);
 
+/* Check for any duplicate addresses. This check is delayed until after
+routing, because the flexibility of the routing configuration means that
+identical addresses with different parentage may end up being redirected to
+different addresses. Checking for duplicates too early (as we previously used
+to) makes this kind of thing not work. */
+
+do_duplicate_check(&addr_local);
+do_duplicate_check(&addr_remote);
+
 /* When acting as an MUA wrapper, we proceed only if all addresses route to a
 remote transport. The check that they all end up in one transaction happens in
 the do_remote_deliveries() function. */
@@ -5686,17 +6394,37 @@ if (addr_local != NULL || addr_remote != NULL)
   that the mode is correct - the group setting doesn't always seem to get
   set automatically. */
 
-  fcntl(journal_fd, F_SETFD, fcntl(journal_fd, F_GETFD) | FD_CLOEXEC);
-  fchown(journal_fd, exim_uid, exim_gid);
-  fchmod(journal_fd, SPOOL_MODE);
+  if(  fcntl(journal_fd, F_SETFD, fcntl(journal_fd, F_GETFD) | FD_CLOEXEC)
+    || fchown(journal_fd, exim_uid, exim_gid)
+    || fchmod(journal_fd, SPOOL_MODE)
+    )
+    {
+    int ret = Uunlink(spoolname);
+    log_write(0, LOG_MAIN|LOG_PANIC, "Couldn't set perms on journal file %s: %s",
+      spoolname, strerror(errno));
+    if(ret  &&  errno != ENOENT)
+      log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to unlink %s: %s",
+        spoolname, strerror(errno));
+    return DELIVER_NOT_ATTEMPTED;
+    }
   }
 
 
+
 /* Now we can get down to the business of actually doing deliveries. Local
 deliveries are done first, then remote ones. If ever the problems of how to
 handle fallback transports are figured out, this section can be put into a loop
 for handling fallbacks, though the uid switching will have to be revised. */
 
+/* Precompile a regex that is used to recognize a parameter in response
+to an LHLO command, if is isn't already compiled. This may be used on both
+local and remote LMTP deliveries. */
+
+if (regex_IGNOREQUOTA == NULL) regex_IGNOREQUOTA =
+  regex_must_compile(US"\\n250[\\s\\-]IGNOREQUOTA(\\s|\\n|$)", FALSE, TRUE);
+
+/* Handle local deliveries */
+
 if (addr_local != NULL)
   {
   DEBUG(D_deliver|D_transport)
@@ -5741,10 +6469,21 @@ if (addr_remote != NULL)
     regex_must_compile(US"\\n250[\\s\\-]AUTH\\s+([\\-\\w\\s]+)(?:\\n|$)",
       FALSE, TRUE);
 
-  #ifdef SUPPORT_TLS
+#ifdef SUPPORT_TLS
   if (regex_STARTTLS == NULL) regex_STARTTLS =
     regex_must_compile(US"\\n250[\\s\\-]STARTTLS(\\s|\\n|$)", FALSE, TRUE);
-  #endif
+#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
 
   /* 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
@@ -5793,15 +6532,34 @@ set_process_info("tidying up after delivering %s", message_id);
 signal(SIGTERM, SIG_IGN);
 
 /* When we are acting as an MUA wrapper, the smtp transport will either have
-succeeded for all addresses, or failed them all. We do not ever want to retry,
-nor do we want to send a bounce message. */
+succeeded for all addresses, or failed them all in normal cases. However, there
+are some setup situations (e.g. when a named port does not exist) that cause an
+immediate exit with deferral of all addresses. Convert those into failures. We
+do not ever want to retry, nor do we want to send a bounce message. */
 
 if (mua_wrapper)
   {
+  if (addr_defer != NULL)
+    {
+    address_item *addr, *nextaddr;
+    for (addr = addr_defer; addr != NULL; addr = nextaddr)
+      {
+      log_write(0, LOG_MAIN, "** %s mua_wrapper forced failure for deferred "
+        "delivery", addr->address);
+      nextaddr = addr->next;
+      addr->next = addr_failed;
+      addr_failed = addr;
+      }
+    addr_defer = NULL;
+    }
+
+  /* Now all should either have succeeded or failed. */
+
   if (addr_failed == NULL) final_yield = DELIVER_MUA_SUCCEEDED; else
     {
     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)
@@ -5809,6 +6567,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");
@@ -5832,6 +6592,169 @@ 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)
+  {
+  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);
+
+  /* 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)))
+    {
+    /* copy and relink address_item and send report with all of them at once later */
+    address_item *addr_next;
+    addr_next = addr_senddsn;
+    addr_senddsn = store_get(sizeof(address_item));
+    memcpy(addr_senddsn, addr_dsntmp, sizeof(address_item));
+    addr_senddsn->next = addr_next;
+    }
+  else
+    {
+      DEBUG(D_deliver) debug_printf("DSN: *** NOT SENDING DSN SUCCESS Message ***\n"); 
+    }
+
+  addr_dsntmp = addr_dsntmp->next;
+  }
+
+if (addr_senddsn != NULL)
+  {
+  pid_t pid;
+  int fd;
+
+  /* create exim process to send message */      
+  pid = child_open_exim(&fd);
+
+  DEBUG(D_deliver) debug_printf("DSN: child_open_exim returns: %d\n", pid);
+     
+  if (pid < 0)  /* Creation of child failed */
+    {
+    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));
+
+      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];
+     
+    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);
+  
+    if (errors_reply_to)
+      fprintf(f, "Reply-To: %s\n", errors_reply_to);
+    fprintf(f, "Auto-Submitted: auto-generated\n"
+       "From: Mail Delivery System <Mailer-Daemon@%s>\n"
+       "To: %s\n"
+       "Subject: Delivery Status Notification\n"
+       "Content-Type: multipart/report; report-type=delivery-status; boundary=%s\n"
+       "MIME-Version: 1.0\n\n"
+
+       "--%s\n"
+       "Content-type: text/plain; charset=us-ascii\n\n"
+   
+       "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);
+
+    addr_dsntmp = addr_senddsn;
+    while(addr_dsntmp)
+      {
+      fprintf(f, "<%s> (relayed %s)\n\n",
+       addr_dsntmp->address,
+       (addr_dsntmp->dsn_flags & rf_dsnlasthop) == 1
+         ? "via non DSN router"
+         : addr_dsntmp->dsn_aware == dsn_support_no
+         ? "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);
+
+    if (dsn_envid != NULL) {
+      /* 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);
+      else
+        fprintf(f, "X-Original-Envelope-ID: error decoding xtext formated ENVID\n");
+      }
+    fputc('\n', f);
+
+    for (addr_dsntmp = addr_senddsn;
+        addr_dsntmp;
+        addr_dsntmp = addr_dsntmp->next)
+      {
+      if (addr_dsntmp->dsn_orcpt)
+        fprintf(f,"Original-Recipient: %s\n", addr_dsntmp->dsn_orcpt);
+
+      fprintf(f, "Action: delivered\n"
+         "Final-Recipient: rfc822;%s\n"
+         "Status: 2.0.0\n",
+       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",
+         addr_dsntmp->host_used->name);
+      else
+       fprintf(f,"Diagnostic-Code: X-Exim; relayed via non %s router\n",
+         (addr_dsntmp->dsn_flags & rf_dsnlasthop) == 1 ? "DSN" : "SMTP");
+      fputc('\n', f);
+      }
+
+    fprintf(f, "--%s\nContent-type: text/rfc822-headers\n\n", boundaryStr);
+           
+    fflush(f);
+    transport_filter_argv = NULL;   /* Just in case */
+    return_path = sender_address;   /* In case not previously set */
+           
+    /* Write the original email out */
+    transport_write_message(NULL, fileno(f), topt, 0, NULL, NULL, NULL, NULL, NULL, 0);
+    fflush(f);
+
+    fprintf(f,"\n");       
+    fprintf(f,"--%s--\n", boundaryStr);
+
+    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
 several messages to get sent if there are addresses with different
@@ -5889,8 +6812,13 @@ 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))
-    {
+  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
+     )
+  {
     addr = addr_failed;
     addr_failed = addr->next;
     if (addr->return_filename != NULL) Uunlink(addr->return_filename);
@@ -5942,6 +6870,12 @@ 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 *dsnlimitmsg;
+      uschar *dsnnotifyhdr;
+      int topt;
+#endif
 
       DEBUG(D_deliver)
         debug_printf("sending error message to: %s\n", bounce_recipient);
@@ -5991,85 +6925,86 @@ while (addr_failed != NULL)
 
       if (errors_reply_to != NULL)
         fprintf(f, "Reply-To: %s\n", errors_reply_to);
-      fprintf(f, "Auto-Submitted: auto-generated\n");
-      fprintf(f, "From: Mail Delivery System <Mailer-Daemon@%s>\n",
-        qualify_domain_sender);
+      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());
+
+      fprintf(f, "Content-Type: multipart/report;"
+           " report-type=delivery-status; boundary=%s\n"
+         "MIME-Version: 1.0\n",
+       boundaryStr);
+#endif
+
       /* Open a template file if one is provided. Log failure to open, but
       carry on - default texts will be used. */
 
-      if (bounce_message_file != NULL)
-        {
-        emf = Ufopen(bounce_message_file, "rb");
-        if (emf == NULL)
+      if (bounce_message_file)
+        if (!(emf = Ufopen(bounce_message_file, "rb")))
           log_write(0, LOG_MAIN|LOG_PANIC, "Failed to open %s for error "
             "message texts: %s", bounce_message_file, strerror(errno));
-        }
 
       /* Quietly copy to configured additional addresses if required. */
 
-      bcc = moan_check_errorcopy(bounce_recipient);
-      if (bcc != NULL) fprintf(f, "Bcc: %s\n", bcc);
+      if ((bcc = moan_check_errorcopy(bounce_recipient)))
+       fprintf(f, "Bcc: %s\n", bcc);
 
       /* The texts for the message can be read from a template file; if there
       isn't one, or if it is too short, built-in texts are used. The first
       emf text is a Subject: and any other headers. */
 
-      emf_text = next_emf(emf, US"header");
-      if (emf_text != NULL) fprintf(f, "%s\n", emf_text); else
-        {
+      if ((emf_text = next_emf(emf, US"header")))
+       fprintf(f, "%s\n", emf_text);
+      else
         fprintf(f, "Subject: Mail delivery failed%s\n\n",
           to_sender? ": returning message to sender" : "");
-        }
 
-      emf_text = next_emf(emf, US"intro");
-      if (emf_text != NULL) fprintf(f, "%s", CS emf_text); else
+#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
+
+      if ((emf_text = next_emf(emf, US"intro")))
+       fprintf(f, "%s", CS emf_text);
+      else
         {
         fprintf(f,
 /* This message has been reworded several times. It seems to be confusing to
 somebody, however it is worded. I have retreated to the original, simple
 wording. */
 "This message was created automatically by mail delivery software.\n");
-        if (bounce_message_text != NULL) fprintf(f, "%s", CS bounce_message_text);
+
+        if (bounce_message_text)
+         fprintf(f, "%s", CS bounce_message_text);
         if (to_sender)
-          {
           fprintf(f,
 "\nA message that you sent could not be delivered to one or more of its\n"
 "recipients. This is a permanent error. The following address(es) failed:\n");
-          }
         else
-          {
           fprintf(f,
 "\nA message sent by\n\n  <%s>\n\n"
 "could not be delivered to one or more of its recipients. The following\n"
 "address(es) failed:\n", sender_address);
-          }
         }
-      fprintf(f, "\n");
+      fputc('\n', f);
 
       /* Process the addresses, leaving them on the msgchain if they have a
       file name for a return message. (There has already been a check in
-      post_process_one() for the existence of data in the message file.) */
+      post_process_one() for the existence of data in the message file.) A TRUE
+      return from print_address_information() means that the address is not
+      hidden. */
 
       paddr = &msgchain;
       for (addr = msgchain; addr != NULL; addr = *paddr)
         {
         if (print_address_information(addr, f, US"  ", US"\n    ", US""))
-          {
-          /* A TRUE return from print_address_information() means that the
-          address is not hidden. If there is a return file, it has already
-          been checked to ensure it is not empty. Omit the bland "return
-          message generated" error, but otherwise include error information. */
-
-          if (addr->return_file < 0 ||
-              addr->message == NULL ||
-              Ustrcmp(addr->message, "return message generated") != 0)
-            {
-            fprintf(f, "\n    ");
-            print_address_error(addr, f);
-            }
-          }
+          print_address_error(addr, f, US"");
 
         /* End the final line for the address */
 
@@ -6094,7 +7029,7 @@ wording. */
           }
         }
 
-      fprintf(f, "\n");
+      fputc('\n', f);
 
       /* Get the next text, whether we need it or not, so as to be
       positioned for the one after. */
@@ -6108,11 +7043,13 @@ wording. */
       fd, and the return_filename field in the *last* one will be set (to the
       name of the file). */
 
-      if (msgchain != NULL)
+      if (msgchain)
         {
         address_item *nextaddr;
 
-        if (emf_text != NULL) fprintf(f, "%s", CS emf_text); else
+        if (emf_text)
+         fprintf(f, "%s", CS emf_text);
+       else
           fprintf(f,
             "The following text was generated during the delivery "
             "attempt%s:\n", (filecount > 1)? "s" : "");
@@ -6124,15 +7061,15 @@ wording. */
 
           /* List all the addresses that relate to this file */
 
-          fprintf(f, "\n");
-          while(addr != NULL)                   /* Insurance */
+         fputc('\n', f);
+          while(addr)                   /* Insurance */
             {
             print_address_information(addr, f, US"------ ",  US"\n       ",
               US" ------\n");
-            if (addr->return_filename != NULL) break;
+            if (addr->return_filename) break;
             addr = addr->next;
             }
-          fprintf(f, "\n");
+         fputc('\n', f);
 
           /* Now copy the file */
 
@@ -6144,7 +7081,7 @@ wording. */
           else
             {
             while ((ch = fgetc(fm)) != EOF) fputc(ch, f);
-            fclose(fm);
+            (void)fclose(fm);
             }
           Uunlink(addr->return_filename);
 
@@ -6155,8 +7092,38 @@ wording. */
           addr->next = handled_addr;
           handled_addr = topaddr;
           }
-        fprintf(f, "\n");
+       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);
+
+      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);
+        else
+          fprintf(f, "X-Original-Envelope-ID: error decoding xtext formated ENVID\n");
+        }
+      fputc('\n', f);
+      for (addr = handled_addr; addr; addr = addr->next)
+        {
+        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);
         }
+#endif
 
       /* 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
@@ -6165,12 +7132,15 @@ 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 != NULL) fprintf(f, "%s", CS emf_text); else
+        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");
@@ -6193,18 +7163,19 @@ wording. */
           {
           struct stat statbuf;
           if (fstat(deliver_datafile, &statbuf) == 0 && statbuf.st_size > max)
-            {
-            if (emf_text != NULL) fprintf(f, "%s", CS emf_text); else
-              {
+           {
+            if (emf_text)
+             fprintf(f, "%s", CS emf_text);
+           else
               fprintf(f,
-"------ The body of the message is %d characters long; only the first\n"
-"------ %d or so are included here.\n", (int)statbuf.st_size, max);
-              }
-            }
+"------ 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);
+           }
           }
 
-        fprintf(f, "\n");
+       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,
@@ -6213,17 +7184,76 @@ wording. */
 
       /* Write final text and close the template file if one is open */
 
-      if (emf != NULL)
+      if (emf)
         {
-        emf_text = next_emf(emf, US"final");
-        if (emf_text != NULL) fprintf(f, "%s", CS emf_text);
-        fclose(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.
+  
+         bounce_return_message is ignored
+         in case RET= is defined we honor these values
+         otherwise bounce_return_body is honored.
+         
+         bounce_return_size_limit is always honored.
+      */
+  
+      fprintf(f, "\n--%s\n", boundaryStr);
+
+      dsnlimitmsg = US"X-Exim-DSN-Information: Due to administrative limits only headers are returned";
+      dsnnotifyhdr = NULL;
+      topt = topt_add_return_path;
+
+      /* RET=HDRS? top priority */
+      if (dsn_ret == dsn_ret_hdrs)
+        topt |= topt_no_body;
+      else
+        /* no full body return at all? */
+        if (!bounce_return_body)
+          {
+          topt |= topt_no_body;
+          /* add header if we overrule RET=FULL */
+          if (dsn_ret == dsn_ret_full)
+            dsnnotifyhdr = dsnlimitmsg;
+          }
+        /* size limited ... return headers only if limit reached */
+        else if (bounce_return_size_limit > 0)
+          {
+          struct stat statbuf;
+          if (fstat(deliver_datafile, &statbuf) == 0 && statbuf.st_size > max)
+            {
+              topt |= topt_no_body;
+              dsnnotifyhdr = dsnlimitmsg;
+            }
+          }
+  
+      if (topt & topt_no_body)
+        fprintf(f,"Content-type: text/rfc822-headers\n\n");
+      else
+        fprintf(f,"Content-type: message/rfc822\n\n");
+
+      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,
+        0, dsnnotifyhdr, NULL, NULL, NULL, NULL, 0);
+      fflush(f);
+      /* we never add the final text. close the file */
+      if (emf)
+        (void)fclose(emf);
+      fprintf(f, "\n--%s--\n", boundaryStr);
+#endif /*EXPERIMENTAL_DSN*/
 
       /* Close the file, which should send an EOF to the child process
       that is receiving the message. Wait for it to finish. */
 
-      fclose(f);
+      (void)fclose(f);
       rc = child_close(pid, 0);     /* Waits for child to close, no timeout */
 
       /* In the test harness, let the child do it's thing first. */
@@ -6307,7 +7337,8 @@ if (addr_defer == NULL)
     else
       {
       if (Uunlink(spoolname) < 0)
-        log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to unlink %s", spoolname);
+        log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to unlink %s: %s",
+                 spoolname, strerror(errno));
       }
     }
 
@@ -6315,12 +7346,28 @@ if (addr_defer == NULL)
 
   sprintf(CS spoolname, "%s/input/%s/%s-D", spool_directory, message_subdir, id);
   if (Uunlink(spoolname) < 0)
-    log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to unlink %s", spoolname);
+    log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to unlink %s: %s",
+      spoolname, strerror(errno));
   sprintf(CS spoolname, "%s/input/%s/%s-H", spool_directory, message_subdir, id);
   if (Uunlink(spoolname) < 0)
-    log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to unlink %s", spoolname);
-  log_write(0, LOG_MAIN, "Completed");
-  }
+    log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to unlink %s: %s",
+      spoolname, strerror(errno));
+
+  /* Log the end of this message, with queue time if requested. */
+
+  if ((log_extra_selector & LX_queue_time_overall) != 0)
+    log_write(0, LOG_MAIN, "Completed QT=%s",
+      readconf_printtime( (int) ((long)time(NULL) - (long)received_time)) );
+  else
+    log_write(0, LOG_MAIN, "Completed");
+
+  /* Unset deliver_freeze so that we won't try to move the spool files further down */
+  deliver_freeze = FALSE;
+
+#ifdef EXPERIMENTAL_EVENT
+  (void) event_raise(event_action, US"msg:complete", NULL);
+#endif
+}
 
 /* If there are deferred addresses, we are keeping this message because it is
 not yet completed. Lose any temporary files that were catching output from
@@ -6437,6 +7484,10 @@ else if (addr_defer != (address_item *)(+1))
   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,
@@ -6501,8 +7552,11 @@ 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
 
-        if (warn_message_file != NULL)
+        if (warn_message_file)
           {
           wmf = Ufopen(warn_message_file, "rb");
           if (wmf == NULL)
@@ -6515,22 +7569,39 @@ else if (addr_defer != (address_item *)(+1))
           string_sprintf("%d minutes", show_time/60):
           string_sprintf("%d hours", show_time/3600);
 
-        if (errors_reply_to != NULL)
+        if (errors_reply_to)
           fprintf(f, "Reply-To: %s\n", errors_reply_to);
-        fprintf(f, "Auto-Submitted: auto-generated\n");
-        fprintf(f, "From: Mail Delivery System <Mailer-Daemon@%s>\n",
-          qualify_domain_sender);
+        fprintf(f, "Auto-Submitted: auto-replied\n");
+        moan_write_from(f);
         fprintf(f, "To: %s\n", recipients);
 
-        wmf_text = next_emf(wmf, US"header");
-        if (wmf_text != NULL)
+#ifdef EXPERIMENTAL_DSN
+        /* generated boundary string and output MIME-Headers */
+        snprintf(boundaryStr, sizeof(boundaryStr)-1,
+         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
+
+        if ((wmf_text = next_emf(wmf, US"header")))
           fprintf(f, "%s\n", wmf_text);
         else
           fprintf(f, "Subject: Warning: message %s delayed %s\n\n",
             message_id, warnmsg_delay);
 
-        wmf_text = next_emf(wmf, US"intro");
-        if (wmf_text != NULL) fprintf(f, "%s", CS wmf_text); else
+#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
+
+        if ((wmf_text = next_emf(wmf, US"intro")))
+         fprintf(f, "%s", CS wmf_text);
+       else
           {
           fprintf(f,
 "This message was created automatically by mail delivery software.\n");
@@ -6540,56 +7611,53 @@ else if (addr_defer != (address_item *)(+1))
 "A message that you sent has not yet been delivered to one or more of its\n"
 "recipients after more than ");
 
-          else fprintf(f,
+          else
+           fprintf(f,
 "A message sent by\n\n  <%s>\n\n"
 "has not yet been delivered to one or more of its recipients after more than \n",
-          sender_address);
+             sender_address);
 
-          fprintf(f, "%s on the queue on %s.\n\n", warnmsg_delay,
-            primary_hostname);
-          fprintf(f, "The message identifier is:     %s\n", message_id);
+          fprintf(f, "%s on the queue on %s.\n\n"
+             "The message identifier is:     %s\n",
+           warnmsg_delay, primary_hostname, message_id);
 
           for (h = header_list; h != NULL; h = h->next)
-            {
             if (strncmpic(h->text, US"Subject:", 8) == 0)
               fprintf(f, "The subject of the message is: %s", h->text + 9);
             else if (strncmpic(h->text, US"Date:", 5) == 0)
               fprintf(f, "The date of the message is:    %s", h->text + 6);
-            }
-          fprintf(f, "\n");
+          fputc('\n', f);
 
           fprintf(f, "The address%s to which the message has not yet been "
             "delivered %s:\n",
-            (addr_defer->next == NULL)? "" : "es",
-            (addr_defer->next == NULL)? "is": "are");
+            !addr_defer->next ? "" : "es",
+            !addr_defer->next ? "is": "are");
           }
 
-        /* List the addresses. For any that are hidden, don't give the delay
-        reason, because it might expose that which is hidden. Also, do not give
-        "retry time not reached" because that isn't helpful. */
+        /* List the addresses, with error information if allowed */
 
-        fprintf(f, "\n");
-        while (addr_defer != NULL)
+#ifdef EXPERIMENTAL_DSN
+        /* store addr_defer for machine readable part */
+        address_item *addr_dsndefer = addr_defer;
+#endif
+        fputc('\n', f);
+        while (addr_defer)
           {
           address_item *addr = addr_defer;
           addr_defer = addr->next;
-          if (print_address_information(addr, f, US"  ", US"\n    ", US"") &&
-              addr->basic_errno > ERRNO_RETRY_BASE)
-            {
-            fprintf(f, "\n    Delay reason: ");
-            print_address_error(addr, f);
-            }
-          fprintf(f, "\n");
+          if (print_address_information(addr, f, US"  ", US"\n    ", US""))
+            print_address_error(addr, f, US"Delay reason: ");
+          fputc('\n', f);
           }
-        fprintf(f, "\n");
+        fputc('\n', f);
 
         /* Final text */
 
-        if (wmf != NULL)
+        if (wmf)
           {
-          wmf_text = next_emf(wmf, US"final");
-          if (wmf_text != NULL) fprintf(f, "%s", CS wmf_text);
-          fclose(wmf);
+          if ((wmf_text = next_emf(wmf, US"final")))
+           fprintf(f, "%s", CS wmf_text);
+          (void)fclose(wmf);
           }
         else
           {
@@ -6600,10 +7668,62 @@ 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,
+         smtp_active_hostname);
+
+        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);
+          else
+            fprintf(f,"X-Original-Envelope-ID: error decoding xtext formated ENVID\n");
+          }
+        fputc('\n', f);
+
+        while (addr_dsndefer)
+          {
+          if (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");
+          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, "\n--%s\n"
+           "Content-type: text/rfc822-headers\n\n",
+         boundaryStr);
+
+        fflush(f);
+        /* header only as required by RFC. only failure DSN needs to honor RET=FULL */
+        int topt = topt_add_return_path | topt_no_body;
+        transport_filter_argv = NULL;   /* Just in case */
+        return_path = sender_address;   /* In case not previously set */
+        /* Write the original email out */
+        transport_write_message(NULL, fileno(f), topt, 0, NULL, NULL, NULL, NULL, NULL, 0);
+        fflush(f);
+
+        fprintf(f,"\n--%s--\n", boundaryStr);
+
+        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. */
 
-        fclose(f);
+        (void)fclose(f);
         if (child_close(pid, 0) == 0)
           {
           warning_count = count;
@@ -6686,7 +7806,7 @@ else if (addr_defer != (address_item *)(+1))
 /* Finished with the message log. If the message is complete, it will have
 been unlinked or renamed above. */
 
-if (message_logs) fclose(message_log);
+if (message_logs) (void)fclose(message_log);
 
 /* Now we can close and remove the journal file. Its only purpose is to record
 successfully completed deliveries asap so that this information doesn't get
@@ -6700,7 +7820,7 @@ the remove_journal flag. When the journal is left, we also don't move the
 message off the main spool if frozen and the option is set. It should get moved
 at the next attempt, after the journal has been inspected. */
 
-if (journal_fd >= 0) close(journal_fd);
+if (journal_fd >= 0) (void)close(journal_fd);
 
 if (remove_journal)
   {
@@ -6711,17 +7831,17 @@ if (remove_journal)
 
   /* Move the message off the spool if reqested */
 
-  #ifdef SUPPORT_MOVE_FROZEN_MESSAGES
+#ifdef SUPPORT_MOVE_FROZEN_MESSAGES
   if (deliver_freeze && move_frozen_messages)
     (void)spool_move_message(id, message_subdir, US"", US"F");
-  #endif
+#endif
   }
 
 /* Closing the data file frees the lock; if the file has been unlinked it
 will go away. Otherwise the message becomes available for another process
 to try delivery. */
 
-close(deliver_datafile);
+(void)close(deliver_datafile);
 deliver_datafile = -1;
 DEBUG(D_deliver) debug_printf("end delivery of %s\n", id);
 
@@ -6732,7 +7852,10 @@ expand_check_condition) to do a lookup. We must therefore be sure everything is
 released. */
 
 search_tidyup();
+acl_where = ACL_WHERE_UNKNOWN;
 return final_yield;
 }
 
+/* vi: aw ai sw=2
+*/
 /* End of deliver.c */