Added acl_not_smtp_mime. This involved a bit of refactoring of the
[exim.git] / src / src / receive.c
index 8f6db6cd179f9ffb3e773600c300467a6e70b4ea..325544643b08c2e43bb1e05d07058614b9f5df12 100644 (file)
@@ -1,18 +1,23 @@
-/* $Cambridge: exim/src/src/receive.c,v 1.1 2004/10/07 10:39:01 ph10 Exp $ */
+/* $Cambridge: exim/src/src/receive.c,v 1.13 2005/04/04 10:33:49 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          *
@@ -92,90 +97,55 @@ return
 
 
 /*************************************************
-*     Check space on spool and log partitions    *
+*          Read space info for a partition       *
 *************************************************/
 
-/* This function is called before accepting a message; if any thresholds are
-set, it checks them. If a message_size is supplied, it checks that there is
-enough space for that size plus the threshold - i.e. that the message won't
-reduce the space to the threshold. Not all OS have statvfs(); for those that
-don't, this function always returns TRUE. For some OS the old function and
-struct name statfs is used; that is handled by a macro, defined in exim.h.
+/* 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
+an inode count.
 
 Arguments:
-  msg_size     the (estimated) size of an incoming message
+  isspool       TRUE for spool partition, FALSE for log partition
+  inodeptr      address of int to receive inode count; -1 if there isn't one
 
-Returns:       FALSE if there isn't enough space, or if the information cannot
-                 be obtained
-               TRUE if no check was done or there is enough space
+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.
 */
 
-BOOL
-receive_check_fs(int msg_size)
+int
+receive_statvfs(BOOL isspool, int *inodeptr)
 {
 #ifdef HAVE_STATFS
-BOOL rc = TRUE;
 struct STATVFS statbuf;
+uschar *path;
+uschar *name;
+uschar buffer[1024];
 
-memset(&statbuf, 0, sizeof(statbuf));
-
-/* The field names 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
-server do not have inodes, and they return -1 for the number available, so we
-do the check only when this field is non-negative.
-
-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 doing the
-test. */
+/* The spool directory must always exist. */
 
-if (check_spool_space > 0 || msg_size > 0 || check_spool_inodes > 0)
+if (isspool)
   {
-  if (STATVFS(CS spool_directory, &statbuf) != 0)
-    {
-    log_write(0, LOG_MAIN|LOG_PANIC, "cannot accept message: failed to stat "
-      "spool directory %s: %s", spool_directory, strerror(errno));
-    smtp_closedown(US"spool directory problem");
-    exim_exit(EXIT_FAILURE);
-    }
-
-  /* check_spool_space is held in K because disks are getting huge */
-
-  if (statbuf.F_BAVAIL < (unsigned long)
-        ((((double)check_spool_space) * 1024.0 + (double)msg_size) /
-            (double)statbuf.F_FRSIZE)
-       ||
-      (statbuf.F_FILES > 0 &&
-       statbuf.F_FAVAIL >= 0 &&
-       statbuf.F_FAVAIL < check_spool_inodes))
-    rc = FALSE;
-
-  DEBUG(D_receive)
-    debug_printf("spool directory %s space = %d blocks; inodes = %d; "
-      "check_space = %dK (%d blocks); inodes = %d; msg_size = %d (%d blocks)\n",
-      spool_directory, (int)statbuf.F_BAVAIL, (int)statbuf.F_FAVAIL,
-      check_spool_space,
-      (int)(((double)check_spool_space * 1024.0) / (double)statbuf.F_FRSIZE),
-      check_spool_inodes, msg_size, (int)(msg_size / statbuf.F_FRSIZE));
-
-  if (!rc)
-    {
-    log_write(0, LOG_MAIN, "spool directory space check failed: space=%d "
-      "inodes=%d", (int)statbuf.F_BAVAIL, (int)statbuf.F_FAVAIL);
-    return FALSE;
-    }
+  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. */
 
-if (check_log_space > 0 || check_log_inodes > 0)
+else
   {
-  uschar *path;
   int sep = ':';              /* Not variable - outside scripts use */
-  uschar *cp;
   uschar *p = log_file_path;
-  uschar buffer[1024];
+  name = US"log";
 
   /* An empty log_file_path means "use the default". This is the same as an
   empty item in a list. */
@@ -186,50 +156,117 @@ if (check_log_space > 0 || check_log_inodes > 0)
     if (Ustrcmp(path, "syslog") != 0) break;
     }
 
-  if (path == NULL) return TRUE;    /* No log files, so no problem */
+  if (path == NULL)  /* No log files */
+    {
+    *inodeptr = -1;
+    return -1;
+    }
 
-  /* An empty string means use the default */
+  /* 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)
-    path = string_sprintf("%s/log/%%slog", spool_directory);
-
-  if ((cp = Ustrrchr(path, '/')) == NULL)
     {
-    DEBUG(D_receive) debug_printf("cannot find slash in %s\n", path);
-    return FALSE;
+    sprintf(CS buffer, CS"%s/log", CS spool_directory);
+    path = buffer;
+    }
+  else
+    {
+    uschar *cp;
+    if ((cp = Ustrrchr(path, '/')) != NULL) *cp = 0;
     }
-  *cp = 0;
+  }
+
+/* We now have the patch; do the business */
+
+memset(&statbuf, 0, sizeof(statbuf));
+
+if (STATVFS(CS path, &statbuf) != 0)
+  {
+  log_write(0, LOG_MAIN|LOG_PANIC, "cannot accept message: failed to stat "
+    "%s directory %s: %s", name, spool_directory, strerror(errno));
+  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. */
+
+#else
+*inodeptr = -1;
+return -1;
+#endif
+}
+
+
+
+
+/*************************************************
+*     Check space on spool and log partitions    *
+*************************************************/
+
+/* This function is called before accepting a message; if any thresholds are
+set, it checks them. If a message_size is supplied, it checks that there is
+enough space for that size plus the threshold - i.e. that the message won't
+reduce the space to the threshold. Not all OS have statvfs(); for those that
+don't, this function always returns TRUE. For some OS the old function and
+struct name statfs is used; that is handled by a macro, defined in exim.h.
+
+Arguments:
+  msg_size     the (estimated) size of an incoming message
+
+Returns:       FALSE if there isn't enough space, or if the information cannot
+                 be obtained
+               TRUE if no check was done or there is enough space
+*/
 
-  if (STATVFS(CS path, &statbuf) != 0)
+BOOL
+receive_check_fs(int msg_size)
+{
+int space, inodes;
+
+if (check_spool_space > 0 || msg_size > 0 || check_spool_inodes > 0)
+  {
+  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) ||
+      (inodes >= 0 && inodes < check_spool_inodes))
     {
-    log_write(0, LOG_MAIN|LOG_PANIC, "cannot accept message: failed to stat "
-      "log directory %s: %s", path, strerror(errno));
-    smtp_closedown(US"log directory problem");
-    exim_exit(EXIT_FAILURE);
+    log_write(0, LOG_MAIN, "spool directory space check failed: space=%d "
+      "inodes=%d", space, inodes);
+    return FALSE;
     }
+  }
 
-  if (statbuf.F_BAVAIL < (unsigned long)
-        (((double)check_log_space * 1024.0) / (double)statbuf.F_FRSIZE)
-      ||
-      statbuf.F_FAVAIL < check_log_inodes) rc = FALSE;
+if (check_log_space > 0 || check_log_inodes > 0)
+  {
+  space = receive_statvfs(FALSE, &inodes);
 
   DEBUG(D_receive)
-    debug_printf("log directory %s space = %d blocks; inodes = %d; "
-      "check_space = %dK (%d blocks); inodes = %d\n",
-      path, (int)statbuf.F_BAVAIL, (int)statbuf.F_FAVAIL,
-      check_log_space,
-      (int)(((double)check_log_space * 1024.0) / (double)statbuf.F_FRSIZE),
-      check_log_inodes);
-
-  if (!rc)
+    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) ||
+      (inodes >= 0 && inodes < check_log_inodes))
     {
     log_write(0, LOG_MAIN, "log directory space check failed: space=%d "
-      "inodes=%d", (int)statbuf.F_BAVAIL, (int)statbuf.F_FAVAIL);
+      "inodes=%d", space, inodes);
     return FALSE;
     }
   }
 
-#endif
 return TRUE;
 }
 
@@ -395,7 +432,7 @@ if (smtp_input)
   }
 else
   {
-  if (filter_test == NULL)
+  if (filter_test == FTEST_NONE)
     {
     fprintf(stderr, "\nexim: %s received - message abandoned\n",
       (sig == SIGTERM)? "SIGTERM" : "SIGINT");
@@ -438,6 +475,11 @@ if (recipients_count >= recipients_list_max)
 
 recipients_list[recipients_count].address = recipient;
 recipients_list[recipients_count].pno = pno;
+#ifdef EXPERIMENTAL_BRIGHTMAIL
+recipients_list[recipients_count].bmi_optin = bmi_current_optin;
+/* reset optin string pointer for next recipient */
+bmi_current_optin = NULL;
+#endif
 recipients_list[recipients_count++].errors_to = NULL;
 }
 
@@ -468,7 +510,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;
     }
   }
@@ -528,7 +570,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')
@@ -558,7 +600,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)
@@ -659,7 +701,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)
@@ -886,6 +928,21 @@ for (h = acl_warn_headers; h != NULL; h = next)
     DEBUG(D_receive|D_acl) debug_printf("  (after Received:)");
     break;
 
+    case htype_add_rfc:
+    /* add header before any header which is NOT Received: or Resent- */
+    last_received = header_list;
+    while ( (last_received->next != NULL) &&
+            ( (header_testname(last_received->next, US"Received", 8, FALSE)) ||
+              (header_testname_incomplete(last_received->next, US"Resent-", 7, FALSE)) ) )
+              last_received = last_received->next;
+    /* last_received now points to the last Received: or Resent-* header
+       in an uninterrupted chain of those header types (seen from the beginning
+       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)");
+    break;
+
     default:
     h->next = NULL;
     header_last->next = h;
@@ -951,6 +1008,146 @@ return s;
 
 
 
+/*************************************************
+*       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);
+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_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;
+}
+
+
+
 /*************************************************
 *                 Receive message                *
 *************************************************/
@@ -971,8 +1168,9 @@ The general actions of this function are:
     blocks.
 
   . If there is a "sender:" header and the message is locally originated,
-    throw it away, unless the caller is trusted, or unless local_sender_retain
-    is set - which can only happen if local_from_check is false.
+    throw it away, unless the caller is trusted, or unless
+    active_local_sender_retain is set - which can only happen if
+    active_local_from_check is false.
 
   . If recipients are to be extracted from the message, build the
     recipients list from the headers, removing any that were on the
@@ -997,7 +1195,7 @@ The general actions of this function are:
 
   . If the sender is local, check that from: is correct, and if not, generate
     a Sender: header, unless message comes from a trusted caller, or this
-    feature is disabled by no_local_from_check.
+    feature is disabled by active_local_from_check being false.
 
   . If there is no "date" header, generate one, for locally-originated
     or submission mode messages only.
@@ -1062,6 +1260,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;
@@ -1143,6 +1342,12 @@ from the spool for delivery. */
 
 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
 message id creation below. */
@@ -1191,7 +1396,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. */
@@ -1255,7 +1460,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;
     }
 
@@ -1270,13 +1475,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 */
         }
       }
@@ -1304,7 +1509,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;
@@ -1314,7 +1519,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 = ' ';
@@ -1389,14 +1594,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 */
     }
 
@@ -1475,18 +1680,18 @@ for (;;)
           if (domain == 0 && newsender[0] != 0)
             newsender = rewrite_address_qualify(newsender, FALSE);
 
-          if (filter_test != NULL || receive_check_set_sender(newsender))
+          if (filter_test != FTEST_NONE || receive_check_set_sender(newsender))
             {
             sender_address = newsender;
 
-            if (trusted_caller || filter_test != NULL)
+            if (trusted_caller || filter_test != FTEST_NONE)
               {
               authenticated_sender = NULL;
               originator_name = US"";
               sender_local = FALSE;
               }
 
-            if (filter_test != NULL)
+            if (filter_test != FTEST_NONE)
               printf("Sender taken from \"From \" line\n");
             }
           }
@@ -1626,7 +1831,7 @@ if (smtp_input && (receive_feof)())
 /* If this is a filter test run and no headers were read, output a warning
 in case there is a mistake in the test message. */
 
-if (filter_test != NULL && header_list->next == NULL)
+if (filter_test != FTEST_NONE && header_list->next == NULL)
   printf("Warning: no message headers read\n");
 
 
@@ -1748,7 +1953,7 @@ for (h = header_list->next; h != NULL; h = h->next)
     otherwise set. However, remove any <> that surround the address
     because the variable doesn't have these. */
 
-    if (filter_test != NULL)
+    if (filter_test != FTEST_NONE)
       {
       uschar *start = h->text + 12;
       uschar *end = start + Ustrlen(start);
@@ -1767,17 +1972,16 @@ for (h = header_list->next; h != NULL; h = h->next)
     /* 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 local_sender_retain is set. (This can only be true if
-    local_from_check is false.) If there are any resent- headers in the
+    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 = ((
-               (sender_local && !trusted_caller && !local_sender_retain) ||
-               submission_mode
+    h->type = ((!active_local_sender_retain &&
+                ((sender_local && !trusted_caller) || submission_mode)
                ) &&
                (!resents_exist||is_resent))?
       htype_old : htype_sender;
@@ -2194,9 +2398,9 @@ more than one address, then the call to parse_extract_address fails, and a
 Sender: header is inserted, as required. */
 
 if (from_header != NULL &&
-     (
-      (sender_local && local_from_check && !trusted_caller) ||
-      (submission_mode && authenticated_id != NULL)
+     (active_local_from_check &&
+       ((sender_local && !trusted_caller) ||
+        (submission_mode && authenticated_id != NULL))
      ))
   {
   BOOL make_sender = TRUE;
@@ -2346,7 +2550,7 @@ DEBUG(D_receive)
 testing mode, that is all this function does. Return TRUE if the message
 ended with a dot. */
 
-if (filter_test != NULL)
+if (filter_test != FTEST_NONE)
   {
   process_info[process_info_len] = 0;
   return message_ended == END_DOT;
@@ -2684,6 +2888,20 @@ else
 
   if (smtp_input && !smtp_batched_input)
     {
+
+#ifdef EXPERIMENTAL_DOMAINKEYS
+    dk_exim_verify_finish();
+#endif
+
+#ifdef WITH_CONTENT_SCAN
+    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)
       {
       uschar *user_msg, *log_msg;
@@ -2693,10 +2911,15 @@ 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)
         {
         Uunlink(spool_name);
+#ifdef WITH_CONTENT_SCAN
+        unspool_mbox();
+#endif
         if (smtp_handle_acl_fail(ACL_WHERE_DATA, rc, user_msg, log_msg) != 0)
           smtp_yield = FALSE;    /* No more messsages after dropped connection */
         smtp_reply = US"";       /* Indicate reply already sent */
@@ -2709,43 +2932,66 @@ 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 (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";
 
   enable_dollar_recipients = FALSE;
   }
 
+#ifdef WITH_CONTENT_SCAN
+unspool_mbox();
+#endif
+
 /* The final check on the message is to run the scan_local() function. The
 version supplied with Exim always accepts, but this is a hook for sysadmins to
 supply their own checking code. The local_scan() function is run even when all
@@ -2916,6 +3162,14 @@ signal(SIGINT, SIG_IGN);
 
 deliver_firsttime = TRUE;
 
+#ifdef EXPERIMENTAL_BRIGHTMAIL
+if (bmi_run == 1) {
+  /* rewind data file */
+  lseek(data_fd, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
+  bmi_verdicts = bmi_process_message(header_list, data_fd);
+};
+#endif
+
 /* Update the timstamp in our Received: header to account for any time taken by
 an ACL or by local_scan(). The new time is the time that all reception
 processing is complete. */
@@ -3202,12 +3456,21 @@ if (smtp_input)
     {
     if (smtp_reply == NULL)
       {
-      smtp_printf("250 OK id=%s\r\n", message_id);
+      if (fake_reject)
+        smtp_respond(550,TRUE,fake_reject_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) smtp_printf("%.1024s\r\n", smtp_reply);
+    else if (smtp_reply[0] != 0)
+      {
+      if (fake_reject && (smtp_reply[0] == '2'))
+        smtp_respond(550,TRUE,fake_reject_text);
+      else
+        smtp_printf("%.1024s\r\n", smtp_reply);
+      }
     }
 
   /* For batched SMTP, generate an error message on failure, and do
@@ -3228,7 +3491,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;
   }