Extra paranoia around STARTTLS-with-data-in-buffer.
[users/jgh/exim.git] / src / src / exim.c
index 49830c0de94c82292a11ec99a4987d50531ea62f..528ffc7c84b65309fb6096aa7becd981306d1739 100644 (file)
@@ -1,10 +1,10 @@
-/* $Cambridge: exim/src/src/exim.c,v 1.55 2007/01/30 15:10:59 ph10 Exp $ */
+/* $Cambridge: exim/src/src/exim.c,v 1.71 2010/06/07 00:12:42 pdp Exp $ */
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2007 */
+/* Copyright (c) University of Cambridge 1995 - 2009 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -14,6 +14,8 @@ Also a few functions that don't naturally fit elsewhere. */
 
 #include "exim.h"
 
 
 #include "exim.h"
 
+extern void init_lookup_list(void);
+
 
 
 /*************************************************
 
 
 /*************************************************
@@ -358,7 +360,7 @@ Returns:   nothing
 */
 
 void
 */
 
 void
-set_process_info(char *format, ...)
+set_process_info(const char *format, ...)
 {
 int len;
 va_list ap;
 {
 int len;
 va_list ap;
@@ -395,7 +397,7 @@ Returns:          the fopened FILE or NULL
 */
 
 FILE *
 */
 
 FILE *
-modefopen(uschar *filename, char *options, mode_t mode)
+modefopen(const uschar *filename, const char *options, mode_t mode)
 {
 mode_t saved_umask = umask(0777);
 FILE *f = Ufopen(filename, options);
 {
 mode_t saved_umask = umask(0777);
 FILE *f = Ufopen(filename, options);
@@ -568,17 +570,20 @@ if (euid == root_uid || euid != uid || egid != gid || igflag)
 
 DEBUG(D_uid)
   {
 
 DEBUG(D_uid)
   {
-  int group_count;
+  int group_count, save_errno;
   gid_t group_list[NGROUPS_MAX];
   debug_printf("changed uid/gid: %s\n  uid=%ld gid=%ld pid=%ld\n", msg,
     (long int)geteuid(), (long int)getegid(), (long int)getpid());
   group_count = getgroups(NGROUPS_MAX, group_list);
   gid_t group_list[NGROUPS_MAX];
   debug_printf("changed uid/gid: %s\n  uid=%ld gid=%ld pid=%ld\n", msg,
     (long int)geteuid(), (long int)getegid(), (long int)getpid());
   group_count = getgroups(NGROUPS_MAX, group_list);
+  save_errno = errno;
   debug_printf("  auxiliary group list:");
   if (group_count > 0)
     {
     int i;
     for (i = 0; i < group_count; i++) debug_printf(" %d", (int)group_list[i]);
     }
   debug_printf("  auxiliary group list:");
   if (group_count > 0)
     {
     int i;
     for (i = 0; i < group_count; i++) debug_printf(" %d", (int)group_list[i]);
     }
+  else if (group_count < 0)
+    debug_printf(" <error: %s>", strerror(save_errno));
   else debug_printf(" <none>");
   debug_printf("\n");
   }
   else debug_printf(" <none>");
   debug_printf("\n");
   }
@@ -681,161 +686,12 @@ else
 
 
 
 
 
 
-/*************************************************
-*         Decode bit settings for log/debug      *
-*************************************************/
-
-/* This function decodes a string containing bit settings in the form of +name
-and/or -name sequences, and sets/unsets bits in a bit string accordingly. It
-also recognizes a numeric setting of the form =<number>, but this is not
-intended for user use. It's an easy way for Exim to pass the debug settings
-when it is re-exec'ed.
-
-The log options are held in two unsigned ints (because there became too many
-for one). The top bit in the table means "put in 2nd selector". This does not
-yet apply to debug options, so the "=" facility sets only the first selector.
-
-The "all" selector, which must be equal to 0xffffffff, is recognized specially.
-It sets all the bits in both selectors. However, there is a facility for then
-unsetting certain bits, because we want to turn off "memory" in the debug case.
-
-A bad value for a debug setting is treated as an unknown option - error message
-to stderr and die. For log settings, which come from the configuration file,
-we write to the log on the way out...
-
-Arguments:
-  selector1      address of the first bit string
-  selector2      address of the second bit string, or NULL
-  notall1        bits to exclude from "all" for selector1
-  notall2        bits to exclude from "all" for selector2
-  string         the configured string
-  options        the table of option names
-  count          size of table
-  which          "log" or "debug"
-
-Returns:         nothing on success - bomb out on failure
-*/
-
-static void
-decode_bits(unsigned int *selector1, unsigned int *selector2, int notall1,
-  int notall2, uschar *string, bit_table *options, int count, uschar *which)
-{
-uschar *errmsg;
-if (string == NULL) return;
-
-if (*string == '=')
-  {
-  char *end;    /* Not uschar */
-  *selector1 = strtoul(CS string+1, &end, 0);
-  if (*end == 0) return;
-  errmsg = string_sprintf("malformed numeric %s_selector setting: %s", which,
-    string);
-  goto ERROR_RETURN;
-  }
-
-/* Handle symbolic setting */
-
-else for(;;)
-  {
-  BOOL adding;
-  uschar *s;
-  int len;
-  bit_table *start, *end;
-
-  while (isspace(*string)) string++;
-  if (*string == 0) return;
-
-  if (*string != '+' && *string != '-')
-    {
-    errmsg = string_sprintf("malformed %s_selector setting: "
-      "+ or - expected but found \"%s\"", which, string);
-    goto ERROR_RETURN;
-    }
-
-  adding = *string++ == '+';
-  s = string;
-  while (isalnum(*string) || *string == '_') string++;
-  len = string - s;
-
-  start = options;
-  end = options + count;
-
-  while (start < end)
-    {
-    bit_table *middle = start + (end - start)/2;
-    int c = Ustrncmp(s, middle->name, len);
-    if (c == 0)
-      {
-      if (middle->name[len] != 0) c = -1; else
-        {
-        unsigned int bit = middle->bit;
-        unsigned int *selector;
-
-        /* The value with all bits set means "force all bits in both selectors"
-        in the case where two are being handled. However, the top bit in the
-        second selector is never set. When setting, some bits can be excluded.
-        */
-
-        if (bit == 0xffffffff)
-          {
-          if (adding)
-            {
-            *selector1 = 0xffffffff ^ notall1;
-            if (selector2 != NULL) *selector2 = 0x7fffffff ^ notall2;
-            }
-          else
-            {
-            *selector1 = 0;
-            if (selector2 != NULL) *selector2 = 0;
-            }
-          }
-
-        /* Otherwise, the 0x80000000 bit means "this value, without the top
-        bit, belongs in the second selector". */
-
-        else
-          {
-          if ((bit & 0x80000000) != 0)
-            {
-            selector = selector2;
-            bit &= 0x7fffffff;
-            }
-          else selector = selector1;
-          if (adding) *selector |= bit; else *selector &= ~bit;
-          }
-        break;  /* Out of loop to match selector name */
-        }
-      }
-    if (c < 0) end = middle; else start = middle + 1;
-    }  /* Loop to match selector name */
-
-  if (start >= end)
-    {
-    errmsg = string_sprintf("unknown %s_selector setting: %c%.*s", which,
-      adding? '+' : '-', len, s);
-    goto ERROR_RETURN;
-    }
-  }    /* Loop for selector names */
-
-/* Handle disasters */
-
-ERROR_RETURN:
-if (Ustrcmp(which, "debug") == 0)
-  {
-  fprintf(stderr, "exim: %s\n", errmsg);
-  exit(EXIT_FAILURE);
-  }
-else log_write(0, LOG_CONFIG|LOG_PANIC_DIE, "%s", errmsg);
-}
-
-
-
 /*************************************************
 *          Show supported features               *
 *************************************************/
 
 /*************************************************
 *          Show supported features               *
 *************************************************/
 
-/* This function is called for -bV and for -d to output the optional features
-of the current Exim binary.
+/* This function is called for -bV/--version and for -d to output the optional
+features of the current Exim binary.
 
 Arguments:  a FILE for printing
 Returns:    nothing
 
 Arguments:  a FILE for printing
 Returns:    nothing
@@ -905,6 +761,9 @@ fprintf(f, "Support for:");
 #ifdef WITH_CONTENT_SCAN
   fprintf(f, " Content_Scanning");
 #endif
 #ifdef WITH_CONTENT_SCAN
   fprintf(f, " Content_Scanning");
 #endif
+#ifndef DISABLE_DKIM
+  fprintf(f, " DKIM");
+#endif
 #ifdef WITH_OLD_DEMIME
   fprintf(f, " Old_Demime");
 #endif
 #ifdef WITH_OLD_DEMIME
   fprintf(f, " Old_Demime");
 #endif
@@ -917,58 +776,58 @@ fprintf(f, "Support for:");
 #ifdef EXPERIMENTAL_BRIGHTMAIL
   fprintf(f, " Experimental_Brightmail");
 #endif
 #ifdef EXPERIMENTAL_BRIGHTMAIL
   fprintf(f, " Experimental_Brightmail");
 #endif
-#ifdef EXPERIMENTAL_DOMAINKEYS
-  fprintf(f, " Experimental_DomainKeys");
+#ifdef EXPERIMENTAL_DCC
+  fprintf(f, " Experimental_DCC");
 #endif
 fprintf(f, "\n");
 
 #endif
 fprintf(f, "\n");
 
-fprintf(f, "Lookups:");
-#ifdef LOOKUP_LSEARCH
+fprintf(f, "Lookups (built-in):");
+#if defined(LOOKUP_LSEARCH) && LOOKUP_LSEARCH!=2
   fprintf(f, " lsearch wildlsearch nwildlsearch iplsearch");
 #endif
   fprintf(f, " lsearch wildlsearch nwildlsearch iplsearch");
 #endif
-#ifdef LOOKUP_CDB
+#if defined(LOOKUP_CDB) && LOOKUP_CDB!=2
   fprintf(f, " cdb");
 #endif
   fprintf(f, " cdb");
 #endif
-#ifdef LOOKUP_DBM
+#if defined(LOOKUP_DBM) && LOOKUP_DBM!=2
   fprintf(f, " dbm dbmnz");
 #endif
   fprintf(f, " dbm dbmnz");
 #endif
-#ifdef LOOKUP_DNSDB
+#if defined(LOOKUP_DNSDB) && LOOKUP_DNSDB!=2
   fprintf(f, " dnsdb");
 #endif
   fprintf(f, " dnsdb");
 #endif
-#ifdef LOOKUP_DSEARCH
+#if defined(LOOKUP_DSEARCH) && LOOKUP_DSEARCH!=2
   fprintf(f, " dsearch");
 #endif
   fprintf(f, " dsearch");
 #endif
-#ifdef LOOKUP_IBASE
+#if defined(LOOKUP_IBASE) && LOOKUP_IBASE!=2
   fprintf(f, " ibase");
 #endif
   fprintf(f, " ibase");
 #endif
-#ifdef LOOKUP_LDAP
+#if defined(LOOKUP_LDAP) && LOOKUP_LDAP!=2
   fprintf(f, " ldap ldapdn ldapm");
 #endif
   fprintf(f, " ldap ldapdn ldapm");
 #endif
-#ifdef LOOKUP_MYSQL
+#if defined(LOOKUP_MYSQL) && LOOKUP_MYSQL!=2
   fprintf(f, " mysql");
 #endif
   fprintf(f, " mysql");
 #endif
-#ifdef LOOKUP_NIS
+#if defined(LOOKUP_NIS) && LOOKUP_NIS!=2
   fprintf(f, " nis nis0");
 #endif
   fprintf(f, " nis nis0");
 #endif
-#ifdef LOOKUP_NISPLUS
+#if defined(LOOKUP_NISPLUS) && LOOKUP_NISPLUS!=2
   fprintf(f, " nisplus");
 #endif
   fprintf(f, " nisplus");
 #endif
-#ifdef LOOKUP_ORACLE
+#if defined(LOOKUP_ORACLE) && LOOKUP_ORACLE!=2
   fprintf(f, " oracle");
 #endif
   fprintf(f, " oracle");
 #endif
-#ifdef LOOKUP_PASSWD
+#if defined(LOOKUP_PASSWD) && LOOKUP_PASSWD!=2
   fprintf(f, " passwd");
 #endif
   fprintf(f, " passwd");
 #endif
-#ifdef LOOKUP_PGSQL
+#if defined(LOOKUP_PGSQL) && LOOKUP_PGSQL!=2
   fprintf(f, " pgsql");
 #endif
   fprintf(f, " pgsql");
 #endif
-#ifdef LOOKUP_SQLITE
+#if defined(LOOKUP_SQLITE) && LOOKUP_SQLITE!=2
   fprintf(f, " sqlite");
 #endif
   fprintf(f, " sqlite");
 #endif
-#ifdef LOOKUP_TESTDB
+#if defined(LOOKUP_TESTDB) && LOOKUP_TESTDB!=2
   fprintf(f, " testdb");
 #endif
   fprintf(f, " testdb");
 #endif
-#ifdef LOOKUP_WHOSON
+#if defined(LOOKUP_WHOSON) && LOOKUP_WHOSON!=2
   fprintf(f, " whoson");
 #endif
 fprintf(f, "\n");
   fprintf(f, " whoson");
 #endif
 fprintf(f, "\n");
@@ -1051,7 +910,69 @@ if (fixed_never_users[0] > 0)
   fprintf(f, "%d\n", (unsigned int)fixed_never_users[i]);
   }
 
   fprintf(f, "%d\n", (unsigned int)fixed_never_users[i]);
   }
 
-fprintf(f, "Size of off_t: %d\n", sizeof(off_t));
+fprintf(f, "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. */
+DEBUG(D_any) do {
+
+  int i;
+
+/* clang defines __GNUC__ (at least, for me) so test for it first */
+#if defined(__clang__)
+  fprintf(f, "Compiler: CLang [%s]\n", __clang_version__);
+#elif defined(__GNUC__)
+  fprintf(f, "Compiler: GCC [%s]\n",
+# ifdef __VERSION__
+      __VERSION__
+# else
+      "? unknown version ?"
+# endif
+      );
+#else
+  fprintf(f, "Compiler: <unknown>\n");
+#endif
+
+#ifdef SUPPORT_TLS
+  tls_version_report(f);
+#endif
+
+#ifdef AUTH_CYRUS_SASL
+  auth_cyrus_sasl_version_report(f);
+#endif
+
+  fprintf(f, "Library version: PCRE: Compile: %d.%d%s\n"
+             "                       Runtime: %s\n",
+          PCRE_MAJOR, PCRE_MINOR,
+          /* PRE_PRERELEASE is either defined and empty or a string.
+           * unless its an ancient version of PCRE in which case it
+           * is not defined */
+#ifdef PCRE_PRERELEASE
+          PCRE_PRERELEASE "",
+#else
+          "",
+#endif
+          pcre_version());
+
+  init_lookup_list();
+  for (i = 0; i < lookup_list_count; i++)
+    {
+    if (lookup_list[i]->version_report)
+      lookup_list[i]->version_report(f);
+    }
+
+#ifdef WHITELIST_D_MACROS
+  fprintf(f, "WHITELIST_D_MACROS: \"%s\"\n", WHITELIST_D_MACROS);
+#else
+  fprintf(f, "WHITELIST_D_MACROS unset\n");
+#endif
+#ifdef TRUSTED_CONFIG_LIST
+  fprintf(f, "TRUSTED_CONFIG_LIST: \"%s\"\n", TRUSTED_CONFIG_LIST);
+#else
+  fprintf(f, "TRUSTED_CONFIG_LIST unset\n");
+#endif
+
+} while (0);
 }
 
 
 }
 
 
@@ -1128,8 +1049,8 @@ Returns:            the dlopen handle or NULL on failure
 */
 
 static void *
 */
 
 static void *
-set_readline(char * (**fn_readline_ptr)(char *),
-             char * (**fn_addhist_ptr)(char *))
+set_readline(char * (**fn_readline_ptr)(const char *),
+             void   (**fn_addhist_ptr)(const char *))
 {
 void *dlhandle;
 void *dlhandle_curses = dlopen("libcurses.so", RTLD_GLOBAL|RTLD_LAZY);
 {
 void *dlhandle;
 void *dlhandle_curses = dlopen("libcurses.so", RTLD_GLOBAL|RTLD_LAZY);
@@ -1139,8 +1060,12 @@ if (dlhandle_curses != NULL) dlclose(dlhandle_curses);
 
 if (dlhandle != NULL)
   {
 
 if (dlhandle != NULL)
   {
-  *fn_readline_ptr = (char *(*)(char*))dlsym(dlhandle, "readline");
-  *fn_addhist_ptr = (char *(*)(char*))dlsym(dlhandle, "add_history");
+  /* Checked manual pages; at least in GNU Readline 6.1, the prototypes are:
+   *   char * readline (const char *prompt);
+   *   void add_history (const char *string);
+   */
+  *fn_readline_ptr = (char *(*)(const char*))dlsym(dlhandle, "readline");
+  *fn_addhist_ptr = (void(*)(const char*))dlsym(dlhandle, "add_history");
   }
 else
   {
   }
 else
   {
@@ -1170,7 +1095,7 @@ Returns:        pointer to dynamic memory, or NULL at end of file
 */
 
 static uschar *
 */
 
 static uschar *
-get_stdinput(char *(*fn_readline)(char *), char *(*fn_addhist)(char *))
+get_stdinput(char *(*fn_readline)(const char *), void(*fn_addhist)(const char *))
 {
 int i;
 int size = 0;
 {
 int i;
 int size = 0;
@@ -1232,6 +1157,154 @@ return yield;
 
 
 
 
 
 
+/*************************************************
+*    Output usage information for the program    *
+*************************************************/
+
+/* This function is called when there are no recipients
+   or a specific --help argument was added.
+
+Arguments:
+  progname      information on what name we were called by
+
+Returns:        DOES NOT RETURN
+*/
+
+static void
+exim_usage(uschar *progname)
+{
+
+/* Handle specific program invocation varients */
+if (Ustrcmp(progname, US"-mailq") == 0)
+  {
+  fprintf(stderr,
+    "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 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);
+}
+
+
+
+/*************************************************
+*    Validate that the macros given are okay     *
+*************************************************/
+
+/* Typically, Exim will drop privileges if macros are supplied.  In some
+cases, we want to not do so.
+
+Arguments:    none (macros is a global)
+Returns:      true if trusted, false otherwise
+*/
+
+static BOOL
+macros_trusted(void)
+{
+#ifdef WHITELIST_D_MACROS
+macro_item *m;
+uschar *whitelisted, *end, *p, **whites, **w;
+int white_count, i, n;
+size_t len;
+BOOL prev_char_item, found;
+#endif
+
+if (macros == NULL)
+  return TRUE;
+#ifndef WHITELIST_D_MACROS
+return FALSE;
+#else
+
+/* We only trust -D overrides for some invoking users:
+root, the exim run-time user, the optional config owner user.
+I don't know why config-owner would be needed, but since they can own the
+config files anyway, there's no security risk to letting them override -D. */
+if ( ! ((real_uid == root_uid)
+     || (real_uid == exim_uid)
+#ifdef CONFIGURE_OWNER
+     || (real_uid == config_uid)
+#endif
+   ))
+  {
+  debug_printf("macros_trusted rejecting macros for uid %d\n", (int) real_uid);
+  return FALSE;
+  }
+
+/* Get a list of macros which are whitelisted */
+whitelisted = string_copy_malloc(US WHITELIST_D_MACROS);
+prev_char_item = FALSE;
+white_count = 0;
+for (p = whitelisted; *p != '\0'; ++p)
+  {
+  if (*p == ':' || isspace(*p))
+    {
+    *p = '\0';
+    if (prev_char_item)
+      ++white_count;
+    prev_char_item = FALSE;
+    continue;
+    }
+  if (!prev_char_item)
+    prev_char_item = TRUE;
+  }
+end = p;
+if (prev_char_item)
+  ++white_count;
+if (!white_count)
+  return FALSE;
+whites = store_malloc(sizeof(uschar *) * (white_count+1));
+for (p = whitelisted, i = 0; (p != end) && (i < white_count); ++p)
+  {
+  if (*p != '\0')
+    {
+    whites[i++] = p;
+    if (i == white_count)
+      break;
+    while (*p != '\0' && p < end)
+      ++p;
+    }
+  }
+whites[i] = NULL;
+
+/* The list of macros should be very short.  Accept the N*M complexity. */
+for (m = macros; m != NULL; m = m->next)
+  {
+  found = FALSE;
+  for (w = whites; *w; ++w)
+    if (Ustrcmp(*w, m->name) == 0)
+      {
+      found = TRUE;
+      break;
+      }
+  if (!found)
+    return FALSE;
+  if (m->replacement == NULL)
+    continue;
+  len = Ustrlen(m->replacement);
+  if (len == 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 overriden to true by whitelisting\n");
+return TRUE;
+#endif
+}
+
+
 /*************************************************
 *          Entry point and high-level code       *
 *************************************************/
 /*************************************************
 *          Entry point and high-level code       *
 *************************************************/
@@ -1261,7 +1334,7 @@ int  arg_error_handling = error_handling;
 int  filter_sfd = -1;
 int  filter_ufd = -1;
 int  group_count;
 int  filter_sfd = -1;
 int  filter_ufd = -1;
 int  group_count;
-int  i;
+int  i, rv;
 int  list_queue_option = 0;
 int  msg_action = 0;
 int  msg_action_arg = -1;
 int  list_queue_option = 0;
 int  msg_action = 0;
 int  msg_action_arg = -1;
@@ -1291,8 +1364,10 @@ BOOL one_msg_action = FALSE;
 BOOL queue_only_set = FALSE;
 BOOL receiving_message = TRUE;
 BOOL sender_ident_set = FALSE;
 BOOL queue_only_set = FALSE;
 BOOL receiving_message = TRUE;
 BOOL sender_ident_set = FALSE;
+BOOL session_local_queue_only;
 BOOL unprivileged;
 BOOL removed_privilege = FALSE;
 BOOL unprivileged;
 BOOL removed_privilege = FALSE;
+BOOL usage_wanted = FALSE;
 BOOL verify_address_mode = FALSE;
 BOOL verify_as_sender = FALSE;
 BOOL version_printed = FALSE;
 BOOL verify_address_mode = FALSE;
 BOOL verify_as_sender = FALSE;
 BOOL version_printed = FALSE;
@@ -1305,6 +1380,7 @@ uschar *ftest_domain = NULL;
 uschar *ftest_localpart = NULL;
 uschar *ftest_prefix = NULL;
 uschar *ftest_suffix = NULL;
 uschar *ftest_localpart = NULL;
 uschar *ftest_prefix = NULL;
 uschar *ftest_suffix = NULL;
+uschar *malware_test_file = NULL;
 uschar *real_sender_address;
 uschar *originator_home = US"/";
 void *reset_point;
 uschar *real_sender_address;
 uschar *originator_home = US"/";
 void *reset_point;
@@ -1332,7 +1408,25 @@ This is a feature to make the lives of binary distributors easier. */
 #ifdef EXIM_USERNAME
 if (route_finduser(US EXIM_USERNAME, &pw, &exim_uid))
   {
 #ifdef EXIM_USERNAME
 if (route_finduser(US EXIM_USERNAME, &pw, &exim_uid))
   {
-  exim_gid = pw->pw_gid;
+  if (exim_uid == 0)
+    {
+    fprintf(stderr, "exim: refusing to run with uid 0 for \"%s\"\n",
+      EXIM_USERNAME);
+    exit(EXIT_FAILURE);
+    }
+  /* 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: 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
   {
   }
 else
   {
@@ -1360,6 +1454,10 @@ if (!route_finduser(US CONFIGURE_OWNERNAME, NULL, &config_uid))
   }
 #endif
 
   }
 #endif
 
+/* We default the system_filter_user to be the Exim run-time user, as a
+sane non-root value. */
+system_filter_uid = exim_uid;
+
 #ifdef CONFIGURE_GROUPNAME
 if (!route_findgroup(US CONFIGURE_GROUPNAME, &config_gid))
   {
 #ifdef CONFIGURE_GROUPNAME
 if (!route_findgroup(US CONFIGURE_GROUPNAME, &config_gid))
   {
@@ -1505,6 +1603,15 @@ regex_smtp_code =
   regex_must_compile(US"^\\d\\d\\d\\s(?:\\d\\.\\d\\d?\\d?\\.\\d\\d?\\d?\\s)?",
     FALSE, TRUE);
 
   regex_must_compile(US"^\\d\\d\\d\\s(?:\\d\\.\\d\\d?\\d?\\.\\d\\d?\\d?\\s)?",
     FALSE, TRUE);
 
+#ifdef WHITELIST_D_MACROS
+/* Precompile the regular expression used to filter the content of macros
+given to -D for permissibility. */
+
+regex_whitelisted_macro =
+  regex_must_compile(US"^[A-Za-z0-9_/.-]*$", FALSE, TRUE);
+#endif
+
+
 /* If the program is called as "mailq" treat it as equivalent to "exim -bp";
 this seems to be a generally accepted convention, since one finds symbolic
 links called "mailq" in standard OS configurations. */
 /* If the program is called as "mailq" treat it as equivalent to "exim -bp";
 this seems to be a generally accepted convention, since one finds symbolic
 links called "mailq" in standard OS configurations. */
@@ -1578,8 +1685,20 @@ real_gid = getgid();
 
 if (real_uid == root_uid)
   {
 
 if (real_uid == root_uid)
   {
-  setgid(real_gid);
-  setuid(real_uid);
+  rv = setgid(real_gid);
+  if (rv)
+    {
+    fprintf(stderr, "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",
+        (long int)real_uid, strerror(errno));
+    exit(EXIT_FAILURE);
+    }
   }
 
 /* If neither the original real uid nor the original euid was root, Exim is
   }
 
 /* If neither the original real uid nor the original euid was root, Exim is
@@ -1587,11 +1706,6 @@ running in an unprivileged state. */
 
 unprivileged = (real_uid != root_uid && original_euid != root_uid);
 
 
 unprivileged = (real_uid != root_uid && original_euid != root_uid);
 
-/* If the first argument is --help, pretend there are no arguments. This will
-cause a brief message to be given. */
-
-if (argc > 1 && Ustrcmp(argv[1], "--help") == 0) argc = 1;
-
 /* Scan the program's arguments. Some can be dealt with right away; others are
 simply recorded for checking and handling afterwards. Do a high-level switch
 on the second character (the one after '-'), to save some effort. */
 /* Scan the program's arguments. Some can be dealt with right away; others are
 simply recorded for checking and handling afterwards. Do a high-level switch
 on the second character (the one after '-'), to save some effort. */
@@ -1656,6 +1770,21 @@ for (i = 1; i < argc; i++)
     argrest++;
     }
 
     argrest++;
     }
 
+  /* deal with --option_aliases */
+  else if (switchchar == '-')
+    {
+    if (Ustrcmp(argrest, "help") == 0)
+      {
+      usage_wanted = TRUE;
+      break;
+      }
+    else if (Ustrcmp(argrest, "version") == 0)
+      {
+      switchchar = 'b';
+      argrest = US"V";
+      }
+    }
+
   /* High-level switch on active initial letter */
 
   switch(switchchar)
   /* High-level switch on active initial letter */
 
   switch(switchchar)
@@ -1766,6 +1895,14 @@ for (i = 1; i < argc; i++)
 
     else if (Ustrcmp(argrest, "m") == 0) receiving_message = TRUE;
 
 
     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; }
+      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. */
     /* -bnq: For locally originating messages, do not qualify unqualified
     addresses. In the envelope, this causes errors; in header lines they
     just get left. */
@@ -1919,6 +2056,103 @@ for (i = 1; i < argc; i++)
           }
         }
       #endif
           }
         }
       #endif
+      if (real_uid != root_uid)
+        {
+        #ifdef TRUSTED_CONFIG_LIST
+
+        if (real_uid != exim_uid
+            #ifdef CONFIGURE_OWNER
+            && real_uid != config_uid
+            #endif
+            )
+          trusted_config = FALSE;
+        else
+          {
+          FILE *trust_list = Ufopen(TRUSTED_CONFIG_LIST, "rb");
+          if (trust_list)
+            {
+            struct stat statbuf;
+
+            if (fstat(fileno(trust_list), &statbuf) != 0 ||
+                (statbuf.st_uid != root_uid        /* owner not root */
+                 #ifdef CONFIGURE_OWNER
+                 && statbuf.st_uid != config_uid   /* owner not the special one */
+                 #endif
+                   ) ||                            /* or */
+                (statbuf.st_gid != root_gid        /* group not root */
+                 #ifdef CONFIGURE_GROUP
+                 && statbuf.st_gid != config_gid   /* group not the special one */
+                 #endif
+                 && (statbuf.st_mode & 020) != 0   /* group writeable */
+                   ) ||                            /* or */
+                (statbuf.st_mode & 2) != 0)        /* world writeable */
+              {
+              trusted_config = FALSE;
+              fclose(trust_list);
+              }
+           else
+              {
+              /* Well, the trust list at least is up to scratch... */
+              void *reset_point = store_get(0);
+              uschar *trusted_configs[32];
+              int nr_configs = 0;
+              int i = 0;
+
+              while (Ufgets(big_buffer, big_buffer_size, trust_list))
+                {
+                uschar *start = big_buffer, *nl;
+                while (*start && isspace(*start))
+                start++;
+                if (*start != '/')
+                  continue;
+                nl = Ustrchr(start, '\n');
+                if (nl)
+                  *nl = 0;
+                trusted_configs[nr_configs++] = string_copy(start);
+                if (nr_configs == 32)
+                  break;
+                }
+              fclose(trust_list);
+
+              if (nr_configs)
+                {
+                int sep = 0;
+                uschar *list = argrest;
+                uschar *filename;
+                while (trusted_config && (filename = string_nextinlist(&list,
+                        &sep, big_buffer, big_buffer_size)) != NULL)
+                  {
+                  for (i=0; i < nr_configs; i++)
+                    {
+                    if (Ustrcmp(filename, trusted_configs[i]) == 0)
+                      break;
+                    }
+                  if (i == nr_configs)
+                    {
+                    trusted_config = FALSE;
+                    break;
+                    }
+                  }
+                store_reset(reset_point);
+                }
+              else
+                {
+                /* No valid prefixes found in trust_list file. */
+                trusted_config = FALSE;
+                }
+              }
+           }
+          else
+            {
+            /* Could not open trust_list file. */
+            trusted_config = FALSE;
+            }
+          }
+      #else
+        /* Not root; don't trust config */
+        trusted_config = FALSE;
+      #endif
+        }
 
       config_main_filelist = argrest;
       config_changed = TRUE;
 
       config_main_filelist = argrest;
       config_changed = TRUE;
@@ -2016,7 +2250,7 @@ for (i = 1; i < argc; i++)
         }
       if (*argrest != 0)
         decode_bits(&selector, NULL, D_memory, 0, argrest, debug_options,
         }
       if (*argrest != 0)
         decode_bits(&selector, NULL, D_memory, 0, argrest, debug_options,
-          debug_options_count, US"debug");
+          debug_options_count, US"debug", 0);
       debug_selector = selector;
       }
     break;
       debug_selector = selector;
       }
     break;
@@ -2283,6 +2517,7 @@ for (i = 1; i < argc; i++)
        -Mes  edit sender
        -Mset load a message for use with -be
        -Mvb  show body
        -Mes  edit sender
        -Mset load a message for use with -be
        -Mvb  show body
+       -Mvc  show copy (of whole message, in RFC 2822 format)
        -Mvh  show header
        -Mvl  show log
     */
        -Mvh  show header
        -Mvl  show log
     */
@@ -2330,6 +2565,11 @@ for (i = 1; i < argc; i++)
       msg_action = MSG_SHOW_BODY;
       one_msg_action = TRUE;
       }
       msg_action = MSG_SHOW_BODY;
       one_msg_action = TRUE;
       }
+    else if (Ustrcmp(argrest, "vc") == 0)
+      {
+      msg_action = MSG_SHOW_COPY;
+      one_msg_action = TRUE;
+      }
     else if (Ustrcmp(argrest, "vh") == 0)
       {
       msg_action = MSG_SHOW_HEADER;
     else if (Ustrcmp(argrest, "vh") == 0)
       {
       msg_action = MSG_SHOW_HEADER;
@@ -2919,9 +3159,11 @@ if ((deliver_selectstring != NULL || deliver_selectstring_sender != NULL) &&
   queue_interval < 0) queue_interval = 0;
 
 
   queue_interval < 0) queue_interval = 0;
 
 
-/* Arguments have been processed. Check for incompatibilities. */
-
 END_ARG:
 END_ARG:
+/* If usage_wanted is set we call the usage function - which never returns */
+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 ||
 if ((
     (smtp_input || extract_recipients || recipients_arg < argc) &&
     (daemon_listen || queue_interval >= 0 || bi_option ||
@@ -2988,7 +3230,8 @@ if (debug_selector != 0)
     debug_printf("Exim version %s uid=%ld gid=%ld pid=%d D=%x\n",
       version_string, (long int)real_uid, (long int)real_gid, (int)getpid(),
       debug_selector);
     debug_printf("Exim version %s uid=%ld gid=%ld pid=%d D=%x\n",
       version_string, (long int)real_uid, (long int)real_gid, (int)getpid(),
       debug_selector);
-    show_whats_supported(stderr);
+    if (!version_printed)
+      show_whats_supported(stderr);
     }
   }
 
     }
   }
 
@@ -3068,6 +3311,11 @@ 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);
 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);
+  }
 
 /* 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
 
 /* 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
@@ -3096,11 +3344,11 @@ if (setgroups(0, NULL) != 0)
 
 /* 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
 
 /* 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
-not root or the exim user, or if this is a filter testing run, remove any
-setuid privilege the program has, and run as the underlying user.
+not root, or if this is a filter testing run, remove any setuid privilege the
+program has and run as the underlying user.
 
 
-If ALT_CONFIG_ROOT_ONLY is defined, the exim user is locked out of this, which
-severely restricts the use of -C for some purposes.
+The exim user is locked out of this, which severely restricts the use of -C
+for some purposes.
 
 Otherwise, set the real ids to the effective values (should be root unless run
 from inetd, which it can either be root or the exim uid, if one is configured).
 
 Otherwise, set the real ids to the effective values (should be root unless run
 from inetd, which it can either be root or the exim uid, if one is configured).
@@ -3112,11 +3360,9 @@ 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 */
 configuration file changes and macro definitions haven't happened. */
 
 if ((                                            /* EITHER */
-    (config_changed || macros != NULL) &&        /* Config changed, and */
+    (!trusted_config ||                          /* Config changed, or */
+     !macros_trusted()) &&                       /*  impermissible macros and */
     real_uid != root_uid &&                      /* Not root, and */
     real_uid != root_uid &&                      /* Not root, and */
-    #ifndef ALT_CONFIG_ROOT_ONLY                 /* (when not locked out) */
-    real_uid != exim_uid &&                      /* Not exim, and */
-    #endif
     !running_in_test_harness                     /* Not fudged */
     ) ||                                         /*   OR   */
     expansion_test                               /* expansion testing */
     !running_in_test_harness                     /* Not fudged */
     ) ||                                         /*   OR   */
     expansion_test                               /* expansion testing */
@@ -3132,9 +3378,13 @@ if ((                                            /* EITHER */
   and should be used for any logging information because attempts to write
   to the log will usually fail. To arrange this, we unset really_exim. However,
   if no stderr is available there is no point - we might as well have a go
   and should be used for any logging information because attempts to write
   to the log will usually fail. To arrange this, we unset really_exim. However,
   if no stderr is available there is no point - we might as well have a go
-  at the log (if it fails, syslog will be written). */
+  at the log (if it fails, syslog will be written).
 
 
-  if (log_stderr != NULL) really_exim = FALSE;
+  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;
   }
 
 /* Privilege is to be retained for the moment. It may be dropped later,
   }
 
 /* Privilege is to be retained for the moment. It may be dropped later,
@@ -3177,8 +3427,8 @@ readconf_main();
 
 /* Handle the decoding of logging options. */
 
 
 /* Handle the decoding of logging options. */
 
-decode_bits(&log_write_selector, &log_extra_selector, 0, 0, log_selector_string,
-  log_options, log_options_count, US"log");
+decode_bits(&log_write_selector, &log_extra_selector, 0, 0,
+  log_selector_string, log_options, log_options_count, US"log", 0);
 
 DEBUG(D_any)
   {
 
 DEBUG(D_any)
   {
@@ -3304,15 +3554,12 @@ else
   }
 
 /* Handle the case when we have removed the setuid privilege because of -C or
   }
 
 /* Handle the case when we have removed the setuid privilege because of -C or
--D. This means that the caller of Exim was not root, and, provided that
-ALT_CONFIG_ROOT_ONLY is not defined, was not the Exim user that is built into
-the binary.
+-D. This means that the caller of Exim was not root.
 
 
-If ALT_CONFIG_ROOT_ONLY is not defined, there is a problem if it turns out we
-were running as the exim user defined in the configuration file (different to
-the one in the binary). The sysadmin may expect this case to retain privilege
-because "the binary was called by the Exim user", but it hasn't, because of the
-order in which it handles this stuff. There are two possibilities:
+There is a problem if we were running as the Exim user. The sysadmin may
+expect this case to retain privilege because "the binary was called by the
+Exim user", but it hasn't, because either the -D option set macros, or the
+-C option set a non-trusted configuration file. There are two possibilities:
 
   (1) If deliver_drop_privilege is set, Exim is not going to re-exec in order
       to do message deliveries. Thus, the fact that it is running as a
 
   (1) If deliver_drop_privilege is set, Exim is not going to re-exec in order
       to do message deliveries. Thus, the fact that it is running as a
@@ -3324,27 +3571,18 @@ order in which it handles this stuff. There are two possibilities:
 
   (2) If deliver_drop_privilege is not set, the configuration won't work as
       apparently intended, and so we log a panic message. In order to retain
 
   (2) If deliver_drop_privilege is not set, the configuration won't work as
       apparently intended, and so we log a panic message. In order to retain
-      root for -C or -D, the caller must either be root or the Exim user
-      defined in the binary (when deliver_drop_ privilege is false).
-
-If ALT_CONFIG_ROOT_ONLY is defined, we don't know whether we were called by the
-built-in exim user or one defined in the configuration. In either event,
-re-enable log processing, assuming the sysadmin knows what they are doing. */
+      root for -C or -D, the caller must either be root or be invoking a
+      trusted configuration file (when deliver_drop_privilege is false). */
 
 
-if (removed_privilege && (config_changed || macros != NULL) &&
+if (removed_privilege && (!trusted_config || macros != NULL) &&
     real_uid == exim_uid)
   {
     real_uid == exim_uid)
   {
-  #ifdef ALT_CONFIG_ROOT_ONLY
-  really_exim = TRUE;   /* let logging work normally */
-  #else
-
   if (deliver_drop_privilege)
     really_exim = TRUE; /* let logging work normally */
   else
     log_write(0, LOG_MAIN|LOG_PANIC,
   if (deliver_drop_privilege)
     really_exim = TRUE; /* let logging work normally */
   else
     log_write(0, LOG_MAIN|LOG_PANIC,
-      "exim user (uid=%d) is defined only at runtime; privilege lost for %s",
-      (int)exim_uid, config_changed? "-C" : "-D");
-  #endif
+      "exim user lost privilege for using %s option",
+      trusted_config? "-D" : "-C");
   }
 
 /* Start up Perl interpreter if Perl support is configured and there is a
   }
 
 /* Start up Perl interpreter if Perl support is configured and there is a
@@ -3369,6 +3607,13 @@ if (opt_perl_at_start && opt_perl_startup != NULL)
   }
 #endif /* EXIM_PERL */
 
   }
 #endif /* EXIM_PERL */
 
+/* Initialise lookup_list
+If debugging, already called above via version reporting.
+This does mean that debugging causes the list to be initialised while root.
+This *should* be harmless -- all modules are loaded from a fixed dir and
+it's code that would, if not a module, be part of Exim already. */
+init_lookup_list();
+
 /* Log the arguments of the call if the configuration file said so. This is
 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
 /* Log the arguments of the call if the configuration file said so. This is
 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
@@ -3474,7 +3719,6 @@ if (real_uid == root_uid || real_uid == exim_uid || real_gid == exim_gid)
 else
   {
   int i, j;
 else
   {
   int i, j;
-
   for (i = 0; i < group_count; i++)
     {
     if (group_list[i] == exim_gid) admin_user = TRUE;
   for (i = 0; i < group_count; i++)
     {
     if (group_list[i] == exim_gid) admin_user = TRUE;
@@ -3530,12 +3774,13 @@ configuration, but the queue run restriction can be relaxed. Only an admin
 user may request that a message be returned to its sender forthwith. Only an
 admin user may specify a debug level greater than D_v (because it might show
 passwords, etc. in lookup queries). Only an admin user may request a queue
 user may request that a message be returned to its sender forthwith. Only an
 admin user may specify a debug level greater than D_v (because it might show
 passwords, etc. in lookup queries). Only an admin user may request a queue
-count. */
+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)
   {
   BOOL debugset = (debug_selector & ~D_v) != 0;
 
 if (!admin_user)
   {
   BOOL debugset = (debug_selector & ~D_v) != 0;
-  if (deliver_give_up || daemon_listen ||
+  if (deliver_give_up || daemon_listen || malware_test_file ||
      (count_queue && queue_list_requires_admin) ||
      (list_queue && queue_list_requires_admin) ||
      (queue_interval >= 0 && prod_requires_admin) ||
      (count_queue && queue_list_requires_admin) ||
      (list_queue && queue_list_requires_admin) ||
      (queue_interval >= 0 && prod_requires_admin) ||
@@ -3638,7 +3883,7 @@ if (receiving_message &&
         (is_inetd && smtp_load_reserve >= 0)
       ))
   {
         (is_inetd && smtp_load_reserve >= 0)
       ))
   {
-  load_average = os_getloadavg();
+  load_average = OS_GETLOADAVG();
   }
 #endif
 
   }
 #endif
 
@@ -3684,7 +3929,55 @@ if (!unprivileged &&                      /* originally had root AND */
 
 /* When we are retaining a privileged uid, we still change to the exim gid. */
 
 
 /* When we are retaining a privileged uid, we still change to the exim gid. */
 
-else setgid(exim_gid);
+else
+  {
+  int rv;
+  rv = setgid(exim_gid);
+  /* Impact of failure is that some stuff might end up with an incorrect group.
+  We track this for failures from root, since any attempt to change privilege
+  by root should succeed and failures should be examined.  For non-root,
+  there's no security risk.  For me, it's { exim -bV } on a just-built binary,
+  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);
+      }
+    else
+      DEBUG(D_any) debug_printf("changing group to %ld failed: %s\n",
+          (long int)exim_gid, strerror(errno));
+    }
+  }
+
+/* Handle a request to scan a file for malware */
+if (malware_test_file)
+  {
+#ifdef WITH_CONTENT_SCAN
+  int result;
+  set_process_info("scanning file for malware");
+  result = malware_in_file(malware_test_file);
+  if (result == FAIL)
+    {
+    printf("No malware found.\n");
+    exit(EXIT_SUCCESS);
+    }
+  if (result != OK)
+    {
+    printf("Malware lookup returned non-okay/fail: %d\n", result);
+    exit(EXIT_FAILURE);
+    }
+  if (malware_name)
+    printf("Malware found: %s\n", malware_name);
+  else
+    printf("Malware scan detected malware of unknown name.\n");
+#else
+  printf("Malware scanning not enabled at compile time.\n");
+#endif
+  exit(EXIT_FAILURE);
+  }
 
 /* Handle a request to list the delivery queue */
 
 
 /* Handle a request to list the delivery queue */
 
@@ -3877,7 +4170,8 @@ if (list_options)
       if (i < argc - 1 &&
           (Ustrcmp(argv[i], "router") == 0 ||
            Ustrcmp(argv[i], "transport") == 0 ||
       if (i < argc - 1 &&
           (Ustrcmp(argv[i], "router") == 0 ||
            Ustrcmp(argv[i], "transport") == 0 ||
-           Ustrcmp(argv[i], "authenticator") == 0))
+           Ustrcmp(argv[i], "authenticator") == 0 ||
+           Ustrcmp(argv[i], "macro") == 0))
         {
         readconf_print(argv[i+1], argv[i]);
         i++;
         {
         readconf_print(argv[i+1], argv[i]);
         i++;
@@ -4288,8 +4582,8 @@ if (expansion_test)
 
   else
     {
 
   else
     {
-    char *(*fn_readline)(char *) = NULL;
-    char *(*fn_addhist)(char *) = NULL;
+    char *(*fn_readline)(const char *) = NULL;
+    void (*fn_addhist)(const char *) = NULL;
 
     #ifdef USE_READLINE
     void *dlhandle = set_readline(&fn_readline, &fn_addhist);
 
     #ifdef USE_READLINE
     void *dlhandle = set_readline(&fn_readline, &fn_addhist);
@@ -4418,14 +4712,9 @@ if (recipients_arg >= argc && !extract_recipients && !smtp_input)
     printf("Configuration file is %s\n", config_main_filename);
     return EXIT_SUCCESS;
     }
     printf("Configuration file is %s\n", config_main_filename);
     return EXIT_SUCCESS;
     }
+
   if (filter_test == FTEST_NONE)
   if (filter_test == FTEST_NONE)
-    {
-    fprintf(stderr,
-"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");
-    return EXIT_FAILURE;
-    }
+    exim_usage(called_as);
   }
 
 
   }
 
 
@@ -4523,11 +4812,11 @@ else
     sender_address);
   }
 
     sender_address);
   }
 
-/* Initialize the local_queue-only flag (this will be ignored if mua_wrapper is
-set) */
+/* Initialize the session_local_queue-only flag (this will be ignored if
+mua_wrapper is set) */
 
 queue_check_only();
 
 queue_check_only();
-local_queue_only = queue_only;
+session_local_queue_only = queue_only;
 
 /* For non-SMTP and for batched SMTP input, check that there is enough space on
 the spool if so configured. On failure, we must not attempt to send an error
 
 /* For non-SMTP and for batched SMTP input, check that there is enough space on
 the spool if so configured. On failure, we must not attempt to send an error
@@ -4886,27 +5175,36 @@ while (more)
     }
 
   /* Else act on the result of message reception. We should not get here unless
     }
 
   /* Else act on the result of message reception. We should not get here unless
-  message_id[0] is non-zero. If queue_only is set, local_queue_only will be
-  TRUE. If it is not, check on the number of messages received in this
-  connection. If that's OK and queue_only_load is set, check that the load
-  average is below it. If it is not, set local_queue_only TRUE. Note that it
-  then remains this way for any subsequent messages on the same SMTP connection.
-  This is a deliberate choice; even though the load average may fall, it
-  doesn't seem right to deliver later messages on the same call when not
-  delivering earlier ones. */
-
-  if (!local_queue_only)
+  message_id[0] is non-zero. If queue_only is set, session_local_queue_only
+  will be TRUE. If it is not, check on the number of messages received in this
+  connection. */
+
+  if (!session_local_queue_only &&
+      smtp_accept_queue_per_connection > 0 &&
+      receive_messagecount > smtp_accept_queue_per_connection)
     {
     {
-    if (smtp_accept_queue_per_connection > 0 &&
-        receive_messagecount > smtp_accept_queue_per_connection)
-      {
-      local_queue_only = TRUE;
-      queue_only_reason = 2;
-      }
-    else if (queue_only_load >= 0)
+    session_local_queue_only = TRUE;
+    queue_only_reason = 2;
+    }
+
+  /* Initialize local_queue_only from session_local_queue_only. If it is false,
+  and queue_only_load is set, check that the load average is below it. If it is
+  not, set local_queue_only TRUE. If queue_only_load_latch is true (the
+  default), we put the whole session into queue_only mode. It then remains this
+  way for any subsequent messages on the same SMTP connection. This is a
+  deliberate choice; even though the load average may fall, it doesn't seem
+  right to deliver later messages on the same call when not delivering earlier
+  ones. However, there are odd cases where this is not wanted, so this can be
+  changed by setting queue_only_load_latch false. */
+
+  local_queue_only = session_local_queue_only;
+  if (!local_queue_only && queue_only_load >= 0)
+    {
+    local_queue_only = (load_average = OS_GETLOADAVG()) > queue_only_load;
+    if (local_queue_only)
       {
       {
-      local_queue_only = (load_average = os_getloadavg()) > queue_only_load;
-      if (local_queue_only) queue_only_reason = 3;
+      queue_only_reason = 3;
+      if (queue_only_load_latch) session_local_queue_only = TRUE;
       }
     }
 
       }
     }