-/* $Cambridge: exim/src/src/daemon.c,v 1.8 2005/02/16 15:24:21 ph10 Exp $ */
-
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2005 */
+/* Copyright (c) University of Cambridge 1995 - 2016 */
/* See the file NOTICE for conditions of use and distribution. */
/* Functions concerned with running Exim as a daemon */
} smtp_slot;
/* An empty slot for initializing (Standard C does not allow constructor
-expressions in assigments except as initializers in declarations). */
+expressions in assignments except as initializers in declarations). */
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;
{
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 wfptr = 0;
-int use_log_write_selector = log_write_selector;
+int save_log_selector = *log_selector;
uschar *whofrom = NULL;
void *reset_point = store_get(0);
input stream. These operations fail only the exceptional circumstances. Note
that never_error() won't use smtp_out if it is NULL. */
-smtp_out = fdopen(accept_socket, "wb");
-if (smtp_out == NULL)
+if (!(smtp_out = fdopen(accept_socket, "wb")))
{
never_error(US"daemon: fdopen() for smtp_out failed", US"", errno);
goto ERROR_RETURN;
}
-dup_accept_socket = dup(accept_socket);
-if (dup_accept_socket < 0)
+if ((dup_accept_socket = dup(accept_socket)) < 0)
{
never_error(US"daemon: couldn't dup socket descriptor",
US"Connection setup failed", errno);
goto ERROR_RETURN;
}
-smtp_in = fdopen(dup_accept_socket, "rb");
-if (smtp_in == NULL)
+if (!(smtp_in = fdopen(dup_accept_socket, "rb")))
{
never_error(US"daemon: fdopen() for smtp_in failed",
US"Connection setup failed", errno);
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;
}
whofrom = string_append(whofrom, &wfsize, &wfptr, 3, "[", sender_host_address, "]");
-if ((log_extra_selector & LX_incoming_port) != 0)
+if (LOGGING(incoming_port))
whofrom = string_append(whofrom, &wfsize, &wfptr, 2, ":", string_sprintf("%d",
sender_host_port));
-if ((log_extra_selector & LX_incoming_interface) != 0)
+if (LOGGING(incoming_interface))
whofrom = string_append(whofrom, &wfsize, &wfptr, 4, " I=[",
interface_address, "]:", string_sprintf("%d", interface_port));
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",
int other_host_count = 0; /* keep a count of non matches to optimise */
for (i = 0; i < smtp_accept_max; ++i)
- {
- if (smtp_slots[i].host_address != NULL)
+ if (smtp_slots[i].host_address)
{
if (Ustrcmp(sender_host_address, smtp_slots[i].host_address) == 0)
host_accept_count++;
((smtp_accept_count - other_host_count) < max_for_this_host))
break;
}
- }
if (host_accept_count >= max_for_this_host)
{
selector is set, check whether the host is on the list for logging. If not,
arrange to unset the selector in the subprocess. */
-if ((log_write_selector & L_smtp_connection) != 0)
+if (LOGGING(smtp_connection))
{
uschar *list = hosts_connection_nolog;
+ memset(sender_host_cache, 0, sizeof(sender_host_cache));
if (list != NULL && verify_check_host(&list) == OK)
- use_log_write_selector &= ~L_smtp_connection;
+ save_log_selector &= ~L_smtp_connection;
else
log_write(L_smtp_connection, LOG_MAIN, "SMTP connection from %s "
"(TCP/IP connection count = %d)", whofrom, smtp_accept_count + 1);
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;
+ 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;
+ *log_selector = save_log_selector;
/* Get the local interface address into permanent store */
/* 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
likely what it depends on.) */
smtp_active_hostname = primary_hostname;
- if (raw_active_hostname != NULL)
+ if (raw_active_hostname)
{
- uschar *nah = expand_string(raw_active_hostname);
- if (nah == NULL)
+ uschar * nah = expand_string(raw_active_hostname);
+ if (!nah)
{
if (!expand_string_forcedfail)
{
_exit(EXIT_FAILURE);
}
}
- else if (nah[0] != 0) smtp_active_hostname = nah;
+ else if (*nah) smtp_active_hostname = nah;
}
/* 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,
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;
/* 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",
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())
{
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 */
}
else
{
- mac_smtp_fflush();
+ if (smtp_out)
+ {
+ int i, fd = fileno(smtp_in);
+ uschar buf[128];
+
+ mac_smtp_fflush();
+ /* drain socket, for clean TCP FINs */
+ if (fcntl(fd, F_SETFL, O_NONBLOCK) == 0)
+ for(i = 16; read(fd, buf, sizeof(buf)) > 0 && i > 0; ) i--;
+ }
search_tidyup();
- _exit((rc == 0)? EXIT_SUCCESS : EXIT_FAILURE);
+ smtp_log_no_mail(); /* Log no mail if configured */
+
+ /*XXX should we pause briefly, hoping that the client will be the
+ active TCP closer hence get the TCP_WAIT endpoint? */
+ DEBUG(D_receive) debug_printf("SMTP>>(close on process exit)\n");
+ _exit(rc ? EXIT_FAILURE : EXIT_SUCCESS);
}
/* Show the recipients when debugging */
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)
- {
- local_queue_only = (load_average = os_getloadavg()) > queue_only_load;
- if (local_queue_only) queue_only_reason = 3;
- }
+ 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. */
+
+ if ( !(local_queue_only = session_local_queue_only)
+ && queue_only_load >= 0
+ && (local_queue_only = (load_average = OS_GETLOADAVG()) > queue_only_load)
+ )
+ {
+ queue_only_reason = 3;
+ if (queue_only_load_latch) session_local_queue_only = TRUE;
}
/* Log the queueing here, when it will get a message id attached, but
if (local_queue_only) switch(queue_only_reason)
{
- case 1:
- log_write(L_delay_delivery,
+ case 1: log_write(L_delay_delivery,
LOG_MAIN, "no immediate delivery: too many connections "
"(%d, max %d)", smtp_accept_count, smtp_accept_queue);
- break;
+ break;
- case 2:
- log_write(L_delay_delivery,
+ case 2: log_write(L_delay_delivery,
LOG_MAIN, "no immediate delivery: more than %d messages "
"received in one connection", smtp_accept_queue_per_connection);
- break;
+ break;
- case 3:
- log_write(L_delay_delivery,
+ case 3: log_write(L_delay_delivery,
LOG_MAIN, "no immediate delivery: load average %.2f",
(double)load_average/1000.0);
- break;
+ break;
}
/* If a delivery attempt is required, spin off a new process to handle it.
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. */
#ifdef SUPPORT_TLS
- tls_close(FALSE);
+ tls_close(TRUE, FALSE);
#endif
/* Reset SIGHUP and SIGCHLD in the child in both cases. */
if (geteuid() != root_uid && !deliver_drop_privilege)
{
signal(SIGALRM, SIG_DFL);
- (void)child_exec_exim(CEE_EXEC_PANIC, FALSE, NULL, FALSE, 2, US"-Mc",
- message_id);
+ (void)child_exec_exim(CEE_EXEC_PANIC, FALSE, NULL, FALSE,
+ 2, US"-Mc", message_id);
/* Control does not return here. */
}
DEBUG(D_any) debug_printf("forked delivery process %d\n", (int)dpid);
}
else
- {
log_write(0, LOG_MAIN|LOG_PANIC, "daemon: delivery process fork "
"failed: %s", strerror(errno));
- }
}
}
}
remember the pid for ticking off when the child completes. */
if (pid < 0)
- {
never_error(US"daemon: accept process fork failed", US"Fork failed", errno);
- }
else
{
int i;
for (i = 0; i < smtp_accept_max; ++i)
- {
if (smtp_slots[i].pid <= 0)
{
smtp_slots[i].pid = pid;
smtp_accept_count++;
break;
}
- }
DEBUG(D_any) debug_printf("%d SMTP accept process%s running\n",
smtp_accept_count, (smtp_accept_count == 1)? "" : "es");
}
/* 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 (smtp_out)
{
- 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 (smtp_in)
{
- 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. */
+log_close_all();
store_reset(reset_point);
sender_host_address = NULL;
}
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
+ /* 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)
+ if (smtp_slots)
{
for (i = 0; i < smtp_accept_max; i++)
- {
if (smtp_slots[i].pid == pid)
{
- if (smtp_slots[i].host_address != NULL)
+ if (smtp_slots[i].host_address)
store_free(smtp_slots[i].host_address);
smtp_slots[i] = empty_smtp_slot;
if (--smtp_accept_count < 0) smtp_accept_count = 0;
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)
+ if (queue_pid_slots)
{
- for (i = 0; i < queue_run_max; i++)
- {
+ int max = atoi(CS expand_string(queue_run_max));
+ for (i = 0; i < max; i++)
if (queue_pid_slots[i] == pid)
{
queue_pid_slots[i] = 0;
queue_run_count, (queue_run_count == 1)? "" : "es");
break;
}
- }
}
}
}
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;
+int local_queue_run_max = atoi(CS expand_string(queue_run_max));
/* 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)
+ {
+ 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)
+ if (setsockopt(3, IPPROTO_TCP, TCP_NODELAY, US &on, sizeof(on)))
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to set socket NODELAY: %s",
+ strerror(errno));
+ }
+
+
+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
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.
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;
{
joinstr[0] = sep;
joinstr[1] = ' ';
- *ptr = string_cat(*ptr, sizeptr, ptrptr, US"<", 1);
+ *ptr = string_catn(*ptr, sizeptr, ptrptr, US"<", 1);
}
- *ptr = string_cat(*ptr, sizeptr, ptrptr, joinstr, 2);
- *ptr = string_cat(*ptr, sizeptr, ptrptr, s, Ustrlen(s));
+ *ptr = string_catn(*ptr, sizeptr, ptrptr, joinstr, 2);
+ *ptr = string_cat (*ptr, sizeptr, ptrptr, s);
}
if (new_smtp_port != NULL)
}
/* Create a list of default SMTP ports, to be used if local_interfaces
- contains entries without explict ports. First count the number of ports, then
+ contains entries without explicit ports. First count the number of ports, then
build a translated list in a vector. */
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))
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. */
for (ipa = addresses; ipa != NULL; ipa = ipa->next)
listen_socket_count++;
- listen_sockets = store_get(sizeof(int *) * 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. */
/* 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
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. */
- close(0); /* Get rid of stdin/stdout/stderr */
- close(1);
- close(2);
- exim_nullstd(); /* Connect stdin/stdout/stderr to /dev/null */
+ (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 (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
+ 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(). */
/* 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;
ip_address_item *ipa;
/* For each IP address, create a socket, bind it to the appropriate port, and
{
BOOL wildcard;
ip_address_item *ipa2;
- int retries = 9;
int af;
if (Ustrchr(ipa->address, ':') != NULL)
wildcard = ipa->address[0] == 0;
}
- listen_sockets[sk] = ip_socket(SOCK_STREAM, af);
- if (listen_sockets[sk] < 0)
+ if ((listen_sockets[sk] = ip_socket(SOCK_STREAM, af)) < 0)
{
if (check_special_case(0, addresses, ipa, FALSE))
{
necessary for (some release of) USAGI Linux; other IP stacks fail at the
listen() stage instead. */
+#ifdef TCP_FASTOPEN
+ tcp_fastopen_ok = TRUE;
+#endif
for(;;)
{
uschar *msg, *addr;
{
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)
- {
if (wildcard)
debug_printf("listening on all interfaces (IPv%c) port %d\n",
- (af == AF_INET6)? '6' : '4', ipa->port);
+ af == AF_INET6 ? '6' : '4', ipa->port);
else
debug_printf("listening on %s port %d\n", ipa->address, ipa->port);
+
+#ifdef TCP_FASTOPEN
+ if (setsockopt(listen_sockets[sk], IPPROTO_TCP, TCP_FASTOPEN,
+ &smtp_connect_backlog, sizeof(smtp_connect_backlog)))
+ {
+ DEBUG(D_any) debug_printf("setsockopt FASTOPEN: %s\n", strerror(errno));
+ tcp_fastopen_ok = FALSE;
}
+#endif
/* Start listening on the bound socket, establishing the maximum backlog of
connections that is allowed. On success, continue to the next address. */
if (!check_special_case(errno, addresses, ipa, TRUE))
log_write(0, LOG_PANIC_DIE, "listen() failed on interface %s: %s",
- wildcard? ((af == AF_INET6)? US"(any IPv6)" : US"(any IPv4)") :
- ipa->address,
+ wildcard
+ ? af == AF_INET6 ? US"(any IPv6)" : US"(any IPv4)" : ipa->address,
strerror(errno));
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
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)
{
- fprintf(f, "%d\n", (int)getpid());
- fchmod(fileno(f), 0644);
- fclose(f);
+ (void)fprintf(f, "%d\n", (int)getpid());
+ (void)fclose(f);
DEBUG(D_any) debug_printf("pid written to %s\n", pid_file_path);
}
else
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). */
-if (queue_interval > 0 && queue_run_max > 0)
+if (queue_interval > 0 && local_queue_run_max > 0)
{
int i;
- queue_pid_slots = store_get(queue_run_max * sizeof(pid_t));
- for (i = 0; i < queue_run_max; i++) queue_pid_slots[i] = 0;
+ queue_pid_slots = store_get(local_queue_run_max * sizeof(pid_t));
+ for (i = 0; i < local_queue_run_max; i++) queue_pid_slots[i] = 0;
}
/* Set up the handler for termination of child processes. */
/* 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(%s): pre-listening socket", version_string);
+
+ /* set up the timeout logic */
+ sigalrm_seen = 1;
+ }
+
+else if (daemon_listen)
{
int i, j;
int smtp_ports = 0;
int smtps_ports = 0;
- ip_address_item *ipa;
- uschar *p = big_buffer;
- uschar *qinfo = (queue_interval > 0)?
- string_sprintf("-q%s", readconf_printtime(queue_interval))
- :
- US"no queue runs";
+ ip_address_item * ipa;
+ uschar * p = big_buffer;
+ uschar * qinfo = queue_interval > 0
+ ? string_sprintf("-q%s", readconf_printtime(queue_interval))
+ : US"no queue runs";
/* Build a list of listening addresses in big_buffer, but limit it to 10
items. The style is for backwards compatibility.
for (j = 0; j < 2; j++)
{
- for (i = 0, ipa = addresses; i < 10 && ipa != NULL; i++, ipa = ipa->next)
+ for (i = 0, ipa = addresses; i < 10 && ipa; i++, ipa = ipa->next)
{
/* First time round, look for SMTP ports; second time round, look for
SMTPS ports. For the first one of each, insert leading text. */
if (host_is_tls_on_connect_port(ipa->port) == (j > 0))
{
- if (j == 0)
- {
- if (smtp_ports++ == 0)
+ if (j == 0)
+ {
+ if (smtp_ports++ == 0)
{
memcpy(p, "SMTP on", 8);
p += 7;
}
- }
- else
- {
- if (smtps_ports++ == 0)
+ }
+ else
+ {
+ if (smtps_ports++ == 0)
{
(void)sprintf(CS p, "%sSMTPS on",
- (smtp_ports == 0)? "":" and for ");
- while (*p != 0) p++;
+ smtp_ports == 0 ? "" : " and for ");
+ while (*p) p++;
}
- }
+ }
/* Now the information about the port (and sometimes interface) */
}
}
- if (ipa != NULL)
+ if (ipa)
{
memcpy(p, " ...", 5);
p += 4;
log_write(0, LOG_MAIN,
"exim %s daemon started: pid=%d, %s, listening for %s",
version_string, getpid(), qinfo, big_buffer);
- set_process_info("daemon: %s, listening for %s", qinfo, big_buffer);
+ set_process_info("daemon(%s): %s, listening for %s",
+ version_string, qinfo, big_buffer);
}
else
{
+ uschar * s = *queue_name
+ ? string_sprintf("-qG%s/%s", queue_name, readconf_printtime(queue_interval))
+ : string_sprintf("-q%s", readconf_printtime(queue_interval));
log_write(0, LOG_MAIN,
- "exim %s daemon started: pid=%d, -q%s, not listening for SMTP",
- version_string, getpid(), readconf_printtime(queue_interval));
- set_process_info("daemon: -q%s, not listening",
- readconf_printtime(queue_interval));
+ "exim %s daemon started: pid=%d, %s, not listening for SMTP",
+ version_string, getpid(), s);
+ set_process_info("daemon(%s): %s, not listening", version_string, s);
}
+/* Do any work it might be useful to amortize over our children
+(eg: compile regex) */
+
+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
struct sockaddr_in accepted;
#endif
- 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;
- /* 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);
+ }
+ }
+ }
+
+ 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("Starting queue-runner: pid %d\n",
- (int)getpid());
+ DEBUG(D_any) debug_printf("SIGALRM received\n");
- /* 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. */
+ /* 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 (debug_daemon) debug_selector = 0;
-
- /* Close any open listening sockets in the child */
+ if (queue_interval > 0 &&
+ (local_queue_run_max <= 0 || queue_run_count < local_queue_run_max))
+ {
+ if ((pid = fork()) == 0)
+ {
+ int sk;
- for (sk = 0; sk < listen_socket_count; sk++) close(listen_sockets[sk]);
+ DEBUG(D_any) debug_printf("Starting queue-runner: pid %d\n",
+ (int)getpid());
- /* Reset SIGHUP and SIGCHLD in the child in both cases. */
+ /* 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. */
- signal(SIGHUP, SIG_DFL);
- signal(SIGCHLD, SIG_DFL);
+ if (debug_daemon) debug_selector = 0;
- /* Re-exec if privilege has been given up, unless deliver_drop_
- privilege is set. Reset SIGALRM before exec(). */
+ /* Close any open listening sockets in the child */
- if (geteuid() != root_uid && !deliver_drop_privilege)
- {
- uschar opt[8];
- uschar *p = opt;
+ for (sk = 0; sk < listen_socket_count; sk++)
+ (void)close(listen_sockets[sk]);
- 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;
-
- (void)child_exec_exim(CEE_EXEC_PANIC, FALSE, NULL, TRUE, 1, opt);
- /* Control never returns here. */
- }
+ /* Reset SIGHUP and SIGCHLD in the child in both cases. */
- /* No need to re-exec; SIGALRM remains set to the default handler */
+ signal(SIGHUP, SIG_DFL);
+ signal(SIGCHLD, SIG_DFL);
- queue_run(NULL, NULL, FALSE);
- _exit(EXIT_SUCCESS);
- }
+ /* Re-exec if privilege has been given up, unless deliver_drop_
+ privilege is set. Reset SIGALRM before exec(). */
- 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 (queue_pid_slots[i] <= 0)
+ if (geteuid() != root_uid && !deliver_drop_privilege)
{
- queue_pid_slots[i] = pid;
- queue_run_count++;
- break;
+ 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] = queue_name
+ ? string_sprintf("%sG%s", opt, queue_name) : opt;
+
+ /* If -R or -S were on the original command line, ensure they get
+ passed on. */
+
+ if (deliver_selectstring)
+ {
+ extra[extracount++] = deliver_selectstring_regex ? US"-Rr" : US"-R";
+ extra[extracount++] = deliver_selectstring;
+ }
+
+ if (deliver_selectstring_sender)
+ {
+ 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. */
}
+
+ /* 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 < local_queue_run_max; ++i)
+ 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
}
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
+
+ /* 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;
-
+ 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. */
{
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);
/* 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);
+ }
}
}
tv.tv_sec = queue_interval;
tv.tv_usec = 0;
select(0, NULL, NULL, NULL, &tv);
- handle_ending_processes();
+ handle_ending_processes();
}
/* Re-enable the SIGCHLD handler if it has been run. It can't do it
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;
/* Control never reaches here */
}
+/* vi: aw ai sw=2
+*/
/* End of exim_daemon.c */
-