RFC3461 support - MIME DSN messages. Bug 118
[exim.git] / src / src / deliver.c
index 02552317a5e0ce2083aa814026898facf32aa51f..68c04877e128dad812380dea81b777da4a83a062 100644 (file)
@@ -1,10 +1,8 @@
-/* $Cambridge: exim/src/src/deliver.c,v 1.38 2006/11/06 15:50:12 ph10 Exp $ */
-
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2006 */
+/* 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;
@@ -624,7 +626,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);
     }
   }
@@ -675,6 +677,231 @@ 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)? "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
+
+/* 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_TPDA
+  tpda_delivery_ip = NULL;     /* presume no successful remote delivery */
+  tpda_delivery_port = 0;
+  tpda_delivery_fqdn = NULL;
+  tpda_delivery_local_part = NULL;
+  tpda_delivery_domain = NULL;
+  tpda_delivery_confirmation = NULL;
+  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 != NULL)
+    {
+    s = string_append(s, &size, &ptr, 2, US" H=", addr->host_list->name);
+    #ifdef EXPERIMENTAL_TPDA
+      tpda_delivery_fqdn = addr->host_list->name;
+    #endif
+    }
+  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_TPDA
+    tpda_delivery_ip =           addr->host_used->address;
+    tpda_delivery_port =         addr->host_used->port;
+    tpda_delivery_fqdn =         addr->host_used->name;
+    tpda_delivery_local_part =   addr->local_part;
+    tpda_delivery_domain =       addr->domain;
+    tpda_delivery_confirmation = addr->message;
+
+    /* 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) != 0 &&
+    addr->message != NULL &&
+    ((addr->host_used != NULL) || (Ustrcmp(addr->transport->driver_name, "lmtp") == 0)))
+  {
+  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);
+  }
+
+/* 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(time(NULL) - 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_TPDA
+if (addr->transport->tpda_delivery_action)
+  {
+  DEBUG(D_deliver)
+    debug_printf("  TPDA(Delivery): tpda_deliver_action=|%s| tpda_delivery_IP=%s\n",
+      addr->transport->tpda_delivery_action, tpda_delivery_ip);
+
+  router_name =    addr->router->name;
+  transport_name = addr->transport->name;
+  if (!expand_string(addr->transport->tpda_delivery_action) && *expand_string_message)
+    log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand tpda_deliver_action in %s: %s\n",
+      transport_name, expand_string_message);
+  router_name = NULL;
+  transport_name = NULL;
+  }
+#endif
+store_reset(reset_point);
+return;
+}
+
+
+
 /*************************************************
 *    Actions at the end of handling an address   *
 *************************************************/
@@ -744,17 +971,18 @@ malformed, it won't ever have gone near LDAP.) */
 if (addr->message != NULL)
   {
   addr->message = string_printing(addr->message);
-  if (Ustrstr(addr->message, "failed to expand") != NULL &&
-      (Ustrstr(addr->message, "ldap:") != NULL ||
+  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))
     {
-    uschar *p = Ustrstr(addr->message, "pass=");
-    if (p != NULL)
-      {
-      p += 5;
-      while (*p != 0 && !isspace(*p)) *p++ = 'x';
-      }
+      addr->message = string_sprintf("Temporary internal error");
     }
   }
 
@@ -774,7 +1002,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. */
 
@@ -839,13 +1067,7 @@ if (addr->return_file >= 0 && addr->return_filename != NULL)
   (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)
   {
@@ -861,10 +1083,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,
@@ -872,119 +1092,35 @@ 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">");
-
-  #ifdef EXPERIMENTAL_SRS
-  if(addr->p.srs_sender)
-    s = string_append(s, &size, &ptr, 3, US" SRS=<", addr->p.srs_sender, US">");
+  /* Certificates for logging (via TPDA) */
+  #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;
   #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">");
-
-  /* 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 */
+  delivery_log(LOG_MAIN, addr, logchar, NULL);
 
-  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);
-      }
-    }
-
-  /* Time on queue and actual time taken to deliver */
-
-  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;
+  #endif
   }
 
 
@@ -1032,6 +1168,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
@@ -1132,6 +1275,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)
@@ -1141,9 +1291,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);
@@ -1151,8 +1299,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": ",
@@ -1216,7 +1367,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);
   }
@@ -1726,7 +1877,20 @@ 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". */
+  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;
@@ -1811,6 +1975,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. */
 
@@ -1844,33 +2011,40 @@ if ((pid = fork()) == 0)
     int i;
     int local_part_length = Ustrlen(addr2->local_part);
     uschar *s;
+    int ret;
 
-    (void)write(pfd[pipe_write], (void *)&(addr2->transport_return), sizeof(int));
-    (void)write(pfd[pipe_write], (void *)&transport_count, sizeof(transport_count));
-    (void)write(pfd[pipe_write], (void *)&(addr2->flags), sizeof(addr2->flags));
-    (void)write(pfd[pipe_write], (void *)&(addr2->basic_errno), sizeof(int));
-    (void)write(pfd[pipe_write], (void *)&(addr2->more_errno), sizeof(int));
-    (void)write(pfd[pipe_write], (void *)&(addr2->special_action), sizeof(int));
-    (void)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))
-      {
-      (void)write(pfd[pipe_write], (void *)&local_part_length, sizeof(int));
-      (void)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;
-      (void)write(pfd[pipe_write], (void *)&message_length, sizeof(int));
-      if (message_length > 0) (void)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");
       }
     }
 
@@ -1979,7 +2153,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));
   }
@@ -2043,9 +2217,7 @@ if (addr->special_action == SPECIAL_WARN &&
           !contains_header(US"Reply-To", warn_message))
         fprintf(f, "Reply-To: %s\n", errors_reply_to);
       fprintf(f, "Auto-Submitted: auto-replied\n");
-      if (!contains_header(US"From", warn_message))
-        fprintf(f, "From: Mail Delivery System <Mailer-Daemon@%s>\n",
-          qualify_domain_sender);
+      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. */
@@ -2321,45 +2493,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);
-              debug_printf("  now=%d first_failed=%d next_try=%d expired=%d\n",
-                (int)now, (int)retry_record->first_failed,
-                (int)retry_record->next_try, retry_record->expired);
-              }
-
-            if (retry != NULL && retry->rules != NULL)
-              {
-              retry_rule *last_rule;
-              for (last_rule = retry->rules;
-                   last_rule->next != NULL;
-                   last_rule = last_rule->next);
-              DEBUG(D_deliver|D_retry)
-                debug_printf("  received_time=%d diff=%d timeout=%d\n",
-                  received_time, (int)now - received_time, last_rule->timeout);
-              if (now - received_time > last_rule->timeout) ok = TRUE;
-              }
-            else
-              {
-              DEBUG(D_deliver|D_retry)
-                debug_printf("no retry rule found: assume timed out\n");
-              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");
@@ -2858,12 +2993,73 @@ while (!done)
 
     #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 (*ptr++)
+      {
+      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;
+
+      #ifdef EXPERIMENTAL_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 (*ptr++)
+      {
+      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;
+
+#ifndef DISABLE_PRDR
+    case 'P':
+    addr->flags |= af_prdr_used;
+    break;
+#endif
+
+    #ifdef EXPERIMENTAL_DSN
+    case 'D':
+    if (addr == NULL) break;
+    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':
@@ -2890,7 +3086,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)
       {
@@ -2901,6 +3097,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++;
@@ -3375,6 +3575,15 @@ while (parcount > max)
 
 
 
+static void
+rmt_dlv_checked_write(int fd, void * buf, int size)
+{
+int ret = write(fd, buf, 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                 *
 *************************************************/
@@ -3607,6 +3816,9 @@ 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. */
 
@@ -3799,8 +4011,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 */
@@ -3891,7 +4105,7 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
       {
       if (h->address == NULL || h->status < hstatus_unusable) continue;
       sprintf(CS big_buffer, "H%c%c%s", h->status, h->why, h->address);
-      (void)write(fd, big_buffer, Ustrlen(big_buffer+3) + 4);
+      rmt_dlv_checked_write(fd, big_buffer, Ustrlen(big_buffer+3) + 4);
       }
 
     /* The number of bytes written. This is the same for each address. Even
@@ -3901,12 +4115,12 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
 
     big_buffer[0] = 'S';
     memcpy(big_buffer+1, &transport_count, sizeof(transport_count));
-    (void)write(fd, big_buffer, sizeof(transport_count) + 1);
+    rmt_dlv_checked_write(fd, big_buffer, sizeof(transport_count) + 1);
 
-    /* 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)
       {
@@ -3914,25 +4128,88 @@ 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);
 
       /* Use an X item only if there's something to send */
-
       #ifdef SUPPORT_TLS
-      if (addr->cipher != NULL)
+      if (addr->cipher)
         {
         ptr = big_buffer;
-        *ptr++ = 'X';
-        sprintf(CS ptr, "%.128s", addr->cipher);
+        sprintf(CS ptr, "X1%.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++);
           }
-        (void)write(fd, big_buffer, ptr - big_buffer);
+
+        rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer);
         }
+      if (addr->peercert)
+       {
+        ptr = big_buffer;
+       *ptr++ = 'X'; *ptr++ = '2';
+       if (!tls_export_cert(ptr, big_buffer_size-2, addr->peercert))
+         while(*ptr++);
+       else
+         *ptr++ = 0;
+        rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer);
+       }
+      if (addr->ourcert)
+       {
+        ptr = big_buffer;
+       *ptr++ = 'X'; *ptr++ = '3';
+       if (!tls_export_cert(ptr, big_buffer_size-2, addr->ourcert))
+         while(*ptr++);
+       else
+         *ptr++ = 0;
+        rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer);
+       }
+      # ifdef EXPERIMENTAL_OCSP
+      if (addr->ocsp > OCSP_NOT_REQ)
+       {
+       ptr = big_buffer;
+       sprintf(CS ptr, "X4%c", addr->ocsp + '0');
+       while(*ptr++);
+        rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer);
+       }
+      # endif
+      #endif   /*SUPPORT_TLS*/
+
+      if (client_authenticator)
+        {
+        ptr = big_buffer;
+       sprintf(CS big_buffer, "C1%.64s", client_authenticator);
+        while(*ptr++);
+        rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer);
+       }
+      if (client_authenticated_id)
+        {
+        ptr = big_buffer;
+       sprintf(CS big_buffer, "C2%.64s", client_authenticated_id);
+        while(*ptr++);
+        rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer);
+       }
+      if (client_authenticated_sender)
+        {
+        ptr = big_buffer;
+       sprintf(CS big_buffer, "C3%.64s", client_authenticated_sender);
+        while(*ptr++);
+        rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer);
+       }
+
+      #ifndef DISABLE_PRDR
+      if (addr->flags & af_prdr_used)
+       rmt_dlv_checked_write(fd, "P", 1);
+      #endif
+
+      #ifdef EXPERIMENTAL_DSN
+      big_buffer[0] = 'D';
+      memcpy(big_buffer+1, &addr->dsn_aware, sizeof(addr->dsn_aware));
+      rmt_dlv_checked_write(fd, big_buffer, sizeof(addr->dsn_aware) + 1);
+      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. */
@@ -3951,7 +4228,7 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
           sprintf(CS ptr, "%.512s", r->message);
           while(*ptr++);
           }
-        (void)write(fd, big_buffer, ptr - big_buffer);
+        rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer);
         }
 
       /* The rest of the information goes in an 'A' item. */
@@ -3986,8 +4263,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';
+
         }
-      (void)write(fd, big_buffer, ptr - big_buffer);
+      rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer);
       }
 
     /* Add termination flag, close the pipe, and that's it. The character
@@ -3997,7 +4279,7 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
 
     big_buffer[0] = 'Z';
     big_buffer[1] = (continue_transport == NULL)? '0' : '1';
-    (void)write(fd, big_buffer, 2);
+    rmt_dlv_checked_write(fd, big_buffer, 2);
     (void)close(fd);
     exit(EXIT_SUCCESS);
     }
@@ -4468,6 +4750,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) :
@@ -4518,6 +4801,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. */
@@ -4808,6 +5094,7 @@ 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 */
@@ -4941,6 +5228,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;
 
@@ -5072,6 +5362,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. */
@@ -5477,7 +5775,7 @@ while (addr_new != NULL)           /* Loop until all addresses dealt with */
 
       if (address_retry_record == NULL)
         {
-        uschar * altkey = string_sprintf("%s:<%s>", addr->address_retry_key,
+        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 &&
@@ -5534,7 +5832,18 @@ while (addr_new != NULL)           /* Loop until all addresses dealt with */
     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. */
+    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)
             &&
@@ -5544,7 +5853,11 @@ while (addr_new != NULL)           /* Loop until all addresses dealt with */
             ||
             (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;
@@ -5636,12 +5949,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)
@@ -5912,12 +6229,23 @@ if (addr_local != NULL || addr_remote != NULL)
   that the mode is correct - the group setting doesn't always seem to get
   set automatically. */
 
-  (void)fcntl(journal_fd, F_SETFD, fcntl(journal_fd, F_GETFD) | FD_CLOEXEC);
-  (void)fchown(journal_fd, exim_uid, exim_gid);
-  (void)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
@@ -5981,6 +6309,17 @@ if (addr_remote != NULL)
     regex_must_compile(US"\\n250[\\s\\-]STARTTLS(\\s|\\n|$)", FALSE, TRUE);
   #endif
 
+  #ifndef DISABLE_PRDR
+  if (regex_PRDR == NULL) regex_PRDR =
+    regex_must_compile(US"\\n250[\\s\\-]PRDR(\\s|\\n|$)", FALSE, TRUE);
+  #endif
+
+  #ifdef EXPERIMENTAL_DSN
+  /* Set the regex to check for DSN support on remote MTA */
+  if (regex_DSN == NULL) regex_DSN  =
+    regex_must_compile(US"\\n250[\\s\\-]DSN(\\s|\\n|$)", FALSE, TRUE);
+  #endif
+
   /* 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
   cannot be delivered in one transaction. */
@@ -6085,6 +6424,166 @@ 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, 63, "%d-eximdsn-%d", time(NULL), rand());      
+    DEBUG(D_deliver) debug_printf("DSN: MIME boundary: %s\n", boundaryStr);
+  
+    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,"To: %s\n", sender_address);
+    fprintf(f,"Subject: Delivery Status Notification\n");
+    fprintf(f,"Content-Type: multipart/report; report-type=delivery-status; boundary=%s\n", boundaryStr);
+    fprintf(f,"MIME-Version: 1.0\n\n");
+
+    fprintf(f,"--%s\n", boundaryStr);
+    fprintf(f,"Content-type: text/plain; charset=us-ascii\n\n");
+   
+    fprintf(f,"This message was created automatically by mail delivery software.\n");
+    fprintf(f," ----- The following addresses had successful delivery notifications -----\n");
+
+    addr_dsntmp = addr_senddsn;
+    while(addr_dsntmp != NULL)
+      {
+      if ((addr_dsntmp->dsn_flags & rf_dsnlasthop) == 1) {
+        fprintf(f,"<%s> (relayed via non DSN router)\n\n", addr_dsntmp->address);
+        }
+      else if (addr_dsntmp->dsn_aware == dsn_support_no) {
+        fprintf(f,"<%s> (relayed to non-DSN-aware mailer)\n\n", addr_dsntmp->address);
+        } 
+      else {
+        fprintf(f,"<%s> (relayed via non \"Remote SMTP\" router)\n\n", addr_dsntmp->address);
+        }
+      addr_dsntmp = addr_dsntmp->next;
+      }
+    fprintf(f,"--%s\n", boundaryStr);
+    fprintf(f,"Content-type: message/delivery-status\n\n");
+           
+    fprintf(f,"Reporting-MTA: dns; %s\n", 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");
+      }
+    fprintf(f,"\n");
+
+    addr_dsntmp = addr_senddsn;
+    while(addr_dsntmp != NULL)
+      {
+      if (addr_dsntmp->dsn_orcpt != NULL) {
+        fprintf(f,"Original-Recipient: %s\n", addr_dsntmp->dsn_orcpt);
+        }
+      fprintf(f,"Action: delivered\n");
+      fprintf(f,"Final-Recipient: rfc822;%s\n", addr_dsntmp->address);
+      fprintf(f,"Status: 2.0.0\n");
+      if ((addr_dsntmp->host_used != NULL) && (addr_dsntmp->host_used->name != NULL))
+        fprintf(f,"Remote-MTA: dns; %s\nDiagnostic-Code: smtp; 250 Ok\n", addr_dsntmp->host_used->name);
+      else
+        if ((addr_dsntmp->dsn_flags & rf_dsnlasthop) == 1)
+          fprintf(f,"Diagnostic-Code: X-Exim; relayed via non DSN router\n");
+        else
+          fprintf(f,"Diagnostic-Code: X-Exim; relayed via non SMTP router\n");
+      fprintf(f,"\n");
+      addr_dsntmp = addr_dsntmp->next;
+      }
+
+    fprintf(f,"--%s\n", boundaryStr);
+    fprintf(f,"Content-type: text/rfc822-headers\n\n");
+           
+    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
+
 /* 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
@@ -6142,8 +6641,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);
@@ -6245,10 +6749,17 @@ while (addr_failed != NULL)
       if (errors_reply_to != NULL)
         fprintf(f, "Reply-To: %s\n", errors_reply_to);
       fprintf(f, "Auto-Submitted: auto-replied\n");
-      fprintf(f, "From: Mail Delivery System <Mailer-Daemon@%s>\n",
-        qualify_domain_sender);
+      moan_write_from(f);
       fprintf(f, "To: %s\n", bounce_recipient);
 
+#ifdef EXPERIMENTAL_DSN
+      /* generate boundary string and output MIME-Headers */
+      uschar boundaryStr[64];
+      snprintf(boundaryStr, 63, "%d-eximdsn-%d", time(NULL), rand());
+      fprintf(f,"Content-Type: multipart/report; report-type=delivery-status; boundary=%s\n", boundaryStr);
+      fprintf(f,"MIME-Version: 1.0\n");
+#endif
+
       /* Open a template file if one is provided. Log failure to open, but
       carry on - default texts will be used. */
 
@@ -6276,6 +6787,12 @@ while (addr_failed != NULL)
           to_sender? ": returning message to sender" : "");
         }
 
+#ifdef EXPERIMENTAL_DSN
+      /* output human readable part as text/plain section */
+      fprintf(f,"--%s\n", boundaryStr);
+      fprintf(f,"Content-type: text/plain; charset=us-ascii\n\n");
+#endif
+
       emf_text = next_emf(emf, US"intro");
       if (emf_text != NULL) fprintf(f, "%s", CS emf_text); else
         {
@@ -6400,6 +6917,32 @@ wording. */
         fprintf(f, "\n");
         }
 
+#ifdef EXPERIMENTAL_DSN
+      /* output machine readable part */
+      fprintf(f,"--%s\n", boundaryStr);
+      fprintf(f,"Content-type: message/delivery-status\n\n");
+      fprintf(f,"Reporting-MTA: dns; %s\n", 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");
+        }
+      fprintf(f,"\n");
+      for (addr = handled_addr; addr != NULL; addr = addr->next)
+        {
+        fprintf(f,"Action: failed\n");
+        fprintf(f,"Final-Recipient: rfc822;%s\n", addr->address);
+        fprintf(f,"Status: 5.0.0\n");
+        if ((addr->host_used != NULL) && (addr->host_used->name != NULL))
+          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
       applied because of the buffering. There is, however, an option
@@ -6407,6 +6950,7 @@ wording. */
 
       emf_text = next_emf(emf, US"copy");
 
+#ifndef EXPERIMENTAL_DSN
       if (bounce_return_message)
         {
         int topt = topt_add_return_path;
@@ -6461,6 +7005,65 @@ wording. */
         if (emf_text != NULL) 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);
+
+      uschar *dsnlimitmsg = US"X-Exim-DSN-Information: Due to administrative limits only headers are returned";
+      uschar *dsnnotifyhdr = NULL;
+      int 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 != NULL)
+        (void)fclose(emf);
+      fprintf(f,"\n");
+      fprintf(f,"--%s--\n", boundaryStr);
+#endif
 
       /* Close the file, which should send an EOF to the child process
       that is receiving the message. Wait for it to finish. */
@@ -6549,7 +7152,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));
       }
     }
 
@@ -6557,10 +7161,12 @@ 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|LOG_PANIC_DIE, "failed to unlink %s: %s",
+      spoolname, strerror(errno));
 
   /* Log the end of this message, with queue time if requested. */
 
@@ -6569,6 +7175,9 @@ if (addr_defer == NULL)
       readconf_printtime(time(NULL) - 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;
   }
 
 /* If there are deferred addresses, we are keeping this message because it is
@@ -6686,6 +7295,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,
@@ -6767,10 +7380,17 @@ else if (addr_defer != (address_item *)(+1))
         if (errors_reply_to != NULL)
           fprintf(f, "Reply-To: %s\n", errors_reply_to);
         fprintf(f, "Auto-Submitted: auto-replied\n");
-        fprintf(f, "From: Mail Delivery System <Mailer-Daemon@%s>\n",
-          qualify_domain_sender);
+        moan_write_from(f);
         fprintf(f, "To: %s\n", recipients);
 
+#ifdef EXPERIMENTAL_DSN
+        /* generated boundary string and output MIME-Headers */
+        uschar boundaryStr[64];
+        snprintf(boundaryStr, 63, "%d-eximdsn-%d", time(NULL), rand());
+        fprintf(f,"Content-Type: multipart/report; report-type=delivery-status; boundary=%s\n", boundaryStr);
+        fprintf(f,"MIME-Version: 1.0\n");
+#endif
+
         wmf_text = next_emf(wmf, US"header");
         if (wmf_text != NULL)
           fprintf(f, "%s\n", wmf_text);
@@ -6778,6 +7398,12 @@ else if (addr_defer != (address_item *)(+1))
           fprintf(f, "Subject: Warning: message %s delayed %s\n\n",
             message_id, warnmsg_delay);
 
+#ifdef EXPERIMENTAL_DSN
+        /* output human readable part as text/plain section */
+        fprintf(f,"--%s\n", boundaryStr);
+        fprintf(f,"Content-type: text/plain; charset=us-ascii\n\n");
+#endif
+
         wmf_text = next_emf(wmf, US"intro");
         if (wmf_text != NULL) fprintf(f, "%s", CS wmf_text); else
           {
@@ -6815,6 +7441,10 @@ else if (addr_defer != (address_item *)(+1))
 
         /* List the addresses, with error information if allowed */
 
+#ifdef EXPERIMENTAL_DSN
+        /* store addr_defer for machine readable part */
+        address_item *addr_dsndefer = addr_defer;
+#endif
         fprintf(f, "\n");
         while (addr_defer != NULL)
           {
@@ -6843,6 +7473,54 @@ 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", boundaryStr);
+        fprintf(f,"Content-type: message/delivery-status\n\n");
+        fprintf(f,"Reporting-MTA: dns; %s\n", 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");
+          }
+        fprintf(f,"\n");
+
+        while (addr_dsndefer != NULL)
+          {
+          if (addr_dsndefer->dsn_orcpt != NULL) {
+            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 != NULL) && (addr_dsndefer->host_used->name != NULL))
+            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", boundaryStr);
+        fprintf(f,"Content-type: text/rfc822-headers\n\n");
+
+        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");
+        fprintf(f,"--%s--\n", boundaryStr);
+
+        fflush(f);
+#endif
+
         /* Close and wait for child process to complete, without a timeout.
         If there's an error, don't update the count. */
 
@@ -6975,7 +7653,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 */