Fix problem with smtp_max_per_host; test for completed processes before
[exim.git] / src / src / daemon.c
index d9375eabf2df79b7c1f6e4b82dedff24973b651a..2495e18ae4a2059e83727f6af3f0f184bb37cd9c 100644 (file)
@@ -1,10 +1,10 @@
-/* $Cambridge: exim/src/src/daemon.c,v 1.1 2004/10/07 10:39:01 ph10 Exp $ */
+/* $Cambridge: exim/src/src/daemon.c,v 1.8 2005/02/16 15:24:21 ph10 Exp $ */
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2004 */
+/* Copyright (c) University of Cambridge 1995 - 2005 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions concerned with running Exim as a daemon */
@@ -39,8 +39,8 @@ static int   accept_retry_errno;
 static BOOL  accept_retry_select_failed;
 
 static int   queue_run_count = 0;
-static pid_t *queue_pid_slots;
-static smtp_slot *smtp_slots;
+static pid_t *queue_pid_slots = NULL;
+static smtp_slot *smtp_slots = NULL;
 
 static BOOL  write_pid = TRUE;
 
@@ -84,8 +84,8 @@ static void
 main_sigchld_handler(int sig)
 {
 sig = sig;    /* Keep picky compilers happy */
+os_non_restarting_signal(SIGCHLD, SIG_DFL);
 sigchld_seen = TRUE;
-signal(SIGCHLD, SIG_DFL);
 }
 
 
@@ -362,6 +362,7 @@ 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;
   #ifdef SA_NOCLDWAIT
   struct sigaction act;
@@ -432,15 +433,26 @@ if (pid == 0)
   /* 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
   main process if there is any delay. Then set up the fullhost information
-  in case there is no HELO/EHLO. */
-
+  in case there is no HELO/EHLO. 
+  
+  If debugging is enabled only for the daemon, we must turn if off while 
+  finding the id, but turn it on again afterwards so that information about the 
+  incoming connection is output. */
+  
+  if (debug_daemon) debug_selector = 0;
   verify_get_ident(IDENT_PORT);
   host_build_sender_fullhost();
+  debug_selector = save_debug_selector; 
 
   DEBUG(D_any)
     debug_printf("Process %d is handling incoming connection from %s\n",
       (int)getpid(), sender_fullhost);
 
+  /* Now disable debugging permanently if it's required only for the daemon
+  process. */
+
+  if (debug_daemon) debug_selector = 0;
+
   /* If there are too many child processes for immediate delivery,
   set the local_queue_only flag, which is initialized from the
   configured value and may therefore already be TRUE. Leave logging
@@ -764,6 +776,72 @@ return FALSE;
 
 
 
+/*************************************************
+*         Handle terminating subprocesses        *
+*************************************************/
+
+/* Handle the termination of child processes. Theoretically, this need be done
+only when sigchld_seen is TRUE, but rumour has it that some systems lose
+SIGCHLD signals at busy times, so to be on the safe side, this function is
+called each time round. It shouldn't be too expensive.
+
+Arguments:  none
+Returns:    nothing
+*/
+
+static void
+handle_ending_processes(void)
+{
+int status;
+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);
+
+  /* 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)
+    {
+    for (i = 0; i < smtp_accept_max; i++)
+      {
+      if (smtp_slots[i].pid == pid)
+        {
+        if (smtp_slots[i].host_address != NULL)
+          store_free(smtp_slots[i].host_address);
+        smtp_slots[i] = empty_smtp_slot;
+        if (--smtp_accept_count < 0) smtp_accept_count = 0;
+        DEBUG(D_any) debug_printf("%d SMTP accept process%s now running\n",
+          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)
+    {
+    for (i = 0; i < queue_run_max; i++)
+      {
+      if (queue_pid_slots[i] == pid)
+        {
+        queue_pid_slots[i] = 0;
+        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");
+        break;
+        }
+      }
+    }
+  }
+}
+
 
 
 /*************************************************
@@ -886,7 +964,9 @@ if (daemon_listen)
   order to perform an "open" on the kernel memory file). */
 
   #ifdef LOAD_AVG_NEEDS_ROOT
-  if (queue_only_load >= 0 || smtp_load_reserve >= 0) (void)os_getloadavg();
+  if (queue_only_load >= 0 || smtp_load_reserve >= 0 ||
+       (deliver_queue_load_max >= 0 && deliver_drop_privilege)) 
+    (void)os_getloadavg();
   #endif
 
   /* If -oX was used, disable the writing of a pid file unless -oP was
@@ -1110,26 +1190,35 @@ if (daemon_listen)
     }
   }
 
-/* We now close all open file descriptors that we know about, and disconnect
-from the controlling terminal, unless background_daemon is unset. This is
-always unset when debugging, but can also be forced. 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. */
+/* The variable background_daemon is always false when debugging, but
+can also be forced false in order to keep a non-debugging daemon in the
+foreground. If background_daemon is true, close all open file descriptors that
+we know about, but then re-open stdin, stdout, and stderr to /dev/null.
+
+This is protection against any called functions (in libraries, or in
+Perl, or whatever) that think they can write to stderr (or stdout). Before this
+was added, it was quite likely that an SMTP connection would use one of these
+file descriptors, in which case writing random stuff to it caused chaos.
+
+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)
   {
-  log_close_all();  /* Just in case anything was logged earlier */
-  search_tidyup();  /* Just in case any were used in reading the config. */
-  close(0);         /* Get rid of stdin/stdout/stderr */
+  log_close_all();    /* Just in case anything was logged earlier */
+  search_tidyup();    /* Just in case any were used in reading the config. */
+  close(0);           /* Get rid of stdin/stdout/stderr */
   close(1);
   close(2);
+  exim_nullstd();     /* Connect stdin/stdout/stderr to /dev/null */ 
   log_stderr = NULL;  /* So no attempt to copy paniclog output */
 
   /* 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 any
-  forking, because the controlling terminal has long gone. Otherwise, fork,
-  in case current process is a process group leader (see 'man setsid' for an
-  explanation). */
+  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(). */
 
   if (getppid() != 1)
     {
@@ -1365,7 +1454,7 @@ if (queue_interval > 0 && queue_run_max > 0)
 /* Set up the handler for termination of child processes. */
 
 sigchld_seen = FALSE;
-signal(SIGCHLD, main_sigchld_handler);
+os_non_restarting_signal(SIGCHLD, main_sigchld_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. */
@@ -1490,7 +1579,6 @@ for (;;)
   #endif
 
   SOCKLEN_T len = sizeof(accepted);
-  int status;
   pid_t pid;
 
   /* This code is placed first in the loop, so that it gets obeyed at the
@@ -1511,9 +1599,16 @@ for (;;)
       if ((pid = fork()) == 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;
         /* Close any open listening sockets in the child */
 
         for (sk = 0; sk < listen_socket_count; sk++) close(listen_sockets[sk]);
@@ -1592,7 +1687,7 @@ for (;;)
 
   if (daemon_listen)
     {
-    int sk, lcount;
+    int sk, lcount, select_errno;
     int max_socket = 0;
     BOOL select_failed = FALSE;
     fd_set select_listen;
@@ -1605,17 +1700,45 @@ for (;;)
       }
 
     DEBUG(D_any) debug_printf("Listening...\n");
+    
+    /* In rare cases we may have had a SIGCHLD signal in the time between 
+    setting the handler (below) and getting back here. If so, pretend that the 
+    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. */
+
+    if (sigchld_seen)
+      {
+      lcount = -1;
+      errno = EINTR;  
+      }
+    else
+      {      
+      lcount = select(max_socket + 1, (SELECT_ARG2_TYPE *)&select_listen,
+        NULL, NULL, NULL);
+      }   
 
-    if ((lcount = select(max_socket + 1, (SELECT_ARG2_TYPE *)&select_listen,
-         NULL, NULL, NULL)) < 0)
+    if (lcount < 0)
       {
       select_failed = TRUE;
       lcount = 1;
       }
-
+      
+    /* Clean up any subprocesses that may have terminated. We need to do this 
+    here so that smtp_accept_max_per_host works when a connection to that host 
+    has completed, and we are about to accept a new one. When this code was 
+    later in the sequence, a new connection could be rejected, even though an 
+    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; 
+      
     /* Loop for all the sockets that are currently ready to go. If select
-    actually failed, we have set the count to 1 and a flag, so as to use the
-    common error code for select/accept below. */
+    actually failed, we have set the count to 1 and select_failed=TRUE, so as
+    to use the common error code for select/accept below. */
 
     while (lcount-- > 0)
       {
@@ -1707,57 +1830,7 @@ for (;;)
     tv.tv_sec = queue_interval;
     tv.tv_usec = 0;
     select(0, NULL, NULL, NULL, &tv);
-    }
-
-  /* Handle the termination of a child process. Theoretically, this need
-  be done only when sigchld_seen is TRUE, but rumour has it that some systems
-  lose SIGCHLD signals at busy times, so to be on the safe side, just
-  do it each time round. It shouldn't be too expensive. */
-
-  while ((pid = waitpid(-1, &status, WNOHANG)) > 0)
-    {
-    int i;
-    DEBUG(D_any) debug_printf("child %d ended: status=0x%x\n", (int)pid,
-      status);
-
-    /* If it's a listening daemon, deal with an accepting process that has
-    terminated. */
-
-    if (daemon_listen)
-      {
-      for (i = 0; i < smtp_accept_max; i++)
-        {
-        if (smtp_slots[i].pid == pid)
-          {
-          if (smtp_slots[i].host_address != NULL)
-            store_free(smtp_slots[i].host_address);
-          smtp_slots[i] = empty_smtp_slot;
-          if (--smtp_accept_count < 0) smtp_accept_count = 0;
-          DEBUG(D_any) debug_printf("%d SMTP accept process%s now running\n",
-            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, if we are keeping track of them. */
-
-    if (queue_interval > 0)
-      {
-      for (i = 0; i < queue_run_max; i++)
-        {
-        if (queue_pid_slots[i] == pid)
-          {
-          queue_pid_slots[i] = 0;
-          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");
-          break;
-          }
-        }
-      }
+    handle_ending_processes(); 
     }
 
   /* Re-enable the SIGCHLD handler if it has been run. It can't do it
@@ -1766,7 +1839,7 @@ for (;;)
   if (sigchld_seen)
     {
     sigchld_seen = FALSE;
-    signal(SIGCHLD, main_sigchld_handler);
+    os_non_restarting_signal(SIGCHLD, main_sigchld_handler);
     }
 
   /* Handle being woken by SIGHUP. We know at this point that the result