SUPPORT_TRANSLATE_IP_ADDRESS didn't cause any output from -bV.
[exim.git] / src / src / deliver.c
index 2bf141c163f3d707aa3db6a6c3ac78730d14c1a8..c166d26019e767f2468a3559c37d44e2777f7ef9 100644 (file)
@@ -1,10 +1,10 @@
-/* $Cambridge: exim/src/src/deliver.c,v 1.2 2004/11/18 10:35:19 ph10 Exp $ */
+/* $Cambridge: exim/src/src/deliver.c,v 1.22 2005/08/02 11:22:24 ph10 Exp $ */
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2004 */
+/* Copyright (c) University of Cambridge 1995 - 2005 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* The main code for delivering a message. */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* The main code for delivering a message. */
@@ -156,6 +156,13 @@ deliver_localpart_data = addr->p.localpart_data;
 deliver_domain = addr->domain;
 self_hostname = addr->self_hostname;
 
 deliver_domain = addr->domain;
 self_hostname = addr->self_hostname;
 
+#ifdef EXPERIMENTAL_BRIGHTMAIL
+bmi_deliver = 1;    /* deliver by default */
+bmi_alt_location = NULL;
+bmi_base64_verdict = NULL;
+bmi_base64_tracker_verdict = NULL;
+#endif
+
 /* If there's only one address we can set everything. */
 
 if (addr->next == NULL)
 /* If there's only one address we can set everything. */
 
 if (addr->next == NULL)
@@ -205,6 +212,19 @@ if (addr->next == NULL)
       deliver_localpart_suffix = addr->parent->suffix;
       }
     }
       deliver_localpart_suffix = addr->parent->suffix;
       }
     }
+
+#ifdef EXPERIMENTAL_BRIGHTMAIL
+    /* Set expansion variables related to Brightmail AntiSpam */
+    bmi_base64_verdict = bmi_get_base64_verdict(deliver_localpart_orig, deliver_domain_orig);
+    bmi_base64_tracker_verdict = bmi_get_base64_tracker_verdict(bmi_base64_verdict);
+    /* get message delivery status (0 - don't deliver | 1 - deliver) */
+    bmi_deliver = bmi_get_delivery_status(bmi_base64_verdict);
+    /* if message is to be delivered, get eventual alternate location */
+    if (bmi_deliver == 1) {
+      bmi_alt_location = bmi_get_alt_location(bmi_base64_verdict);
+    };
+#endif
+
   }
 
 /* For multiple addresses, don't set local part, and leave the domain and
   }
 
 /* For multiple addresses, don't set local part, and leave the domain and
@@ -266,7 +286,7 @@ doesn't always get set automatically. */
 
 if (fd >= 0)
   {
 
 if (fd >= 0)
   {
-  fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
+  (void)fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
   if (fchown(fd, exim_uid, exim_gid) < 0)
     {
     *error = US"chown";
   if (fchown(fd, exim_uid, exim_gid) < 0)
     {
     *error = US"chown";
@@ -709,9 +729,27 @@ else if (driver_type == DTYPE_ROUTER)
 
 /* If there's an error message set, ensure that it contains only printing
 characters - it should, but occasionally things slip in and this at least
 
 /* If there's an error message set, ensure that it contains only printing
 characters - it should, but occasionally things slip in and this at least
-stops the log format from getting wrecked. */
+stops the log format from getting wrecked. We also scan the message for an LDAP
+expansion item that has a password setting, and flatten the password. This is a
+fudge, but I don't know a cleaner way of doing this. (If the item is badly
+malformed, it won't ever have gone near LDAP.) */
 
 
-if (addr->message != NULL) addr->message = string_printing(addr->message);
+if (addr->message != NULL)
+  {
+  addr->message = string_printing(addr->message);
+  if (Ustrstr(addr->message, "failed to expand") != NULL &&
+      (Ustrstr(addr->message, "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';
+      }
+    }
+  }
 
 /* If we used a transport that has one of the "return_output" options set, and
 if it did in fact generate some output, then for return_output we treat the
 
 /* If we used a transport that has one of the "return_output" options set, and
 if it did in fact generate some output, then for return_output we treat the
@@ -760,7 +798,7 @@ if (addr->return_file >= 0 && addr->return_filename != NULL)
           log_write(0, LOG_MAIN, "<%s>: %s transport output: %s",
             addr->address, tb->name, s);
           }
           log_write(0, LOG_MAIN, "<%s>: %s transport output: %s",
             addr->address, tb->name, s);
           }
-        fclose(f);
+        (void)fclose(f);
         }
       }
 
         }
       }
 
@@ -791,7 +829,7 @@ if (addr->return_file >= 0 && addr->return_filename != NULL)
     addr->return_file = -1;
     }
 
     addr->return_file = -1;
     }
 
-  close(addr->return_file);
+  (void)close(addr->return_file);
   }
 
 /* Create the address string for logging. Must not do this earlier, because
   }
 
 /* Create the address string for logging. Must not do this earlier, because
@@ -840,6 +878,11 @@ if (result == OK)
   if ((log_extra_selector & LX_sender_on_delivery) != 0)
     s = string_append(s, &size, &ptr, 3, US" F=<", sender_address, US">");
 
   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">");
+  #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
   /* 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
@@ -1411,18 +1454,21 @@ return rc;
 *************************************************/
 
 /* Check that this base address hasn't previously been delivered to its routed
 *************************************************/
 
 /* Check that this base address hasn't previously been delivered to its routed
-transport. The check is necessary at delivery time in order to handle homonymic
-addresses correctly in cases where the pattern of redirection changes between
-delivery attempts (so the unique fields change). Non-homonymic previous
-delivery is detected earlier, at routing time (which saves unnecessary
-routing).
+transport. If it has been delivered, mark it done. The check is necessary at
+delivery time in order to handle homonymic addresses correctly in cases where
+the pattern of redirection changes between delivery attempts (so the unique
+fields change). Non-homonymic previous delivery is detected earlier, at routing
+time (which saves unnecessary routing).
+
+Arguments:
+  addr      the address item
+  testing   TRUE if testing wanted only, without side effects
 
 
-Argument:   the address item
 Returns:    TRUE if previously delivered by the transport
 */
 
 static BOOL
 Returns:    TRUE if previously delivered by the transport
 */
 
 static BOOL
-previously_transported(address_item *addr)
+previously_transported(address_item *addr, BOOL testing)
 {
 (void)string_format(big_buffer, big_buffer_size, "%s/%s",
   addr->unique + (testflag(addr, af_homonym)? 3:0), addr->transport->name);
 {
 (void)string_format(big_buffer, big_buffer_size, "%s/%s",
   addr->unique + (testflag(addr, af_homonym)? 3:0), addr->transport->name);
@@ -1432,7 +1478,7 @@ if (tree_search(tree_nonrecipients, big_buffer) != 0)
   DEBUG(D_deliver|D_route|D_transport)
     debug_printf("%s was previously delivered (%s transport): discarded\n",
     addr->address, addr->transport->name);
   DEBUG(D_deliver|D_route|D_transport)
     debug_printf("%s was previously delivered (%s transport): discarded\n",
     addr->address, addr->transport->name);
-  child_done(addr, tod_stamp(tod_log));
+  if (!testing) child_done(addr, tod_stamp(tod_log));
   return TRUE;
   }
 
   return TRUE;
   }
 
@@ -1496,8 +1542,14 @@ transport_instance *tp = addr->transport;
 /* Set up the return path from the errors or sender address. If the transport
 has its own return path setting, expand it and replace the existing value. */
 
 /* Set up the return path from the errors or sender address. If the transport
 has its own return path setting, expand it and replace the existing value. */
 
-return_path = (addr->p.errors_address != NULL)?
-  addr->p.errors_address : sender_address;
+if(addr->p.errors_address != NULL)
+  return_path = addr->p.errors_address;
+#ifdef EXPERIMENTAL_SRS
+else if(addr->p.srs_sender != NULL)
+  return_path = addr->p.srs_sender;
+#endif
+else
+  return_path = sender_address;
 
 if (tp->return_path != NULL)
   {
 
 if (tp->return_path != NULL)
   {
@@ -1676,8 +1728,8 @@ if ((pid = fork()) == 0)
   half - for transports that exec things (e.g. pipe). Then set the required
   gid/uid. */
 
   half - for transports that exec things (e.g. pipe). Then set the required
   gid/uid. */
 
-  close(pfd[pipe_read]);
-  fcntl(pfd[pipe_write], F_SETFD, fcntl(pfd[pipe_write], F_GETFD) |
+  (void)close(pfd[pipe_read]);
+  (void)fcntl(pfd[pipe_write], F_SETFD, fcntl(pfd[pipe_write], F_GETFD) |
     FD_CLOEXEC);
   exim_setugid(uid, gid, use_initgroups,
     string_sprintf("local delivery to %s <%s> transport=%s", addr->local_part,
     FD_CLOEXEC);
   exim_setugid(uid, gid, use_initgroups,
     string_sprintf("local delivery to %s <%s> transport=%s", addr->local_part,
@@ -1742,13 +1794,13 @@ if ((pid = fork()) == 0)
     int local_part_length = Ustrlen(addr2->local_part);
     uschar *s;
 
     int local_part_length = Ustrlen(addr2->local_part);
     uschar *s;
 
-    write(pfd[pipe_write], (void *)&(addr2->transport_return), sizeof(int));
-    write(pfd[pipe_write], (void *)&transport_count, sizeof(transport_count));
-    write(pfd[pipe_write], (void *)&(addr2->flags), sizeof(addr2->flags));
-    write(pfd[pipe_write], (void *)&(addr2->basic_errno), sizeof(int));
-    write(pfd[pipe_write], (void *)&(addr2->more_errno), sizeof(int));
-    write(pfd[pipe_write], (void *)&(addr2->special_action), sizeof(int));
-    write(pfd[pipe_write], (void *)&(addr2->transport),
+    (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 *));
 
     /* For a file delivery, pass back the local part, in case the original
       sizeof(transport_instance *));
 
     /* For a file delivery, pass back the local part, in case the original
@@ -1757,8 +1809,8 @@ if ((pid = fork()) == 0)
 
     if (testflag(addr2, af_file))
       {
 
     if (testflag(addr2, af_file))
       {
-      write(pfd[pipe_write], (void *)&local_part_length, sizeof(int));
-      write(pfd[pipe_write], addr2->local_part, local_part_length);
+      (void)write(pfd[pipe_write], (void *)&local_part_length, sizeof(int));
+      (void)write(pfd[pipe_write], addr2->local_part, local_part_length);
       }
 
     /* Now any messages */
       }
 
     /* Now any messages */
@@ -1766,15 +1818,15 @@ if ((pid = fork()) == 0)
     for (i = 0, s = addr2->message; i < 2; i++, s = addr2->user_message)
       {
       int message_length = (s == NULL)? 0 : Ustrlen(s) + 1;
     for (i = 0, s = addr2->message; i < 2; i++, s = addr2->user_message)
       {
       int message_length = (s == NULL)? 0 : Ustrlen(s) + 1;
-      write(pfd[pipe_write], (void *)&message_length, sizeof(int));
-      if (message_length > 0) write(pfd[pipe_write], s, message_length);
+      (void)write(pfd[pipe_write], (void *)&message_length, sizeof(int));
+      if (message_length > 0) (void)write(pfd[pipe_write], s, message_length);
       }
     }
 
   /* OK, this process is now done. Free any cached resources that it opened,
   and close the pipe we were writing down before exiting. */
 
       }
     }
 
   /* OK, this process is now done. Free any cached resources that it opened,
   and close the pipe we were writing down before exiting. */
 
-  close(pfd[pipe_write]);
+  (void)close(pfd[pipe_write]);
   search_tidyup();
   exit(EXIT_SUCCESS);
   }
   search_tidyup();
   exit(EXIT_SUCCESS);
   }
@@ -1793,7 +1845,7 @@ on an empty pipe. We check that a status exists for each address before
 overwriting the address structure. If data is missing, the default DEFER status
 will remain. Afterwards, close the reading end. */
 
 overwriting the address structure. If data is missing, the default DEFER status
 will remain. Afterwards, close the reading end. */
 
-close(pfd[pipe_write]);
+(void)close(pfd[pipe_write]);
 
 for (addr2 = addr; addr2 != NULL; addr2 = addr2->next)
   {
 
 for (addr2 = addr; addr2 != NULL; addr2 = addr2->next)
   {
@@ -1843,7 +1895,7 @@ for (addr2 = addr; addr2 != NULL; addr2 = addr2->next)
     }
   }
 
     }
   }
 
-close(pfd[pipe_read]);
+(void)close(pfd[pipe_read]);
 
 /* Unless shadowing, write all successful addresses immediately to the journal
 file, to ensure they are recorded asap. For homonymic addresses, use the base
 
 /* Unless shadowing, write all successful addresses immediately to the journal
 file, to ensure they are recorded asap. For homonymic addresses, use the base
@@ -1946,7 +1998,7 @@ if (addr->special_action == SPECIAL_WARN &&
 
       /* Close and wait for child process to complete, without a timeout. */
 
 
       /* Close and wait for child process to complete, without a timeout. */
 
-      fclose(f);
+      (void)fclose(f);
       (void)child_close(pid, 0);
       }
     }
       (void)child_close(pid, 0);
       }
     }
@@ -2019,7 +2071,7 @@ while (addr_local != NULL)
   attempts. Non-homonymic previous delivery is detected earlier, at routing
   time. */
 
   attempts. Non-homonymic previous delivery is detected earlier, at routing
   time. */
 
-  if (previously_transported(addr)) continue;
+  if (previously_transported(addr, FALSE)) continue;
 
   /* There are weird cases where logging is disabled */
 
 
   /* There are weird cases where logging is disabled */
 
@@ -2060,6 +2112,7 @@ while (addr_local != NULL)
     same characteristics. These are:
 
       same transport
     same characteristics. These are:
 
       same transport
+      not previously delivered (see comment about 50 lines above)
       same local part if the transport's configuration contains $local_part
       same domain if the transport's configuration contains $domain
       same errors address
       same local part if the transport's configuration contains $local_part
       same domain if the transport's configuration contains $domain
       same errors address
@@ -2073,6 +2126,7 @@ while (addr_local != NULL)
       {
       BOOL ok =
         tp == next->transport &&
       {
       BOOL ok =
         tp == next->transport &&
+        !previously_transported(next, TRUE) &&
         (!uses_lp  || Ustrcmp(next->local_part, addr->local_part) == 0) &&
         (!uses_dom || Ustrcmp(next->domain, addr->domain) == 0) &&
         same_strings(next->p.errors_address, addr->p.errors_address) &&
         (!uses_lp  || Ustrcmp(next->local_part, addr->local_part) == 0) &&
         (!uses_dom || Ustrcmp(next->domain, addr->domain) == 0) &&
         same_strings(next->p.errors_address, addr->p.errors_address) &&
@@ -2536,13 +2590,13 @@ also by optional retry data.
 
 Read in large chunks into the big buffer and then scan through, interpreting
 the data therein. In most cases, only a single read will be necessary. No
 
 Read in large chunks into the big buffer and then scan through, interpreting
 the data therein. In most cases, only a single read will be necessary. No
-individual item will ever be anywhere near 500 bytes in length, so by ensuring
-that we read the next chunk when there is less than 500 bytes left in the
-non-final chunk, we can assume each item is complete in store before handling
-it. Actually, each item is written using a single write(), which is atomic for
-small items (less than PIPE_BUF, which seems to be at least 512 in any Unix) so
-even if we are reading while the subprocess is still going, we should never
-have only a partial item in the buffer.
+individual item will ever be anywhere near 2500 bytes in length, so by ensuring
+that we read the next chunk when there is less than 2500 bytes left in the
+non-final chunk, we can assume each item is complete in the buffer before
+handling it. Each item is written using a single write(), which is atomic for
+small items (less than PIPE_BUF, which seems to be at least 512 in any Unix and
+often bigger) so even if we are reading while the subprocess is still going, we
+should never have only a partial item in the buffer.
 
 Argument:
   poffset     the offset of the parlist item
 
 Argument:
   poffset     the offset of the parlist item
@@ -2578,7 +2632,9 @@ completed.
 
 Each separate item is written to the pipe in a single write(), and as they are
 all short items, the writes will all be atomic and we should never find
 
 Each separate item is written to the pipe in a single write(), and as they are
 all short items, the writes will all be atomic and we should never find
-ourselves in the position of having read an incomplete item. */
+ourselves in the position of having read an incomplete item. "Short" in this
+case can mean up to about 1K in the case when there is a long error message
+associated with an address. */
 
 DEBUG(D_deliver) debug_printf("reading pipe for subprocess %d (%s)\n",
   (int)p->pid, eop? "ended" : "not ended");
 
 DEBUG(D_deliver) debug_printf("reading pipe for subprocess %d (%s)\n",
   (int)p->pid, eop? "ended" : "not ended");
@@ -2592,7 +2648,7 @@ while (!done)
   There will be only one read if we get all the available data (i.e. don't
   fill the buffer completely). */
 
   There will be only one read if we get all the available data (i.e. don't
   fill the buffer completely). */
 
-  if (remaining < 500 && unfinished)
+  if (remaining < 2500 && unfinished)
     {
     int len;
     int available = big_buffer_size - remaining;
     {
     int len;
     int available = big_buffer_size - remaining;
@@ -2829,7 +2885,7 @@ if (!eop && !done)
 /* Close our end of the pipe, to prevent deadlock if the far end is still
 pushing stuff into it. */
 
 /* Close our end of the pipe, to prevent deadlock if the far end is still
 pushing stuff into it. */
 
-close(fd);
+(void)close(fd);
 p->fd = -1;
 
 /* If we have finished without error, but haven't had data for every address,
 p->fd = -1;
 
 /* If we have finished without error, but haven't had data for every address,
@@ -3348,7 +3404,7 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
   attempts. Non-homonymic previous delivery is detected earlier, at routing
   time. */
 
   attempts. Non-homonymic previous delivery is detected earlier, at routing
   time. */
 
-  if (previously_transported(addr)) continue;
+  if (previously_transported(addr, FALSE)) continue;
 
   /* Force failure if the message is too big. */
 
 
   /* Force failure if the message is too big. */
 
@@ -3483,8 +3539,14 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
   /* Compute the return path, expanding a new one if required. The old one
   must be set first, as it might be referred to in the expansion. */
 
   /* Compute the return path, expanding a new one if required. The old one
   must be set first, as it might be referred to in the expansion. */
 
-  return_path = (addr->p.errors_address != NULL)?
-    addr->p.errors_address : sender_address;
+  if(addr->p.errors_address != NULL)
+    return_path = addr->p.errors_address;
+#ifdef EXPERIMENTAL_SRS
+  else if(addr->p.srs_sender != NULL)
+    return_path = addr->p.srs_sender;
+#endif
+  else
+    return_path = sender_address;
 
   if (tp->return_path != NULL)
     {
 
   if (tp->return_path != NULL)
     {
@@ -3611,9 +3673,9 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
     distinguishes between EOF and no-more-data. */
 
     #ifdef O_NONBLOCK
     distinguishes between EOF and no-more-data. */
 
     #ifdef O_NONBLOCK
-    fcntl(pfd[pipe_read], F_SETFL, O_NONBLOCK);
+    (void)fcntl(pfd[pipe_read], F_SETFL, O_NONBLOCK);
     #else
     #else
-    fcntl(pfd[pipe_read], F_SETFL, O_NDELAY);
+    (void)fcntl(pfd[pipe_read], F_SETFL, O_NDELAY);
     #endif
 
     /* If the maximum number of subprocesses already exist, wait for a process
     #endif
 
     /* If the maximum number of subprocesses already exist, wait for a process
@@ -3645,8 +3707,8 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
 
   if (poffset >= remote_max_parallel)
     {
 
   if (poffset >= remote_max_parallel)
     {
-    close(pfd[pipe_write]);
-    close(pfd[pipe_read]);
+    (void)close(pfd[pipe_write]);
+    (void)close(pfd[pipe_read]);
     remote_post_process(addr, LOG_MAIN|LOG_PANIC,
       US"Unexpectedly no free subprocess slot", fallback);
     continue;
     remote_post_process(addr, LOG_MAIN|LOG_PANIC,
       US"Unexpectedly no free subprocess slot", fallback);
     continue;
@@ -3686,13 +3748,13 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
     a new process that may be forked to do another delivery down the same
     SMTP connection. */
 
     a new process that may be forked to do another delivery down the same
     SMTP connection. */
 
-    fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
+    (void)fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
 
     /* Close open file descriptors for the pipes of other processes
     that are running in parallel. */
 
     for (poffset = 0; poffset < remote_max_parallel; poffset++)
 
     /* Close open file descriptors for the pipes of other processes
     that are running in parallel. */
 
     for (poffset = 0; poffset < remote_max_parallel; poffset++)
-      if (parlist[poffset].pid != 0) close(parlist[poffset].fd);
+      if (parlist[poffset].pid != 0) (void)close(parlist[poffset].fd);
 
     /* This process has inherited a copy of the file descriptor
     for the data file, but its file pointer is shared with all the
 
     /* This process has inherited a copy of the file descriptor
     for the data file, but its file pointer is shared with all the
@@ -3702,7 +3764,7 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
     the parent process. There doesn't seem to be any way of doing
     a dup-with-new-file-pointer. */
 
     the parent process. There doesn't seem to be any way of doing
     a dup-with-new-file-pointer. */
 
-    close(deliver_datafile);
+    (void)close(deliver_datafile);
     sprintf(CS spoolname, "%s/input/%s/%s-D", spool_directory, message_subdir,
       message_id);
     deliver_datafile = Uopen(spoolname, O_RDWR | O_APPEND, 0);
     sprintf(CS spoolname, "%s/input/%s/%s-D", spool_directory, message_subdir,
       message_id);
     deliver_datafile = Uopen(spoolname, O_RDWR | O_APPEND, 0);
@@ -3713,7 +3775,7 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
 
     /* Set the close-on-exec flag */
 
 
     /* Set the close-on-exec flag */
 
-    fcntl(deliver_datafile, F_SETFD, fcntl(deliver_datafile, F_GETFD) |
+    (void)fcntl(deliver_datafile, F_SETFD, fcntl(deliver_datafile, F_GETFD) |
       FD_CLOEXEC);
 
     /* Set the uid/gid of this process; bombs out on failure. */
       FD_CLOEXEC);
 
     /* Set the uid/gid of this process; bombs out on failure. */
@@ -3726,7 +3788,7 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
     and run the transport. Afterwards, transport_count will contain the number
     of bytes written. */
 
     and run the transport. Afterwards, transport_count will contain the number
     of bytes written. */
 
-    close(pfd[pipe_read]);
+    (void)close(pfd[pipe_read]);
     set_process_info("delivering %s using %s", message_id, tp->name);
     debug_print_string(tp->debug_string);
     if (!(tp->info->code)(addr->transport, addr)) replicate_status(addr);
     set_process_info("delivering %s using %s", message_id, tp->name);
     debug_print_string(tp->debug_string);
     if (!(tp->info->code)(addr->transport, addr)) replicate_status(addr);
@@ -3755,7 +3817,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);
       {
       if (h->address == NULL || h->status < hstatus_unusable) continue;
       sprintf(CS big_buffer, "H%c%c%s", h->status, h->why, h->address);
-      write(fd, big_buffer, Ustrlen(big_buffer+3) + 4);
+      (void)write(fd, big_buffer, Ustrlen(big_buffer+3) + 4);
       }
 
     /* The number of bytes written. This is the same for each address. Even
       }
 
     /* The number of bytes written. This is the same for each address. Even
@@ -3765,7 +3827,7 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
 
     big_buffer[0] = 'S';
     memcpy(big_buffer+1, &transport_count, sizeof(transport_count));
 
     big_buffer[0] = 'S';
     memcpy(big_buffer+1, &transport_count, sizeof(transport_count));
-    write(fd, big_buffer, sizeof(transport_count) + 1);
+    (void)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'
 
     /* Information about what happened to each address. Three item types are
     used: an optional 'X' item first, for TLS information, followed by 'R'
@@ -3795,7 +3857,7 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
           sprintf(CS ptr, "%.512s", addr->peerdn);
           while(*ptr++);
           }
           sprintf(CS ptr, "%.512s", addr->peerdn);
           while(*ptr++);
           }
-        write(fd, big_buffer, ptr - big_buffer);
+        (void)write(fd, big_buffer, ptr - big_buffer);
         }
       #endif
 
         }
       #endif
 
@@ -3815,7 +3877,7 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
           sprintf(CS ptr, "%.512s", r->message);
           while(*ptr++);
           }
           sprintf(CS ptr, "%.512s", r->message);
           while(*ptr++);
           }
-        write(fd, big_buffer, ptr - big_buffer);
+        (void)write(fd, big_buffer, ptr - big_buffer);
         }
 
       /* The rest of the information goes in an 'A' item. */
         }
 
       /* The rest of the information goes in an 'A' item. */
@@ -3851,7 +3913,7 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
         memcpy(ptr, &(addr->host_used->port), sizeof(addr->host_used->port));
         ptr += sizeof(addr->host_used->port);
         }
         memcpy(ptr, &(addr->host_used->port), sizeof(addr->host_used->port));
         ptr += sizeof(addr->host_used->port);
         }
-      write(fd, big_buffer, ptr - big_buffer);
+      (void)write(fd, big_buffer, ptr - big_buffer);
       }
 
     /* Add termination flag, close the pipe, and that's it. The character
       }
 
     /* Add termination flag, close the pipe, and that's it. The character
@@ -3861,20 +3923,20 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
 
     big_buffer[0] = 'Z';
     big_buffer[1] = (continue_transport == NULL)? '0' : '1';
 
     big_buffer[0] = 'Z';
     big_buffer[1] = (continue_transport == NULL)? '0' : '1';
-    write(fd, big_buffer, 2);
-    close(fd);
+    (void)write(fd, big_buffer, 2);
+    (void)close(fd);
     exit(EXIT_SUCCESS);
     }
 
   /* Back in the mainline: close the unwanted half of the pipe. */
 
     exit(EXIT_SUCCESS);
     }
 
   /* Back in the mainline: close the unwanted half of the pipe. */
 
-  close(pfd[pipe_write]);
+  (void)close(pfd[pipe_write]);
 
   /* Fork failed; defer with error message */
 
   if (pid < 0)
     {
 
   /* Fork failed; defer with error message */
 
   if (pid < 0)
     {
-    close(pfd[pipe_read]);
+    (void)close(pfd[pipe_read]);
     remote_post_process(addr, LOG_MAIN|LOG_PANIC,
       string_sprintf("fork failed for remote delivery to %s: %s",
         addr->domain, strerror(errno)), fallback);
     remote_post_process(addr, LOG_MAIN|LOG_PANIC,
       string_sprintf("fork failed for remote delivery to %s: %s",
         addr->domain, strerror(errno)), fallback);
@@ -4135,7 +4197,6 @@ if (addr->parent != NULL && testflag(addr, af_hide_child))
   printed = US"an undisclosed address";
   yield = FALSE;
   }
   printed = US"an undisclosed address";
   yield = FALSE;
   }
-
 else if (!testflag(addr, af_pfr) || addr->parent == NULL)
   printed = addr->address;
 
 else if (!testflag(addr, af_pfr) || addr->parent == NULL)
   printed = addr->address;
 
@@ -4172,7 +4233,6 @@ return yield;
 
 
 
 
 
 
-
 /*************************************************
 *         Print error for an address             *
 *************************************************/
 /*************************************************
 *         Print error for an address             *
 *************************************************/
@@ -4182,47 +4242,108 @@ a bounce or a warning message. It tries to format the message reasonably by
 introducing newlines. All lines are indented by 4; the initial printing
 position must be set before calling.
 
 introducing newlines. All lines are indented by 4; the initial printing
 position must be set before calling.
 
+This function used always to print the error. Nowadays we want to restrict it
+to cases such as SMTP errors from a remote host, and errors from :fail: and
+filter "fail". We no longer pass other information willy-nilly in bounce and
+warning messages. Text in user_message is always output; text in message only
+if the af_pass_message flag is set.
+
 Arguments:
 Arguments:
-  addr         points to the address
+  addr         the address
   f            the FILE to print on
   f            the FILE to print on
+  s            some leading text
 
 Returns:       nothing
 */
 
 static void
 
 Returns:       nothing
 */
 
 static void
-print_address_error(address_item *addr, FILE *f)
+print_address_error(address_item *addr, FILE *f, uschar *t)
 {
 {
+int count = Ustrlen(t);
 uschar *s = (addr->user_message != NULL)? addr->user_message : addr->message;
 uschar *s = (addr->user_message != NULL)? addr->user_message : addr->message;
-if (addr->basic_errno > 0)
-  {
-  fprintf(f, "%s%s", strerror(addr->basic_errno),
-    (s == NULL)? "" : ":\n    ");
-  }
-if (s == NULL)
+
+if (addr->user_message != NULL)
+  s = addr->user_message;
+else
   {
   {
-  if (addr->basic_errno <= 0) fprintf(f, "unknown error");
+  if (!testflag(addr, af_pass_message) || addr->message == NULL) return;
+  s = addr->message;
   }
   }
-else
+
+fprintf(f, "\n    %s", t);
+
+while (*s != 0)
   {
   {
-  int count = 0;
-  while (*s != 0)
+  if (*s == '\\' && s[1] == 'n')
+    {
+    fprintf(f, "\n    ");
+    s += 2;
+    count = 0;
+    }
+  else
     {
     {
-    if (*s == '\\' && s[1] == 'n')
+    fputc(*s, f);
+    count++;
+    if (*s++ == ':' && isspace(*s) && count > 45)
       {
       {
-      fprintf(f, "\n    ");
-      s += 2;
+      fprintf(f, "\n   ");  /* sic (because space follows) */
       count = 0;
       }
       count = 0;
       }
-    else
-      {
-      fputc(*s, f);
-      count++;
-      if (*s++ == ':' && isspace(*s) && count > 45)
-        {
-        fprintf(f, "\n   ");  /* sic (because space follows) */
-        count = 0;
-        }
-      }
+    }
+  }
+}
+
+
+
+
+
+
+/*************************************************
+*     Check list of addresses for duplication    *
+*************************************************/
+
+/* This function was introduced when the test for duplicate addresses that are
+not pipes, files, or autoreplies was moved from the middle of routing to when
+routing was complete. That was to fix obscure cases when the routing history
+affects the subsequent routing of identical addresses. If that change has to be
+reversed, this function is no longer needed. For a while, the old code that was
+affected by this change is commented with !!!OLD-DE-DUP!!! so it can be found
+easily.
+
+This function is called after routing, to check that the final routed addresses
+are not duplicates. If we detect a duplicate, we remember what it is a
+duplicate of. Note that pipe, file, and autoreply de-duplication is handled
+during routing, so we must leave such "addresses" alone here, as otherwise they
+will incorrectly be discarded.
+
+Argument:     address of list anchor
+Returns:      nothing
+*/
+
+static void
+do_duplicate_check(address_item **anchor)
+{
+address_item *addr;
+while ((addr = *anchor) != NULL)
+  {
+  tree_node *tnode;
+  if (testflag(addr, af_pfr))
+    {
+    anchor = &(addr->next);
+    }
+  else if ((tnode = tree_search(tree_duplicates, addr->unique)) != NULL)
+    {
+    DEBUG(D_deliver|D_route)
+      debug_printf("%s is a duplicate address: discarded\n", addr->unique);
+    *anchor = addr->next;
+    addr->dupof = tnode->data.ptr;
+    addr->next = addr_duplicate;
+    addr_duplicate = addr;
+    }
+  else
+    {
+    tree_add_duplicate(addr->unique, addr);
+    anchor = &(addr->next);
     }
   }
 }
     }
   }
 }
@@ -4361,11 +4482,8 @@ if ((rc = spool_read_header(spoolname, TRUE, TRUE)) != spool_read_OK)
     sprintf(CS big_buffer, "%s/input/%s/%s", spool_directory, message_subdir,
       spoolname);
     if (Ustat(big_buffer, &statbuf) == 0)
     sprintf(CS big_buffer, "%s/input/%s/%s", spool_directory, message_subdir,
       spoolname);
     if (Ustat(big_buffer, &statbuf) == 0)
-      {
-      int size = statbuf.st_size;   /* Because might be a long */
-      log_write(0, LOG_MAIN, "Format error in spool file %s: size=%d",
-        spoolname, size);
-      }
+      log_write(0, LOG_MAIN, "Format error in spool file %s: "
+        "size=" OFF_T_FMT, spoolname, statbuf.st_size);
     else log_write(0, LOG_MAIN, "Format error in spool file %s", spoolname);
     }
   else
     else log_write(0, LOG_MAIN, "Format error in spool file %s", spoolname);
     }
   else
@@ -4399,7 +4517,7 @@ if ((rc = spool_read_header(spoolname, TRUE, TRUE)) != spool_read_OK)
       readconf_printtime(keep_malformed));
     }
 
       readconf_printtime(keep_malformed));
     }
 
-  close(deliver_datafile);
+  (void)close(deliver_datafile);
   deliver_datafile = -1;
   return continue_closedown();   /* yields DELIVER_NOT_ATTEMPTED */
   }
   deliver_datafile = -1;
   return continue_closedown();   /* yields DELIVER_NOT_ATTEMPTED */
   }
@@ -4425,7 +4543,7 @@ if (jread != NULL)
     DEBUG(D_deliver) debug_printf("Previously delivered address %s taken from "
       "journal file\n", big_buffer);
     }
     DEBUG(D_deliver) debug_printf("Previously delivered address %s taken from "
       "journal file\n", big_buffer);
     }
-  fclose(jread);
+  (void)fclose(jread);
   /* Panic-dies on error */
   (void)spool_write_header(message_id, SW_DELIVERING, NULL);
   }
   /* Panic-dies on error */
   (void)spool_write_header(message_id, SW_DELIVERING, NULL);
   }
@@ -4440,7 +4558,7 @@ else if (errno != ENOENT)
 
 if (recipients_list == NULL)
   {
 
 if (recipients_list == NULL)
   {
-  close(deliver_datafile);
+  (void)close(deliver_datafile);
   deliver_datafile = -1;
   log_write(0, LOG_MAIN, "Spool error: no recipients for %s", spoolname);
   return continue_closedown();   /* yields DELIVER_NOT_ATTEMPTED */
   deliver_datafile = -1;
   log_write(0, LOG_MAIN, "Spool error: no recipients for %s", spoolname);
   return continue_closedown();   /* yields DELIVER_NOT_ATTEMPTED */
@@ -4483,19 +4601,24 @@ if (deliver_freeze)
     log_write(0, LOG_MAIN, "Unfrozen by errmsg timer");
     }
 
     log_write(0, LOG_MAIN, "Unfrozen by errmsg timer");
     }
 
-  /* If there's no auto thaw, or we haven't reached the auto thaw time yet, and
-  this delivery is not forced by an admin user, do not attempt delivery of this
-  message. Note that forced is set for continuing messages down the same
-  channel, in order to skip load checking and ignore hold domains, but we
-  don't want unfreezing in that case. */
+  /* If this is a bounce message, or there's no auto thaw, or we haven't
+  reached the auto thaw time yet, and this delivery is not forced by an admin
+  user, do not attempt delivery of this message. Note that forced is set for
+  continuing messages down the same channel, in order to skip load checking and
+  ignore hold domains, but we don't want unfreezing in that case. */
 
   else
     {
 
   else
     {
-    if ((auto_thaw <= 0 || now <= deliver_frozen_at + auto_thaw) &&
-      (!forced || !deliver_force_thaw || !admin_user ||
-        continue_hostname != NULL))
+    if ((sender_address[0] == 0 ||
+         auto_thaw <= 0 ||
+         now <= deliver_frozen_at + auto_thaw
+        )
+        &&
+        (!forced || !deliver_force_thaw || !admin_user ||
+          continue_hostname != NULL
+        ))
       {
       {
-      close(deliver_datafile);
+      (void)close(deliver_datafile);
       deliver_datafile = -1;
       log_write(L_skip_delivery, LOG_MAIN, "Message is frozen");
       return continue_closedown();   /* yields DELIVER_NOT_ATTEMPTED */
       deliver_datafile = -1;
       log_write(L_skip_delivery, LOG_MAIN, "Message is frozen");
       return continue_closedown();   /* yields DELIVER_NOT_ATTEMPTED */
@@ -4617,6 +4740,8 @@ 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!) */
       RDO_REWRITE,
     NULL,                   /* No :include: restriction (not used in filter) */
     NULL,                   /* No sieve vacation directory (not sieve!) */
+    NULL,                   /* No sieve user address (not sieve!) */
+    NULL,                   /* No sieve subaddress (not sieve!) */
     &ugid,                  /* uid/gid data */
     &addr_new,              /* Where to hang generated addresses */
     &filter_message,        /* Where to put error message */
     &ugid,                  /* uid/gid data */
     &addr_new,              /* Where to hang generated addresses */
     &filter_message,        /* Where to put error message */
@@ -4628,7 +4753,7 @@ else if (system_filter != NULL && process_recipients != RECIP_FAIL_TIMEOUT)
 
   if (rc == FF_ERROR || rc == FF_NONEXIST)
     {
 
   if (rc == FF_ERROR || rc == FF_NONEXIST)
     {
-    close(deliver_datafile);
+    (void)close(deliver_datafile);
     deliver_datafile = -1;
     log_write(0, LOG_MAIN|LOG_PANIC, "Error in system filter: %s",
       string_printing(filter_message));
     deliver_datafile = -1;
     log_write(0, LOG_MAIN|LOG_PANIC, "Error in system filter: %s",
       string_printing(filter_message));
@@ -4895,6 +5020,7 @@ if (process_recipients != RECIP_IGNORE)
         case RECIP_FAIL_FILTER:
         new->message =
           (filter_message == NULL)? US"delivery cancelled" : filter_message;
         case RECIP_FAIL_FILTER:
         new->message =
           (filter_message == NULL)? US"delivery cancelled" : filter_message;
+        setflag(new, af_pass_message);
         goto RECIP_QUEUE_FAILED;   /* below */
 
 
         goto RECIP_QUEUE_FAILED;   /* below */
 
 
@@ -5243,6 +5369,18 @@ while (addr_new != NULL)           /* Loop until all addresses dealt with */
       continue;
       }
 
       continue;
       }
 
+
+    /* !!!OLD-DE-DUP!!!  We used to test for duplicates at this point, in order
+    to save effort on routing duplicate addresses. However, facilities have
+    been added to Exim so that now two identical addresses that are children of
+    other addresses may be routed differently as a result of their previous
+    routing history. For example, different redirect routers may have given
+    them different redirect_router values, but there are other cases too.
+    Therefore, tests for duplicates now take place when routing is complete.
+    This is the old code, kept for a while for the record, and in case this
+    radical change has to be backed out for some reason. */
+
+    #ifdef NEVER
     /* If it's a duplicate, remember what it's a duplicate of */
 
     if ((tnode = tree_search(tree_duplicates, addr->unique)) != NULL)
     /* If it's a duplicate, remember what it's a duplicate of */
 
     if ((tnode = tree_search(tree_duplicates, addr->unique)) != NULL)
@@ -5258,6 +5396,9 @@ while (addr_new != NULL)           /* Loop until all addresses dealt with */
     /* Record this address, so subsequent duplicates get picked up. */
 
     tree_add_duplicate(addr->unique, addr);
     /* Record this address, so subsequent duplicates get picked up. */
 
     tree_add_duplicate(addr->unique, addr);
+    #endif
+
+
 
     /* Get the routing retry status, saving the two retry keys (with and
     without the local part) for subsequent use. Ignore retry records that
 
     /* Get the routing retry status, saving the two retry keys (with and
     without the local part) for subsequent use. Ignore retry records that
@@ -5570,6 +5711,21 @@ Ensure they are not set in transports. */
 local_user_gid = (gid_t)(-1);
 local_user_uid = (uid_t)(-1);
 
 local_user_gid = (gid_t)(-1);
 local_user_uid = (uid_t)(-1);
 
+
+/* !!!OLD-DE-DUP!!! The next two statement were introduced when checking for
+duplicates was moved from within routing to afterwards. If that change has to
+be backed out, they should be removed. */
+
+/* Check for any duplicate addresses. This check is delayed until after
+routing, because the flexibility of the routing configuration means that
+identical addresses with different parentage may end up being redirected to
+different addresses. Checking for duplicates too early (as we previously used
+to) makes this kind of thing not work. */
+
+do_duplicate_check(&addr_local);
+do_duplicate_check(&addr_remote);
+
+
 /* When acting as an MUA wrapper, we proceed only if all addresses route to a
 remote transport. The check that they all end up in one transaction happens in
 the do_remote_deliveries() function. */
 /* When acting as an MUA wrapper, we proceed only if all addresses route to a
 remote transport. The check that they all end up in one transaction happens in
 the do_remote_deliveries() function. */
@@ -5689,9 +5845,9 @@ if (addr_local != NULL || addr_remote != NULL)
   that the mode is correct - the group setting doesn't always seem to get
   set automatically. */
 
   that the mode is correct - the group setting doesn't always seem to get
   set automatically. */
 
-  fcntl(journal_fd, F_SETFD, fcntl(journal_fd, F_GETFD) | FD_CLOEXEC);
-  fchown(journal_fd, exim_uid, exim_gid);
-  fchmod(journal_fd, SPOOL_MODE);
+  (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);
   }
 
 
   }
 
 
@@ -5700,6 +5856,15 @@ deliveries are done first, then remote ones. If ever the problems of how to
 handle fallback transports are figured out, this section can be put into a loop
 for handling fallbacks, though the uid switching will have to be revised. */
 
 handle fallback transports are figured out, this section can be put into a loop
 for handling fallbacks, though the uid switching will have to be revised. */
 
+/* Precompile a regex that is used to recognize a parameter in response
+to an LHLO command, if is isn't already compiled. This may be used on both
+local and remote LMTP deliveries. */
+
+if (regex_IGNOREQUOTA == NULL) regex_IGNOREQUOTA =
+  regex_must_compile(US"\\n250[\\s\\-]IGNOREQUOTA(\\s|\\n|$)", FALSE, TRUE);
+
+/* Handle local deliveries */
+
 if (addr_local != NULL)
   {
   DEBUG(D_deliver|D_transport)
 if (addr_local != NULL)
   {
   DEBUG(D_deliver|D_transport)
@@ -5796,11 +5961,29 @@ set_process_info("tidying up after delivering %s", message_id);
 signal(SIGTERM, SIG_IGN);
 
 /* When we are acting as an MUA wrapper, the smtp transport will either have
 signal(SIGTERM, SIG_IGN);
 
 /* When we are acting as an MUA wrapper, the smtp transport will either have
-succeeded for all addresses, or failed them all. We do not ever want to retry,
-nor do we want to send a bounce message. */
+succeeded for all addresses, or failed them all in normal cases. However, there
+are some setup situations (e.g. when a named port does not exist) that cause an
+immediate exit with deferral of all addresses. Convert those into failures. We
+do not ever want to retry, nor do we want to send a bounce message. */
 
 if (mua_wrapper)
   {
 
 if (mua_wrapper)
   {
+  if (addr_defer != NULL)
+    {
+    address_item *addr, *nextaddr;
+    for (addr = addr_defer; addr != NULL; addr = nextaddr)
+      {
+      log_write(0, LOG_MAIN, "** %s mua_wrapper forced failure for deferred "
+        "delivery", addr->address);
+      nextaddr = addr->next;
+      addr->next = addr_failed;
+      addr_failed = addr;
+      }
+    addr_defer = NULL;
+    }
+
+  /* Now all should either have succeeded or failed. */
+
   if (addr_failed == NULL) final_yield = DELIVER_MUA_SUCCEEDED; else
     {
     uschar *s = (addr_failed->user_message != NULL)?
   if (addr_failed == NULL) final_yield = DELIVER_MUA_SUCCEEDED; else
     {
     uschar *s = (addr_failed->user_message != NULL)?
@@ -6053,26 +6236,15 @@ wording. */
 
       /* Process the addresses, leaving them on the msgchain if they have a
       file name for a return message. (There has already been a check in
 
       /* Process the addresses, leaving them on the msgchain if they have a
       file name for a return message. (There has already been a check in
-      post_process_one() for the existence of data in the message file.) */
+      post_process_one() for the existence of data in the message file.) A TRUE
+      return from print_address_information() means that the address is not
+      hidden. */
 
       paddr = &msgchain;
       for (addr = msgchain; addr != NULL; addr = *paddr)
         {
         if (print_address_information(addr, f, US"  ", US"\n    ", US""))
 
       paddr = &msgchain;
       for (addr = msgchain; addr != NULL; addr = *paddr)
         {
         if (print_address_information(addr, f, US"  ", US"\n    ", US""))
-          {
-          /* A TRUE return from print_address_information() means that the
-          address is not hidden. If there is a return file, it has already
-          been checked to ensure it is not empty. Omit the bland "return
-          message generated" error, but otherwise include error information. */
-
-          if (addr->return_file < 0 ||
-              addr->message == NULL ||
-              Ustrcmp(addr->message, "return message generated") != 0)
-            {
-            fprintf(f, "\n    ");
-            print_address_error(addr, f);
-            }
-          }
+          print_address_error(addr, f, US"");
 
         /* End the final line for the address */
 
 
         /* End the final line for the address */
 
@@ -6147,7 +6319,7 @@ wording. */
           else
             {
             while ((ch = fgetc(fm)) != EOF) fputc(ch, f);
           else
             {
             while ((ch = fgetc(fm)) != EOF) fputc(ch, f);
-            fclose(fm);
+            (void)fclose(fm);
             }
           Uunlink(addr->return_filename);
 
             }
           Uunlink(addr->return_filename);
 
@@ -6200,8 +6372,8 @@ wording. */
             if (emf_text != NULL) fprintf(f, "%s", CS emf_text); else
               {
               fprintf(f,
             if (emf_text != NULL) fprintf(f, "%s", CS emf_text); else
               {
               fprintf(f,
-"------ The body of the message is %d characters long; only the first\n"
-"------ %d or so are included here.\n", (int)statbuf.st_size, max);
+"------ The body of the message is " OFF_T_FMT " characters long; only the first\n"
+"------ %d or so are included here.\n", statbuf.st_size, max);
               }
             }
           }
               }
             }
           }
@@ -6220,13 +6392,13 @@ wording. */
         {
         emf_text = next_emf(emf, US"final");
         if (emf_text != NULL) fprintf(f, "%s", CS emf_text);
         {
         emf_text = next_emf(emf, US"final");
         if (emf_text != NULL) fprintf(f, "%s", CS emf_text);
-        fclose(emf);
+        (void)fclose(emf);
         }
 
       /* Close the file, which should send an EOF to the child process
       that is receiving the message. Wait for it to finish. */
 
         }
 
       /* Close the file, which should send an EOF to the child process
       that is receiving the message. Wait for it to finish. */
 
-      fclose(f);
+      (void)fclose(f);
       rc = child_close(pid, 0);     /* Waits for child to close, no timeout */
 
       /* In the test harness, let the child do it's thing first. */
       rc = child_close(pid, 0);     /* Waits for child to close, no timeout */
 
       /* In the test harness, let the child do it's thing first. */
@@ -6322,7 +6494,14 @@ if (addr_defer == NULL)
   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);
   sprintf(CS spoolname, "%s/input/%s/%s-H", spool_directory, message_subdir, id);
   if (Uunlink(spoolname) < 0)
     log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to unlink %s", spoolname);
-  log_write(0, LOG_MAIN, "Completed");
+
+  /* Log the end of this message, with queue time if requested. */
+
+  if ((log_extra_selector & LX_queue_time_overall) != 0)
+    log_write(0, LOG_MAIN, "Completed QT=%s",
+      readconf_printtime(time(NULL) - received_time));
+  else
+    log_write(0, LOG_MAIN, "Completed");
   }
 
 /* If there are deferred addresses, we are keeping this message because it is
   }
 
 /* If there are deferred addresses, we are keeping this message because it is
@@ -6567,21 +6746,15 @@ else if (addr_defer != (address_item *)(+1))
             (addr_defer->next == NULL)? "is": "are");
           }
 
             (addr_defer->next == NULL)? "is": "are");
           }
 
-        /* List the addresses. For any that are hidden, don't give the delay
-        reason, because it might expose that which is hidden. Also, do not give
-        "retry time not reached" because that isn't helpful. */
+        /* List the addresses, with error information if allowed */
 
         fprintf(f, "\n");
         while (addr_defer != NULL)
           {
           address_item *addr = addr_defer;
           addr_defer = addr->next;
 
         fprintf(f, "\n");
         while (addr_defer != NULL)
           {
           address_item *addr = addr_defer;
           addr_defer = addr->next;
-          if (print_address_information(addr, f, US"  ", US"\n    ", US"") &&
-              addr->basic_errno > ERRNO_RETRY_BASE)
-            {
-            fprintf(f, "\n    Delay reason: ");
-            print_address_error(addr, f);
-            }
+          if (print_address_information(addr, f, US"  ", US"\n    ", US""))
+            print_address_error(addr, f, US"Delay reason: ");
           fprintf(f, "\n");
           }
         fprintf(f, "\n");
           fprintf(f, "\n");
           }
         fprintf(f, "\n");
@@ -6592,7 +6765,7 @@ else if (addr_defer != (address_item *)(+1))
           {
           wmf_text = next_emf(wmf, US"final");
           if (wmf_text != NULL) fprintf(f, "%s", CS wmf_text);
           {
           wmf_text = next_emf(wmf, US"final");
           if (wmf_text != NULL) fprintf(f, "%s", CS wmf_text);
-          fclose(wmf);
+          (void)fclose(wmf);
           }
         else
           {
           }
         else
           {
@@ -6606,7 +6779,7 @@ else if (addr_defer != (address_item *)(+1))
         /* Close and wait for child process to complete, without a timeout.
         If there's an error, don't update the count. */
 
         /* Close and wait for child process to complete, without a timeout.
         If there's an error, don't update the count. */
 
-        fclose(f);
+        (void)fclose(f);
         if (child_close(pid, 0) == 0)
           {
           warning_count = count;
         if (child_close(pid, 0) == 0)
           {
           warning_count = count;
@@ -6689,7 +6862,7 @@ else if (addr_defer != (address_item *)(+1))
 /* Finished with the message log. If the message is complete, it will have
 been unlinked or renamed above. */
 
 /* Finished with the message log. If the message is complete, it will have
 been unlinked or renamed above. */
 
-if (message_logs) fclose(message_log);
+if (message_logs) (void)fclose(message_log);
 
 /* Now we can close and remove the journal file. Its only purpose is to record
 successfully completed deliveries asap so that this information doesn't get
 
 /* Now we can close and remove the journal file. Its only purpose is to record
 successfully completed deliveries asap so that this information doesn't get
@@ -6703,7 +6876,7 @@ the remove_journal flag. When the journal is left, we also don't move the
 message off the main spool if frozen and the option is set. It should get moved
 at the next attempt, after the journal has been inspected. */
 
 message off the main spool if frozen and the option is set. It should get moved
 at the next attempt, after the journal has been inspected. */
 
-if (journal_fd >= 0) close(journal_fd);
+if (journal_fd >= 0) (void)close(journal_fd);
 
 if (remove_journal)
   {
 
 if (remove_journal)
   {
@@ -6724,7 +6897,7 @@ if (remove_journal)
 will go away. Otherwise the message becomes available for another process
 to try delivery. */
 
 will go away. Otherwise the message becomes available for another process
 to try delivery. */
 
-close(deliver_datafile);
+(void)close(deliver_datafile);
 deliver_datafile = -1;
 DEBUG(D_deliver) debug_printf("end delivery of %s\n", id);
 
 deliver_datafile = -1;
 DEBUG(D_deliver) debug_printf("end delivery of %s\n", id);