constification
[exim.git] / src / src / deliver.c
index efb85348c794b9b0b78cbcb8f52c13643791282b..9d459167e68b32b90517e0c88221c4ea0fc9a8bd 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) The Exim Maintainers 2020 - 2022 */
+/* Copyright (c) The Exim Maintainers 2020 - 2023 */
 /* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 /* SPDX-License-Identifier: GPL-2.0-or-later */
@@ -68,7 +68,6 @@ static address_item *addr_new = NULL;
 static address_item *addr_remote = NULL;
 static address_item *addr_route = NULL;
 static address_item *addr_succeed = NULL;
-static address_item *addr_senddsn = NULL;
 
 static FILE *message_log = NULL;
 static BOOL update_spool;
@@ -724,7 +723,7 @@ child_done(address_item * addr, const uschar * now)
 {
 while (addr->parent)
   {
-  address_item *aa;
+  address_item * aa;
 
   addr = addr->parent;
   if (--addr->child_count > 0) return;   /* Incomplete parent */
@@ -1279,7 +1278,7 @@ if (LOGGING(deliver_time))
 /* string_cat() always leaves room for the terminator. Release the
 store we used to build the line after writing it. */
 
-log_write(0, flags, "%s", string_from_gstring(g));
+log_write(0, flags, "%Y", g);
 
 #ifndef DISABLE_EVENT
 if (!msg) msg_event_raise(US"msg:delivery", addr);
@@ -1338,25 +1337,21 @@ if (LOGGING(deliver_time))
 if (addr->message)
   g = string_append(g, 2, US": ", addr->message);
 
- {
-  const uschar * s = string_from_gstring(g);
+/* Log the deferment in the message log, but don't clutter it
+up with retry-time defers after the first delivery attempt. */
 
-  /* Log the deferment in the message log, but don't clutter it
-  up with retry-time defers after the first delivery attempt. */
+if (f.deliver_firsttime || addr->basic_errno > ERRNO_RETRY_BASE)
+  deliver_msglog("%s %.*s\n", now, g->ptr, g->s);
 
-  if (f.deliver_firsttime || addr->basic_errno > ERRNO_RETRY_BASE)
-    deliver_msglog("%s %s\n", now, s);
+/* Write the main log and reset the store.
+For errors of the type "retry time not reached" (also remotes skipped
+on queue run), logging is controlled by L_retry_defer. Note that this kind
+of error number is negative, and all the retry ones are less than any
+others. */
 
-  /* Write the main log and reset the store.
-  For errors of the type "retry time not reached" (also remotes skipped
-  on queue run), logging is controlled by L_retry_defer. Note that this kind
-  of error number is negative, and all the retry ones are less than any
-  others. */
 
-
-  log_write(addr->basic_errno <= ERRNO_RETRY_BASE ? L_retry_defer : 0, logflags,
-    "== %s", s);
- }
+log_write(addr->basic_errno <= ERRNO_RETRY_BASE ? L_retry_defer : 0, logflags,
+  "== %Y", g);
 
 store_reset(reset_point);
 return;
@@ -1422,16 +1417,12 @@ if (LOGGING(deliver_time))
 /* Do the logging. For the message log, "routing failed" for those cases,
 just to make it clearer. */
 
- {
-  const uschar * s = string_from_gstring(g);
-
-  if (driver_kind)
-    deliver_msglog("%s %s failed for %s\n", now, driver_kind, s);
-  else
-    deliver_msglog("%s %s\n", now, s);
+if (driver_kind)
+  deliver_msglog("%s %s failed for %.*s\n", now, driver_kind, g->ptr, g->s);
+else
+  deliver_msglog("%s %.*s\n", now, g->ptr, g->s);
 
-  log_write(0, LOG_MAIN, "** %s", s);
- }
+log_write(0, LOG_MAIN, "** %Y", g);
 
 store_reset(reset_point);
 return;
@@ -2372,7 +2363,9 @@ if ((pid = exim_fork(US"delivery-local")) == 0)
      addr->local_part, tp->name);
 
     /* Setting these globals in the subprocess means we need never clear them */
-    transport_name = addr->transport->name;
+
+    transport_name = tp->name;
+    if (addr->router) router_name = addr->router->name;
     driver_srcfile = tp->srcfile;
     driver_srcline = tp->srcline;
 
@@ -2383,7 +2376,7 @@ if ((pid = exim_fork(US"delivery-local")) == 0)
       {
       ok = transport_set_up_command(&transport_filter_argv,
         tp->filter_command,
-        TRUE, PANIC, addr, FALSE, US"transport filter", NULL);
+        TSUC_EXPAND_ARGS, PANIC, addr, US"transport filter", NULL);
       transport_filter_timeout = tp->filter_timeout;
       }
     else transport_filter_argv = NULL;
@@ -3352,8 +3345,8 @@ while (!done)
 
   pipeheader[PIPE_HEADER_SIZE] = '\0';
   DEBUG(D_deliver)
-    debug_printf("got %ld bytes (pipeheader) from transport process %d\n",
-      (long) got, pid);
+    debug_printf("got %ld bytes (pipeheader) '%c' from transport process %d\n",
+      (long) got, *id, pid);
 
   {
   /* If we can't decode the pipeheader, the subprocess seems to have a
@@ -3468,7 +3461,7 @@ while (!done)
 
     /* Put the amount of data written into the parlist block */
 
-    case 'S':
+    case 'S':          /* Size */
       memcpy(&(p->transport_count), ptr, sizeof(transport_count));
       ptr += sizeof(transport_count);
       break;
@@ -3558,7 +3551,7 @@ while (!done)
       if (*subid > '1') setflag(addr, af_tcp_fastopen_data);
       break;
 
-    case 'D':
+    case 'D':          /* DSN */
       if (!addr) goto ADDR_MISMATCH;
       memcpy(&(addr->dsn_aware), ptr, sizeof(addr->dsn_aware));
       ptr += sizeof(addr->dsn_aware);
@@ -4664,7 +4657,9 @@ all pipes, so I do not see a reason to use non-blocking IO here
     host_item *h;
 
     /* Setting these globals in the subprocess means we need never clear them */
-    transport_name = addr->transport->name;
+
+    transport_name = tp->name;
+    if (addr->router) router_name = addr->router->name;
     driver_srcfile = tp->srcfile;
     driver_srcline = tp->srcline;
 
@@ -5371,6 +5366,11 @@ while (*s)
       fprintf(f, "\n   ");  /* sic (because space follows) */
       count = 0;
       }
+    else if (count > 254)      /* arbitrary limit */
+      {
+      fprintf(f, "[truncated]");
+      do s++; while (*s && !(*s == '\\' && s[1] == '\n'));
+      }
     }
 }
 
@@ -5401,19 +5401,18 @@ uschar * s = testflag(addr, af_pass_message) ? addr->message : NULL;
 unsigned cnt;
 
 /* af_pass_message and addr->message set ? print remote host answer */
-if (s)
-  {
-  DEBUG(D_deliver)
-    debug_printf("DSN Diagnostic-Code: addr->message = %s\n", addr->message);
+if (!s)
+  return;
 
-  /* search first ": ". we assume to find the remote-MTA answer there */
-  if (!(s = Ustrstr(addr->message, ": ")))
-    return;                            /* not found, bail out */
-  s += 2;  /* skip ": " */
-  cnt = fprintf(f, "Diagnostic-Code: smtp; ");
-  }
-/* no message available. do nothing */
-else return;
+DEBUG(D_deliver)
+  debug_printf("DSN Diagnostic-Code: addr->message = %s\n", addr->message);
+
+/* search first ": ". we assume to find the remote-MTA answer there */
+if (!(s = Ustrstr(addr->message, ": ")))
+  return;                              /* not found, bail out */
+
+s += 2;  /* skip ": " */
+cnt = fprintf(f, "Diagnostic-Code: smtp; ");
 
 while (*s)
   {
@@ -5552,6 +5551,27 @@ else if (!(fp = Ufopen(s, "rb")))
 return fp;
 }
 
+
+/* Output the given header and string, converting either
+the sequence "\n" or a real newline into newline plus space.
+If that still takes us past column 78, look for the last space
+and split there too.
+Append a newline if string did not have one.
+Limit to about 1024 chars total. */
+
+static void
+dsn_put_wrapped(FILE * fp, const uschar * header, const uschar * s)
+{
+gstring * g = string_cat(NULL, header);
+
+g = string_cat(g, s);
+gstring_release_unused(g);
+fprintf(fp, "%s\n", wrap_header(string_from_gstring(g), 79, 1023, US" ", 1));
+}
+
+
+
+
 /*************************************************
 *              Send a bounce message             *
 *************************************************/
@@ -5723,7 +5743,7 @@ wording. */
 
     if (addr->return_file >= 0)
       {
-      paddr = &(addr->next);
+      paddr = &addr->next;
       filecount++;
       }
 
@@ -5819,7 +5839,7 @@ wording. */
   if (dsn_envid)
     {
     /* must be decoded from xtext: see RFC 3461:6.3a */
-    uschar *xdec_envid;
+    uschar * xdec_envid;
     if (auth_xtextdecode(dsn_envid, &xdec_envid) > 0)
       fprintf(fp, "Original-Envelope-ID: %s\n", dsn_envid);
     else
@@ -5830,6 +5850,9 @@ wording. */
   for (address_item * addr = handled_addr; addr; addr = addr->next)
     {
     host_item * hu;
+#ifdef EXPERIMENTAL_DSN_INFO
+    const uschar * s;
+#endif
 
     print_dsn_addr_action(fp, addr, US"failed", US"5.0.0");
 
@@ -5837,8 +5860,6 @@ wording. */
       {
       fprintf(fp, "Remote-MTA: dns; %s\n", hu->name);
 #ifdef EXPERIMENTAL_DSN_INFO
-      {
-      const uschar * s;
       if (hu->address)
        {
        uschar * p = hu->port == 25
@@ -5846,15 +5867,18 @@ wording. */
        fprintf(fp, "Remote-MTA: X-ip; [%s]%s\n", hu->address, p);
        }
       if ((s = addr->smtp_greeting) && *s)
-       fprintf(fp, "X-Remote-MTA-smtp-greeting: X-str; %.900s\n", s);
+       dsn_put_wrapped(fp, US"X-Remote-MTA-smtp-greeting: X-str; ", s);
       if ((s = addr->helo_response) && *s)
-       fprintf(fp, "X-Remote-MTA-helo-response: X-str; %.900s\n", s);
-      if ((s = addr->message) && *s)
-       fprintf(fp, "X-Exim-Diagnostic: X-str; %.900s\n", s);
-      }
+       dsn_put_wrapped(fp, US"X-Remote-MTA-helo-response: X-str; ", s);
+      if (testflag(addr, af_pass_message) && (s = addr->message) && *s)
+       dsn_put_wrapped(fp, US"X-Exim-Diagnostic: X-str; ", s);
 #endif
       print_dsn_diagnostic_code(addr, fp);
       }
+#ifdef EXPERIMENTAL_DSN_INFO
+      else if (testflag(addr, af_pass_message) && (s = addr->message) && *s)
+       dsn_put_wrapped(fp, US"X-Exim-Diagnostic: X-str; ", s);
+#endif
     fputc('\n', fp);
     }
 
@@ -5930,7 +5954,7 @@ wording. */
 
     tctx.u.fd = fileno(fp);
     tctx.tblock = &tb;
-    tctx.options = topt;
+    tctx.options = topt | topt_truncate_headers;
     tb.add_headers = dsnnotifyhdr;
 
     /*XXX no checking for failure!  buggy! */
@@ -5961,7 +5985,7 @@ wording. */
 
   if (rc != 0)
     {
-    uschar *s = US"";
+    uschar * s = US"";
     if (now - received_time.tv_sec < retry_maximum_timeout && !addr_defer)
       {
       addr_defer = (address_item *)(+1);
@@ -5993,6 +6017,337 @@ wording. */
   }
 }
 
+/*************************************************
+*              Send a warning message            *
+*************************************************/
+/* Return: boolean success */
+
+static BOOL
+send_warning_message(const uschar * recipients, int queue_time, int show_time)
+{
+int fd;
+pid_t pid = child_open_exim(&fd, US"delay-warning-message");
+FILE * wmf = NULL, * f = fdopen(fd, "wb");
+uschar * wmf_text, * bound;
+transport_ctx tctx = {{0}};
+
+
+if (pid <= 0) return FALSE;
+
+if (warn_message_file)
+  wmf = expand_open(warn_message_file,
+         US"warn_message_file", US"warning");
+
+warnmsg_recipients = recipients;
+warnmsg_delay = queue_time < 120*60
+  ? string_sprintf("%d minutes", show_time/60)
+  : string_sprintf("%d hours", show_time/3600);
+
+if (errors_reply_to)
+  fprintf(f, "Reply-To: %s\n", errors_reply_to);
+fprintf(f, "Auto-Submitted: auto-replied\n");
+moan_write_from(f);
+fprintf(f, "To: %s\n", recipients);
+moan_write_references(f, NULL);
+
+/* generated boundary string and output MIME-Headers */
+bound = string_sprintf(TIME_T_FMT "-eximdsn-%d", time(NULL), rand());
+
+fprintf(f, "Content-Type: multipart/report;"
+    " report-type=delivery-status; boundary=%s\n"
+    "MIME-Version: 1.0\n",
+  bound);
+
+if ((wmf_text = next_emf(wmf, US"header")))
+  fprintf(f, "%s\n", wmf_text);
+else
+  fprintf(f, "Subject: Warning: message %s delayed %s\n\n",
+    message_id, warnmsg_delay);
+
+/* output human readable part as text/plain section */
+fprintf(f, "--%s\n"
+    "Content-type: text/plain; charset=us-ascii\n\n",
+  bound);
+
+if ((wmf_text = next_emf(wmf, US"intro")))
+  fprintf(f, "%s", CS wmf_text);
+else
+  {
+  fprintf(f,
+"This message was created automatically by mail delivery software.\n");
+
+  if (Ustrcmp(recipients, sender_address) == 0)
+    fprintf(f,
+"A message that you sent has not yet been delivered to one or more of its\n"
+"recipients after more than ");
+
+  else
+    fprintf(f,
+"A message sent by\n\n  <%s>\n\n"
+"has not yet been delivered to one or more of its recipients after more than \n",
+      sender_address);
+
+  fprintf(f, "%s on the queue on %s.\n\n"
+      "The message identifier is:     %s\n",
+    warnmsg_delay, primary_hostname, message_id);
+
+  for (header_line * h = header_list; h; h = h->next)
+    if (strncmpic(h->text, US"Subject:", 8) == 0)
+      fprintf(f, "The subject of the message is: %s", h->text + 9);
+    else if (strncmpic(h->text, US"Date:", 5) == 0)
+      fprintf(f, "The date of the message is:    %s", h->text + 6);
+  fputc('\n', f);
+
+  fprintf(f, "The address%s to which the message has not yet been "
+    "delivered %s:\n",
+    !addr_defer->next ? "" : "es",
+    !addr_defer->next ? "is": "are");
+  }
+
+/* List the addresses, with error information if allowed */
+
+fputc('\n', f);
+for (address_item * addr = addr_defer; addr; addr = addr->next)
+  {
+  if (print_address_information(addr, f, US"  ", US"\n    ", US""))
+    print_address_error(addr, f, US"Delay reason: ");
+  fputc('\n', f);
+  }
+fputc('\n', f);
+
+/* Final text */
+
+if (wmf)
+  {
+  if ((wmf_text = next_emf(wmf, US"final")))
+    fprintf(f, "%s", CS wmf_text);
+  (void)fclose(wmf);
+  }
+else
+  {
+  fprintf(f,
+"No action is required on your part. Delivery attempts will continue for\n"
+"some time, and this warning may be repeated at intervals if the message\n"
+"remains undelivered. Eventually the mail delivery software will give up,\n"
+"and when that happens, the message will be returned to you.\n");
+  }
+
+/* output machine readable part */
+fprintf(f, "\n--%s\n"
+    "Content-type: message/delivery-status\n\n"
+    "Reporting-MTA: dns; %s\n",
+  bound,
+  smtp_active_hostname);
+
+
+if (dsn_envid)
+  {
+  /* must be decoded from xtext: see RFC 3461:6.3a */
+  uschar *xdec_envid;
+  if (auth_xtextdecode(dsn_envid, &xdec_envid) > 0)
+    fprintf(f,"Original-Envelope-ID: %s\n", dsn_envid);
+  else
+    fprintf(f,"X-Original-Envelope-ID: error decoding xtext formatted ENVID\n");
+  }
+fputc('\n', f);
+
+for (address_item * addr = addr_defer; addr; addr = addr->next)
+  {
+  host_item * hu;
+
+  print_dsn_addr_action(f, addr, US"delayed", US"4.0.0");
+
+  if ((hu = addr->host_used) && hu->name)
+    {
+    fprintf(f, "Remote-MTA: dns; %s\n", hu->name);
+    print_dsn_diagnostic_code(addr, f);
+    }
+  fputc('\n', f);
+  }
+
+fprintf(f, "--%s\n"
+    "Content-type: text/rfc822-headers\n\n",
+  bound);
+
+fflush(f);
+/* header only as required by RFC. only failure DSN needs to honor RET=FULL */
+tctx.u.fd = fileno(f);
+tctx.options = topt_add_return_path | topt_truncate_headers | topt_no_body;
+transport_filter_argv = NULL;   /* Just in case */
+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);
+
+fprintf(f,"\n--%s--\n", bound);
+
+fflush(f);
+
+/* Close and wait for child process to complete, without a timeout.
+If there's an error, don't update the count. */
+
+(void)fclose(f);
+return child_close(pid, 0) == 0;
+}
+
+/*************************************************
+*              Send a success-DSN                *
+*************************************************/
+
+static void
+maybe_send_dsn(const address_item * const addr_succeed)
+{
+address_item * addr_senddsn = NULL;
+
+for (const address_item * a = addr_succeed; a; a = a->next)
+  {
+  /* af_ignore_error not honored here. it's not an error */
+  DEBUG(D_deliver) debug_printf("DSN: processing router : %s\n"
+      "DSN: processing successful delivery address: %s\n"
+      "DSN: Sender_address: %s\n"
+      "DSN: orcpt: %s  flags: 0x%x\n"
+      "DSN: envid: %s  ret: %d\n"
+      "DSN: Final recipient: %s\n"
+      "DSN: Remote SMTP server supports DSN: %d\n",
+      a->router ? a->router->name : US"(unknown)",
+      a->address,
+      sender_address,
+      a->dsn_orcpt ? a->dsn_orcpt : US"NULL",
+      a->dsn_flags,
+      dsn_envid ? dsn_envid : US"NULL", dsn_ret,
+      a->address,
+      a->dsn_aware
+      );
+
+  /* send report if next hop not DSN aware or a router flagged "last DSN hop"
+  and a report was requested */
+
+  if (  (a->dsn_aware != dsn_support_yes || a->dsn_flags & rf_dsnlasthop)
+     && a->dsn_flags & rf_notify_success
+     )
+    {
+    /* copy and relink address_item and send report with all of them at once later */
+    address_item * addr_next = addr_senddsn;
+    addr_senddsn = store_get(sizeof(address_item), GET_UNTAINTED);
+    *addr_senddsn = *a;
+    addr_senddsn->next = addr_next;
+    }
+  else
+    DEBUG(D_deliver) debug_printf("DSN: not sending DSN success message\n");
+  }
+
+if (addr_senddsn)
+  {                            /* create exim process to send message */
+  int fd;
+  pid_t pid = child_open_exim(&fd, US"DSN");
+
+  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 success-dsn 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 */
+    uschar * bound;
+    transport_ctx tctx = {{0}};
+
+    DEBUG(D_deliver)
+      debug_printf("sending success-dsn to: %s\n", sender_address);
+
+    /* build unique id for MIME boundary */
+    bound = string_sprintf(TIME_T_FMT "-eximdsn-%d", time(NULL), rand());
+    DEBUG(D_deliver) debug_printf("DSN: MIME boundary: %s\n", bound);
+
+    if (errors_reply_to)
+      fprintf(f, "Reply-To: %s\n", errors_reply_to);
+
+    moan_write_from(f);
+    fprintf(f, "Auto-Submitted: auto-generated\n"
+       "To: %s\n"
+       "Subject: Delivery Status Notification\n",
+      sender_address);
+    moan_write_references(f, NULL);
+    fprintf(f, "Content-Type: multipart/report;"
+               " report-type=delivery-status; boundary=%s\n"
+       "MIME-Version: 1.0\n\n"
+
+       "--%s\n"
+       "Content-type: text/plain; charset=us-ascii\n\n"
+
+       "This message was created automatically by mail delivery software.\n"
+       " ----- The following addresses had successful delivery notifications -----\n",
+      bound, bound);
+
+    for (address_item * a = addr_senddsn; a; a = a->next)
+      fprintf(f, "<%s> (relayed %s)\n\n",
+       a->address,
+       a->dsn_flags & rf_dsnlasthop ? "via non DSN router"
+       : a->dsn_aware == dsn_support_no ? "to non-DSN-aware mailer"
+       : "via non \"Remote SMTP\" router"
+       );
+
+    fprintf(f, "--%s\n"
+       "Content-type: message/delivery-status\n\n"
+       "Reporting-MTA: dns; %s\n",
+      bound, smtp_active_hostname);
+
+    if (dsn_envid)
+      {                        /* must be decoded from xtext: see RFC 3461:6.3a */
+      uschar * xdec_envid;
+      if (auth_xtextdecode(dsn_envid, &xdec_envid) > 0)
+        fprintf(f, "Original-Envelope-ID: %s\n", dsn_envid);
+      else
+        fprintf(f, "X-Original-Envelope-ID: error decoding xtext formatted ENVID\n");
+      }
+    fputc('\n', f);
+
+    for (address_item * a = addr_senddsn; a; a = a->next)
+      {
+      host_item * hu;
+
+      print_dsn_addr_action(f, a, US"delivered", US"2.0.0");
+
+      if ((hu = a->host_used) && hu->name)
+        fprintf(f, "Remote-MTA: dns; %s\nDiagnostic-Code: smtp; 250 Ok\n\n",
+         hu->name);
+      else
+       fprintf(f, "Diagnostic-Code: X-Exim; relayed via non %s router\n\n",
+         a->dsn_flags & rf_dsnlasthop ? "DSN" : "SMTP");
+      }
+
+    fprintf(f, "--%s\nContent-type: text/rfc822-headers\n\n", bound);
+
+    fflush(f);
+    transport_filter_argv = NULL;   /* Just in case */
+    return_path = sender_address;   /* In case not previously set */
+
+    /* Write the original email out */
+
+    tctx.u.fd = fd;
+    tctx.options = topt_add_return_path | topt_truncate_headers | 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);
+
+    fprintf(f,"\n--%s--\n", bound);
+
+    fflush(f);
+    fclose(f);
+    (void) child_close(pid, 0);     /* Waits for child to close, no timeout */
+    }
+  }
+}
+
 /*************************************************
 *              Deliver one message               *
 *************************************************/
@@ -6033,7 +6388,7 @@ Returns:      When the global variable mua_wrapper is FALSE:
 */
 
 int
-deliver_message(uschar *id, BOOL forced, BOOL give_up)
+deliver_message(const uschar * id, BOOL forced, BOOL give_up)
 {
 int i, rc;
 int final_yield = DELIVER_ATTEMPTED_NORMAL;
@@ -6119,7 +6474,7 @@ opening the data file, message_subdir gets set. */
 if ((deliver_datafile = spool_open_datafile(id)) < 0)
   return continue_closedown();  /* yields DELIVER_NOT_ATTEMPTED */
 
-/* The value of message_size at this point has been set to the data length,
+/* tHe value of message_size at this point has been set to the data length,
 plus one for the blank line that notionally precedes the data. */
 
 /* Now read the contents of the header file, which will set up the headers in
@@ -6152,8 +6507,8 @@ give up; if the message has been around for sufficiently long, remove it. */
     if (rc != spool_read_hdrerror)
       {
       received_time.tv_sec = received_time.tv_usec = 0;
-      /*XXX subsec precision?*/
-      for (i = 0; i < 6; i++)
+      /*III subsec precision?*/
+      for (i = 0; i < MESSAGE_ID_TIME_LEN; i++)
        received_time.tv_sec = received_time.tv_sec * BASE_62 + tab62[id[i] - '0'];
       }
 
@@ -6658,8 +7013,9 @@ if (process_recipients != RECIP_IGNORE)
   for (i = 0; i < recipients_count; i++)
     if (!tree_search(tree_nonrecipients, recipients_list[i].address))
       {
-      recipient_item *r = recipients_list + i;
-      address_item *new = deliver_make_addr(r->address, FALSE);
+      recipient_item * r = recipients_list + i;
+      address_item * new = deliver_make_addr(r->address, FALSE);
+
       new->prop.errors_address = r->errors_to;
 #ifdef SUPPORT_I18N
       if ((new->prop.utf8_msg = message_smtputf8))
@@ -6719,6 +7075,8 @@ if (process_recipients != RECIP_IGNORE)
 
         case RECIP_FAIL:
          new->message  = US"delivery cancelled by administrator";
+         /* not setting af_pass_message here means that will not
+         appear in the bounce message */
          /* Fall through */
 
         /* Common code for the failure cases above. If this is not a bounce
@@ -6727,7 +7085,7 @@ if (process_recipients != RECIP_IGNORE)
         The incident has already been logged. */
 
         RECIP_QUEUE_FAILED:
-         if (sender_address[0])
+         if (*sender_address)
            {
            new->next = addr_failed;
            addr_failed = new;
@@ -7784,156 +8142,8 @@ else if (!f.dont_deliver)
   retry_update(&addr_defer, &addr_failed, &addr_succeed);
 
 /* Send DSN for successful messages if requested */
-addr_senddsn = NULL;
-
-for (address_item * a = addr_succeed; a; a = a->next)
-  {
-  /* af_ignore_error not honored here. it's not an error */
-  DEBUG(D_deliver) debug_printf("DSN: processing router : %s\n"
-      "DSN: processing successful delivery address: %s\n"
-      "DSN: Sender_address: %s\n"
-      "DSN: orcpt: %s  flags: 0x%x\n"
-      "DSN: envid: %s  ret: %d\n"
-      "DSN: Final recipient: %s\n"
-      "DSN: Remote SMTP server supports DSN: %d\n",
-      a->router ? a->router->name : US"(unknown)",
-      a->address,
-      sender_address,
-      a->dsn_orcpt ? a->dsn_orcpt : US"NULL",
-      a->dsn_flags,
-      dsn_envid ? dsn_envid : US"NULL", dsn_ret,
-      a->address,
-      a->dsn_aware
-      );
-
-  /* send report if next hop not DSN aware or a router flagged "last DSN hop"
-  and a report was requested */
-
-  if (  (a->dsn_aware != dsn_support_yes || a->dsn_flags & rf_dsnlasthop)
-     && a->dsn_flags & rf_notify_success
-     )
-    {
-    /* copy and relink address_item and send report with all of them at once later */
-    address_item * addr_next = addr_senddsn;
-    addr_senddsn = store_get(sizeof(address_item), GET_UNTAINTED);
-    *addr_senddsn = *a;
-    addr_senddsn->next = addr_next;
-    }
-  else
-    DEBUG(D_deliver) debug_printf("DSN: not sending DSN success message\n");
-  }
 
-if (addr_senddsn)
-  {
-  pid_t pid;
-  int fd;
-
-  /* create exim process to send message */
-  pid = child_open_exim(&fd, US"DSN");
-
-  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 success-dsn 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 */
-    uschar * bound;
-    transport_ctx tctx = {{0}};
-
-    DEBUG(D_deliver)
-      debug_printf("sending success-dsn to: %s\n", sender_address);
-
-    /* build unique id for MIME boundary */
-    bound = string_sprintf(TIME_T_FMT "-eximdsn-%d", time(NULL), rand());
-    DEBUG(D_deliver) debug_printf("DSN: MIME boundary: %s\n", bound);
-
-    if (errors_reply_to)
-      fprintf(f, "Reply-To: %s\n", errors_reply_to);
-
-    moan_write_from(f);
-    fprintf(f, "Auto-Submitted: auto-generated\n"
-       "To: %s\n"
-       "Subject: Delivery Status Notification\n",
-      sender_address);
-    moan_write_references(f, NULL);
-    fprintf(f, "Content-Type: multipart/report;"
-               " report-type=delivery-status; boundary=%s\n"
-       "MIME-Version: 1.0\n\n"
-
-       "--%s\n"
-       "Content-type: text/plain; charset=us-ascii\n\n"
-
-       "This message was created automatically by mail delivery software.\n"
-       " ----- The following addresses had successful delivery notifications -----\n",
-      bound, bound);
-
-    for (address_item * a = addr_senddsn; a; a = a->next)
-      fprintf(f, "<%s> (relayed %s)\n\n",
-       a->address,
-       a->dsn_flags & rf_dsnlasthop ? "via non DSN router"
-       : a->dsn_aware == dsn_support_no ? "to non-DSN-aware mailer"
-       : "via non \"Remote SMTP\" router"
-       );
-
-    fprintf(f, "--%s\n"
-       "Content-type: message/delivery-status\n\n"
-       "Reporting-MTA: dns; %s\n",
-      bound, smtp_active_hostname);
-
-    if (dsn_envid)
-      {                        /* must be decoded from xtext: see RFC 3461:6.3a */
-      uschar * xdec_envid;
-      if (auth_xtextdecode(dsn_envid, &xdec_envid) > 0)
-        fprintf(f, "Original-Envelope-ID: %s\n", dsn_envid);
-      else
-        fprintf(f, "X-Original-Envelope-ID: error decoding xtext formatted ENVID\n");
-      }
-    fputc('\n', f);
-
-    for (address_item * a = addr_senddsn; a; a = a->next)
-      {
-      host_item * hu;
-
-      print_dsn_addr_action(f, a, US"delivered", US"2.0.0");
-
-      if ((hu = a->host_used) && hu->name)
-        fprintf(f, "Remote-MTA: dns; %s\nDiagnostic-Code: smtp; 250 Ok\n\n",
-         hu->name);
-      else
-       fprintf(f, "Diagnostic-Code: X-Exim; relayed via non %s router\n\n",
-         a->dsn_flags & rf_dsnlasthop ? "DSN" : "SMTP");
-      }
-
-    fprintf(f, "--%s\nContent-type: text/rfc822-headers\n\n", bound);
-
-    fflush(f);
-    transport_filter_argv = NULL;   /* Just in case */
-    return_path = sender_address;   /* In case not previously set */
-
-    /* Write the original email out */
-
-    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);
-
-    fprintf(f,"\n--%s--\n", bound);
-
-    fflush(f);
-    fclose(f);
-    rc = child_close(pid, 0);     /* Waits for child to close, no timeout */
-    }
-  }
+maybe_send_dsn(addr_succeed);
 
 /* 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
@@ -8115,7 +8325,7 @@ was set just to keep the message on the spool, so there is nothing to do here.
 
 else if (addr_defer != (address_item *)(+1))
   {
-  uschar *recipients = US"";
+  uschar * recipients = US"";
   BOOL want_warning_msg = FALSE;
 
   deliver_domain = testflag(addr_defer, af_pfr)
@@ -8123,7 +8333,7 @@ else if (addr_defer != (address_item *)(+1))
 
   for (address_item * addr = addr_defer; addr; addr = addr->next)
     {
-    address_item *otaddr;
+    address_item * otaddr;
 
     if (addr->basic_errno > ERRNO_WARN_BASE) want_warning_msg = TRUE;
 
@@ -8245,181 +8455,11 @@ else if (addr_defer != (address_item *)(+1))
     have been. */
 
     if (warning_count < count)
-      {
-      header_line *h;
-      int fd;
-      pid_t pid = child_open_exim(&fd, US"delay-warning-message");
-
-      if (pid > 0)
-        {
-        uschar * wmf_text;
-        FILE * wmf = NULL;
-        FILE * f = fdopen(fd, "wb");
-       uschar * bound;
-       transport_ctx tctx = {{0}};
-
-        if (warn_message_file)
-         wmf = expand_open(warn_message_file,
-                 US"warn_message_file", US"warning");
-
-        warnmsg_recipients = recipients;
-        warnmsg_delay = queue_time < 120*60
-         ? string_sprintf("%d minutes", show_time/60)
-         : string_sprintf("%d hours", show_time/3600);
-
-        if (errors_reply_to)
-          fprintf(f, "Reply-To: %s\n", errors_reply_to);
-        fprintf(f, "Auto-Submitted: auto-replied\n");
-        moan_write_from(f);
-        fprintf(f, "To: %s\n", recipients);
-       moan_write_references(f, NULL);
-
-        /* generated boundary string and output MIME-Headers */
-        bound = string_sprintf(TIME_T_FMT "-eximdsn-%d", time(NULL), rand());
-
-        fprintf(f, "Content-Type: multipart/report;"
-           " report-type=delivery-status; boundary=%s\n"
-           "MIME-Version: 1.0\n",
-         bound);
-
-        if ((wmf_text = next_emf(wmf, US"header")))
-          fprintf(f, "%s\n", wmf_text);
-        else
-          fprintf(f, "Subject: Warning: message %s delayed %s\n\n",
-            message_id, warnmsg_delay);
-
-        /* output human readable part as text/plain section */
-        fprintf(f, "--%s\n"
-           "Content-type: text/plain; charset=us-ascii\n\n",
-         bound);
-
-        if ((wmf_text = next_emf(wmf, US"intro")))
-         fprintf(f, "%s", CS wmf_text);
-       else
-          {
-          fprintf(f,
-"This message was created automatically by mail delivery software.\n");
-
-          if (Ustrcmp(recipients, sender_address) == 0)
-            fprintf(f,
-"A message that you sent has not yet been delivered to one or more of its\n"
-"recipients after more than ");
-
-          else
-           fprintf(f,
-"A message sent by\n\n  <%s>\n\n"
-"has not yet been delivered to one or more of its recipients after more than \n",
-             sender_address);
-
-          fprintf(f, "%s on the queue on %s.\n\n"
-             "The message identifier is:     %s\n",
-           warnmsg_delay, primary_hostname, message_id);
-
-          for (h = header_list; h; h = h->next)
-            if (strncmpic(h->text, US"Subject:", 8) == 0)
-              fprintf(f, "The subject of the message is: %s", h->text + 9);
-            else if (strncmpic(h->text, US"Date:", 5) == 0)
-              fprintf(f, "The date of the message is:    %s", h->text + 6);
-          fputc('\n', f);
-
-          fprintf(f, "The address%s to which the message has not yet been "
-            "delivered %s:\n",
-            !addr_defer->next ? "" : "es",
-            !addr_defer->next ? "is": "are");
-          }
-
-        /* List the addresses, with error information if allowed */
-
-        fputc('\n', f);
-       for (address_item * addr = addr_defer; addr; addr = addr->next)
-          {
-          if (print_address_information(addr, f, US"  ", US"\n    ", US""))
-            print_address_error(addr, f, US"Delay reason: ");
-          fputc('\n', f);
-          }
-        fputc('\n', f);
-
-        /* Final text */
-
-        if (wmf)
-          {
-          if ((wmf_text = next_emf(wmf, US"final")))
-           fprintf(f, "%s", CS wmf_text);
-          (void)fclose(wmf);
-          }
-        else
-          {
-          fprintf(f,
-"No action is required on your part. Delivery attempts will continue for\n"
-"some time, and this warning may be repeated at intervals if the message\n"
-"remains undelivered. Eventually the mail delivery software will give up,\n"
-"and when that happens, the message will be returned to you.\n");
-          }
-
-        /* output machine readable part */
-        fprintf(f, "\n--%s\n"
-           "Content-type: message/delivery-status\n\n"
-           "Reporting-MTA: dns; %s\n",
-         bound,
-         smtp_active_hostname);
-
-
-        if (dsn_envid)
-         {
-          /* must be decoded from xtext: see RFC 3461:6.3a */
-          uschar *xdec_envid;
-          if (auth_xtextdecode(dsn_envid, &xdec_envid) > 0)
-            fprintf(f,"Original-Envelope-ID: %s\n", dsn_envid);
-          else
-            fprintf(f,"X-Original-Envelope-ID: error decoding xtext formatted ENVID\n");
-          }
-        fputc('\n', f);
-
-       for (address_item * addr = addr_defer; addr; addr = addr->next)
-          {
-         host_item * hu;
-
-         print_dsn_addr_action(f, addr, US"delayed", US"4.0.0");
-
-          if ((hu = addr->host_used) && hu->name)
-            {
-            fprintf(f, "Remote-MTA: dns; %s\n", hu->name);
-            print_dsn_diagnostic_code(addr, f);
-            }
-         fputc('\n', f);
-          }
-
-        fprintf(f, "--%s\n"
-           "Content-type: text/rfc822-headers\n\n",
-         bound);
-
-        fflush(f);
-        /* header only as required by RFC. only failure DSN needs to honor RET=FULL */
-       tctx.u.fd = fileno(f);
-        tctx.options = 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 */
-       /*XXX no checking for failure!  buggy! */
-        transport_write_message(&tctx, 0);
-        fflush(f);
-
-        fprintf(f,"\n--%s--\n", bound);
-
-        fflush(f);
-
-        /* Close and wait for child process to complete, without a timeout.
-        If there's an error, don't update the count. */
-
-        (void)fclose(f);
-        if (child_close(pid, 0) == 0)
-          {
-          warning_count = count;
-          update_spool = TRUE;    /* Ensure spool rewritten */
-          }
-        }
-      }
+      if (send_warning_message(recipients, queue_time, show_time))
+       {
+       warning_count = count;
+       update_spool = TRUE;    /* Ensure spool rewritten */
+       }
     }
 
   /* Clear deliver_domain */
@@ -8444,27 +8484,23 @@ else if (addr_defer != (address_item *)(+1))
 
   if (f.deliver_freeze)
     {
-    if (freeze_tell && freeze_tell[0] != 0 && !f.local_error_message)
+    if (freeze_tell && *freeze_tell && !f.local_error_message)
       {
-      uschar *s = string_copy(frozen_info);
-      uschar *ss = Ustrstr(s, " by the system filter: ");
+      uschar * s = string_copy(frozen_info);
+      uschar * ss = Ustrstr(s, " by the system filter: ");
 
-      if (ss != NULL)
+      if (ss)
         {
         ss[21] = '.';
         ss[22] = '\n';
         }
 
-      ss = s;
-      while (*ss != 0)
-        {
+      for (ss = s; *ss; )
         if (*ss == '\\' && ss[1] == 'n')
-          {
-          *ss++ = ' ';
-          *ss++ = '\n';
-          }
-        else ss++;
-        }
+          { *ss++ = ' '; *ss++ = '\n'; }
+        else
+         ss++;
+
       moan_tell_someone(freeze_tell, addr_defer, US"Message frozen",
         "Message %s has been frozen%s.\nThe sender is <%s>.\n", message_id,
         s, sender_address);