SPDX: license tags (mostly by guesswork)
[exim.git] / src / src / daemon.c
index 89a7a018373ea0a577546feef02233ad139398df..0afc7ca86611cd8e98b97965ef8d3aa54c650715 100644 (file)
@@ -2,8 +2,10 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) The Exim Maintainers 2020 - 2022 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
+/* SPDX-License-Identifier: GPL-2.0-only */
 
 /* Functions concerned with running Exim as a daemon */
 
@@ -19,9 +21,9 @@ typedef struct smtp_slot {
 } smtp_slot;
 
 /* An empty slot for initializing (Standard C does not allow constructor
-expressions in assigments except as initializers in declarations). */
+expressions in assignments except as initializers in declarations). */
 
-static smtp_slot empty_smtp_slot = { 0, NULL };
+static smtp_slot empty_smtp_slot = { .pid = 0, .host_address = NULL };
 
 
 
@@ -31,6 +33,7 @@ static smtp_slot empty_smtp_slot = { 0, NULL };
 
 static SIGNAL_BOOL sigchld_seen;
 static SIGNAL_BOOL sighup_seen;
+static SIGNAL_BOOL sigterm_seen;
 
 static int   accept_retry_count = 0;
 static int   accept_retry_errno;
@@ -57,7 +60,6 @@ Returns:  nothing
 static void
 sighup_handler(int sig)
 {
-sig = sig;    /* Keep picky compilers happy */
 sighup_seen = TRUE;
 signal(SIGHUP, sighup_handler);
 }
@@ -81,12 +83,21 @@ Returns:  nothing
 static void
 main_sigchld_handler(int sig)
 {
-sig = sig;    /* Keep picky compilers happy */
 os_non_restarting_signal(SIGCHLD, SIG_DFL);
 sigchld_seen = TRUE;
 }
 
 
+/* SIGTERM handler.  Try to get the daemon pid file removed
+before exiting. */
+
+static void
+main_sigterm_handler(int sig)
+{
+sigterm_seen = TRUE;
+}
+
+
 
 
 /*************************************************
@@ -106,13 +117,44 @@ Returns:         nothing
 static void
 never_error(uschar *log_msg, uschar *smtp_msg, int was_errno)
 {
-uschar *emsg = (was_errno <= 0)? US"" :
-  string_sprintf(": %s", strerror(was_errno));
+uschar *emsg = was_errno <= 0
+  ? US"" : string_sprintf(": %s", strerror(was_errno));
 log_write(0, LOG_MAIN|LOG_PANIC, "%s%s", log_msg, emsg);
-if (smtp_out != NULL) smtp_printf("421 %s\r\n", smtp_msg);
+if (smtp_out) smtp_printf("421 %s\r\n", FALSE, smtp_msg);
+}
+
+
+
+
+/*************************************************
+*************************************************/
+
+#ifndef EXIM_HAVE_ABSTRACT_UNIX_SOCKETS
+static void
+unlink_notifier_socket(void)
+{
+uschar * s = expand_string(notifier_socket);
+DEBUG(D_any) debug_printf("unlinking notifier socket %s\n", s);
+Uunlink(s);
 }
+#endif
+
 
+static void
+close_daemon_sockets(int daemon_notifier_fd,
+  struct pollfd * fd_polls, int listen_socket_count)
+{
+if (daemon_notifier_fd >= 0)
+  {
+  (void) close(daemon_notifier_fd);
+  daemon_notifier_fd = -1;
+#ifndef EXIM_HAVE_ABSTRACT_UNIX_SOCKETS
+  unlink_notifier_socket();
+#endif
+  }
 
+for (int i = 0; i < listen_socket_count; i++) (void) close(fd_polls[i].fd);
+}
 
 
 /*************************************************
@@ -126,7 +168,7 @@ is required so that they can be closed in the sub-process. Take care not to
 leak store in this process - reset the stacking pool at the end.
 
 Arguments:
-  listen_sockets        sockets which are listening for incoming calls
+  fd_polls        sockets which are listening for incoming calls
   listen_socket_count   count of listening sockets
   accept_socket         socket of the current accepted call
   accepted              socket information about the current call
@@ -135,7 +177,7 @@ Returns:            nothing
 */
 
 static void
-handle_smtp_call(int *listen_sockets, int listen_socket_count,
+handle_smtp_call(struct pollfd *fd_polls, int listen_socket_count,
   int accept_socket, struct sockaddr *accepted)
 {
 pid_t pid;
@@ -143,12 +185,10 @@ union sockaddr_46 interface_sockaddr;
 EXIM_SOCKLEN_T ifsize = sizeof(interface_sockaddr);
 int dup_accept_socket = -1;
 int max_for_this_host = 0;
-int wfsize = 0;
-int wfptr = 0;
 int save_log_selector = *log_selector;
-uschar *whofrom = NULL;
+gstring * whofrom;
 
-void *reset_point = store_get(0);
+rmark reset_point = store_mark();
 
 /* Make the address available in ASCII representation, and also fish out
 the remote port. */
@@ -161,23 +201,20 @@ DEBUG(D_any) debug_printf("Connection request from %s port %d\n",
 input stream. These operations fail only the exceptional circumstances. Note
 that never_error() won't use smtp_out if it is NULL. */
 
-smtp_out = fdopen(accept_socket, "wb");
-if (smtp_out == NULL)
+if (!(smtp_out = fdopen(accept_socket, "wb")))
   {
   never_error(US"daemon: fdopen() for smtp_out failed", US"", errno);
   goto ERROR_RETURN;
   }
 
-dup_accept_socket = dup(accept_socket);
-if (dup_accept_socket < 0)
+if ((dup_accept_socket = dup(accept_socket)) < 0)
   {
   never_error(US"daemon: couldn't dup socket descriptor",
     US"Connection setup failed", errno);
   goto ERROR_RETURN;
   }
 
-smtp_in = fdopen(dup_accept_socket, "rb");
-if (smtp_in == NULL)
+if (!(smtp_in = fdopen(dup_accept_socket, "rb")))
   {
   never_error(US"daemon: fdopen() for smtp_in failed",
     US"Connection setup failed", errno);
@@ -192,7 +229,7 @@ if (getsockname(accept_socket, (struct sockaddr *)(&interface_sockaddr),
   {
   log_write(0, LOG_MAIN | ((errno == ECONNRESET)? 0 : LOG_PANIC),
     "getsockname() failed: %s", strerror(errno));
-  smtp_printf("421 Local problem: getsockname() failed; please try again later\r\n");
+  smtp_printf("421 Local problem: getsockname() failed; please try again later\r\n", FALSE);
   goto ERROR_RETURN;
   }
 
@@ -204,17 +241,16 @@ DEBUG(D_interface) debug_printf("interface address=%s port=%d\n",
 the local interface data. This is for logging; at the end of this function the
 memory is reclaimed. */
 
-whofrom = string_append(whofrom, &wfsize, &wfptr, 3, "[", sender_host_address, "]");
+whofrom = string_append(NULL, 3, "[", sender_host_address, "]");
 
 if (LOGGING(incoming_port))
-  whofrom = string_append(whofrom, &wfsize, &wfptr, 2, ":", string_sprintf("%d",
-    sender_host_port));
+  whofrom = string_fmt_append(whofrom, ":%d", sender_host_port);
 
 if (LOGGING(incoming_interface))
-  whofrom = string_append(whofrom, &wfsize, &wfptr, 4, " I=[",
-    interface_address, "]:", string_sprintf("%d", interface_port));
+  whofrom = string_fmt_append(whofrom, " I=[%s]:%d",
+    interface_address, interface_port);
 
-whofrom[wfptr] = 0;    /* Terminate the newly-built string */
+(void) string_from_gstring(whofrom);    /* Terminate the newly-built string */
 
 /* Check maximum number of connections. We do not check for reserved
 connections or unacceptable hosts here. That is done in the subprocess because
@@ -225,10 +261,10 @@ if (smtp_accept_max > 0 && smtp_accept_count >= smtp_accept_max)
   DEBUG(D_any) debug_printf("rejecting SMTP connection: count=%d max=%d\n",
     smtp_accept_count, smtp_accept_max);
   smtp_printf("421 Too many concurrent SMTP connections; "
-    "please try again later.\r\n");
+    "please try again later.\r\n", FALSE);
   log_write(L_connection_reject,
             LOG_MAIN, "Connection from %s refused: too many connections",
-    whofrom);
+    whofrom->s);
   goto ERROR_RETURN;
   }
 
@@ -240,14 +276,14 @@ subprocess because it might take time. */
 if (smtp_load_reserve >= 0)
   {
   load_average = OS_GETLOADAVG();
-  if (smtp_reserve_hosts == NULL && load_average > smtp_load_reserve)
+  if (!smtp_reserve_hosts && load_average > smtp_load_reserve)
     {
     DEBUG(D_any) debug_printf("rejecting SMTP connection: load average = %.2f\n",
       (double)load_average/1000.0);
-    smtp_printf("421 Too much load; please try again later.\r\n");
+    smtp_printf("421 Too much load; please try again later.\r\n", FALSE);
     log_write(L_connection_reject,
               LOG_MAIN, "Connection from %s refused: load average = %.2f",
-      whofrom, (double)load_average/1000.0);
+      whofrom->s, (double)load_average/1000.0);
     goto ERROR_RETURN;
     }
   }
@@ -260,14 +296,14 @@ to provide host-specific limits according to $sender_host address, but because
 this is in the daemon mainline, only fast expansions (such as inline address
 checks) should be used. The documentation is full of warnings. */
 
-if (smtp_accept_max_per_host != NULL)
+if (smtp_accept_max_per_host)
   {
   uschar *expanded = expand_string(smtp_accept_max_per_host);
-  if (expanded == NULL)
+  if (!expanded)
     {
-    if (!expand_string_forcedfail)
+    if (!f.expand_string_forcedfail)
       log_write(0, LOG_MAIN|LOG_PANIC, "expansion of smtp_accept_max_per_host "
-        "failed for %s: %s", whofrom, expand_string_message);
+        "failed for %s: %s", whofrom->s, expand_string_message);
     }
   /* For speed, interpret a decimal number inline here */
   else
@@ -275,9 +311,9 @@ if (smtp_accept_max_per_host != NULL)
     uschar *s = expanded;
     while (isdigit(*s))
       max_for_this_host = max_for_this_host * 10 + *s++ - '0';
-    if (*s != 0)
+    if (*s)
       log_write(0, LOG_MAIN|LOG_PANIC, "expansion of smtp_accept_max_per_host "
-        "for %s contains non-digit: %s", whofrom, expanded);
+        "for %s contains non-digit: %s", whofrom->s, expanded);
     }
   }
 
@@ -285,16 +321,13 @@ if (smtp_accept_max_per_host != NULL)
 per host_address checks. Note that at this stage smtp_accept_count contains the
 count of *other* connections, not including this one. */
 
-if ((max_for_this_host > 0) &&
-    (smtp_accept_count >= max_for_this_host))
+if (max_for_this_host > 0 && smtp_accept_count >= max_for_this_host)
   {
-  int i;
   int host_accept_count = 0;
   int other_host_count = 0;    /* keep a count of non matches to optimise */
 
-  for (i = 0; i < smtp_accept_max; ++i)
-    {
-    if (smtp_slots[i].host_address != NULL)
+  for (int i = 0; i < smtp_accept_max; ++i)
+    if (smtp_slots[i].host_address)
       {
       if (Ustrcmp(sender_host_address, smtp_slots[i].host_address) == 0)
        host_accept_count++;
@@ -305,11 +338,10 @@ if ((max_for_this_host > 0) &&
       early, either by hitting the target, or finding there are not enough
       connections left to make the target. */
 
-      if ((host_accept_count >= max_for_this_host) ||
-         ((smtp_accept_count - other_host_count) < max_for_this_host))
+      if (  host_accept_count >= max_for_this_host
+         || smtp_accept_count - other_host_count < max_for_this_host)
        break;
       }
-    }
 
   if (host_accept_count >= max_for_this_host)
     {
@@ -317,10 +349,11 @@ if ((max_for_this_host > 0) &&
       "IP address: count=%d max=%d\n",
       host_accept_count, max_for_this_host);
     smtp_printf("421 Too many concurrent SMTP connections "
-      "from this IP address; please try again later.\r\n");
+      "from this IP address; please try again later.\r\n", FALSE);
     log_write(L_connection_reject,
               LOG_MAIN, "Connection from %s refused: too many connections "
-      "from that IP address", whofrom);
+      "from that IP address", whofrom->s);
+    search_tidyup();
     goto ERROR_RETURN;
     }
   }
@@ -342,35 +375,40 @@ if (LOGGING(smtp_connection))
   {
   uschar *list = hosts_connection_nolog;
   memset(sender_host_cache, 0, sizeof(sender_host_cache));
-  if (list != NULL && verify_check_host(&list) == OK)
+  if (list && verify_check_host(&list) == OK)
     save_log_selector &= ~L_smtp_connection;
   else
     log_write(L_smtp_connection, LOG_MAIN, "SMTP connection from %s "
-      "(TCP/IP connection count = %d)", whofrom, smtp_accept_count + 1);
+      "(TCP/IP connection count = %d)", whofrom->s, smtp_accept_count + 1);
   }
 
 /* Now we can fork the accepting process; do a lookup tidy, just in case any
 expansion above did a lookup. */
 
 search_tidyup();
-pid = fork();
+pid = exim_fork(US"daemon-accept");
 
 /* Handle the child process */
 
 if (pid == 0)
   {
-  int i;
   int queue_only_reason = 0;
   int old_pool = store_pool;
   int save_debug_selector = debug_selector;
   BOOL local_queue_only;
   BOOL session_local_queue_only;
-  #ifdef SA_NOCLDWAIT
+#ifdef SA_NOCLDWAIT
   struct sigaction act;
-  #endif
+#endif
 
   smtp_accept_count++;    /* So that it includes this process */
 
+  /* If the listen backlog was over the monitoring level, log it. */
+
+  if (smtp_listen_backlog > smtp_backlog_monitor)
+    log_write(0, LOG_MAIN, "listen backlog %d I=[%s]:%d",
+               smtp_listen_backlog, interface_address, interface_port);
+
   /* May have been modified for the subprocess */
 
   *log_selector = save_log_selector;
@@ -390,24 +428,24 @@ if (pid == 0)
   likely what it depends on.) */
 
   smtp_active_hostname = primary_hostname;
-  if (raw_active_hostname != NULL)
+  if (raw_active_hostname)
     {
-    uschar *nah = expand_string(raw_active_hostname);
-    if (nah == NULL)
+    uschar * nah = expand_string(raw_active_hostname);
+    if (!nah)
       {
-      if (!expand_string_forcedfail)
+      if (!f.expand_string_forcedfail)
         {
         log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand \"%s\" "
           "(smtp_active_hostname): %s", raw_active_hostname,
           expand_string_message);
         smtp_printf("421 Local configuration error; "
-          "please try again later.\r\n");
+          "please try again later.\r\n", FALSE);
         mac_smtp_fflush();
         search_tidyup();
-        _exit(EXIT_FAILURE);
+        exim_underbar_exit(EXIT_FAILURE);
         }
       }
-    else if (nah[0] != 0) smtp_active_hostname = nah;
+    else if (*nah) smtp_active_hostname = nah;
     }
 
   /* Initialize the queueing flags */
@@ -422,7 +460,7 @@ if (pid == 0)
   extensive comment before the reception loop in exim.c for a fuller
   explanation of this logic. */
 
-  for (i = 0; i < listen_socket_count; i++) (void)close(listen_sockets[i]);
+  close_daemon_sockets(daemon_notifier_fd, fd_polls, listen_socket_count);
 
   /* Set FD_CLOEXEC on the SMTP socket. We don't want any rogue child processes
   to be able to communicate with them, under any circumstances. */
@@ -431,14 +469,16 @@ if (pid == 0)
   (void)fcntl(dup_accept_socket, F_SETFD,
               fcntl(dup_accept_socket, F_GETFD) | FD_CLOEXEC);
 
-  #ifdef SA_NOCLDWAIT
+#ifdef SA_NOCLDWAIT
   act.sa_handler = SIG_IGN;
   sigemptyset(&(act.sa_mask));
   act.sa_flags = SA_NOCLDWAIT;
   sigaction(SIGCHLD, &act, NULL);
-  #else
+#else
   signal(SIGCHLD, SIG_IGN);
-  #endif
+#endif
+  signal(SIGTERM, SIG_DFL);
+  signal(SIGINT, SIG_DFL);
 
   /* Attempt to get an id from the sending machine via the RFC 1413
   protocol. We do this in the sub-process in order not to hold up the
@@ -449,7 +489,7 @@ if (pid == 0)
   finding the id, but turn it on again afterwards so that information about the
   incoming connection is output. */
 
-  if (debug_daemon) debug_selector = 0;
+  if (f.debug_daemon) debug_selector = 0;
   verify_get_ident(IDENT_PORT);
   host_build_sender_fullhost();
   debug_selector = save_debug_selector;
@@ -461,7 +501,7 @@ if (pid == 0)
   /* Now disable debugging permanently if it's required only for the daemon
   process. */
 
-  if (debug_daemon) debug_selector = 0;
+  if (f.debug_daemon) debug_selector = 0;
 
   /* If there are too many child processes for immediate delivery,
   set the session_local_queue_only flag, which is initialized from the
@@ -490,14 +530,14 @@ if (pid == 0)
     {
     mac_smtp_fflush();
     search_tidyup();
-    _exit(EXIT_SUCCESS);
+    exim_underbar_exit(EXIT_SUCCESS);
     }
 
   for (;;)
     {
     int rc;
     message_id[0] = 0;            /* Clear out any previous message_id */
-    reset_point = store_get(0);   /* Save current store high water point */
+    reset_point = store_mark();   /* Save current store high water point */
 
     DEBUG(D_any)
       debug_printf("Process %d is ready for new message\n", (int)getpid());
@@ -515,31 +555,45 @@ if (pid == 0)
       search_tidyup();                    /* Close cached databases */
       if (!ok)                            /* Connection was dropped */
         {
+       cancel_cutthrough_connection(TRUE, US"receive dropped");
         mac_smtp_fflush();
         smtp_log_no_mail();               /* Log no mail if configured */
-        _exit(EXIT_SUCCESS);
+        exim_underbar_exit(EXIT_SUCCESS);
         }
       if (message_id[0] == 0) continue;   /* No message was accepted */
       }
-    else
+    else                               /* bad smtp_setup_msg() */
       {
-      mac_smtp_fflush();
+      if (smtp_out)
+       {
+       int fd = fileno(smtp_in);
+       uschar buf[128];
+
+       mac_smtp_fflush();
+       /* drain socket, for clean TCP FINs */
+       if (fcntl(fd, F_SETFL, O_NONBLOCK) == 0)
+         for(int i = 16; read(fd, buf, sizeof(buf)) > 0 && i > 0; ) i--;
+       }
+      cancel_cutthrough_connection(TRUE, US"message setup dropped");
       search_tidyup();
       smtp_log_no_mail();                 /* Log no mail if configured */
-      _exit((rc == 0)? EXIT_SUCCESS : EXIT_FAILURE);
+
+      /*XXX should we pause briefly, hoping that the client will be the
+      active TCP closer hence get the TCP_WAIT endpoint? */
+      DEBUG(D_receive) debug_printf("SMTP>>(close on process exit)\n");
+      exim_underbar_exit(rc ? EXIT_FAILURE : EXIT_SUCCESS);
       }
 
     /* Show the recipients when debugging */
 
     DEBUG(D_receive)
       {
-      int i;
-      if (sender_address != NULL)
+      if (sender_address)
         debug_printf("Sender: %s\n", sender_address);
-      if (recipients_list != NULL)
+      if (recipients_list)
         {
         debug_printf("Recipients:\n");
-        for (i = 0; i < recipients_count; i++)
+        for (int i = 0; i < recipients_count; i++)
           debug_printf("  %s\n", recipients_list[i].address);
         }
       }
@@ -556,7 +610,14 @@ if (pid == 0)
 
     /* Reclaim up the store used in accepting this message */
 
-    store_reset(reset_point);
+      {
+      int r = receive_messagecount;
+      BOOL q = f.queue_only_policy;
+      smtp_reset(reset_point);
+      reset_point = NULL;
+      f.queue_only_policy = q;
+      receive_messagecount = r;
+      }
 
     /* If queue_only is set or if there are too many incoming connections in
     existence, session_local_queue_only will be TRUE. If it is not, check
@@ -582,15 +643,13 @@ if (pid == 0)
     very long-lived connections from scanning appliances where this is not the
     best strategy. In such cases, queue_only_load_latch should be set false. */
 
-    local_queue_only = session_local_queue_only;
-    if (!local_queue_only && queue_only_load >= 0)
+    if (  !(local_queue_only = session_local_queue_only)
+       && queue_only_load >= 0
+       && (local_queue_only = (load_average = OS_GETLOADAVG()) > queue_only_load)
+       )
       {
-      local_queue_only = (load_average = OS_GETLOADAVG()) > queue_only_load;
-      if (local_queue_only)
-        {
-        queue_only_reason = 3;
-        if (queue_only_load_latch) session_local_queue_only = TRUE;
-        }
+      queue_only_reason = 3;
+      if (queue_only_load_latch) session_local_queue_only = TRUE;
       }
 
     /* Log the queueing here, when it will get a message id attached, but
@@ -598,80 +657,82 @@ if (pid == 0)
 
     if (local_queue_only) switch(queue_only_reason)
       {
-      case 1:
-      log_write(L_delay_delivery,
+      case 1: log_write(L_delay_delivery,
                 LOG_MAIN, "no immediate delivery: too many connections "
                 "(%d, max %d)", smtp_accept_count, smtp_accept_queue);
-      break;
+             break;
 
-      case 2:
-      log_write(L_delay_delivery,
+      case 2: log_write(L_delay_delivery,
                 LOG_MAIN, "no immediate delivery: more than %d messages "
                 "received in one connection", smtp_accept_queue_per_connection);
-      break;
+             break;
 
-      case 3:
-      log_write(L_delay_delivery,
+      case 3: log_write(L_delay_delivery,
                 LOG_MAIN, "no immediate delivery: load average %.2f",
                 (double)load_average/1000.0);
-      break;
+             break;
       }
 
     /* If a delivery attempt is required, spin off a new process to handle it.
     If we are not root, we have to re-exec exim unless deliveries are being
     done unprivileged. */
 
-    else if (!queue_only_policy && !deliver_freeze)
+    else if (  (!f.queue_only_policy || f.queue_smtp)
+            && !f.deliver_freeze)
       {
       pid_t dpid;
 
-      /* Before forking, ensure that the C output buffer is flushed. Otherwise
-      anything that it in it will get duplicated, leading to duplicate copies
-      of the pending output. */
+      /* We used to flush smtp_out before forking so that buffered data was not
+      duplicated, but now we want to pipeline the responses for data and quit.
+      Instead, hard-close the fd underlying smtp_out right after fork to discard
+      the data buffer. */
 
-      mac_smtp_fflush();
-
-      if ((dpid = fork()) == 0)
+      if ((dpid = exim_fork(US"daemon-accept-delivery")) == 0)
         {
         (void)fclose(smtp_in);
+       (void)close(fileno(smtp_out));
         (void)fclose(smtp_out);
+       smtp_in = smtp_out = NULL;
 
         /* Don't ever molest the parent's SSL connection, but do clean up
         the data structures if necessary. */
 
-        #ifdef SUPPORT_TLS
-        tls_close(TRUE, FALSE);
-        #endif
+#ifndef DISABLE_TLS
+        tls_close(NULL, TLS_NO_SHUTDOWN);
+#endif
 
         /* Reset SIGHUP and SIGCHLD in the child in both cases. */
 
         signal(SIGHUP,  SIG_DFL);
         signal(SIGCHLD, SIG_DFL);
+        signal(SIGTERM, SIG_DFL);
+        signal(SIGINT, SIG_DFL);
 
         if (geteuid() != root_uid && !deliver_drop_privilege)
           {
           signal(SIGALRM, SIG_DFL);
-          (void)child_exec_exim(CEE_EXEC_PANIC, FALSE, NULL, FALSE, 2, US"-Mc",
-            message_id);
+         delivery_re_exec(CEE_EXEC_PANIC);
           /* Control does not return here. */
           }
 
         /* No need to re-exec; SIGALRM remains set to the default handler */
 
-        (void)deliver_message(message_id, FALSE, FALSE);
+        (void) deliver_message(message_id, FALSE, FALSE);
         search_tidyup();
-        _exit(EXIT_SUCCESS);
+        exim_underbar_exit(EXIT_SUCCESS);
         }
 
       if (dpid > 0)
         {
+       release_cutthrough_connection(US"passed for delivery");
         DEBUG(D_any) debug_printf("forked delivery process %d\n", (int)dpid);
         }
       else
-        {
+       {
+       cancel_cutthrough_connection(TRUE, US"delivery fork failed");
         log_write(0, LOG_MAIN|LOG_PANIC, "daemon: delivery process fork "
           "failed: %s", strerror(errno));
-        }
+       }
       }
     }
   }
@@ -682,25 +743,21 @@ failed. Otherwise, keep count of the number of accepting processes and
 remember the pid for ticking off when the child completes. */
 
 if (pid < 0)
-  {
   never_error(US"daemon: accept process fork failed", US"Fork failed", errno);
-  }
 else
   {
-  int i;
-  for (i = 0; i < smtp_accept_max; ++i)
-    {
+  for (int i = 0; i < smtp_accept_max; ++i)
     if (smtp_slots[i].pid <= 0)
       {
       smtp_slots[i].pid = pid;
-      if (smtp_accept_max_per_host != NULL)
+      /* Connection closes come asyncronously, so we cannot stack this store */
+      if (smtp_accept_max_per_host)
         smtp_slots[i].host_address = string_copy_malloc(sender_host_address);
       smtp_accept_count++;
       break;
       }
-    }
   DEBUG(D_any) debug_printf("%d SMTP accept process%s running\n",
-    smtp_accept_count, (smtp_accept_count == 1)? "" : "es");
+    smtp_accept_count, smtp_accept_count == 1 ? "" : "es");
   }
 
 /* Get here via goto in error cases */
@@ -715,7 +772,7 @@ manifest itself as a broken pipe, so drop that one too. If the streams don't
 exist, something went wrong while setting things up. Make sure the socket
 descriptors are closed, in order to drop the connection. */
 
-if (smtp_out != NULL)
+if (smtp_out)
   {
   if (fclose(smtp_out) != 0 && errno != ECONNRESET && errno != EPIPE)
     log_write(0, LOG_MAIN|LOG_PANIC, "daemon: fclose(smtp_out) failed: %s",
@@ -724,7 +781,7 @@ if (smtp_out != NULL)
   }
 else (void)close(accept_socket);
 
-if (smtp_in != NULL)
+if (smtp_in)
   {
   if (fclose(smtp_in) != 0 && errno != ECONNRESET && errno != EPIPE)
     log_write(0, LOG_MAIN|LOG_PANIC, "daemon: fclose(smtp_in) failed: %s",
@@ -737,8 +794,8 @@ else (void)close(dup_accept_socket);
 the incoming host address and an expanded active_hostname. */
 
 log_close_all();
+interface_address = sender_host_name = sender_host_address = NULL;
 store_reset(reset_point);
-sender_host_address = NULL;
 }
 
 
@@ -829,7 +886,6 @@ pid_t pid;
 
 while ((pid = waitpid(-1, &status, WNOHANG)) > 0)
   {
-  int i;
   DEBUG(D_any)
     {
     debug_printf("child %d ended: status=0x%x\n", (int)pid, status);
@@ -845,13 +901,13 @@ while ((pid = waitpid(-1, &status, WNOHANG)) > 0)
   /* If it's a listening daemon for which we are keeping track of individual
   subprocesses, deal with an accepting process that has terminated. */
 
-  if (smtp_slots != NULL)
+  if (smtp_slots)
     {
+    int i;
     for (i = 0; i < smtp_accept_max; i++)
-      {
       if (smtp_slots[i].pid == pid)
         {
-        if (smtp_slots[i].host_address != NULL)
+        if (smtp_slots[i].host_address)
           store_free(smtp_slots[i].host_address);
         smtp_slots[i] = empty_smtp_slot;
         if (--smtp_accept_count < 0) smtp_accept_count = 0;
@@ -859,17 +915,16 @@ while ((pid = waitpid(-1, &status, WNOHANG)) > 0)
           smtp_accept_count, (smtp_accept_count == 1)? "" : "es");
         break;
         }
-      }
     if (i < smtp_accept_max) continue;  /* Found an accepting process */
     }
 
   /* If it wasn't an accepting process, see if it was a queue-runner
   process that we are tracking. */
 
-  if (queue_pid_slots != NULL)
+  if (queue_pid_slots)
     {
-    for (i = 0; i < queue_run_max; i++)
-      {
+    int max = atoi(CS expand_string(queue_run_max));
+    for (int i = 0; i < max; i++)
       if (queue_pid_slots[i] == pid)
         {
         queue_pid_slots[i] = 0;
@@ -878,12 +933,406 @@ while ((pid = waitpid(-1, &status, WNOHANG)) > 0)
           queue_run_count, (queue_run_count == 1)? "" : "es");
         break;
         }
-      }
     }
   }
 }
 
 
+static void
+set_pid_file_path(void)
+{
+if (override_pid_file_path)
+  pid_file_path = override_pid_file_path;
+
+if (!*pid_file_path)
+  pid_file_path = string_sprintf("%s/exim-daemon.pid", spool_directory);
+
+if (pid_file_path[0] != '/')
+  log_write(0, LOG_PANIC_DIE, "pid file path %s must be absolute\n", pid_file_path);
+}
+
+
+enum pid_op { PID_WRITE, PID_CHECK, PID_DELETE };
+
+/* Do various pid file operations as safe as possible. Ideally we'd just
+drop the privileges for creation of the pid file and not care at all about removal of
+the file. FIXME.
+Returns: true on success, false + errno==EACCES otherwise
+*/
+
+static BOOL
+operate_on_pid_file(const enum pid_op operation, const pid_t pid)
+{
+char pid_line[sizeof(int) * 3 + 2];
+const int pid_len = snprintf(pid_line, sizeof(pid_line), "%d\n", (int)pid);
+BOOL lines_match = FALSE;
+uschar * path, * base, * dir;
+
+const int dir_flags = O_RDONLY | O_NONBLOCK;
+const int base_flags = O_NOFOLLOW | O_NONBLOCK;
+const mode_t base_mode = 0644;
+struct stat sb;
+int cwd_fd = -1, dir_fd = -1, base_fd = -1;
+BOOL success = FALSE;
+errno = EACCES;
+
+set_pid_file_path();
+if (!f.running_in_test_harness && real_uid != root_uid && real_uid != exim_uid) goto cleanup;
+if (pid_len < 2 || pid_len >= (int)sizeof(pid_line)) goto cleanup;
+
+path = string_copy(pid_file_path);
+if ((base = Ustrrchr(path, '/')) == NULL)      /* should not happen, but who knows */
+  log_write(0, LOG_MAIN|LOG_PANIC_DIE, "pid file path \"%s\" does not contain a '/'", pid_file_path);
+
+dir = base != path ? path : US"/";
+*base++ = '\0';
+
+if (!dir || !*dir || *dir != '/') goto cleanup;
+if (!base || !*base || Ustrchr(base, '/') != NULL) goto cleanup;
+
+cwd_fd = open(".", dir_flags);
+if (cwd_fd < 0 || fstat(cwd_fd, &sb) != 0 || !S_ISDIR(sb.st_mode)) goto cleanup;
+dir_fd = open(CS dir, dir_flags);
+if (dir_fd < 0 || fstat(dir_fd, &sb) != 0 || !S_ISDIR(sb.st_mode)) goto cleanup;
+
+/* emulate openat */
+if (fchdir(dir_fd) != 0) goto cleanup;
+base_fd = open(CS base, O_RDONLY | base_flags);
+if (fchdir(cwd_fd) != 0)
+  log_write(0, LOG_MAIN|LOG_PANIC_DIE, "can't return to previous working dir: %s", strerror(errno));
+
+if (base_fd >= 0)
+  {
+  char line[sizeof(pid_line)];
+  ssize_t len = -1;
+
+  if (fstat(base_fd, &sb) != 0 || !S_ISREG(sb.st_mode)) goto cleanup;
+  if ((sb.st_mode & 07777) != base_mode || sb.st_nlink != 1) goto cleanup;
+  if (sb.st_size < 2 || sb.st_size >= (off_t)sizeof(line)) goto cleanup;
+
+  len = read(base_fd, line, sizeof(line));
+  if (len != (ssize_t)sb.st_size) goto cleanup;
+  line[len] = '\0';
+
+  if (strspn(line, "0123456789") != (size_t)len-1) goto cleanup;
+  if (line[len-1] != '\n') goto cleanup;
+  lines_match = len == pid_len && strcmp(line, pid_line) == 0;
+  }
+
+if (operation == PID_WRITE)
+  {
+  if (!lines_match)
+    {
+    if (base_fd >= 0)
+      {
+      int error = -1;
+      /* emulate unlinkat */
+      if (fchdir(dir_fd) != 0) goto cleanup;
+      error = unlink(CS base);
+      if (fchdir(cwd_fd) != 0)
+        log_write(0, LOG_MAIN|LOG_PANIC_DIE, "can't return to previous working dir: %s", strerror(errno));
+      if (error) goto cleanup;
+      (void)close(base_fd);
+      base_fd = -1;
+     }
+    /* emulate openat */
+    if (fchdir(dir_fd) != 0) goto cleanup;
+    base_fd = open(CS base, O_WRONLY | O_CREAT | O_EXCL | base_flags, base_mode);
+    if (fchdir(cwd_fd) != 0)
+        log_write(0, LOG_MAIN|LOG_PANIC_DIE, "can't return to previous working dir: %s", strerror(errno));
+    if (base_fd < 0) goto cleanup;
+    if (fchmod(base_fd, base_mode) != 0) goto cleanup;
+    if (write(base_fd, pid_line, pid_len) != pid_len) goto cleanup;
+    DEBUG(D_any) debug_printf("pid written to %s\n", pid_file_path);
+    }
+  }
+else
+  {
+  if (!lines_match) goto cleanup;
+  if (operation == PID_DELETE)
+    {
+    int error = -1;
+    /* emulate unlinkat */
+    if (fchdir(dir_fd) != 0) goto cleanup;
+    error = unlink(CS base);
+    if (fchdir(cwd_fd) != 0)
+        log_write(0, LOG_MAIN|LOG_PANIC_DIE, "can't return to previous working dir: %s", strerror(errno));
+    if (error) goto cleanup;
+    }
+  }
+
+success = TRUE;
+errno = 0;
+
+cleanup:
+if (cwd_fd >= 0) (void)close(cwd_fd);
+if (dir_fd >= 0) (void)close(dir_fd);
+if (base_fd >= 0) (void)close(base_fd);
+return success;
+}
+
+
+/* Remove the daemon's pidfile.  Note: runs with root privilege,
+as a direct child of the daemon.  Does not return. */
+
+void
+delete_pid_file(void)
+{
+const BOOL success = operate_on_pid_file(PID_DELETE, getppid());
+
+DEBUG(D_any)
+  debug_printf("delete pid file %s %s: %s\n", pid_file_path,
+    success ? "success" : "failure", strerror(errno));
+
+exim_exit(EXIT_SUCCESS);
+}
+
+
+/* Called by the daemon; exec a child to get the pid file deleted
+since we may require privs for the containing directory */
+
+static void
+daemon_die(void)
+{
+int pid;
+
+DEBUG(D_any) debug_printf("SIGTERM/SIGINT seen\n");
+#if !defined(DISABLE_TLS) && (defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT))
+tls_watch_invalidate();
+#endif
+
+if (daemon_notifier_fd >= 0)
+  {
+  close(daemon_notifier_fd);
+  daemon_notifier_fd = -1;
+#ifndef EXIM_HAVE_ABSTRACT_UNIX_SOCKETS
+  unlink_notifier_socket();
+#endif
+  }
+
+if (f.running_in_test_harness || write_pid)
+  {
+  if ((pid = exim_fork(US"daemon-del-pidfile")) == 0)
+    {
+    if (override_pid_file_path)
+      (void)child_exec_exim(CEE_EXEC_PANIC, FALSE, NULL, FALSE, 3,
+       "-oP", override_pid_file_path, "-oPX");
+    else
+      (void)child_exec_exim(CEE_EXEC_PANIC, FALSE, NULL, FALSE, 1, "-oPX");
+
+    /* Control never returns here. */
+    }
+  if (pid > 0)
+    child_close(pid, 1);
+  }
+exim_exit(EXIT_SUCCESS);
+}
+
+
+/*************************************************
+*      Listener socket for local work prompts   *
+*************************************************/
+
+ssize_t
+daemon_client_sockname(struct sockaddr_un * sup, uschar ** sname)
+{
+#ifdef EXIM_HAVE_ABSTRACT_UNIX_SOCKETS
+sup->sun_path[0] = 0;  /* Abstract local socket addr - Linux-specific? */
+return offsetof(struct sockaddr_un, sun_path) + 1
+  + snprintf(sup->sun_path+1, sizeof(sup->sun_path)-1, "exim_%d", getpid());
+#else
+*sname = string_sprintf("%s/p_%d", spool_directory, getpid());
+return offsetof(struct sockaddr_un, sun_path)
+  + snprintf(sup->sun_path, sizeof(sup->sun_path), "%s", sname);
+#endif
+}
+
+ssize_t
+daemon_notifier_sockname(struct sockaddr_un * sup)
+{
+#ifdef EXIM_HAVE_ABSTRACT_UNIX_SOCKETS
+sup->sun_path[0] = 0;  /* Abstract local socket addr - Linux-specific? */
+return offsetof(struct sockaddr_un, sun_path) + 1
+  + snprintf(sup->sun_path+1, sizeof(sup->sun_path)-1, "%s",
+              expand_string(notifier_socket));
+#else
+return offsetof(struct sockaddr_un, sun_path)
+  + snprintf(sup->sun_path, sizeof(sup->sun_path), "%s",
+              expand_string(notifier_socket));
+#endif
+}
+
+
+static void
+daemon_notifier_socket(void)
+{
+int fd;
+const uschar * where;
+struct sockaddr_un sa_un = {.sun_family = AF_UNIX};
+ssize_t len;
+
+if (!notifier_socket || !*notifier_socket)
+  {
+  DEBUG(D_any) debug_printf("-oY used so not creating notifier socket\n");
+  return;
+  }
+if (override_local_interfaces && !override_pid_file_path)
+  {
+  DEBUG(D_any)
+    debug_printf("-oX used without -oP so not creating notifier socket\n");
+  return;
+  }
+
+DEBUG(D_any) debug_printf("creating notifier socket\n");
+
+#ifdef SOCK_CLOEXEC
+if ((fd = socket(PF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0)) < 0)
+  { where = US"socket"; goto bad; }
+#else
+if ((fd = socket(PF_UNIX, SOCK_DGRAM, 0)) < 0)
+  { where = US"socket"; goto bad; }
+(void)fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
+#endif
+
+len = daemon_notifier_sockname(&sa_un);
+
+#ifdef EXIM_HAVE_ABSTRACT_UNIX_SOCKETS
+DEBUG(D_any) debug_printf(" @%s\n", sa_un.sun_path+1);
+#else                  /* filesystem-visible and persistent; will neeed removal */
+DEBUG(D_any) debug_printf(" %s\n", sa_un.sun_path);
+#endif
+
+if (bind(fd, (const struct sockaddr *)&sa_un, (socklen_t)len) < 0)
+  { where = US"bind"; goto bad; }
+
+#ifdef SO_PASSCRED             /* Linux */
+if (setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)) < 0)
+  { where = US"SO_PASSCRED"; goto bad2; }
+#elif defined(LOCAL_CREDS)     /* FreeBSD-ish */
+if (setsockopt(fd, 0, LOCAL_CREDS, &on, sizeof(on)) < 0)
+  { where = US"LOCAL_CREDS"; goto bad2; }
+#endif
+
+/* debug_printf("%s: fd %d\n", __FUNCTION__, fd); */
+daemon_notifier_fd = fd;
+return;
+
+bad2:
+#ifndef EXIM_HAVE_ABSTRACT_UNIX_SOCKETS
+  Uunlink(sa_un.sun_path);
+#endif
+bad:
+  log_write(0, LOG_MAIN|LOG_PANIC, "%s %s: %s",
+    __FUNCTION__, where, strerror(errno));
+  close(fd);
+  return;
+}
+
+
+static uschar queuerun_msgid[MESSAGE_ID_LENGTH+1];
+
+/* The notifier socket has something to read. Pull the message from it, decode
+and do the action.
+
+Return TRUE if a sigalrm should be emulated */
+
+static BOOL
+daemon_notification(void)
+{
+uschar buf[256], cbuf[256];
+struct sockaddr_un sa_un;
+struct iovec iov = {.iov_base = buf, .iov_len = sizeof(buf)-1};
+struct msghdr msg = { .msg_name = &sa_un,
+                     .msg_namelen = sizeof(sa_un),
+                     .msg_iov = &iov,
+                     .msg_iovlen = 1,
+                     .msg_control = cbuf,
+                     .msg_controllen = sizeof(cbuf)
+                   };
+ssize_t sz;
+
+buf[sizeof(buf)-1] = 0;
+if ((sz = recvmsg(daemon_notifier_fd, &msg, 0)) <= 0) return FALSE;
+if (sz >= sizeof(buf)) return FALSE;
+
+#ifdef notdef
+debug_printf("addrlen %d\n", msg.msg_namelen);
+#endif
+DEBUG(D_queue_run) debug_printf("%s from addr '%s%.*s'\n", __FUNCTION__,
+  *sa_un.sun_path ? "" : "@",
+  (int)msg.msg_namelen - (*sa_un.sun_path ? 0 : 1),
+  sa_un.sun_path + (*sa_un.sun_path ? 0 : 1));
+
+/* Refuse to handle the item unless the peer has good credentials */
+#ifdef SCM_CREDENTIALS
+# define EXIM_SCM_CR_TYPE SCM_CREDENTIALS
+#elif defined(LOCAL_CREDS) && defined(SCM_CREDS)
+# define EXIM_SCM_CR_TYPE SCM_CREDS
+#else
+       /* The OS has no way to get the creds of the caller (for a unix/datagram socket.
+       Punt; don't try to check. */
+#endif
+
+#ifdef EXIM_SCM_CR_TYPE
+for (struct cmsghdr * cp = CMSG_FIRSTHDR(&msg);
+     cp;
+     cp = CMSG_NXTHDR(&msg, cp))
+  if (cp->cmsg_level == SOL_SOCKET && cp->cmsg_type == EXIM_SCM_CR_TYPE)
+  {
+# ifdef SCM_CREDENTIALS                                        /* Linux */
+  struct ucred * cr = (struct ucred *) CMSG_DATA(cp);
+  if (cr->uid && cr->uid != exim_uid)
+    {
+    DEBUG(D_queue_run) debug_printf("%s: sender creds pid %d uid %d gid %d\n",
+      __FUNCTION__, (int)cr->pid, (int)cr->uid, (int)cr->gid);
+    }
+# elif defined(LOCAL_CREDS)                            /* BSD-ish */
+  struct sockcred * cr = (struct sockcred *) CMSG_DATA(cp);
+  if (cr->sc_uid && cr->sc_uid != exim_uid)
+    {
+    DEBUG(D_queue_run) debug_printf("%s: sender creds pid ??? uid %d gid %d\n",
+      __FUNCTION__, (int)cr->sc_uid, (int)cr->sc_gid);
+    }
+# endif
+  break;
+  }
+#endif
+
+buf[sz] = 0;
+switch (buf[0])
+  {
+#ifndef DISABLE_QUEUE_RAMP
+  case NOTIFY_MSG_QRUN:
+    /* this should be a message_id */
+    DEBUG(D_queue_run)
+      debug_printf("%s: qrunner trigger: %s\n", __FUNCTION__, buf+1);
+    memcpy(queuerun_msgid, buf+1, MESSAGE_ID_LENGTH+1);
+    return TRUE;
+#endif
+
+  case NOTIFY_QUEUE_SIZE_REQ:
+    {
+    uschar buf[16];
+    int len = snprintf(CS buf, sizeof(buf), "%u", queue_count_cached());
+
+    DEBUG(D_queue_run)
+      debug_printf("%s: queue size request: %s\n", __FUNCTION__, buf);
+
+    if (sendto(daemon_notifier_fd, buf, len, 0,
+               (const struct sockaddr *)&sa_un, msg.msg_namelen) < 0)
+      log_write(0, LOG_MAIN|LOG_PANIC,
+       "%s: sendto: %s\n", __FUNCTION__, strerror(errno));
+    break;
+    }
+
+  case NOTIFY_REGEX:
+    regex_at_daemon(buf);
+    break;
+  }
+return FALSE;
+}
+
+
 
 /*************************************************
 *              Exim Daemon Mainline              *
@@ -911,29 +1360,35 @@ There are no arguments to this function, and it never returns. */
 void
 daemon_go(void)
 {
-struct passwd *pw;
-int *listen_sockets = NULL;
-int listen_socket_count = 0;
-ip_address_item *addresses = NULL;
+struct passwd * pw;
+struct pollfd * fd_polls, * tls_watch_poll = NULL, * dnotify_poll = NULL;
+int listen_socket_count = 0, poll_fd_count;
+ip_address_item * addresses = NULL;
 time_t last_connection_time = (time_t)0;
+int local_queue_run_max = atoi(CS expand_string(queue_run_max));
+
+process_purpose = US"daemon";
 
 /* If any debugging options are set, turn on the D_pid bit so that all
 debugging lines get the pid added. */
 
 DEBUG(D_any|D_v) debug_selector |= D_pid;
 
-if (inetd_wait_mode)
-  {
-  int on = 1;
+/* Allocate enough pollstructs for inetd mode plus the ancillary sockets;
+also used when there are no listen sockets. */
+
+fd_polls = store_get(sizeof(struct pollfd) * 3, GET_UNTAINTED);
 
+if (f.inetd_wait_mode)
+  {
   listen_socket_count = 1;
-  listen_sockets = store_get(sizeof(int));
   (void) close(3);
   if (dup2(0, 3) == -1)
     log_write(0, LOG_MAIN|LOG_PANIC_DIE,
         "failed to dup inetd socket safely away: %s", strerror(errno));
 
-  listen_sockets[0] = 3;
+  fd_polls[0].fd = 3;
+  fd_polls[0].events = POLLIN;
   (void) close(0);
   (void) close(1);
   (void) close(2);
@@ -963,18 +1418,18 @@ if (inetd_wait_mode)
   }
 
 
-if (inetd_wait_mode || daemon_listen)
+if (f.inetd_wait_mode || f.daemon_listen)
   {
   /* If any option requiring a load average to be available during the
   reception of a message is set, call os_getloadavg() while we are root
   for those OS for which this is necessary the first time it is called (in
   order to perform an "open" on the kernel memory file). */
 
-  #ifdef LOAD_AVG_NEEDS_ROOT
+#ifdef LOAD_AVG_NEEDS_ROOT
   if (queue_only_load >= 0 || smtp_load_reserve >= 0 ||
        (deliver_queue_load_max >= 0 && deliver_drop_privilege))
     (void)os_getloadavg();
-  #endif
+#endif
   }
 
 
@@ -1045,7 +1500,7 @@ The preparation code decodes options and sets up the relevant data. We do this
 first, so that we can return non-zero if there are any syntax errors, and also
 write to stderr. */
 
-if (daemon_listen && !inetd_wait_mode)
+if (f.daemon_listen && !f.inetd_wait_mode)
   {
   int *default_smtp_port;
   int sep;
@@ -1061,62 +1516,41 @@ if (daemon_listen && !inetd_wait_mode)
   that contain neither a dot nor a colon are used to override daemon_smtp_port.
   Any other items are used to override local_interfaces. */
 
-  if (override_local_interfaces != NULL)
+  if (override_local_interfaces)
     {
-    uschar *new_smtp_port = NULL;
-    uschar *new_local_interfaces = NULL;
-    int portsize = 0;
-    int portptr = 0;
-    int ifacesize = 0;
-    int ifaceptr = 0;
+    gstring * new_smtp_port = NULL;
+    gstring * new_local_interfaces = NULL;
 
-    if (override_pid_file_path == NULL) write_pid = FALSE;
+    if (!override_pid_file_path) write_pid = FALSE;
 
     list = override_local_interfaces;
     sep = 0;
-    while ((s = string_nextinlist(&list, &sep, big_buffer, big_buffer_size)))
+    while ((s = string_nextinlist(&list, &sep, NULL, 0)))
       {
       uschar joinstr[4];
-      uschar **ptr;
-      int *sizeptr;
-      int *ptrptr;
-
-      if (Ustrpbrk(s, ".:") == NULL)
-        {
-        ptr = &new_smtp_port;
-        sizeptr = &portsize;
-        ptrptr = &portptr;
-        }
-      else
-        {
-        ptr = &new_local_interfaces;
-        sizeptr = &ifacesize;
-        ptrptr = &ifaceptr;
-        }
+      gstring ** gp = Ustrpbrk(s, ".:") ? &new_local_interfaces : &new_smtp_port;
 
-      if (*ptr == NULL)
+      if (!*gp)
         {
         joinstr[0] = sep;
         joinstr[1] = ' ';
-        *ptr = string_cat(*ptr, sizeptr, ptrptr, US"<", 1);
+        *gp = string_catn(*gp, US"<", 1);
         }
 
-      *ptr = string_cat(*ptr, sizeptr, ptrptr, joinstr, 2);
-      *ptr = string_cat(*ptr, sizeptr, ptrptr, s, Ustrlen(s));
+      *gp = string_catn(*gp, joinstr, 2);
+      *gp = string_cat (*gp, s);
       }
 
-    if (new_smtp_port != NULL)
+    if (new_smtp_port)
       {
-      new_smtp_port[portptr] = 0;
-      daemon_smtp_port = new_smtp_port;
+      daemon_smtp_port = string_from_gstring(new_smtp_port);
       DEBUG(D_any) debug_printf("daemon_smtp_port overridden by -oX:\n  %s\n",
         daemon_smtp_port);
       }
 
-    if (new_local_interfaces != NULL)
+    if (new_local_interfaces)
       {
-      new_local_interfaces[ifaceptr] = 0;
-      local_interfaces = new_local_interfaces;
+      local_interfaces = string_from_gstring(new_local_interfaces);
       local_iface_source = US"-oX data";
       DEBUG(D_any) debug_printf("local_interfaces overridden by -oX:\n  %s\n",
         local_interfaces);
@@ -1124,18 +1558,18 @@ if (daemon_listen && !inetd_wait_mode)
     }
 
   /* Create a list of default SMTP ports, to be used if local_interfaces
-  contains entries without explict ports. First count the number of ports, then
+  contains entries without explicit ports. First count the number of ports, then
   build a translated list in a vector. */
 
   list = daemon_smtp_port;
   sep = 0;
-  while ((s = string_nextinlist(&list, &sep, big_buffer, big_buffer_size)))
+  while ((s = string_nextinlist(&list, &sep, NULL, 0)))
     pct++;
-  default_smtp_port = store_get((pct+1) * sizeof(int));
+  default_smtp_port = store_get((pct+1) * sizeof(int), GET_UNTAINTED);
   list = daemon_smtp_port;
   sep = 0;
   for (pct = 0;
-       (s = string_nextinlist(&list, &sep, big_buffer, big_buffer_size));
+       (s = string_nextinlist(&list, &sep, NULL, 0));
        pct++)
     {
     if (isdigit(*s))
@@ -1159,9 +1593,12 @@ if (daemon_listen && !inetd_wait_mode)
 
   list = tls_in.on_connect_ports;
   sep = 0;
+  /* the list isn't expanded so cannot be tainted.  If it ever is we will trap here */
   while ((s = string_nextinlist(&list, &sep, big_buffer, big_buffer_size)))
     if (!isdigit(*s))
       {
+      gstring * g = NULL;
+
       list = tls_in.on_connect_ports;
       tls_in.on_connect_ports = NULL;
       sep = 0;
@@ -1169,14 +1606,15 @@ if (daemon_listen && !inetd_wait_mode)
        {
         if (!isdigit(*s))
          {
-         struct servent *smtp_service = getservbyname(CS s, "tcp");
+         struct servent * smtp_service = getservbyname(CS s, "tcp");
          if (!smtp_service)
            log_write(0, LOG_PANIC_DIE|LOG_CONFIG, "TCP port \"%s\" not found", s);
-         s= string_sprintf("%d", (int)ntohs(smtp_service->s_port));
+         s = string_sprintf("%d", (int)ntohs(smtp_service->s_port));
          }
-       tls_in.on_connect_ports = string_append_listele(tls_in.on_connect_ports,
-           ':', s);
+       g = string_append_listele(g, ':', s);
        }
+      if (g)
+       tls_in.on_connect_ports = g->s;
       break;
       }
 
@@ -1195,11 +1633,10 @@ if (daemon_listen && !inetd_wait_mode)
   In the same scan, fill in missing port numbers from the default list. When
   there is more than one item in the list, extra items are created. */
 
-  for (ipa = addresses; ipa != NULL; ipa = ipa->next)
+  for (ipa = addresses; ipa; ipa = ipa->next)
     {
-    int i;
-
-    if (Ustrcmp(ipa->address, "0.0.0.0") == 0) ipa->address[0] = 0;
+    if (Ustrcmp(ipa->address, "0.0.0.0") == 0)
+      ipa->address[0] = 0;
     else if (Ustrcmp(ipa->address, "::0") == 0)
       {
       ipa->address[0] = ':';
@@ -1211,12 +1648,14 @@ if (daemon_listen && !inetd_wait_mode)
     if (daemon_smtp_port[0] <= 0)
       log_write(0, LOG_MAIN|LOG_PANIC_DIE, "no port specified for interface "
         "%s and daemon_smtp_port is unset; cannot start daemon",
-        (ipa->address[0] == 0)? US"\"all IPv4\"" :
-        (ipa->address[1] == 0)? US"\"all IPv6\"" : ipa->address);
+        ipa->address[0] == 0 ? US"\"all IPv4\"" :
+        ipa->address[1] == 0 ? US"\"all IPv6\"" : ipa->address);
+
     ipa->port = default_smtp_port[0];
-    for (i = 1; default_smtp_port[i] > 0; i++)
+    for (int i = 1; default_smtp_port[i] > 0; i++)
       {
-      ip_address_item *new = store_get(sizeof(ip_address_item));
+      ip_address_item * new = store_get(sizeof(ip_address_item), GET_UNTAINTED);
+
       memcpy(new->address, ipa->address, Ustrlen(ipa->address) + 1);
       new->port = default_smtp_port[i];
       new->next = ipa->next;
@@ -1231,15 +1670,14 @@ if (daemon_listen && !inetd_wait_mode)
   also simplifies the construction of the "daemon started" log line. */
 
   pipa = &addresses;
-  for (ipa = addresses; ipa != NULL; pipa = &(ipa->next), ipa = ipa->next)
+  for (ipa = addresses; ipa; pipa = &ipa->next, ipa = ipa->next)
     {
     ip_address_item *ipa2;
 
     /* Handle an IPv4 wildcard */
 
     if (ipa->address[0] == 0)
-      {
-      for (ipa2 = ipa; ipa2->next != NULL; ipa2 = ipa2->next)
+      for (ipa2 = ipa; ipa2->next; ipa2 = ipa2->next)
         {
         ip_address_item *ipa3 = ipa2->next;
         if (ipa3->address[0] == ':' &&
@@ -1252,13 +1690,11 @@ if (daemon_listen && !inetd_wait_mode)
           break;
           }
         }
-      }
 
     /* Handle an IPv6 wildcard. */
 
     else if (ipa->address[0] == ':' && ipa->address[1] == 0)
-      {
-      for (ipa2 = ipa; ipa2->next != NULL; ipa2 = ipa2->next)
+      for (ipa2 = ipa; ipa2->next; ipa2 = ipa2->next)
         {
         ip_address_item *ipa3 = ipa2->next;
         if (ipa3->address[0] == 0 && ipa3->port == ipa->port)
@@ -1270,18 +1706,22 @@ if (daemon_listen && !inetd_wait_mode)
           break;
           }
         }
-      }
     }
 
-  /* Get a vector to remember all the sockets in */
+  /* Get a vector to remember all the sockets in.
+  Two extra elements for the ancillary sockets */
 
-  for (ipa = addresses; ipa != NULL; ipa = ipa->next)
+  for (ipa = addresses; ipa; ipa = ipa->next)
     listen_socket_count++;
-  listen_sockets = store_get(sizeof(int) * listen_socket_count);
+  fd_polls = store_get(sizeof(struct pollfd) * (listen_socket_count + 2),
+                           GET_UNTAINTED);
+  for (struct pollfd * p = fd_polls; p < fd_polls + listen_socket_count + 2;
+       p++)
+    { p->fd = -1; p->events = POLLIN; }
 
   } /* daemon_listen but not inetd_wait_mode */
 
-if (daemon_listen)
+if (f.daemon_listen)
   {
 
   /* Do a sanity check on the max connects value just to save us from getting
@@ -1300,9 +1740,8 @@ if (daemon_listen)
 
   if (smtp_accept_max > 0)
     {
-    int i;
-    smtp_slots = store_get(smtp_accept_max * sizeof(smtp_slot));
-    for (i = 0; i < smtp_accept_max; i++) smtp_slots[i] = empty_smtp_slot;
+    smtp_slots = store_get(smtp_accept_max * sizeof(smtp_slot), GET_UNTAINTED);
+    for (int i = 0; i < smtp_accept_max; i++) smtp_slots[i] = empty_smtp_slot;
     }
   }
 
@@ -1321,7 +1760,7 @@ Then disconnect from the controlling terminal, Most modern Unixes seem to have
 setsid() for getting rid of the controlling terminal. For any OS that doesn't,
 setsid() can be #defined as a no-op, or as something else. */
 
-if (background_daemon || inetd_wait_mode)
+if (f.background_daemon || f.inetd_wait_mode)
   {
   log_close_all();    /* Just in case anything was logged earlier */
   search_tidyup();    /* Just in case any were used in reading the config. */
@@ -1332,31 +1771,36 @@ if (background_daemon || inetd_wait_mode)
   log_stderr = NULL;  /* So no attempt to copy paniclog output */
   }
 
-if (background_daemon)
+if (f.background_daemon)
   {
   /* If the parent process of this one has pid == 1, we are re-initializing the
   daemon as the result of a SIGHUP. In this case, there is no need to do
   anything, because the controlling terminal has long gone. Otherwise, fork, in
   case current process is a process group leader (see 'man setsid' for an
-  explanation) before calling setsid(). */
+  explanation) before calling setsid().
+  All other forks want daemon_listen cleared. Rather than blow a register, jsut
+  restore it here. */
 
   if (getppid() != 1)
     {
-    pid_t pid = fork();
+    BOOL daemon_listen = f.daemon_listen;
+    pid_t pid = exim_fork(US"daemon");
     if (pid < 0) log_write(0, LOG_MAIN|LOG_PANIC_DIE,
       "fork() failed when starting daemon: %s", strerror(errno));
     if (pid > 0) exit(EXIT_SUCCESS);      /* in parent process, just exit */
     (void)setsid();                       /* release controlling terminal */
+    f.daemon_listen = daemon_listen;
     }
   }
 
 /* We are now in the disconnected, daemon process (unless debugging). Set up
 the listening sockets if required. */
 
-if (daemon_listen && !inetd_wait_mode)
+daemon_notifier_socket();
+
+if (f.daemon_listen && !f.inetd_wait_mode)
   {
   int sk;
-  int on = 1;
   ip_address_item *ipa;
 
   /* For each IP address, create a socket, bind it to the appropriate port, and
@@ -1368,8 +1812,8 @@ if (daemon_listen && !inetd_wait_mode)
   for (ipa = addresses, sk = 0; sk < listen_socket_count; ipa = ipa->next, sk++)
     {
     BOOL wildcard;
-    ip_address_item *ipa2;
-    int af;
+    ip_address_item * ipa2;
+    int fd, af;
 
     if (Ustrchr(ipa->address, ':') != NULL)
       {
@@ -1382,7 +1826,7 @@ if (daemon_listen && !inetd_wait_mode)
       wildcard = ipa->address[0] == 0;
       }
 
-    if ((listen_sockets[sk] = ip_socket(SOCK_STREAM, af)) < 0)
+    if ((fd_polls[sk].fd = fd = ip_socket(SOCK_STREAM, af)) < 0)
       {
       if (check_special_case(0, addresses, ipa, FALSE))
         {
@@ -1391,35 +1835,32 @@ if (daemon_listen && !inetd_wait_mode)
         goto SKIP_SOCKET;
         }
       log_write(0, LOG_PANIC_DIE, "IPv%c socket creation failed: %s",
-        (af == AF_INET6)? '6' : '4', strerror(errno));
+        af == AF_INET6 ? '6' : '4', strerror(errno));
       }
 
     /* If this is an IPv6 wildcard socket, set IPV6_V6ONLY if that option is
     available. Just log failure (can get protocol not available, just like
     socket creation can). */
 
-    #ifdef IPV6_V6ONLY
+#ifdef IPV6_V6ONLY
     if (af == AF_INET6 && wildcard &&
-        setsockopt(listen_sockets[sk], IPPROTO_IPV6, IPV6_V6ONLY, (char *)(&on),
-          sizeof(on)) < 0)
+        setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)) < 0)
       log_write(0, LOG_MAIN, "Setting IPV6_V6ONLY on daemon's IPv6 wildcard "
         "socket failed (%s): carrying on without it", strerror(errno));
-    #endif  /* IPV6_V6ONLY */
+#endif  /* IPV6_V6ONLY */
 
     /* Set SO_REUSEADDR so that the daemon can be restarted while a connection
     is being handled.  Without this, a connection will prevent reuse of the
     smtp port for listening. */
 
-    if (setsockopt(listen_sockets[sk], SOL_SOCKET, SO_REUSEADDR,
-                   (uschar *)(&on), sizeof(on)) < 0)
+    if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
       log_write(0, LOG_MAIN|LOG_PANIC_DIE, "setting SO_REUSEADDR on socket "
         "failed when starting daemon: %s", strerror(errno));
 
     /* Set TCP_NODELAY; Exim does its own buffering. There is a switch to
     disable this because it breaks some broken clients. */
 
-    if (tcp_nodelay) setsockopt(listen_sockets[sk], IPPROTO_TCP, TCP_NODELAY,
-      (uschar *)(&on), sizeof(on));
+    if (tcp_nodelay) setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on));
 
     /* Now bind the socket to the required port; if Exim is being restarted
     it may not always be possible to bind immediately, even with SO_REUSEADDR
@@ -1431,20 +1872,26 @@ if (daemon_listen && !inetd_wait_mode)
     necessary for (some release of) USAGI Linux; other IP stacks fail at the
     listen() stage instead. */
 
+#ifdef TCP_FASTOPEN
+    f.tcp_fastopen_ok = TRUE;
+#endif
     for(;;)
       {
       uschar *msg, *addr;
-      if (ip_bind(listen_sockets[sk], af, ipa->address, ipa->port) >= 0) break;
+      if (ip_bind(fd, af, ipa->address, ipa->port) >= 0) break;
       if (check_special_case(errno, addresses, ipa, TRUE))
         {
         DEBUG(D_any) debug_printf("wildcard IPv4 bind() failed after IPv6 "
           "listen() success; EADDRINUSE ignored\n");
-        (void)close(listen_sockets[sk]);
+        (void)close(fd);
         goto SKIP_SOCKET;
         }
       msg = US strerror(errno);
-      addr = wildcard? ((af == AF_INET6)? US"(any IPv6)" : US"(any IPv4)") :
-        ipa->address;
+      addr = wildcard
+        ? af == AF_INET6
+       ? US"(any IPv6)"
+       : US"(any IPv4)"
+       : ipa->address;
       if (daemon_startup_retries <= 0)
         log_write(0, LOG_MAIN|LOG_PANIC_DIE,
           "socket bind() to port %d for address %s failed: %s: "
@@ -1458,18 +1905,38 @@ if (daemon_listen && !inetd_wait_mode)
       }
 
     DEBUG(D_any)
-      {
       if (wildcard)
         debug_printf("listening on all interfaces (IPv%c) port %d\n",
-          (af == AF_INET6)? '6' : '4', ipa->port);
+          af == AF_INET6 ? '6' : '4', ipa->port);
       else
         debug_printf("listening on %s port %d\n", ipa->address, ipa->port);
-      }
 
     /* Start listening on the bound socket, establishing the maximum backlog of
-    connections that is allowed. On success, continue to the next address. */
+    connections that is allowed. On success, add to the set of sockets for select
+    and continue to the next address. */
 
-    if (listen(listen_sockets[sk], smtp_connect_backlog) >= 0) continue;
+#if defined(TCP_FASTOPEN) && !defined(__APPLE__)
+    if (  f.tcp_fastopen_ok
+       && setsockopt(fd, IPPROTO_TCP, TCP_FASTOPEN,
+                   &smtp_connect_backlog, sizeof(smtp_connect_backlog)))
+      {
+      DEBUG(D_any) debug_printf("setsockopt FASTOPEN: %s\n", strerror(errno));
+      f.tcp_fastopen_ok = FALSE;
+      }
+#endif
+    if (listen(fd, smtp_connect_backlog) >= 0)
+      {
+#if defined(TCP_FASTOPEN) && defined(__APPLE__)
+      if (  f.tcp_fastopen_ok
+        && setsockopt(fd, IPPROTO_TCP, TCP_FASTOPEN, &on, sizeof(on)))
+       {
+       DEBUG(D_any) debug_printf("setsockopt FASTOPEN: %s\n", strerror(errno));
+       f.tcp_fastopen_ok = FALSE;
+       }
+#endif
+      fd_polls[sk].fd = fd;
+      continue;
+      }
 
     /* Listening has failed. In an IPv6 environment, as for bind(), if listen()
     fails with the error EADDRINUSE and we are doing IPv4 wildcard listening
@@ -1479,19 +1946,19 @@ if (daemon_listen && !inetd_wait_mode)
 
     if (!check_special_case(errno, addresses, ipa, TRUE))
       log_write(0, LOG_PANIC_DIE, "listen() failed on interface %s: %s",
-        wildcard? ((af == AF_INET6)? US"(any IPv6)" : US"(any IPv4)") :
-        ipa->address,
+        wildcard
+       ? af == AF_INET6 ? US"(any IPv6)" : US"(any IPv4)" : ipa->address,
         strerror(errno));
 
     DEBUG(D_any) debug_printf("wildcard IPv4 listen() failed after IPv6 "
       "listen() success; EADDRINUSE ignored\n");
-    (void)close(listen_sockets[sk]);
+    (void)close(fd);
 
     /* Come here if there has been a problem with the socket which we
     are going to ignore. We remove the address from the chain, and back up the
     counts. */
 
-    SKIP_SOCKET:
+  SKIP_SOCKET:
     sk--;                          /* Back up the count */
     listen_socket_count--;         /* Reduce the total */
     if (ipa == addresses) addresses = ipa->next; else
@@ -1507,7 +1974,8 @@ if (daemon_listen && !inetd_wait_mode)
 /* If we are not listening, we want to write a pid file only if -oP was
 explicitly given. */
 
-else if (override_pid_file_path == NULL) write_pid = FALSE;
+else if (!override_pid_file_path)
+  write_pid = FALSE;
 
 /* Write the pid to a known file for assistance in identification, if required.
 We do this before giving up root privilege, because on some systems it is
@@ -1523,29 +1991,13 @@ automatically. Consequently, Exim 4 writes a pid file only
 
 The variable daemon_write_pid is used to control this. */
 
-if (running_in_test_harness || write_pid)
+if (f.running_in_test_harness || write_pid)
   {
-  FILE *f;
-
-  if (override_pid_file_path != NULL)
-    pid_file_path = override_pid_file_path;
-
-  if (pid_file_path[0] == 0)
-    pid_file_path = string_sprintf("%s/exim-daemon.pid", spool_directory);
-
-  f = modefopen(pid_file_path, "wb", 0644);
-  if (f != NULL)
-    {
-    (void)fprintf(f, "%d\n", (int)getpid());
-    (void)fclose(f);
-    DEBUG(D_any) debug_printf("pid written to %s\n", pid_file_path);
-    }
-  else
-    {
-    DEBUG(D_any)
-      debug_printf("%s\n", string_open_failed(errno, "pid file %s",
-        pid_file_path));
-    }
+  const enum pid_op operation = (f.running_in_test_harness
+     || real_uid == root_uid
+     || (real_uid == exim_uid && !override_pid_file_path)) ? PID_WRITE : PID_CHECK;
+  if (!operate_on_pid_file(operation, getpid()))
+    DEBUG(D_any) debug_printf("%s pid file %s: %s\n", (operation == PID_WRITE) ? "write" : "check", pid_file_path, strerror(errno));
   }
 
 /* Set up the handler for SIGHUP, which causes a restart of the daemon. */
@@ -1566,24 +2018,28 @@ coming from Exim, not whoever started the daemon. */
 
 originator_uid = exim_uid;
 originator_gid = exim_gid;
-originator_login = ((pw = getpwuid(exim_uid)) != NULL)?
-  string_copy_malloc(US pw->pw_name) : US"exim";
+originator_login = (pw = getpwuid(exim_uid))
+  ? string_copy_perm(US pw->pw_name, FALSE) : US"exim";
 
 /* Get somewhere to keep the list of queue-runner pids if we are keeping track
 of them (and also if we are doing queue runs). */
 
-if (queue_interval > 0 && queue_run_max > 0)
+if (queue_interval > 0 && local_queue_run_max > 0)
   {
-  int i;
-  queue_pid_slots = store_get(queue_run_max * sizeof(pid_t));
-  for (i = 0; i < queue_run_max; i++) queue_pid_slots[i] = 0;
+  queue_pid_slots = store_get(local_queue_run_max * sizeof(pid_t), GET_UNTAINTED);
+  for (int i = 0; i < local_queue_run_max; i++) queue_pid_slots[i] = 0;
   }
 
-/* Set up the handler for termination of child processes. */
+/* Set up the handler for termination of child processes, and the one
+telling us to die. */
 
 sigchld_seen = FALSE;
 os_non_restarting_signal(SIGCHLD, main_sigchld_handler);
 
+sigterm_seen = FALSE;
+os_non_restarting_signal(SIGTERM, main_sigterm_handler);
+os_non_restarting_signal(SIGINT, main_sigterm_handler);
+
 /* If we are to run the queue periodically, pretend the alarm has just gone
 off. This will cause the first queue-runner to get kicked off straight away. */
 
@@ -1592,7 +2048,7 @@ sigalrm_seen = (queue_interval > 0);
 /* Log the start up of a daemon - at least one of listening or queue running
 must be set up. */
 
-if (inetd_wait_mode)
+if (f.inetd_wait_mode)
   {
   uschar *p = big_buffer;
 
@@ -1607,20 +2063,19 @@ if (inetd_wait_mode)
   set_process_info("daemon(%s): pre-listening socket", version_string);
 
   /* set up the timeout logic */
-  sigalrm_seen = 1;
+  sigalrm_seen = TRUE;
   }
 
-else if (daemon_listen)
+else if (f.daemon_listen)
   {
-  int i, j;
   int smtp_ports = 0;
   int smtps_ports = 0;
-  ip_address_item *ipa;
-  uschar *p = big_buffer;
-  uschar *qinfo = (queue_interval > 0)?
-    string_sprintf("-q%s", readconf_printtime(queue_interval))
-    :
-    US"no queue runs";
+  ip_address_item * ipa;
+  uschar * p;
+  uschar * qinfo = queue_interval > 0
+    ? string_sprintf("-q%s%s",
+       f.queue_2stage ? "q" : "", readconf_printtime(queue_interval))
+    US"no queue runs";
 
   /* Build a list of listening addresses in big_buffer, but limit it to 10
   items. The style is for backwards compatibility.
@@ -1629,88 +2084,154 @@ else if (daemon_listen)
   deprecated protocol that starts TLS without using STARTTLS), and others
   listening for standard SMTP. Keep their listings separate. */
 
-  for (j = 0; j < 2; j++)
+  for (int j = 0, i; j < 2; j++)
     {
-    for (i = 0, ipa = addresses; i < 10 && ipa != NULL; i++, ipa = ipa->next)
-       {
-       /* First time round, look for SMTP ports; second time round, look for
-       SMTPS ports. For the first one of each, insert leading text. */
-
-       if (host_is_tls_on_connect_port(ipa->port) == (j > 0))
-         {
-         if (j == 0)
-           {
-           if (smtp_ports++ == 0)
-             {
-             memcpy(p, "SMTP on", 8);
-             p += 7;
-             }
-           }
-         else
-           {
-           if (smtps_ports++ == 0)
-             {
-             (void)sprintf(CS p, "%sSMTPS on",
-               (smtp_ports == 0)? "":" and for ");
-             while (*p != 0) p++;
-             }
-           }
-
-         /* Now the information about the port (and sometimes interface) */
-
-         if (ipa->address[0] == ':' && ipa->address[1] == 0)
-           {
-           if (ipa->next != NULL && ipa->next->address[0] == 0 &&
-               ipa->next->port == ipa->port)
-             {
-             (void)sprintf(CS p, " port %d (IPv6 and IPv4)", ipa->port);
-             ipa = ipa->next;
-             }
-           else if (ipa->v6_include_v4)
-             (void)sprintf(CS p, " port %d (IPv6 with IPv4)", ipa->port);
-           else
-             (void)sprintf(CS p, " port %d (IPv6)", ipa->port);
-           }
-         else if (ipa->address[0] == 0)
-           (void)sprintf(CS p, " port %d (IPv4)", ipa->port);
-         else
-           (void)sprintf(CS p, " [%s]:%d", ipa->address, ipa->port);
-         while (*p != 0) p++;
-         }
-       }
-
-    if (ipa != NULL)
+    for (i = 0, ipa = addresses; i < 10 && ipa; i++, ipa = ipa->next)
       {
-      memcpy(p, " ...", 5);
-      p += 4;
+      /* First time round, look for SMTP ports; second time round, look for
+      SMTPS ports. Build IP+port strings. */
+
+      if (host_is_tls_on_connect_port(ipa->port) == (j > 0))
+       {
+       if (j == 0)
+         smtp_ports++;
+       else
+         smtps_ports++;
+
+       /* Now the information about the port (and sometimes interface) */
+
+       if (ipa->address[0] == ':' && ipa->address[1] == 0)
+         {                                             /* v6 wildcard */
+         if (ipa->next && ipa->next->address[0] == 0 &&
+             ipa->next->port == ipa->port)
+           {
+           ipa->log = string_sprintf(" port %d (IPv6 and IPv4)", ipa->port);
+           (ipa = ipa->next)->log = NULL;
+           }
+         else if (ipa->v6_include_v4)
+           ipa->log = string_sprintf(" port %d (IPv6 with IPv4)", ipa->port);
+         else
+           ipa->log = string_sprintf(" port %d (IPv6)", ipa->port);
+         }
+       else if (ipa->address[0] == 0)                  /* v4 wildcard */
+         ipa->log = string_sprintf(" port %d (IPv4)", ipa->port);
+       else                            /* check for previously-seen IP */
+         {
+         ip_address_item * i2;
+         for (i2 = addresses; i2 != ipa; i2 = i2->next)
+           if (  host_is_tls_on_connect_port(i2->port) == (j > 0)
+              && Ustrcmp(ipa->address, i2->address) == 0
+              )
+             {                         /* found; append port to list */
+             for (p = i2->log; *p; ) p++;      /* end of existing string */
+             if (*--p == '}') *p = '\0';       /* drop EOL */
+             while (isdigit(*--p)) ;           /* char before port */
+
+             i2->log = *p == ':'               /* no list yet?     { */
+               ? string_sprintf("%.*s{%s,%d}",
+                 (int)(p - i2->log + 1), i2->log, p+1, ipa->port)
+               : string_sprintf("%s,%d}", i2->log, ipa->port);
+             ipa->log = NULL;
+             break;
+             }
+         if (i2 == ipa)                /* first-time IP */
+           ipa->log = string_sprintf(" [%s]:%d", ipa->address, ipa->port);
+         }
+       }
       }
     }
 
+  p = big_buffer;
+  for (int j = 0, i; j < 2; j++)
+    {
+    /* First time round, look for SMTP ports; second time round, look for
+    SMTPS ports. For the first one of each, insert leading text. */
+
+    if (j == 0)
+      {
+      if (smtp_ports > 0)
+       p += sprintf(CS p, "SMTP on");
+      }
+    else
+      if (smtps_ports > 0)
+       p += sprintf(CS p, "%sSMTPS on",
+         smtp_ports == 0 ? "" : " and for ");
+
+    /* Now the information about the port (and sometimes interface) */
+
+    for (i = 0, ipa = addresses; i < 10 && ipa; i++, ipa = ipa->next)
+      if (host_is_tls_on_connect_port(ipa->port) == (j > 0))
+       if (ipa->log)
+         p += sprintf(CS p, "%s",  ipa->log);
+
+    if (ipa)
+      p += sprintf(CS p, " ...");
+    }
+
   log_write(0, LOG_MAIN,
     "exim %s daemon started: pid=%d, %s, listening for %s",
     version_string, getpid(), qinfo, big_buffer);
-  set_process_info("daemon(%s): %s, listening for %s", version_string, qinfo, big_buffer);
+  set_process_info("daemon(%s): %s, listening for %s",
+    version_string, qinfo, big_buffer);
   }
 
 else
   {
+  uschar * s = *queue_name
+    ? string_sprintf("-qG%s/%s", queue_name, readconf_printtime(queue_interval))
+    : string_sprintf("-q%s", readconf_printtime(queue_interval));
   log_write(0, LOG_MAIN,
-    "exim %s daemon started: pid=%d, -q%s, not listening for SMTP",
-    version_string, getpid(), readconf_printtime(queue_interval));
-  set_process_info("daemon(%s): -q%s, not listening",
-    version_string,
-    readconf_printtime(queue_interval));
+    "exim %s daemon started: pid=%d, %s, not listening for SMTP",
+    version_string, getpid(), s);
+  set_process_info("daemon(%s): %s, not listening", version_string, s);
   }
 
 /* Do any work it might be useful to amortize over our children
 (eg: compile regex) */
 
-deliver_init();
 dns_pattern_init();
+smtp_deliver_init();   /* Used for callouts */
+
+#ifndef DISABLE_DKIM
+  {
+# ifdef MEASURE_TIMING
+  struct timeval t0;
+  gettimeofday(&t0, NULL);
+# endif
+  dkim_exim_init();
+# ifdef MEASURE_TIMING
+  report_time_since(&t0, US"dkim_exim_init (delta)");
+# endif
+  }
+#endif
 
 #ifdef WITH_CONTENT_SCAN
 malware_init();
 #endif
+#ifdef SUPPORT_SPF
+spf_init();
+#endif
+#ifndef DISABLE_TLS
+tls_daemon_init();
+#endif
+
+/* Add ancillary sockets to the set for select */
+
+poll_fd_count = listen_socket_count;
+#ifndef DISABLE_TLS
+if (tls_watch_fd >= 0)
+  {
+  tls_watch_poll = &fd_polls[poll_fd_count++];
+  tls_watch_poll->fd = tls_watch_fd;
+  tls_watch_poll->events = POLLIN;
+  }
+#endif
+if (daemon_notifier_fd >= 0)
+  {
+  dnotify_poll = &fd_polls[poll_fd_count++];
+  dnotify_poll->fd = daemon_notifier_fd;
+  dnotify_poll->events = POLLIN;
+  }
 
 /* Close the log so it can be renamed and moved. In the few cases below where
 this long-running process writes to the log (always exceptional conditions), it
@@ -1724,19 +2245,19 @@ DEBUG(D_any) debug_print_ids(US"daemon running with");
 
 smtp_input = TRUE;
 
+#ifdef MEASURE_TIMING
+report_time_since(&timestamp_startup, US"daemon loop start");  /* testcase 0022 */
+#endif
+
 /* Enter the never-ending loop... */
 
 for (;;)
   {
-  #if HAVE_IPV6
-  struct sockaddr_in6 accepted;
-  #else
-  struct sockaddr_in accepted;
-  #endif
-
-  EXIM_SOCKLEN_T len;
   pid_t pid;
 
+  if (sigterm_seen)
+    daemon_die();      /* Does not return */
+
   /* This code is placed first in the loop, so that it gets obeyed at the
   start, before the first wait, for the queue-runner case, so that the first
   one can be started immediately.
@@ -1780,42 +2301,43 @@ for (;;)
         }
 
       sigalrm_seen = FALSE;
-      alarm(resignal_interval);
+      ALARM(resignal_interval);
       }
 
     else
       {
-      DEBUG(D_any) debug_printf("SIGALRM received\n");
+      DEBUG(D_any) debug_printf("%s received\n",
+#ifndef DISABLE_QUEUE_RAMP
+       *queuerun_msgid ? "qrun notification" :
+#endif
+       "SIGALRM");
 
       /* Do a full queue run in a child process, if required, unless we already
       have enough queue runners on the go. If we are not running as root, a
       re-exec is required. */
 
-      if (queue_interval > 0 &&
-         (queue_run_max <= 0 || queue_run_count < queue_run_max))
+      if (  queue_interval > 0
+         && (local_queue_run_max <= 0 || queue_run_count < local_queue_run_max))
         {
-        if ((pid = fork()) == 0)
+        if ((pid = exim_fork(US"queue-runner")) == 0)
           {
-          int sk;
-
-          DEBUG(D_any) debug_printf("Starting queue-runner: pid %d\n",
-            (int)getpid());
-
           /* Disable debugging if it's required only for the daemon process. We
           leave the above message, because it ties up with the "child ended"
           debugging messages. */
 
-          if (debug_daemon) debug_selector = 0;
+          if (f.debug_daemon) debug_selector = 0;
 
           /* Close any open listening sockets in the child */
 
-          for (sk = 0; sk < listen_socket_count; sk++)
-            (void)close(listen_sockets[sk]);
+         close_daemon_sockets(daemon_notifier_fd,
+           fd_polls, listen_socket_count);
 
           /* Reset SIGHUP and SIGCHLD in the child in both cases. */
 
           signal(SIGHUP,  SIG_DFL);
           signal(SIGCHLD, SIG_DFL);
+          signal(SIGTERM, SIG_DFL);
+          signal(SIGINT, SIG_DFL);
 
           /* Re-exec if privilege has been given up, unless deliver_drop_
           privilege is set. Reset SIGALRM before exec(). */
@@ -1824,48 +2346,71 @@ for (;;)
             {
             uschar opt[8];
             uschar *p = opt;
-            uschar *extra[5];
+            uschar *extra[7];
             int extracount = 1;
 
             signal(SIGALRM, SIG_DFL);
             *p++ = '-';
             *p++ = 'q';
-            if (queue_2stage) *p++ = 'q';
-            if (queue_run_first_delivery) *p++ = 'i';
-            if (queue_run_force) *p++ = 'f';
-            if (deliver_force_thaw) *p++ = 'f';
-            if (queue_run_local) *p++ = 'l';
+            if (  f.queue_2stage
+#ifndef DISABLE_QUEUE_RAMP
+              && !*queuerun_msgid
+#endif
+              ) *p++ = 'q';
+            if (f.queue_run_first_delivery) *p++ = 'i';
+            if (f.queue_run_force) *p++ = 'f';
+            if (f.deliver_force_thaw) *p++ = 'f';
+            if (f.queue_run_local) *p++ = 'l';
             *p = 0;
-            extra[0] = opt;
+           extra[0] = *queue_name
+             ? string_sprintf("%sG%s", opt, queue_name) : opt;
+
+#ifndef DISABLE_QUEUE_RAMP
+           if (*queuerun_msgid)
+             {
+             log_write(0, LOG_MAIN, "notify triggered queue run");
+             extra[extracount++] = queuerun_msgid;     /* Trigger only the */
+             extra[extracount++] = queuerun_msgid;     /* one message      */
+             }
+#endif
 
             /* If -R or -S were on the original command line, ensure they get
             passed on. */
 
-            if (deliver_selectstring != NULL)
+            if (deliver_selectstring)
               {
-              extra[extracount++] = deliver_selectstring_regex? US"-Rr" : US"-R";
+              extra[extracount++] = f.deliver_selectstring_regex ? US"-Rr" : US"-R";
               extra[extracount++] = deliver_selectstring;
               }
 
-            if (deliver_selectstring_sender != NULL)
+            if (deliver_selectstring_sender)
               {
-              extra[extracount++] = deliver_selectstring_sender_regex?
-                US"-Sr" : US"-S";
+              extra[extracount++] = f.deliver_selectstring_sender_regex
+               ? US"-Sr" : US"-S";
               extra[extracount++] = deliver_selectstring_sender;
               }
 
             /* Overlay this process with a new execution. */
 
-            (void)child_exec_exim(CEE_EXEC_PANIC, FALSE, NULL, TRUE, extracount,
-              extra[0], extra[1], extra[2], extra[3], extra[4]);
+            (void)child_exec_exim(CEE_EXEC_PANIC, FALSE, NULL, FALSE, extracount,
+              extra[0], extra[1], extra[2], extra[3], extra[4], extra[5], extra[6]);
 
             /* Control never returns here. */
             }
 
           /* No need to re-exec; SIGALRM remains set to the default handler */
 
-          queue_run(NULL, NULL, FALSE);
-          _exit(EXIT_SUCCESS);
+#ifndef DISABLE_QUEUE_RAMP
+         if (*queuerun_msgid)
+           {
+           log_write(0, LOG_MAIN, "notify triggered queue run");
+           f.queue_2stage = FALSE;
+           queue_run(queuerun_msgid, queuerun_msgid, FALSE);
+           }
+         else
+#endif
+           queue_run(NULL, NULL, FALSE);
+          exim_underbar_exit(EXIT_SUCCESS);
           }
 
         if (pid < 0)
@@ -1876,25 +2421,27 @@ for (;;)
           }
         else
           {
-          int i;
-          for (i = 0; i < queue_run_max; ++i)
-            {
+          for (int i = 0; i < local_queue_run_max; ++i)
             if (queue_pid_slots[i] <= 0)
               {
               queue_pid_slots[i] = pid;
               queue_run_count++;
               break;
               }
-            }
           DEBUG(D_any) debug_printf("%d queue-runner process%s running\n",
-            queue_run_count, (queue_run_count == 1)? "" : "es");
+            queue_run_count, queue_run_count == 1 ? "" : "es");
           }
         }
 
       /* Reset the alarm clock */
 
       sigalrm_seen = FALSE;
-      alarm(queue_interval);
+#ifndef DISABLE_QUEUE_RAMP
+      if (*queuerun_msgid)
+       *queuerun_msgid = 0;
+      else
+#endif
+       ALARM(queue_interval);
       }
 
     } /* sigalrm_seen */
@@ -1909,19 +2456,10 @@ for (;;)
   new OS. In fact, the later addition of listening on specific interfaces only
   requires this way of working anyway. */
 
-  if (daemon_listen)
+  if (f.daemon_listen)
     {
-    int sk, lcount, select_errno;
-    int max_socket = 0;
+    int lcount;
     BOOL select_failed = FALSE;
-    fd_set select_listen;
-
-    FD_ZERO(&select_listen);
-    for (sk = 0; sk < listen_socket_count; sk++)
-      {
-      FD_SET(listen_sockets[sk], &select_listen);
-      if (listen_sockets[sk] > max_socket) max_socket = listen_sockets[sk];
-      }
 
     DEBUG(D_any) debug_printf("Listening...\n");
 
@@ -1938,10 +2476,7 @@ for (;;)
       errno = EINTR;
       }
     else
-      {
-      lcount = select(max_socket + 1, (SELECT_ARG2_TYPE *)&select_listen,
-        NULL, NULL, NULL);
-      }
+      lcount = poll(fd_polls, poll_fd_count, -1);
 
     if (lcount < 0)
       {
@@ -1956,9 +2491,23 @@ for (;;)
     old one had just finished. Preserve the errno from any select() failure for
     the use of the common select/accept error processing below. */
 
-    select_errno = errno;
-    handle_ending_processes();
-    errno = select_errno;
+      {
+      int select_errno = errno;
+      handle_ending_processes();
+
+#ifndef DISABLE_TLS
+      {
+      int old_tfd;
+      /* Create or rotate any required keys; handle (delayed) filewatch event */
+
+      if ((old_tfd = tls_daemon_tick()) >= 0)
+       for (struct pollfd * p = &fd_polls[listen_socket_count];
+            p < fd_polls + poll_fd_count; p++)
+         if (p->fd == old_tfd) { p->fd = tls_watch_fd ; break; }
+      }
+#endif
+      errno = select_errno;
+      }
 
     /* Loop for all the sockets that are currently ready to go. If select
     actually failed, we have set the count to 1 and select_failed=TRUE, so as
@@ -1967,20 +2516,60 @@ for (;;)
     while (lcount-- > 0)
       {
       int accept_socket = -1;
+#if HAVE_IPV6
+      struct sockaddr_in6 accepted;
+#else
+      struct sockaddr_in accepted;
+#endif
+
       if (!select_failed)
-        {
-        for (sk = 0; sk < listen_socket_count; sk++)
-          {
-          if (FD_ISSET(listen_sockets[sk], &select_listen))
+       {
+#if !defined(DISABLE_TLS) && (defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT))
+       if (tls_watch_poll && tls_watch_poll->revents & POLLIN)
+         {
+         tls_watch_poll->revents = 0;
+          tls_watch_trigger_time = time(NULL); /* Set up delayed event */
+         tls_watch_discard_event(tls_watch_fd);
+         break;        /* to top of daemon loop */
+         }
+#endif
+       if (dnotify_poll && dnotify_poll->revents & POLLIN)
+         {
+         dnotify_poll->revents = 0;
+         sigalrm_seen = daemon_notification();
+         break;        /* to top of daemon loop */
+         }
+       for (struct pollfd * p = fd_polls; p < fd_polls + listen_socket_count;
+            p++)
+         if (p->revents & POLLIN)
             {
-            len = sizeof(accepted);
-            accept_socket = accept(listen_sockets[sk],
-              (struct sockaddr *)&accepted, &len);
-            FD_CLR(listen_sockets[sk], &select_listen);
+           EXIM_SOCKLEN_T alen = sizeof(accepted);
+#ifdef TCP_INFO
+           struct tcp_info ti;
+           socklen_t tlen = sizeof(ti);
+
+           /* If monitoring the backlog is wanted, grab for later logging */
+
+           smtp_listen_backlog = 0;
+           if (  smtp_backlog_monitor > 0
+              && getsockopt(p->fd, IPPROTO_TCP, TCP_INFO, &ti, &tlen) == 0)
+             {
+# ifdef EXIM_HAVE_TCPI_UNACKED
+             DEBUG(D_interface) debug_printf("listen fd %d queue max %u curr %u\n",
+                     p->fd, ti.tcpi_sacked, ti.tcpi_unacked);
+             smtp_listen_backlog = ti.tcpi_unacked;
+# elif defined(__FreeBSD__)    /* This does not work. Investigate kernel sourcecode. */
+             DEBUG(D_interface) debug_printf("listen fd %d queue max %u curr %u\n",
+                     p->fd, ti.__tcpi_sacked, ti.__tcpi_unacked);
+             smtp_listen_backlog = ti.__tcpi_unacked;
+# endif
+             }
+#endif
+           p->revents = 0;
+            accept_socket = accept(p->fd, (struct sockaddr *)&accepted, &alen);
             break;
             }
-          }
-        }
+       }
 
       /* If select or accept has failed and this was not caused by an
       interruption, log the incident and try again. With asymmetric TCP/IP
@@ -1999,48 +2588,46 @@ for (;;)
           accept_retry_errno = errno;
           accept_retry_select_failed = select_failed;
           }
-        else
-          {
-          if (errno != accept_retry_errno ||
-              select_failed != accept_retry_select_failed ||
-              accept_retry_count >= 50)
-            {
-            log_write(0, LOG_MAIN | ((accept_retry_count >= 50)? LOG_PANIC : 0),
-              "%d %s() failure%s: %s",
-              accept_retry_count,
-              accept_retry_select_failed? "select" : "accept",
-              (accept_retry_count == 1)? "" : "s",
-              strerror(accept_retry_errno));
-            log_close_all();
-            accept_retry_count = 0;
-            accept_retry_errno = errno;
-            accept_retry_select_failed = select_failed;
-            }
-          }
+        else if (  errno != accept_retry_errno
+               || select_failed != accept_retry_select_failed
+               || accept_retry_count >= 50)
+         {
+         log_write(0, LOG_MAIN | (accept_retry_count >= 50 ? LOG_PANIC : 0),
+           "%d %s() failure%s: %s",
+           accept_retry_count,
+           accept_retry_select_failed ? "select" : "accept",
+           accept_retry_count == 1 ? "" : "s",
+           strerror(accept_retry_errno));
+         log_close_all();
+         accept_retry_count = 0;
+         accept_retry_errno = errno;
+         accept_retry_select_failed = select_failed;
+         }
         accept_retry_count++;
         }
-
-      else
-        {
-        if (accept_retry_count > 0)
-          {
-          log_write(0, LOG_MAIN, "%d %s() failure%s: %s",
-            accept_retry_count,
-            accept_retry_select_failed? "select" : "accept",
-            (accept_retry_count == 1)? "" : "s",
-            strerror(accept_retry_errno));
-          log_close_all();
-          accept_retry_count = 0;
-          }
-        }
+      else if (accept_retry_count > 0)
+       {
+       log_write(0, LOG_MAIN, "%d %s() failure%s: %s",
+         accept_retry_count,
+         accept_retry_select_failed ? "select" : "accept",
+         accept_retry_count == 1 ? "" : "s",
+         strerror(accept_retry_errno));
+       log_close_all();
+       accept_retry_count = 0;
+       }
 
       /* If select/accept succeeded, deal with the connection. */
 
       if (accept_socket >= 0)
         {
+#ifdef TCP_QUICKACK /* Avoid pure-ACKs while in tls protocol pingpong phase */
+       /* Unfortunately we cannot be certain to do this before a TLS-on-connect
+       Client Hello arrives and is acked. We do it as early as possible. */
+       (void) setsockopt(accept_socket, IPPROTO_TCP, TCP_QUICKACK, US &off, sizeof(off));
+#endif
         if (inetd_wait_timeout)
           last_connection_time = time(NULL);
-        handle_smtp_call(listen_sockets, listen_socket_count, accept_socket,
+        handle_smtp_call(fd_polls, listen_socket_count, accept_socket,
           (struct sockaddr *)&accepted);
         }
       }
@@ -2055,10 +2642,8 @@ for (;;)
 
   else
     {
-    struct timeval tv;
-    tv.tv_sec = queue_interval;
-    tv.tv_usec = 0;
-    select(0, NULL, NULL, NULL, &tv);
+    struct pollfd p;
+    poll(&p, 0, queue_interval * 1000);
     handle_ending_processes();
     }
 
@@ -2081,12 +2666,10 @@ for (;;)
 
   if (sighup_seen)
     {
-    int sk;
     log_write(0, LOG_MAIN, "pid %d: SIGHUP received: re-exec daemon",
       getpid());
-    for (sk = 0; sk < listen_socket_count; sk++)
-      (void)close(listen_sockets[sk]);
-    alarm(0);
+    close_daemon_sockets(daemon_notifier_fd, fd_polls, listen_socket_count);
+    ALARM_CLR(0);
     signal(SIGHUP, SIG_IGN);
     sighup_argv[0] = exim_path;
     exim_nullstd();