Set FD_CLOEXEC on SMTP sockets after forking to handle the connection.
[exim.git] / src / src / daemon.c
index fb74d83f2121abacfebe74146dffdf533f84436f..3db9be374214e9e3f37b54af3e1c219c8c7f2cd1 100644 (file)
@@ -1,10 +1,10 @@
-/* $Cambridge: exim/src/src/daemon.c,v 1.12 2005/06/22 15:44:37 ph10 Exp $ */
+/* $Cambridge: exim/src/src/daemon.c,v 1.27 2009/11/16 19:50:36 nm4 Exp $ */
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2005 */
+/* Copyright (c) University of Cambridge 1995 - 2009 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions concerned with running Exim as a daemon */
@@ -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;
   }
@@ -240,7 +241,7 @@ subprocess because it might take time. */
 
 if (smtp_load_reserve >= 0)
   {
-  load_average = os_getloadavg();
+  load_average = OS_GETLOADAVG();
   if (smtp_reserve_hosts == NULL && load_average > smtp_load_reserve)
     {
     DEBUG(D_any) debug_printf("rejecting SMTP connection: load average = %.2f\n",
@@ -364,10 +365,13 @@ if (pid == 0)
   int old_pool = store_pool;
   int save_debug_selector = debug_selector;
   BOOL local_queue_only;
+  BOOL session_local_queue_only;
   #ifdef SA_NOCLDWAIT
   struct sigaction act;
   #endif
 
+  smtp_accept_count++;    /* So that it includes this process */
+
   /* May have been modified for the subprocess */
 
   log_write_selector = use_log_write_selector;
@@ -410,7 +414,7 @@ if (pid == 0)
   /* Initialize the queueing flags */
 
   queue_check_only();
-  local_queue_only = queue_only;
+  session_local_queue_only = queue_only;
 
   /* Close the listening sockets, and set the SIGCHLD handler to SIG_IGN.
   We also attempt to set things up so that children are automatically reaped,
@@ -419,7 +423,14 @@ 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]);
+
+  /* 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. */
+  (void)fcntl(accept_socket, F_SETFD,
+              fcntl(accept_socket, F_GETFD) | FD_CLOEXEC);
+  (void)fcntl(dup_accept_socket, F_SETFD,
+              fcntl(dup_accept_socket, F_GETFD) | FD_CLOEXEC);
 
   #ifdef SA_NOCLDWAIT
   act.sa_handler = SIG_IGN;
@@ -454,20 +465,27 @@ if (pid == 0)
   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
+  set the session_local_queue_only flag, which is initialized from the
   configured value and may therefore already be TRUE. Leave logging
-  till later so it will have a message id attached. */
+  till later so it will have a message id attached. Note that there is no
+  possibility of re-calculating this per-message, because the value of
+  smtp_accept_count does not change in this subprocess. */
 
-  if (smtp_accept_queue > 0 && smtp_accept_count >= smtp_accept_queue)
+  if (smtp_accept_queue > 0 && smtp_accept_count > smtp_accept_queue)
     {
-    local_queue_only = TRUE;
+    session_local_queue_only = TRUE;
     queue_only_reason = 1;
     }
 
   /* 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 +517,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 +526,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);
       }
 
@@ -540,26 +560,37 @@ if (pid == 0)
     store_reset(reset_point);
 
     /* If queue_only is set or if there are too many incoming connections in
-    existence, local_queue_only will be TRUE. If it is not, check whether we
-    have received too many messages in this session for immediate delivery. If
-    not, and queue_only_load is set, check that the load average is below it.
-    Note that, once set, local_queue_only remains set for any subsequent
-    messages on the same SMTP connection. This is a deliberate choice; even
-    though the load average may fall, it doesn't seem right to deliver later
-    messages on the same call when not delivering earlier ones. */
-
-    if (!local_queue_only)
+    existence, session_local_queue_only will be TRUE. If it is not, check
+    whether we have received too many messages in this session for immediate
+    delivery. */
+
+    if (!session_local_queue_only &&
+        smtp_accept_queue_per_connection > 0 &&
+        receive_messagecount > smtp_accept_queue_per_connection)
       {
-      if (smtp_accept_queue_per_connection > 0 &&
-          receive_messagecount > smtp_accept_queue_per_connection)
-        {
-        local_queue_only = TRUE;
-        queue_only_reason = 2;
-        }
-      else if (queue_only_load >= 0)
+      session_local_queue_only = TRUE;
+      queue_only_reason = 2;
+      }
+
+    /* Initialize local_queue_only from session_local_queue_only. If it is not
+    true, and queue_only_load is set, check that the load average is below it.
+    If local_queue_only is set by this means, we also set if for the session if
+    queue_only_load_latch is true (the default). This means that, once set,
+    local_queue_only remains set for any subsequent messages on the same SMTP
+    connection. This is a deliberate choice; even though the load average may
+    fall, it doesn't seem right to deliver later messages on the same call when
+    not delivering earlier ones. However, the are special circumstances such as
+    very long-lived connections from scanning appliances where this is not the
+    best strategy. In such cases, queue_only_load_latch should be set false. */
+
+    local_queue_only = session_local_queue_only;
+    if (!local_queue_only && queue_only_load >= 0)
+      {
+      local_queue_only = (load_average = OS_GETLOADAVG()) > queue_only_load;
+      if (local_queue_only)
         {
-        local_queue_only = (load_average = os_getloadavg()) > queue_only_load;
-        if (local_queue_only) queue_only_reason = 3;
+        queue_only_reason = 3;
+        if (queue_only_load_latch) session_local_queue_only = TRUE;
         }
       }
 
@@ -603,8 +634,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 +711,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. */
@@ -870,6 +902,7 @@ There are no arguments to this function, and it never returns. */
 void
 daemon_go(void)
 {
+struct passwd *pw;
 int *listen_sockets = NULL;
 int listen_socket_count = 0;
 ip_address_item *addresses = NULL;
@@ -1208,9 +1241,9 @@ 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);
+  (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 */
 
@@ -1320,7 +1353,7 @@ 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);
@@ -1366,7 +1399,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
@@ -1414,11 +1447,10 @@ 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)
     {
     (void)fprintf(f, "%d\n", (int)getpid());
-    (void)fchmod(fileno(f), 0644);
     (void)fclose(f);
     DEBUG(D_any) debug_printf("pid written to %s\n", pid_file_path);
     }
@@ -1443,6 +1475,14 @@ cannot do this. */
 
 exim_setugid(exim_uid, exim_gid, geteuid()==root_uid, US"running as a daemon");
 
+/* Update the originator_xxx fields so that received messages as listed as
+coming from Exim, not whoever started the daemon. */
+
+originator_uid = exim_uid;
+originator_gid = exim_gid;
+originator_login = ((pw = getpwuid(exim_uid)) != NULL)?
+  string_copy_malloc(US pw->pw_name) : US"exim";
+
 /* 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). */
 
@@ -1580,7 +1620,7 @@ for (;;)
   struct sockaddr_in accepted;
   #endif
 
-  EXIM_SOCKLEN_T len = sizeof(accepted);
+  EXIM_SOCKLEN_T len;
   pid_t pid;
 
   /* This code is placed first in the loop, so that it gets obeyed at the
@@ -1613,7 +1653,8 @@ for (;;)
 
         /* 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. */
 
@@ -1627,6 +1668,8 @@ for (;;)
           {
           uschar opt[8];
           uschar *p = opt;
+          uschar *extra[5];
+          int extracount = 1;
 
           signal(SIGALRM, SIG_DFL);
           *p++ = '-';
@@ -1637,8 +1680,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. */
           }
 
@@ -1751,6 +1815,7 @@ for (;;)
           {
           if (FD_ISSET(listen_sockets[sk], &select_listen))
             {
+            len = sizeof(accepted);
             accept_socket = accept(listen_sockets[sk],
               (struct sockaddr *)&accepted, &len);
             FD_CLR(listen_sockets[sk], &select_listen);
@@ -1857,7 +1922,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;