Added acl_warn_skipped log selector (default on).
[exim.git] / src / src / receive.c
index 1194f2f59465cc18b6f298f097e68c23108757ba..3f7170b3ecbb5115ba15b572330e8d7b469527e8 100644 (file)
@@ -1,18 +1,23 @@
-/* $Cambridge: exim/src/src/receive.c,v 1.8 2004/12/29 16:00:58 ph10 Exp $ */
+/* $Cambridge: exim/src/src/receive.c,v 1.24 2005/09/12 10:08:54 ph10 Exp $ */
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2004 */
+/* Copyright (c) University of Cambridge 1995 - 2005 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Code for receiving a message and setting up spool files. */
 
-
 #include "exim.h"
 
-
+#ifdef EXPERIMENTAL_DOMAINKEYS
+#define RECEIVE_GETC dk_receive_getc
+#define RECEIVE_UNGETC dk_receive_ungetc
+#else
+#define RECEIVE_GETC receive_getc
+#define RECEIVE_UNGETC receive_ungetc
+#endif
 
 /*************************************************
 *                Local static variables          *
@@ -95,28 +100,28 @@ return
 *          Read space info for a partition       *
 *************************************************/
 
-/* This function is called by receive_check_fs() below, and also by string 
-expansion for variables such as $spool_space. The field names for the statvfs 
+/* This function is called by receive_check_fs() below, and also by string
+expansion for variables such as $spool_space. The field names for the statvfs
 structure are macros, because not all OS have F_FAVAIL and it seems tidier to
 have macros for F_BAVAIL and F_FILES as well. Some kinds of file system do not
 have inodes, and they return -1 for the number available.
 
 Later: It turns out that some file systems that do not have the concept of
 inodes return 0 rather than -1. Such systems should also return 0 for the total
-number of inodes, so we require that to be greater than zero before returning 
+number of inodes, so we require that to be greater than zero before returning
 an inode count.
 
 Arguments:
   isspool       TRUE for spool partition, FALSE for log partition
   inodeptr      address of int to receive inode count; -1 if there isn't one
-  
+
 Returns:        available on-root space, in kilobytes
-                -1 for log partition if there isn't one  
-                
-All values are -1 if the STATFS functions are not available. 
+                -1 for log partition if there isn't one
+
+All values are -1 if the STATFS functions are not available.
 */
 
-int 
+int
 receive_statvfs(BOOL isspool, int *inodeptr)
 {
 #ifdef HAVE_STATFS
@@ -129,10 +134,10 @@ uschar buffer[1024];
 
 if (isspool)
   {
-  path = spool_directory; 
-  name = US"spool"; 
-  } 
-  
+  path = spool_directory;
+  name = US"spool";
+  }
+
 /* Need to cut down the log file path to the directory, and to ignore any
 appearance of "syslog" in it. */
 
@@ -140,7 +145,7 @@ else
   {
   int sep = ':';              /* Not variable - outside scripts use */
   uschar *p = log_file_path;
-  name = US"log"; 
+  name = US"log";
 
   /* An empty log_file_path means "use the default". This is the same as an
   empty item in a list. */
@@ -153,26 +158,26 @@ else
 
   if (path == NULL)  /* No log files */
     {
-    *inodeptr = -1; 
-    return -1;       
-    } 
+    *inodeptr = -1;
+    return -1;
+    }
 
-  /* An empty string means use the default, which is in the spool directory. 
-  But don't just use the spool directory, as it is possible that the log 
+  /* An empty string means use the default, which is in the spool directory.
+  But don't just use the spool directory, as it is possible that the log
   subdirectory has been symbolically linked elsewhere. */
 
-  if (path[0] == 0) 
+  if (path[0] == 0)
     {
     sprintf(CS buffer, CS"%s/log", CS spool_directory);
     path = buffer;
-    }  
-  else 
+    }
+  else
     {
-    uschar *cp; 
+    uschar *cp;
     if ((cp = Ustrrchr(path, '/')) != NULL) *cp = 0;
-    } 
+    }
   }
-  
+
 /* We now have the patch; do the business */
 
 memset(&statbuf, 0, sizeof(statbuf));
@@ -184,11 +189,11 @@ if (STATVFS(CS path, &statbuf) != 0)
   smtp_closedown(US"spool or log directory problem");
   exim_exit(EXIT_FAILURE);
   }
-  
+
 *inodeptr = (statbuf.F_FILES > 0)? statbuf.F_FAVAIL : -1;
 
 /* Disks are getting huge. Take care with computing the size in kilobytes. */
+
 return (int)(((double)statbuf.F_BAVAIL * (double)statbuf.F_FRSIZE)/1024.0);
 
 /* Unable to find partition sizes in this environment. */
@@ -228,16 +233,16 @@ int space, inodes;
 
 if (check_spool_space > 0 || msg_size > 0 || check_spool_inodes > 0)
   {
-  space = receive_statvfs(TRUE, &inodes); 
-  
+  space = receive_statvfs(TRUE, &inodes);
+
   DEBUG(D_receive)
     debug_printf("spool directory space = %dK inodes = %d "
       "check_space = %dK inodes = %d msg_size = %d\n",
       space, inodes, check_spool_space, check_spool_inodes, msg_size);
-  
-  if ((space >= 0 && space < check_spool_space) || 
+
+  if ((space >= 0 && space < check_spool_space) ||
       (inodes >= 0 && inodes < check_spool_inodes))
-    {   
+    {
     log_write(0, LOG_MAIN, "spool directory space check failed: space=%d "
       "inodes=%d", space, inodes);
     return FALSE;
@@ -246,22 +251,22 @@ if (check_spool_space > 0 || msg_size > 0 || check_spool_inodes > 0)
 
 if (check_log_space > 0 || check_log_inodes > 0)
   {
-  space = receive_statvfs(FALSE, &inodes); 
-  
+  space = receive_statvfs(FALSE, &inodes);
+
   DEBUG(D_receive)
     debug_printf("log directory space = %dK inodes = %d "
       "check_space = %dK inodes = %d\n",
       space, inodes, check_log_space, check_log_inodes);
-  
-  if ((space >= 0 && space < check_log_space) || 
+
+  if ((space >= 0 && space < check_log_space) ||
       (inodes >= 0 && inodes < check_log_inodes))
-    {   
+    {
     log_write(0, LOG_MAIN, "log directory space check failed: space=%d "
       "inodes=%d", space, inodes);
     return FALSE;
     }
-  }   
-  
+  }
+
 return TRUE;
 }
 
@@ -298,8 +303,8 @@ if (spool_name[0] != 0)
 
 /* Now close the file if it is open, either as a fd or a stream. */
 
-if (data_file != NULL) fclose(data_file);
-  else if (data_fd >= 0) close(data_fd);
+if (data_file != NULL) (void)fclose(data_file);
+  else if (data_fd >= 0) (void)close(data_fd);
 
 /* Attempt to close down an SMTP connection tidily. */
 
@@ -346,8 +351,9 @@ if (smtp_input)
   msg = US"SMTP incoming data timeout";
   log_write(L_lost_incoming_connection,
             LOG_MAIN, "SMTP data timeout (message abandoned) on connection "
-            "from %s",
-            (sender_fullhost != NULL)? sender_fullhost : US"local process");
+            "from %s F=<%s>",
+            (sender_fullhost != NULL)? sender_fullhost : US"local process",
+            sender_address);
   }
 else
   {
@@ -505,7 +511,7 @@ for (count = 0; count < recipients_count; count++)
     {
     if ((--recipients_count - count) > 0)
       memmove(recipients_list + count, recipients_list + count + 1,
-              (recipients_count - count)*sizeof(recipient_item));
+        (recipients_count - count)*sizeof(recipient_item));
     return TRUE;
     }
   }
@@ -565,7 +571,7 @@ if (!dot_ends)
   {
   register int last_ch = '\n';
 
-  for (; (ch = (receive_getc)()) != EOF; last_ch = ch)
+  for (; (ch = (RECEIVE_GETC)()) != EOF; last_ch = ch)
     {
     if (ch == 0) body_zerocount++;
     if (last_ch == '\r' && ch != '\n')
@@ -595,7 +601,7 @@ if (!dot_ends)
 
 ch_state = 1;
 
-while ((ch = (receive_getc)()) != EOF)
+while ((ch = (RECEIVE_GETC)()) != EOF)
   {
   if (ch == 0) body_zerocount++;
   switch (ch_state)
@@ -696,7 +702,7 @@ read_message_data_smtp(FILE *fout)
 int ch_state = 0;
 register int ch;
 
-while ((ch = (receive_getc)()) != EOF)
+while ((ch = (RECEIVE_GETC)()) != EOF)
   {
   if (ch == 0) body_zerocount++;
   switch (ch_state)
@@ -859,7 +865,7 @@ if (error_handling == ERRORS_SENDER)
     error_rc = EXIT_FAILURE;
   }
 else fprintf(stderr, "exim: %s%s\n", text2, text1);  /* Sic */
-fclose(f);
+(void)fclose(f);
 exim_exit(error_rc);
 }
 
@@ -935,7 +941,7 @@ for (h = acl_warn_headers; h != NULL; h = next)
        of all headers. Our current header must follow it. */
     h->next = last_received->next;
     last_received->next = h;
-    DEBUG(D_receive|D_acl) debug_printf("  (before any non-Received: or Resent-*: header)");        
+    DEBUG(D_receive|D_acl) debug_printf("  (before any non-Received: or Resent-*: header)");
     break;
 
     default:
@@ -1002,6 +1008,149 @@ return s;
 
 
 
+#ifdef WITH_CONTENT_SCAN
+
+/*************************************************
+*       Run the MIME ACL on a message            *
+*************************************************/
+
+/* This code is in a subroutine so that it can be used for both SMTP
+and non-SMTP messages. It is called with a non-NULL ACL pointer.
+
+Arguments:
+  acl                The ACL to run (acl_smtp_mime or acl_not_smtp_mime)
+  smtp_yield_ptr     Set FALSE to kill messages after dropped connection
+  smtp_reply_ptr     Where SMTP reply is being built
+  blackholed_by_ptr  Where "blackholed by" message is being built
+
+Returns:             TRUE to carry on; FALSE to abandon the message
+*/
+
+static BOOL
+run_mime_acl(uschar *acl, BOOL *smtp_yield_ptr, uschar **smtp_reply_ptr,
+  uschar **blackholed_by_ptr)
+{
+FILE *mbox_file;
+uschar rfc822_file_path[2048];
+unsigned long mbox_size;
+header_line *my_headerlist;
+uschar *user_msg, *log_msg;
+int mime_part_count_buffer = -1;
+int rc;
+
+memset(CS rfc822_file_path,0,2048);
+
+/* check if it is a MIME message */
+my_headerlist = header_list;
+while (my_headerlist != NULL) {
+  /* skip deleted headers */
+  if (my_headerlist->type == '*') {
+    my_headerlist = my_headerlist->next;
+    continue;
+  };
+  if (strncmpic(my_headerlist->text, US"Content-Type:", 13) == 0) {
+    DEBUG(D_receive) debug_printf("Found Content-Type: header - executing acl_smtp_mime.\n");
+    goto DO_MIME_ACL;
+  };
+  my_headerlist = my_headerlist->next;
+};
+
+DEBUG(D_receive) debug_printf("No Content-Type: header - presumably not a MIME message.\n");
+return TRUE;
+
+DO_MIME_ACL:
+/* make sure the eml mbox file is spooled up */
+mbox_file = spool_mbox(&mbox_size);
+if (mbox_file == NULL) {
+  /* error while spooling */
+  log_write(0, LOG_MAIN|LOG_PANIC,
+         "acl_smtp_mime: error while creating mbox spool file, message temporarily rejected.");
+  Uunlink(spool_name);
+  unspool_mbox();
+  smtp_respond(451, TRUE, US"temporary local problem");
+  message_id[0] = 0;            /* Indicate no message accepted */
+  *smtp_reply_ptr = US"";       /* Indicate reply already sent */
+  return FALSE;                 /* Indicate skip to end of receive function */
+};
+
+mime_is_rfc822 = 0;
+
+MIME_ACL_CHECK:
+mime_part_count = -1;
+rc = mime_acl_check(acl, mbox_file, NULL, &user_msg, &log_msg);
+(void)fclose(mbox_file);
+
+if (Ustrlen(rfc822_file_path) > 0) {
+  mime_part_count = mime_part_count_buffer;
+
+  if (unlink(CS rfc822_file_path) == -1) {
+    log_write(0, LOG_PANIC,
+         "acl_smtp_mime: can't unlink RFC822 spool file, skipping.");
+      goto END_MIME_ACL;
+  };
+};
+
+/* check if we must check any message/rfc822 attachments */
+if (rc == OK) {
+  uschar temp_path[1024];
+  int n;
+  struct dirent *entry;
+  DIR *tempdir;
+
+  (void)string_format(temp_path, 1024, "%s/scan/%s", spool_directory,
+    message_id);
+
+ tempdir = opendir(CS temp_path);
+ n = 0;
+ do {
+   entry = readdir(tempdir);
+   if (entry == NULL) break;
+    if (strncmpic(US entry->d_name,US"__rfc822_",9) == 0) {
+      (void)string_format(rfc822_file_path, 2048,"%s/scan/%s/%s", spool_directory, message_id, entry->d_name);
+     debug_printf("RFC822 attachment detected: running MIME ACL for '%s'\n", rfc822_file_path);
+     break;
+    };
+ } while (1);
+ closedir(tempdir);
+
+  if (entry != NULL) {
+    mbox_file = Ufopen(rfc822_file_path,"rb");
+    if (mbox_file == NULL) {
+      log_write(0, LOG_PANIC,
+         "acl_smtp_mime: can't open RFC822 spool file, skipping.");
+      unlink(CS rfc822_file_path);
+      goto END_MIME_ACL;
+    };
+    /* set RFC822 expansion variable */
+    mime_is_rfc822 = 1;
+    mime_part_count_buffer = mime_part_count;
+    goto MIME_ACL_CHECK;
+  };
+};
+
+END_MIME_ACL:
+add_acl_headers(US"MIME");
+if (rc == DISCARD)
+  {
+  recipients_count = 0;
+  *blackholed_by_ptr = US"MIME ACL";
+  }
+else if (rc != OK)
+  {
+  Uunlink(spool_name);
+  unspool_mbox();
+  if (smtp_handle_acl_fail(ACL_WHERE_MIME, rc, user_msg, log_msg) != 0)
+    *smtp_yield_ptr = FALSE;    /* No more messsages after dropped connection */
+  *smtp_reply_ptr = US"";       /* Indicate reply already sent */
+  message_id[0] = 0;            /* Indicate no message accepted */
+  return FALSE;                 /* Cause skip to end of receive function */
+  };
+
+return TRUE;
+}
+
+#endif  /* WITH_CONTENT_SCAN */
+
 
 /*************************************************
 *                 Receive message                *
@@ -1011,9 +1160,10 @@ return s;
 Either a non-null list of recipients, or the extract flag will be true, or
 both. The flag sender_local is true for locally generated messages. The flag
 submission_mode is true if an ACL has obeyed "control = submission". The flag
-smtp_input is true if the message is to be handled using SMTP conventions about
-termination and lines starting with dots. For non-SMTP messages, dot_ends is
-true for dot-terminated messages.
+suppress_local_fixups is true if an ACL has obeyed "control =
+suppress_local_fixups". The flag smtp_input is true if the message is to be
+handled using SMTP conventions about termination and lines starting with dots.
+For non-SMTP messages, dot_ends is true for dot-terminated messages.
 
 If a message was successfully read, message_id[0] will be non-zero.
 
@@ -1115,6 +1265,7 @@ BOOL yield = FALSE;
 BOOL resents_exist = FALSE;
 uschar *resent_prefix = US"";
 uschar *blackholed_by = NULL;
+uschar *blackhole_log_msg = US"";
 
 flock_t lock_data;
 error_block *bad_addresses = NULL;
@@ -1190,11 +1341,15 @@ received_count = 1;     /* For the one we will add */
 
 if (thismessage_size_limit <= 0) thismessage_size_limit = INT_MAX;
 
-/* While reading the message, body_linecount and body_zerocount is computed.
-The full message_ linecount is set up only when the headers are read back in
-from the spool for delivery. */
+/* While reading the message, the following counts are computed. */
+
+message_linecount = body_linecount = body_zerocount = 0;
 
-body_linecount = body_zerocount = 0;
+#ifdef EXPERIMENTAL_DOMAINKEYS
+/* Call into DK to set up the context. Check if DK is to be run are carried out
+   inside dk_exim_verify_init(). */
+dk_exim_verify_init();
+#endif
 
 /* Remember the time of reception. Exim uses time+pid for uniqueness of message
 ids, and fractions of a second are required. See the comments that precede the
@@ -1244,7 +1399,7 @@ next->text. */
 
 for (;;)
   {
-  int ch = (receive_getc)();
+  int ch = (RECEIVE_GETC)();
 
   /* If we hit EOF on a SMTP connection, it's an error, since incoming
   SMTP must have a correct "." terminator. */
@@ -1308,7 +1463,7 @@ for (;;)
   if (ch == '\n')
     {
     if (first_line_ended_crlf == TRUE_UNSET) first_line_ended_crlf = FALSE;
-      else if (first_line_ended_crlf) receive_ungetc(' ');
+      else if (first_line_ended_crlf) RECEIVE_UNGETC(' ');
     goto EOL;
     }
 
@@ -1323,13 +1478,13 @@ for (;;)
 
   if (ptr == 0 && ch == '.' && (smtp_input || dot_ends))
     {
-    ch = (receive_getc)();
+    ch = (RECEIVE_GETC)();
     if (ch == '\r')
       {
-      ch = (receive_getc)();
+      ch = (RECEIVE_GETC)();
       if (ch != '\n')
         {
-        receive_ungetc(ch);
+        RECEIVE_UNGETC(ch);
         ch = '\r';              /* Revert to CR */
         }
       }
@@ -1357,7 +1512,7 @@ for (;;)
 
   if (ch == '\r')
     {
-    ch = (receive_getc)();
+    ch = (RECEIVE_GETC)();
     if (ch == '\n')
       {
       if (first_line_ended_crlf == TRUE_UNSET) first_line_ended_crlf = TRUE;
@@ -1367,7 +1522,7 @@ for (;;)
     /* Otherwise, put back the character after CR, and turn the bare CR
     into LF SP. */
 
-    ch = (receive_ungetc)(ch);
+    ch = (RECEIVE_UNGETC)(ch);
     next->text[ptr++] = '\n';
     message_size++;
     ch = ' ';
@@ -1418,7 +1573,11 @@ for (;;)
   /* End of header line reached */
 
   EOL:
-  receive_linecount++;          /* For BSMTP errors */
+
+  /* Keep track of lines for BSMTP errors and overall message_linecount. */
+
+  receive_linecount++;
+  message_linecount++;
 
   /* Now put in the terminating newline. There is always space for
   at least two more characters. */
@@ -1442,14 +1601,14 @@ for (;;)
 
   if (ch != EOF)
     {
-    int nextch = (receive_getc)();
+    int nextch = (RECEIVE_GETC)();
     if (nextch == ' ' || nextch == '\t')
       {
       next->text[ptr++] = nextch;
       message_size++;
       continue;                      /* Iterate the loop */
       }
-    else if (nextch != EOF) (receive_ungetc)(nextch);   /* For next time */
+    else if (nextch != EOF) (RECEIVE_UNGETC)(nextch);   /* For next time */
     else ch = EOF;                   /* Cause main loop to exit at end */
     }
 
@@ -1818,18 +1977,22 @@ for (h = header_list->next; h != NULL; h = h->next)
     break;
 
     /* If there is a "Sender:" header and the message is locally originated,
-    and from an untrusted caller, or if we are in submission mode for a remote
-    message, mark it "old" so that it will not be transmitted with the message,
-    unless active_local_sender_retain is set. (This can only be true if
-    active_local_from_check is false.) If there are any resent- headers in the
-    message, apply this rule to Resent-Sender: instead of Sender:. Messages
-    with multiple resent- header sets cannot be tidily handled. (For this
-    reason, at least one MUA - Pine - turns old resent- headers into X-resent-
-    headers when resending, leaving just one set.) */
+    and from an untrusted caller and suppress_local_fixups is not set, or if we
+    are in submission mode for a remote message, mark it "old" so that it will
+    not be transmitted with the message, unless active_local_sender_retain is
+    set. (This can only be true if active_local_from_check is false.) If there
+    are any resent- headers in the message, apply this rule to Resent-Sender:
+    instead of Sender:. Messages with multiple resent- header sets cannot be
+    tidily handled. (For this reason, at least one MUA - Pine - turns old
+    resent- headers into X-resent- headers when resending, leaving just one
+    set.) */
 
     case htype_sender:
     h->type = ((!active_local_sender_retain &&
-                ((sender_local && !trusted_caller) || submission_mode)
+                (
+                (sender_local && !trusted_caller && !suppress_local_fixups)
+                  || submission_mode
+                )
                ) &&
                (!resents_exist||is_resent))?
       htype_old : htype_sender;
@@ -2091,11 +2254,13 @@ ensure that it is an empty string. */
 message_subdir[0] = split_spool_directory? message_id[5] : 0;
 
 /* Now that we have the message-id, if there is no message-id: header, generate
-one, but only for local or submission mode messages. This can be
-user-configured if required, but we had better flatten any illegal characters
-therein. */
+one, but only for local (without suppress_local_fixups) or submission mode
+messages. This can be user-configured if required, but we had better flatten
+any illegal characters therein. */
 
-if (msgid_header == NULL && (sender_host_address == NULL || submission_mode))
+if (msgid_header == NULL &&
+      ((sender_host_address == NULL && !suppress_local_fixups)
+        || submission_mode))
   {
   uschar *p;
   uschar *id_text = US"";
@@ -2169,46 +2334,75 @@ for (i = 0; i < recipients_count; i++)
     rewrite_address(recipients_list[i].address, TRUE, TRUE,
       global_rewrite_rules, rewrite_existflags);
 
-/* If there is no From: header, generate one for local or submission_mode
-messages. If there is no sender address, but the sender is local or this is a
-local delivery error, use the originator login. This shouldn't happen for
-genuine bounces, but might happen for autoreplies. The addition of From: must
-be done *before* checking for the possible addition of a Sender: header,
-because untrusted_set_sender allows an untrusted user to set anything in the
-envelope (which might then get info From:) but we still want to ensure a valid
-Sender: if it is required. */
-
-if (from_header == NULL && (sender_host_address == NULL || submission_mode))
+/* If there is no From: header, generate one for local (without
+suppress_local_fixups) or submission_mode messages. If there is no sender
+address, but the sender is local or this is a local delivery error, use the
+originator login. This shouldn't happen for genuine bounces, but might happen
+for autoreplies. The addition of From: must be done *before* checking for the
+possible addition of a Sender: header, because untrusted_set_sender allows an
+untrusted user to set anything in the envelope (which might then get info
+From:) but we still want to ensure a valid Sender: if it is required. */
+
+if (from_header == NULL &&
+    ((sender_host_address == NULL && !suppress_local_fixups)
+      || submission_mode))
   {
+  uschar *oname = US"";
+
+  /* Use the originator_name if this is a locally submitted message and the
+  caller is not trusted. For trusted callers, use it only if -F was used to
+  force its value or if we have a non-SMTP message for which -f was not used
+  to set the sender. */
+
+  if (sender_host_address == NULL)
+    {
+    if (!trusted_caller || sender_name_forced ||
+         (!smtp_input && !sender_address_forced))
+      oname = originator_name;
+    }
+
+  /* For non-locally submitted messages, the only time we use the originator
+  name is when it was forced by the /name= option on control=submission. */
+
+  else
+    {
+    if (submission_name != NULL) oname = submission_name;
+    }
+
   /* Envelope sender is empty */
 
   if (sender_address[0] == 0)
     {
+    uschar *fromstart, *fromend;
+
+    fromstart = string_sprintf("%sFrom: %s%s", resent_prefix,
+      oname, (oname[0] == 0)? "" : " <");
+    fromend = (oname[0] == 0)? US"" : US">";
+
     if (sender_local || local_error_message)
       {
-      header_add(htype_from, "%sFrom: %s%s%s@%s%s\n", resent_prefix,
-        originator_name,
-        (originator_name[0] == 0)? "" : " <",
-        local_part_quote(originator_login),
-        qualify_domain_sender,
-        (originator_name[0] == 0)? "" : ">");
+      header_add(htype_from, "%s%s@%s%s\n", fromstart,
+        local_part_quote(originator_login), qualify_domain_sender,
+        fromend);
       }
     else if (submission_mode && authenticated_id != NULL)
       {
       if (submission_domain == NULL)
         {
-        header_add(htype_from, "%sFrom: %s@%s\n", resent_prefix,
-          local_part_quote(authenticated_id), qualify_domain_sender);
+        header_add(htype_from, "%s%s@%s%s\n", fromstart,
+          local_part_quote(authenticated_id), qualify_domain_sender,
+          fromend);
         }
       else if (submission_domain[0] == 0)  /* empty => whole address set */
         {
-        header_add(htype_from, "%sFrom: %s\n", resent_prefix,
-          authenticated_id);
+        header_add(htype_from, "%s%s%s\n", fromstart, authenticated_id,
+          fromend);
         }
       else
         {
-        header_add(htype_from, "%sFrom: %s@%s\n", resent_prefix,
-          local_part_quote(authenticated_id), submission_domain);
+        header_add(htype_from, "%s%s@%s%s\n", fromstart,
+          local_part_quote(authenticated_id), submission_domain,
+          fromend);
         }
       from_header = header_last;    /* To get it checked for Sender: */
       }
@@ -2220,34 +2414,31 @@ if (from_header == NULL && (sender_host_address == NULL || submission_mode))
 
   else
     {
-    if (!smtp_input || sender_local)
-      header_add(htype_from, "%sFrom: %s%s%s%s\n",
-        resent_prefix, originator_name,
-        (originator_name[0] == 0)? "" : " <",
-        (sender_address_unrewritten == NULL)?
-          sender_address : sender_address_unrewritten,
-        (originator_name[0] == 0)? "" : ">");
-    else
-      header_add(htype_from, "%sFrom: %s\n", resent_prefix, sender_address);
+    header_add(htype_from, "%sFrom: %s%s%s%s\n", resent_prefix,
+      oname,
+      (oname[0] == 0)? "" : " <",
+      (sender_address_unrewritten == NULL)?
+        sender_address : sender_address_unrewritten,
+      (oname[0] == 0)? "" : ">");
 
     from_header = header_last;    /* To get it checked for Sender: */
     }
   }
 
 
-/* If the sender is local, or if we are in submission mode and there is an
-authenticated_id, check that an existing From: is correct, and if not, generate
-a Sender: header, unless disabled. Any previously-existing Sender: header was
-removed above. Note that sender_local, as well as being TRUE if the caller of
-exim is not trusted, is also true if a trusted caller did not supply a -f
-argument for non-smtp input. To allow trusted callers to forge From: without
-supplying -f, we have to test explicitly here. If the From: header contains
-more than one address, then the call to parse_extract_address fails, and a
-Sender: header is inserted, as required. */
+/* If the sender is local (without suppress_local_fixups), or if we are in
+submission mode and there is an authenticated_id, check that an existing From:
+is correct, and if not, generate a Sender: header, unless disabled. Any
+previously-existing Sender: header was removed above. Note that sender_local,
+as well as being TRUE if the caller of exim is not trusted, is also true if a
+trusted caller did not supply a -f argument for non-smtp input. To allow
+trusted callers to forge From: without supplying -f, we have to test explicitly
+here. If the From: header contains more than one address, then the call to
+parse_extract_address fails, and a Sender: header is inserted, as required. */
 
 if (from_header != NULL &&
      (active_local_from_check &&
-       ((sender_local && !trusted_caller) ||
+       ((sender_local && !trusted_caller && !suppress_local_fixups) ||
         (submission_mode && authenticated_id != NULL))
      ))
   {
@@ -2309,12 +2500,27 @@ if (from_header != NULL &&
 
   if (make_sender)
     {
-    if (submission_mode)
+    if (submission_mode && submission_name == NULL)
       header_add(htype_sender, "%sSender: %s\n", resent_prefix,
         generated_sender_address);
     else
       header_add(htype_sender, "%sSender: %s <%s>\n",
-        resent_prefix, originator_name, generated_sender_address);
+        resent_prefix,
+        submission_mode? submission_name : originator_name,
+        generated_sender_address);
+    }
+
+  /* Ensure that a non-null envelope sender address corresponds to the
+  submission mode sender address. */
+
+  if (submission_mode && sender_address[0] != 0)
+    {
+    if (sender_address_unrewritten == NULL)
+      sender_address_unrewritten = sender_address;
+    sender_address = generated_sender_address;
+    log_write(L_address_rewrite, LOG_MAIN,
+      "\"%s\" from env-from rewritten as \"%s\" by submission mode",
+      sender_address_unrewritten, generated_sender_address);
     }
   }
 
@@ -2374,11 +2580,13 @@ if (!to_or_cc_header_exists && !bcc_header_exists)
 ******/
 
 /* If there is no date header, generate one if the message originates locally
-(i.e. not over TCP/IP) or the submission mode flag is set. Messages without
-Date: are not valid, but it seems to be more confusing if Exim adds one to
-all remotely-originated messages. */
+(i.e. not over TCP/IP) and suppress_local_fixups is not set, or if the
+submission mode flag is set. Messages without Date: are not valid, but it seems
+to be more confusing if Exim adds one to all remotely-originated messages. */
 
-if (!date_header_exists && (sender_host_address == NULL || submission_mode))
+if (!date_header_exists &&
+      ((sender_host_address == NULL && !suppress_local_fixups)
+        || submission_mode))
   header_add(htype_other, "%sDate: %s\n", resent_prefix, tod_stamp(tod_full));
 
 search_tidyup();    /* Free any cached resources */
@@ -2430,8 +2638,8 @@ if (data_fd < 0)
 /* Make sure the file's group is the Exim gid, and double-check the mode
 because the group setting doesn't always get set automatically. */
 
-fchown(data_fd, exim_uid, exim_gid);
-fchmod(data_fd, SPOOL_MODE);
+(void)fchown(data_fd, exim_uid, exim_gid);
+(void)fchmod(data_fd, SPOOL_MODE);
 
 /* We now have data file open. Build a stream for it and lock it. We lock only
 the first line of the file (containing the message ID) because otherwise there
@@ -2460,7 +2668,7 @@ if (next != NULL)
   {
   uschar *s = next->text;
   int len = next->slen;
-  fwrite(s, 1, len, data_file);
+  (void)fwrite(s, 1, len, data_file);
   body_linecount++;                 /* Assumes only 1 line */
   }
 
@@ -2478,6 +2686,7 @@ if (!ferror(data_file) && !(receive_feof)() && message_ended != END_DOT)
   else message_ended = read_message_data(data_file);
 
   receive_linecount += body_linecount;  /* For BSMTP errors mainly */
+  message_linecount += body_linecount;
 
   /* Handle premature termination of SMTP */
 
@@ -2650,7 +2859,7 @@ if (extract_recip && (bad_addresses != NULL || recipients_count == 0))
   if (recipients_count == 0 || error_handling == ERRORS_STDERR)
     {
     Uunlink(spool_name);
-    fclose(data_file);
+    (void)fclose(data_file);
     exim_exit(error_rc);
     }
   }
@@ -2737,128 +2946,18 @@ else
   if (smtp_input && !smtp_batched_input)
     {
 
+#ifdef EXPERIMENTAL_DOMAINKEYS
+    dk_exim_verify_finish();
+#endif
+
 #ifdef WITH_CONTENT_SCAN
-     /* MIME ACL hook */
-    if (acl_smtp_mime != NULL && recipients_count > 0)
-      {
-      FILE *mbox_file;
-      uschar rfc822_file_path[2048];
-      unsigned long mbox_size;
-      header_line *my_headerlist;
-      uschar *user_msg, *log_msg;
-      int mime_part_count_buffer = -1;
-      
-      memset(CS rfc822_file_path,0,2048);
-      
-      /* check if it is a MIME message */
-      my_headerlist = header_list;
-      while (my_headerlist != NULL) {
-        /* skip deleted headers */
-        if (my_headerlist->type == '*') {
-          my_headerlist = my_headerlist->next;
-          continue;
-        };
-        if (strncmpic(my_headerlist->text, US"Content-Type:", 13) == 0) {
-          DEBUG(D_receive) debug_printf("Found Content-Type: header - executing acl_smtp_mime.\n");
-          goto DO_MIME_ACL;
-        };
-        my_headerlist = my_headerlist->next;
-      };
-      
-      DEBUG(D_receive) debug_printf("No Content-Type: header - presumably not a MIME message.\n");
-      goto NO_MIME_ACL;
-      
-      DO_MIME_ACL:
-      /* make sure the eml mbox file is spooled up */
-      mbox_file = spool_mbox(&mbox_size);
-      if (mbox_file == NULL) {
-        /* error while spooling */
-        log_write(0, LOG_MAIN|LOG_PANIC,
-               "acl_smtp_mime: error while creating mbox spool file, message temporarily rejected.");
-        Uunlink(spool_name);
-        unspool_mbox(); 
-        smtp_respond(451, TRUE, US"temporary local problem");
-        message_id[0] = 0;            /* Indicate no message accepted */
-        smtp_reply = US"";            /* Indicate reply already sent */
-        goto TIDYUP;                  /* Skip to end of function */
-      };
-      
-      mime_is_rfc822 = 0;
-
-      MIME_ACL_CHECK:
-      mime_part_count = -1;
-      rc = mime_acl_check(mbox_file, NULL, &user_msg, &log_msg);
-      fclose(mbox_file);
-      
-      if (Ustrlen(rfc822_file_path) > 0) {
-        mime_part_count = mime_part_count_buffer;
-        
-        if (unlink(CS rfc822_file_path) == -1) {
-          log_write(0, LOG_PANIC,
-               "acl_smtp_mime: can't unlink RFC822 spool file, skipping.");
-            goto END_MIME_ACL;
-        };
-      };
-      
-      /* check if we must check any message/rfc822 attachments */
-      if (rc == OK) {
-        uschar temp_path[1024];
-        int n;
-        struct dirent *entry;
-        DIR *tempdir;
-        snprintf(CS temp_path, 1024, "%s/scan/%s", spool_directory, message_id);
-
-       tempdir = opendir(CS temp_path);
-       n = 0;
-       do {
-         entry = readdir(tempdir);
-         if (entry == NULL) break;
-          if (strncmpic(US entry->d_name,US"__rfc822_",9) == 0) {
-            snprintf(CS rfc822_file_path, 2048,"%s/scan/%s/%s", spool_directory, message_id, entry->d_name);
-           debug_printf("RFC822 attachment detected: running MIME ACL for '%s'\n", rfc822_file_path);
-           break;
-          }; 
-       } while (1);
-       closedir(tempdir);
-        
-        if (entry != NULL) {
-          mbox_file = Ufopen(rfc822_file_path,"r");
-          if (mbox_file == NULL) {
-            log_write(0, LOG_PANIC,
-               "acl_smtp_mime: can't open RFC822 spool file, skipping.");
-            unlink(CS rfc822_file_path);
-            goto END_MIME_ACL;
-          };
-          /* set RFC822 expansion variable */
-          mime_is_rfc822 = 1;
-          mime_part_count_buffer = mime_part_count;
-          goto MIME_ACL_CHECK;
-        };
-      };
-      
-      END_MIME_ACL:
-      add_acl_headers(US"MIME");
-      if (rc == DISCARD)      
-        {
-        recipients_count = 0;
-        blackholed_by = US"MIME ACL";
-        }
-      else if (rc != OK)
-        {
-        Uunlink(spool_name);
-        unspool_mbox();
-        if (smtp_handle_acl_fail(ACL_WHERE_MIME, rc, user_msg, log_msg) != 0)
-          smtp_yield = FALSE;    /* No more messsages after dropped connection */
-        smtp_reply = US"";       /* Indicate reply already sent */
-        message_id[0] = 0;       /* Indicate no message accepted */
-        goto TIDYUP;             /* Skip to end of function */
-        }; 
-      }
-    NO_MIME_ACL:      
+    if (acl_smtp_mime != NULL &&
+        !run_mime_acl(acl_smtp_mime, &smtp_yield, &smtp_reply, &blackholed_by))
+      goto TIDYUP;
 #endif /* WITH_CONTENT_SCAN */
 
+    /* Check the recipients count again, as the MIME ACL might have changed
+    them. */
 
     if (acl_smtp_data != NULL && recipients_count > 0)
       {
@@ -2869,6 +2968,8 @@ else
         {
         recipients_count = 0;
         blackholed_by = US"DATA ACL";
+        if (log_msg != NULL)
+          blackhole_log_msg = string_sprintf(": %s", log_msg);
         }
       else if (rc != OK)
         {
@@ -2888,38 +2989,56 @@ else
   /* Handle non-SMTP and batch SMTP (i.e. non-interactive) messages. Note that
   we cannot take different actions for permanent and temporary rejections. */
 
-  else if (acl_not_smtp != NULL)
+  else
     {
-    uschar *user_msg, *log_msg;
-    rc = acl_check(ACL_WHERE_NOTSMTP, NULL, acl_not_smtp, &user_msg, &log_msg);
-    if (rc == DISCARD)
-      {
-      recipients_count = 0;
-      blackholed_by = US"non-SMTP ACL";
-      }
-    else if (rc != OK)
+
+#ifdef WITH_CONTENT_SCAN
+    if (acl_not_smtp_mime != NULL &&
+        !run_mime_acl(acl_not_smtp_mime, &smtp_yield, &smtp_reply,
+          &blackholed_by))
+      goto TIDYUP;
+#endif /* WITH_CONTENT_SCAN */
+
+    if (acl_not_smtp != NULL)
       {
-      Uunlink(spool_name);
-      log_write(0, LOG_MAIN|LOG_REJECT, "F=<%s> rejected by non-SMTP ACL: %s",
-        sender_address, log_msg);
-      if (user_msg == NULL) user_msg = US"local configuration problem";   
-      if (smtp_batched_input)
+      uschar *user_msg, *log_msg;
+      rc = acl_check(ACL_WHERE_NOTSMTP, NULL, acl_not_smtp, &user_msg, &log_msg);
+      if (rc == DISCARD)
         {
-        moan_smtp_batch(NULL, "%d %s", 550, user_msg);
-        /* Does not return */
+        recipients_count = 0;
+        blackholed_by = US"non-SMTP ACL";
+        if (log_msg != NULL)
+          blackhole_log_msg = string_sprintf(": %s", log_msg);
         }
-      else
+      else if (rc != OK)
         {
-        fseek(data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
-        give_local_error(ERRMESS_LOCAL_ACL, user_msg,
-          US"message rejected by non-SMTP ACL: ", error_rc, data_file,
-            header_list);
-        /* Does not return */
+        Uunlink(spool_name);
+#ifdef WITH_CONTENT_SCAN
+        unspool_mbox();
+#endif
+        log_write(0, LOG_MAIN|LOG_REJECT, "F=<%s> rejected by non-SMTP ACL: %s",
+          sender_address, log_msg);
+        if (user_msg == NULL) user_msg = US"local configuration problem";
+        if (smtp_batched_input)
+          {
+          moan_smtp_batch(NULL, "%d %s", 550, user_msg);
+          /* Does not return */
+          }
+        else
+          {
+          fseek(data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
+          give_local_error(ERRMESS_LOCAL_ACL, user_msg,
+            US"message rejected by non-SMTP ACL: ", error_rc, data_file,
+              header_list);
+          /* Does not return */
+          }
         }
+      add_acl_headers(US"non-SMTP");
       }
-    add_acl_headers(US"non-SMTP");
     }
 
+  /* The applicable ACLs have been run */
+
   if (deliver_freeze) frozen_by = US"ACL";     /* for later logging */
   if (queue_only_policy) queued_by = US"ACL";
 
@@ -3326,7 +3445,7 @@ if (message_logs && blackholed_by == NULL)
       {
       log_write(0, LOG_MAIN|LOG_PANIC, "Couldn't fdopen message log %s: %s",
         spool_name, strerror(errno));
-      close(fd);
+      (void)close(fd);
       }
     else
       {
@@ -3336,7 +3455,7 @@ if (message_logs && blackholed_by == NULL)
         frozen_by);
       if (queue_only_policy) fprintf(message_log,
         "%s no immediate delivery: queued by %s\n", now, queued_by);
-      fclose(message_log);
+      (void)fclose(message_log);
       }
     }
   }
@@ -3368,8 +3487,8 @@ possible for fclose() to fail - but what to do? What has happened to the lock
 if this happens? */
 
 TIDYUP:
-process_info[process_info_len] = 0;          /* Remove message id */
-if (data_file != NULL) fclose(data_file);    /* Frees the lock */
+process_info[process_info_len] = 0;                /* Remove message id */
+if (data_file != NULL) (void)fclose(data_file);    /* Frees the lock */
 
 /* Now reset signal handlers to their defaults */
 
@@ -3394,19 +3513,21 @@ if (smtp_input)
     {
     if (smtp_reply == NULL)
       {
-      if (fake_reject)
-        smtp_respond(550,TRUE,fake_reject_text);
-      else  
-        smtp_printf("250 OK id=%s\r\n", message_id);      
+      if (fake_response != OK)
+        smtp_respond(fake_response == DEFER ? 450 : 550,
+                     TRUE, fake_response_text);
+      else
+        smtp_printf("250 OK id=%s\r\n", message_id);
       if (host_checking)
         fprintf(stdout,
           "\n**** SMTP testing: that is not a real message id!\n\n");
       }
     else if (smtp_reply[0] != 0)
       {
-      if (fake_reject && (smtp_reply[0] == '2'))
-        smtp_respond(550,TRUE,fake_reject_text);
-      else 
+      if (fake_response != OK && (smtp_reply[0] == '2'))
+        smtp_respond(fake_response == DEFER ? 450 : 550,
+                     TRUE, fake_response_text);
+      else
         smtp_printf("%.1024s\r\n", smtp_reply);
       }
     }
@@ -3429,7 +3550,7 @@ if (blackholed_by != NULL)
   uschar *detail = (local_scan_data != NULL)?
     string_printing(local_scan_data) :
     string_sprintf("(%s discarded recipients)", blackholed_by);
-  log_write(0, LOG_MAIN, "=> blackhole %s", detail);
+  log_write(0, LOG_MAIN, "=> blackhole %s%s", detail, blackhole_log_msg);
   log_write(0, LOG_MAIN, "Completed");
   message_id[0] = 0;
   }