-/* $Cambridge: exim/src/src/daemon.c,v 1.4 2005/01/04 10:00:42 ph10 Exp $ */
+/* $Cambridge: exim/src/src/daemon.c,v 1.10 2005/03/15 14:09:12 ph10 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
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;
{
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;
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;
/* 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",
+/*************************************************
+* 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;
+ }
+ }
+ }
+ }
+}
+
/*************************************************
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
}
}
-/* 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)
{
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
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]);
if (daemon_listen)
{
- int sk, lcount;
+ int sk, lcount, select_errno;
int max_socket = 0;
BOOL select_failed = FALSE;
fd_set select_listen;
}
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. */
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