inetd wait mode support with -bw
authorPhil Pennock <pdp@exim.org>
Tue, 8 May 2012 21:44:36 +0000 (14:44 -0700)
committerPhil Pennock <pdp@exim.org>
Tue, 8 May 2012 21:44:36 +0000 (14:44 -0700)
doc/doc-docbook/spec.xfpt
doc/doc-txt/ChangeLog
doc/doc-txt/NewStuff
doc/doc-txt/OptionLists.txt
src/src/daemon.c
src/src/exim.c
src/src/globals.c
src/src/globals.h
src/src/log.c

index 2a01a1e5b1bf9574ab60d1819a209f0cf9774cdf..2202e6b75823c415d346545ebd749accaa1354c8 100644 (file)
@@ -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"
index d202cf16b37ad6166524f6c4f6601b528fae572e..de7c178787dc74360993014d73e164f17afe127a 100644 (file)
@@ -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
 -----------------
index 1c8190597024a796fc0a0d23053a7b2bfe1bbbfb..432bbd220a9fda370f081db2f2db6e3fd9f82d45 100644 (file)
@@ -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
 ------------
index 20aeff965819a3a1d85ebdd023265f38c2ba3c92..c9629688a8ae6c7abaf742e35c12b924e1553e9c 100644 (file)
@@ -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
index 27b4cb26567045add35870477d9f3f30e21a3714..de794f693ddc54713f2cb3b8b4be4900be833949 100644 (file)
@@ -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);
+        }
       }
     }
 
index 90ecd0629631b9dcfdb7186117a17d935b45ff48..b53d6ca9b98afb2c61f3639fd8ed9417f41ea361 100644 (file)
@@ -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)
     {
index af0c14b021687ba7ad0460759d5edcec186d1250..b68544e54d590335f6e644623c98409695c9a3b3 100644 (file)
@@ -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;
index f9540785ce52abe2365ddd8b82842aa28ff7c738..d267a52b7651ea2b6db857f3a73b64dbb86bc2db 100644 (file)
@@ -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 */
 
index bfd1fef86efc15c1a447cbb18556b5e53231cf98..75285c136728930dbd13b2a11f9231f951d60999 100644 (file)
@@ -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;
   }