Clean up compiler warnings from { gcc -Wall }, many of which I introduced with
[exim.git] / src / src / exim.c
index c38a58c54b4e017433a686eb9da65ca4bdc75c6c..50950faa500b5031ee19b8e5535f9671e45029b7 100644 (file)
@@ -1,10 +1,10 @@
-/* $Cambridge: exim/src/src/exim.c,v 1.13 2005/01/11 15:51:02 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    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2005 */
+/* Copyright (c) University of Cambridge 1995 - 2009 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -375,6 +375,38 @@ va_end(ap);
 
 
 
+/*************************************************
+*   Call fopen() with umask 777 and adjust mode  *
+*************************************************/
+
+/* Exim runs with umask(0) so that files created with open() have the mode that
+is specified in the open() call. However, there are some files, typically in
+the spool directory, that are created with fopen(). They end up world-writeable
+if no precautions are taken. Although the spool directory is not accessible to
+the world, this is an untidiness. So this is a wrapper function for fopen()
+that sorts out the mode of the created file.
+
+Arguments:
+   filename       the file name
+   options        the fopen() options
+   mode           the required mode
+
+Returns:          the fopened FILE or NULL
+*/
+
+FILE *
+modefopen(uschar *filename, char *options, mode_t mode)
+{
+mode_t saved_umask = umask(0777);
+FILE *f = Ufopen(filename, options);
+(void)umask(saved_umask);
+if (f != NULL) (void)fchmod(fileno(f), mode);
+return f;
+}
+
+
+
+
 /*************************************************
 *   Ensure stdin, stdout, and stderr exist       *
 *************************************************/
@@ -406,10 +438,10 @@ for (i = 0; i <= 2; i++)
     if (devnull < 0) devnull = open("/dev/null", O_RDWR);
     if (devnull < 0) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s",
       string_open_failed(errno, "/dev/null"));
-    if (devnull != i) dup2(devnull, i);
+    if (devnull != i) (void)dup2(devnull, i);
     }
   }
-if (devnull > 2) close(devnull);
+if (devnull > 2) (void)close(devnull);
 }
 
 
@@ -459,19 +491,19 @@ if (smtp_input)
   #ifdef SUPPORT_TLS
   tls_close(FALSE);      /* Shut down the TLS library */
   #endif
-  close(fileno(smtp_in));
-  close(fileno(smtp_out));
+  (void)close(fileno(smtp_in));
+  (void)close(fileno(smtp_out));
   smtp_in = NULL;
   }
 else
   {
-  close(0);                                           /* stdin */
-  if ((debug_selector & D_resolver) == 0)  close(1);  /* stdout */
-  if (debug_selector == 0)                            /* stderr */
+  (void)close(0);                                          /* stdin */
+  if ((debug_selector & D_resolver) == 0) (void)close(1);  /* stdout */
+  if (debug_selector == 0)                                 /* stderr */
     {
     if (!synchronous_delivery)
       {
-      close(2);
+      (void)close(2);
       log_stderr = NULL;
       }
     (void)setsid();
@@ -586,7 +618,8 @@ exit(rc);
 *************************************************/
 
 /* Called to extract the port from the values given to -oMa and -oMi.
-It also checks the syntax of the address.
+It also checks the syntax of the address, and terminates it before the
+port data when a port is extracted.
 
 Argument:
   address   the address, with possible port on the end
@@ -598,8 +631,8 @@ Returns:    the port, or zero if there isn't one
 static int
 check_port(uschar *address)
 {
-int port = host_extract_port(address);
-if (string_is_ip_address(address, NULL) == 0) 
+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);
@@ -648,146 +681,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.
-
-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
-  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, 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 "set all bits in both selectors"
-        in the case where two are being handled. However, the top bit in the
-        second selector is never set. */
-
-        if (bit == 0xffffffff)
-          {
-          *selector1 = adding? bit : 0;
-          if (selector2 != NULL) *selector2 = adding? 0x7fffffff : 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               *
 *************************************************/
 
-/* 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
@@ -817,18 +716,27 @@ fprintf(f, "Using tdb\n");
 #endif
 
 fprintf(f, "Support for:");
+#ifdef SUPPORT_CRYPTEQ
+  fprintf(f, " crypteq");
+#endif
 #if HAVE_ICONV
   fprintf(f, " iconv()");
 #endif
 #if HAVE_IPV6
   fprintf(f, " IPv6");
 #endif
+#ifdef HAVE_SETCLASSRESOURCES
+  fprintf(f, " use_setclassresources");
+#endif
 #ifdef SUPPORT_PAM
   fprintf(f, " PAM");
 #endif
 #ifdef EXIM_PERL
   fprintf(f, " Perl");
 #endif
+#ifdef EXPAND_DLFUNC
+  fprintf(f, " Expand_dlfunc");
+#endif
 #ifdef USE_TCP_WRAPPERS
   fprintf(f, " TCPwrappers");
 #endif
@@ -839,9 +747,18 @@ fprintf(f, "Support for:");
   fprintf(f, " OpenSSL");
   #endif
 #endif
+#ifdef SUPPORT_TRANSLATE_IP_ADDRESS
+  fprintf(f, " translate_ip_address");
+#endif
+#ifdef SUPPORT_MOVE_FROZEN_MESSAGES
+  fprintf(f, " move_frozen_messages");
+#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
@@ -854,6 +771,9 @@ fprintf(f, "Support for:");
 #ifdef EXPERIMENTAL_BRIGHTMAIL
   fprintf(f, " Experimental_Brightmail");
 #endif
+#ifdef EXPERIMENTAL_DCC
+  fprintf(f, " Experimental_DCC");
+#endif
 fprintf(f, "\n");
 
 fprintf(f, "Lookups:");
@@ -896,6 +816,9 @@ fprintf(f, "Lookups:");
 #ifdef LOOKUP_PGSQL
   fprintf(f, " pgsql");
 #endif
+#ifdef LOOKUP_SQLITE
+  fprintf(f, " sqlite");
+#endif
 #ifdef LOOKUP_TESTDB
   fprintf(f, " testdb");
 #endif
@@ -911,6 +834,9 @@ fprintf(f, "Authenticators:");
 #ifdef AUTH_CYRUS_SASL
   fprintf(f, " cyrus_sasl");
 #endif
+#ifdef AUTH_DOVECOT
+  fprintf(f, " dovecot");
+#endif
 #ifdef AUTH_PLAINTEXT
   fprintf(f, " plaintext");
 #endif
@@ -978,6 +904,16 @@ if (fixed_never_users[0] > 0)
     fprintf(f, "%d:", (unsigned int)fixed_never_users[i]);
   fprintf(f, "%d\n", (unsigned int)fixed_never_users[i]);
   }
+
+fprintf(f, "Size of off_t: " SIZE_T_FMT "\n", sizeof(off_t));
+
+/* This runtime check is to help diagnose library linkage mismatches which
+result in segfaults and the like; as such, it's left until the end,
+just in case.  There will still be a "Configuration file is" line still to
+come. */
+#ifdef SUPPORT_TLS
+tls_version_report(f);
+#endif
 }
 
 
@@ -1103,7 +1039,7 @@ int size = 0;
 int ptr = 0;
 uschar *yield = NULL;
 
-if (fn_readline == NULL) printf("> ");
+if (fn_readline == NULL) { printf("> "); fflush(stdout); }
 
 for (i = 0;; i++)
   {
@@ -1158,6 +1094,43 @@ 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);
+}
+
+
+
 /*************************************************
 *          Entry point and high-level code       *
 *************************************************/
@@ -1216,8 +1189,11 @@ BOOL more = TRUE;
 BOOL one_msg_action = 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 usage_wanted = FALSE;
 BOOL verify_address_mode = FALSE;
 BOOL verify_as_sender = FALSE;
 BOOL version_printed = FALSE;
@@ -1225,10 +1201,12 @@ uschar *alias_arg = NULL;
 uschar *called_as = US"";
 uschar *start_queue_run_id = NULL;
 uschar *stop_queue_run_id = NULL;
+uschar *expansion_test_message = NULL;
 uschar *ftest_domain = 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;
@@ -1256,6 +1234,12 @@ This is a feature to make the lives of binary distributors easier. */
 #ifdef EXIM_USERNAME
 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_gid = pw->pw_gid;
   }
 else
@@ -1401,10 +1385,17 @@ message_id_external[0] = 'E';
 message_id = message_id_external + 1;
 message_id[0] = 0;
 
-/* Set the umask to zero so that any files that Exim creates are created
-with the modes that it specifies. */
+/* Set the umask to zero so that any files Exim creates using open() are
+created with the modes that it specifies. NOTE: Files created with fopen() have
+a problem, which was not recognized till rather late (February 2006). With this
+umask, such files will be world writeable. (They are all content scanning files
+in the spool directory, which isn't world-accessible, so this is not a
+disaster, but it's untidy.) I don't want to change this overall setting,
+however, because it will interact badly with the open() calls. Instead, there's
+now a function called modefopen() that fiddles with the umask while calling
+fopen(). */
 
-umask(0);
+(void)umask(0);
 
 /* Precompile the regular expression for matching a message id. Keep this in
 step with the code that generates ids in the accept.c module. We need to do
@@ -1414,6 +1405,14 @@ using mac_ismsgid, which uses this. */
 regex_ismsgid =
   regex_must_compile(US"^(?:[^\\W_]{6}-){2}[^\\W_]{2}$", FALSE, TRUE);
 
+/* Precompile the regular expression that is used for matching an SMTP error
+code, possibly extended, at the start of an error message. Note that the
+terminating whitespace character is included. */
+
+regex_smtp_code =
+  regex_must_compile(US"^\\d\\d\\d\\s(?:\\d\\.\\d\\d?\\d?\\.\\d\\d?\\d?\\s)?",
+    FALSE, TRUE);
+
 /* 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. */
@@ -1496,11 +1495,6 @@ running in an unprivileged state. */
 
 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. */
@@ -1565,6 +1559,21 @@ for (i = 1; i < argc; i++)
     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)
@@ -1591,10 +1600,21 @@ for (i = 1; i < argc; i++)
         else if (*argrest != 0) { badarg = TRUE; break; }
       }
 
-    /* -be: Run in expansion test mode */
+    /* -be:  Run in expansion test mode
+       -bem: Ditto, but read a message from a file first
+    */
 
     else if (*argrest == 'e')
+      {
       expansion_test = checking = TRUE;
+      if (argrest[1] == 'm')
+        {
+        if (++i >= argc) { badarg = TRUE; break; }
+        expansion_test_message = argv[i];
+        argrest++;
+        }
+      if (argrest[1] != 0) { badarg = TRUE; break; }
+      }
 
     /* -bF:  Run system filter test */
 
@@ -1664,6 +1684,14 @@ for (i = 1; i < argc; i++)
 
     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. */
@@ -1890,7 +1918,7 @@ for (i = 1; i < argc; i++)
     break;
 
     /* -d: Set debug level (see also -v below) or set the drop_cr option.
-    The latter is now a no-op, retained for compatibility only. If -dd is used, 
+    The latter is now a no-op, retained for compatibility only. If -dd is used,
     debugging subprocesses of the daemon is disabled. */
 
     case 'd':
@@ -1913,8 +1941,8 @@ for (i = 1; i < argc; i++)
         argrest++;
         }
       if (*argrest != 0)
-        decode_bits(&selector, NULL, argrest, debug_options,
-          debug_options_count, US"debug");
+        decode_bits(&selector, NULL, D_memory, 0, argrest, debug_options,
+          debug_options_count, US"debug", 0);
       debug_selector = selector;
       }
     break;
@@ -1965,6 +1993,7 @@ for (i = 1; i < argc; i++)
         { badarg = TRUE; break; }
       }
     originator_name = argrest;
+    sender_name_forced = TRUE;
     break;
 
 
@@ -2062,6 +2091,9 @@ for (i = 1; i < argc; i++)
 
     if (Ustrcmp(argrest, "C") == 0)
       {
+      union sockaddr_46 interface_sock;
+      EXIM_SOCKLEN_T size = sizeof(interface_sock);
+
       if (argc != i + 6)
         {
         fprintf(stderr, "exim: too many or too few arguments after -MC\n");
@@ -2091,6 +2123,19 @@ for (i = 1; i < argc; i++)
         return EXIT_FAILURE;
         }
 
+      /* Set up $sending_ip_address and $sending_port */
+
+      if (getsockname(fileno(stdin), (struct sockaddr *)(&interface_sock),
+          &size) == 0)
+        sending_ip_address = host_ntoa(-1, &interface_sock, NULL,
+          &sending_port);
+      else
+        {
+        fprintf(stderr, "exim: getsockname() failed after -MC option: %s\n",
+          strerror(errno));
+        return EXIT_FAILURE;
+        }
+
       if (running_in_test_harness) millisleep(500);
       break;
       }
@@ -2162,7 +2207,9 @@ for (i = 1; i < argc; i++)
        -Mmad mark all recipients delivered
        -Mmd  mark recipients(s) delivered
        -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
     */
@@ -2199,12 +2246,22 @@ for (i = 1; i < argc; i++)
       one_msg_action = TRUE;
       }
     else if (Ustrcmp(argrest, "rm") == 0) msg_action = MSG_REMOVE;
+    else if (Ustrcmp(argrest, "set") == 0)
+      {
+      msg_action = MSG_LOAD;
+      one_msg_action = TRUE;
+      }
     else if (Ustrcmp(argrest, "t") == 0)  msg_action = MSG_THAW;
     else if (Ustrcmp(argrest, "vb") == 0)
       {
       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;
@@ -2434,7 +2491,11 @@ for (i = 1; i < argc; i++)
 
       /* -oMt: Set sender ident */
 
-      else if (Ustrcmp(argrest, "Mt") == 0) sender_ident = argv[++i];
+      else if (Ustrcmp(argrest, "Mt") == 0)
+        {
+        sender_ident_set = TRUE;
+        sender_ident = argv[++i];
+        }
 
       /* Else a bad argument */
 
@@ -2534,6 +2595,11 @@ 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);
+      }
 
     /* -qq...: Do queue runs in a 2-stage manner */
 
@@ -2642,7 +2708,6 @@ for (i = 1; i < argc; i++)
         }
       }
     else deliver_selectstring = argrest;
-    if (queue_interval < 0) queue_interval = 0;
     break;
 
 
@@ -2690,7 +2755,6 @@ for (i = 1; i < argc; i++)
         }
       }
     else deliver_selectstring_sender = argrest;
-    if (queue_interval < 0) queue_interval = 0;
     break;
 
     /* -Tqt is an option that is exclusively for use by the testing suite.
@@ -2781,9 +2845,17 @@ for (i = 1; i < argc; i++)
   }
 
 
-/* Arguments have been processed. Check for incompatibilities. */
+/* If -R or -S have been specified without -q, assume a single queue run. */
+
+if ((deliver_selectstring != NULL || deliver_selectstring_sender != NULL) &&
+  queue_interval < 0) queue_interval = 0;
+
 
 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 ||
@@ -2792,13 +2864,14 @@ if ((
     ) ||
     (
     msg_action_arg > 0 &&
-    (daemon_listen || queue_interval >= 0 || list_options || checking ||
-     bi_option || test_retry_arg >= 0 || test_rewrite_arg >= 0)
+    (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) &&
     (sender_address != NULL || list_options || list_queue || checking ||
-     bi_option)
+      bi_option)
     ) ||
     (
     daemon_listen && queue_interval == 0
@@ -2823,6 +2896,10 @@ if ((
     ) ||
     (
     deliver_selectstring != NULL && queue_interval < 0
+    ) ||
+    (
+    msg_action == MSG_LOAD &&
+      (!expansion_test || expansion_test_message != NULL)
     )
    )
   {
@@ -3034,8 +3111,8 @@ readconf_main();
 
 /* Handle the decoding of logging options. */
 
-decode_bits(&log_write_selector, &log_extra_selector, 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)
   {
@@ -3231,8 +3308,8 @@ 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 ((log_extra_selector & LX_arguments) != 0 && really_exim
-     && !list_options && !checking)
+if (((debug_selector & D_any) != 0 || (log_extra_selector & LX_arguments) != 0)
+      && really_exim && !list_options && !checking)
   {
   int i;
   uschar *p = big_buffer;
@@ -3264,18 +3341,23 @@ if ((log_extra_selector & LX_arguments) != 0 && really_exim
       (p - big_buffer) - 4), printing, quote);
     while (*p) p++;
     }
-  log_write(0, LOG_MAIN, "%s", big_buffer);
+
+  if ((log_extra_selector & LX_arguments) != 0)
+    log_write(0, LOG_MAIN, "%s", big_buffer);
+  else
+    debug_printf("%s\n", big_buffer);
   }
 
 /* Set the working directory to be the top-level spool directory. We don't rely
 on this in the code, which always uses fully qualified names, but it's useful
 for core dumps etc. Don't complain if it fails - the spool directory might not
 be generally accessible and calls with the -C option (and others) have lost
-privilege by now. */
+privilege by now. Before the chdir, we try to ensure that the directory exists.
+*/
 
 if (Uchdir(spool_directory) != 0)
   {
-  (void)directory_make(spool_directory, US"", SPOOL_DIRECTORY_MODE, TRUE);
+  (void)directory_make(spool_directory, US"", SPOOL_DIRECTORY_MODE, FALSE);
   (void)Uchdir(spool_directory);
   }
 
@@ -3287,7 +3369,7 @@ script. */
 
 if (bi_option)
   {
-  fclose(config_file);
+  (void)fclose(config_file);
   if (bi_command != NULL)
     {
     int i = 0;
@@ -3326,7 +3408,6 @@ if (real_uid == root_uid || real_uid == exim_uid || real_gid == exim_gid)
 else
   {
   int i, j;
-
   for (i = 0; i < group_count; i++)
     {
     if (group_list[i] == exim_gid) admin_user = TRUE;
@@ -3382,12 +3463,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
-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 (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) ||
@@ -3447,7 +3529,7 @@ barf. */
 if (smtp_input)
   {
   union sockaddr_46 inetd_sock;
-  SOCKLEN_T size = sizeof(inetd_sock);
+  EXIM_SOCKLEN_T size = sizeof(inetd_sock);
   if (getpeername(0, (struct sockaddr *)(&inetd_sock), &size) == 0)
     {
     int family = ((struct sockaddr *)(&inetd_sock))->sa_family;
@@ -3490,7 +3572,7 @@ if (receiving_message &&
         (is_inetd && smtp_load_reserve >= 0)
       ))
   {
-  load_average = os_getloadavg();
+  load_average = OS_GETLOADAVG();
   }
 #endif
 
@@ -3513,7 +3595,9 @@ root privilege above as a result of -C, -D, -be, -bf or -bF, remove it now
 except when starting the daemon or doing some kind of delivery or address
 testing (-bt). These are the only cases when root need to be retained. We run
 as exim for -bv and -bh. However, if deliver_drop_privilege is set, root is
-retained only for starting the daemon. */
+retained only for starting the daemon. We always do the initgroups() in this
+situation (controlled by the TRUE below), in order to be as close as possible
+to the state Exim usually runs in. */
 
 if (!unprivileged &&                      /* originally had root AND */
     !removed_privilege &&                 /* still got root AND      */
@@ -3529,13 +3613,40 @@ if (!unprivileged &&                      /* originally had root AND */
         )
       ))
   {
-  exim_setugid(exim_uid, exim_gid, FALSE, US"privilege not needed");
+  exim_setugid(exim_uid, exim_gid, TRUE, US"privilege not needed");
   }
 
 /* When we are retaining a privileged uid, we still change to the exim gid. */
 
 else setgid(exim_gid);
 
+/* 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 */
 
 if (list_queue)
@@ -3554,12 +3665,12 @@ if (count_queue)
   exit(EXIT_SUCCESS);
   }
 
-/* Handle actions on specific messages, except for the force delivery action,
-which is done below. Some actions take a whole list of message ids, which
-are known to continue up to the end of the arguments. Others take a single
-message id and then operate on the recipients list. */
+/* Handle actions on specific messages, except for the force delivery and
+message load actions, which are done below. Some actions take a whole list of
+message ids, which are known to continue up to the end of the arguments. Others
+take a single message id and then operate on the recipients list. */
 
-if (msg_action_arg > 0 && msg_action != MSG_DELIVER)
+if (msg_action_arg > 0 && msg_action != MSG_DELIVER && msg_action != MSG_LOAD)
   {
   int yield = EXIT_SUCCESS;
   set_process_info("acting on specified messages");
@@ -3641,11 +3752,13 @@ if (test_retry_arg >= 0)
       return EXIT_FAILURE;
       }
 
-    /* For the rcpt_4xx errors, a value of 255 means "any", and a code > 100 as
-    an error is for matching codes to the decade. Turn them into a real error
-    code, off the decade. */
+    /* For the {MAIL,RCPT,DATA}_4xx errors, a value of 255 means "any", and a
+    code > 100 as an error is for matching codes to the decade. Turn them into
+    a real error code, off the decade. */
 
-    if (basic_errno == ERRNO_RCPT4XX)
+    if (basic_errno == ERRNO_MAIL4XX ||
+        basic_errno == ERRNO_RCPT4XX ||
+        basic_errno == ERRNO_DATA4XX)
       {
       int code = (more_errno >> 8) & 255;
       if (code == 255)
@@ -3725,7 +3838,8 @@ if (list_options)
       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++;
@@ -3737,16 +3851,19 @@ if (list_options)
 
 
 /* Handle a request to deliver one or more messages that are already on the
-queue. Values of msg_action other than MSG_DELIVER are dealt with above. This
-is typically used for a small number when prodding by hand (when the option
-forced_delivery will be set) or when re-execing to regain root privilege.
-Each message delivery must happen in a separate process, so we fork a process
-for each one, and run them sequentially so that debugging output doesn't get
-intertwined, and to avoid spawning too many processes if a long list is given.
-However, don't fork for the last one; this saves a process in the common case
-when Exim is called to deliver just one message. */
-
-if (msg_action_arg > 0)
+queue. Values of msg_action other than MSG_DELIVER and MSG_LOAD are dealt with
+above. MSG_LOAD is handled with -be (which is the only time it applies) below.
+
+Delivery of specific messages is typically used for a small number when
+prodding by hand (when the option forced_delivery will be set) or when
+re-execing to regain root privilege. Each message delivery must happen in a
+separate process, so we fork a process for each one, and run them sequentially
+so that debugging output doesn't get intertwined, and to avoid spawning too
+many processes if a long list is given. However, don't fork for the last one;
+this saves a process in the common case when Exim is called to deliver just one
+message. */
+
+if (msg_action_arg > 0 && msg_action != MSG_LOAD)
   {
   if (prod_requires_admin && !admin_user)
     {
@@ -3879,7 +3996,7 @@ for (i = 0;;)
 
 /* If we cannot get a user login, log the incident and give up, unless the
 configuration specifies something to use. When running in the test harness,
-any setting of unknown_login overrides the actual login name. */
+any setting of unknown_login overrides the actual name. */
 
 if (originator_login == NULL || running_in_test_harness)
   {
@@ -3913,12 +4030,17 @@ DEBUG(D_receive) debug_printf("originator: uid=%d gid=%d login=%s name=%s\n",
 
 /* Run in daemon and/or queue-running mode. The function daemon_go() never
 returns. We leave this till here so that the originator_ fields are available
-for incoming messages via the daemon. */
+for incoming messages via the daemon. The daemon cannot be run in mua_wrapper
+mode. */
 
 if (daemon_listen || queue_interval > 0)
   {
-  if (mua_wrapper) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Daemon cannot be "
-    "run when mua_wrapper is set");
+  if (mua_wrapper)
+    {
+    fprintf(stderr, "Daemon cannot be run when mua_wrapper is set\n");
+    log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Daemon cannot be run when "
+      "mua_wrapper is set");
+    }
   daemon_go();
   }
 
@@ -3955,12 +4077,14 @@ if ((sender_address == NULL && !smtp_input) ||
   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. */
+  via -oMas and -oMai and if so, they will already be set. Otherwise, force
+  defaults except when host checking. */
 
-  if (authenticated_sender == NULL)
+  if (authenticated_sender == NULL && !host_checking)
     authenticated_sender = string_sprintf("%s@%s", originator_login,
       qualify_domain_sender);
-  if (authenticated_id == NULL) authenticated_id = originator_login;
+  if (authenticated_id == NULL && !host_checking)
+    authenticated_id = originator_login;
   }
 
 /* Trusted callers are always permitted to specify the sender address.
@@ -4059,18 +4183,65 @@ if (verify_address_mode || address_test_mode)
   exim_exit(exit_value);
   }
 
-/* Handle expansion checking */
+/* Handle expansion checking. Either expand items on the command line, or read
+from stdin if there aren't any. If -Mset was specified, load the message so
+that its variables can be used, but restrict this facility to admin users.
+Otherwise, if -bem was used, read a message from stdin. */
 
 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);
+      }
+    message_id = argv[msg_action_arg];
+    (void)string_format(spoolname, sizeof(spoolname), "%s-H", message_id);
+    if (!spool_open_datafile(message_id))
+      printf ("Failed to load message datafile %s\n", message_id);
+    if (spool_read_header(spoolname, TRUE, FALSE) != spool_read_OK)
+      printf ("Failed to load message %s\n", message_id);
+    }
+
+  /* Read a test message from a file. We fudge it up to be on stdin, saving
+  stdin itself for later reading of expansion strings. */
+
+  else if (expansion_test_message != NULL)
+    {
+    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,
+        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;
+    read_message_body(receive_msg(extract_recipients));
+    message_linecount += body_linecount;
+    (void)dup2(save_stdin, 0);
+    (void)close(save_stdin);
+    clearerr(stdin);               /* Required by Darwin */
+    }
+
+  /* Allow $recipients for this testing */
+
+  enable_dollar_recipients = TRUE;
+
+  /* Expand command line items */
+
   if (recipients_arg < argc)
     {
     while (recipients_arg < argc)
       {
       uschar *s = argv[recipients_arg++];
       uschar *ss = expand_string(s);
-      if (ss == NULL)
-        printf ("Failed: %s\n", expand_string_message);
+      if (ss == NULL) printf ("Failed: %s\n", expand_string_message);
       else printf("%s\n", CS ss);
       }
     }
@@ -4102,6 +4273,14 @@ if (expansion_test)
     #endif
     }
 
+  /* The data file will be open after -Mset */
+
+  if (deliver_datafile >= 0)
+    {
+    (void)close(deliver_datafile);
+    deliver_datafile = -1;
+    }
+
   exim_exit(EXIT_SUCCESS);
   }
 
@@ -4125,24 +4304,28 @@ if (raw_active_hostname != NULL)
   }
 
 /* Handle host checking: this facility mocks up an incoming SMTP call from a
-given IP address so that the blocking and relay configuration can be tested. An
-RFC 1413 call is made only if we are running in the test harness and an
-incoming interface and both ports are specified, because there is no TCP/IP
-call to find the ident for. */
+given IP address so that the blocking and relay configuration can be tested.
+Unless a sender_ident was set by -oMt, we discard it (the default is the
+caller's login name). An RFC 1413 call is made only if we are running in the
+test harness and an incoming interface and both ports are specified, because
+there is no TCP/IP call to find the ident for. */
 
 if (host_checking)
   {
-  int x[4]; 
+  int x[4];
   int size;
-    
-  sender_ident = NULL;
-  if (running_in_test_harness && sender_host_port != 0 &&
-      interface_address != NULL && interface_port != 0)
-    verify_get_ident(1413);
-    
+
+  if (!sender_ident_set)
+    {
+    sender_ident = NULL;
+    if (running_in_test_harness && sender_host_port != 0 &&
+        interface_address != NULL && interface_port != 0)
+      verify_get_ident(1413);
+    }
+
   /* In case the given address is a non-canonical IPv6 address, canonicize
   it. The code works for both IPv4 and IPv6, as it happens. */
-  
+
   size = host_aton(sender_host_address, x);
   sender_host_address = store_get(48);  /* large enough for full IPv6 */
   (void)host_nmtoa(size, x, -1, sender_host_address, ':');
@@ -4166,6 +4349,11 @@ if (host_checking)
     log_write_selector &= ~L_smtp_connection;
   log_write(L_smtp_connection, LOG_MAIN, "%s", smtp_get_connection_info());
 
+  /* NOTE: We do *not* call smtp_log_no_mail() if smtp_start_session() fails,
+  because a log line has already been written for all its failure exists
+  (usually "connection refused: <reason>") and writing another one is
+  unnecessary clutter. */
+
   if (smtp_start_session())
     {
     reset_point = store_get(0);
@@ -4175,6 +4363,7 @@ if (host_checking)
       if (smtp_setup_msg() <= 0) break;
       if (!receive_msg(FALSE)) break;
       }
+    smtp_log_no_mail();
     }
   exim_exit(EXIT_SUCCESS);
   }
@@ -4191,14 +4380,9 @@ if (recipients_arg >= argc && !extract_recipients && !smtp_input)
     printf("Configuration file is %s\n", config_main_filename);
     return EXIT_SUCCESS;
     }
+
   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);
   }
 
 
@@ -4244,7 +4428,7 @@ sender_ident. */
 
 else if (is_inetd)
   {
-  fclose(stderr);
+  (void)fclose(stderr);
   exim_nullstd();                       /* Re-open to /dev/null */
   verify_get_ident(IDENT_PORT);
   host_build_sender_fullhost();
@@ -4274,18 +4458,19 @@ else if (!is_inetd) sender_host_unknown = TRUE;
 if exim is started from inetd. In this case fd 0 will be set to the socket,
 but fd 1 will not be set. This also happens for passed SMTP channels. */
 
-if (fstat(1, &statbuf) < 0) dup2(0, 1);
+if (fstat(1, &statbuf) < 0) (void)dup2(0, 1);
 
-/* Set up the incoming protocol name and the state of the program. Root
-is allowed to force received protocol via the -oMr option above, and if we are
-in a non-local SMTP state it means we have come via inetd and the process info
-has already been set up. We don't set received_protocol here for smtp input,
-as it varies according to batch/HELO/EHLO/AUTH/TLS. */
+/* Set up the incoming protocol name and the state of the program. Root is
+allowed to force received protocol via the -oMr option above. If we have come
+via inetd, the process info has already been set up. We don't set
+received_protocol here for smtp input, as it varies according to
+batch/HELO/EHLO/AUTH/TLS. */
 
 if (smtp_input)
   {
-  if (sender_local) set_process_info("accepting a local SMTP message from <%s>",
-    sender_address);
+  if (!is_inetd) set_process_info("accepting a local %sSMTP message from <%s>",
+    smtp_batched_input? "batched " : "",
+    (sender_address!= NULL)? sender_address : originator_login);
   }
 else
   {
@@ -4295,11 +4480,11 @@ else
     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();
-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
@@ -4312,8 +4497,13 @@ if ((!smtp_input || smtp_batched_input) && !receive_check_fs(0))
   return EXIT_FAILURE;
   }
 
-/* If this is smtp input of any kind, handle the start of the SMTP
-session. */
+/* If this is smtp input of any kind, real or batched, handle the start of the
+SMTP session.
+
+NOTE: We do *not* call smtp_log_no_mail() if smtp_start_session() fails,
+because a log line has already been written for all its failure exists
+(usually "connection refused: <reason>") and writing another one is
+unnecessary clutter. */
 
 if (smtp_input)
   {
@@ -4329,12 +4519,12 @@ if (smtp_input)
     }
   }
 
-/* Otherwise, set up the input size limit here */
+/* Otherwise, set up the input size limit here. */
 
 else
   {
-  thismessage_size_limit = expand_string_integer(message_size_limit);
-  if (thismessage_size_limit < 0)
+  thismessage_size_limit = expand_string_integer(message_size_limit, TRUE);
+  if (expand_string_message != NULL)
     {
     if (thismessage_size_limit == -1)
       log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to expand "
@@ -4401,20 +4591,13 @@ while (more)
   store_reset(reset_point);
   message_id[0] = 0;
 
-  /* In the SMTP case, we have to handle the initial SMTP input and build the
-  recipients list, before calling receive_msg() to read the message proper.
-  Whatever sender address is actually given in the SMTP transaction is
-  actually ignored for local senders - we use the actual sender, which is
-  normally either the underlying user running this process or a -f argument
-  provided by a trusted caller. It is saved in real_sender_address.
-
-  However, if this value is NULL, we are dealing with a trusted caller when
-  -f was not used; in this case, the SMTP sender is allowed to stand.
-
-  Also, if untrusted_set_sender is set, we permit sender addresses that match
-  anything in its list.
-
-  The variable raw_sender_address holds the sender address before rewriting. */
+  /* Handle the SMTP case; call smtp_setup_mst() to deal with the initial SMTP
+  input and build the recipients list, before calling receive_msg() to read the
+  message proper. Whatever sender address is given in the SMTP transaction is
+  often ignored for local senders - we use the actual sender, which is normally
+  either the underlying user running this process or a -f argument provided by
+  a trusted caller. It is saved in real_sender_address. The test for whether to
+  accept the SMTP sender is encapsulated in receive_check_set_sender(). */
 
   if (smtp_input)
     {
@@ -4427,14 +4610,36 @@ while (more)
         sender_address = raw_sender = real_sender_address;
         sender_address_unrewritten = NULL;
         }
+
+      /* For batched SMTP, we have to run the acl_not_smtp_start ACL, since it
+      isn't really SMTP, so no other ACL will run until the acl_not_smtp one at
+      the very end. The result of the ACL is ignored (as for other non-SMTP
+      messages). It is run for its potential side effects. */
+
+      if (smtp_batched_input && acl_not_smtp_start != NULL)
+        {
+        uschar *user_msg, *log_msg;
+        enable_dollar_recipients = TRUE;
+        (void)acl_check(ACL_WHERE_NOTSMTP_START, NULL, acl_not_smtp_start,
+          &user_msg, &log_msg);
+        enable_dollar_recipients = FALSE;
+        }
+
+      /* Now get the data for the message */
+
       more = receive_msg(extract_recipients);
       if (message_id[0] == 0)
         {
         if (more) continue;
+        smtp_log_no_mail();               /* Log no mail if configured */
         exim_exit(EXIT_FAILURE);
         }
       }
-    else exim_exit((rc == 0)? EXIT_SUCCESS : EXIT_FAILURE);
+    else
+      {
+      smtp_log_no_mail();               /* Log no mail if configured */
+      exim_exit((rc == 0)? EXIT_SUCCESS : EXIT_FAILURE);
+      }
     }
 
   /* In the non-SMTP case, we have all the information from the command
@@ -4545,6 +4750,19 @@ while (more)
         }
       }
 
+    /* Run the acl_not_smtp_start ACL if required. The result of the ACL is
+    ignored; rejecting here would just add complication, and it can just as
+    well be done later. Allow $recipients to be visible in the ACL. */
+
+    if (acl_not_smtp_start != NULL)
+      {
+      uschar *user_msg, *log_msg;
+      enable_dollar_recipients = TRUE;
+      (void)acl_check(ACL_WHERE_NOTSMTP_START, NULL, acl_not_smtp_start,
+        &user_msg, &log_msg);
+      enable_dollar_recipients = FALSE;
+      }
+
     /* Read the data for the message. If filter_test is not FTEST_NONE, this
     will just read the headers for the message, and not write anything onto the
     spool. */
@@ -4600,52 +4818,61 @@ while (more)
     if (ftest_prefix != NULL) printf("Prefix    = %s\n", ftest_prefix);
     if (ftest_suffix != NULL) printf("Suffix    = %s\n", ftest_suffix);
 
-    chdir("/");   /* Get away from wherever the user is running this from */
-    
-    /* Now we run either a system filter test, or a user filter test, or both. 
-    In the latter case, headers added by the system filter will persist and be 
-    available to the user filter. We need to copy the filter variables 
+    (void)chdir("/");   /* Get away from wherever the user is running this from */
+
+    /* Now we run either a system filter test, or a user filter test, or both.
+    In the latter case, headers added by the system filter will persist and be
+    available to the user filter. We need to copy the filter variables
     explicitly. */
-    
+
     if ((filter_test & FTEST_SYSTEM) != 0)
       {
       if (!filter_runtest(filter_sfd, filter_test_sfile, TRUE, more))
         exim_exit(EXIT_FAILURE);
-      }      
-      
+      }
+
     memcpy(filter_sn, filter_n, sizeof(filter_sn));
-      
+
     if ((filter_test & FTEST_USER) != 0)
       {
       if (!filter_runtest(filter_ufd, filter_test_ufile, FALSE, more))
         exim_exit(EXIT_FAILURE);
-      }      
-      
+      }
+
     exim_exit(EXIT_SUCCESS);
     }
 
   /* 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;
       }
     }