Add comments as to why smtp_log_no_mail() is not called when
[exim.git] / src / src / daemon.c
index 737d2ffe4927ad2779e14a0e40d1ab0c7ed8c673..1e639453cd1fc51fe39c520ee37fdf292d9da776 100644 (file)
@@ -1,10 +1,10 @@
-/* $Cambridge: exim/src/src/daemon.c,v 1.3 2004/11/11 16:03:47 ph10 Exp $ */
+/* $Cambridge: exim/src/src/daemon.c,v 1.21 2007/01/17 11:29:39 ph10 Exp $ */
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2004 */
+/* Copyright (c) University of Cambridge 1995 - 2007 */
 /* 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;
 
@@ -142,7 +142,7 @@ handle_smtp_call(int *listen_sockets, int listen_socket_count,
 {
 pid_t pid;
 union sockaddr_46 interface_sockaddr;
-SOCKLEN_T ifsize = sizeof(interface_sockaddr);
+EXIM_SOCKLEN_T ifsize = sizeof(interface_sockaddr);
 int dup_accept_socket = -1;
 int max_for_this_host = 0;
 int wfsize = 0;
@@ -186,13 +186,14 @@ if (smtp_in == NULL)
   goto ERROR_RETURN;
   }
 
-/* Get the data for the local interface address. */
+/* Get the data for the local interface address. Panic for most errors, but
+"connection reset by peer" just means the connection went away. */
 
 if (getsockname(accept_socket, (struct sockaddr *)(&interface_sockaddr),
      &ifsize) < 0)
   {
-  log_write(0, LOG_MAIN|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");
   goto ERROR_RETURN;
   }
@@ -362,7 +363,7 @@ if (pid == 0)
   int i;
   int queue_only_reason = 0;
   int old_pool = store_pool;
-  int save_debug_selector = debug_selector; 
+  int save_debug_selector = debug_selector;
   BOOL local_queue_only;
   #ifdef SA_NOCLDWAIT
   struct sigaction act;
@@ -419,7 +420,7 @@ if (pid == 0)
   extensive comment before the reception loop in exim.c for a fuller
   explanation of this logic. */
 
-  for (i = 0; i < listen_socket_count; i++) close(listen_sockets[i]);
+  for (i = 0; i < listen_socket_count; i++) (void)close(listen_sockets[i]);
 
   #ifdef SA_NOCLDWAIT
   act.sa_handler = SIG_IGN;
@@ -433,16 +434,16 @@ 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. 
-  
-  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 
+  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_selector = save_debug_selector;
 
   DEBUG(D_any)
     debug_printf("Process %d is handling incoming connection from %s\n",
@@ -467,7 +468,12 @@ if (pid == 0)
   /* Handle the start of the SMTP session, then loop, accepting incoming
   messages from the SMTP connection. The end will come at the QUIT command,
   when smtp_setup_msg() returns 0. A break in the connection causes the
-  process to die (see accept.c). */
+  process to die (see accept.c).
+
+  NOTE: We do *not* call smtp_log_no_mail() if smtp_start_session() fails,
+  because a log line has already been written for all its failure exists
+  (usually "connection refused: <reason>") and writing another one is
+  unnecessary clutter. */
 
   if (!smtp_start_session())
     {
@@ -499,6 +505,7 @@ if (pid == 0)
       if (!ok)                            /* Connection was dropped */
         {
         mac_smtp_fflush();
+        smtp_log_no_mail();               /* Log no mail if configured */
         _exit(EXIT_SUCCESS);
         }
       if (message_id[0] == 0) continue;   /* No message was accepted */
@@ -507,6 +514,7 @@ if (pid == 0)
       {
       mac_smtp_fflush();
       search_tidyup();
+      smtp_log_no_mail();                 /* Log no mail if configured */
       _exit((rc == 0)? EXIT_SUCCESS : EXIT_FAILURE);
       }
 
@@ -603,8 +611,8 @@ if (pid == 0)
 
       if ((dpid = fork()) == 0)
         {
-        fclose(smtp_in);
-        fclose(smtp_out);
+        (void)fclose(smtp_in);
+        (void)fclose(smtp_out);
 
         /* Don't ever molest the parent's SSL connection, but do clean up
         the data structures if necessary. */
@@ -680,27 +688,28 @@ ERROR_RETURN:
 /* Close the streams associated with the socket which will also close the
 socket fds in this process. We can't do anything if fclose() fails, but
 logging brings it to someone's attention. However, "connection reset by peer"
-isn't really a problem, so skip that one. If the streams don't exist, something
-went wrong while setting things up. Make sure the socket descriptors are
-closed, in order to drop the connection. */
+isn't really a problem, so skip that one. On Solaris, a dropped connection can
+manifest itself as a broken pipe, so drop that one too. If the streams don't
+exist, something went wrong while setting things up. Make sure the socket
+descriptors are closed, in order to drop the connection. */
 
 if (smtp_out != NULL)
   {
-  if (fclose(smtp_out) != 0 && errno != ECONNRESET)
+  if (fclose(smtp_out) != 0 && errno != ECONNRESET && errno != EPIPE)
     log_write(0, LOG_MAIN|LOG_PANIC, "daemon: fclose(smtp_out) failed: %s",
       strerror(errno));
   smtp_out = NULL;
   }
-else close(accept_socket);
+else (void)close(accept_socket);
 
 if (smtp_in != NULL)
   {
-  if (fclose(smtp_in) != 0 && errno != ECONNRESET)
+  if (fclose(smtp_in) != 0 && errno != ECONNRESET && errno != EPIPE)
     log_write(0, LOG_MAIN|LOG_PANIC, "daemon: fclose(smtp_in) failed: %s",
       strerror(errno));
   smtp_in = NULL;
   }
-else close(dup_accept_socket);
+else (void)close(dup_accept_socket);
 
 /* Release any store used in this process, including the store used for holding
 the incoming host address and an expanded active_hostname. */
@@ -776,6 +785,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;
+        }
+      }
+    }
+  }
+}
+
 
 
 /*************************************************
@@ -898,7 +973,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
@@ -1122,26 +1199,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 */
-  close(1);
-  close(2);
+  log_close_all();    /* Just in case anything was logged earlier */
+  search_tidyup();    /* Just in case any were used in reading the config. */
+  (void)close(0);           /* Get rid of stdin/stdout/stderr */
+  (void)close(1);
+  (void)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)
     {
@@ -1172,7 +1258,6 @@ if (daemon_listen)
     {
     BOOL wildcard;
     ip_address_item *ipa2;
-    int retries = 9;
     int af;
 
     if (Ustrchr(ipa->address, ':') != NULL)
@@ -1244,19 +1329,22 @@ if (daemon_listen)
         {
         DEBUG(D_any) debug_printf("wildcard IPv4 bind() failed after IPv6 "
           "listen() success; EADDRINUSE ignored\n");
-        close(listen_sockets[sk]);
+        (void)close(listen_sockets[sk]);
         goto SKIP_SOCKET;
         }
       msg = US strerror(errno);
       addr = wildcard? ((af == AF_INET6)? US"(any IPv6)" : US"(any IPv4)") :
         ipa->address;
-      if (retries-- <= 0)
+      if (daemon_startup_retries <= 0)
         log_write(0, LOG_MAIN|LOG_PANIC_DIE,
           "socket bind() to port %d for address %s failed: %s: "
           "daemon abandoned", ipa->port, addr, msg);
       log_write(0, LOG_MAIN, "socket bind() to port %d for address %s "
-        "failed: %s: waiting before trying again", ipa->port, addr, msg);
-      sleep(30);
+        "failed: %s: waiting %s before trying again (%d more %s)",
+        ipa->port, addr, msg, readconf_printtime(daemon_startup_sleep),
+        daemon_startup_retries, (daemon_startup_retries > 1)? "tries" : "try");
+      daemon_startup_retries--;
+      sleep(daemon_startup_sleep);
       }
 
     DEBUG(D_any)
@@ -1287,7 +1375,7 @@ if (daemon_listen)
 
     DEBUG(D_any) debug_printf("wildcard IPv4 listen() failed after IPv6 "
       "listen() success; EADDRINUSE ignored\n");
-    close(listen_sockets[sk]);
+    (void)close(listen_sockets[sk]);
 
     /* 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
@@ -1335,12 +1423,11 @@ if (running_in_test_harness || write_pid)
   if (pid_file_path[0] == 0)
     pid_file_path = string_sprintf("%s/exim-daemon.pid", spool_directory);
 
-  f = Ufopen(pid_file_path, "wb");
+  f = modefopen(pid_file_path, "wb", 0644);
   if (f != NULL)
     {
-    fprintf(f, "%d\n", (int)getpid());
-    fchmod(fileno(f), 0644);
-    fclose(f);
+    (void)fprintf(f, "%d\n", (int)getpid());
+    (void)fclose(f);
     DEBUG(D_any) debug_printf("pid written to %s\n", pid_file_path);
     }
   else
@@ -1501,8 +1588,7 @@ for (;;)
   struct sockaddr_in accepted;
   #endif
 
-  SOCKLEN_T len = sizeof(accepted);
-  int status;
+  EXIM_SOCKLEN_T len = sizeof(accepted);
   pid_t pid;
 
   /* This code is placed first in the loop, so that it gets obeyed at the
@@ -1523,19 +1609,20 @@ 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" 
+        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]);
+        for (sk = 0; sk < listen_socket_count; sk++)
+          (void)close(listen_sockets[sk]);
 
         /* Reset SIGHUP and SIGCHLD in the child in both cases. */
 
@@ -1549,6 +1636,8 @@ for (;;)
           {
           uschar opt[8];
           uschar *p = opt;
+          uschar *extra[4];
+          int extracount = 1;
 
           signal(SIGALRM, SIG_DFL);
           *p++ = '-';
@@ -1559,8 +1648,29 @@ for (;;)
           if (deliver_force_thaw) *p++ = 'f';
           if (queue_run_local) *p++ = 'l';
           *p = 0;
+          extra[0] = opt;
+
+          /* If -R or -S were on the original command line, ensure they get
+          passed on. */
+
+          if (deliver_selectstring != NULL)
+            {
+            extra[extracount++] = deliver_selectstring_regex? US"-Rr" : US"-R";
+            extra[extracount++] = deliver_selectstring;
+            }
+
+          if (deliver_selectstring_sender != NULL)
+            {
+            extra[extracount++] = deliver_selectstring_sender_regex?
+              US"-Sr" : US"-S";
+            extra[extracount++] = deliver_selectstring_sender;
+            }
+
+          /* Overlay this process with a new execution. */
+
+          (void)child_exec_exim(CEE_EXEC_PANIC, FALSE, NULL, TRUE, extracount,
+            extra[0], extra[1], extra[2], extra[3], extra[4]);
 
-          (void)child_exec_exim(CEE_EXEC_PANIC, FALSE, NULL, TRUE, 1, opt);
           /* Control never returns here. */
           }
 
@@ -1611,7 +1721,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;
@@ -1624,31 +1734,42 @@ 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 
+
+    /* 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 
+    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;  
+      errno = EINTR;
       }
     else
-      {      
+      {
       lcount = select(max_socket + 1, (SELECT_ARG2_TYPE *)&select_listen,
         NULL, NULL, NULL);
-      }   
+      }
 
     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 select_failed=TRUE, so as
     to use the common error code for select/accept below. */
@@ -1743,57 +1864,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
@@ -1818,7 +1889,8 @@ for (;;)
     int sk;
     log_write(0, LOG_MAIN, "pid %d: SIGHUP received: re-exec daemon",
       getpid());
-    for (sk = 0; sk < listen_socket_count; sk++) close(listen_sockets[sk]);
+    for (sk = 0; sk < listen_socket_count; sk++)
+      (void)close(listen_sockets[sk]);
     alarm(0);
     signal(SIGHUP, SIG_IGN);
     sighup_argv[0] = exim_path;