tidying
[exim.git] / src / src / daemon.c
index 50c202c56f9f278c98b884ee987c1cf027ef663a..88114d648122193bcf11e465813a56737515a89c 100644 (file)
@@ -2,9 +2,10 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
+/* Copyright (c) The Exim Maintainers 2020 - 2023 */
 /* Copyright (c) University of Cambridge 1995 - 2018 */
 /* Copyright (c) University of Cambridge 1995 - 2018 */
-/* Copyright (c) The Exim Maintainers 2020 */
 /* See the file NOTICE for conditions of use and distribution. */
 /* See the file NOTICE for conditions of use and distribution. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 
 /* Functions concerned with running Exim as a daemon */
 
 
 /* Functions concerned with running Exim as a daemon */
 
 /* Structure for holding data for each SMTP connection */
 
 typedef struct smtp_slot {
 /* Structure for holding data for each SMTP connection */
 
 typedef struct smtp_slot {
-  pid_t pid;                       /* pid of the spawned reception process */
-  uschar *host_address;            /* address of the client host */
+  pid_t                pid;            /* pid of the spawned reception process */
+  uschar *     host_address;   /* address of the client host */
 } smtp_slot;
 
 } smtp_slot;
 
+typedef struct runner_slot {
+  pid_t                pid;            /* pid of spawned queue-runner process */
+  const uschar *queue_name;    /* pointer to the name in the qrunner struct */
+} runner_slot;
+
 /* An empty slot for initializing (Standard C does not allow constructor
 expressions in assignments except as initializers in declarations). */
 
 static smtp_slot empty_smtp_slot = { .pid = 0, .host_address = NULL };
 
 /* An empty slot for initializing (Standard C does not allow constructor
 expressions in assignments except as initializers in declarations). */
 
 static smtp_slot empty_smtp_slot = { .pid = 0, .host_address = NULL };
 
-
-
 /*************************************************
 *               Local static variables           *
 *************************************************/
 /*************************************************
 *               Local static variables           *
 *************************************************/
@@ -38,12 +42,17 @@ static int   accept_retry_count = 0;
 static int   accept_retry_errno;
 static BOOL  accept_retry_select_failed;
 
 static int   accept_retry_errno;
 static BOOL  accept_retry_select_failed;
 
-static int   queue_run_count = 0;
-static pid_t *queue_pid_slots = NULL;
-static smtp_slot *smtp_slots = NULL;
+static int   queue_run_count = 0;      /* current runners */
+
+static unsigned queue_runner_slot_count = 0;
+static runner_slot * queue_runner_slots = NULL;
+static smtp_slot * smtp_slots = NULL;
 
 static BOOL  write_pid = TRUE;
 
 
 static BOOL  write_pid = TRUE;
 
+#ifndef EXIM_HAVE_ABSTRACT_UNIX_SOCKETS
+static uschar * notifier_socket_name;
+#endif
 
 
 /*************************************************
 
 
 /*************************************************
@@ -87,7 +96,7 @@ sigchld_seen = TRUE;
 }
 
 
 }
 
 
-/* SIGTERM handler.  Try to get the damon pif file removed
+/* SIGTERM handler.  Try to get the daemon pid file removed
 before exiting. */
 
 static void
 before exiting. */
 
 static void
@@ -119,7 +128,7 @@ never_error(uschar *log_msg, uschar *smtp_msg, int 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);
 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) smtp_printf("421 %s\r\n", FALSE, smtp_msg);
+if (smtp_out) smtp_printf("421 %s\r\n", SP_NO_MORE, smtp_msg);
 }
 
 
 }
 
 
@@ -128,31 +137,27 @@ if (smtp_out) smtp_printf("421 %s\r\n", FALSE, smtp_msg);
 /*************************************************
 *************************************************/
 
 /*************************************************
 *************************************************/
 
-#ifndef EXIM_HAVE_ABSTRACT_UNIX_SOCKETS
 static void
 unlink_notifier_socket(void)
 {
 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);
-}
+#ifndef EXIM_HAVE_ABSTRACT_UNIX_SOCKETS
+DEBUG(D_any) debug_printf("unlinking notifier socket %s\n", notifier_socket_name);
+Uunlink(notifier_socket_name);
 #endif
 #endif
+}
 
 
 static void
 close_daemon_sockets(int daemon_notifier_fd,
 
 
 static void
 close_daemon_sockets(int daemon_notifier_fd,
-  int * listen_sockets, int listen_socket_count)
+  struct pollfd * fd_polls, int listen_socket_count)
 {
 if (daemon_notifier_fd >= 0)
   {
   (void) close(daemon_notifier_fd);
   daemon_notifier_fd = -1;
 {
 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(listen_sockets[i]);
+for (int i = 0; i < listen_socket_count; i++) (void) close(fd_polls[i].fd);
 }
 
 
 }
 
 
@@ -167,7 +172,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:
 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
   listen_socket_count   count of listening sockets
   accept_socket         socket of the current accepted call
   accepted              socket information about the current call
@@ -176,7 +181,7 @@ Returns:            nothing
 */
 
 static void
 */
 
 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;
   int accept_socket, struct sockaddr *accepted)
 {
 pid_t pid;
@@ -228,7 +233,7 @@ if (getsockname(accept_socket, (struct sockaddr *)(&interface_sockaddr),
   {
   log_write(0, LOG_MAIN | ((errno == ECONNRESET)? 0 : LOG_PANIC),
     "getsockname() failed: %s", strerror(errno));
   {
   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", FALSE);
+  smtp_printf("421 Local problem: getsockname() failed; please try again later\r\n", SP_NO_MORE);
   goto ERROR_RETURN;
   }
 
   goto ERROR_RETURN;
   }
 
@@ -249,8 +254,6 @@ if (LOGGING(incoming_interface))
   whofrom = string_fmt_append(whofrom, " I=[%s]:%d",
     interface_address, interface_port);
 
   whofrom = string_fmt_append(whofrom, " I=[%s]:%d",
     interface_address, interface_port);
 
-(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
 it might take some time. */
 /* Check maximum number of connections. We do not check for reserved
 connections or unacceptable hosts here. That is done in the subprocess because
 it might take some time. */
@@ -260,10 +263,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; "
   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", FALSE);
+    "please try again later.\r\n", SP_NO_MORE);
   log_write(L_connection_reject,
   log_write(L_connection_reject,
-            LOG_MAIN, "Connection from %s refused: too many connections",
-    whofrom->s);
+            LOG_MAIN, "Connection from %Y refused: too many connections",
+    whofrom);
   goto ERROR_RETURN;
   }
 
   goto ERROR_RETURN;
   }
 
@@ -275,14 +278,14 @@ subprocess because it might take time. */
 if (smtp_load_reserve >= 0)
   {
   load_average = OS_GETLOADAVG();
 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);
     {
     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", FALSE);
+    smtp_printf("421 Too much load; please try again later.\r\n", SP_NO_MORE);
     log_write(L_connection_reject,
     log_write(L_connection_reject,
-              LOG_MAIN, "Connection from %s refused: load average = %.2f",
-      whofrom->s, (double)load_average/1000.0);
+              LOG_MAIN, "Connection from %Y refused: load average = %.2f",
+      whofrom, (double)load_average/1000.0);
     goto ERROR_RETURN;
     }
   }
     goto ERROR_RETURN;
     }
   }
@@ -295,14 +298,15 @@ 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. */
 
 this is in the daemon mainline, only fast expansions (such as inline address
 checks) should be used. The documentation is full of warnings. */
 
+GET_OPTION("smtp_accept_max_per_host");
 if (smtp_accept_max_per_host)
   {
 if (smtp_accept_max_per_host)
   {
-  uschar *expanded = expand_string(smtp_accept_max_per_host);
+  uschar * expanded = expand_string(smtp_accept_max_per_host);
   if (!expanded)
     {
     if (!f.expand_string_forcedfail)
       log_write(0, LOG_MAIN|LOG_PANIC, "expansion of smtp_accept_max_per_host "
   if (!expanded)
     {
     if (!f.expand_string_forcedfail)
       log_write(0, LOG_MAIN|LOG_PANIC, "expansion of smtp_accept_max_per_host "
-        "failed for %s: %s", whofrom->s, expand_string_message);
+        "failed for %Y: %s", whofrom, expand_string_message);
     }
   /* For speed, interpret a decimal number inline here */
   else
     }
   /* For speed, interpret a decimal number inline here */
   else
@@ -312,7 +316,7 @@ if (smtp_accept_max_per_host)
       max_for_this_host = max_for_this_host * 10 + *s++ - '0';
     if (*s)
       log_write(0, LOG_MAIN|LOG_PANIC, "expansion of smtp_accept_max_per_host "
       max_for_this_host = max_for_this_host * 10 + *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->s, expanded);
+        "for %Y contains non-digit: %s", whofrom, expanded);
     }
   }
 
     }
   }
 
@@ -348,40 +352,17 @@ if (max_for_this_host > 0 && smtp_accept_count >= max_for_this_host)
       "IP address: count=%d max=%d\n",
       host_accept_count, max_for_this_host);
     smtp_printf("421 Too many concurrent SMTP connections "
       "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", FALSE);
+      "from this IP address; please try again later.\r\n", SP_NO_MORE);
     log_write(L_connection_reject,
     log_write(L_connection_reject,
-              LOG_MAIN, "Connection from %s refused: too many connections "
-      "from that IP address", whofrom->s);
+              LOG_MAIN, "Connection from %Y refused: too many connections "
+      "from that IP address", whofrom);
     search_tidyup();
     goto ERROR_RETURN;
     }
   }
 
     search_tidyup();
     goto ERROR_RETURN;
     }
   }
 
-/* OK, the connection count checks have been passed. Before we can fork the
-accepting process, we must first log the connection if requested. This logging
-used to happen in the subprocess, but doing that means that the value of
-smtp_accept_count can be out of step by the time it is logged. So we have to do
-the logging here and accept the performance cost. Note that smtp_accept_count
-hasn't yet been incremented to take account of this connection.
-
-In order to minimize the cost (because this is going to happen for every
-connection), do a preliminary selector test here. This saves ploughing through
-the generalized logging code each time when the selector is false. If the
-selector is set, check whether the host is on the list for logging. If not,
-arrange to unset the selector in the subprocess. */
-
-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)
-    save_log_selector &= ~L_smtp_connection;
-  else
-    log_write(L_smtp_connection, LOG_MAIN, "SMTP connection from %s "
-      "(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
+/* OK, the connection count checks have been passed.
+Now we can fork the accepting process; do a lookup tidy, just in case any
 expansion above did a lookup. */
 
 search_tidyup();
 expansion above did a lookup. */
 
 search_tidyup();
@@ -396,11 +377,44 @@ if (pid == 0)
   int save_debug_selector = debug_selector;
   BOOL local_queue_only;
   BOOL session_local_queue_only;
   int save_debug_selector = debug_selector;
   BOOL local_queue_only;
   BOOL session_local_queue_only;
-  #ifdef SA_NOCLDWAIT
+#ifdef SA_NOCLDWAIT
   struct sigaction act;
   struct sigaction act;
-  #endif
+#endif
 
   smtp_accept_count++;    /* So that it includes this process */
 
   smtp_accept_count++;    /* So that it includes this process */
+  connection_id = getpid();
+
+  /* Log the connection if requested.
+  In order to minimize the cost (because this is going to happen for every
+  connection), do a preliminary selector test here. This saves ploughing through
+  the generalized logging code each time when the selector is false. If the
+  selector is set, check whether the host is on the list for logging. If not,
+  arrange to unset the selector in the subprocess.
+
+  jgh 2023/08/08 :- moved this logging in from the parent process, just
+  pre-fork.  There was a claim back from 2004 that smtp_accept_count could have
+  become out-of-date by the time the child could log it, and I can't see how
+  that could happen. */
+
+  if (LOGGING(smtp_connection))
+    {
+    uschar * list = hosts_connection_nolog;
+    memset(sender_host_cache, 0, sizeof(sender_host_cache));
+    if (list && verify_check_host(&list) == OK)
+      save_log_selector &= ~L_smtp_connection;
+    else if (LOGGING(connection_id))
+      log_write(L_smtp_connection, LOG_MAIN, "SMTP connection from %Y "
+       "Ci=%lu (TCP/IP connection count = %d)", whofrom, connection_id, smtp_accept_count);
+    else
+      log_write(L_smtp_connection, LOG_MAIN, "SMTP connection from %Y "
+       "(TCP/IP connection count = %d)", whofrom, smtp_accept_count);
+    }
+
+  /* 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 */
 
 
   /* May have been modified for the subprocess */
 
@@ -421,6 +435,7 @@ if (pid == 0)
   likely what it depends on.) */
 
   smtp_active_hostname = primary_hostname;
   likely what it depends on.) */
 
   smtp_active_hostname = primary_hostname;
+  GET_OPTION("smtp_active_hostname");
   if (raw_active_hostname)
     {
     uschar * nah = expand_string(raw_active_hostname);
   if (raw_active_hostname)
     {
     uschar * nah = expand_string(raw_active_hostname);
@@ -432,7 +447,7 @@ if (pid == 0)
           "(smtp_active_hostname): %s", raw_active_hostname,
           expand_string_message);
         smtp_printf("421 Local configuration error; "
           "(smtp_active_hostname): %s", raw_active_hostname,
           expand_string_message);
         smtp_printf("421 Local configuration error; "
-          "please try again later.\r\n", FALSE);
+          "please try again later.\r\n", SP_NO_MORE);
         mac_smtp_fflush();
         search_tidyup();
         exim_underbar_exit(EXIT_FAILURE);
         mac_smtp_fflush();
         search_tidyup();
         exim_underbar_exit(EXIT_FAILURE);
@@ -453,7 +468,7 @@ if (pid == 0)
   extensive comment before the reception loop in exim.c for a fuller
   explanation of this logic. */
 
   extensive comment before the reception loop in exim.c for a fuller
   explanation of this logic. */
 
-  close_daemon_sockets(daemon_notifier_fd, listen_sockets, listen_socket_count);
+  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. */
 
   /* 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. */
@@ -462,15 +477,16 @@ if (pid == 0)
   (void)fcntl(dup_accept_socket, F_SETFD,
               fcntl(dup_accept_socket, F_GETFD) | FD_CLOEXEC);
 
   (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);
   act.sa_handler = SIG_IGN;
   sigemptyset(&(act.sa_mask));
   act.sa_flags = SA_NOCLDWAIT;
   sigaction(SIGCHLD, &act, NULL);
-  #else
+#else
   signal(SIGCHLD, SIG_IGN);
   signal(SIGCHLD, SIG_IGN);
-  #endif
+#endif
   signal(SIGTERM, SIG_DFL);
   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
 
   /* 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
@@ -552,9 +568,9 @@ if (pid == 0)
         smtp_log_no_mail();               /* Log no mail if configured */
         exim_underbar_exit(EXIT_SUCCESS);
         }
         smtp_log_no_mail();               /* Log no mail if configured */
         exim_underbar_exit(EXIT_SUCCESS);
         }
-      if (message_id[0] == 0) continue;   /* No message was accepted */
+      if (!message_id[0]) continue;    /* No message was accepted */
       }
       }
-    else
+    else                               /* bad smtp_setup_msg() */
       {
       if (smtp_out)
        {
       {
       if (smtp_out)
        {
@@ -674,16 +690,17 @@ if (pid == 0)
       {
       pid_t dpid;
 
       {
       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. */
-
-      mac_smtp_fflush();
+      /* 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. */
 
       if ((dpid = exim_fork(US"daemon-accept-delivery")) == 0)
         {
         (void)fclose(smtp_in);
 
       if ((dpid = exim_fork(US"daemon-accept-delivery")) == 0)
         {
         (void)fclose(smtp_in);
+       (void)close(fileno(smtp_out));
         (void)fclose(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. */
 
         /* Don't ever molest the parent's SSL connection, but do clean up
         the data structures if necessary. */
@@ -697,6 +714,7 @@ if (pid == 0)
         signal(SIGHUP,  SIG_DFL);
         signal(SIGCHLD, SIG_DFL);
         signal(SIGTERM, SIG_DFL);
         signal(SIGHUP,  SIG_DFL);
         signal(SIGCHLD, SIG_DFL);
         signal(SIGTERM, SIG_DFL);
+        signal(SIGINT, SIG_DFL);
 
         if (geteuid() != root_uid && !deliver_drop_privilege)
           {
 
         if (geteuid() != root_uid && !deliver_drop_privilege)
           {
@@ -784,10 +802,8 @@ else (void)close(dup_accept_socket);
 the incoming host address and an expanded active_hostname. */
 
 log_close_all();
 the incoming host address and an expanded active_hostname. */
 
 log_close_all();
-interface_address =
-sender_host_address = NULL;
+interface_address = sender_host_name = sender_host_address = NULL;
 store_reset(reset_point);
 store_reset(reset_point);
-sender_host_address = NULL;
 }
 
 
 }
 
 
@@ -913,24 +929,34 @@ while ((pid = waitpid(-1, &status, WNOHANG)) > 0)
   /* If it wasn't an accepting process, see if it was a queue-runner
   process that we are tracking. */
 
   /* If it wasn't an accepting process, see if it was a queue-runner
   process that we are tracking. */
 
-  if (queue_pid_slots)
-    {
-    int max = atoi(CS expand_string(queue_run_max));
-    for (int i = 0; i < max; i++)
-      if (queue_pid_slots[i] == pid)
+  if (queue_runner_slots)
+    for (unsigned i = 0; i < queue_runner_slot_count; i++)
+      {
+      runner_slot * r = queue_runner_slots + i;
+      if (r->pid == pid)
         {
         {
-        queue_pid_slots[i] = 0;
+        r->pid = 0;                    /* free up the slot */
+
         if (--queue_run_count < 0) queue_run_count = 0;
         DEBUG(D_any) debug_printf("%d queue-runner process%s now running\n",
         if (--queue_run_count < 0) queue_run_count = 0;
         DEBUG(D_any) debug_printf("%d queue-runner process%s now running\n",
-          queue_run_count, (queue_run_count == 1)? "" : "es");
+          queue_run_count, queue_run_count == 1 ? "" : "es");
+
+       for (qrunner ** p = &qrunners, * q = qrunners; q; p = &q->next, q = *p)
+         if (q->name == r->queue_name)
+           {
+           if (q->interval)            /* a periodic queue run */
+             q->run_count--;
+           else                        /* a one-time run */
+             *p = q->next;             /* drop this qrunner */
+           break;
+           }
         break;
         }
         break;
         }
-    }
+      }
   }
 }
 
 
   }
 }
 
 
-
 static void
 set_pid_file_path(void)
 {
 static void
 set_pid_file_path(void)
 {
@@ -939,37 +965,144 @@ if (override_pid_file_path)
 
 if (!*pid_file_path)
   pid_file_path = string_sprintf("%s/exim-daemon.pid", spool_directory);
 
 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);
 }
 
 
 }
 
 
-/* Remove the daemon's pidfile.  Note: runs with root privilege,
-as a direct child of the daemon.  Does not return. */
+enum pid_op { PID_WRITE, PID_CHECK, PID_DELETE };
 
 
-void
-delete_pid_file(void)
+/* 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)
 {
 {
-uschar * daemon_pid = string_sprintf("%d\n", (int)getppid());
-FILE * f;
+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();
 
 set_pid_file_path();
-if ((f = Ufopen(pid_file_path, "rb")))
+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 (  fgets(CS big_buffer, big_buffer_size, f)
-       && Ustrcmp(daemon_pid, big_buffer) == 0
-     )
-    if (Uunlink(pid_file_path) == 0)
+  if (!lines_match)
+    {
+    if (base_fd >= 0)
       {
       {
-      DEBUG(D_any)
-       debug_printf("%s unlink: %s\n", pid_file_path, strerror(errno));
-      }
-    else
-      DEBUG(D_any)
-       debug_printf("unlinked %s\n", pid_file_path);
-  fclose(f);
+      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
   }
 else
-  DEBUG(D_any)
-    debug_printf("%s\n", string_open_failed("pid file %s", pid_file_path));
+  {
+  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);
 }
 
 exim_exit(EXIT_SUCCESS);
 }
 
@@ -982,7 +1115,7 @@ daemon_die(void)
 {
 int pid;
 
 {
 int pid;
 
-DEBUG(D_any) debug_printf("SIGTERM seen\n");
+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 !defined(DISABLE_TLS) && (defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT))
 tls_watch_invalidate();
 #endif
@@ -991,9 +1124,7 @@ if (daemon_notifier_fd >= 0)
   {
   close(daemon_notifier_fd);
   daemon_notifier_fd = -1;
   {
   close(daemon_notifier_fd);
   daemon_notifier_fd = -1;
-#ifndef EXIM_HAVE_ABSTRACT_UNIX_SOCKETS
   unlink_notifier_socket();
   unlink_notifier_socket();
-#endif
   }
 
 if (f.running_in_test_harness || write_pid)
   }
 
 if (f.running_in_test_harness || write_pid)
@@ -1019,15 +1150,47 @@ exim_exit(EXIT_SUCCESS);
 *      Listener socket for local work prompts   *
 *************************************************/
 
 *      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", CS *sname);
+#endif
+}
+
+ssize_t
+daemon_notifier_sockname(struct sockaddr_un * sup)
+{
+GET_OPTION("notifier_socket");
+#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",
+              CS expand_string(notifier_socket));
+#else
+notifier_socket_name = expand_string(notifier_socket);
+return offsetof(struct sockaddr_un, sun_path)
+  + snprintf(sup->sun_path, sizeof(sup->sun_path), "%s",
+              CS notifier_socket_name);
+#endif
+}
+
+
 static void
 daemon_notifier_socket(void)
 {
 int fd;
 const uschar * where;
 struct sockaddr_un sa_un = {.sun_family = AF_UNIX};
 static void
 daemon_notifier_socket(void)
 {
 int fd;
 const uschar * where;
 struct sockaddr_un sa_un = {.sun_family = AF_UNIX};
-int len;
+ssize_t len;
 
 
-if (!notifier_socket || !*notifier_socket)
+if (!f.notifier_socket_en)
   {
   DEBUG(D_any) debug_printf("-oY used so not creating notifier socket\n");
   return;
   {
   DEBUG(D_any) debug_printf("-oY used so not creating notifier socket\n");
   return;
@@ -1038,6 +1201,11 @@ if (override_local_interfaces && !override_pid_file_path)
     debug_printf("-oX used without -oP so not creating notifier socket\n");
   return;
   }
     debug_printf("-oX used without -oP so not creating notifier socket\n");
   return;
   }
+if (!notifier_socket || !*notifier_socket)
+  {
+  DEBUG(D_any) debug_printf("no name for notifier socket\n");
+  return;
+  }
 
 DEBUG(D_any) debug_printf("creating notifier socket\n");
 
 
 DEBUG(D_any) debug_printf("creating notifier socket\n");
 
@@ -1050,20 +1218,15 @@ if ((fd = socket(PF_UNIX, SOCK_DGRAM, 0)) < 0)
 (void)fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
 #endif
 
 (void)fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
 #endif
 
+len = daemon_notifier_sockname(&sa_un);
+
 #ifdef EXIM_HAVE_ABSTRACT_UNIX_SOCKETS
 #ifdef EXIM_HAVE_ABSTRACT_UNIX_SOCKETS
-sa_un.sun_path[0] = 0; /* Abstract local socket addr - Linux-specific? */
-len = offsetof(struct sockaddr_un, sun_path) + 1
-  + snprintf(sa_un.sun_path+1, sizeof(sa_un.sun_path)-1, "%s",
-             expand_string(notifier_socket));
 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+1);
 #else                  /* filesystem-visible and persistent; will neeed removal */
-len = offsetof(struct sockaddr_un, sun_path)
-  + snprintf(sa_un.sun_path, sizeof(sa_un.sun_path), "%s", 
-             expand_string(notifier_socket));
 DEBUG(D_any) debug_printf(" %s\n", sa_un.sun_path);
 #endif
 
 DEBUG(D_any) debug_printf(" %s\n", sa_un.sun_path);
 #endif
 
-if (bind(fd, (const struct sockaddr *)&sa_un, len) < 0)
+if (bind(fd, (const struct sockaddr *)&sa_un, (socklen_t)len) < 0)
   { where = US"bind"; goto bad; }
 
 #ifdef SO_PASSCRED             /* Linux */
   { where = US"bind"; goto bad; }
 
 #ifdef SO_PASSCRED             /* Linux */
@@ -1090,10 +1253,17 @@ bad:
 }
 
 
 }
 
 
+/* Data for notifier-triggered queue runs */
+
 static uschar queuerun_msgid[MESSAGE_ID_LENGTH+1];
 static uschar queuerun_msgid[MESSAGE_ID_LENGTH+1];
+static const uschar * queuerun_msg_qname;
 
 
-/* Return TRUE if a sigalrm should be emulated */
-static BOOL
+
+/* The notifier socket has something to read. Pull the message from it, decode
+and do the action.
+*/
+
+static void
 daemon_notification(void)
 {
 uschar buf[256], cbuf[256];
 daemon_notification(void)
 {
 uschar buf[256], cbuf[256];
@@ -1109,16 +1279,27 @@ struct msghdr msg = { .msg_name = &sa_un,
 ssize_t sz;
 
 buf[sizeof(buf)-1] = 0;
 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;
+if ((sz = recvmsg(daemon_notifier_fd, &msg, 0)) <= 0) return;
+if (sz >= sizeof(buf)) return;
 
 #ifdef notdef
 debug_printf("addrlen %d\n", msg.msg_namelen);
 #endif
 
 #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));
+DEBUG(D_queue_run)
+  if (msg.msg_namelen > 0)
+    {
+    BOOL abstract = !*sa_un.sun_path;
+    char * name = sa_un.sun_path + (abstract ? 1 : 0);
+    int namelen =  (int)msg.msg_namelen - abstract ? 1 : 0;
+    if (*name)
+      debug_printf("%s from addr '%s%.*s'\n", __FUNCTION__,
+       abstract ? "@" : "",
+       namelen, name);
+    else
+      debug_printf("%s (from unknown addr)\n", __FUNCTION__);
+    }
+  else
+    debug_printf("%s (from unknown addr)\n", __FUNCTION__);
 
 /* Refuse to handle the item unless the peer has good credentials */
 #ifdef SCM_CREDENTIALS
 
 /* Refuse to handle the item unless the peer has good credentials */
 #ifdef SCM_CREDENTIALS
@@ -1142,7 +1323,6 @@ for (struct cmsghdr * cp = CMSG_FIRSTHDR(&msg);
     {
     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);
     {
     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);
-    return FALSE;
     }
 # elif defined(LOCAL_CREDS)                            /* BSD-ish */
   struct sockcred * cr = (struct sockcred *) CMSG_DATA(cp);
     }
 # elif defined(LOCAL_CREDS)                            /* BSD-ish */
   struct sockcred * cr = (struct sockcred *) CMSG_DATA(cp);
@@ -1150,7 +1330,6 @@ for (struct cmsghdr * cp = CMSG_FIRSTHDR(&msg);
     {
     DEBUG(D_queue_run) debug_printf("%s: sender creds pid ??? uid %d gid %d\n",
       __FUNCTION__, (int)cr->sc_uid, (int)cr->sc_gid);
     {
     DEBUG(D_queue_run) debug_printf("%s: sender creds pid ??? uid %d gid %d\n",
       __FUNCTION__, (int)cr->sc_uid, (int)cr->sc_gid);
-    return FALSE;
     }
 # endif
   break;
     }
 # endif
   break;
@@ -1165,8 +1344,16 @@ switch (buf[0])
     /* this should be a message_id */
     DEBUG(D_queue_run)
       debug_printf("%s: qrunner trigger: %s\n", __FUNCTION__, buf+1);
     /* 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);
     memcpy(queuerun_msgid, buf+1, MESSAGE_ID_LENGTH+1);
-    return TRUE;
+
+    for (qrunner * q = qrunners; q; q = q->next)
+      if (q->name
+         ? Ustrcmp(q->name, buf+1+MESSAGE_ID_LENGTH+1) == 0
+         : !buf[1+MESSAGE_ID_LENGTH+1]
+        )
+       { queuerun_msg_qname = q->name; break; }
+    return;
 #endif
 
   case NOTIFY_QUEUE_SIZE_REQ:
 #endif
 
   case NOTIFY_QUEUE_SIZE_REQ:
@@ -1181,10 +1368,313 @@ switch (buf[0])
                (const struct sockaddr *)&sa_un, msg.msg_namelen) < 0)
       log_write(0, LOG_MAIN|LOG_PANIC,
        "%s: sendto: %s\n", __FUNCTION__, strerror(errno));
                (const struct sockaddr *)&sa_un, msg.msg_namelen) < 0)
       log_write(0, LOG_MAIN|LOG_PANIC,
        "%s: sendto: %s\n", __FUNCTION__, strerror(errno));
-    return FALSE;
+    break;
     }
     }
+
+  case NOTIFY_REGEX:
+    regex_at_daemon(buf);
+    break;
   }
   }
-return FALSE;
+return;
+}
+
+
+
+static void
+daemon_inetd_wtimeout(time_t last_connection_time)
+{
+time_t resignal_interval = inetd_wait_timeout;
+
+if (last_connection_time == (time_t)0)
+  {
+  DEBUG(D_any)
+    debug_printf("inetd wait timeout expired, but still not seen first message, ignoring\n");
+  }
+else
+  {
+  time_t now = time(NULL);
+  if (now == (time_t)-1)
+    {
+    DEBUG(D_any) debug_printf("failed to get time: %s\n", strerror(errno));
+    }
+  else if ((now - last_connection_time) >= inetd_wait_timeout)
+    {
+    DEBUG(D_any)
+      debug_printf("inetd wait timeout %d expired, ending daemon\n",
+         inetd_wait_timeout);
+    log_write(0, LOG_MAIN, "exim %s daemon terminating, inetd wait timeout reached.\n",
+       version_string);
+    daemon_die();              /* Does not return */
+    }
+  else
+    resignal_interval -= (now - last_connection_time);
+  }
+
+sigalrm_seen = FALSE;
+ALARM(resignal_interval);
+}
+
+
+/* Re-sort the qrunners list, and return the shortest interval.
+That could be negatime.
+The next-tick times should have been updated by any runs initiated,
+though will not be when the global limit on runners was reached.
+
+Unlikely to have many queues, so insertion-sort.
+*/
+
+static int
+next_qrunner_interval(void)
+{
+qrunner * sorted = NULL;
+for (qrunner * q = qrunners, * next; q; q = next)
+  {
+  next = q->next;
+  q->next = NULL;
+  if (sorted)
+    {
+    qrunner ** p = &sorted;
+    for (qrunner * qq; qq = *p; p = &qq->next)
+      if (  q->next_tick < qq->next_tick
+        || q->next_tick == qq->next_tick && q->interval < qq->interval
+        )
+       {
+       *p = q;
+       q->next = qq;
+       goto INSERTED;
+       }
+    *p = q;
+  INSERTED: ;
+    }
+  else
+    sorted = q;
+  }
+qrunners = sorted;
+return qrunners ? qrunners->next_tick - time(NULL) : 0;
+}
+
+/* See if we can do a queue run.  If policy limit permit, kick one off.
+If both notification and timer events are present, handle the former
+and leave the timer outstanding.
+
+Return the number of seconds until the next due runner.
+*/
+
+static int
+daemon_qrun(int local_queue_run_max, struct pollfd * fd_polls, int listen_socket_count)
+{
+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. In the calling process, restart the alamr timer for the next run.  */
+
+if (is_multiple_qrun())                                /* we are managing periodic runs */
+  if (local_queue_run_max <= 0 || queue_run_count < local_queue_run_max)
+    {
+    qrunner * q = NULL;
+
+#ifndef DISABLE_QUEUE_RAMP
+    /* If this is a triggered run for a specific message, see if we can start
+    another runner for this queue. */
+
+    if (*queuerun_msgid)
+      {
+      for (qrunner * qq = qrunners; qq; qq = qq->next)
+       if (qq->name == queuerun_msg_qname)
+         {
+         q = qq->run_count < qq->run_max ? qq : NULL;
+         break;
+         }
+      }
+    else
+#endif
+      /* Normal periodic run: in order of run priority, find the first queue
+      for which we can start a runner */
+
+      for (q = qrunners; q; q = q->next)
+       if (q->run_count < q->run_max) break;
+
+    if (q)                                     /* found a queue to run */
+      {
+      pid_t pid;
+
+      /* Bump this queue's next-tick by it's interval */
+
+      if (q->interval)
+       {
+       time_t now = time(NULL);
+       do ; while ((q->next_tick += q->interval) <= now);
+       }
+
+      if ((pid = exim_fork(US"queue-runner")) == 0)
+       {
+       /* 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 (f.debug_daemon) debug_selector = 0;
+
+       /* Close any open listening sockets in the child */
+
+       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(). */
+
+       if (geteuid() != root_uid && !deliver_drop_privilege)
+         {
+         uschar opt[8];
+         uschar *p = opt;
+         uschar *extra[7];
+         int extracount = 1;
+
+         signal(SIGALRM, SIG_DFL);
+         queue_name = US"";
+
+         *p++ = '-';
+         *p++ = 'q';
+         if (  q->queue_2stage
+#ifndef DISABLE_QUEUE_RAMP
+            && !*queuerun_msgid
+#endif
+            ) *p++ = 'q';
+         if (q->queue_run_first_delivery) *p++ = 'i';
+         if (q->queue_run_force) *p++ = 'f';
+         if (q->deliver_force_thaw) *p++ = 'f';
+         if (q->queue_run_local) *p++ = 'l';
+         *p = 0;
+
+         extra[0] = q->name
+           ? string_sprintf("%sG%s", opt, q->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)
+           {
+           extra[extracount++] = f.deliver_selectstring_regex ? US"-Rr" : US"-R";
+           extra[extracount++] = deliver_selectstring;
+           }
+
+         if (deliver_selectstring_sender)
+           {
+           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, 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 */
+
+#ifndef DISABLE_QUEUE_RAMP
+       if (*queuerun_msgid)
+         {
+         log_write(0, LOG_MAIN, "notify triggered queue run");
+         f.queue_2stage = FALSE;
+         queue_run(q, queuerun_msgid, queuerun_msgid, FALSE);
+         }
+       else
+#endif
+         queue_run(q, NULL, NULL, FALSE);
+       exim_underbar_exit(EXIT_SUCCESS);
+       }
+
+      if (pid < 0)
+       {
+       log_write(0, LOG_MAIN|LOG_PANIC, "daemon: fork of queue-runner "
+         "process failed: %s", strerror(errno));
+       log_close_all();
+       }
+      else
+       {
+       for (int i = 0; i < local_queue_run_max; ++i)
+         if (queue_runner_slots[i].pid <= 0)
+           {
+           queue_runner_slots[i].pid = pid;
+           queue_runner_slots[i].queue_name = q->name;
+           q->run_count++;
+           queue_run_count++;
+           break;
+           }
+       DEBUG(D_any) debug_printf("%d queue-runner process%s running\n",
+         queue_run_count, queue_run_count == 1 ? "" : "es");
+       }
+      }
+    }
+
+/* The queue run has been initiated (unless we were already running enough) */
+
+#ifndef DISABLE_QUEUE_RAMP
+if (*queuerun_msgid)           /* it was a fast-ramp kick; dealt with */
+  *queuerun_msgid = 0;
+else                           /* periodic or one-time queue run */
+#endif
+  /* Set up next timer callback. Impose a minimum 1s tick,
+  even when a run was outstanding */
+  {
+  int interval = next_qrunner_interval();
+  if (interval <= 0) interval = 1;
+
+  sigalrm_seen = FALSE;
+  if (qrunners)                        /* there are still periodic qrunners */
+    {
+    ALARM(interval);           /* set up next qrun tick */
+    return interval;
+    }
+  }
+return 0;
+}
+
+
+
+
+static const uschar *
+describe_queue_runners(void)
+{
+gstring * g = NULL;
+
+if (!is_multiple_qrun()) return US"no queue runs";
+
+for (qrunner * q = qrunners; q; q = q->next)
+  {
+  g = string_catn(g, US"-q", 2);
+  if (q->queue_2stage) g = string_catn(g, US"q", 1);
+  if (q->name) g = string_append(g, 3, US"G", q->name, US"/");
+  g = string_cat(g, readconf_printtime(q->interval));
+  g = string_catn(g, US" ", 1);
+  }
+gstring_trim(g, 1);
+gstring_release_unused(g);
+return string_from_gstring(g);
 }
 
 
 }
 
 
@@ -1214,12 +1704,38 @@ There are no arguments to this function, and it never returns. */
 void
 daemon_go(void)
 {
 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;
 time_t last_connection_time = (time_t)0;
-int local_queue_run_max = atoi(CS expand_string(queue_run_max));
+int local_queue_run_max = 0;
+
+if (is_multiple_qrun())
+  {
+  /* Nuber of runner-tracking structs needed:  If the option queue_run_max has
+  no expandable elements then it is the overall maximum; else we assume it
+  depends on the queue name, and add them up to get the maximum.
+  Evaluate both that and the individual limits. */
+
+  GET_OPTION("queue_run_max");
+  if (Ustrchr(queue_run_max, '$') != NULL)
+    {
+    for (qrunner * q = qrunners; q; q = q->next)
+      {
+      queue_name = q->name;
+      local_queue_run_max +=
+       (q->run_max = atoi(CS expand_string(queue_run_max)));
+      }
+    queue_name = US"";
+    }
+  else
+    {
+    local_queue_run_max = atoi(CS expand_string(queue_run_max));
+    for (qrunner * q = qrunners; q; q = q->next)
+      q->run_max = local_queue_run_max;
+    }
+  }
 
 process_purpose = US"daemon";
 
 
 process_purpose = US"daemon";
 
@@ -1228,16 +1744,21 @@ debugging lines get the pid added. */
 
 DEBUG(D_any|D_v) debug_selector |= D_pid;
 
 
 DEBUG(D_any|D_v) debug_selector |= D_pid;
 
+/* 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;
 if (f.inetd_wait_mode)
   {
   listen_socket_count = 1;
-  listen_sockets = store_get(sizeof(int), FALSE);
   (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));
 
   (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);
   (void) close(0);
   (void) close(1);
   (void) close(2);
@@ -1274,11 +1795,11 @@ if (f.inetd_wait_mode || f.daemon_listen)
   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). */
 
   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();
   if (queue_only_load >= 0 || smtp_load_reserve >= 0 ||
        (deliver_queue_load_max >= 0 && deliver_drop_privilege))
     (void)os_getloadavg();
-  #endif
+#endif
   }
 
 
   }
 
 
@@ -1414,7 +1935,7 @@ if (f.daemon_listen && !f.inetd_wait_mode)
   sep = 0;
   while ((s = string_nextinlist(&list, &sep, NULL, 0)))
     pct++;
   sep = 0;
   while ((s = string_nextinlist(&list, &sep, NULL, 0)))
     pct++;
-  default_smtp_port = store_get((pct+1) * sizeof(int), FALSE);
+  default_smtp_port = store_get((pct+1) * sizeof(int), GET_UNTAINTED);
   list = daemon_smtp_port;
   sep = 0;
   for (pct = 0;
   list = daemon_smtp_port;
   sep = 0;
   for (pct = 0;
@@ -1503,7 +2024,7 @@ if (f.daemon_listen && !f.inetd_wait_mode)
     ipa->port = default_smtp_port[0];
     for (int i = 1; default_smtp_port[i] > 0; i++)
       {
     ipa->port = default_smtp_port[0];
     for (int i = 1; default_smtp_port[i] > 0; i++)
       {
-      ip_address_item *new = store_get(sizeof(ip_address_item), FALSE);
+      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];
 
       memcpy(new->address, ipa->address, Ustrlen(ipa->address) + 1);
       new->port = default_smtp_port[i];
@@ -1557,11 +2078,16 @@ if (f.daemon_listen && !f.inetd_wait_mode)
         }
     }
 
         }
     }
 
-  /* 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; ipa = ipa->next)
     listen_socket_count++;
 
   for (ipa = addresses; ipa; ipa = ipa->next)
     listen_socket_count++;
-  listen_sockets = store_get(sizeof(int) * listen_socket_count, FALSE);
+  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 */
 
 
   } /* daemon_listen but not inetd_wait_mode */
 
@@ -1584,7 +2110,7 @@ if (f.daemon_listen)
 
   if (smtp_accept_max > 0)
     {
 
   if (smtp_accept_max > 0)
     {
-    smtp_slots = store_get(smtp_accept_max * sizeof(smtp_slot), FALSE);
+    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;
     }
   }
     for (int i = 0; i < smtp_accept_max; i++) smtp_slots[i] = empty_smtp_slot;
     }
   }
@@ -1621,15 +2147,19 @@ if (f.background_daemon)
   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
   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)
     {
 
   if (getppid() != 1)
     {
+    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 */
     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;
     }
   }
 
     }
   }
 
@@ -1652,8 +2182,8 @@ if (f.daemon_listen && !f.inetd_wait_mode)
   for (ipa = addresses, sk = 0; sk < listen_socket_count; ipa = ipa->next, sk++)
     {
     BOOL wildcard;
   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)
       {
 
     if (Ustrchr(ipa->address, ':') != NULL)
       {
@@ -1666,7 +2196,7 @@ if (f.daemon_listen && !f.inetd_wait_mode)
       wildcard = ipa->address[0] == 0;
       }
 
       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))
         {
       {
       if (check_special_case(0, addresses, ipa, FALSE))
         {
@@ -1675,7 +2205,7 @@ if (f.daemon_listen && !f.inetd_wait_mode)
         goto SKIP_SOCKET;
         }
       log_write(0, LOG_PANIC_DIE, "IPv%c socket creation failed: %s",
         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
       }
 
     /* If this is an IPv6 wildcard socket, set IPV6_V6ONLY if that option is
@@ -1684,8 +2214,7 @@ if (f.daemon_listen && !f.inetd_wait_mode)
 
 #ifdef IPV6_V6ONLY
     if (af == AF_INET6 && wildcard &&
 
 #ifdef IPV6_V6ONLY
     if (af == AF_INET6 && wildcard &&
-        setsockopt(listen_sockets[sk], IPPROTO_IPV6, IPV6_V6ONLY, CS (&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 */
       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 */
@@ -1694,16 +2223,14 @@ if (f.daemon_listen && !f.inetd_wait_mode)
     is being handled.  Without this, a connection will prevent reuse of the
     smtp port for listening. */
 
     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,
-                   US (&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. */
 
       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,
-      US (&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
 
     /* 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
@@ -1721,12 +2248,12 @@ if (f.daemon_listen && !f.inetd_wait_mode)
     for(;;)
       {
       uschar *msg, *addr;
     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");
       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);
         goto SKIP_SOCKET;
         }
       msg = US strerror(errno);
@@ -1754,30 +2281,30 @@ if (f.daemon_listen && !f.inetd_wait_mode)
       else
         debug_printf("listening on %s port %d\n", ipa->address, 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, add to the set of sockets for select
+    and continue to the next address. */
+
 #if defined(TCP_FASTOPEN) && !defined(__APPLE__)
     if (  f.tcp_fastopen_ok
 #if defined(TCP_FASTOPEN) && !defined(__APPLE__)
     if (  f.tcp_fastopen_ok
-       && setsockopt(listen_sockets[sk], IPPROTO_TCP, TCP_FASTOPEN,
+       && 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
                    &smtp_connect_backlog, sizeof(smtp_connect_backlog)))
       {
       DEBUG(D_any) debug_printf("setsockopt FASTOPEN: %s\n", strerror(errno));
       f.tcp_fastopen_ok = FALSE;
       }
 #endif
-
-    /* Start listening on the bound socket, establishing the maximum backlog of
-    connections that is allowed. On success, continue to the next address. */
-
-    if (listen(listen_sockets[sk], smtp_connect_backlog) >= 0)
+    if (listen(fd, smtp_connect_backlog) >= 0)
       {
 #if defined(TCP_FASTOPEN) && defined(__APPLE__)
       if (  f.tcp_fastopen_ok
       {
 #if defined(TCP_FASTOPEN) && defined(__APPLE__)
       if (  f.tcp_fastopen_ok
-        && setsockopt(listen_sockets[sk], IPPROTO_TCP, TCP_FASTOPEN,
-                     &on, sizeof(on)))
+        && 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
        {
        DEBUG(D_any) debug_printf("setsockopt FASTOPEN: %s\n", strerror(errno));
        f.tcp_fastopen_ok = FALSE;
        }
 #endif
+      fd_polls[sk].fd = fd;
       continue;
       }
 
       continue;
       }
 
@@ -1795,7 +2322,7 @@ if (f.daemon_listen && !f.inetd_wait_mode)
 
     DEBUG(D_any) debug_printf("wildcard IPv4 listen() failed after IPv6 "
       "listen() success; EADDRINUSE ignored\n");
 
     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
 
     /* 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
@@ -1836,18 +2363,11 @@ The variable daemon_write_pid is used to control this. */
 
 if (f.running_in_test_harness || write_pid)
   {
 
 if (f.running_in_test_harness || write_pid)
   {
-  FILE *f;
-
-  set_pid_file_path();
-  if ((f = modefopen(pid_file_path, "wb", 0644)))
-    {
-    (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("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. */
   }
 
 /* Set up the handler for SIGHUP, which causes a restart of the daemon. */
@@ -1874,10 +2394,11 @@ originator_login = (pw = getpwuid(exim_uid))
 /* 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). */
 
 /* 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 && local_queue_run_max > 0)
+if (is_multiple_qrun() && local_queue_run_max > 0)
   {
   {
-  queue_pid_slots = store_get(local_queue_run_max * sizeof(pid_t), FALSE);
-  for (int i = 0; i < local_queue_run_max; i++) queue_pid_slots[i] = 0;
+  queue_runner_slot_count = local_queue_run_max;
+  queue_runner_slots = store_get(local_queue_run_max * sizeof(runner_slot), GET_UNTAINTED);
+  memset(queue_runner_slots, 0, local_queue_run_max * sizeof(runner_slot));
   }
 
 /* Set up the handler for termination of child processes, and the one
   }
 
 /* Set up the handler for termination of child processes, and the one
@@ -1888,11 +2409,15 @@ os_non_restarting_signal(SIGCHLD, main_sigchld_handler);
 
 sigterm_seen = FALSE;
 os_non_restarting_signal(SIGTERM, main_sigterm_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
 
 /* 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. */
+off. This will cause the first queue-runner to get kicked off straight away.
+Get an initial sort of the list of queues, to prioritize the initial q-runs */
 
 
-sigalrm_seen = (queue_interval > 0);
+
+if ((sigalrm_seen = is_multiple_qrun()))
+  (void) next_qrunner_interval();
 
 /* Log the start up of a daemon - at least one of listening or queue running
 must be set up. */
 
 /* Log the start up of a daemon - at least one of listening or queue running
 must be set up. */
@@ -1921,20 +2446,16 @@ else if (f.daemon_listen)
   int smtps_ports = 0;
   ip_address_item * ipa;
   uschar * p;
   int smtps_ports = 0;
   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";
+  const uschar * qinfo = describe_queue_runners();
 
   /* Build a list of listening addresses in big_buffer, but limit it to 10
   items. The style is for backwards compatibility.
 
 
   /* Build a list of listening addresses in big_buffer, but limit it to 10
   items. The style is for backwards compatibility.
 
-  It is now possible to have some ports listening for SMTPS (the old,
-  deprecated protocol that starts TLS without using STARTTLS), and others
-  listening for standard SMTP. Keep their listings separate. */
+  It is possible to have some ports listening for SMTPS (as opposed to TLS
+  startted by STARTTLS), and others listening for standard SMTP. Keep their
+  listings separate. */
 
   for (int j = 0, i; j < 2; j++)
 
   for (int j = 0, i; j < 2; j++)
-    {
     for (i = 0, ipa = addresses; i < 10 && ipa; i++, ipa = ipa->next)
       {
       /* First time round, look for SMTP ports; second time round, look for
     for (i = 0, ipa = addresses; i < 10 && ipa; i++, ipa = ipa->next)
       {
       /* First time round, look for SMTP ports; second time round, look for
@@ -1972,11 +2493,11 @@ else if (f.daemon_listen)
               && Ustrcmp(ipa->address, i2->address) == 0
               )
              {                         /* found; append port to list */
               && Ustrcmp(ipa->address, i2->address) == 0
               )
              {                         /* found; append port to list */
-             for (p = i2->log; *p; ) p++;      /* end of existing string */
+             for (p = i2->log; *p; ) p++;      /* end of existing string   { */
              if (*--p == '}') *p = '\0';       /* drop EOL */
              while (isdigit(*--p)) ;           /* char before port */
 
              if (*--p == '}') *p = '\0';       /* drop EOL */
              while (isdigit(*--p)) ;           /* char before port */
 
-             i2->log = *p == ':'               /* no list yet? */
+             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);
                ? string_sprintf("%.*s{%s,%d}",
                  (int)(p - i2->log + 1), i2->log, p+1, ipa->port)
                : string_sprintf("%s,%d}", i2->log, ipa->port);
@@ -1988,7 +2509,6 @@ else if (f.daemon_listen)
          }
        }
       }
          }
        }
       }
-    }
 
   p = big_buffer;
   for (int j = 0, i; j < 2; j++)
 
   p = big_buffer;
   for (int j = 0, i; j < 2; j++)
@@ -2024,11 +2544,9 @@ else if (f.daemon_listen)
     version_string, qinfo, big_buffer);
   }
 
     version_string, qinfo, big_buffer);
   }
 
-else
+else   /* no listening sockets, only queue-runs */
   {
   {
-  uschar * s = *queue_name
-    ? string_sprintf("-qG%s/%s", queue_name, readconf_printtime(queue_interval))
-    : string_sprintf("-q%s", readconf_printtime(queue_interval));
+  const uschar * s = describe_queue_runners();
   log_write(0, LOG_MAIN,
     "exim %s daemon started: pid=%d, %s, not listening for SMTP",
     version_string, getpid(), s);
   log_write(0, LOG_MAIN,
     "exim %s daemon started: pid=%d, %s, not listening for SMTP",
     version_string, getpid(), s);
@@ -2064,6 +2582,24 @@ spf_init();
 tls_daemon_init();
 #endif
 
 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
 closes the log afterwards, for the same reason. */
 /* 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
 closes the log afterwards, for the same reason. */
@@ -2084,14 +2620,7 @@ report_time_since(&timestamp_startup, US"daemon loop start");    /* testcase 0022 *
 
 for (;;)
   {
 
 for (;;)
   {
-  #if HAVE_IPV6
-  struct sockaddr_in6 accepted;
-  #else
-  struct sockaddr_in accepted;
-  #endif
-
-  EXIM_SOCKLEN_T len;
-  pid_t pid;
+  int nolisten_sleep = 60;
 
   if (sigterm_seen)
     daemon_die();      /* Does not return */
 
   if (sigterm_seen)
     daemon_die();      /* Does not return */
@@ -2102,186 +2631,12 @@ for (;;)
 
   The other option is that we have an inetd wait timeout specified to -bw. */
 
 
   The other option is that we have an inetd wait timeout specified to -bw. */
 
-  if (sigalrm_seen)
-    {
+  if (sigalrm_seen || *queuerun_msgid)
     if (inetd_wait_timeout > 0)
     if (inetd_wait_timeout > 0)
-      {
-      time_t resignal_interval = inetd_wait_timeout;
-
-      if (last_connection_time == (time_t)0)
-        {
-        DEBUG(D_any)
-          debug_printf("inetd wait timeout expired, but still not seen first message, ignoring\n");
-        }
-      else
-        {
-        time_t now = time(NULL);
-        if (now == (time_t)-1)
-          {
-          DEBUG(D_any) debug_printf("failed to get time: %s\n", strerror(errno));
-          }
-        else
-          {
-          if ((now - last_connection_time) >= inetd_wait_timeout)
-            {
-            DEBUG(D_any)
-              debug_printf("inetd wait timeout %d expired, ending daemon\n",
-                  inetd_wait_timeout);
-            log_write(0, LOG_MAIN, "exim %s daemon terminating, inetd wait timeout reached.\n",
-                version_string);
-            exit(EXIT_SUCCESS);
-            }
-          else
-            {
-            resignal_interval -= (now - last_connection_time);
-            }
-          }
-        }
-
-      sigalrm_seen = FALSE;
-      ALARM(resignal_interval);
-      }
-
+      daemon_inetd_wtimeout(last_connection_time);     /* Might not return */
     else
     else
-      {
-      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
-         && (local_queue_run_max <= 0 || queue_run_count < local_queue_run_max))
-        {
-        if ((pid = exim_fork(US"queue-runner")) == 0)
-          {
-          /* 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 (f.debug_daemon) debug_selector = 0;
-
-          /* Close any open listening sockets in the child */
-
-         close_daemon_sockets(daemon_notifier_fd,
-           listen_sockets, 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);
-
-          /* Re-exec if privilege has been given up, unless deliver_drop_
-          privilege is set. Reset SIGALRM before exec(). */
-
-          if (geteuid() != root_uid && !deliver_drop_privilege)
-            {
-            uschar opt[8];
-            uschar *p = opt;
-            uschar *extra[7];
-            int extracount = 1;
-
-            signal(SIGALRM, SIG_DFL);
-            *p++ = '-';
-            *p++ = 'q';
-            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] = *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)
-              {
-              extra[extracount++] = f.deliver_selectstring_regex ? US"-Rr" : US"-R";
-              extra[extracount++] = deliver_selectstring;
-              }
-
-            if (deliver_selectstring_sender)
-              {
-              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, 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 */
-
-#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)
-          {
-          log_write(0, LOG_MAIN|LOG_PANIC, "daemon: fork of queue-runner "
-            "process failed: %s", strerror(errno));
-          log_close_all();
-          }
-        else
-          {
-          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");
-          }
-        }
-
-      /* Reset the alarm clock */
-
-      sigalrm_seen = FALSE;
-#ifndef DISABLE_QUEUE_RAMP
-      if (*queuerun_msgid)
-       *queuerun_msgid = 0;
-      else
-#endif
-       ALARM(queue_interval);
-      }
-
-    } /* sigalrm_seen */
+      nolisten_sleep =
+       daemon_qrun(local_queue_run_max, fd_polls, listen_socket_count);
 
 
   /* Sleep till a connection happens if listening, and handle the connection if
 
 
   /* Sleep till a connection happens if listening, and handle the connection if
@@ -2296,28 +2651,7 @@ for (;;)
   if (f.daemon_listen)
     {
     int lcount;
   if (f.daemon_listen)
     {
     int lcount;
-    int max_socket = 0;
     BOOL select_failed = FALSE;
     BOOL select_failed = FALSE;
-    fd_set select_listen;
-
-    FD_ZERO(&select_listen);
-#ifndef DISABLE_TLS
-    if (tls_watch_fd >= 0)
-      {
-      FD_SET(tls_watch_fd, &select_listen);
-      if (tls_watch_fd > max_socket) max_socket = tls_watch_fd;
-      }
-#endif
-    if (daemon_notifier_fd >= 0)
-      {
-      FD_SET(daemon_notifier_fd, &select_listen);
-      if (daemon_notifier_fd > max_socket) max_socket = daemon_notifier_fd;
-      }
-    for (int 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");
 
 
     DEBUG(D_any) debug_printf("Listening...\n");
 
@@ -2326,7 +2660,9 @@ for (;;)
     select() was interrupted so that we reap the child. This might still leave
     a small window when a SIGCHLD could get lost. However, since we use SIGCHLD
     only to do the reaping more quickly, it shouldn't result in anything other
     select() was interrupted so that we reap the child. This might still leave
     a small window when a SIGCHLD could get lost. However, since we use SIGCHLD
     only to do the reaping more quickly, it shouldn't result in anything other
-    than a delay until something else causes a wake-up. */
+    than a delay until something else causes a wake-up.
+    For the normal case, wait for either a pollable fd (eg. new connection) or
+    or a SIGALRM (for a periodic queue run) */
 
     if (sigchld_seen)
       {
 
     if (sigchld_seen)
       {
@@ -2334,8 +2670,7 @@ for (;;)
       errno = EINTR;
       }
     else
       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)
       {
 
     if (lcount < 0)
       {
@@ -2355,8 +2690,15 @@ for (;;)
       handle_ending_processes();
 
 #ifndef DISABLE_TLS
       handle_ending_processes();
 
 #ifndef DISABLE_TLS
+      {
+      int old_tfd;
       /* Create or rotate any required keys; handle (delayed) filewatch event */
       /* Create or rotate any required keys; handle (delayed) filewatch event */
-      tls_daemon_tick();
+
+      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;
       }
 #endif
       errno = select_errno;
       }
@@ -2368,32 +2710,66 @@ for (;;)
     while (lcount-- > 0)
       {
       int accept_socket = -1;
     while (lcount-- > 0)
       {
       int accept_socket = -1;
+#if HAVE_IPV6
+      struct sockaddr_in6 accepted;
+#else
+      struct sockaddr_in accepted;
+#endif
 
       if (!select_failed)
        {
 #if !defined(DISABLE_TLS) && (defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT))
 
       if (!select_failed)
        {
 #if !defined(DISABLE_TLS) && (defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT))
-       if (tls_watch_fd >= 0 && FD_ISSET(tls_watch_fd, &select_listen))
+       if (tls_watch_poll && tls_watch_poll->revents & POLLIN)
          {
          {
-         FD_CLR(tls_watch_fd, &select_listen);
+         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
           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 (  daemon_notifier_fd >= 0
-          && FD_ISSET(daemon_notifier_fd, &select_listen))
+       /* Handle the daemon-notifier socket.  If it was a fast-ramp
+       notification then queuerun_msgid will have a nonzerolength string. */
+
+       if (dnotify_poll && dnotify_poll->revents & POLLIN)
          {
          {
-         FD_CLR(daemon_notifier_fd, &select_listen);
-         sigalrm_seen = daemon_notification();
+         dnotify_poll->revents = 0;
+         daemon_notification();
          break;        /* to top of daemon loop */
          }
          break;        /* to top of daemon loop */
          }
-        for (int sk = 0; sk < listen_socket_count; sk++)
-          if (FD_ISSET(listen_sockets[sk], &select_listen))
+       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);
+#if defined(__FreeBSD__) && defined(SO_LISTENQLEN)
+           int backlog;
+           socklen_t blen = sizeof(backlog);
+
+           if (  smtp_backlog_monitor > 0
+              && getsockopt(p->fd, SOL_SOCKET, SO_LISTENQLEN, &backlog, &blen) == 0)
+             {
+             DEBUG(D_interface)
+               debug_printf("listen fd %d queue curr %d\n", p->fd, backlog);
+             smtp_listen_backlog = backlog;
+             }
+
+#elif defined(TCP_INFO) && defined(EXIM_HAVE_TCPI_UNACKED)
+           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)
+             {
+             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
+           p->revents = 0;
+            accept_socket = accept(p->fd, (struct sockaddr *)&accepted, &alen);
             break;
             }
        }
             break;
             }
        }
@@ -2415,21 +2791,21 @@ for (;;)
           accept_retry_errno = errno;
           accept_retry_select_failed = select_failed;
           }
           accept_retry_errno = errno;
           accept_retry_select_failed = select_failed;
           }
-        else if (  errno != accept_retry_errno 
+        else if (  errno != accept_retry_errno
                || select_failed != accept_retry_select_failed
                || accept_retry_count >= 50)
                || 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;
-            }
+         {
+         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)
         accept_retry_count++;
         }
       else if (accept_retry_count > 0)
@@ -2454,7 +2830,7 @@ for (;;)
 #endif
         if (inetd_wait_timeout)
           last_connection_time = time(NULL);
 #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);
         }
       }
           (struct sockaddr *)&accepted);
         }
       }
@@ -2469,10 +2845,8 @@ for (;;)
 
   else
     {
 
   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, nolisten_sleep * 1000);
     handle_ending_processes();
     }
 
     handle_ending_processes();
     }
 
@@ -2497,8 +2871,8 @@ for (;;)
     {
     log_write(0, LOG_MAIN, "pid %d: SIGHUP received: re-exec daemon",
       getpid());
     {
     log_write(0, LOG_MAIN, "pid %d: SIGHUP received: re-exec daemon",
       getpid());
-    close_daemon_sockets(daemon_notifier_fd,
-      listen_sockets, listen_socket_count);
+    close_daemon_sockets(daemon_notifier_fd, fd_polls, listen_socket_count);
+    unlink_notifier_socket();
     ALARM_CLR(0);
     signal(SIGHUP, SIG_IGN);
     sighup_argv[0] = exim_path;
     ALARM_CLR(0);
     signal(SIGHUP, SIG_IGN);
     sighup_argv[0] = exim_path;