X-Git-Url: https://vcs.fsf.org/?a=blobdiff_plain;f=src%2Fsrc%2Fdaemon.c;h=de794f693ddc54713f2cb3b8b4be4900be833949;hb=9ee44efbe7474892e931cd517195cb690e765ab6;hp=7b84490e92d5df39e0f441077636af5459e9b393;hpb=184e88237dea64ce48076cdd0184612d057cbafd;p=exim.git diff --git a/src/src/daemon.c b/src/src/daemon.c index 7b84490e9..de794f693 100644 --- a/src/src/daemon.c +++ b/src/src/daemon.c @@ -1,10 +1,8 @@ -/* $Cambridge: exim/src/src/daemon.c,v 1.19 2007/01/08 10:50:17 ph10 Exp $ */ - /************************************************* * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2007 */ +/* 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 */ @@ -31,8 +29,8 @@ static smtp_slot empty_smtp_slot = { 0, NULL }; * Local static variables * *************************************************/ -static volatile BOOL sigchld_seen; -static volatile BOOL sighup_seen; +static SIGNAL_BOOL sigchld_seen; +static SIGNAL_BOOL sighup_seen; static int accept_retry_count = 0; static int accept_retry_errno; @@ -241,7 +239,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", @@ -365,10 +363,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; @@ -411,7 +412,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, @@ -422,6 +423,13 @@ if (pid == 0) 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; sigemptyset(&(act.sa_mask)); @@ -455,20 +463,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: ") and writing another one is + unnecessary clutter. */ if (!smtp_start_session()) { @@ -500,6 +515,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 */ @@ -508,6 +524,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); } @@ -541,26 +558,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; } } @@ -800,8 +828,17 @@ 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); + DEBUG(D_any) + { + debug_printf("child %d ended: status=0x%x\n", (int)pid, status); +#ifdef WCOREDUMP + if (WIFEXITED(status)) + debug_printf(" normal exit, %d\n", WEXITSTATUS(status)); + else if (WIFSIGNALED(status)) + debug_printf(" signal exit, signal %d%s\n", WTERMSIG(status), + WCOREDUMP(status) ? " (core dumped)" : ""); +#endif + } /* If it's a listening daemon for which we are keeping track of individual subprocesses, deal with an accepting process that has terminated. */ @@ -872,15 +909,71 @@ 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; +time_t last_connection_time = (time_t)0; /* If any debugging options are set, turn on the D_pid bit so that all debugging lines get the pid added. */ DEBUG(D_any|D_v) debug_selector |= D_pid; +if (inetd_wait_mode) + { + int on = 1; + + listen_socket_count = 1; + listen_sockets = store_get(sizeof(int *)); + (void) close(3); + if (dup2(0, 3) == -1) + { + log_write(0, LOG_MAIN|LOG_PANIC_DIE, + "failed to dup inetd socket safely away: %s", strerror(errno)); + } + listen_sockets[0] = 3; + (void) close(0); + (void) close(1); + (void) close(2); + exim_nullstd(); + + if (debug_file == stderr) + { + /* need a call to log_write before call to open debug_file, so that + log.c:file_path has been initialised. This is unfortunate. */ + log_write(0, LOG_MAIN, "debugging Exim in inetd wait mode starting"); + + fclose(debug_file); + debug_file = NULL; + exim_nullstd(); /* re-open fd2 after we just closed it again */ + debug_logging_activate(US"-wait", NULL); + } + + DEBUG(D_any) debug_printf("running in inetd wait mode\n"); + + /* As per below, when creating sockets ourselves, we handle tcp_nodelay for + our own buffering; we assume though that inetd set the socket REUSEADDR. */ + + if (tcp_nodelay) setsockopt(3, IPPROTO_TCP, TCP_NODELAY, + (uschar *)(&on), sizeof(on)); + } + + +if (inetd_wait_mode || daemon_listen) + { + /* If any option requiring a load average to be available during the + reception of a message is set, call os_getloadavg() while we are root + for those OS for which this is necessary the first time it is called (in + order to perform an "open" on the kernel memory file). */ + + #ifdef LOAD_AVG_NEEDS_ROOT + if (queue_only_load >= 0 || smtp_load_reserve >= 0 || + (deliver_queue_load_max >= 0 && deliver_drop_privilege)) + (void)os_getloadavg(); + #endif + } + /* Do the preparation for setting up a listener on one or more interfaces, and possible on various ports. This is controlled by the combination of @@ -949,7 +1042,7 @@ The preparation code decodes options and sets up the relevant data. We do this first, so that we can return non-zero if there are any syntax errors, and also write to stderr. */ -if (daemon_listen) +else if (daemon_listen) { int *default_smtp_port; int sep; @@ -960,17 +1053,6 @@ if (daemon_listen) ip_address_item *ipa; ip_address_item **pipa; - /* If any option requiring a load average to be available during the - reception of a message is set, call os_getloadavg() while we are root - for those OS for which this is necessary the first time it is called (in - order to perform an "open" on the kernel memory file). */ - - #ifdef LOAD_AVG_NEEDS_ROOT - 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 explicitly used to force it. Then scan the string given to -oX. Any items that contain neither a dot nor a colon are used to override daemon_smtp_port. @@ -1170,6 +1252,11 @@ if (daemon_listen) listen_socket_count++; listen_sockets = store_get(sizeof(int *) * listen_socket_count); + } /* daemon_listen but not inetd_wait_mode */ + +if (daemon_listen) + { + /* Do a sanity check on the max connects value just to save us from getting a huge amount of store. */ @@ -1195,7 +1282,8 @@ if (daemon_listen) /* 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. +we know about, but then re-open stdin, stdout, and stderr to /dev/null. Also +do this for inetd_wait mode. 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 @@ -1206,7 +1294,7 @@ 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) +if (background_daemon || inetd_wait_mode) { log_close_all(); /* Just in case anything was logged earlier */ search_tidyup(); /* Just in case any were used in reading the config. */ @@ -1215,7 +1303,10 @@ if (background_daemon) (void)close(2); exim_nullstd(); /* Connect stdin/stdout/stderr to /dev/null */ log_stderr = NULL; /* So no attempt to copy paniclog output */ + } +if (background_daemon) + { /* 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 anything, because the controlling terminal has long gone. Otherwise, fork, in @@ -1235,7 +1326,7 @@ if (background_daemon) /* We are now in the disconnected, daemon process (unless debugging). Set up the listening sockets if required. */ -if (daemon_listen) +if (daemon_listen && !inetd_wait_mode) { int sk; int on = 1; @@ -1444,6 +1535,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). */ @@ -1467,7 +1566,25 @@ sigalrm_seen = (queue_interval > 0); /* Log the start up of a daemon - at least one of listening or queue running must be set up. */ -if (daemon_listen) +if (inetd_wait_mode) + { + uschar *p = big_buffer; + + if (inetd_wait_timeout >= 0) + sprintf(CS p, "terminating after %d seconds", inetd_wait_timeout); + else + sprintf(CS p, "with no wait timeout"); + + log_write(0, LOG_MAIN, + "exim %s daemon started: pid=%d, launched with listening socket, %s", + version_string, getpid(), big_buffer); + set_process_info("daemon: pre-listening socket"); + + /* set up the timeout logic */ + sigalrm_seen = 1; + } + +else if (daemon_listen) { int i, j; int smtp_ports = 0; @@ -1581,126 +1698,170 @@ 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 - start, before the first wait. This causes the first queue-runner to be - started immediately. */ + start, before the first wait, for the queue-runner case, so that the first + one can be started immediately. + + The other option is that we have an inetd wait timeout specified to -bw. */ if (sigalrm_seen) { - DEBUG(D_any) debug_printf("SIGALRM received\n"); + if (inetd_wait_timeout > 0) + { + time_t resignal_interval = inetd_wait_timeout; + + if (last_connection_time == (time_t)0) + { + DEBUG(D_any) + debug_printf("inetd wait timeout expired, but still not seen first message, ignoring\n"); + } + else + { + time_t now = time(NULL); + if (now == (time_t)-1) + { + DEBUG(D_any) debug_printf("failed to get time: %s\n", strerror(errno)); + } + else + { + if ((now - last_connection_time) >= inetd_wait_timeout) + { + DEBUG(D_any) + debug_printf("inetd wait timeout %d expired, ending daemon\n", + inetd_wait_timeout); + log_write(0, LOG_MAIN, "exim %s daemon terminating, inetd wait timeout reached.\n", + version_string); + exit(EXIT_SUCCESS); + } + else + { + resignal_interval -= (now - last_connection_time); + } + } + } - /* Do a full queue run in a child process, if required, unless we already - have enough queue runners on the go. If we are not running as root, a - re-exec is required. */ + sigalrm_seen = FALSE; + alarm(resignal_interval); + } - if (queue_interval > 0 && - (queue_run_max <= 0 || queue_run_count < queue_run_max)) + else { - if ((pid = fork()) == 0) - { - int sk; + DEBUG(D_any) debug_printf("SIGALRM received\n"); - DEBUG(D_any) debug_printf("Starting queue-runner: pid %d\n", - (int)getpid()); + /* Do a full queue run in a child process, if required, unless we already + have enough queue runners on the go. If we are not running as root, a + re-exec is required. */ - /* 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 (queue_interval > 0 && + (queue_run_max <= 0 || queue_run_count < queue_run_max)) + { + if ((pid = fork()) == 0) + { + int sk; - if (debug_daemon) debug_selector = 0; + DEBUG(D_any) debug_printf("Starting queue-runner: pid %d\n", + (int)getpid()); - /* Close any open listening sockets in the child */ + /* 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. */ - for (sk = 0; sk < listen_socket_count; sk++) - (void)close(listen_sockets[sk]); + if (debug_daemon) debug_selector = 0; - /* Reset SIGHUP and SIGCHLD in the child in both cases. */ + /* Close any open listening sockets in the child */ - signal(SIGHUP, SIG_DFL); - signal(SIGCHLD, SIG_DFL); + for (sk = 0; sk < listen_socket_count; sk++) + (void)close(listen_sockets[sk]); - /* Re-exec if privilege has been given up, unless deliver_drop_ - privilege is set. Reset SIGALRM before exec(). */ + /* Reset SIGHUP and SIGCHLD in the child in both cases. */ - if (geteuid() != root_uid && !deliver_drop_privilege) - { - uschar opt[8]; - uschar *p = opt; - uschar *extra[4]; - int extracount = 1; + signal(SIGHUP, SIG_DFL); + signal(SIGCHLD, SIG_DFL); - signal(SIGALRM, SIG_DFL); - *p++ = '-'; - *p++ = 'q'; - if (queue_2stage) *p++ = 'q'; - if (queue_run_first_delivery) *p++ = 'i'; - if (queue_run_force) *p++ = 'f'; - 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; - } + /* Re-exec if privilege has been given up, unless deliver_drop_ + privilege is set. Reset SIGALRM before exec(). */ - if (deliver_selectstring_sender != NULL) + if (geteuid() != root_uid && !deliver_drop_privilege) { - extra[extracount++] = deliver_selectstring_sender_regex? - US"-Sr" : US"-S"; - extra[extracount++] = deliver_selectstring_sender; + uschar opt[8]; + uschar *p = opt; + uschar *extra[5]; + int extracount = 1; + + signal(SIGALRM, SIG_DFL); + *p++ = '-'; + *p++ = 'q'; + if (queue_2stage) *p++ = 'q'; + if (queue_run_first_delivery) *p++ = 'i'; + if (queue_run_force) *p++ = 'f'; + 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]); + + /* Control never returns here. */ } - /* 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]); + /* No need to re-exec; SIGALRM remains set to the default handler */ - /* Control never returns here. */ + queue_run(NULL, NULL, FALSE); + _exit(EXIT_SUCCESS); } - /* No need to re-exec; SIGALRM remains set to the default handler */ - - queue_run(NULL, NULL, FALSE); - _exit(EXIT_SUCCESS); - } - - if (pid < 0) - { - log_write(0, LOG_MAIN|LOG_PANIC, "daemon: fork of queue-runner " - "process failed: %s", strerror(errno)); - log_close_all(); - } - else - { - int i; - for (i = 0; i < queue_run_max; ++i) + if (pid < 0) + { + log_write(0, LOG_MAIN|LOG_PANIC, "daemon: fork of queue-runner " + "process failed: %s", strerror(errno)); + log_close_all(); + } + else { - if (queue_pid_slots[i] <= 0) + int i; + for (i = 0; i < queue_run_max; ++i) { - queue_pid_slots[i] = pid; - queue_run_count++; - break; + if (queue_pid_slots[i] <= 0) + { + queue_pid_slots[i] = pid; + queue_run_count++; + break; + } } + DEBUG(D_any) debug_printf("%d queue-runner process%s running\n", + queue_run_count, (queue_run_count == 1)? "" : "es"); } - DEBUG(D_any) debug_printf("%d queue-runner process%s running\n", - queue_run_count, (queue_run_count == 1)? "" : "es"); } - } - /* Reset the alarm clock */ + /* Reset the alarm clock */ - sigalrm_seen = FALSE; - alarm(queue_interval); - } + sigalrm_seen = FALSE; + alarm(queue_interval); + } + + } /* sigalrm_seen */ /* Sleep till a connection happens if listening, and handle the connection if @@ -1776,6 +1937,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); @@ -1839,8 +2001,12 @@ for (;;) /* If select/accept succeeded, deal with the connection. */ if (accept_socket >= 0) + { + if (inetd_wait_timeout) + last_connection_time = time(NULL); handle_smtp_call(listen_sockets, listen_socket_count, accept_socket, (struct sockaddr *)&accepted); + } } }