Gitweb:
http://git.exim.org/exim.git/commitdiff/9ee44efbe7474892e931cd517195cb690e765ab6
Commit: 9ee44efbe7474892e931cd517195cb690e765ab6
Parent: 5b4569757c6dc749c250f065705f65c938bffb2e
Author: Phil Pennock <pdp@???>
AuthorDate: Tue May 8 14:44:36 2012 -0700
Committer: Phil Pennock <pdp@???>
CommitDate: Tue May 8 14:44:36 2012 -0700
inetd wait mode support with -bw
---
doc/doc-docbook/spec.xfpt | 17 +++
doc/doc-txt/ChangeLog | 2 +
doc/doc-txt/NewStuff | 6 +
doc/doc-txt/OptionLists.txt | 1 +
src/src/daemon.c | 323 +++++++++++++++++++++++++++++--------------
src/src/exim.c | 23 +++-
src/src/globals.c | 2 +
src/src/globals.h | 2 +
src/src/log.c | 4 +-
9 files changed, 275 insertions(+), 105 deletions(-)
diff --git a/doc/doc-docbook/spec.xfpt b/doc/doc-docbook/spec.xfpt
index 2a01a1e..2202e6b 100644
--- a/doc/doc-docbook/spec.xfpt
+++ b/doc/doc-docbook/spec.xfpt
@@ -3386,6 +3386,23 @@ This option acts like &%-bv%&, but verifies the address as a sender rather
than a recipient address. This affects any rewriting and qualification that
might happen.
+.vitem &%-bw%&
+.oindex "&%-bw%&"
+.cindex "daemon"
+.cindex "inetd"
+.cindex "inetd" "wait mode"
+This option runs Exim as a daemon, awaiting incoming SMTP connections,
+similarly to the &%-bd%& option. All port specifications on the command-line
+and in the configuration file are ignored. Queue-running may not be specified.
+
+In this mode, Exim expects to be passed a socket as fd 0 (stdin) which is
+listening for connections. This permits the system to start up and have
+inetd (or equivalent) listen on the SMTP ports, starting an Exim daemon for
+each port only when the first connection is received.
+
+If the option is given as &%-bw%&<&'time'&> then the time is a timeout, after
+which the daemon will exit, which should cause inetd to listen once more.
+
.vitem &%-C%&&~<&'filelist'&>
.oindex "&%-C%&"
.cindex "configuration file" "alternate"
diff --git a/doc/doc-txt/ChangeLog b/doc/doc-txt/ChangeLog
index d202cf1..de7c178 100644
--- a/doc/doc-txt/ChangeLog
+++ b/doc/doc-txt/ChangeLog
@@ -91,6 +91,8 @@ PP/20 Revert part of NM/04, it broke log_path containing %D expansions.
PP/21 Defaulting "accept_8bitmime" to true, not false.
+PP/22 Added -bw for inetd wait mode support.
+
Exim version 4.77
-----------------
diff --git a/doc/doc-txt/NewStuff b/doc/doc-txt/NewStuff
index 1c81905..432bbd2 100644
--- a/doc/doc-txt/NewStuff
+++ b/doc/doc-txt/NewStuff
@@ -62,6 +62,12 @@ Version 4.78
Those who disagree, or know that they are talking to mail servers that,
even today, are not 8-bit clean, need to turn off this option.
+ 9. Exim can now be started with -bw (with an optional timeout, given as
+ -bw<timespec>). With this, stdin at startup is a socket that is
+ already listening for connections. This has a more modern name of
+ "socket activation", but forcing the activated socket to fd 0. We're
+ interested in adding more support for modern variants.
+
Version 4.77
------------
diff --git a/doc/doc-txt/OptionLists.txt b/doc/doc-txt/OptionLists.txt
index 20aeff9..c962968 100644
--- a/doc/doc-txt/OptionLists.txt
+++ b/doc/doc-txt/OptionLists.txt
@@ -641,6 +641,7 @@ provide compatibility with Sendmail.
-bV Verify version number
-bv Test recipient address verification
-bvs Test sender address verification
+-bw + Inetd wait mode
-C + Use alternate configuration file
-D + Define macro for configuration file
-d + Turn on debugging output
diff --git a/src/src/daemon.c b/src/src/daemon.c
index 27b4cb2..de794f6 100644
--- a/src/src/daemon.c
+++ b/src/src/daemon.c
@@ -913,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
@@ -987,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;
@@ -998,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.
@@ -1208,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. */
@@ -1233,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
@@ -1244,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. */
@@ -1253,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
@@ -1273,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;
@@ -1513,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;
@@ -1631,122 +1702,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;
+
+ 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[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)
{
- if (queue_pid_slots[i] <= 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)
{
- 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
@@ -1886,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);
+ }
}
}
diff --git a/src/src/exim.c b/src/src/exim.c
index 90ecd06..b53d6ca 100644
--- a/src/src/exim.c
+++ b/src/src/exim.c
@@ -2060,6 +2060,24 @@ for (i = 1; i < argc; i++)
show_whats_supported(stdout);
}
+ /* -bw: inetd wait mode, accept a listening socket as stdin */
+
+ else if (*argrest == 'w')
+ {
+ inetd_wait_mode = TRUE;
+ background_daemon = FALSE;
+ daemon_listen = TRUE;
+ if (*(++argrest) != '\0')
+ {
+ inetd_wait_timeout = readconf_readtime(argrest, 0, FALSE);
+ if (inetd_wait_timeout <= 0)
+ {
+ fprintf(stderr, "exim: bad time value %s: abandoned\n", argv[i]);
+ exit(EXIT_FAILURE);
+ }
+ }
+ }
+
else badarg = TRUE;
break;
@@ -3222,6 +3240,9 @@ if ((
daemon_listen && queue_interval == 0
) ||
(
+ inetd_wait_mode && queue_interval >= 0
+ ) ||
+ (
list_options &&
(checking || smtp_input || extract_recipients ||
filter_test != FTEST_NONE || bi_option)
@@ -4406,7 +4427,7 @@ returns. We leave this till here so that the originator_ fields are available
for incoming messages via the daemon. The daemon cannot be run in mua_wrapper
mode. */
-if (daemon_listen || queue_interval > 0)
+if (daemon_listen || inetd_wait_mode || queue_interval > 0)
{
if (mua_wrapper)
{
diff --git a/src/src/globals.c b/src/src/globals.c
index af0c14b..b68544e 100644
--- a/src/src/globals.c
+++ b/src/src/globals.c
@@ -659,6 +659,8 @@ uschar *hosts_connection_nolog = NULL;
int ignore_bounce_errors_after = 10*7*24*60*60; /* 10 weeks */
BOOL ignore_fromline_local = FALSE;
uschar *ignore_fromline_hosts = NULL;
+BOOL inetd_wait_mode = FALSE;
+int inetd_wait_timeout = -1;
uschar *interface_address = NULL;
int interface_port = -1;
BOOL is_inetd = FALSE;
diff --git a/src/src/globals.h b/src/src/globals.h
index f954078..d267a52 100644
--- a/src/src/globals.h
+++ b/src/src/globals.h
@@ -424,6 +424,8 @@ extern uschar *hosts_treat_as_local; /* For routing */
extern int ignore_bounce_errors_after; /* Keep them for this time. */
extern BOOL ignore_fromline_local; /* Local SMTP ignore fromline */
extern uschar *ignore_fromline_hosts; /* Hosts permitted to send "From " */
+extern BOOL inetd_wait_mode; /* Whether running in inetd wait mode */
+extern int inetd_wait_timeout; /* Timeout for inetd wait mode */
extern BOOL is_inetd; /* True for inetd calls */
extern uschar *iterate_item; /* Item from iterate list */
diff --git a/src/src/log.c b/src/src/log.c
index bfd1fef..75285c1 100644
--- a/src/src/log.c
+++ b/src/src/log.c
@@ -1306,7 +1306,7 @@ misconfiguration.
The first use of this is in ACL logic, "control = debug/tag=foo/opts=+expand"
which can be combined with conditions, etc, to activate extra logging only
-for certain sources. */
+for certain sources. The second use is inetd wait mode debug preservation. */
void
debug_logging_activate(uschar *tag_name, uschar *opts)
@@ -1316,7 +1316,7 @@ int fd = -1;
if (debug_file)
{
debug_printf("DEBUGGING ACTIVATED FROM WITHIN CONFIG.\n"
- "DEBUG: Tag=\"%s\" Opts=\"%s\"\n", tag_name, opts);
+ "DEBUG: Tag=\"%s\" Opts=\"%s\"\n", tag_name, opts ? opts : US"");
return;
}