Magnus' patch for $sending_ip_address and $sending_port.
[exim.git] / src / src / exim.c
index 44e0a9a14691b7766c04f0933ad4cf934703ea24..49830c0de94c82292a11ec99a4987d50531ea62f 100644 (file)
@@ -1,10 +1,10 @@
-/* $Cambridge: exim/src/src/exim.c,v 1.35 2006/02/22 14:46:44 ph10 Exp $ */
+/* $Cambridge: exim/src/src/exim.c,v 1.55 2007/01/30 15:10:59 ph10 Exp $ */
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2006 */
+/* Copyright (c) University of Cambridge 1995 - 2007 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -397,10 +397,9 @@ Returns:          the fopened FILE or NULL
 FILE *
 modefopen(uschar *filename, char *options, mode_t mode)
 {
 FILE *
 modefopen(uschar *filename, char *options, mode_t mode)
 {
-FILE *f;
-umask(0777);
-f = Ufopen(filename, options);
-umask(0);
+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;
 }
 if (f != NULL) (void)fchmod(fileno(f), mode);
 return f;
 }
@@ -875,8 +874,8 @@ fprintf(f, "Support for:");
 #if HAVE_IPV6
   fprintf(f, " IPv6");
 #endif
 #if HAVE_IPV6
   fprintf(f, " IPv6");
 #endif
-#ifdef HAVE_LOGIN_CAP
-  fprintf(f, " use_classresources");
+#ifdef HAVE_SETCLASSRESOURCES
+  fprintf(f, " use_setclassresources");
 #endif
 #ifdef SUPPORT_PAM
   fprintf(f, " PAM");
 #endif
 #ifdef SUPPORT_PAM
   fprintf(f, " PAM");
@@ -981,6 +980,9 @@ fprintf(f, "Authenticators:");
 #ifdef AUTH_CYRUS_SASL
   fprintf(f, " cyrus_sasl");
 #endif
 #ifdef AUTH_CYRUS_SASL
   fprintf(f, " cyrus_sasl");
 #endif
+#ifdef AUTH_DOVECOT
+  fprintf(f, " dovecot");
+#endif
 #ifdef AUTH_PLAINTEXT
   fprintf(f, " plaintext");
 #endif
 #ifdef AUTH_PLAINTEXT
   fprintf(f, " plaintext");
 #endif
@@ -1175,7 +1177,7 @@ int size = 0;
 int ptr = 0;
 uschar *yield = NULL;
 
 int ptr = 0;
 uschar *yield = NULL;
 
-if (fn_readline == NULL) printf("> ");
+if (fn_readline == NULL) { printf("> "); fflush(stdout); }
 
 for (i = 0;; i++)
   {
 
 for (i = 0;; i++)
   {
@@ -1288,6 +1290,7 @@ BOOL more = TRUE;
 BOOL one_msg_action = FALSE;
 BOOL queue_only_set = FALSE;
 BOOL receiving_message = TRUE;
 BOOL one_msg_action = FALSE;
 BOOL queue_only_set = FALSE;
 BOOL receiving_message = TRUE;
+BOOL sender_ident_set = FALSE;
 BOOL unprivileged;
 BOOL removed_privilege = FALSE;
 BOOL verify_address_mode = FALSE;
 BOOL unprivileged;
 BOOL removed_privilege = FALSE;
 BOOL verify_address_mode = FALSE;
@@ -1297,6 +1300,7 @@ uschar *alias_arg = NULL;
 uschar *called_as = US"";
 uschar *start_queue_run_id = NULL;
 uschar *stop_queue_run_id = 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_domain = NULL;
 uschar *ftest_localpart = NULL;
 uschar *ftest_prefix = NULL;
@@ -1473,7 +1477,7 @@ message_id_external[0] = 'E';
 message_id = message_id_external + 1;
 message_id[0] = 0;
 
 message_id = message_id_external + 1;
 message_id[0] = 0;
 
-/* Set the umask to zero so that any files that Exim creates using open() are
+/* 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
 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
@@ -1483,7 +1487,7 @@ 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(). */
 
 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
 
 /* 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
@@ -1493,6 +1497,14 @@ using mac_ismsgid, which uses this. */
 regex_ismsgid =
   regex_must_compile(US"^(?:[^\\W_]{6}-){2}[^\\W_]{2}$", FALSE, TRUE);
 
 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. */
 /* 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. */
@@ -1670,10 +1682,21 @@ for (i = 1; i < argc; i++)
         else if (*argrest != 0) { badarg = TRUE; break; }
       }
 
         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')
 
     else if (*argrest == 'e')
+      {
       expansion_test = checking = TRUE;
       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 */
 
 
     /* -bF:  Run system filter test */
 
@@ -2142,6 +2165,9 @@ for (i = 1; i < argc; i++)
 
     if (Ustrcmp(argrest, "C") == 0)
       {
 
     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");
       if (argc != i + 6)
         {
         fprintf(stderr, "exim: too many or too few arguments after -MC\n");
@@ -2171,6 +2197,19 @@ for (i = 1; i < argc; i++)
         return EXIT_FAILURE;
         }
 
         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;
       }
       if (running_in_test_harness) millisleep(500);
       break;
       }
@@ -2242,6 +2281,7 @@ for (i = 1; i < argc; i++)
        -Mmad mark all recipients delivered
        -Mmd  mark recipients(s) delivered
        -Mes  edit sender
        -Mmad mark all recipients delivered
        -Mmd  mark recipients(s) delivered
        -Mes  edit sender
+       -Mset load a message for use with -be
        -Mvb  show body
        -Mvh  show header
        -Mvl  show log
        -Mvb  show body
        -Mvh  show header
        -Mvl  show log
@@ -2279,6 +2319,11 @@ for (i = 1; i < argc; i++)
       one_msg_action = TRUE;
       }
     else if (Ustrcmp(argrest, "rm") == 0) msg_action = MSG_REMOVE;
       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)
       {
     else if (Ustrcmp(argrest, "t") == 0)  msg_action = MSG_THAW;
     else if (Ustrcmp(argrest, "vb") == 0)
       {
@@ -2514,7 +2559,11 @@ for (i = 1; i < argc; i++)
 
       /* -oMt: Set sender ident */
 
 
       /* -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 */
 
 
       /* Else a bad argument */
 
@@ -2614,6 +2663,11 @@ for (i = 1; i < argc; i++)
 
     case 'q':
     receiving_message = FALSE;
 
     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 */
 
 
     /* -qq...: Do queue runs in a 2-stage manner */
 
@@ -2722,7 +2776,6 @@ for (i = 1; i < argc; i++)
         }
       }
     else deliver_selectstring = argrest;
         }
       }
     else deliver_selectstring = argrest;
-    if (queue_interval < 0) queue_interval = 0;
     break;
 
 
     break;
 
 
@@ -2770,7 +2823,6 @@ for (i = 1; i < argc; i++)
         }
       }
     else deliver_selectstring_sender = argrest;
         }
       }
     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.
     break;
 
     /* -Tqt is an option that is exclusively for use by the testing suite.
@@ -2861,6 +2913,12 @@ for (i = 1; i < argc; i++)
   }
 
 
   }
 
 
+/* 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;
+
+
 /* Arguments have been processed. Check for incompatibilities. */
 
 END_ARG:
 /* Arguments have been processed. Check for incompatibilities. */
 
 END_ARG:
@@ -2872,13 +2930,14 @@ if ((
     ) ||
     (
     msg_action_arg > 0 &&
     ) ||
     (
     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 ||
     ) ||
     (
     (daemon_listen || queue_interval >= 0) &&
     (sender_address != NULL || list_options || list_queue || checking ||
-     bi_option)
+      bi_option)
     ) ||
     (
     daemon_listen && queue_interval == 0
     ) ||
     (
     daemon_listen && queue_interval == 0
@@ -2903,6 +2962,10 @@ if ((
     ) ||
     (
     deliver_selectstring != NULL && queue_interval < 0
     ) ||
     (
     deliver_selectstring != NULL && queue_interval < 0
+    ) ||
+    (
+    msg_action == MSG_LOAD &&
+      (!expansion_test || expansion_test_message != NULL)
     )
    )
   {
     )
    )
   {
@@ -3598,7 +3661,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
 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      */
 
 if (!unprivileged &&                      /* originally had root AND */
     !removed_privilege &&                 /* still got root AND      */
@@ -3614,7 +3679,7 @@ 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. */
   }
 
 /* When we are retaining a privileged uid, we still change to the exim gid. */
@@ -3639,12 +3704,12 @@ if (count_queue)
   exit(EXIT_SUCCESS);
   }
 
   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");
   {
   int yield = EXIT_SUCCESS;
   set_process_info("acting on specified messages");
@@ -3726,11 +3791,13 @@ if (test_retry_arg >= 0)
       return EXIT_FAILURE;
       }
 
       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)
       {
       int code = (more_errno >> 8) & 255;
       if (code == 255)
@@ -3822,16 +3889,19 @@ if (list_options)
 
 
 /* Handle a request to deliver one or more messages that are already on the
 
 
 /* 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)
     {
   {
   if (prod_requires_admin && !admin_user)
     {
@@ -4045,12 +4115,14 @@ if ((sender_address == NULL && !smtp_input) ||
   sender_local = TRUE;
 
   /* A trusted caller can supply authenticated_sender and authenticated_id
   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);
     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.
   }
 
 /* Trusted callers are always permitted to specify the sender address.
@@ -4149,18 +4221,65 @@ if (verify_address_mode || address_test_mode)
   exim_exit(exit_value);
   }
 
   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 (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 (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);
       }
     }
       else printf("%s\n", CS ss);
       }
     }
@@ -4192,6 +4311,14 @@ if (expansion_test)
     #endif
     }
 
     #endif
     }
 
+  /* The data file will be open after -Mset */
+
+  if (deliver_datafile >= 0)
+    {
+    (void)close(deliver_datafile);
+    deliver_datafile = -1;
+    }
+
   exim_exit(EXIT_SUCCESS);
   }
 
   exim_exit(EXIT_SUCCESS);
   }
 
@@ -4215,20 +4342,24 @@ if (raw_active_hostname != NULL)
   }
 
 /* Handle host checking: this facility mocks up an incoming SMTP call from a
   }
 
 /* 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 size;
 
 
 if (host_checking)
   {
   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. */
 
   /* In case the given address is a non-canonical IPv6 address, canonicize
   it. The code works for both IPv4 and IPv6, as it happens. */
@@ -4256,6 +4387,11 @@ if (host_checking)
     log_write_selector &= ~L_smtp_connection;
   log_write(L_smtp_connection, LOG_MAIN, "%s", smtp_get_connection_info());
 
     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);
   if (smtp_start_session())
     {
     reset_point = store_get(0);
@@ -4265,6 +4401,7 @@ if (host_checking)
       if (smtp_setup_msg() <= 0) break;
       if (!receive_msg(FALSE)) break;
       }
       if (smtp_setup_msg() <= 0) break;
       if (!receive_msg(FALSE)) break;
       }
+    smtp_log_no_mail();
     }
   exim_exit(EXIT_SUCCESS);
   }
     }
   exim_exit(EXIT_SUCCESS);
   }
@@ -4366,16 +4503,17 @@ but fd 1 will not be set. This also happens for passed SMTP channels. */
 
 if (fstat(1, &statbuf) < 0) (void)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 (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
   {
   }
 else
   {
@@ -4402,8 +4540,13 @@ if ((!smtp_input || smtp_batched_input) && !receive_check_fs(0))
   return EXIT_FAILURE;
   }
 
   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)
   {
 
 if (smtp_input)
   {
@@ -4419,12 +4562,12 @@ if (smtp_input)
     }
   }
 
     }
   }
 
-/* Otherwise, set up the input size limit here */
+/* Otherwise, set up the input size limit here. */
 
 else
   {
 
 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 "
     {
     if (thismessage_size_limit == -1)
       log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to expand "
@@ -4491,20 +4634,13 @@ while (more)
   store_reset(reset_point);
   message_id[0] = 0;
 
   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)
     {
 
   if (smtp_input)
     {
@@ -4517,14 +4653,36 @@ while (more)
         sender_address = raw_sender = real_sender_address;
         sender_address_unrewritten = NULL;
         }
         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;
       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);
         }
       }
         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
     }
 
   /* In the non-SMTP case, we have all the information from the command
@@ -4635,6 +4793,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. */
     /* 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. */