X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/3d23590382767a12bc27fb9e5f1c546293cf84b5..d7d7b7b91dd75cec636fc144da7e27eed860f971:/src/src/daemon.c diff --git a/src/src/daemon.c b/src/src/daemon.c index 19a8cea35..fade1a8d8 100644 --- a/src/src/daemon.c +++ b/src/src/daemon.c @@ -1,10 +1,10 @@ -/* $Cambridge: exim/src/src/daemon.c,v 1.2 2004/11/10 10:29:56 ph10 Exp $ */ +/* $Cambridge: exim/src/src/daemon.c,v 1.14 2006/02/07 11:19:00 ph10 Exp $ */ /************************************************* * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2004 */ +/* Copyright (c) University of Cambridge 1995 - 2006 */ /* 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); } @@ -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; @@ -362,7 +362,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 +419,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 +433,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", @@ -603,8 +603,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. */ @@ -691,7 +691,7 @@ if (smtp_out != NULL) strerror(errno)); smtp_out = NULL; } -else close(accept_socket); +else (void)close(accept_socket); if (smtp_in != NULL) { @@ -700,7 +700,7 @@ if (smtp_in != NULL) 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 +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; + } + } + } + } +} + /************************************************* @@ -898,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 @@ -1122,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 */ - 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 +1249,6 @@ if (daemon_listen) { BOOL wildcard; ip_address_item *ipa2; - int retries = 9; int af; if (Ustrchr(ipa->address, ':') != NULL) @@ -1244,19 +1320,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 +1366,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 @@ -1338,9 +1417,9 @@ if (running_in_test_harness || write_pid) f = Ufopen(pid_file_path, "wb"); if (f != NULL) { - fprintf(f, "%d\n", (int)getpid()); - fchmod(fileno(f), 0644); - fclose(f); + (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); } else @@ -1377,7 +1456,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. */ @@ -1501,8 +1580,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 +1601,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. */ @@ -1611,7 +1690,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; @@ -1625,16 +1704,44 @@ for (;;) DEBUG(D_any) debug_printf("Listening...\n"); - if ((lcount = select(max_socket + 1, (SELECT_ARG2_TYPE *)&select_listen, - NULL, NULL, NULL)) < 0) + /* 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 < 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) { @@ -1726,57 +1833,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 @@ -1785,7 +1842,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 @@ -1801,7 +1858,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;