X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/cd59ab18b06626887aecef760c416ae7936924da..35deab6a4e72f25219fa7989be018f39f46a2276:/src/src/daemon.c diff --git a/src/src/daemon.c b/src/src/daemon.c index c11e2ed0a..39da13cc8 100644 --- a/src/src/daemon.c +++ b/src/src/daemon.c @@ -1,10 +1,8 @@ -/* $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 - 2009 */ +/* Copyright (c) University of Cambridge 1995 - 2014 */ /* See the file NOTICE for conditions of use and distribution. */ /* Functions concerned with running Exim as a daemon */ @@ -384,7 +382,7 @@ if (pid == 0) /* Check for a tls-on-connect port */ - if (host_is_tls_on_connect_port(interface_port)) tls_on_connect = TRUE; + if (host_is_tls_on_connect_port(interface_port)) tls_in.on_connect = TRUE; /* Expand smtp_active_hostname if required. We do not do this any earlier, because it may depend on the local interface address (indeed, that is most @@ -641,7 +639,7 @@ if (pid == 0) the data structures if necessary. */ #ifdef SUPPORT_TLS - tls_close(FALSE); + tls_close(TRUE, FALSE); #endif /* Reset SIGHUP and SIGCHLD in the child in both cases. */ @@ -830,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. */ @@ -906,12 +913,67 @@ 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 @@ -980,28 +1042,17 @@ 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) +if (daemon_listen && !inetd_wait_mode) { int *default_smtp_port; int sep; int pct = 0; uschar *s; - uschar *list; + const uschar * list; uschar *local_iface_source = US"local_interfaces"; 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. @@ -1020,8 +1071,7 @@ if (daemon_listen) list = override_local_interfaces; sep = 0; - while ((s = string_nextinlist(&list,&sep,big_buffer,big_buffer_size)) - != NULL) + while ((s = string_nextinlist(&list, &sep, big_buffer, big_buffer_size))) { uschar joinstr[4]; uschar **ptr; @@ -1076,13 +1126,13 @@ if (daemon_listen) list = daemon_smtp_port; sep = 0; - while ((s = string_nextinlist(&list,&sep,big_buffer,big_buffer_size)) != NULL) + while ((s = string_nextinlist(&list, &sep, big_buffer, big_buffer_size))) pct++; default_smtp_port = store_get((pct+1) * sizeof(int)); list = daemon_smtp_port; sep = 0; for (pct = 0; - (s = string_nextinlist(&list,&sep,big_buffer,big_buffer_size)) != NULL; + (s = string_nextinlist(&list, &sep, big_buffer, big_buffer_size)); pct++) { if (isdigit(*s)) @@ -1095,13 +1145,38 @@ if (daemon_listen) else { struct servent *smtp_service = getservbyname(CS s, "tcp"); - if (smtp_service == NULL) + if (!smtp_service) log_write(0, LOG_PANIC_DIE|LOG_CONFIG, "TCP port \"%s\" not found", s); default_smtp_port[pct] = ntohs(smtp_service->s_port); } } default_smtp_port[pct] = 0; + /* Check the list of TLS-on-connect ports and do name lookups if needed */ + + list = tls_in.on_connect_ports; + sep = 0; + while ((s = string_nextinlist(&list, &sep, big_buffer, big_buffer_size))) + if (!isdigit(*s)) + { + list = tls_in.on_connect_ports; + tls_in.on_connect_ports = NULL; + sep = 0; + while ((s = string_nextinlist(&list, &sep, big_buffer, big_buffer_size))) + { + if (!isdigit(*s)) + { + struct servent *smtp_service = getservbyname(CS s, "tcp"); + if (!smtp_service) + log_write(0, LOG_PANIC_DIE|LOG_CONFIG, "TCP port \"%s\" not found", s); + s= string_sprintf("%d", (int)ntohs(smtp_service->s_port)); + } + tls_in.on_connect_ports = string_append_listele(tls_in.on_connect_ports, + ':', s); + } + break; + } + /* Create the list of local interfaces, possibly with ports included. This list may contain references to 0.0.0.0 and ::0 as wildcards. These special values are converted below. */ @@ -1201,6 +1276,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. */ @@ -1226,7 +1306,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 @@ -1237,7 +1318,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. */ @@ -1246,7 +1327,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 @@ -1266,7 +1350,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; @@ -1506,7 +1590,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; @@ -1597,6 +1699,15 @@ else readconf_printtime(queue_interval)); } +/* Do any work it might be useful to amortize over our children +(eg: compile regex) */ + +deliver_init(); +dns_pattern_init(); + +#ifdef WITH_CONTENT_SCAN +malware_init(); +#endif /* Close the log so it can be renamed and moved. In the few cases below where this long-running process writes to the log (always exceptional conditions), it @@ -1624,122 +1735,166 @@ for (;;) 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; - /* 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. */ + 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); + } + } + } - if (queue_interval > 0 && - (queue_run_max <= 0 || queue_run_count < queue_run_max)) + sigalrm_seen = FALSE; + alarm(resignal_interval); + } + + 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[5]; - 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 @@ -1879,8 +2034,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); + } } } @@ -1939,5 +2098,6 @@ for (;;) /* Control never reaches here */ } +/* vi: aw ai sw=2 +*/ /* End of exim_daemon.c */ -