Use dsn_from for success-DSN messages. Bug 2404
[exim.git] / src / src / deliver.c
index 0a0ed8a342b6ce60c2b1ebed92f245312c44c287..ada042a24e935e6579bbdc33ccfe10120733862a 100644 (file)
@@ -284,13 +284,12 @@ 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; addr2 = addr2->next)
+  for (address_item * addr2 = addr->next; addr2; addr2 = addr2->next)
     {
     if (deliver_domain && Ustrcmp(deliver_domain, addr2->domain) != 0)
       deliver_domain = NULL;
@@ -329,11 +328,9 @@ Returns:    a file descriptor, or -1 (with errno set)
 static int
 open_msglog_file(uschar *filename, int mode, uschar **error)
 {
-int fd, i;
-
-for (i = 2; i > 0; i--)
+for (int i = 2; i > 0; i--)
   {
-  fd = Uopen(filename,
+  int fd = Uopen(filename,
 #ifdef O_CLOEXEC
     O_CLOEXEC |
 #endif
@@ -350,7 +347,7 @@ for (i = 2; i > 0; i--)
 #ifndef O_CLOEXEC
     (void)fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
 #endif
-    if (fchown(fd, exim_uid, exim_gid) < 0)
+    if (exim_fchown(fd, exim_uid, exim_gid, filename) < 0)
       {
       *error = US"chown";
       return -1;
@@ -370,7 +367,7 @@ for (i = 2; i > 0; i--)
                        MSGLOG_DIRECTORY_MODE, TRUE);
   }
 
-*error = US"create";
+*error = US"create or open";
 return -1;
 }
 
@@ -422,8 +419,7 @@ Returns:     nothing
 static void
 replicate_status(address_item *addr)
 {
-address_item *addr2;
-for (addr2 = addr->next; addr2; addr2 = addr2->next)
+for (address_item * addr2 = addr->next; addr2; addr2 = addr2->next)
   {
   addr2->transport =       addr->transport;
   addr2->transport_return = addr->transport_return;
@@ -657,8 +653,6 @@ Returns:      nothing
 static void
 address_done(address_item *addr, uschar *now)
 {
-address_item *dup;
-
 update_spool = TRUE;        /* Ensure spool gets updated */
 
 /* Top-level address */
@@ -685,7 +679,7 @@ else tree_add_nonrecipient(addr->unique);
 /* Check the list of duplicate addresses and ensure they are now marked
 done as well. */
 
-for (dup = addr_duplicate; dup; dup = dup->next)
+for (address_item * dup = addr_duplicate; dup; dup = dup->next)
   if (Ustrcmp(addr->unique, dup->unique) == 0)
     {
     tree_add_nonrecipient(dup->unique);
@@ -716,9 +710,10 @@ Returns:    nothing
 static void
 child_done(address_item *addr, uschar *now)
 {
-address_item *aa;
 while (addr->parent)
   {
+  address_item *aa;
+
   addr = addr->parent;
   if (--addr->child_count > 0) return;   /* Incomplete parent */
   address_done(addr, now);
@@ -761,10 +756,9 @@ d_log_interface(gstring * g)
 if (LOGGING(incoming_interface) && LOGGING(outgoing_interface)
     && sending_ip_address)
   {
-  g = string_append(g, 2, US" I=[", sending_ip_address);
-  g = LOGGING(outgoing_port)
-    ? string_append(g, 2, US"]:", string_sprintf("%d", sending_port))
-    : string_catn(g, US"]", 1);
+  g = string_fmt_append(g, " I=[%s]", sending_ip_address);
+  if (LOGGING(outgoing_port))
+    g = string_fmt_append(g, ":%d", sending_port);
   }
 return g;
 }
@@ -784,21 +778,21 @@ if (LOGGING(dnssec) && h->dnssec == DS_YES)
 g = string_append(g, 3, US" [", h->address, US"]");
 
 if (LOGGING(outgoing_port))
-  g = string_append(g, 2, US":", string_sprintf("%d", h->port));
+  g = string_fmt_append(g, ":%d", h->port);
 
 #ifdef SUPPORT_SOCKS
 if (LOGGING(proxy) && proxy_local_address)
   {
   g = string_append(g, 3, US" PRX=[", proxy_local_address, US"]");
   if (LOGGING(outgoing_port))
-    g = string_append(g, 2, US":", string_sprintf("%d", proxy_local_port));
+    g = string_fmt_append(g, ":%d", proxy_local_port);
   }
 #endif
 
 g = d_log_interface(g);
 
 if (testflag(addr, af_tcp_fastopen))
-  g = string_catn(g, US" TFO", 4);
+  g = string_catn(g, US" TFO*", testflag(addr, af_tcp_fastopen_data) ? 5 : 4);
 
 return g;
 }
@@ -807,14 +801,20 @@ return g;
 
 
 
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
 static gstring *
-d_tlslog(gstring * s, address_item * addr)
+d_tlslog(gstring * g, address_item * addr)
 {
 if (LOGGING(tls_cipher) && addr->cipher)
-  s = string_append(s, 2, US" X=", addr->cipher);
+  {
+  g = string_append(g, 2, US" X=", addr->cipher);
+#ifdef EXPERIMENTAL_TLS_RESUME
+  if (LOGGING(tls_resumption) && testflag(addr, af_tls_resume))
+    g = string_catn(g, US"*", 1);
+#endif
+  }
 if (LOGGING(tls_certificate_verified) && addr->cipher)
-  s = string_append(s, 2, US" CV=",
+  g = string_append(g, 2, US" CV=",
     testflag(addr, af_cert_verified)
     ?
 #ifdef SUPPORT_DANE
@@ -825,8 +825,8 @@ if (LOGGING(tls_certificate_verified) && addr->cipher)
       "yes"
     : "no");
 if (LOGGING(tls_peerdn) && addr->peerdn)
-  s = string_append(s, 3, US" DN=\"", string_printing(addr->peerdn), US"\"");
-return s;
+  g = string_append(g, 3, US" DN=\"", string_printing(addr->peerdn), US"\"");
+return g;
 }
 #endif
 
@@ -851,7 +851,7 @@ if (action)
   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, transport_name ? transport_name : US"main", expand_string_message);
 
   event_name = event_data = NULL;
 
@@ -862,6 +862,7 @@ if (action)
     {
     DEBUG(D_deliver)
       debug_printf("Event(%s): event_action returned \"%s\"\n", event, s);
+    errno = ERRNO_EVENT;
     return s;
     }
   }
@@ -877,20 +878,33 @@ const uschar * save_host = deliver_host;
 const uschar * save_address = deliver_host_address;
 const int      save_port =   deliver_host_port;
 
-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, "smtp") == 0
-         || Ustrcmp(addr->transport->driver_name, "lmtp") == 0
-        ? addr->message : NULL);
+if (!addr->transport)
+  {
+  if (Ustrcmp(event, "msg:fail:delivery") == 0)
+    {
+     /* An address failed with no transport involved. This happens when
+     a filter was used which triggered a fail command (in such a case
+     a transport isn't needed).  Convert it to an internal fail event. */
+
+    (void) event_raise(event_action, US"msg:fail:internal", addr->message);
+    }
+  }
+else
+  {
+  transport_name = addr->transport->name;
+
+  (void) event_raise(addr->transport->event_action, event,
+           addr->host_used
+           || Ustrcmp(addr->transport->driver_name, "smtp") == 0
+           || Ustrcmp(addr->transport->driver_name, "lmtp") == 0
+           || Ustrcmp(addr->transport->driver_name, "autoreply") == 0
+          ? addr->message : NULL);
+  }
 
 deliver_host_port =    save_port;
 deliver_host_address = save_address;
@@ -1049,8 +1063,7 @@ if (  (all_parents || testflag(addr, af_pfr))
    && addr->parent != topaddr)
   {
   uschar *s = US" (";
-  address_item *addr2;
-  for (addr2 = addr->parent; addr2 != topaddr; addr2 = addr2->parent)
+  for (address_item * addr2 = addr->parent; addr2 != topaddr; addr2 = addr2->parent)
     {
     g = string_catn(g, s, 2);
     g = string_cat (g, addr2->address);
@@ -1095,7 +1108,7 @@ static uschar buf[sizeof("0.000s")];
 if (diff->tv_sec >= 5 || !LOGGING(millisec))
   return readconf_printtime((int)diff->tv_sec);
 
-sprintf(CS buf, "%d.%03ds", (int)diff->tv_sec, (int)diff->tv_usec/1000);
+sprintf(CS buf, "%u.%03us", (uint)diff->tv_sec, (uint)diff->tv_usec/1000);
 return buf;
 }
 
@@ -1183,8 +1196,7 @@ if (addr->router)
 g = string_append(g, 2, US" T=", addr->transport->name);
 
 if (LOGGING(delivery_size))
-  g = string_append(g, 2, US" S=",
-    string_sprintf("%d", transport_count));
+  g = string_fmt_append(g, " S=%d", transport_count);
 
 /* Local delivery */
 
@@ -1219,7 +1231,7 @@ else
 #endif
     }
 
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
   g = d_tlslog(g, addr);
 #endif
 
@@ -1234,8 +1246,15 @@ else
       }
     }
 
-  if (LOGGING(pipelining) && testflag(addr, af_pipelining))
-    g = string_catn(g, US" L", 2);
+  if (LOGGING(pipelining))
+    {
+    if (testflag(addr, af_pipelining))
+      g = string_catn(g, US" L", 2);
+#ifdef SUPPORT_PIPE_CONNECT
+    if (testflag(addr, af_early_pipe))
+      g = string_catn(g, US"*", 1);
+#endif
+    }
 
 #ifndef DISABLE_PRDR
   if (testflag(addr, af_prdr_used))
@@ -1253,12 +1272,11 @@ if (  LOGGING(smtp_confirmation)
    && (addr->host_used || Ustrcmp(addr->transport->driver_name, "lmtp") == 0)
    )
   {
-  unsigned i;
   unsigned lim = big_buffer_size < 1024 ? big_buffer_size : 1024;
   uschar *p = big_buffer;
   uschar *ss = addr->message;
   *p++ = '\"';
-  for (i = 0; i < lim && ss[i] != 0; i++)      /* limit logged amount */
+  for (int i = 0; i < lim && ss[i] != 0; i++)  /* limit logged amount */
     {
     if (ss[i] == '\"' || ss[i] == '\\') *p++ = '\\'; /* quote \ and " */
     *p++ = ss[i];
@@ -1325,13 +1343,12 @@ if (driver_name)
   {
   if (driver_kind[1] == 't' && addr->router)
     g = string_append(g, 2, US" R=", addr->router->name);
-  g = string_cat(g, string_sprintf(" %c=%s", toupper(driver_kind[1]), driver_name));
+  g = string_fmt_append(g, " %c=%s", toupper(driver_kind[1]), driver_name);
   }
 else if (driver_kind)
   g = string_append(g, 2, US" ", driver_kind);
 
-/*XXX need an s+s+p sprintf */
-g = string_cat(g, string_sprintf(" defer (%d)", addr->basic_errno));
+g = string_fmt_append(g, " defer (%d)", addr->basic_errno);
 
 if (addr->basic_errno > 0)
   g = string_append(g, 2, US": ",
@@ -1345,8 +1362,7 @@ if (addr->host_used)
   if (LOGGING(outgoing_port))
     {
     int port = addr->host_used->port;
-    g = string_append(g, 2,
-         US":", port == PORT_NONE ? US"25" : string_sprintf("%d", port));
+    g = string_fmt_append(g, ":%d", port == PORT_NONE ? 25 : port);
     }
   }
 
@@ -1383,6 +1399,16 @@ failure_log(address_item * addr, uschar * driver_kind, uschar * now)
 void * reset_point;
 gstring * g = reset_point = string_get(256);
 
+#ifndef DISABLE_EVENT
+/* Message failures for which we will send a DSN get their event raised
+later so avoid doing it here. */
+
+if (  !addr->prop.ignore_error
+   && !(addr->dsn_flags & (rf_dsnflags & ~rf_notify_failure))
+   )
+  msg_event_raise(US"msg:fail:delivery", addr);
+#endif
+
 /* Build up the log line for the message and main logs */
 
 /* Create the address string for logging. Must not do this earlier, because
@@ -1409,7 +1435,7 @@ if (addr->transport)
 if (addr->host_used)
   g = d_hostlog(g, addr);
 
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
 g = d_tlslog(g, addr);
 #endif
 
@@ -1431,10 +1457,6 @@ else
 
 log_write(0, LOG_MAIN, "** %s", g->s);
 
-#ifndef DISABLE_EVENT
-msg_event_raise(US"msg:fail:delivery", addr);
-#endif
-
 store_reset(reset_point);
 return;
 }
@@ -1613,7 +1635,7 @@ if (result == OK)
     }
 
   /* Certificates for logging (via events) */
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
   tls_out.ourcert = addr->ourcert;
   addr->ourcert = NULL;
   tls_out.peercert = addr->peercert;
@@ -1629,7 +1651,7 @@ if (result == OK)
 
   delivery_log(LOG_MAIN, addr, logchar, NULL);
 
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
   tls_free_cert(&tls_out.ourcert);
   tls_free_cert(&tls_out.peercert);
   tls_out.cipher = NULL;
@@ -1760,22 +1782,20 @@ Returns:       nothing
 static void
 common_error(BOOL logit, address_item *addr, int code, uschar *format, ...)
 {
-address_item *addr2;
 addr->basic_errno = code;
 
 if (format)
   {
   va_list ap;
-  uschar buffer[512];
+  gstring * g;
+
   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 " SIZE_T_FMT, sizeof(buffer));
+  g = string_vformat(NULL, TRUE, CS format, ap);
   va_end(ap);
-  addr->message = string_copy(buffer);
+  addr->message = string_from_gstring(g);
   }
 
-for (addr2 = addr->next; addr2; addr2 = addr2->next)
+for (address_item * addr2 = addr->next; addr2; addr2 = addr2->next)
   {
   addr2->basic_errno = code;
   addr2->message = addr->message;
@@ -1805,9 +1825,8 @@ Returns:      TRUE if the uid is on the list
 static BOOL
 check_never_users(uid_t uid, uid_t *nusers)
 {
-int i;
 if (!nusers) return FALSE;
-for (i = 1; i <= (int)(nusers[0]); i++) if (nusers[i] == uid) return TRUE;
+for (int i = 1; i <= (int)(nusers[0]); i++) if (nusers[i] == uid) return TRUE;
 return FALSE;
 }
 
@@ -2352,9 +2371,8 @@ if ((pid = fork()) == 0)
 
   DEBUG(D_deliver)
     {
-    address_item *batched;
     debug_printf("  home=%s current=%s\n", deliver_home, working_directory);
-    for (batched = addr->next; batched; batched = batched->next)
+    for (address_item * batched = addr->next; batched; batched = batched->next)
       debug_printf("additional batched address: %s\n", batched->address);
     }
 
@@ -2888,7 +2906,7 @@ while (addr_local)
   of these checks, rather than for all local deliveries, because some local
   deliveries (e.g. to pipes) can take a substantial time. */
 
-  if (!(dbm_file = dbfn_open(US"retry", O_RDONLY, &dbblock, FALSE)))
+  if (!(dbm_file = dbfn_open(US"retry", O_RDONLY, &dbblock, FALSE, TRUE)))
     {
     DEBUG(D_deliver|D_retry|D_hints_lookup)
       debug_printf("no retry data available\n");
@@ -3092,12 +3110,7 @@ while (addr_local)
 
         DEBUG(D_deliver|D_transport)
           debug_printf("%s shadow transport returned %s for %s\n",
-            stp->name,
-            sresult == OK ?    "OK" :
-            sresult == DEFER ? "DEFER" :
-            sresult == FAIL ?  "FAIL" :
-            sresult == PANIC ? "PANIC" : "?",
-            shadow_addr->address);
+            stp->name, rc_to_string(sresult), shadow_addr->address);
         }
 
       DEBUG(D_deliver|D_transport)
@@ -3126,12 +3139,7 @@ while (addr_local)
 
     DEBUG(D_deliver|D_transport)
       debug_printf("%s transport returned %s for %s\n",
-        tp->name,
-        result == OK ?    "OK" :
-        result == DEFER ? "DEFER" :
-        result == FAIL ?  "FAIL" :
-        result == PANIC ? "PANIC" : "?",
-        addr2->address);
+        tp->name, rc_to_string(result), addr2->address);
 
     /* If there is a retry_record, or if delivery is deferred, build a retry
     item for setting a new retry time or deleting the old retry record from
@@ -3264,9 +3272,8 @@ while (  *aptr
 
 DEBUG(D_deliver)
   {
-  address_item *addr;
   debug_printf("remote addresses after sorting:\n");
-  for (addr = addr_remote; addr; addr = addr->next)
+  for (address_item * addr = addr_remote; addr; addr = addr->next)
     debug_printf("  %s\n", addr->address);
   }
 }
@@ -3296,8 +3303,8 @@ 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.
 
-hs12: This assumption is not true anymore, since we got quit large items (certificate
-information and such)
+hs12: This assumption is not true anymore, since we get quite large items (certificate
+information and such).
 
 Argument:
   poffset     the offset of the parlist item
@@ -3498,7 +3505,7 @@ 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
+#ifndef DISABLE_TLS
     case 'X':
       if (!addr) goto ADDR_MISMATCH;          /* Below, in 'A' handler */
       switch (*subid)
@@ -3536,7 +3543,7 @@ while (!done)
        }
       while (*ptr++);
       break;
-#endif /*SUPPORT_TLS*/
+#endif /*DISABLE_TLS*/
 
     case 'C':  /* client authenticator information */
       switch (*subid)
@@ -3557,6 +3564,9 @@ while (!done)
     case 'L':
       switch (*subid)
        {
+#ifdef SUPPORT_PIPE_CONNECT
+       case 2: setflag(addr, af_early_pipe);   /*FALLTHROUGH*/
+#endif
        case 1: setflag(addr, af_pipelining); break;
        }
       break;
@@ -3568,6 +3578,7 @@ while (!done)
     case 'T':
       setflag(addr, af_tcp_fastopen_conn);
       if (*subid > '0') setflag(addr, af_tcp_fastopen);
+      if (*subid > '1') setflag(addr, af_tcp_fastopen_data);
       break;
 
     case 'D':
@@ -3767,12 +3778,10 @@ static void
 remote_post_process(address_item *addr, int logflags, uschar *msg,
   BOOL fallback)
 {
-host_item *h;
-
 /* If any host addresses were found to be unusable, add them to the unusable
 tree so that subsequent deliveries don't try them. */
 
-for (h = addr->host_list; h; h = h->next)
+for (host_item * h = addr->host_list; h; h = h->next)
   if (h->address)
     if (h->status >= hstatus_unusable) tree_add_unusable(h);
 
@@ -4206,7 +4215,6 @@ static BOOL
 do_remote_deliveries(BOOL fallback)
 {
 int parmax;
-int delivery_count;
 int poffset;
 
 parcount = 0;    /* Number of executing subprocesses */
@@ -4230,7 +4238,7 @@ if (!parlist)
 
 /* Now loop for each remote delivery */
 
-for (delivery_count = 0; addr_remote; delivery_count++)
+for (int delivery_count = 0; addr_remote; delivery_count++)
   {
   pid_t pid;
   uid_t uid;
@@ -4529,9 +4537,8 @@ for (delivery_count = 0; addr_remote; delivery_count++)
         && addr->host_list
         )
        {
-       host_item * h;
        ok = FALSE;
-       for (h = addr->host_list; h; h = h->next)
+       for (host_item * h = addr->host_list; h; h = h->next)
          if (Ustrcmp(h->name, continue_hostname) == 0)
   /*XXX should also check port here */
            { ok = TRUE; break; }
@@ -4583,12 +4590,9 @@ for (delivery_count = 0; addr_remote; delivery_count++)
     interface to the transport. */
 
     for (next = addr_remote; next && !f.continue_more; next = next->next)
-      {
-      host_item *h;
-      for (h = next->host_list; h; h = h->next)
+      for (host_item * h = next->host_list; h; h = h->next)
         if (Ustrcmp(h->name, continue_hostname) == 0)
           { f.continue_more = TRUE; break; }
-      }
     }
 
   /* The transports set up the process info themselves as they may connect
@@ -4666,12 +4670,10 @@ all pipes, so I do not see a reason to use non-blocking IO here
 
   search_tidyup();
 
-
   if ((pid = fork()) == 0)
     {
     int fd = pfd[pipe_write];
     host_item *h;
-    DEBUG(D_deliver) debug_selector |= D_pid;  // hs12
 
     /* Setting this global in the subprocess means we need never clear it */
     transport_name = tp->name;
@@ -4798,9 +4800,12 @@ all pipes, so I do not see a reason to use non-blocking IO here
 #ifdef SUPPORT_DANE
       if (tls_out.dane_verified)        setflag(addr, af_dane_verified);
 #endif
+# ifdef EXPERIMENTAL_TLS_RESUME
+      if (tls_out.resumption & RESUME_USED) setflag(addr, af_tls_resume);
+# endif
 
       /* Use an X item only if there's something to send */
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
       if (addr->cipher)
         {
         ptr = big_buffer + sprintf(CS big_buffer, "%.128s", addr->cipher) + 1;
@@ -4843,7 +4848,7 @@ all pipes, so I do not see a reason to use non-blocking IO here
         rmt_dlv_checked_write(fd, 'X', '4', big_buffer, ptr - big_buffer);
        }
 # endif
-#endif /*SUPPORT_TLS*/
+#endif /*DISABLE_TLS*/
 
       if (client_authenticator)
         {
@@ -4867,6 +4872,11 @@ all pipes, so I do not see a reason to use non-blocking IO here
 #endif
 
       if (testflag(addr, af_pipelining))
+#ifdef SUPPORT_PIPE_CONNECT
+       if (testflag(addr, af_early_pipe))
+         rmt_dlv_checked_write(fd, 'L', '2', NULL, 0);
+       else
+#endif
          rmt_dlv_checked_write(fd, 'L', '1', NULL, 0);
 
       if (testflag(addr, af_chunking_used))
@@ -4874,7 +4884,9 @@ all pipes, so I do not see a reason to use non-blocking IO here
 
       if (testflag(addr, af_tcp_fastopen_conn))
        rmt_dlv_checked_write(fd, 'T',
-         testflag(addr, af_tcp_fastopen) ? '1' : '0', NULL, 0);
+         testflag(addr, af_tcp_fastopen) ? testflag(addr, af_tcp_fastopen_data)
+         ? '2' : '1' : '0',
+         NULL, 0);
 
       memcpy(big_buffer, &addr->dsn_aware, sizeof(addr->dsn_aware));
       rmt_dlv_checked_write(fd, 'D', '0', big_buffer, sizeof(addr->dsn_aware));
@@ -5000,7 +5012,7 @@ all pipes, so I do not see a reason to use non-blocking IO here
 
   if (cutthrough.cctx.sock >= 0 && cutthrough.callout_hold_only)
     {
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
     if (cutthrough.is_tls)
       tls_close(cutthrough.cctx.tls_ctx, TLS_NO_SHUTDOWN);
 #endif
@@ -5232,15 +5244,12 @@ static int
 continue_closedown(void)
 {
 if (continue_transport)
-  {
-  transport_instance *t;
-  for (t = transports; t; t = t->next)
+  for (transport_instance * t = transports; t; t = t->next)
     if (Ustrcmp(t->name, continue_transport) == 0)
       {
       if (t->info->closedown) (t->info->closedown)(t);
       break;
       }
-  }
 return DELIVER_NOT_ATTEMPTED;
 }
 
@@ -5348,7 +5357,7 @@ static void
 print_address_error(address_item *addr, FILE *f, uschar *t)
 {
 int count = Ustrlen(t);
-uschar *s = testflag(addr, af_pass_message)? addr->message : NULL;
+uschar *s = testflag(addr, af_pass_message) ? addr->message : NULL;
 
 if (!s && !(s = addr->user_message))
   return;
@@ -5569,7 +5578,8 @@ message size. This use of strcpy() is OK because the length id is checked when
 it is obtained from a command line (the -M or -q options), and otherwise it is
 known to be a valid message id. */
 
-Ustrcpy(message_id, id);
+if (id != message_id)
+  Ustrcpy(message_id, id);
 f.deliver_force = forced;
 return_count = 0;
 message_size = 0;
@@ -5854,9 +5864,7 @@ else if (system_filter && process_recipients != RECIP_FAIL_TIMEOUT)
     ugid.uid_set = ugid.gid_set = TRUE;
     }
   else
-    {
     ugid.uid_set = ugid.gid_set = FALSE;
-    }
 
   return_path = sender_address;
   f.enable_dollar_recipients = TRUE;   /* Permit $recipients in system filter */
@@ -6236,30 +6244,35 @@ if (process_recipients != RECIP_IGNORE)
         }
 
 #ifndef DISABLE_EVENT
-      if (process_recipients != RECIP_ACCEPT)
+      if (process_recipients != RECIP_ACCEPT && event_action)
        {
        uschar * save_local =  deliver_localpart;
        const uschar * save_domain = deliver_domain;
+       uschar * addr = new->address, * errmsg = NULL;
+       int start, end, dom;
 
-       deliver_localpart = expand_string(
-                     string_sprintf("${local_part:%s}", new->address));
-       deliver_domain =    expand_string(
-                     string_sprintf("${domain:%s}", new->address));
+       if (!parse_extract_address(addr, &errmsg, &start, &end, &dom, TRUE))
+         log_write(0, LOG_MAIN|LOG_PANIC,
+                "failed to parse address '%.100s': %s\n", addr, errmsg);
+       else
+         {
+         deliver_localpart =
+           string_copyn(addr+start, dom ? (dom-1) - start : end - start);
+         deliver_domain = dom ? CUS string_copyn(addr+dom, end - dom) : CUS"";
 
-       (void) event_raise(event_action,
-                     US"msg:fail:internal", new->message);
+         event_raise(event_action, US"msg:fail:internal", new->message);
 
-       deliver_localpart = save_local;
-       deliver_domain =    save_domain;
+         deliver_localpart = save_local;
+         deliver_domain = save_domain;
+         }
        }
 #endif
       }
 
 DEBUG(D_deliver)
   {
-  address_item *p;
   debug_printf("Delivery address list:\n");
-  for (p = addr_new; p; p = p->next)
+  for (address_item * p = addr_new; p; p = p->next)
     debug_printf("  %s %s\n", p->address,
       p->onetime_parent ? p->onetime_parent : US"");
   }
@@ -6317,7 +6330,7 @@ while (addr_new)           /* Loop until all addresses dealt with */
   /* Failure to open the retry database is treated the same as if it does
   not exist. In both cases, dbm_file is NULL. */
 
-  if (!(dbm_file = dbfn_open(US"retry", O_RDONLY, &dbblock, FALSE)))
+  if (!(dbm_file = dbfn_open(US"retry", O_RDONLY, &dbblock, FALSE, TRUE)))
     DEBUG(D_deliver|D_retry|D_route|D_hints_lookup)
       debug_printf("no retry data available\n");
 
@@ -6573,13 +6586,21 @@ while (addr_new)           /* Loop until all addresses dealt with */
       if (  domain_retry_record
          && now - domain_retry_record->time_stamp > retry_data_expire
         )
+       {
+       DEBUG(D_deliver|D_retry)
+         debug_printf("domain retry record present but expired\n");
         domain_retry_record = NULL;    /* Ignore if too old */
+       }
 
       address_retry_record = dbfn_read(dbm_file, addr->address_retry_key);
       if (  address_retry_record
          && now - address_retry_record->time_stamp > retry_data_expire
         )
+       {
+       DEBUG(D_deliver|D_retry)
+         debug_printf("address retry record present but expired\n");
         address_retry_record = NULL;   /* Ignore if too old */
+       }
 
       if (!address_retry_record)
         {
@@ -6588,7 +6609,11 @@ while (addr_new)           /* Loop until all addresses dealt with */
         address_retry_record = dbfn_read(dbm_file, altkey);
         if (  address_retry_record
           && now - address_retry_record->time_stamp > retry_data_expire)
+         {
+         DEBUG(D_deliver|D_retry)
+           debug_printf("address<sender> retry record present but expired\n");
           address_retry_record = NULL;   /* Ignore if too old */
+         }
         }
       }
     else
@@ -6597,9 +6622,18 @@ while (addr_new)           /* Loop until all addresses dealt with */
     DEBUG(D_deliver|D_retry)
       {
       if (!domain_retry_record)
-        debug_printf("no domain retry record\n");
+       debug_printf("no   domain  retry record\n");
+      else
+       debug_printf("have domain  retry record; next_try = now%+d\n",
+                     f.running_in_test_harness ? 0 :
+                     (int)(domain_retry_record->next_try - now));
+
       if (!address_retry_record)
-        debug_printf("no address retry record\n");
+       debug_printf("no   address retry record\n");
+      else
+       debug_printf("have address retry record; next_try = now%+d\n",
+                     f.running_in_test_harness ? 0 :
+                     (int)(address_retry_record->next_try - now));
       }
 
     /* If we are sending a message down an existing SMTP connection, we must
@@ -6621,6 +6655,9 @@ while (addr_new)           /* Loop until all addresses dealt with */
       addr->message = US"reusing SMTP connection skips previous routing defer";
       addr->basic_errno = ERRNO_RRETRY;
       (void)post_process_one(addr, DEFER, LOG_MAIN, EXIM_DTYPE_ROUTER, 0);
+
+      addr->message = domain_retry_record->text;
+      setflag(addr, af_pass_message);
       }
 
     /* If we are in a queue run, defer routing unless there is no retry data or
@@ -6674,6 +6711,16 @@ while (addr_new)           /* Loop until all addresses dealt with */
       addr->message = US"retry time not reached";
       addr->basic_errno = ERRNO_RRETRY;
       (void)post_process_one(addr, DEFER, LOG_MAIN, EXIM_DTYPE_ROUTER, 0);
+
+      /* For remote-retry errors (here and just above) that we've not yet
+      hit the rery time, use the error recorded in the retry database
+      as info in the warning message.  This lets us send a message even
+      when we're not failing on a fresh attempt.  We assume that this
+      info is not sensitive. */
+
+      addr->message = domain_retry_record
+       ? domain_retry_record->text : address_retry_record->text;
+      setflag(addr, af_pass_message);
       }
 
     /* The domain is OK for routing. Remember if retry data exists so it
@@ -6869,22 +6916,21 @@ while (addr_new)           /* Loop until all addresses dealt with */
 
 DEBUG(D_deliver|D_retry|D_route)
   {
-  address_item *p;
   debug_printf(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
   debug_printf("After routing:\n  Local deliveries:\n");
-  for (p = addr_local; p; p = p->next)
+  for (address_item * p = addr_local; p; p = p->next)
     debug_printf("    %s\n", p->address);
 
   debug_printf("  Remote deliveries:\n");
-  for (p = addr_remote; p; p = p->next)
+  for (address_item * p = addr_remote; p; p = p->next)
     debug_printf("    %s\n", p->address);
 
   debug_printf("  Failed addresses:\n");
-  for (p = addr_failed; p; p = p->next)
+  for (address_item * p = addr_failed; p; p = p->next)
     debug_printf("    %s\n", p->address);
 
   debug_printf("  Deferred addresses:\n");
-  for (p = addr_defer; p; p = p->next)
+  for (address_item * p = addr_defer; p; p = p->next)
     debug_printf("    %s\n", p->address);
   }
 
@@ -7004,7 +7050,7 @@ if (  f.header_rewritten
   }
 
 
-/* If there are any deliveries to be and we do not already have the journal
+/* If there are any deliveries to do and we do not already have the journal
 file, create it. This is used to record successful deliveries as soon as
 possible after each delivery is known to be complete. A file opened with
 O_APPEND is used so that several processes can run simultaneously.
@@ -7035,7 +7081,7 @@ if (addr_local || addr_remote)
     that the mode is correct - the group setting doesn't always seem to get
     set automatically. */
 
-    if(  fchown(journal_fd, exim_uid, exim_gid)
+    if(  exim_fchown(journal_fd, exim_uid, exim_gid, fname)
       || fchmod(journal_fd, SPOOL_MODE)
 #ifndef O_CLOEXEC
       || fcntl(journal_fd, F_SETFD, fcntl(journal_fd, F_GETFD) | FD_CLOEXEC)
@@ -7166,8 +7212,8 @@ if (mua_wrapper)
   {
   if (addr_defer)
     {
-    address_item *addr, *nextaddr;
-    for (addr = addr_defer; addr; addr = nextaddr)
+    address_item * nextaddr;
+    for (address_item * addr = addr_defer; addr; addr = nextaddr)
       {
       log_write(0, LOG_MAIN, "** %s mua_wrapper forced failure for deferred "
         "delivery", addr->address);
@@ -7248,7 +7294,6 @@ for (addr_dsntmp = addr_succeed; addr_dsntmp; addr_dsntmp = addr_dsntmp->next)
   if (  (  addr_dsntmp->dsn_aware != dsn_support_yes
        || addr_dsntmp->dsn_flags & rf_dsnlasthop
         )
-     && addr_dsntmp->dsn_flags & rf_dsnflags
      && addr_dsntmp->dsn_flags & rf_notify_success
      )
     {
@@ -7282,7 +7327,7 @@ if (addr_senddsn)
     }
   else  /* Creation of child succeeded */
     {
-    FILE *f = fdopen(fd, "wb");
+    FILE * f = fdopen(fd, "wb");
     /* header only as required by RFC. only failure DSN needs to honor RET=FULL */
     uschar * bound;
     transport_ctx tctx = {{0}};
@@ -7297,8 +7342,8 @@ if (addr_senddsn)
     if (errors_reply_to)
       fprintf(f, "Reply-To: %s\n", errors_reply_to);
 
+    moan_write_from(f);
     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"
@@ -7309,17 +7354,15 @@ if (addr_senddsn)
 
        "This message was created automatically by mail delivery software.\n"
        " ----- The following addresses had successful delivery notifications -----\n",
-      qualify_domain_sender, sender_address, bound, bound);
+      sender_address, bound, bound);
 
     for (addr_dsntmp = addr_senddsn; addr_dsntmp;
         addr_dsntmp = addr_dsntmp->next)
       fprintf(f, "<%s> (relayed %s)\n\n",
        addr_dsntmp->address,
-       (addr_dsntmp->dsn_flags & rf_dsnlasthop) == 1
-         ? "via non DSN router"
-         : addr_dsntmp->dsn_aware == dsn_support_no
-         ? "to non-DSN-aware mailer"
-         : "via non \"Remote SMTP\" router"
+       addr_dsntmp->dsn_flags & rf_dsnlasthop ? "via non DSN router"
+       : addr_dsntmp->dsn_aware == dsn_support_no ? "to non-DSN-aware mailer"
+       : "via non \"Remote SMTP\" router"
        );
 
     fprintf(f, "--%s\n"
@@ -7354,7 +7397,7 @@ if (addr_senddsn)
          addr_dsntmp->host_used->name);
       else
        fprintf(f, "Diagnostic-Code: X-Exim; relayed via non %s router\n\n",
-         (addr_dsntmp->dsn_flags & rf_dsnlasthop) == 1 ? "DSN" : "SMTP");
+         addr_dsntmp->dsn_flags & rf_dsnlasthop ? "DSN" : "SMTP");
       }
 
     fprintf(f, "--%s\nContent-type: text/rfc822-headers\n\n", bound);
@@ -7365,8 +7408,10 @@ if (addr_senddsn)
 
     /* Write the original email out */
 
-    tctx.u.fd = fileno(f);
+    tctx.u.fd = fd;
     tctx.options = topt_add_return_path | topt_no_body;
+    /*XXX hmm, FALSE(fail) retval ignored.
+    Could error for any number of reasons, and they are not handled. */
     transport_write_message(&tctx, 0);
     fflush(f);
 
@@ -7435,14 +7480,16 @@ while (addr_failed)
   mark the recipient done. */
 
   if (  addr_failed->prop.ignore_error
-     || (  addr_failed->dsn_flags & rf_dsnflags
-        && (addr_failed->dsn_flags & rf_notify_failure) != rf_notify_failure
-     )  )
+     || addr_failed->dsn_flags & (rf_dsnflags & ~rf_notify_failure)
+     )
     {
     addr = addr_failed;
     addr_failed = addr->next;
     if (addr->return_filename) Uunlink(addr->return_filename);
 
+#ifndef DISABLE_EVENT
+    msg_event_raise(US"msg:fail:delivery", addr);
+#endif
     log_write(0, LOG_MAIN, "%s%s%s%s: error ignored",
       addr->address,
       !addr->parent ? US"" : US" <",
@@ -7832,6 +7879,7 @@ wording. */
        tctx.options = topt;
        tb.add_headers = dsnnotifyhdr;
 
+       /*XXX no checking for failure!  buggy! */
        transport_write_message(&tctx, 0);
        }
       fflush(fp);
@@ -7973,6 +8021,8 @@ the parent's domain.
 If all the deferred addresses have an error number that indicates "retry time
 not reached", skip sending the warning message, because it won't contain the
 reason for the delay. It will get sent at the next real delivery attempt.
+  Exception: for retries caused by a remote peer we use the error message
+  store in the retry DB as the reason.
 However, if at least one address has tried, we'd better include all of them in
 the message.
 
@@ -7990,18 +8040,17 @@ was set just to keep the message on the spool, so there is nothing to do here.
 
 else if (addr_defer != (address_item *)(+1))
   {
-  address_item *addr;
   uschar *recipients = US"";
-  BOOL delivery_attempted = FALSE;
+  BOOL want_warning_msg = FALSE;
 
   deliver_domain = testflag(addr_defer, af_pfr)
     ? addr_defer->parent->domain : addr_defer->domain;
 
-  for (addr = addr_defer; addr; addr = addr->next)
+  for (address_item * addr = addr_defer; addr; addr = addr->next)
     {
     address_item *otaddr;
 
-    if (addr->basic_errno > ERRNO_RETRY_BASE) delivery_attempted = TRUE;
+    if (addr->basic_errno > ERRNO_WARN_BASE) want_warning_msg = TRUE;
 
     if (deliver_domain)
       {
@@ -8073,9 +8122,9 @@ else if (addr_defer != (address_item *)(+1))
   it also defers). */
 
   if (  !f.queue_2stage
-     && delivery_attempted
-     && (  ((addr_defer->dsn_flags & rf_dsnflags) == 0)
-        || (addr_defer->dsn_flags & rf_notify_delay) == rf_notify_delay
+     && want_warning_msg
+     && (  !(addr_defer->dsn_flags & rf_dsnflags)
+        || addr_defer->dsn_flags & rf_notify_delay
        )
      && delay_warning[1] > 0
      && sender_address[0] != 0
@@ -8124,7 +8173,7 @@ else if (addr_defer != (address_item *)(+1))
 
     DEBUG(D_deliver)
       {
-      debug_printf("time on queue = %s\n", readconf_printtime(queue_time));
+      debug_printf("time on queue = %s  id %s  addr %s\n", readconf_printtime(queue_time), message_id, addr_defer->address);
       debug_printf("warning counts: required %d done %d\n", count,
         warning_count);
       }
@@ -8298,6 +8347,7 @@ else if (addr_defer != (address_item *)(+1))
         return_path = sender_address;   /* In case not previously set */
 
         /* Write the original email out */
+       /*XXX no checking for failure!  buggy! */
         transport_write_message(&tctx, 0);
         fflush(f);
 
@@ -8461,17 +8511,11 @@ if (!regex_SIZE) regex_SIZE =
   regex_must_compile(US"\\n250[\\s\\-]SIZE(\\s|\\n|$)", FALSE, TRUE);
 
 if (!regex_AUTH) regex_AUTH =
-  regex_must_compile(US"\\n250[\\s\\-]AUTH\\s+([\\-\\w\\s]+)(?:\\n|$)",
-    FALSE, TRUE);
+  regex_must_compile(AUTHS_REGEX, FALSE, TRUE);
 
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
 if (!regex_STARTTLS) regex_STARTTLS =
   regex_must_compile(US"\\n250[\\s\\-]STARTTLS(\\s|\\n|$)", FALSE, TRUE);
-
-# ifdef EXPERIMENTAL_REQUIRETLS
-if (!regex_REQUIRETLS) regex_REQUIRETLS =
-  regex_must_compile(US"\\n250[\\s\\-]REQUIRETLS(\\s|\\n|$)", FALSE, TRUE);
-# endif
 #endif
 
 if (!regex_CHUNKING) regex_CHUNKING =
@@ -8492,6 +8536,11 @@ if (!regex_DSN) regex_DSN  =
 
 if (!regex_IGNOREQUOTA) regex_IGNOREQUOTA =
   regex_must_compile(US"\\n250[\\s\\-]IGNOREQUOTA(\\s|\\n|$)", FALSE, TRUE);
+
+#ifdef SUPPORT_PIPE_CONNECT
+if (!regex_EARLY_PIPE) regex_EARLY_PIPE =
+  regex_must_compile(US"\\n250[\\s\\-]" EARLY_PIPE_FEATURE_NAME "(\\s|\\n|$)", FALSE, TRUE);
+#endif
 }
 
 
@@ -8554,7 +8603,7 @@ if (cutthrough.cctx.sock >= 0 && cutthrough.callout_hold_only)
   smtp_peer_options = cutthrough.peer_options;
   continue_sequence = 0;
 
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
   if (cutthrough.is_tls)
     {
     int pfd[2], pid;
@@ -8597,6 +8646,7 @@ else
   }
 return;                /* compiler quietening; control does not reach here. */
 
+#ifndef DISABLE_TLS
 fail:
   log_write(0,
     LOG_MAIN | (exec_type == CEE_EXEC_EXIT ? LOG_PANIC : LOG_PANIC_DIE),
@@ -8606,6 +8656,7 @@ fail:
   Note: this must be _exit(), not exit(). */
 
   _exit(EX_EXECFAILED);
+#endif
 }
 
 /* vi: aw ai sw=2