Refactor startup error exits
[exim.git] / src / src / exim.c
index 354cdf7a807c41037e189d382579c407065c7ead..198e81d892c43588841e270cb2f25c58780a841c 100644 (file)
@@ -187,7 +187,15 @@ DEBUG(D_process_info) debug_printf("set_process_info: %s", process_info);
 va_end(ap);
 }
 
+/***********************************************
+*            Handler for SIGTERM               *
+***********************************************/
 
+static void
+term_handler(int sig)
+{
+  exit(1);
+}
 
 
 /*************************************************
@@ -412,7 +420,7 @@ if (exim_tvcmp(&now_tv, then_tv) <= 0)
 
   DEBUG(D_transport|D_receive)
     {
-    if (!running_in_test_harness)
+    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,
@@ -542,9 +550,9 @@ close_unwanted(void)
 {
 if (smtp_input)
   {
-  #ifdef SUPPORT_TLS
-  tls_close(TRUE, FALSE);      /* Shut down the TLS library */
-  #endif
+#ifdef SUPPORT_TLS
+  tls_close(NULL, TLS_NO_SHUTDOWN);      /* Shut down the TLS library */
+#endif
   (void)close(fileno(smtp_in));
   (void)close(fileno(smtp_out));
   smtp_in = NULL;
@@ -555,7 +563,7 @@ else
   if ((debug_selector & D_resolver) == 0) (void)close(1);  /* stdout */
   if (debug_selector == 0)                                 /* stderr */
     {
-    if (!synchronous_delivery)
+    if (!f.synchronous_delivery)
       {
       (void)close(2);
       log_stderr = NULL;
@@ -601,21 +609,18 @@ if (euid == root_uid || euid != uid || egid != gid || igflag)
   if (igflag)
     {
     struct passwd *pw = getpwuid(uid);
-    if (pw != NULL)
-      {
-      if (initgroups(pw->pw_name, gid) != 0)
-        log_write(0,LOG_MAIN|LOG_PANIC_DIE,"initgroups failed for uid=%ld: %s",
-          (long int)uid, strerror(errno));
-      }
-    else log_write(0, LOG_MAIN|LOG_PANIC_DIE, "cannot run initgroups(): "
-      "no passwd entry for uid=%ld", (long int)uid);
+    if (!pw)
+      log_write(0, LOG_MAIN|LOG_PANIC_DIE, "cannot run initgroups(): "
+       "no passwd entry for uid=%ld", (long int)uid);
+
+    if (initgroups(pw->pw_name, gid) != 0)
+      log_write(0,LOG_MAIN|LOG_PANIC_DIE,"initgroups failed for uid=%ld: %s",
+       (long int)uid, strerror(errno));
     }
 
   if (setgid(gid) < 0 || setuid(uid) < 0)
-    {
     log_write(0, LOG_MAIN|LOG_PANIC_DIE, "unable to set gid=%ld or uid=%ld "
       "(euid=%ld): %s", (long int)gid, (long int)uid, (long int)euid, msg);
-    }
   }
 
 /* Debugging output included uid/gid and all groups */
@@ -670,6 +675,17 @@ exit(rc);
 
 
 
+/* Print error string, then die */
+static void
+exim_fail(const char * fmt, ...)
+{
+va_list ap;
+va_start(ap, fmt);
+vfprintf(stderr, fmt, ap);
+exit(EXIT_FAILURE);
+}
+
+
 
 /*************************************************
 *         Extract port from host address         *
@@ -691,10 +707,7 @@ check_port(uschar *address)
 {
 int port = host_address_extract_port(address);
 if (string_is_ip_address(address, NULL) == 0)
-  {
-  fprintf(stderr, "exim abandoned: \"%s\" is not an IP address\n", address);
-  exit(EXIT_FAILURE);
-  }
+  exim_fail("exim abandoned: \"%s\" is not an IP address\n", address);
 return port;
 }
 
@@ -785,187 +798,193 @@ Returns:    nothing
 */
 
 static void
-show_whats_supported(FILE * f)
+show_whats_supported(FILE * fp)
 {
 auth_info * authi;
 
-DEBUG(D_any) {} else show_db_version(f);
+DEBUG(D_any) {} else show_db_version(fp);
 
-fprintf(f, "Support for:");
+fprintf(fp, "Support for:");
 #ifdef SUPPORT_CRYPTEQ
-  fprintf(f, " crypteq");
+  fprintf(fp, " crypteq");
 #endif
 #if HAVE_ICONV
-  fprintf(f, " iconv()");
+  fprintf(fp, " iconv()");
 #endif
 #if HAVE_IPV6
-  fprintf(f, " IPv6");
+  fprintf(fp, " IPv6");
 #endif
 #ifdef HAVE_SETCLASSRESOURCES
-  fprintf(f, " use_setclassresources");
+  fprintf(fp, " use_setclassresources");
 #endif
 #ifdef SUPPORT_PAM
-  fprintf(f, " PAM");
+  fprintf(fp, " PAM");
 #endif
 #ifdef EXIM_PERL
-  fprintf(f, " Perl");
+  fprintf(fp, " Perl");
 #endif
 #ifdef EXPAND_DLFUNC
-  fprintf(f, " Expand_dlfunc");
+  fprintf(fp, " Expand_dlfunc");
 #endif
 #ifdef USE_TCP_WRAPPERS
-  fprintf(f, " TCPwrappers");
+  fprintf(fp, " TCPwrappers");
 #endif
 #ifdef SUPPORT_TLS
 # ifdef USE_GNUTLS
-  fprintf(f, " GnuTLS");
+  fprintf(fp, " GnuTLS");
 # else
-  fprintf(f, " OpenSSL");
+  fprintf(fp, " OpenSSL");
 # endif
 #endif
 #ifdef SUPPORT_TRANSLATE_IP_ADDRESS
-  fprintf(f, " translate_ip_address");
+  fprintf(fp, " translate_ip_address");
 #endif
 #ifdef SUPPORT_MOVE_FROZEN_MESSAGES
-  fprintf(f, " move_frozen_messages");
+  fprintf(fp, " move_frozen_messages");
 #endif
 #ifdef WITH_CONTENT_SCAN
-  fprintf(f, " Content_Scanning");
+  fprintf(fp, " Content_Scanning");
+#endif
+#ifdef SUPPORT_DANE
+  fprintf(fp, " DANE");
 #endif
 #ifndef DISABLE_DKIM
-  fprintf(f, " DKIM");
+  fprintf(fp, " DKIM");
 #endif
 #ifndef DISABLE_DNSSEC
-  fprintf(f, " DNSSEC");
+  fprintf(fp, " DNSSEC");
 #endif
 #ifndef DISABLE_EVENT
-  fprintf(f, " Event");
+  fprintf(fp, " Event");
 #endif
 #ifdef SUPPORT_I18N
-  fprintf(f, " I18N");
+  fprintf(fp, " I18N");
 #endif
 #ifndef DISABLE_OCSP
-  fprintf(f, " OCSP");
+  fprintf(fp, " OCSP");
 #endif
 #ifndef DISABLE_PRDR
-  fprintf(f, " PRDR");
+  fprintf(fp, " PRDR");
 #endif
 #ifdef SUPPORT_PROXY
-  fprintf(f, " PROXY");
+  fprintf(fp, " PROXY");
 #endif
 #ifdef SUPPORT_SOCKS
-  fprintf(f, " SOCKS");
+  fprintf(fp, " SOCKS");
 #endif
 #ifdef SUPPORT_SPF
-  fprintf(f, " SPF");
+  fprintf(fp, " SPF");
 #endif
 #ifdef TCP_FASTOPEN
   deliver_init();
-  if (tcp_fastopen_ok) fprintf(f, " TCP_Fast_Open");
+  if (f.tcp_fastopen_ok) fprintf(fp, " TCP_Fast_Open");
 #endif
 #ifdef EXPERIMENTAL_LMDB
-  fprintf(f, " Experimental_LMDB");
+  fprintf(fp, " Experimental_LMDB");
 #endif
 #ifdef EXPERIMENTAL_QUEUEFILE
-  fprintf(f, " Experimental_QUEUEFILE");
+  fprintf(fp, " Experimental_QUEUEFILE");
 #endif
 #ifdef EXPERIMENTAL_SRS
-  fprintf(f, " Experimental_SRS");
+  fprintf(fp, " Experimental_SRS");
 #endif
-#ifdef EXPERIMENTAL_BRIGHTMAIL
-  fprintf(f, " Experimental_Brightmail");
+#ifdef EXPERIMENTAL_ARC
+  fprintf(fp, " Experimental_ARC");
 #endif
-#ifdef EXPERIMENTAL_DANE
-  fprintf(f, " Experimental_DANE");
+#ifdef EXPERIMENTAL_BRIGHTMAIL
+  fprintf(fp, " Experimental_Brightmail");
 #endif
 #ifdef EXPERIMENTAL_DCC
-  fprintf(f, " Experimental_DCC");
+  fprintf(fp, " Experimental_DCC");
 #endif
 #ifdef EXPERIMENTAL_DMARC
-  fprintf(f, " Experimental_DMARC");
+  fprintf(fp, " Experimental_DMARC");
 #endif
 #ifdef EXPERIMENTAL_DSN_INFO
-  fprintf(f, " Experimental_DSN_info");
+  fprintf(fp, " Experimental_DSN_info");
 #endif
-fprintf(f, "\n");
+#ifdef EXPERIMENTAL_REQUIRETLS
+  fprintf(fp, " Experimental_REQUIRETLS");
+#endif
+fprintf(fp, "\n");
 
-fprintf(f, "Lookups (built-in):");
+fprintf(fp, "Lookups (built-in):");
 #if defined(LOOKUP_LSEARCH) && LOOKUP_LSEARCH!=2
-  fprintf(f, " lsearch wildlsearch nwildlsearch iplsearch");
+  fprintf(fp, " lsearch wildlsearch nwildlsearch iplsearch");
 #endif
 #if defined(LOOKUP_CDB) && LOOKUP_CDB!=2
-  fprintf(f, " cdb");
+  fprintf(fp, " cdb");
 #endif
 #if defined(LOOKUP_DBM) && LOOKUP_DBM!=2
-  fprintf(f, " dbm dbmjz dbmnz");
+  fprintf(fp, " dbm dbmjz dbmnz");
 #endif
 #if defined(LOOKUP_DNSDB) && LOOKUP_DNSDB!=2
-  fprintf(f, " dnsdb");
+  fprintf(fp, " dnsdb");
 #endif
 #if defined(LOOKUP_DSEARCH) && LOOKUP_DSEARCH!=2
-  fprintf(f, " dsearch");
+  fprintf(fp, " dsearch");
 #endif
 #if defined(LOOKUP_IBASE) && LOOKUP_IBASE!=2
-  fprintf(f, " ibase");
+  fprintf(fp, " ibase");
 #endif
 #if defined(LOOKUP_LDAP) && LOOKUP_LDAP!=2
-  fprintf(f, " ldap ldapdn ldapm");
+  fprintf(fp, " ldap ldapdn ldapm");
 #endif
 #ifdef EXPERIMENTAL_LMDB
-  fprintf(f, " lmdb");
+  fprintf(fp, " lmdb");
 #endif
 #if defined(LOOKUP_MYSQL) && LOOKUP_MYSQL!=2
-  fprintf(f, " mysql");
+  fprintf(fp, " mysql");
 #endif
 #if defined(LOOKUP_NIS) && LOOKUP_NIS!=2
-  fprintf(f, " nis nis0");
+  fprintf(fp, " nis nis0");
 #endif
 #if defined(LOOKUP_NISPLUS) && LOOKUP_NISPLUS!=2
-  fprintf(f, " nisplus");
+  fprintf(fp, " nisplus");
 #endif
 #if defined(LOOKUP_ORACLE) && LOOKUP_ORACLE!=2
-  fprintf(f, " oracle");
+  fprintf(fp, " oracle");
 #endif
 #if defined(LOOKUP_PASSWD) && LOOKUP_PASSWD!=2
-  fprintf(f, " passwd");
+  fprintf(fp, " passwd");
 #endif
 #if defined(LOOKUP_PGSQL) && LOOKUP_PGSQL!=2
-  fprintf(f, " pgsql");
+  fprintf(fp, " pgsql");
 #endif
 #if defined(LOOKUP_REDIS) && LOOKUP_REDIS!=2
-  fprintf(f, " redis");
+  fprintf(fp, " redis");
 #endif
 #if defined(LOOKUP_SQLITE) && LOOKUP_SQLITE!=2
-  fprintf(f, " sqlite");
+  fprintf(fp, " sqlite");
 #endif
 #if defined(LOOKUP_TESTDB) && LOOKUP_TESTDB!=2
-  fprintf(f, " testdb");
+  fprintf(fp, " testdb");
 #endif
 #if defined(LOOKUP_WHOSON) && LOOKUP_WHOSON!=2
-  fprintf(f, " whoson");
+  fprintf(fp, " whoson");
 #endif
-fprintf(f, "\n");
+fprintf(fp, "\n");
 
-auth_show_supported(f);
-route_show_supported(f);
-transport_show_supported(f);
+auth_show_supported(fp);
+route_show_supported(fp);
+transport_show_supported(fp);
 
 #ifdef WITH_CONTENT_SCAN
-malware_show_supported(f);
+malware_show_supported(fp);
 #endif
 
 if (fixed_never_users[0] > 0)
   {
   int i;
-  fprintf(f, "Fixed never_users: ");
+  fprintf(fp, "Fixed never_users: ");
   for (i = 1; i <= (int)fixed_never_users[0] - 1; i++)
-    fprintf(f, "%d:", (unsigned int)fixed_never_users[i]);
-  fprintf(f, "%d\n", (unsigned int)fixed_never_users[i]);
+    fprintf(fp, "%d:", (unsigned int)fixed_never_users[i]);
+  fprintf(fp, "%d\n", (unsigned int)fixed_never_users[i]);
   }
 
-fprintf(f, "Configure owner: %d:%d\n", config_uid, config_gid);
+fprintf(fp, "Configure owner: %d:%d\n", config_uid, config_gid);
 
-fprintf(f, "Size of off_t: " SIZE_T_FMT "\n", sizeof(off_t));
+fprintf(fp, "Size of off_t: " SIZE_T_FMT "\n", sizeof(off_t));
 
 /* Everything else is details which are only worth reporting when debugging.
 Perhaps the tls_version_report should move into this too. */
@@ -975,9 +994,9 @@ DEBUG(D_any) do {
 
 /* clang defines __GNUC__ (at least, for me) so test for it first */
 #if defined(__clang__)
-  fprintf(f, "Compiler: CLang [%s]\n", __clang_version__);
+  fprintf(fp, "Compiler: CLang [%s]\n", __clang_version__);
 #elif defined(__GNUC__)
-  fprintf(f, "Compiler: GCC [%s]\n",
+  fprintf(fp, "Compiler: GCC [%s]\n",
 # ifdef __VERSION__
       __VERSION__
 # else
@@ -985,29 +1004,29 @@ DEBUG(D_any) do {
 # endif
       );
 #else
-  fprintf(f, "Compiler: <unknown>\n");
+  fprintf(fp, "Compiler: <unknown>\n");
 #endif
 
 #if defined(__GLIBC__) && !defined(__UCLIBC__)
-  fprintf(f, "Library version: Glibc: Compile: %d.%d\n",
+  fprintf(fp, "Library version: Glibc: Compile: %d.%d\n",
                __GLIBC__, __GLIBC_MINOR__);
   if (__GLIBC_PREREQ(2, 1))
-    fprintf(f, "                        Runtime: %s\n",
+    fprintf(fp, "                        Runtime: %s\n",
                gnu_get_libc_version());
 #endif
 
-show_db_version(f);
+show_db_version(fp);
 
 #ifdef SUPPORT_TLS
-  tls_version_report(f);
+  tls_version_report(fp);
 #endif
 #ifdef SUPPORT_I18N
-  utf8_version_report(f);
+  utf8_version_report(fp);
 #endif
 
   for (authi = auths_available; *authi->driver_name != '\0'; ++authi)
     if (authi->version_report)
-      (*authi->version_report)(f);
+      (*authi->version_report)(fp);
 
   /* PCRE_PRERELEASE is either defined and empty or a bare sequence of
   characters; unless it's an ancient version of PCRE in which case it
@@ -1017,7 +1036,7 @@ show_db_version(f);
 #endif
 #define QUOTE(X) #X
 #define EXPAND_AND_QUOTE(X) QUOTE(X)
-  fprintf(f, "Library version: PCRE: Compile: %d.%d%s\n"
+  fprintf(fp, "Library version: PCRE: Compile: %d.%d%s\n"
              "                       Runtime: %s\n",
           PCRE_MAJOR, PCRE_MINOR,
           EXPAND_AND_QUOTE(PCRE_PRERELEASE) "",
@@ -1028,17 +1047,17 @@ show_db_version(f);
   init_lookup_list();
   for (i = 0; i < lookup_list_count; i++)
     if (lookup_list[i]->version_report)
-      lookup_list[i]->version_report(f);
+      lookup_list[i]->version_report(fp);
 
 #ifdef WHITELIST_D_MACROS
-  fprintf(f, "WHITELIST_D_MACROS: \"%s\"\n", WHITELIST_D_MACROS);
+  fprintf(fp, "WHITELIST_D_MACROS: \"%s\"\n", WHITELIST_D_MACROS);
 #else
-  fprintf(f, "WHITELIST_D_MACROS unset\n");
+  fprintf(fp, "WHITELIST_D_MACROS unset\n");
 #endif
 #ifdef TRUSTED_CONFIG_LIST
-  fprintf(f, "TRUSTED_CONFIG_LIST: \"%s\"\n", TRUSTED_CONFIG_LIST);
+  fprintf(fp, "TRUSTED_CONFIG_LIST: \"%s\"\n", TRUSTED_CONFIG_LIST);
 #else
-  fprintf(f, "TRUSTED_CONFIG_LIST unset\n");
+  fprintf(fp, "TRUSTED_CONFIG_LIST unset\n");
 #endif
 
 } while (0);
@@ -1273,20 +1292,15 @@ exim_usage(uschar *progname)
 
 /* Handle specific program invocation variants */
 if (Ustrcmp(progname, US"-mailq") == 0)
-  {
-  fprintf(stderr,
+  exim_fail(
     "mailq - list the contents of the mail queue\n\n"
     "For a list of options, see the Exim documentation.\n");
-  exit(EXIT_FAILURE);
-  }
 
 /* Generic usage - we output this whatever happens */
-fprintf(stderr,
+exim_fail(
   "Exim is a Mail Transfer Agent. It is normally called by Mail User Agents,\n"
   "not directly from a shell command line. Options and/or arguments control\n"
   "what it does when called. For a list of options, see the Exim documentation.\n");
-
-exit(EXIT_FAILURE);
 }
 
 
@@ -1295,32 +1309,6 @@ exit(EXIT_FAILURE);
 *    Validate that the macros given are okay     *
 *************************************************/
 
-#ifdef WHITELIST_D_MACROS
-static void
-wlist_check(uschar * name, uschar * val, void * ctx)
-{
-uschar ** w, ** whites = ctx;
-unsigned len;
-int n;
-
-for (w = whites; *w; ++w)
-  if (Ustrcmp(*w, name) == 0) break;
-if (*w) 
-  {
-  if (!val || !*val) return;
-  len = Ustrlen(val);
-  if ((n = pcre_exec(regex_whitelisted_macro, NULL, CS val, len,
-                   0, PCRE_EOPT, NULL, 0)) >= 0)
-    return;
-  if (n != PCRE_ERROR_NOMATCH)
-    debug_printf("macros_trusted checking %s returned %d\n", name, n);
-  }
-*whites = NULL;
-return;
-}
-#endif
-
-
 /* Typically, Exim will drop privileges if macros are supplied.  In some
 cases, we want to not do so.
 
@@ -1333,7 +1321,7 @@ macros_trusted(BOOL opt_D_used)
 {
 #ifdef WHITELIST_D_MACROS
 macro_item *m;
-uschar *whitelisted, *end, *p, **whites;
+uschar *whitelisted, *end, *p, **whites, **w;
 int white_count, i, n;
 size_t len;
 BOOL prev_char_item, found;
@@ -1396,9 +1384,32 @@ for (p = whitelisted, i = 0; (p != end) && (i < white_count); ++p)
   }
 whites[i] = NULL;
 
-tree_walk(tree_macros, wlist_check, whites);
-if (!*whites) return FALSE;
-
+/* The list of commandline macros should be very short.
+Accept the N*M complexity. */
+for (m = macros_user; m; m = m->next) if (m->command_line)
+  {
+  found = FALSE;
+  for (w = whites; *w; ++w)
+    if (Ustrcmp(*w, m->name) == 0)
+      {
+      found = TRUE;
+      break;
+      }
+  if (!found)
+    return FALSE;
+  if (!m->replacement)
+    continue;
+  if ((len = m->replen) == 0)
+    continue;
+  n = pcre_exec(regex_whitelisted_macro, NULL, CS m->replacement, len,
+   0, PCRE_EOPT, NULL, 0);
+  if (n < 0)
+    {
+    if (n != PCRE_ERROR_NOMATCH)
+      debug_printf("macros_trusted checking %s returned %d\n", m->name, n);
+    return FALSE;
+    }
+  }
 DEBUG(D_any) debug_printf("macros_trusted overridden to true by whitelisting\n");
 return TRUE;
 #endif
@@ -1430,10 +1441,7 @@ len = Ustrlen(big_buffer);
 if (isupper(big_buffer[0]))
   {
   if (macro_read_assignment(big_buffer))
-    {
-    uschar * s = Ustrchr(big_buffer, '=');
-    printf("Defined macro '%.*s'\n", (int)(s - big_buffer), big_buffer);
-    }
+    printf("Defined macro '%s'\n", mlast->name);
   }
 else
   if ((line = expand_string(big_buffer))) printf("%s\n", CS line);
@@ -1441,6 +1449,7 @@ else
 }
 
 
+
 /*************************************************
 *          Entry point and high-level code       *
 *************************************************/
@@ -1556,49 +1565,32 @@ This is a feature to make the lives of binary distributors easier. */
 if (route_finduser(US EXIM_USERNAME, &pw, &exim_uid))
   {
   if (exim_uid == 0)
-    {
-    fprintf(stderr, "exim: refusing to run with uid 0 for \"%s\"\n",
-      EXIM_USERNAME);
-    exit(EXIT_FAILURE);
-    }
+    exim_fail("exim: refusing to run with uid 0 for \"%s\"\n", EXIM_USERNAME);
+
   /* If ref:name uses a number as the name, route_finduser() returns
   TRUE with exim_uid set and pw coerced to NULL. */
   if (pw)
     exim_gid = pw->pw_gid;
 #ifndef EXIM_GROUPNAME
   else
-    {
-    fprintf(stderr,
+    exim_fail(
         "exim: ref:name should specify a usercode, not a group.\n"
         "exim: can't let you get away with it unless you also specify a group.\n");
-    exit(EXIT_FAILURE);
-    }
 #endif
   }
 else
-  {
-  fprintf(stderr, "exim: failed to find uid for user name \"%s\"\n",
-    EXIM_USERNAME);
-  exit(EXIT_FAILURE);
-  }
+  exim_fail("exim: failed to find uid for user name \"%s\"\n", EXIM_USERNAME);
 #endif
 
 #ifdef EXIM_GROUPNAME
 if (!route_findgroup(US EXIM_GROUPNAME, &exim_gid))
-  {
-  fprintf(stderr, "exim: failed to find gid for group name \"%s\"\n",
-    EXIM_GROUPNAME);
-  exit(EXIT_FAILURE);
-  }
+  exim_fail("exim: failed to find gid for group name \"%s\"\n", EXIM_GROUPNAME);
 #endif
 
 #ifdef CONFIGURE_OWNERNAME
 if (!route_finduser(US CONFIGURE_OWNERNAME, NULL, &config_uid))
-  {
-  fprintf(stderr, "exim: failed to find uid for user name \"%s\"\n",
+  exim_fail("exim: failed to find uid for user name \"%s\"\n",
     CONFIGURE_OWNERNAME);
-  exit(EXIT_FAILURE);
-  }
 #endif
 
 /* We default the system_filter_user to be the Exim run-time user, as a
@@ -1607,11 +1599,8 @@ system_filter_uid = exim_uid;
 
 #ifdef CONFIGURE_GROUPNAME
 if (!route_findgroup(US CONFIGURE_GROUPNAME, &config_gid))
-  {
-  fprintf(stderr, "exim: failed to find gid for group name \"%s\"\n",
+  exim_fail("exim: failed to find gid for group name \"%s\"\n",
     CONFIGURE_GROUPNAME);
-  exit(EXIT_FAILURE);
-  }
 #endif
 
 /* In the Cygwin environment, some initialization used to need doing.
@@ -1625,8 +1614,10 @@ OS_INIT
 /* Check a field which is patched when we are running Exim within its
 testing harness; do a fast initial check, and then the whole thing. */
 
-running_in_test_harness =
+f.running_in_test_harness =
   *running_status == '<' && Ustrcmp(running_status, "<<<testing>>>") == 0;
+if (f.running_in_test_harness)
+  debug_store = TRUE;
 
 /* The C standard says that the equivalent of setlocale(LC_ALL, "C") is obeyed
 at the start of a program; however, it seems that some environments do not
@@ -1643,10 +1634,7 @@ os_non_restarting_signal(SIGALRM, sigalrm_handler);
 because store_malloc writes a log entry on failure. */
 
 if (!(log_buffer = US malloc(LOG_BUFFER_SIZE)))
-  {
-  fprintf(stderr, "exim: failed to get store for log buffer\n");
-  exit(EXIT_FAILURE);
-  }
+  exim_fail("exim: failed to get store for log buffer\n");
 
 /* Initialize the default log options. */
 
@@ -1679,6 +1667,10 @@ descriptive text. */
 set_process_info("initializing");
 os_restarting_signal(SIGUSR1, usr1_handler);
 
+/* If running in a dockerized environment, the TERM signal is only
+delegated to the PID 1 if we request it by setting an signal handler */
+if (getpid() == 1) signal(SIGTERM, term_handler);
+
 /* SIGHUP is used to get the daemon to reconfigure. It gets set as appropriate
 in the daemon code. For the rest of Exim's uses, we ignore it. */
 
@@ -1785,7 +1777,7 @@ message has been sent). */
 if ((namelen == 5 && Ustrcmp(argv[0], "rmail") == 0) ||
     (namelen  > 5 && Ustrncmp(argv[0] + namelen - 6, "/rmail", 6) == 0))
   {
-  dot_ends = FALSE;
+  f.dot_ends = FALSE;
   called_as = US"-rmail";
   errors_sender_rc = EXIT_SUCCESS;
   }
@@ -1837,20 +1829,12 @@ real_gid = getgid();
 
 if (real_uid == root_uid)
   {
-  rv = setgid(real_gid);
-  if (rv)
-    {
-    fprintf(stderr, "exim: setgid(%ld) failed: %s\n",
+  if ((rv = setgid(real_gid)))
+    exim_fail("exim: setgid(%ld) failed: %s\n",
         (long int)real_gid, strerror(errno));
-    exit(EXIT_FAILURE);
-    }
-  rv = setuid(real_uid);
-  if (rv)
-    {
-    fprintf(stderr, "exim: setuid(%ld) failed: %s\n",
+  if ((rv = setuid(real_uid)))
+    exim_fail("exim: setuid(%ld) failed: %s\n",
         (long int)real_uid, strerror(errno));
-    exit(EXIT_FAILURE);
-    }
   }
 
 /* If neither the original real uid nor the original euid was root, Exim is
@@ -1907,7 +1891,7 @@ for (i = 1; i < argc; i++)
     {
     switchchar = arg[3];
     argrest += 2;
-    queue_2stage = TRUE;
+    f.queue_2stage = TRUE;
     }
 
   /* Make -r synonymous with -f, since it is a documented alias */
@@ -1978,8 +1962,8 @@ for (i = 1; i < argc; i++)
 
     if (*argrest == 'd')
       {
-      daemon_listen = TRUE;
-      if (*(++argrest) == 'f') background_daemon = FALSE;
+      f.daemon_listen = TRUE;
+      if (*(++argrest) == 'f') f.background_daemon = FALSE;
         else if (*argrest != 0) { badarg = TRUE; break; }
       }
 
@@ -2006,10 +1990,7 @@ for (i = 1; i < argc; i++)
       filter_test |= checking = FTEST_SYSTEM;
       if (*(++argrest) != 0) { badarg = TRUE; break; }
       if (++i < argc) filter_test_sfile = argv[i]; else
-        {
-        fprintf(stderr, "exim: file name expected after %s\n", argv[i-1]);
-        exit(EXIT_FAILURE);
-        }
+        exim_fail("exim: file name expected after %s\n", argv[i-1]);
       }
 
     /* -bf:  Run user filter test
@@ -2025,18 +2006,12 @@ for (i = 1; i < argc; i++)
         {
         filter_test |= checking = FTEST_USER;
         if (++i < argc) filter_test_ufile = argv[i]; else
-          {
-          fprintf(stderr, "exim: file name expected after %s\n", argv[i-1]);
-          exit(EXIT_FAILURE);
-          }
+          exim_fail("exim: file name expected after %s\n", argv[i-1]);
         }
       else
         {
         if (++i >= argc)
-          {
-          fprintf(stderr, "exim: string expected after %s\n", arg);
-          exit(EXIT_FAILURE);
-          }
+          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];
@@ -2051,8 +2026,8 @@ for (i = 1; i < argc; i++)
       {
       if (++i >= argc) { badarg = TRUE; break; }
       sender_host_address = argv[i];
-      host_checking = checking = log_testing_mode = TRUE;
-      host_checking_callout = argrest[1] == 'c';
+      host_checking = checking = f.log_testing_mode = TRUE;
+      f.host_checking_callout = argrest[1] == 'c';
       message_logs = FALSE;
       }
 
@@ -2109,8 +2084,8 @@ for (i = 1; i < argc; i++)
 
     else if (Ustrcmp(argrest, "nq") == 0)
       {
-      allow_unqualified_sender = FALSE;
-      allow_unqualified_recipient = FALSE;
+      f.allow_unqualified_sender = FALSE;
+      f.allow_unqualified_recipient = FALSE;
       }
 
     /* -bpxx: List the contents of the mail queue, in various forms. If
@@ -2209,18 +2184,18 @@ for (i = 1; i < argc; i++)
     /* -bt: address testing mode */
 
     else if (Ustrcmp(argrest, "t") == 0)
-      address_test_mode = checking = log_testing_mode = TRUE;
+      f.address_test_mode = checking = f.log_testing_mode = TRUE;
 
     /* -bv: verify addresses */
 
     else if (Ustrcmp(argrest, "v") == 0)
-      verify_address_mode = checking = log_testing_mode = TRUE;
+      verify_address_mode = checking = f.log_testing_mode = TRUE;
 
     /* -bvs: verify sender addresses */
 
     else if (Ustrcmp(argrest, "vs") == 0)
       {
-      verify_address_mode = checking = log_testing_mode = TRUE;
+      verify_address_mode = checking = f.log_testing_mode = TRUE;
       verify_as_sender = TRUE;
       }
 
@@ -2233,25 +2208,19 @@ for (i = 1; i < argc; i++)
       printf("%s\n", CS version_copyright);
       version_printed = TRUE;
       show_whats_supported(stdout);
-      log_testing_mode = TRUE;
+      f.log_testing_mode = TRUE;
       }
 
     /* -bw: inetd wait mode, accept a listening socket as stdin */
 
     else if (*argrest == 'w')
       {
-      inetd_wait_mode = TRUE;
-      background_daemon = FALSE;
-      daemon_listen = TRUE;
+      f.inetd_wait_mode = TRUE;
+      f.background_daemon = FALSE;
+      f.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);
-          }
-        }
+        if ((inetd_wait_timeout = readconf_readtime(argrest, 0, FALSE)) <= 0)
+          exim_fail("exim: bad time value %s: abandoned\n", argv[i]);
       }
 
     else badarg = TRUE;
@@ -2281,10 +2250,7 @@ for (i = 1; i < argc; i++)
              Ustrncmp(filename, ALT_CONFIG_PREFIX, len) != 0 ||
              Ustrstr(filename, "/../") != NULL) &&
              (Ustrcmp(filename, "/dev/null") != 0 || real_uid != root_uid))
-          {
-          fprintf(stderr, "-C Permission denied\n");
-          exit(EXIT_FAILURE);
-          }
+          exim_fail("-C Permission denied\n");
         }
       #endif
       if (real_uid != root_uid)
@@ -2296,7 +2262,7 @@ for (i = 1; i < argc; i++)
             && real_uid != config_uid
             #endif
             )
-          trusted_config = FALSE;
+          f.trusted_config = FALSE;
         else
           {
           FILE *trust_list = Ufopen(TRUSTED_CONFIG_LIST, "rb");
@@ -2318,7 +2284,7 @@ for (i = 1; i < argc; i++)
                    ) ||                            /* or */
                 (statbuf.st_mode & 2) != 0)        /* world writeable */
               {
-              trusted_config = FALSE;
+              f.trusted_config = FALSE;
               fclose(trust_list);
               }
            else
@@ -2350,7 +2316,7 @@ for (i = 1; i < argc; i++)
                 int sep = 0;
                 const uschar *list = argrest;
                 uschar *filename;
-                while (trusted_config && (filename = string_nextinlist(&list,
+                while (f.trusted_config && (filename = string_nextinlist(&list,
                         &sep, big_buffer, big_buffer_size)) != NULL)
                   {
                   for (i=0; i < nr_configs; i++)
@@ -2360,7 +2326,7 @@ for (i = 1; i < argc; i++)
                     }
                   if (i == nr_configs)
                     {
-                    trusted_config = FALSE;
+                    f.trusted_config = FALSE;
                     break;
                     }
                   }
@@ -2369,24 +2335,24 @@ for (i = 1; i < argc; i++)
               else
                 {
                 /* No valid prefixes found in trust_list file. */
-                trusted_config = FALSE;
+                f.trusted_config = FALSE;
                 }
               }
            }
           else
             {
             /* Could not open trust_list file. */
-            trusted_config = FALSE;
+            f.trusted_config = FALSE;
             }
           }
       #else
         /* Not root; don't trust config */
-        trusted_config = FALSE;
+        f.trusted_config = FALSE;
       #endif
         }
 
       config_main_filelist = argrest;
-      config_changed = TRUE;
+      f.config_changed = TRUE;
       }
     break;
 
@@ -2394,10 +2360,9 @@ for (i = 1; i < argc; i++)
     /* -D: set up a macro definition */
 
     case 'D':
-    #ifdef DISABLE_D_OPTION
-    fprintf(stderr, "exim: -D is not available in this Exim binary\n");
-    exit(EXIT_FAILURE);
-    #else
+#ifdef DISABLE_D_OPTION
+      exim_fail("exim: -D is not available in this Exim binary\n");
+#else
       {
       int ptr = 0;
       macro_item *m;
@@ -2408,11 +2373,8 @@ for (i = 1; i < argc; i++)
       while (isspace(*s)) s++;
 
       if (*s < 'A' || *s > 'Z')
-        {
-        fprintf(stderr, "exim: macro name set by -D must start with "
+        exim_fail("exim: macro name set by -D must start with "
           "an upper case letter\n");
-        exit(EXIT_FAILURE);
-        }
 
       while (isalnum(*s) || *s == '_')
         {
@@ -2428,21 +2390,16 @@ for (i = 1; i < argc; i++)
         while (isspace(*s)) s++;
         }
 
-      if (macro_search(name))
-        {
-        fprintf(stderr, "exim: duplicated -D in command line\n");
-        exit(EXIT_FAILURE);
-        }
+      for (m = macros_user; m; m = m->next)
+        if (Ustrcmp(m->name, name) == 0)
+          exim_fail("exim: duplicated -D in command line\n");
 
       m = macro_create(name, s, TRUE);
 
       if (clmacro_count >= MAX_CLMACROS)
-        {
-        fprintf(stderr, "exim: too many -D options on command line\n");
-        exit(EXIT_FAILURE);
-        }
-      clmacros[clmacro_count++] = string_sprintf("-D%s=%s",
-       m->tnode.name, m->tnode.data.ptr);
+        exim_fail("exim: too many -D options on command line\n");
+      clmacros[clmacro_count++] = string_sprintf("-D%s=%s", m->name,
+        m->replacement);
       }
     #endif
     break;
@@ -2467,7 +2424,7 @@ for (i = 1; i < argc; i++)
       debug_file = NULL;
       if (*argrest == 'd')
         {
-        debug_daemon = TRUE;
+        f.debug_daemon = TRUE;
         argrest++;
         }
       if (*argrest != 0)
@@ -2486,7 +2443,7 @@ for (i = 1; i < argc; i++)
     message_reference at it, for logging. */
 
     case 'E':
-    local_error_message = TRUE;
+    f.local_error_message = TRUE;
     if (mac_ismsgid(argrest)) message_reference = argrest;
     break;
 
@@ -2523,7 +2480,7 @@ for (i = 1; i < argc; i++)
         { badarg = TRUE; break; }
       }
     originator_name = argrest;
-    sender_name_forced = TRUE;
+    f.sender_name_forced = TRUE;
     break;
 
 
@@ -2571,13 +2528,10 @@ for (i = 1; i < argc; i++)
 #endif
         allow_domain_literals = FALSE;
         strip_trailing_dot = FALSE;
-        if (sender_address == NULL)
-          {
-          fprintf(stderr, "exim: bad -f address \"%s\": %s\n", argrest, errmess);
-          return EXIT_FAILURE;
-          }
+        if (!sender_address)
+          exim_fail("exim: bad -f address \"%s\": %s\n", argrest, errmess);
         }
-      sender_address_forced = TRUE;
+      f.sender_address_forced = TRUE;
       }
     break;
 
@@ -2608,7 +2562,7 @@ for (i = 1; i < argc; i++)
     not to be documented for sendmail but mailx (at least) uses it) */
 
     case 'i':
-    if (*argrest == 0) dot_ends = FALSE; else badarg = TRUE;
+    if (*argrest == 0) f.dot_ends = FALSE; else badarg = TRUE;
     break;
 
 
@@ -2621,17 +2575,10 @@ for (i = 1; i < argc; i++)
       if(++i < argc) argrest = argv[i]; else
         { badarg = TRUE; break; }
       }
-    sz = Ustrlen(argrest);
-    if (sz > 32)
-      {
-      fprintf(stderr, "exim: the -L syslog name is too long: \"%s\"\n", argrest);
-      return EXIT_FAILURE;
-      }
+    if ((sz = Ustrlen(argrest)) > 32)
+      exim_fail("exim: the -L syslog name is too long: \"%s\"\n", argrest);
     if (sz < 1)
-      {
-      fprintf(stderr, "exim: the -L syslog name is too short\n");
-      return EXIT_FAILURE;
-      }
+      exim_fail("exim: the -L syslog name is too short\n");
     cmdline_syslog_name = argrest;
     break;
 
@@ -2657,16 +2604,10 @@ for (i = 1; i < argc; i++)
       EXIM_SOCKLEN_T size = sizeof(interface_sock);
 
       if (argc != i + 6)
-        {
-        fprintf(stderr, "exim: too many or too few arguments after -MC\n");
-        return EXIT_FAILURE;
-        }
+        exim_fail("exim: too many or too few arguments after -MC\n");
 
       if (msg_action_arg >= 0)
-        {
-        fprintf(stderr, "exim: incompatible arguments\n");
-        return EXIT_FAILURE;
-        }
+        exim_fail("exim: incompatible arguments\n");
 
       continue_transport = argv[++i];
       continue_hostname = argv[++i];
@@ -2679,11 +2620,8 @@ for (i = 1; i < argc; i++)
       queue_run_pipe = passed_qr_pipe;
 
       if (!mac_ismsgid(argv[i]))
-        {
-        fprintf(stderr, "exim: malformed message id %s after -MC option\n",
+        exim_fail("exim: malformed message id %s after -MC option\n",
           argv[i]);
-        return EXIT_FAILURE;
-        }
 
       /* Set up $sending_ip_address and $sending_port, unless proxied */
 
@@ -2693,13 +2631,10 @@ for (i = 1; i < argc; i++)
          sending_ip_address = host_ntoa(-1, &interface_sock, NULL,
            &sending_port);
        else
-         {
-         fprintf(stderr, "exim: getsockname() failed after -MC option: %s\n",
+         exim_fail("exim: getsockname() failed after -MC option: %s\n",
            strerror(errno));
-         return EXIT_FAILURE;
-         }
 
-      if (running_in_test_harness) millisleep(500);
+      if (f.running_in_test_harness) millisleep(500);
       break;
       }
 
@@ -2711,7 +2646,7 @@ for (i = 1; i < argc; i++)
     precedes -MC (see above). The flag indicates that the host to which
     Exim is connected has accepted an AUTH sequence. */
 
-       case 'A': smtp_authenticated = TRUE; break;
+       case 'A': f.smtp_authenticated = TRUE; break;
 
     /* -MCD: set the smtp_use_dsn flag; this indicates that the host
        that exim is connected to supports the esmtp extension DSN */
@@ -2771,9 +2706,19 @@ for (i = 1; i < argc; i++)
 
        default:  badarg = TRUE; break;
        }
-       break;
+      break;
       }
 
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+    /* -MS   set REQUIRETLS on (new) message */
+
+    else if (*argrest == 'S')
+      {
+      tls_requiretls |= REQUIRETLS_MSG;
+      break;
+      }
+#endif
+
     /* -M[x]: various operations on the following list of message ids:
        -M    deliver the messages, ignoring next retry times and thawing
        -Mc   deliver the messages, checking next retry times, no thawing
@@ -2798,7 +2743,7 @@ for (i = 1; i < argc; i++)
     else if (*argrest == 0)
       {
       msg_action = MSG_DELIVER;
-      forced_delivery = deliver_force_thaw = TRUE;
+      forced_delivery = f.deliver_force_thaw = TRUE;
       }
     else if (Ustrcmp(argrest, "ar") == 0)
       {
@@ -2859,10 +2804,7 @@ for (i = 1; i < argc; i++)
 
     msg_action_arg = i + 1;
     if (msg_action_arg >= argc)
-      {
-      fprintf(stderr, "exim: no message ids given after %s option\n", arg);
-      return EXIT_FAILURE;
-      }
+      exim_fail("exim: no message ids given after %s option\n", arg);
 
     /* Some require only message ids to follow */
 
@@ -2870,11 +2812,8 @@ for (i = 1; i < argc; i++)
       {
       int j;
       for (j = msg_action_arg; j < argc; j++) if (!mac_ismsgid(argv[j]))
-        {
-        fprintf(stderr, "exim: malformed message id %s after %s option\n",
+        exim_fail("exim: malformed message id %s after %s option\n",
           argv[j], arg);
-        return EXIT_FAILURE;
-        }
       goto END_ARG;   /* Remaining args are ids */
       }
 
@@ -2884,11 +2823,8 @@ for (i = 1; i < argc; i++)
     else
       {
       if (!mac_ismsgid(argv[msg_action_arg]))
-        {
-        fprintf(stderr, "exim: malformed message id %s after %s option\n",
+        exim_fail("exim: malformed message id %s after %s option\n",
           argv[msg_action_arg], arg);
-        return EXIT_FAILURE;
-        }
       i++;
       }
     break;
@@ -2908,7 +2844,7 @@ for (i = 1; i < argc; i++)
     case 'N':
     if (*argrest == 0)
       {
-      dont_deliver = TRUE;
+      f.dont_deliver = TRUE;
       debug_selector |= D_v;
       debug_file = stderr;
       }
@@ -2932,10 +2868,7 @@ for (i = 1; i < argc; i++)
     if (*argrest == 0)
       {
       if (++i >= argc)
-        {
-        fprintf(stderr, "exim: string expected after -O\n");
-        exit(EXIT_FAILURE);
-        }
+        exim_fail("exim: string expected after -O\n");
       }
     break;
 
@@ -2950,10 +2883,7 @@ for (i = 1; i < argc; i++)
       if (alias_arg[0] == 0)
         {
         if (i+1 < argc) alias_arg = argv[++i]; else
-          {
-          fprintf(stderr, "exim: string expected after -oA\n");
-          exit(EXIT_FAILURE);
-          }
+          exim_fail("exim: string expected after -oA\n");
         }
       }
 
@@ -2974,10 +2904,7 @@ for (i = 1; i < argc; i++)
       if (p != NULL)
         {
         if (!isdigit(*p))
-          {
-          fprintf(stderr, "exim: number expected after -oB\n");
-          exit(EXIT_FAILURE);
-          }
+          exim_fail("exim: number expected after -oB\n");
         connection_max_messages = Uatoi(p);
         }
       }
@@ -2986,7 +2913,7 @@ for (i = 1; i < argc; i++)
 
     else if (Ustrcmp(argrest, "db") == 0)
       {
-      synchronous_delivery = FALSE;
+      f.synchronous_delivery = FALSE;
       arg_queue_only = FALSE;
       queue_only_set = TRUE;
       }
@@ -2997,7 +2924,7 @@ for (i = 1; i < argc; i++)
 
     else if (Ustrcmp(argrest, "df") == 0 || Ustrcmp(argrest, "di") == 0)
       {
-      synchronous_delivery = TRUE;
+      f.synchronous_delivery = TRUE;
       arg_queue_only = FALSE;
       queue_only_set = TRUE;
       }
@@ -3006,7 +2933,7 @@ for (i = 1; i < argc; i++)
 
     else if (Ustrcmp(argrest, "dq") == 0)
       {
-      synchronous_delivery = FALSE;
+      f.synchronous_delivery = FALSE;
       arg_queue_only = TRUE;
       queue_only_set = TRUE;
       }
@@ -3016,7 +2943,7 @@ for (i = 1; i < argc; i++)
 
     else if (Ustrcmp(argrest, "dqs") == 0)
       {
-      queue_smtp = TRUE;
+      f.queue_smtp = TRUE;
       arg_queue_only = FALSE;
       queue_only_set = TRUE;
       }
@@ -3030,7 +2957,7 @@ for (i = 1; i < argc; i++)
 
     else if (Ustrcmp(argrest, "i") == 0 ||
              Ustrcmp(argrest, "itrue") == 0)
-      dot_ends = FALSE;
+      f.dot_ends = FALSE;
 
     /* -oM*: Set various characteristics for an incoming message; actually
     acted on for trusted callers only. */
@@ -3038,10 +2965,7 @@ for (i = 1; i < argc; i++)
     else if (*argrest == 'M')
       {
       if (i+1 >= argc)
-        {
-        fprintf(stderr, "exim: data expected after -o%s\n", argrest);
-        exit(EXIT_FAILURE);
-        }
+        exim_fail("exim: data expected after -o%s\n", argrest);
 
       /* -oMa: Set sender host address */
 
@@ -3069,15 +2993,9 @@ for (i = 1; i < argc; i++)
       else if (Ustrcmp(argrest, "Mm") == 0)
         {
         if (!mac_ismsgid(argv[i+1]))
-          {
-            fprintf(stderr,"-oMm must be a valid message ID\n");
-            exit(EXIT_FAILURE);
-          }
-        if (!trusted_config)
-          {
-            fprintf(stderr,"-oMm must be called by a trusted user/config\n");
-            exit(EXIT_FAILURE);
-          }
+            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];
         }
 
@@ -3086,11 +3004,9 @@ for (i = 1; i < argc; i++)
       else if (Ustrcmp(argrest, "Mr") == 0)
 
         if (received_protocol)
-          {
-          fprintf(stderr, "received_protocol is set already\n");
-          exit(EXIT_FAILURE);
-          }
-        else received_protocol = argv[++i];
+          exim_fail("received_protocol is set already\n");
+        else
+         received_protocol = argv[++i];
 
       /* -oMs: Set sender host name */
 
@@ -3142,10 +3058,7 @@ for (i = 1; i < argc; i++)
         }
       else *tp = readconf_readtime(argrest + 1, 0, FALSE);
       if (*tp < 0)
-        {
-        fprintf(stderr, "exim: bad time value %s: abandoned\n", argv[i]);
-        exit(EXIT_FAILURE);
-        }
+        exim_fail("exim: bad time value %s: abandoned\n", argv[i]);
       }
 
     /* -oX <list>: Override local_interfaces and/or default daemon ports */
@@ -3189,10 +3102,7 @@ for (i = 1; i < argc; i++)
       uschar *hn;
 
       if (received_protocol)
-        {
-        fprintf(stderr, "received_protocol is set already\n");
-        exit(EXIT_FAILURE);
-        }
+        exim_fail("received_protocol is set already\n");
 
       hn = Ustrchr(argrest, ':');
       if (hn == NULL)
@@ -3212,16 +3122,13 @@ for (i = 1; i < argc; i++)
     case 'q':
     receiving_message = FALSE;
     if (queue_interval >= 0)
-      {
-      fprintf(stderr, "exim: -q specified more than once\n");
-      exit(EXIT_FAILURE);
-      }
+      exim_fail("exim: -q specified more than once\n");
 
     /* -qq...: Do queue runs in a 2-stage manner */
 
     if (*argrest == 'q')
       {
-      queue_2stage = TRUE;
+      f.queue_2stage = TRUE;
       argrest++;
       }
 
@@ -3229,7 +3136,7 @@ for (i = 1; i < argc; i++)
 
     if (*argrest == 'i')
       {
-      queue_run_first_delivery = TRUE;
+      f.queue_run_first_delivery = TRUE;
       argrest++;
       }
 
@@ -3238,10 +3145,10 @@ for (i = 1; i < argc; i++)
 
     if (*argrest == 'f')
       {
-      queue_run_force = TRUE;
+      f.queue_run_force = TRUE;
       if (*++argrest == 'f')
         {
-        deliver_force_thaw = TRUE;
+        f.deliver_force_thaw = TRUE;
         argrest++;
         }
       }
@@ -3250,7 +3157,7 @@ for (i = 1; i < argc; i++)
 
     if (*argrest == 'l')
       {
-      queue_run_local = TRUE;
+      f.queue_run_local = TRUE;
       argrest++;
       }
 
@@ -3283,10 +3190,7 @@ for (i = 1; i < argc; i++)
 
     else if ((queue_interval = readconf_readtime(*argrest ? argrest : argv[++i],
                                                0, FALSE)) <= 0)
-      {
-      fprintf(stderr, "exim: bad time value %s: abandoned\n", argv[i]);
-      exit(EXIT_FAILURE);
-      }
+      exim_fail("exim: bad time value %s: abandoned\n", argv[i]);
     break;
 
 
@@ -3308,9 +3212,9 @@ for (i = 1; i < argc; i++)
       for (i = 0; i < nelem(rsopts); i++)
         if (Ustrcmp(argrest, rsopts[i]) == 0)
           {
-          if (i != 2) queue_run_force = TRUE;
-          if (i >= 2) deliver_selectstring_regex = TRUE;
-          if (i == 1 || i == 4) deliver_force_thaw = TRUE;
+          if (i != 2) f.queue_run_force = TRUE;
+          if (i >= 2) f.deliver_selectstring_regex = TRUE;
+          if (i == 1 || i == 4) f.deliver_force_thaw = TRUE;
           argrest += Ustrlen(rsopts[i]);
           }
       }
@@ -3323,10 +3227,7 @@ for (i = 1; i < argc; i++)
     else if (i+1 < argc)
       deliver_selectstring = argv[++i];
     else
-      {
-      fprintf(stderr, "exim: string expected after -R\n");
-      exit(EXIT_FAILURE);
-      }
+      exim_fail("exim: string expected after -R\n");
     break;
 
 
@@ -3353,9 +3254,9 @@ for (i = 1; i < argc; i++)
       for (i = 0; i < nelem(rsopts); i++)
         if (Ustrcmp(argrest, rsopts[i]) == 0)
           {
-          if (i != 2) queue_run_force = TRUE;
-          if (i >= 2) deliver_selectstring_sender_regex = TRUE;
-          if (i == 1 || i == 4) deliver_force_thaw = TRUE;
+          if (i != 2) f.queue_run_force = TRUE;
+          if (i >= 2) f.deliver_selectstring_sender_regex = TRUE;
+          if (i == 1 || i == 4) f.deliver_force_thaw = TRUE;
           argrest += Ustrlen(rsopts[i]);
           }
       }
@@ -3368,10 +3269,7 @@ for (i = 1; i < argc; i++)
     else if (i+1 < argc)
       deliver_selectstring_sender = argv[++i];
     else
-      {
-      fprintf(stderr, "exim: string expected after -S\n");
-      exit(EXIT_FAILURE);
-      }
+      exim_fail("exim: string expected after -S\n");
     break;
 
     /* -Tqt is an option that is exclusively for use by the testing suite.
@@ -3380,7 +3278,7 @@ for (i = 1; i < argc; i++)
     tested. Otherwise variability of clock ticks etc. cause problems. */
 
     case 'T':
-    if (running_in_test_harness && Ustrcmp(argrest, "qt") == 0)
+    if (f.running_in_test_harness && Ustrcmp(argrest, "qt") == 0)
       fudged_queue_times = argv[++i];
     else badarg = TRUE;
     break;
@@ -3397,7 +3295,7 @@ for (i = 1; i < argc; i++)
     else if (Ustrcmp(argrest, "i") == 0)
       {
       extract_recipients = TRUE;
-      dot_ends = FALSE;
+      f.dot_ends = FALSE;
       }
 
     /* -tls-on-connect: don't wait for STARTTLS (for old clients) */
@@ -3450,19 +3348,15 @@ for (i = 1; i < argc; i++)
     case 'X':
     if (*argrest == '\0')
       if (++i >= argc)
-        {
-        fprintf(stderr, "exim: string expected after -X\n");
-        exit(EXIT_FAILURE);
-        }
+        exim_fail("exim: string expected after -X\n");
     break;
 
     case 'z':
     if (*argrest == '\0')
-      if (++i < argc) log_oneline = argv[i]; else
-        {
-        fprintf(stderr, "exim: file name expected after %s\n", argv[i-1]);
-        exit(EXIT_FAILURE);
-        }
+      if (++i < argc)
+       log_oneline = argv[i];
+      else
+        exim_fail("exim: file name expected after %s\n", argv[i-1]);
     break;
 
     /* All other initial characters are errors */
@@ -3475,11 +3369,8 @@ for (i = 1; i < argc; i++)
   /* Failed to recognize the option, or syntax error */
 
   if (badarg)
-    {
-    fprintf(stderr, "exim abandoned: unknown, malformed, or incomplete "
+    exim_fail("exim abandoned: unknown, malformed, or incomplete "
       "option %s\n", arg);
-    exit(EXIT_FAILURE);
-    }
   }
 
 
@@ -3497,26 +3388,26 @@ if (usage_wanted) exim_usage(called_as);
 /* Arguments have been processed. Check for incompatibilities. */
 if ((
     (smtp_input || extract_recipients || recipients_arg < argc) &&
-    (daemon_listen || queue_interval >= 0 || bi_option ||
+    (f.daemon_listen || queue_interval >= 0 || bi_option ||
       test_retry_arg >= 0 || test_rewrite_arg >= 0 ||
       filter_test != FTEST_NONE || (msg_action_arg > 0 && !one_msg_action))
     ) ||
     (
     msg_action_arg > 0 &&
-    (daemon_listen || queue_interval > 0 || list_options ||
+    (f.daemon_listen || queue_interval > 0 || list_options ||
       (checking && msg_action != MSG_LOAD) ||
       bi_option || test_retry_arg >= 0 || test_rewrite_arg >= 0)
     ) ||
     (
-    (daemon_listen || queue_interval > 0) &&
+    (f.daemon_listen || queue_interval > 0) &&
     (sender_address != NULL || list_options || list_queue || checking ||
       bi_option)
     ) ||
     (
-    daemon_listen && queue_interval == 0
+    f.daemon_listen && queue_interval == 0
     ) ||
     (
-    inetd_wait_mode && queue_interval >= 0
+    f.inetd_wait_mode && queue_interval >= 0
     ) ||
     (
     list_options &&
@@ -3525,11 +3416,11 @@ if ((
     ) ||
     (
     verify_address_mode &&
-    (address_test_mode || smtp_input || extract_recipients ||
+    (f.address_test_mode || smtp_input || extract_recipients ||
       filter_test != FTEST_NONE || bi_option)
     ) ||
     (
-    address_test_mode && (smtp_input || extract_recipients ||
+    f.address_test_mode && (smtp_input || extract_recipients ||
       filter_test != FTEST_NONE || bi_option)
     ) ||
     (
@@ -3544,10 +3435,7 @@ if ((
       (!expansion_test || expansion_test_message != NULL)
     )
    )
-  {
-  fprintf(stderr, "exim: incompatible command-line options or arguments\n");
-  exit(EXIT_FAILURE);
-  }
+  exim_fail("exim: incompatible command-line options or arguments\n");
 
 /* If debugging is set up, set the file and the file descriptor to pass on to
 child processes. It should, of course, be 2 for stderr. Also, force the daemon
@@ -3557,8 +3445,8 @@ if (debug_selector != 0)
   {
   debug_file = stderr;
   debug_fd = fileno(debug_file);
-  background_daemon = FALSE;
-  if (running_in_test_harness) millisleep(100);   /* lets caller finish */
+  f.background_daemon = FALSE;
+  if (f.running_in_test_harness) millisleep(100);   /* lets caller finish */
   if (debug_selector != D_v)    /* -v only doesn't show this */
     {
     debug_printf("Exim version %s uid=%ld gid=%ld pid=%d D=%x\n",
@@ -3644,12 +3532,8 @@ check on the additional groups for the admin user privilege - can't do that
 till after reading the config, which might specify the exim gid. Therefore,
 save the group list here first. */
 
-group_count = getgroups(NGROUPS_MAX, group_list);
-if (group_count < 0)
-  {
-  fprintf(stderr, "exim: getgroups() failed: %s\n", strerror(errno));
-  exit(EXIT_FAILURE);
-  }
+if ((group_count = getgroups(NGROUPS_MAX, group_list)) < 0)
+  exim_fail("exim: getgroups() failed: %s\n", strerror(errno));
 
 /* There is a fundamental difference in some BSD systems in the matter of
 groups. FreeBSD and BSDI are known to be different; NetBSD and OpenBSD are
@@ -3667,14 +3551,8 @@ 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. */
 
-if (setgroups(0, NULL) != 0)
-  {
-  if (setgroups(1, group_list) != 0 && !unprivileged)
-    {
-    fprintf(stderr, "exim: setgroups() failed: %s\n", strerror(errno));
-    exit(EXIT_FAILURE);
-    }
-  }
+if (setgroups(0, NULL) != 0 && setgroups(1, group_list) != 0 && !unprivileged)
+  exim_fail("exim: setgroups() failed: %s\n", strerror(errno));
 
 /* If the configuration file name has been altered by an argument on the
 command line (either a new file name or a macro definition) and the caller is
@@ -3694,10 +3572,10 @@ values (such as the path name). If running in the test harness, pretend that
 configuration file changes and macro definitions haven't happened. */
 
 if ((                                            /* EITHER */
-    (!trusted_config ||                          /* Config changed, or */
+    (!f.trusted_config ||                          /* Config changed, or */
      !macros_trusted(opt_D_used)) &&            /*  impermissible macros and */
     real_uid != root_uid &&                      /* Not root, and */
-    !running_in_test_harness                     /* Not fudged */
+    !f.running_in_test_harness                     /* Not fudged */
     ) ||                                         /*   OR   */
     expansion_test                               /* expansion testing */
     ||                                           /*   OR   */
@@ -3717,8 +3595,8 @@ if ((                                            /* EITHER */
   Note that if the invoker is Exim, the logs remain available. Messing with
   this causes unlogged successful deliveries.  */
 
-  if ((log_stderr != NULL) && (real_uid != exim_uid))
-    really_exim = FALSE;
+  if (log_stderr && real_uid != exim_uid)
+    f.really_exim = FALSE;
   }
 
 /* Privilege is to be retained for the moment. It may be dropped later,
@@ -3726,32 +3604,21 @@ depending on the job that this Exim process has been asked to do. For now, set
 the real uid to the effective so that subsequent re-execs of Exim are done by a
 privileged user. */
 
-else exim_setugid(geteuid(), getegid(), FALSE, US"forcing real = effective");
+else
+  exim_setugid(geteuid(), getegid(), FALSE, US"forcing real = effective");
 
 /* If testing a filter, open the file(s) now, before wasting time doing other
 setups and reading the message. */
 
-if ((filter_test & FTEST_SYSTEM) != 0)
-  {
-  filter_sfd = Uopen(filter_test_sfile, O_RDONLY, 0);
-  if (filter_sfd < 0)
-    {
-    fprintf(stderr, "exim: failed to open %s: %s\n", filter_test_sfile,
+if (filter_test & FTEST_SYSTEM)
+  if ((filter_sfd = Uopen(filter_test_sfile, O_RDONLY, 0)) < 0)
+    exim_fail("exim: failed to open %s: %s\n", filter_test_sfile,
       strerror(errno));
-    return EXIT_FAILURE;
-    }
-  }
 
-if ((filter_test & FTEST_USER) != 0)
-  {
-  filter_ufd = Uopen(filter_test_ufile, O_RDONLY, 0);
-  if (filter_ufd < 0)
-    {
-    fprintf(stderr, "exim: failed to open %s: %s\n", filter_test_ufile,
+if (filter_test & FTEST_USER)
+  if ((filter_ufd = Uopen(filter_test_ufile, O_RDONLY, 0)) < 0)
+    exim_fail("exim: failed to open %s: %s\n", filter_test_ufile,
       strerror(errno));
-    return EXIT_FAILURE;
-    }
-  }
 
 /* Initialise lookup_list
 If debugging, already called above via version reporting.
@@ -3765,7 +3632,7 @@ This needs to happen before we read the main configuration. */
 init_lookup_list();
 
 #ifdef SUPPORT_I18N
-if (running_in_test_harness) smtputf8_advertise_hosts = NULL;
+if (f.running_in_test_harness) smtputf8_advertise_hosts = NULL;
 #endif
 
 /* Read the main runtime configuration data; this gives up if there
@@ -3814,17 +3681,17 @@ since some actions can be performed by non-admin users. Instead, set admin_user
 for later interrogation. */
 
 if (real_uid == root_uid || real_uid == exim_uid || real_gid == exim_gid)
-  admin_user = TRUE;
+  f.admin_user = TRUE;
 else
   {
   int i, j;
-  for (i = 0; i < group_count && !admin_user; i++)
+  for (i = 0; i < group_count && !f.admin_user; i++)
     if (group_list[i] == exim_gid)
-      admin_user = TRUE;
+      f.admin_user = TRUE;
     else if (admin_groups)
-      for (j = 1; j <= (int)admin_groups[0] && !admin_user; j++)
+      for (j = 1; j <= (int)admin_groups[0] && !f.admin_user; j++)
         if (admin_groups[j] == group_list[i])
-          admin_user = TRUE;
+          f.admin_user = TRUE;
   }
 
 /* Another group of privileged users are the trusted users. These are root,
@@ -3833,32 +3700,30 @@ are permitted to specify sender_addresses with -f on the command line, and
 other message parameters as well. */
 
 if (real_uid == root_uid || real_uid == exim_uid)
-  trusted_caller = TRUE;
+  f.trusted_caller = TRUE;
 else
   {
   int i, j;
 
   if (trusted_users)
-    for (i = 1; i <= (int)trusted_users[0] && !trusted_caller; i++)
+    for (i = 1; i <= (int)trusted_users[0] && !f.trusted_caller; i++)
       if (trusted_users[i] == real_uid)
-        trusted_caller = TRUE;
+        f.trusted_caller = TRUE;
 
   if (trusted_groups)
-    for (i = 1; i <= (int)trusted_groups[0] && !trusted_caller; i++)
+    for (i = 1; i <= (int)trusted_groups[0] && !f.trusted_caller; i++)
       if (trusted_groups[i] == real_gid)
-        trusted_caller = TRUE;
-      else for (j = 0; j < group_count && !trusted_caller; j++)
+        f.trusted_caller = TRUE;
+      else for (j = 0; j < group_count && !f.trusted_caller; j++)
         if (trusted_groups[i] == group_list[j])
-          trusted_caller = TRUE;
+          f.trusted_caller = TRUE;
   }
 
 /* At this point, we know if the user is privileged and some command-line
 options become possibly impermissible, depending upon the configuration file. */
 
-if (checking && commandline_checks_require_admin && !admin_user) {
-  fprintf(stderr, "exim: those command-line flags are set to require admin\n");
-  exit(EXIT_FAILURE);
-}
+if (checking && commandline_checks_require_admin && !f.admin_user)
+  exim_fail("exim: those command-line flags are set to require admin\n");
 
 /* Handle the decoding of logging options. */
 
@@ -3878,39 +3743,28 @@ DEBUG(D_any)
 /* If domain literals are not allowed, check the sender address that was
 supplied with -f. Ditto for a stripped trailing dot. */
 
-if (sender_address != NULL)
+if (sender_address)
   {
   if (sender_address[sender_address_domain] == '[' && !allow_domain_literals)
-    {
-    fprintf(stderr, "exim: bad -f address \"%s\": domain literals not "
+    exim_fail("exim: bad -f address \"%s\": domain literals not "
       "allowed\n", sender_address);
-    return EXIT_FAILURE;
-    }
   if (f_end_dot && !strip_trailing_dot)
-    {
-    fprintf(stderr, "exim: bad -f address \"%s.\": domain is malformed "
+    exim_fail("exim: bad -f address \"%s.\": domain is malformed "
       "(trailing dot not allowed)\n", sender_address);
-    return EXIT_FAILURE;
-    }
   }
 
 /* See if an admin user overrode our logging. */
 
-if (cmdline_syslog_name != NULL)
-  {
-  if (admin_user)
+if (cmdline_syslog_name)
+  if (f.admin_user)
     {
     syslog_processname = cmdline_syslog_name;
     log_file_path = string_copy(CUS"syslog");
     }
   else
-    {
     /* not a panic, non-privileged users should not be able to spam paniclog */
-    fprintf(stderr,
+    exim_fail(
         "exim: you lack sufficient privilege to specify syslog process name\n");
-    return EXIT_FAILURE;
-    }
-  }
 
 /* Paranoia check of maximum lengths of certain strings. There is a check
 on the length of the log file path in log.c, which will come into effect
@@ -3940,7 +3794,7 @@ if (Ustrlen(syslog_processname) > 32)
     "syslog_processname is longer than 32 chars: aborting");
 
 if (log_oneline)
-  if (admin_user)
+  if (f.admin_user)
     {
     log_write(0, LOG_MAIN, "%s", log_oneline);
     return EXIT_SUCCESS;
@@ -3982,7 +3836,7 @@ this. We have to make a new environment if TZ is wrong, but don't bother if
 timestamps_utc is set, because then all times are in UTC anyway. */
 
 if (timezone_string && strcmpic(timezone_string, US"UTC") == 0)
-  timestamps_utc = TRUE;
+  f.timestamps_utc = TRUE;
 else
   {
   uschar *envtz = US getenv("TZ");
@@ -4035,14 +3889,14 @@ Exim user", but it hasn't, because either the -D option set macros, or the
       trusted configuration file (when deliver_drop_privilege is false). */
 
 if (  removed_privilege
-   && (!trusted_config || opt_D_used)
+   && (!f.trusted_config || opt_D_used)
    && real_uid == exim_uid)
   if (deliver_drop_privilege)
-    really_exim = TRUE; /* let logging work normally */
+    f.really_exim = TRUE; /* let logging work normally */
   else
     log_write(0, LOG_MAIN|LOG_PANIC,
       "exim user lost privilege for using %s option",
-      trusted_config? "-D" : "-C");
+      f.trusted_config? "-D" : "-C");
 
 /* Start up Perl interpreter if Perl support is configured and there is a
 perl_startup option, and the configuration or the command line specifies
@@ -4056,12 +3910,8 @@ if (opt_perl_at_start && opt_perl_startup != NULL)
   {
   uschar *errstr;
   DEBUG(D_any) debug_printf("Starting Perl interpreter\n");
-  errstr = init_perl(opt_perl_startup);
-  if (errstr != NULL)
-    {
-    fprintf(stderr, "exim: error in perl_startup code: %s\n", errstr);
-    return EXIT_FAILURE;
-    }
+  if ((errstr = init_perl(opt_perl_startup)))
+    exim_fail("exim: error in perl_startup code: %s\n", errstr);
   opt_perl_started = TRUE;
   }
 #endif /* EXIM_PERL */
@@ -4071,16 +3921,24 @@ a debugging feature for finding out what arguments certain MUAs actually use.
 Don't attempt it if logging is disabled, or if listing variables or if
 verifying/testing addresses or expansions. */
 
-if (((debug_selector & D_any) != 0 || LOGGING(arguments))
-      && really_exim && !list_options && !checking)
+if (  (debug_selector & D_any  ||  LOGGING(arguments))
+   && f.really_exim && !list_options && !checking)
   {
   int i;
   uschar *p = big_buffer;
   Ustrcpy(p, "cwd= (failed)");
 
-  Ustrncpy(p + 4, initial_cwd, big_buffer_size-5);
+  if (!initial_cwd)
+    p += 13;
+  else
+    {
+    Ustrncpy(p + 4, initial_cwd, big_buffer_size-5);
+    p += 4 + Ustrlen(initial_cwd);
+    /* in case p is near the end and we don't provide enough space for
+     * string_format to be willing to write. */
+    *p = '\0';
+    }
 
-  while (*p) p++;
   (void)string_format(p, big_buffer_size - (p - big_buffer), " %d args:", argc);
   while (*p) p++;
   for (i = 0; i < argc; i++)
@@ -4151,8 +4009,7 @@ if (bi_option)
       (argv[1] == NULL)? US"" : argv[1]);
 
     execv(CS argv[0], (char *const *)argv);
-    fprintf(stderr, "exim: exec failed: %s\n", strerror(errno));
-    exit(EXIT_FAILURE);
+    exim_fail("exim: exec failed: %s\n", strerror(errno));
     }
   else
     {
@@ -4165,8 +4022,8 @@ if (bi_option)
 configuration file.  We leave these prints here to ensure that syslog setup,
 logfile setup, and so on has already happened. */
 
-if (trusted_caller) DEBUG(D_any) debug_printf("trusted user\n");
-if (admin_user) DEBUG(D_any) debug_printf("admin user\n");
+if (f.trusted_caller) DEBUG(D_any) debug_printf("trusted user\n");
+if (f.admin_user) DEBUG(D_any) debug_printf("admin user\n");
 
 /* Only an admin user may start the daemon or force a queue run in the default
 configuration, but the queue run restriction can be relaxed. Only an admin
@@ -4176,18 +4033,15 @@ passwords, etc. in lookup queries). Only an admin user may request a queue
 count. Only an admin user can use the test interface to scan for email
 (because Exim will be in the spool dir and able to look at mails). */
 
-if (!admin_user)
+if (!f.admin_user)
   {
   BOOL debugset = (debug_selector & ~D_v) != 0;
-  if (deliver_give_up || daemon_listen || malware_test_file ||
+  if (deliver_give_up || f.daemon_listen || malware_test_file ||
      (count_queue && queue_list_requires_admin) ||
      (list_queue && queue_list_requires_admin) ||
      (queue_interval >= 0 && prod_requires_admin) ||
-     (debugset && !running_in_test_harness))
-    {
-    fprintf(stderr, "exim:%s permission denied\n", debugset? " debugging" : "");
-    exit(EXIT_FAILURE);
-    }
+     (debugset && !f.running_in_test_harness))
+    exim_fail("exim:%s permission denied\n", debugset? " debugging" : "");
   }
 
 /* If the real user is not root or the exim uid, the argument for passing
@@ -4198,20 +4052,17 @@ regression testing. */
 
 if (real_uid != root_uid && real_uid != exim_uid &&
      (continue_hostname != NULL ||
-       (dont_deliver &&
-         (queue_interval >= 0 || daemon_listen || msg_action_arg > 0)
-       )) && !running_in_test_harness)
-  {
-  fprintf(stderr, "exim: Permission denied\n");
-  return EXIT_FAILURE;
-  }
+       (f.dont_deliver &&
+         (queue_interval >= 0 || f.daemon_listen || msg_action_arg > 0)
+       )) && !f.running_in_test_harness)
+  exim_fail("exim: Permission denied\n");
 
 /* If the caller is not trusted, certain arguments are ignored when running for
 real, but are permitted when checking things (-be, -bv, -bt, -bh, -bf, -bF).
 Note that authority for performing certain actions on messages is tested in the
 queue_action() function. */
 
-if (!trusted_caller && !checking)
+if (!f.trusted_caller && !checking)
   {
   sender_host_name = sender_host_address = interface_address =
     sender_ident = received_protocol = NULL;
@@ -4234,16 +4085,13 @@ else
 /* If the caller is trusted, then they can use -G to suppress_local_fixups. */
 if (flag_G)
   {
-  if (trusted_caller)
+  if (f.trusted_caller)
     {
-    suppress_local_fixups = suppress_local_fixups_default = TRUE;
+    f.suppress_local_fixups = f.suppress_local_fixups_default = TRUE;
     DEBUG(D_acl) debug_printf("suppress_local_fixups forced on by -G\n");
     }
   else
-    {
-    fprintf(stderr, "exim: permission denied (-G requires a trusted user)\n");
-    return EXIT_FAILURE;
-    }
+    exim_fail("exim: permission denied (-G requires a trusted user)\n");
   }
 
 /* If an SMTP message is being received check to see if the standard input is a
@@ -4271,18 +4119,15 @@ if (smtp_input)
 
       if (real_uid == root_uid || real_uid == exim_uid || interface_port < 1024)
         {
-        is_inetd = TRUE;
+        f.is_inetd = TRUE;
         sender_host_address = host_ntoa(-1, (struct sockaddr *)(&inetd_sock),
           NULL, &sender_host_port);
         if (mua_wrapper) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Input from "
           "inetd is not supported when mua_wrapper is set");
         }
       else
-        {
-        fprintf(stderr,
+        exim_fail(
           "exim: Permission denied (unprivileged user, unprivileged port)\n");
-        return EXIT_FAILURE;
-        }
       }
     }
   }
@@ -4294,7 +4139,7 @@ root. There will be further calls later for each message received. */
 #ifdef LOAD_AVG_NEEDS_ROOT
 if (receiving_message &&
       (queue_only_load >= 0 ||
-        (is_inetd && smtp_load_reserve >= 0)
+        (f.is_inetd && smtp_load_reserve >= 0)
       ))
   {
   load_average = OS_GETLOADAVG();
@@ -4326,7 +4171,7 @@ to the state Exim usually runs in. */
 
 if (!unprivileged &&                      /* originally had root AND */
     !removed_privilege &&                 /* still got root AND      */
-    !daemon_listen &&                     /* not starting the daemon */
+    !f.daemon_listen &&                     /* not starting the daemon */
     queue_interval <= 0 &&                /* (either kind of daemon) */
       (                                   /*    AND EITHER           */
       deliver_drop_privilege ||           /* requested unprivileged  */
@@ -4334,7 +4179,7 @@ if (!unprivileged &&                      /* originally had root AND */
         queue_interval < 0 &&             /* not running the queue   */
         (msg_action_arg < 0 ||            /*       and               */
           msg_action != MSG_DELIVER) &&   /* not delivering and      */
-        (!checking || !address_test_mode) /* not address checking    */
+        (!checking || !f.address_test_mode) /* not address checking    */
    )  ) )
   exim_setugid(exim_uid, exim_gid, TRUE, US"privilege not needed");
 
@@ -4351,11 +4196,7 @@ else
   no need to complain then. */
   if (rv == -1)
     if (!(unprivileged || removed_privilege))
-      {
-      fprintf(stderr,
-          "exim: changing group failed: %s\n", strerror(errno));
-      exit(EXIT_FAILURE);
-      }
+      exim_fail("exim: changing group failed: %s\n", strerror(errno));
     else
       DEBUG(D_any) debug_printf("changing group to %ld failed: %s\n",
           (long int)exim_gid, strerror(errno));
@@ -4416,6 +4257,12 @@ if (msg_action_arg > 0 && msg_action != MSG_DELIVER && msg_action != MSG_LOAD)
   int yield = EXIT_SUCCESS;
   set_process_info("acting on specified messages");
 
+  /* ACL definitions may be needed when removing a message (-Mrm) because
+  event_action gets expanded */
+
+  if (msg_action == MSG_REMOVE)
+    readconf_rest();
+
   if (!one_msg_action)
     {
     for (i = msg_action_arg; i < argc; i++)
@@ -4429,7 +4276,7 @@ if (msg_action_arg > 0 && msg_action != MSG_DELIVER && msg_action != MSG_LOAD)
   }
 
 /* We used to set up here to skip reading the ACL section, on
- (msg_action_arg > 0 || (queue_interval == 0 && !daemon_listen)
+ (msg_action_arg > 0 || (queue_interval == 0 && !f.daemon_listen)
 Now, since the intro of the ${acl } expansion, ACL definitions may be
 needed in transports so we lost the optimisation. */
 
@@ -4625,13 +4472,13 @@ message. */
 
 if (msg_action_arg > 0 && msg_action != MSG_LOAD)
   {
-  if (prod_requires_admin && !admin_user)
+  if (prod_requires_admin && !f.admin_user)
     {
     fprintf(stderr, "exim: Permission denied\n");
     exim_exit(EXIT_FAILURE, US"main");
     }
   set_process_info("delivering specified messages");
-  if (deliver_give_up) forced_delivery = deliver_force_thaw = TRUE;
+  if (deliver_give_up) forced_delivery = f.deliver_force_thaw = TRUE;
   for (i = msg_action_arg; i < argc; i++)
     {
     int status;
@@ -4658,7 +4505,7 @@ if (msg_action_arg > 0 && msg_action != MSG_LOAD)
 /* If only a single queue run is requested, without SMTP listening, we can just
 turn into a queue runner, with an optional starting message id. */
 
-if (queue_interval == 0 && !daemon_listen)
+if (queue_interval == 0 && !f.daemon_listen)
   {
   DEBUG(D_queue_run) debug_printf("Single queue run%s%s%s%s\n",
     (start_queue_run_id == NULL)? US"" : US" starting at ",
@@ -4695,7 +4542,7 @@ for (i = 0;;)
 
     if (!originator_name)
       {
-      if (!sender_address || (!trusted_caller && filter_test == FTEST_NONE))
+      if (!sender_address || (!f.trusted_caller && filter_test == FTEST_NONE))
         {
         uschar *name = US pw->pw_gecos;
         uschar *amp = Ustrchr(name, '&');
@@ -4760,7 +4607,7 @@ for (i = 0;;)
 configuration specifies something to use. When running in the test harness,
 any setting of unknown_login overrides the actual name. */
 
-if (originator_login == NULL || running_in_test_harness)
+if (originator_login == NULL || f.running_in_test_harness)
   {
   if (unknown_login != NULL)
     {
@@ -4795,7 +4642,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 || inetd_wait_mode || queue_interval > 0)
+if (f.daemon_listen || f.inetd_wait_mode || queue_interval > 0)
   {
   if (mua_wrapper)
     {
@@ -4819,7 +4666,7 @@ originator_* variables set. */
 
 if (test_rewrite_arg >= 0)
   {
-  really_exim = FALSE;
+  f.really_exim = FALSE;
   if (test_rewrite_arg >= argc)
     {
     printf("-brw needs an address argument\n");
@@ -4834,9 +4681,9 @@ unless a trusted caller supplies a sender address with -f, or is passing in the
 message via SMTP (inetd invocation or otherwise). */
 
 if ((sender_address == NULL && !smtp_input) ||
-    (!trusted_caller && filter_test == FTEST_NONE))
+    (!f.trusted_caller && filter_test == FTEST_NONE))
   {
-  sender_local = TRUE;
+  f.sender_local = TRUE;
 
   /* A trusted caller can supply authenticated_sender and authenticated_id
   via -oMas and -oMai and if so, they will already be set. Otherwise, force
@@ -4869,14 +4716,14 @@ if ((!smtp_input && sender_address == NULL) ||
        !checking))                       /* Not running tests, including filter tests */
     {
     sender_address = originator_login;
-    sender_address_forced = FALSE;
+    f.sender_address_forced = FALSE;
     sender_address_domain = 0;
     }
   }
 
 /* Remember whether an untrusted caller set the sender address */
 
-sender_set_untrusted = sender_address != originator_login && !trusted_caller;
+f.sender_set_untrusted = sender_address != originator_login && !f.trusted_caller;
 
 /* Ensure that the sender address is fully qualified unless it is the empty
 address, which indicates an error message, or doesn't exist (root caller, smtp
@@ -4895,7 +4742,7 @@ predicated upon the sender. If no arguments are given, read addresses from
 stdin. Set debug_level to at least D_v to get full output for address testing.
 */
 
-if (verify_address_mode || address_test_mode)
+if (verify_address_mode || f.address_test_mode)
   {
   int exit_value = 0;
   int flags = vopt_qualify;
@@ -4955,11 +4802,8 @@ if (expansion_test)
   if (msg_action_arg > 0 && msg_action == MSG_LOAD)
     {
     uschar spoolname[256];  /* Not big_buffer; used in spool_read_header() */
-    if (!admin_user)
-      {
-      fprintf(stderr, "exim: permission denied\n");
-      exit(EXIT_FAILURE);
-      }
+    if (!f.admin_user)
+      exim_fail("exim: permission denied\n");
     message_id = argv[msg_action_arg];
     (void)string_format(spoolname, sizeof(spoolname), "%s-H", message_id);
     if ((deliver_datafile = spool_open_datafile(message_id)) < 0)
@@ -4976,11 +4820,8 @@ if (expansion_test)
     int save_stdin = dup(0);
     int fd = Uopen(expansion_test_message, O_RDONLY, 0);
     if (fd < 0)
-      {
-      fprintf(stderr, "exim: failed to open %s: %s\n", expansion_test_message,
+      exim_fail("exim: failed to open %s: %s\n", expansion_test_message,
         strerror(errno));
-      return EXIT_FAILURE;
-      }
     (void) dup2(fd, 0);
     filter_test = FTEST_USER;      /* Fudge to make it look like filter test */
     message_ended = END_NOTENDED;
@@ -4993,11 +4834,11 @@ if (expansion_test)
 
   /* Only admin users may see config-file macros this way */
 
-  if (!admin_user) tree_macros = NULL;
+  if (!f.admin_user) macros_user = macros = mlast = NULL;
 
   /* Allow $recipients for this testing */
 
-  enable_dollar_recipients = TRUE;
+  f.enable_dollar_recipients = TRUE;
 
   /* Expand command line items */
 
@@ -5047,7 +4888,7 @@ if (raw_active_hostname != NULL)
   uschar *nah = expand_string(raw_active_hostname);
   if (nah == NULL)
     {
-    if (!expand_string_forcedfail)
+    if (!f.expand_string_forcedfail)
       log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to expand \"%s\" "
         "(smtp_active_hostname): %s", raw_active_hostname,
         expand_string_message);
@@ -5070,7 +4911,7 @@ if (host_checking)
   if (!sender_ident_set)
     {
     sender_ident = NULL;
-    if (running_in_test_harness && sender_host_port != 0 &&
+    if (f.running_in_test_harness && sender_host_port != 0 &&
         interface_address != NULL && interface_port != 0)
       verify_get_ident(1413);
     }
@@ -5088,8 +4929,8 @@ if (host_checking)
   smtp_input = TRUE;
   smtp_in = stdin;
   smtp_out = stdout;
-  sender_local = FALSE;
-  sender_host_notsocket = TRUE;
+  f.sender_local = FALSE;
+  f.sender_host_notsocket = TRUE;
   debug_file = stderr;
   debug_fd = fileno(debug_file);
   fprintf(stdout, "\n**** SMTP testing session as if from host %s\n"
@@ -5140,6 +4981,8 @@ if (recipients_arg >= argc && !extract_recipients && !smtp_input)
   {
   if (version_printed)
     {
+    if (Ustrchr(config_main_filelist, ':'))
+      printf("Configuration file search path is %s\n", config_main_filelist);
     printf("Configuration file is %s\n", config_main_filename);
     return EXIT_SUCCESS;
     }
@@ -5171,11 +5014,11 @@ to override any SMTP queueing. */
 
 if (mua_wrapper)
   {
-  synchronous_delivery = TRUE;
+  f.synchronous_delivery = TRUE;
   arg_error_handling = ERRORS_STDERR;
   remote_max_parallel = 1;
   deliver_drop_privilege = TRUE;
-  queue_smtp = FALSE;
+  f.queue_smtp = FALSE;
   queue_smtp_domains = NULL;
 #ifdef SUPPORT_I18N
   message_utf8_downconvert = -1;       /* convert-if-needed */
@@ -5198,7 +5041,7 @@ if (!smtp_input) error_handling = arg_error_handling;
 logging being sent down the socket and make an identd call to get the
 sender_ident. */
 
-else if (is_inetd)
+else if (f.is_inetd)
   {
   (void)fclose(stderr);
   exim_nullstd();                       /* Re-open to /dev/null */
@@ -5213,18 +5056,18 @@ already been done (which it will have been for inetd). This caters for the
 case when it is forced by -oMa. However, we must flag that it isn't a socket,
 so that the test for IP options is skipped for -bs input. */
 
-if (sender_host_address != NULL && sender_fullhost == NULL)
+if (sender_host_address && !sender_fullhost)
   {
   host_build_sender_fullhost();
   set_process_info("handling incoming connection from %s via -oMa",
     sender_fullhost);
-  sender_host_notsocket = TRUE;
+  f.sender_host_notsocket = TRUE;
   }
 
 /* Otherwise, set the sender host as unknown except for inetd calls. This
 prevents host checking in the case of -bs not from inetd and also for -bS. */
 
-else if (!is_inetd) sender_host_unknown = TRUE;
+else if (!f.is_inetd) f.sender_host_unknown = TRUE;
 
 /* If stdout does not exist, then dup stdin to stdout. This can happen
 if exim is started from inetd. In this case fd 0 will be set to the socket,
@@ -5240,7 +5083,7 @@ batch/HELO/EHLO/AUTH/TLS. */
 
 if (smtp_input)
   {
-  if (!is_inetd) set_process_info("accepting a local %sSMTP message from <%s>",
+  if (!f.is_inetd) set_process_info("accepting a local %sSMTP message from <%s>",
     smtp_batched_input? "batched " : "",
     (sender_address!= NULL)? sender_address : originator_login);
   }
@@ -5267,10 +5110,7 @@ message! (For interactive SMTP, the check happens at MAIL FROM and an SMTP
 error code is given.) */
 
 if ((!smtp_input || smtp_batched_input) && !receive_check_fs(0))
-  {
-  fprintf(stderr, "exim: insufficient disk space\n");
-  return EXIT_FAILURE;
-  }
+  exim_fail("exim: insufficient disk space\n");
 
 /* If this is smtp input of any kind, real or batched, handle the start of the
 SMTP session.
@@ -5337,7 +5177,7 @@ this is logically inconsistent. In other words, it doesn't like the paranoia.
 As a consequence of this, the waitpid() below is now excluded if we are sure
 that SIG_IGN works. */
 
-if (!synchronous_delivery)
+if (!f.synchronous_delivery)
   {
   #ifdef SA_NOCLDWAIT
   struct sigaction act;
@@ -5392,10 +5232,10 @@ while (more)
       if (smtp_batched_input && acl_not_smtp_start != NULL)
         {
         uschar *user_msg, *log_msg;
-        enable_dollar_recipients = TRUE;
+        f.enable_dollar_recipients = TRUE;
         (void)acl_check(ACL_WHERE_NOTSMTP_START, NULL, acl_not_smtp_start,
           &user_msg, &log_msg);
-        enable_dollar_recipients = FALSE;
+        f.enable_dollar_recipients = FALSE;
         }
 
       /* Now get the data for the message */
@@ -5432,8 +5272,8 @@ while (more)
 
     /* These options cannot be changed dynamically for non-SMTP messages */
 
-    active_local_sender_retain = local_sender_retain;
-    active_local_from_check = local_from_check;
+    f.active_local_sender_retain = local_sender_retain;
+    f.active_local_from_check = local_from_check;
 
     /* Save before any rewriting */
 
@@ -5486,7 +5326,7 @@ while (more)
          allow_utf8_domains = b;
        }
 #endif
-        if (domain == 0 && !allow_unqualified_recipient)
+        if (domain == 0 && !f.allow_unqualified_recipient)
           {
           recipient = NULL;
           errmess = US"unqualified recipient address not allowed";
@@ -5540,10 +5380,10 @@ while (more)
     if (acl_not_smtp_start)
       {
       uschar *user_msg, *log_msg;
-      enable_dollar_recipients = TRUE;
+      f.enable_dollar_recipients = TRUE;
       (void)acl_check(ACL_WHERE_NOTSMTP_START, NULL, acl_not_smtp_start,
         &user_msg, &log_msg);
-      enable_dollar_recipients = FALSE;
+      f.enable_dollar_recipients = FALSE;
       }
 
     /* Pause for a while waiting for input.  If none received in that time,
@@ -5675,7 +5515,7 @@ while (more)
   are ignored. */
 
   if (mua_wrapper)
-    local_queue_only = queue_only_policy = deliver_freeze = FALSE;
+    local_queue_only = f.queue_only_policy = f.deliver_freeze = FALSE;
 
   /* Log the queueing here, when it will get a message id attached, but
   not if queue_only is set (case 0). Case 1 doesn't happen here (too many
@@ -5700,7 +5540,7 @@ while (more)
       }
     }
 
-  else if (queue_only_policy || deliver_freeze)
+  else if (f.queue_only_policy || f.deliver_freeze)
     cancel_cutthrough_connection(TRUE, US"no delivery; queueing");
 
   /* Else do the delivery unless the ACL or local_scan() called for queue only
@@ -5752,7 +5592,7 @@ while (more)
       /* In the parent, wait if synchronous delivery is required. This will
       always be the case in MUA wrapper mode. */
 
-      if (synchronous_delivery)
+      if (f.synchronous_delivery)
        {
        int status;
        while (wait(&status) != pid);