Fix parsing of cmdline -os & -pr options. Bug 2538
[exim.git] / src / src / exim.c
index 3290d63465b334be55a151c2fbc8737aa3d8287c..63ac620e7b1351b36e4d73294a809d4486e9fbdf 100644 (file)
@@ -23,6 +23,10 @@ Also a few functions that don't naturally fit elsewhere. */
 # endif
 #endif
 
+#ifndef _TIME_H
+# include <time.h>
+#endif
+
 extern void init_lookup_list(void);
 
 
@@ -106,7 +110,7 @@ if (use_malloc)
   pcre_free = function_store_free;
   }
 if (caseless) options |= PCRE_CASELESS;
-yield = pcre_compile(CCS pattern, options, (const char **)&error, &offset, NULL);
+yield = pcre_compile(CCS pattern, options, CCSS &error, &offset, NULL);
 pcre_malloc = function_store_get;
 pcre_free = function_dummy_free;
 if (yield == NULL)
@@ -202,7 +206,7 @@ va_end(ap);
 static void
 term_handler(int sig)
 {
-  exit(1);
+exit(1);
 }
 
 
@@ -292,7 +296,7 @@ will wait for ever, so we panic in this instance. (There was a case of this
 when a bug in a function that calls milliwait() caused it to pass invalid data.
 That's when I added the check. :-)
 
-We assume it to be not worth sleeping for under 100us; this value will
+We assume it to be not worth sleeping for under 50us; this value will
 require revisiting as hardware advances.  This avoids the issue of
 a zero-valued timer setting meaning "never fire".
 
@@ -306,7 +310,7 @@ milliwait(struct itimerval *itval)
 sigset_t sigmask;
 sigset_t old_sigmask;
 
-if (itval->it_value.tv_usec < 100 && itval->it_value.tv_sec == 0)
+if (itval->it_value.tv_usec < 50 && itval->it_value.tv_sec == 0)
   return;
 (void)sigemptyset(&sigmask);                           /* Empty mask */
 (void)sigaddset(&sigmask, SIGALRM);                    /* Add SIGALRM */
@@ -377,6 +381,25 @@ return 0;
 *          Clock tick wait function              *
 *************************************************/
 
+#ifdef _POSIX_MONOTONIC_CLOCK
+/* Amount CLOCK_MONOTONIC is behind realtime, at startup. */
+static struct timespec offset_ts;
+
+static void
+exim_clock_init(void)
+{
+struct timeval tv;
+if (clock_gettime(CLOCK_MONOTONIC, &offset_ts) != 0) return;
+(void)gettimeofday(&tv, NULL);
+offset_ts.tv_sec = tv.tv_sec - offset_ts.tv_sec;
+offset_ts.tv_nsec = tv.tv_usec * 1000 - offset_ts.tv_nsec;
+if (offset_ts.tv_nsec >= 0) return;
+offset_ts.tv_sec--;
+offset_ts.tv_nsec += 1000*1000*1000;
+}
+#endif
+
+
 /* Exim uses a time + a pid to generate a unique identifier in two places: its
 message IDs, and in file names for maildir deliveries. Because some OS now
 re-use pids within the same second, sub-second times are now being used.
@@ -388,7 +411,7 @@ function prepares for the time when things are faster - and it also copes with
 clocks that go backwards.
 
 Arguments:
-  then_tv      A timeval which was used to create uniqueness; its usec field
+  tgt_tv       A timeval which was used to create uniqueness; its usec field
                  has been rounded down to the value of the resolution.
                  We want to be sure the current time is greater than this.
   resolution   The resolution that was used to divide the microseconds
@@ -398,26 +421,45 @@ Returns:       nothing
 */
 
 void
-exim_wait_tick(struct timeval *then_tv, int resolution)
+exim_wait_tick(struct timeval * tgt_tv, int resolution)
 {
 struct timeval now_tv;
 long int now_true_usec;
 
-(void)gettimeofday(&now_tv, NULL);
-now_true_usec = now_tv.tv_usec;
-now_tv.tv_usec = (now_true_usec/resolution) * resolution;
+#ifdef _POSIX_MONOTONIC_CLOCK
+struct timespec now_ts;
 
-if (exim_tvcmp(&now_tv, then_tv) <= 0)
+if (clock_gettime(CLOCK_MONOTONIC, &now_ts) == 0)
+  {
+  now_ts.tv_sec += offset_ts.tv_sec;
+  if ((now_ts.tv_nsec += offset_ts.tv_nsec) >= 1000*1000*1000)
+    {
+    now_ts.tv_sec++;
+    now_ts.tv_nsec -= 1000*1000*1000;
+    }
+  now_tv.tv_sec = now_ts.tv_sec;
+  now_true_usec = (now_ts.tv_nsec / (resolution * 1000)) * resolution;
+  now_tv.tv_usec = now_true_usec;
+  }
+else
+#endif
+  {
+  (void)gettimeofday(&now_tv, NULL);
+  now_true_usec = now_tv.tv_usec;
+  now_tv.tv_usec = (now_true_usec/resolution) * resolution;
+  }
+
+while (exim_tvcmp(&now_tv, tgt_tv) <= 0)
   {
   struct itimerval itval;
   itval.it_interval.tv_sec = 0;
   itval.it_interval.tv_usec = 0;
-  itval.it_value.tv_sec = then_tv->tv_sec - now_tv.tv_sec;
-  itval.it_value.tv_usec = then_tv->tv_usec + resolution - now_true_usec;
+  itval.it_value.tv_sec = tgt_tv->tv_sec - now_tv.tv_sec;
+  itval.it_value.tv_usec = tgt_tv->tv_usec + resolution - now_true_usec;
 
   /* We know that, overall, "now" is less than or equal to "then". Therefore, a
   negative value for the microseconds is possible only in the case when "now"
-  is more than a second less than "then". That means that itval.it_value.tv_sec
+  is more than a second less than "tgt". That means that itval.it_value.tv_sec
   is greater than zero. The following correction is therefore safe. */
 
   if (itval.it_value.tv_usec < 0)
@@ -431,14 +473,21 @@ if (exim_tvcmp(&now_tv, then_tv) <= 0)
     if (!f.running_in_test_harness)
       {
       debug_printf("tick check: " TIME_T_FMT ".%06lu " TIME_T_FMT ".%06lu\n",
-        then_tv->tv_sec, (long) then_tv->tv_usec,
+        tgt_tv->tv_sec, (long) tgt_tv->tv_usec,
                now_tv.tv_sec, (long) now_tv.tv_usec);
-      debug_printf("waiting " TIME_T_FMT ".%06lu\n",
+      debug_printf("waiting " TIME_T_FMT ".%06lu sec\n",
         itval.it_value.tv_sec, (long) itval.it_value.tv_usec);
       }
     }
 
   milliwait(&itval);
+
+  /* Be prapared to go around if the kernel does not implement subtick
+  granularity (GNU Hurd) */
+
+  (void)gettimeofday(&now_tv, NULL);
+  now_true_usec = now_tv.tv_usec;
+  now_tv.tv_usec = (now_true_usec/resolution) * resolution;
   }
 }
 
@@ -901,7 +950,7 @@ fprintf(fp, "Support for:");
 #ifndef DISABLE_OCSP
   fprintf(fp, " OCSP");
 #endif
-#ifdef SUPPORT_PIPE_CONNECT
+#ifndef DISABLE_PIPE_CONNECT
   fprintf(fp, " PIPE_CONNECT");
 #endif
 #ifndef DISABLE_PRDR
@@ -920,18 +969,9 @@ fprintf(fp, "Support for:");
   fprintf(fp, " DMARC");
 #endif
 #ifdef TCP_FASTOPEN
-  deliver_init();
+  tcp_init();
   if (f.tcp_fastopen_ok) fprintf(fp, " TCP_Fast_Open");
 #endif
-#ifdef EXPERIMENTAL_LMDB
-  fprintf(fp, " Experimental_LMDB");
-#endif
-#ifdef EXPERIMENTAL_QUEUEFILE
-  fprintf(fp, " Experimental_QUEUEFILE");
-#endif
-#ifdef EXPERIMENTAL_SRS
-  fprintf(fp, " Experimental_SRS");
-#endif
 #ifdef EXPERIMENTAL_ARC
   fprintf(fp, " Experimental_ARC");
 #endif
@@ -944,6 +984,18 @@ fprintf(fp, "Support for:");
 #ifdef EXPERIMENTAL_DSN_INFO
   fprintf(fp, " Experimental_DSN_info");
 #endif
+#ifdef EXPERIMENTAL_LMDB
+  fprintf(fp, " Experimental_LMDB");
+#endif
+#ifdef EXPERIMENTAL_QUEUE_RAMP
+  fprintf(fp, " Experimental_Queue_Ramp");
+#endif
+#ifdef EXPERIMENTAL_QUEUEFILE
+  fprintf(fp, " Experimental_QUEUEFILE");
+#endif
+#if defined(EXPERIMENTAL_SRS) || defined(EXPERIMENTAL_SRS_NATIVE)
+  fprintf(fp, " Experimental_SRS");
+#endif
 #ifdef EXPERIMENTAL_TLS_RESUME
   fprintf(fp, " Experimental_TLS_resume");
 #endif
@@ -1065,6 +1117,9 @@ show_db_version(fp);
 #ifdef SUPPORT_I18N
   utf8_version_report(fp);
 #endif
+#ifdef SUPPORT_SPF
+  spf_lib_version_report(fp);
+#endif
 
   for (auth_info * authi = auths_available; *authi->driver_name != '\0'; ++authi)
     if (authi->version_report)
@@ -1213,9 +1268,9 @@ void *dlhandle;
 void *dlhandle_curses = dlopen("libcurses." DYNLIB_FN_EXT, RTLD_GLOBAL|RTLD_LAZY);
 
 dlhandle = dlopen("libreadline." DYNLIB_FN_EXT, RTLD_GLOBAL|RTLD_NOW);
-if (dlhandle_curses != NULL) dlclose(dlhandle_curses);
+if (dlhandle_curses) dlclose(dlhandle_curses);
 
-if (dlhandle != NULL)
+if (dlhandle)
   {
   /* Checked manual pages; at least in GNU Readline 6.1, the prototypes are:
    *   char * readline (const char *prompt);
@@ -1225,9 +1280,7 @@ if (dlhandle != NULL)
   *fn_addhist_ptr = (void(*)(const char*))dlsym(dlhandle, "add_history");
   }
 else
-  {
   DEBUG(D_any) debug_printf("failed to load readline: %s\n", dlerror());
-  }
 
 return dlhandle;
 }
@@ -1666,6 +1719,12 @@ make quite sure. */
 
 setlocale(LC_ALL, "C");
 
+/* Get the offset between CLOCK_MONOTONIC and wallclock */
+
+#ifdef _POSIX_MONOTONIC_CLOCK
+exim_clock_init();
+#endif
+
 /* Set up the default handler for timing using alarm(). */
 
 os_non_restarting_signal(SIGALRM, sigalrm_handler);
@@ -1983,7 +2042,7 @@ for (i = 1; i < argc; i++)
             ignore = TRUE;
           break;
         }
-      if (!ignore) { badarg = TRUE; break; }
+      if (!ignore) badarg = TRUE;
       }
     break;
 
@@ -1991,283 +2050,291 @@ for (i = 1; i < argc; i++)
     so has no need of it. */
 
     case 'B':
-    if (*argrest == 0) i++;       /* Skip over the type */
+    if (!*argrest) i++;       /* Skip over the type */
     break;
 
 
     case 'b':
-    receiving_message = FALSE;    /* Reset TRUE for -bm, -bS, -bs below */
-
-    /* -bd:  Run in daemon mode, awaiting SMTP connections.
-       -bdf: Ditto, but in the foreground.
-    */
-
-    if (*argrest == 'd')
-      {
-      f.daemon_listen = TRUE;
-      if (*(++argrest) == 'f') f.background_daemon = FALSE;
-        else if (*argrest != 0) { badarg = TRUE; break; }
-      }
-
-    /* -be:  Run in expansion test mode
-       -bem: Ditto, but read a message from a file first
-    */
-
-    else if (*argrest == 'e')
-      {
-      expansion_test = checking = TRUE;
-      if (argrest[1] == 'm')
-        {
-        if (++i >= argc) { badarg = TRUE; break; }
-        expansion_test_message = argv[i];
-        argrest++;
-        }
-      if (argrest[1] != 0) { badarg = TRUE; break; }
-      }
-
-    /* -bF:  Run system filter test */
-
-    else if (*argrest == 'F')
-      {
-      filter_test |= checking = FTEST_SYSTEM;
-      if (*(++argrest) != 0) { badarg = TRUE; break; }
-      if (++i < argc) filter_test_sfile = argv[i]; else
-        exim_fail("exim: file name expected after %s\n", argv[i-1]);
-      }
-
-    /* -bf:  Run user filter test
-       -bfd: Set domain for filter testing
-       -bfl: Set local part for filter testing
-       -bfp: Set prefix for filter testing
-       -bfs: Set suffix for filter testing
-    */
-
-    else if (*argrest == 'f')
-      {
-      if (*(++argrest) == 0)
-        {
-        filter_test |= checking = FTEST_USER;
-        if (++i < argc) filter_test_ufile = argv[i]; else
-          exim_fail("exim: file name expected after %s\n", argv[i-1]);
-        }
-      else
-        {
-        if (++i >= argc)
-          exim_fail("exim: string expected after %s\n", arg);
-        if (Ustrcmp(argrest, "d") == 0) ftest_domain = argv[i];
-        else if (Ustrcmp(argrest, "l") == 0) ftest_localpart = argv[i];
-        else if (Ustrcmp(argrest, "p") == 0) ftest_prefix = argv[i];
-        else if (Ustrcmp(argrest, "s") == 0) ftest_suffix = argv[i];
-        else { badarg = TRUE; break; }
-        }
-      }
-
-    /* -bh: Host checking - an IP address must follow. */
-
-    else if (Ustrcmp(argrest, "h") == 0 || Ustrcmp(argrest, "hc") == 0)
-      {
-      if (++i >= argc) { badarg = TRUE; break; }
-      sender_host_address = argv[i];
-      host_checking = checking = f.log_testing_mode = TRUE;
-      f.host_checking_callout = argrest[1] == 'c';
-      message_logs = FALSE;
-      }
-
-    /* -bi: This option is used by sendmail to initialize *the* alias file,
-    though it has the -oA option to specify a different file. Exim has no
-    concept of *the* alias file, but since Sun's YP make script calls
-    sendmail this way, some support must be provided. */
-
-    else if (Ustrcmp(argrest, "i") == 0) bi_option = TRUE;
-
-    /* -bI: provide information, of the type to follow after a colon.
-    This is an Exim flag. */
-
-    else if (argrest[0] == 'I' && Ustrlen(argrest) >= 2 && argrest[1] == ':')
-      {
-      uschar *p = &argrest[2];
-      info_flag = CMDINFO_HELP;
-      if (Ustrlen(p))
-        {
-        if (strcmpic(p, CUS"sieve") == 0)
-          {
-          info_flag = CMDINFO_SIEVE;
-          info_stdout = TRUE;
-          }
-        else if (strcmpic(p, CUS"dscp") == 0)
-          {
-          info_flag = CMDINFO_DSCP;
-          info_stdout = TRUE;
-          }
-        else if (strcmpic(p, CUS"help") == 0)
-          {
-          info_stdout = TRUE;
-          }
-        }
-      }
-
-    /* -bm: Accept and deliver message - the default option. Reinstate
-    receiving_message, which got turned off for all -b options. */
-
-    else if (Ustrcmp(argrest, "m") == 0) receiving_message = TRUE;
-
-    /* -bmalware: test the filename given for malware */
-
-    else if (Ustrcmp(argrest, "malware") == 0)
-      {
-      if (++i >= argc) { badarg = TRUE; break; }
-      checking = TRUE;
-      malware_test_file = argv[i];
-      }
-
-    /* -bnq: For locally originating messages, do not qualify unqualified
-    addresses. In the envelope, this causes errors; in header lines they
-    just get left. */
-
-    else if (Ustrcmp(argrest, "nq") == 0)
-      {
-      f.allow_unqualified_sender = FALSE;
-      f.allow_unqualified_recipient = FALSE;
-      }
-
-    /* -bpxx: List the contents of the mail queue, in various forms. If
-    the option is -bpc, just a queue count is needed. Otherwise, if the
-    first letter after p is r, then order is random. */
-
-    else if (*argrest == 'p')
-      {
-      if (*(++argrest) == 'c')
-        {
-        count_queue = TRUE;
-        if (*(++argrest) != 0) badarg = TRUE;
-        break;
-        }
-
-      if (*argrest == 'r')
-        {
-        list_queue_option = 8;
-        argrest++;
-        }
-      else list_queue_option = 0;
-
-      list_queue = TRUE;
-
-      /* -bp: List the contents of the mail queue, top-level only */
-
-      if (*argrest == 0) {}
-
-      /* -bpu: List the contents of the mail queue, top-level undelivered */
-
-      else if (Ustrcmp(argrest, "u") == 0) list_queue_option += 1;
-
-      /* -bpa: List the contents of the mail queue, including all delivered */
-
-      else if (Ustrcmp(argrest, "a") == 0) list_queue_option += 2;
-
-      /* Unknown after -bp[r] */
-
-      else
-        {
-        badarg = TRUE;
-        break;
-        }
-      }
-
-
-    /* -bP: List the configuration variables given as the address list.
-    Force -v, so configuration errors get displayed. */
-
-    else if (Ustrcmp(argrest, "P") == 0)
-      {
-      /* -bP config: we need to setup here, because later,
-       * when list_options is checked, the config is read already */
-      if (argv[i+1] && Ustrcmp(argv[i+1], "config") == 0)
-        {
-        list_config = TRUE;
-        readconf_save_config(version_string);
-        }
-      else
-        {
-        list_options = TRUE;
-        debug_selector |= D_v;
-        debug_file = stderr;
-        }
-      }
-
-    /* -brt: Test retry configuration lookup */
-
-    else if (Ustrcmp(argrest, "rt") == 0)
       {
-      checking = TRUE;
-      test_retry_arg = i + 1;
-      goto END_ARG;
-      }
+      receiving_message = FALSE;    /* Reset TRUE for -bm, -bS, -bs below */
 
-    /* -brw: Test rewrite configuration */
+      switch (*argrest++)
+       {
+       /* -bd:  Run in daemon mode, awaiting SMTP connections.
+          -bdf: Ditto, but in the foreground.
+       */
+       case 'd':
+         f.daemon_listen = TRUE;
+         if (*argrest == 'f') f.background_daemon = FALSE;
+         else if (*argrest) badarg = TRUE;
+         break;
+
+       /* -be:  Run in expansion test mode
+          -bem: Ditto, but read a message from a file first
+       */
+       case 'e':
+         expansion_test = checking = TRUE;
+         if (*argrest == 'm')
+           {
+           if (++i >= argc) { badarg = TRUE; break; }
+           expansion_test_message = argv[i];
+           argrest++;
+           }
+         if (*argrest) badarg = TRUE;
+         break;
+
+       /* -bF:  Run system filter test */
+       case 'F':
+         filter_test |= checking = FTEST_SYSTEM;
+         if (*argrest) { badarg = TRUE; break; }
+         if (++i < argc) filter_test_sfile = argv[i]; else
+           exim_fail("exim: file name expected after %s\n", argv[i-1]);
+         break;
+
+       /* -bf:  Run user filter test
+          -bfd: Set domain for filter testing
+          -bfl: Set local part for filter testing
+          -bfp: Set prefix for filter testing
+          -bfs: Set suffix for filter testing
+       */
+       case 'f':
+         if (!*argrest)
+           {
+           filter_test |= checking = FTEST_USER;
+           if (++i < argc) filter_test_ufile = argv[i];
+           else exim_fail("exim: file name expected after %s\n", argv[i-1]);
+           }
+         else
+           {
+           if (++i >= argc)
+             exim_fail("exim: string expected after %s\n", arg);
+           if (Ustrcmp(argrest, "d") == 0) ftest_domain = argv[i];
+           else if (Ustrcmp(argrest, "l") == 0) ftest_localpart = argv[i];
+           else if (Ustrcmp(argrest, "p") == 0) ftest_prefix = argv[i];
+           else if (Ustrcmp(argrest, "s") == 0) ftest_suffix = argv[i];
+           else badarg = TRUE;
+           }
+         break;
+
+       /* -bh: Host checking - an IP address must follow. */
+       case 'h':
+         if (!*argrest || Ustrcmp(argrest, "c") == 0)
+           {
+           if (++i >= argc) { badarg = TRUE; break; }
+           sender_host_address = argv[i];
+           host_checking = checking = f.log_testing_mode = TRUE;
+           f.host_checking_callout = *argrest == 'c';
+           message_logs = FALSE;
+           }
+         else badarg = TRUE;
+         break;
+
+       /* -bi: This option is used by sendmail to initialize *the* alias file,
+       though it has the -oA option to specify a different file. Exim has no
+       concept of *the* alias file, but since Sun's YP make script calls
+       sendmail this way, some support must be provided. */
+       case 'i':
+         if (!*++argrest) bi_option = TRUE;
+         else badarg = TRUE;
+         break;
+
+       /* -bI: provide information, of the type to follow after a colon.
+       This is an Exim flag. */
+       case 'I':
+         if (Ustrlen(argrest) >= 1 && *argrest == ':')
+           {
+           uschar *p = argrest+1;
+           info_flag = CMDINFO_HELP;
+           if (Ustrlen(p))
+             if (strcmpic(p, CUS"sieve") == 0)
+               {
+               info_flag = CMDINFO_SIEVE;
+               info_stdout = TRUE;
+               }
+             else if (strcmpic(p, CUS"dscp") == 0)
+               {
+               info_flag = CMDINFO_DSCP;
+               info_stdout = TRUE;
+               }
+             else if (strcmpic(p, CUS"help") == 0)
+               info_stdout = TRUE;
+           }
+         else badarg = TRUE;
+         break;
+
+       /* -bm: Accept and deliver message - the default option. Reinstate
+       receiving_message, which got turned off for all -b options.
+          -bmalware: test the filename given for malware */
+       case 'm':
+         if (!*argrest) receiving_message = TRUE;
+         else if (Ustrcmp(argrest, "alware") == 0)
+           {
+           if (++i >= argc) { badarg = TRUE; break; }
+           checking = TRUE;
+           malware_test_file = argv[i];
+           }
+         else badarg = TRUE;
+         break;
+
+       /* -bnq: For locally originating messages, do not qualify unqualified
+       addresses. In the envelope, this causes errors; in header lines they
+       just get left. */
+       case 'n':
+         if (Ustrcmp(argrest, "q") == 0)
+           {
+           f.allow_unqualified_sender = FALSE;
+           f.allow_unqualified_recipient = FALSE;
+           }
+         else badarg = TRUE;
+         break;
+
+       /* -bpxx: List the contents of the mail queue, in various forms. If
+       the option is -bpc, just a queue count is needed. Otherwise, if the
+       first letter after p is r, then order is random. */
+       case 'p':
+         if (*argrest == 'c')
+           {
+           count_queue = TRUE;
+           if (*++argrest) badarg = TRUE;
+           break;
+           }
 
-    else if (Ustrcmp(argrest, "rw") == 0)
-      {
-      checking = TRUE;
-      test_rewrite_arg = i + 1;
-      goto END_ARG;
-      }
+         if (*argrest == 'r')
+           {
+           list_queue_option = 8;
+           argrest++;
+           }
+         else list_queue_option = 0;
 
-    /* -bS: Read SMTP commands on standard input, but produce no replies -
-    all errors are reported by sending messages. */
+         list_queue = TRUE;
 
-    else if (Ustrcmp(argrest, "S") == 0)
-      smtp_input = smtp_batched_input = receiving_message = TRUE;
+         /* -bp: List the contents of the mail queue, top-level only */
 
-    /* -bs: Read SMTP commands on standard input and produce SMTP replies
-    on standard output. */
+         if (!*argrest) {}
 
-    else if (Ustrcmp(argrest, "s") == 0) smtp_input = receiving_message = TRUE;
+         /* -bpu: List the contents of the mail queue, top-level undelivered */
 
-    /* -bt: address testing mode */
+         else if (Ustrcmp(argrest, "u") == 0) list_queue_option += 1;
 
-    else if (Ustrcmp(argrest, "t") == 0)
-      f.address_test_mode = checking = f.log_testing_mode = TRUE;
+         /* -bpa: List the contents of the mail queue, including all delivered */
 
-    /* -bv: verify addresses */
+         else if (Ustrcmp(argrest, "a") == 0) list_queue_option += 2;
 
-    else if (Ustrcmp(argrest, "v") == 0)
-      verify_address_mode = checking = f.log_testing_mode = TRUE;
+         /* Unknown after -bp[r] */
 
-    /* -bvs: verify sender addresses */
+         else badarg = TRUE;
+         break;
 
-    else if (Ustrcmp(argrest, "vs") == 0)
-      {
-      verify_address_mode = checking = f.log_testing_mode = TRUE;
-      verify_as_sender = TRUE;
-      }
 
-    /* -bV: Print version string and support details */
+       /* -bP: List the configuration variables given as the address list.
+       Force -v, so configuration errors get displayed. */
+       case 'P':
 
-    else if (Ustrcmp(argrest, "V") == 0)
-      {
-      printf("Exim version %s #%s built %s\n", version_string,
-        version_cnumber, version_date);
-      printf("%s\n", CS version_copyright);
-      version_printed = TRUE;
-      show_whats_supported(stdout);
-      f.log_testing_mode = TRUE;
-      }
+         /* -bP config: we need to setup here, because later,
+          * when list_options is checked, the config is read already */
+         if (*argrest)
+           badarg = TRUE;
+         else if (argv[i+1] && Ustrcmp(argv[i+1], "config") == 0)
+           {
+           list_config = TRUE;
+           readconf_save_config(version_string);
+           }
+         else
+           {
+           list_options = TRUE;
+           debug_selector |= D_v;
+           debug_file = stderr;
+           }
+         break;
+
+       /* -brt: Test retry configuration lookup */
+       case 'r':
+         if (Ustrcmp(argrest, "t") == 0)
+           {
+           checking = TRUE;
+           test_retry_arg = i + 1;
+           goto END_ARG;
+           }
 
-    /* -bw: inetd wait mode, accept a listening socket as stdin */
+         /* -brw: Test rewrite configuration */
 
-    else if (*argrest == 'w')
-      {
-      f.inetd_wait_mode = TRUE;
-      f.background_daemon = FALSE;
-      f.daemon_listen = TRUE;
-      if (*(++argrest) != '\0')
-        if ((inetd_wait_timeout = readconf_readtime(argrest, 0, FALSE)) <= 0)
-          exim_fail("exim: bad time value %s: abandoned\n", argv[i]);
+         else if (Ustrcmp(argrest, "w") == 0)
+           {
+           checking = TRUE;
+           test_rewrite_arg = i + 1;
+           goto END_ARG;
+           }
+         else badarg = TRUE;
+         break;
+
+       /* -bS: Read SMTP commands on standard input, but produce no replies -
+       all errors are reported by sending messages. */
+       case 'S':
+         if (!*argrest)
+           smtp_input = smtp_batched_input = receiving_message = TRUE;
+         else badarg = TRUE;
+         break;
+
+       /* -bs: Read SMTP commands on standard input and produce SMTP replies
+       on standard output. */
+       case 's':
+         if (!*argrest) smtp_input = receiving_message = TRUE;
+         else badarg = TRUE;
+         break;
+
+       /* -bt: address testing mode */
+       case 't':
+         if (!*argrest)
+           f.address_test_mode = checking = f.log_testing_mode = TRUE;
+         else badarg = TRUE;
+         break;
+
+       /* -bv: verify addresses */
+       case 'v':
+         if (!*argrest)
+           verify_address_mode = checking = f.log_testing_mode = TRUE;
+
+       /* -bvs: verify sender addresses */
+
+         else if (Ustrcmp(argrest, "s") == 0)
+           {
+           verify_address_mode = checking = f.log_testing_mode = TRUE;
+           verify_as_sender = TRUE;
+           }
+         else badarg = TRUE;
+         break;
+
+       /* -bV: Print version string and support details */
+       case 'V':
+         if (!*argrest)
+           {
+           printf("Exim version %s #%s built %s\n", version_string,
+             version_cnumber, version_date);
+           printf("%s\n", CS version_copyright);
+           version_printed = TRUE;
+           show_whats_supported(stdout);
+           f.log_testing_mode = TRUE;
+           }
+         else badarg = TRUE;
+         break;
+
+       /* -bw: inetd wait mode, accept a listening socket as stdin */
+       case 'w':
+         f.inetd_wait_mode = TRUE;
+         f.background_daemon = FALSE;
+         f.daemon_listen = TRUE;
+         if (*argrest)
+           if ((inetd_wait_timeout = readconf_readtime(argrest, 0, FALSE)) <= 0)
+             exim_fail("exim: bad time value %s: abandoned\n", argv[i]);
+         break;
+
+       default:
+         badarg = TRUE;
+         break;
+       }
+      break;
       }
 
-    else badarg = TRUE;
-    break;
-
 
     /* -C: change configuration file list; ignore if it isn't really
     a change! Enforce a prefix check if required. */
@@ -2895,212 +2962,225 @@ for (i = 1; i < argc; i++)
 
     case 'O':
     if (*argrest == 0)
-      {
       if (++i >= argc)
         exim_fail("exim: string expected after -O\n");
-      }
     break;
 
     case 'o':
-
-    /* -oA: Set an argument for the bi command (sendmail's "alternate alias
-    file" option). */
-
-    if (*argrest == 'A')
-      {
-      alias_arg = argrest + 1;
-      if (alias_arg[0] == 0)
-        {
-        if (i+1 < argc) alias_arg = argv[++i]; else
-          exim_fail("exim: string expected after -oA\n");
-        }
-      }
-
-    /* -oB: Set a connection message max value for remote deliveries */
-
-    else if (*argrest == 'B')
-      {
-      uschar *p = argrest + 1;
-      if (p[0] == 0)
-        {
-        if (i+1 < argc && isdigit((argv[i+1][0]))) p = argv[++i]; else
-          {
-          connection_max_messages = 1;
-          p = NULL;
-          }
-        }
-
-      if (p != NULL)
-        {
-        if (!isdigit(*p))
-          exim_fail("exim: number expected after -oB\n");
-        connection_max_messages = Uatoi(p);
-        }
-      }
-
-    /* -odb: background delivery */
-
-    else if (Ustrcmp(argrest, "db") == 0)
-      {
-      f.synchronous_delivery = FALSE;
-      arg_queue_only = FALSE;
-      queue_only_set = TRUE;
-      }
-
-    /* -odf: foreground delivery (smail-compatible option); same effect as
-       -odi: interactive (synchronous) delivery (sendmail-compatible option)
-    */
-
-    else if (Ustrcmp(argrest, "df") == 0 || Ustrcmp(argrest, "di") == 0)
+    switch (*argrest++)
       {
-      f.synchronous_delivery = TRUE;
-      arg_queue_only = FALSE;
-      queue_only_set = TRUE;
-      }
-
-    /* -odq: queue only */
+      /* -oA: Set an argument for the bi command (sendmail's "alternate alias
+      file" option). */
+      case 'A':
+       if (!*(alias_arg = argrest))
+         if (i+1 < argc) alias_arg = argv[++i];
+         else exim_fail("exim: string expected after -oA\n");
+       break;
 
-    else if (Ustrcmp(argrest, "dq") == 0)
-      {
-      f.synchronous_delivery = FALSE;
-      arg_queue_only = TRUE;
-      queue_only_set = TRUE;
-      }
+      /* -oB: Set a connection message max value for remote deliveries */
+      case 'B':
+       {
+       uschar * p = argrest;
+       if (!*p)
+         if (i+1 < argc && isdigit((argv[i+1][0])))
+           p = argv[++i];
+         else
+           {
+           connection_max_messages = 1;
+           p = NULL;
+           }
 
-    /* -odqs: queue SMTP only - do local deliveries and remote routing,
-    but no remote delivery */
+       if (p)
+         {
+         if (!isdigit(*p))
+           exim_fail("exim: number expected after -oB\n");
+         connection_max_messages = Uatoi(p);
+         }
+       }
+       break;
 
-    else if (Ustrcmp(argrest, "dqs") == 0)
-      {
-      f.queue_smtp = TRUE;
-      arg_queue_only = FALSE;
-      queue_only_set = TRUE;
-      }
+      /* -odb: background delivery */
+
+      case 'd':
+       if (Ustrcmp(argrest, "b") == 0)
+         {
+         f.synchronous_delivery = FALSE;
+         arg_queue_only = FALSE;
+         queue_only_set = TRUE;
+         }
+
+      /* -odd: testsuite-only: add no inter-process delays */
+
+       else if (Ustrcmp(argrest, "d") == 0)
+         f.testsuite_delays = FALSE;
+
+      /* -odf: foreground delivery (smail-compatible option); same effect as
+        -odi: interactive (synchronous) delivery (sendmail-compatible option)
+      */
+
+       else if (Ustrcmp(argrest, "f") == 0 || Ustrcmp(argrest, "i") == 0)
+         {
+         f.synchronous_delivery = TRUE;
+         arg_queue_only = FALSE;
+         queue_only_set = TRUE;
+         }
+
+      /* -odq: queue only */
+
+       else if (Ustrcmp(argrest, "q") == 0)
+         {
+         f.synchronous_delivery = FALSE;
+         arg_queue_only = TRUE;
+         queue_only_set = TRUE;
+         }
+
+      /* -odqs: queue SMTP only - do local deliveries and remote routing,
+      but no remote delivery */
+
+       else if (Ustrcmp(argrest, "qs") == 0)
+         {
+         f.queue_smtp = TRUE;
+         arg_queue_only = FALSE;
+         queue_only_set = TRUE;
+         }
+       else badarg = TRUE;
+       break;
 
-    /* -oex: Sendmail error flags. As these are also accepted without the
-    leading -o prefix, for compatibility with vacation and other callers,
-    they are handled with -e above. */
+      /* -oex: Sendmail error flags. As these are also accepted without the
+      leading -o prefix, for compatibility with vacation and other callers,
+      they are handled with -e above. */
 
-    /* -oi:     Set flag so dot doesn't end non-SMTP input (same as -i)
-       -oitrue: Another sendmail syntax for the same */
+      /* -oi:     Set flag so dot doesn't end non-SMTP input (same as -i)
+        -oitrue: Another sendmail syntax for the same */
 
-    else if (Ustrcmp(argrest, "i") == 0 ||
-             Ustrcmp(argrest, "itrue") == 0)
-      f.dot_ends = FALSE;
+      case 'i':
+       if (!*argrest || Ustrcmp(argrest, "true") == 0)
+         f.dot_ends = FALSE;
+       else badarg = TRUE;
+       break;
 
     /* -oM*: Set various characteristics for an incoming message; actually
     acted on for trusted callers only. */
 
-    else if (*argrest == 'M')
-      {
-      if (i+1 >= argc)
-        exim_fail("exim: data expected after -o%s\n", argrest);
+      case 'M':
+       {
+       if (i+1 >= argc)
+         exim_fail("exim: data expected after -oM%s\n", argrest);
 
-      /* -oMa: Set sender host address */
+       /* -oMa: Set sender host address */
 
-      if (Ustrcmp(argrest, "Ma") == 0) sender_host_address = argv[++i];
+       if (Ustrcmp(argrest, "a") == 0) sender_host_address = argv[++i];
 
-      /* -oMaa: Set authenticator name */
+       /* -oMaa: Set authenticator name */
 
-      else if (Ustrcmp(argrest, "Maa") == 0)
-        sender_host_authenticated = argv[++i];
+       else if (Ustrcmp(argrest, "aa") == 0)
+         sender_host_authenticated = argv[++i];
 
-      /* -oMas: setting authenticated sender */
+       /* -oMas: setting authenticated sender */
 
-      else if (Ustrcmp(argrest, "Mas") == 0)
-       authenticated_sender = string_copy_taint(argv[++i], TRUE);
+       else if (Ustrcmp(argrest, "as") == 0)
+         authenticated_sender = string_copy_taint(argv[++i], TRUE);
 
-      /* -oMai: setting authenticated id */
+       /* -oMai: setting authenticated id */
 
-      else if (Ustrcmp(argrest, "Mai") == 0)
-       authenticated_id = string_copy_taint(argv[++i], TRUE);
+       else if (Ustrcmp(argrest, "ai") == 0)
+         authenticated_id = string_copy_taint(argv[++i], TRUE);
 
-      /* -oMi: Set incoming interface address */
+       /* -oMi: Set incoming interface address */
 
-      else if (Ustrcmp(argrest, "Mi") == 0) interface_address = argv[++i];
+       else if (Ustrcmp(argrest, "i") == 0) interface_address = argv[++i];
 
-      /* -oMm: Message reference */
+       /* -oMm: Message reference */
 
-      else if (Ustrcmp(argrest, "Mm") == 0)
-        {
-        if (!mac_ismsgid(argv[i+1]))
-            exim_fail("-oMm must be a valid message ID\n");
-        if (!f.trusted_config)
-            exim_fail("-oMm must be called by a trusted user/config\n");
-          message_reference = argv[++i];
-        }
+       else if (Ustrcmp(argrest, "m") == 0)
+         {
+         if (!mac_ismsgid(argv[i+1]))
+             exim_fail("-oMm must be a valid message ID\n");
+         if (!f.trusted_config)
+             exim_fail("-oMm must be called by a trusted user/config\n");
+           message_reference = argv[++i];
+         }
 
-      /* -oMr: Received protocol */
+       /* -oMr: Received protocol */
 
-      else if (Ustrcmp(argrest, "Mr") == 0)
+       else if (Ustrcmp(argrest, "r") == 0)
 
-        if (received_protocol)
-          exim_fail("received_protocol is set already\n");
-        else
-         received_protocol = argv[++i];
+         if (received_protocol)
+           exim_fail("received_protocol is set already\n");
+         else
+           received_protocol = argv[++i];
 
-      /* -oMs: Set sender host name */
+       /* -oMs: Set sender host name */
 
-      else if (Ustrcmp(argrest, "Ms") == 0)
-       sender_host_name = string_copy_taint(argv[++i], TRUE);
+       else if (Ustrcmp(argrest, "s") == 0)
+         sender_host_name = string_copy_taint(argv[++i], TRUE);
 
-      /* -oMt: Set sender ident */
+       /* -oMt: Set sender ident */
 
-      else if (Ustrcmp(argrest, "Mt") == 0)
-        {
-        sender_ident_set = TRUE;
-        sender_ident = argv[++i];
-        }
+       else if (Ustrcmp(argrest, "t") == 0)
+         {
+         sender_ident_set = TRUE;
+         sender_ident = argv[++i];
+         }
 
-      /* Else a bad argument */
+       /* Else a bad argument */
 
-      else
-        {
-        badarg = TRUE;
-        break;
-        }
-      }
-
-    /* -om: Me-too flag for aliases. Exim always does this. Some programs
-    seem to call this as -m (undocumented), so that is also accepted (see
-    above). */
+       else
+         badarg = TRUE;
+       }
+       break;
 
-    else if (Ustrcmp(argrest, "m") == 0) {}
+      /* -om: Me-too flag for aliases. Exim always does this. Some programs
+      seem to call this as -m (undocumented), so that is also accepted (see
+      above). */
+      /* -oo: An ancient flag for old-style addresses which still seems to
+      crop up in some calls (see in SCO). */
 
-    /* -oo: An ancient flag for old-style addresses which still seems to
-    crop up in some calls (see in SCO). */
+      case 'm':
+      case 'o':
+       if (*argrest) badarg = TRUE;
+       break;
 
-    else if (Ustrcmp(argrest, "o") == 0) {}
+      /* -oP <name>: set pid file path for daemon
+        -oPX:       delete pid file of daemon */
 
-    /* -oP <name>: set pid file path for daemon */
+      case 'P':
+       if (!*argrest) override_pid_file_path = argv[++i];
+       else if (Ustrcmp(argrest, "X") == 0) delete_pid_file();
+       else badarg = TRUE;
+       break;
 
-    else if (Ustrcmp(argrest, "P") == 0)
-      override_pid_file_path = argv[++i];
 
-    /* -or <n>: set timeout for non-SMTP acceptance
-       -os <n>: set timeout for SMTP acceptance */
+      /* -or <n>: set timeout for non-SMTP acceptance
+        -os <n>: set timeout for SMTP acceptance */
 
-    else if (*argrest == 'r' || *argrest == 's')
-      {
-      int *tp = (*argrest == 'r')?
-        &arg_receive_timeout : &arg_smtp_receive_timeout;
-      if (argrest[1] == 0)
-        {
-        if (i+1 < argc) *tp= readconf_readtime(argv[++i], 0, FALSE);
-        }
-      else *tp = readconf_readtime(argrest + 1, 0, FALSE);
-      if (*tp < 0)
-        exim_fail("exim: bad time value %s: abandoned\n", argv[i]);
-      }
+      case 'r':
+      case 's':
+       {
+       int * tp = argrest[-1] == 'r'
+         ? &arg_receive_timeout : &arg_smtp_receive_timeout;
+       if (*argrest)
+         *tp = readconf_readtime(argrest, 0, FALSE);
+       else if (i+1 < argc)
+         *tp = readconf_readtime(argv[++i], 0, FALSE);
+
+       if (*tp < 0)
+         exim_fail("exim: bad time value %s: abandoned\n", argv[i]);
+       }
+       break;
 
-    /* -oX <list>: Override local_interfaces and/or default daemon ports */
+      /* -oX <list>: Override local_interfaces and/or default daemon ports */
 
-    else if (Ustrcmp(argrest, "X") == 0)
-      override_local_interfaces = argv[++i];
+      case 'X':
+       if (*argrest) badarg = TRUE;
+       else override_local_interfaces = argv[++i];
+       break;
 
-    /* Unknown -o argument */
+      /* Unknown -o argument */
 
-    else badarg = TRUE;
+      default:
+       badarg = TRUE;
+      }
     break;
 
 
@@ -3579,11 +3659,15 @@ an error return. The following code should cope with both types of system.
  in the call to exim_setugid().
 
 However, if this process isn't running as root, setgroups() can't be used
-since you have to be root to run it, even if throwing away groups. Not being
-root here happens only in some unusual configurations. We just ignore the
-error. */
+since you have to be root to run it, even if throwing away groups.
+Except, sigh, for Hurd - where you can.
+Not being root here happens only in some unusual configurations. */
 
-if (setgroups(0, NULL) != 0 && setgroups(1, group_list) != 0 && !unprivileged)
+if (  !unprivileged
+#ifndef OS_SETGROUPS_ZERO_DROPS_ALL
+   && setgroups(0, NULL) != 0
+#endif
+   && setgroups(1, group_list) != 0)
   exim_fail("exim: setgroups() failed: %s\n", strerror(errno));
 
 /* If the configuration file name has been altered by an argument on the
@@ -4275,7 +4359,7 @@ if (list_queue)
 if (count_queue)
   {
   set_process_info("counting the queue");
-  queue_count();
+  fprintf(stdout, "%u\n", queue_count());
   exit(EXIT_SUCCESS);
   }
 
@@ -4300,6 +4384,11 @@ if (msg_action_arg > 0 && msg_action != MSG_DELIVER && msg_action != MSG_LOAD)
     for (i = msg_action_arg; i < argc; i++)
       if (!queue_action(argv[i], msg_action, NULL, 0, 0))
         yield = EXIT_FAILURE;
+    switch (msg_action)
+      {
+      case MSG_REMOVE: case MSG_FREEZE: case MSG_THAW: break;
+      default: printf("\n"); break;
+      }
     }
 
   else if (!queue_action(argv[msg_action_arg], msg_action, argv, argc,
@@ -4483,31 +4572,9 @@ if (list_config)
   }
 
 
-/* Initialise subsystems as required */
-#ifndef DISABLE_DKIM
-  {
-# ifdef MEASURE_TIMING
-  struct timeval t0;
-  gettimeofday(&t0, NULL);
-# endif
-  dkim_exim_init();
-# ifdef MEASURE_TIMING
-  report_time_since(&t0, US"dkim_exim_init (delta)");
-# endif
-  }
-#endif
-
-  {
-#ifdef MEASURE_TIMING
-  struct timeval t0;
-  gettimeofday(&t0, NULL);
-#endif
-  deliver_init();
-#ifdef MEASURE_TIMING
-  report_time_since(&t0, US"deliver_init (delta)");
-#endif
-  }
+/* Initialise subsystems as required. */
 
+tcp_init();
 
 /* Handle a request to deliver one or more messages that are already on the
 queue. Values of msg_action other than MSG_DELIVER and MSG_LOAD are dealt with
@@ -4702,6 +4769,23 @@ if (f.daemon_listen || f.inetd_wait_mode || queue_interval > 0)
     log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Daemon cannot be run when "
       "mua_wrapper is set");
     }
+
+# ifndef DISABLE_TLS
+  /* This also checks that the library linkage is working and we can call
+  routines in it, so call even if tls_require_ciphers is unset */
+    {
+# ifdef MEASURE_TIMING
+    struct timeval t0, diff;
+    (void)gettimeofday(&t0, NULL);
+# endif
+    if (!tls_dropprivs_validate_require_cipher(FALSE))
+      exit(1);
+# ifdef MEASURE_TIMING
+    report_time_since(&t0, US"validate_ciphers (delta)");
+# endif
+    }
+#endif
+
   daemon_go();
   }
 
@@ -4817,8 +4901,9 @@ if (verify_address_mode || f.address_test_mode)
     {
     while (recipients_arg < argc)
       {
-      uschar *s = argv[recipients_arg++];
-      while (*s != 0)
+      /* Supplied addresses are tainted since they come from a user */
+      uschar * s = string_copy_taint(argv[recipients_arg++], TRUE);
+      while (*s)
         {
         BOOL finished = FALSE;
         uschar *ss = parse_find_address_end(s, FALSE);
@@ -4826,16 +4911,16 @@ if (verify_address_mode || f.address_test_mode)
         test_address(s, flags, &exit_value);
         s = ss;
         if (!finished)
-          while (*(++s) != 0 && (*s == ',' || isspace(*s)));
+          while (*++s == ',' || isspace(*s)) ;
         }
       }
     }
 
   else for (;;)
     {
-    uschar *s = get_stdinput(NULL, NULL);
-    if (s == NULL) break;
-    test_address(s, flags, &exit_value);
+    uschar * s = get_stdinput(NULL, NULL);
+    if (!s) break;
+    test_address(string_copy_taint(s, TRUE), flags, &exit_value);
     }
 
   route_tidyup();
@@ -4962,9 +5047,9 @@ if (host_checking)
   if (!sender_ident_set)
     {
     sender_ident = NULL;
-    if (f.running_in_test_harness && sender_host_port != 0 &&
-        interface_address != NULL && interface_port != 0)
-      verify_get_ident(1413);
+    if (f.running_in_test_harness && sender_host_port
+       && interface_address && interface_port)
+      verify_get_ident(1223);          /* note hardwired port number */
     }
 
   /* In case the given address is a non-canonical IPv6 address, canonicalize
@@ -5329,13 +5414,13 @@ while (more)
 
     raw_sender = string_copy(sender_address);
 
-    /* Loop for each argument */
+    /* Loop for each argument (supplied by user hence tainted) */
 
     for (int i = 0; i < count; i++)
       {
       int start, end, domain;
-      uschar *errmess;
-      uschar *s = list[i];
+      uschar * errmess;
+      uschar * s = string_copy_taint(list[i], TRUE);
 
       /* Loop for each comma-separated address */