Installed a modified version of Tony's submission enhancement patch +
[users/jgh/exim.git] / src / src / acl.c
index 92ebc18ec7d3e785d0339b9f975e65a63eb64d37..357abfad3490f70236a7c49ec381756e1cde4198 100644 (file)
@@ -1,10 +1,10 @@
-/* $Cambridge: exim/src/src/acl.c,v 1.5 2004/11/04 12:19:48 ph10 Exp $ */
+/* $Cambridge: exim/src/src/acl.c,v 1.32 2005/05/17 15:00:04 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 handling Access Control Lists (ACLs) */
@@ -34,26 +34,100 @@ static int msgcond[] = { FAIL, OK, OK, FAIL, OK, FAIL, OK };
 /* ACL condition and modifier codes - keep in step with the table that
 follows. */
 
-enum { ACLC_ACL, ACLC_AUTHENTICATED, ACLC_CONDITION, ACLC_CONTROL, ACLC_DELAY,
-  ACLC_DNSLISTS, ACLC_DOMAINS, ACLC_ENCRYPTED, ACLC_ENDPASS, ACLC_HOSTS,
-  ACLC_LOCAL_PARTS, ACLC_LOG_MESSAGE, ACLC_LOGWRITE, ACLC_MESSAGE,
-  ACLC_RECIPIENTS, ACLC_SENDER_DOMAINS, ACLC_SENDERS, ACLC_SET, ACLC_VERIFY };
+enum { ACLC_ACL, ACLC_AUTHENTICATED,
+#ifdef EXPERIMENTAL_BRIGHTMAIL
+       ACLC_BMI_OPTIN,
+#endif
+ACLC_CONDITION, ACLC_CONTROL,
+#ifdef WITH_CONTENT_SCAN
+       ACLC_DECODE,
+#endif
+       ACLC_DELAY,
+#ifdef WITH_OLD_DEMIME
+       ACLC_DEMIME,
+#endif
+#ifdef EXPERIMENTAL_DOMAINKEYS
+       ACLC_DK_DOMAIN_SOURCE,
+       ACLC_DK_POLICY,
+       ACLC_DK_SENDER_DOMAINS,
+       ACLC_DK_SENDER_LOCAL_PARTS,
+       ACLC_DK_SENDERS,
+       ACLC_DK_STATUS,
+#endif
+       ACLC_DNSLISTS, ACLC_DOMAINS, ACLC_ENCRYPTED, ACLC_ENDPASS,
+       ACLC_HOSTS, ACLC_LOCAL_PARTS, ACLC_LOG_MESSAGE, ACLC_LOGWRITE,
+#ifdef WITH_CONTENT_SCAN
+       ACLC_MALWARE,
+#endif
+       ACLC_MESSAGE,
+#ifdef WITH_CONTENT_SCAN
+       ACLC_MIME_REGEX,
+#endif
+       ACLC_RECIPIENTS,
+#ifdef WITH_CONTENT_SCAN
+       ACLC_REGEX,
+#endif
+       ACLC_SENDER_DOMAINS, ACLC_SENDERS, ACLC_SET,
+#ifdef WITH_CONTENT_SCAN
+       ACLC_SPAM,
+#endif
+#ifdef EXPERIMENTAL_SPF
+       ACLC_SPF,
+#endif
+       ACLC_VERIFY };
 
 /* ACL conditions/modifiers: "delay", "control", "endpass", "message",
 "log_message", "logwrite", and "set" are modifiers that look like conditions
 but always return TRUE. They are used for their side effects. */
 
-static uschar *conditions[] = { US"acl", US"authenticated", US"condition",
-  US"control", US"delay", US"dnslists", US"domains", US"encrypted",
+static uschar *conditions[] = { US"acl", US"authenticated",
+#ifdef EXPERIMENTAL_BRIGHTMAIL
+  US"bmi_optin",
+#endif
+  US"condition",
+  US"control",
+#ifdef WITH_CONTENT_SCAN
+  US"decode",
+#endif
+  US"delay",
+#ifdef WITH_OLD_DEMIME
+  US"demime",
+#endif
+#ifdef EXPERIMENTAL_DOMAINKEYS
+  US"dk_domain_source",
+  US"dk_policy",
+  US"dk_sender_domains",
+  US"dk_sender_local_parts",
+  US"dk_senders",
+  US"dk_status",
+#endif
+  US"dnslists", US"domains", US"encrypted",
   US"endpass", US"hosts", US"local_parts", US"log_message", US"logwrite",
-  US"message", US"recipients", US"sender_domains", US"senders", US"set",
+#ifdef WITH_CONTENT_SCAN
+  US"malware",
+#endif
+  US"message",
+#ifdef WITH_CONTENT_SCAN
+  US"mime_regex",
+#endif
+  US"recipients",
+#ifdef WITH_CONTENT_SCAN
+  US"regex",
+#endif
+  US"sender_domains", US"senders", US"set",
+#ifdef WITH_CONTENT_SCAN
+  US"spam",
+#endif
+#ifdef EXPERIMENTAL_SPF
+  US"spf",
+#endif
   US"verify" };
-  
+
 /* ACL control names */
 
 static uschar *controls[] = { US"error", US"caseful_local_part",
   US"caselower_local_part", US"enforce_sync", US"no_enforce_sync", US"freeze",
-  US"queue_only", US"submission", US"no_multiline"}; 
+  US"queue_only", US"submission", US"no_multiline"};
 
 /* Flags to indicate for which conditions /modifiers a string expansion is done
 at the outer level. In the other cases, expansion already occurs in the
@@ -62,9 +136,26 @@ checking functions. */
 static uschar cond_expand_at_top[] = {
   TRUE,    /* acl */
   FALSE,   /* authenticated */
+#ifdef EXPERIMENTAL_BRIGHTMAIL
+  TRUE,    /* bmi_optin */
+#endif
   TRUE,    /* condition */
   TRUE,    /* control */
+#ifdef WITH_CONTENT_SCAN
+  TRUE,    /* decode */
+#endif
   TRUE,    /* delay */
+#ifdef WITH_OLD_DEMIME
+  TRUE,    /* demime */
+#endif
+#ifdef EXPERIMENTAL_DOMAINKEYS
+  TRUE,    /* dk_domain_source */
+  TRUE,    /* dk_policy */
+  TRUE,    /* dk_sender_domains */
+  TRUE,    /* dk_sender_local_parts */
+  TRUE,    /* dk_senders */
+  TRUE,    /* dk_status */
+#endif
   TRUE,    /* dnslists */
   FALSE,   /* domains */
   FALSE,   /* encrypted */
@@ -73,11 +164,26 @@ static uschar cond_expand_at_top[] = {
   FALSE,   /* local_parts */
   TRUE,    /* log_message */
   TRUE,    /* logwrite */
+#ifdef WITH_CONTENT_SCAN
+  TRUE,    /* malware */
+#endif
   TRUE,    /* message */
+#ifdef WITH_CONTENT_SCAN
+  TRUE,    /* mime_regex */
+#endif
   FALSE,   /* recipients */
+#ifdef WITH_CONTENT_SCAN
+  TRUE,    /* regex */
+#endif
   FALSE,   /* sender_domains */
   FALSE,   /* senders */
   TRUE,    /* set */
+#ifdef WITH_CONTENT_SCAN
+  TRUE,    /* spam */
+#endif
+#ifdef EXPERIMENTAL_SPF
+  TRUE,    /* spf */
+#endif
   TRUE     /* verify */
 };
 
@@ -86,9 +192,26 @@ static uschar cond_expand_at_top[] = {
 static uschar cond_modifiers[] = {
   FALSE,   /* acl */
   FALSE,   /* authenticated */
+#ifdef EXPERIMENTAL_BRIGHTMAIL
+  TRUE,    /* bmi_optin */
+#endif
   FALSE,   /* condition */
   TRUE,    /* control */
+#ifdef WITH_CONTENT_SCAN
+  FALSE,   /* decode */
+#endif
   TRUE,    /* delay */
+#ifdef WITH_OLD_DEMIME
+  FALSE,   /* demime */
+#endif
+#ifdef EXPERIMENTAL_DOMAINKEYS
+  FALSE,   /* dk_domain_source */
+  FALSE,   /* dk_policy */
+  FALSE,   /* dk_sender_domains */
+  FALSE,   /* dk_sender_local_parts */
+  FALSE,   /* dk_senders */
+  FALSE,   /* dk_status */
+#endif
   FALSE,   /* dnslists */
   FALSE,   /* domains */
   FALSE,   /* encrypted */
@@ -96,64 +219,159 @@ static uschar cond_modifiers[] = {
   FALSE,   /* hosts */
   FALSE,   /* local_parts */
   TRUE,    /* log_message */
-  TRUE,    /* log_write */
+  TRUE,    /* logwrite */
+#ifdef WITH_CONTENT_SCAN
+  FALSE,   /* malware */
+#endif
   TRUE,    /* message */
+#ifdef WITH_CONTENT_SCAN
+  FALSE,   /* mime_regex */
+#endif
   FALSE,   /* recipients */
+#ifdef WITH_CONTENT_SCAN
+  FALSE,   /* regex */
+#endif
   FALSE,   /* sender_domains */
   FALSE,   /* senders */
   TRUE,    /* set */
+#ifdef WITH_CONTENT_SCAN
+  FALSE,   /* spam */
+#endif
+#ifdef EXPERIMENTAL_SPF
+  FALSE,   /* spf */
+#endif
   FALSE    /* verify */
 };
 
 /* Bit map vector of which conditions are not allowed at certain times. For
-each condition, there's a bitmap of dis-allowed times. */
+each condition, there's a bitmap of dis-allowed times. For some, it is easier
+to specify the negation of a small number of allowed times. */
 
 static unsigned int cond_forbids[] = {
   0,                                               /* acl */
+
   (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_CONNECT)|   /* authenticated */
     (1<<ACL_WHERE_HELO),
+
+#ifdef EXPERIMENTAL_BRIGHTMAIL
+  (1<<ACL_WHERE_AUTH)|                             /* bmi_optin */
+    (1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO)|
+    (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_MIME)|
+    (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
+    (1<<ACL_WHERE_MAILAUTH)|
+    (1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_STARTTLS)|
+    (1<<ACL_WHERE_VRFY)|(1<<ACL_WHERE_PREDATA),
+#endif
+
   0,                                               /* condition */
 
   /* Certain types of control are always allowed, so we let it through
-  always and check in the control processing itself */
+  always and check in the control processing itself. */
 
   0,                                               /* control */
+
+#ifdef WITH_CONTENT_SCAN
+  (unsigned int)
+  ~(1<<ACL_WHERE_MIME),                            /* decode */
+#endif
+
   0,                                               /* delay */
-  (1<<ACL_WHERE_NOTSMTP),                          /* dnslists */
 
-  (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_AUTH)|      /* domains */
+#ifdef WITH_OLD_DEMIME
+  (unsigned int)
+  ~((1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP)),   /* demime */
+#endif
+
+#ifdef EXPERIMENTAL_DOMAINKEYS
+  (1<<ACL_WHERE_AUTH)|                             /* dk_domain_source */
     (1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO)|
-    (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_PREDATA)|
+    (1<<ACL_WHERE_RCPT)|(1<<ACL_WHERE_PREDATA)|
     (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
     (1<<ACL_WHERE_MAILAUTH)|(1<<ACL_WHERE_QUIT)|
     (1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_STARTTLS)|
     (1<<ACL_WHERE_VRFY),
 
-  (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_CONNECT)|   /* encrypted */
-    (1<<ACL_WHERE_HELO),
-  0,                                               /* endpass */
-  (1<<ACL_WHERE_NOTSMTP),                          /* hosts */
+  (1<<ACL_WHERE_AUTH)|                             /* dk_policy */
+    (1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO)|
+    (1<<ACL_WHERE_RCPT)|(1<<ACL_WHERE_PREDATA)|
+    (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
+    (1<<ACL_WHERE_MAILAUTH)|(1<<ACL_WHERE_QUIT)|
+    (1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_STARTTLS)|
+    (1<<ACL_WHERE_VRFY),
 
-  (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_AUTH)|      /* local_parts */
+  (1<<ACL_WHERE_AUTH)|                             /* dk_sender_domains */
     (1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO)|
-    (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_PREDATA)|
+    (1<<ACL_WHERE_RCPT)|(1<<ACL_WHERE_PREDATA)|
     (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
     (1<<ACL_WHERE_MAILAUTH)|(1<<ACL_WHERE_QUIT)|
     (1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_STARTTLS)|
     (1<<ACL_WHERE_VRFY),
 
-  0,                                               /* log_message */
-  0,                                               /* logwrite */
-  0,                                               /* message */
+  (1<<ACL_WHERE_AUTH)|                             /* dk_sender_local_parts */
+    (1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO)|
+    (1<<ACL_WHERE_RCPT)|(1<<ACL_WHERE_PREDATA)|
+    (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
+    (1<<ACL_WHERE_MAILAUTH)|(1<<ACL_WHERE_QUIT)|
+    (1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_STARTTLS)|
+    (1<<ACL_WHERE_VRFY),
 
-  (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_AUTH)|      /* recipients */
+  (1<<ACL_WHERE_AUTH)|                             /* dk_senders */
     (1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO)|
-    (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_PREDATA)|
+    (1<<ACL_WHERE_RCPT)|(1<<ACL_WHERE_PREDATA)|
     (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
     (1<<ACL_WHERE_MAILAUTH)|(1<<ACL_WHERE_QUIT)|
     (1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_STARTTLS)|
     (1<<ACL_WHERE_VRFY),
 
+  (1<<ACL_WHERE_AUTH)|                             /* dk_status */
+    (1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO)|
+    (1<<ACL_WHERE_RCPT)|(1<<ACL_WHERE_PREDATA)|
+    (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
+    (1<<ACL_WHERE_MAILAUTH)|(1<<ACL_WHERE_QUIT)|
+    (1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_STARTTLS)|
+    (1<<ACL_WHERE_VRFY),
+#endif
+
+  (1<<ACL_WHERE_NOTSMTP),                          /* dnslists */
+
+  (unsigned int)
+  ~(1<<ACL_WHERE_RCPT),                            /* domains */
+
+  (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_CONNECT)|   /* encrypted */
+    (1<<ACL_WHERE_HELO),
+
+  0,                                               /* endpass */
+
+  (1<<ACL_WHERE_NOTSMTP),                          /* hosts */
+
+  (unsigned int)
+  ~(1<<ACL_WHERE_RCPT),                            /* local_parts */
+
+  0,                                               /* log_message */
+
+  0,                                               /* logwrite */
+
+#ifdef WITH_CONTENT_SCAN
+  (unsigned int)
+  ~((1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP)),   /* malware */
+#endif
+
+  0,                                               /* message */
+
+#ifdef WITH_CONTENT_SCAN
+  (unsigned int)
+  ~(1<<ACL_WHERE_MIME),                            /* mime_regex */
+#endif
+
+  (unsigned int)
+  ~(1<<ACL_WHERE_RCPT),                            /* recipients */
+
+#ifdef WITH_CONTENT_SCAN
+  (unsigned int)
+  ~((1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP)|    /* regex */
+    (1<<ACL_WHERE_MIME)),
+#endif
+
   (1<<ACL_WHERE_AUTH)|(1<<ACL_WHERE_CONNECT)|      /* sender_domains */
     (1<<ACL_WHERE_HELO)|
     (1<<ACL_WHERE_MAILAUTH)|(1<<ACL_WHERE_QUIT)|
@@ -168,6 +386,19 @@ static unsigned int cond_forbids[] = {
 
   0,                                               /* set */
 
+#ifdef WITH_CONTENT_SCAN
+  (unsigned int)
+  ~((1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP)),   /* spam */
+#endif
+
+#ifdef EXPERIMENTAL_SPF
+  (1<<ACL_WHERE_AUTH)|(1<<ACL_WHERE_CONNECT)|      /* spf */
+    (1<<ACL_WHERE_HELO)|
+    (1<<ACL_WHERE_MAILAUTH)|
+    (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
+    (1<<ACL_WHERE_STARTTLS)|(1<<ACL_WHERE_VRFY),
+#endif
+
   /* Certain types of verify are always allowed, so we let it through
   always and check in the verify function itself */
 
@@ -177,32 +408,71 @@ static unsigned int cond_forbids[] = {
 
 /* Return values from decode_control() */
 
-enum { CONTROL_ERROR, CONTROL_CASEFUL_LOCAL_PART, CONTROL_CASELOWER_LOCAL_PART,
+enum {
+#ifdef EXPERIMENTAL_BRIGHTMAIL
+  CONTROL_BMI_RUN,
+#endif
+#ifdef EXPERIMENTAL_DOMAINKEYS
+  CONTROL_DK_VERIFY,
+#endif
+  CONTROL_ERROR, CONTROL_CASEFUL_LOCAL_PART, CONTROL_CASELOWER_LOCAL_PART,
   CONTROL_ENFORCE_SYNC, CONTROL_NO_ENFORCE_SYNC, CONTROL_FREEZE,
-  CONTROL_QUEUE_ONLY, CONTROL_SUBMISSION, CONTROL_NO_MULTILINE };
+  CONTROL_QUEUE_ONLY, CONTROL_SUBMISSION,
+#ifdef WITH_CONTENT_SCAN
+  CONTROL_NO_MBOX_UNSPOOL,
+#endif
+  CONTROL_FAKEREJECT, CONTROL_NO_MULTILINE };
 
 /* Bit map vector of which controls are not allowed at certain times. For
 each control, there's a bitmap of dis-allowed times. For some, it is easier to
 specify the negation of a small number of allowed times. */
 
 static unsigned int control_forbids[] = {
+#ifdef EXPERIMENTAL_BRIGHTMAIL
+  0,                                               /* bmi_run */
+#endif
+#ifdef EXPERIMENTAL_DOMAINKEYS
+  (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP),      /* dk_verify */
+#endif
+
   0,                                               /* error */
+
+  (unsigned int)
   ~(1<<ACL_WHERE_RCPT),                            /* caseful_local_part */
+
+  (unsigned int)
   ~(1<<ACL_WHERE_RCPT),                            /* caselower_local_part */
+
   (1<<ACL_WHERE_NOTSMTP),                          /* enforce_sync */
+
   (1<<ACL_WHERE_NOTSMTP),                          /* no_enforce_sync */
-   
+
+  (unsigned int)
   ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|       /* freeze */
     (1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
-    (1<<ACL_WHERE_NOTSMTP)),
-     
+    (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_MIME)),
+
+  (unsigned int)
   ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|       /* queue_only */
     (1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
-    (1<<ACL_WHERE_NOTSMTP)),
-     
+    (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_MIME)),
+
+  (unsigned int)
   ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|       /* submission */
-    (1<<ACL_WHERE_PREDATA)),                       
-     
+    (1<<ACL_WHERE_PREDATA)),
+
+#ifdef WITH_CONTENT_SCAN
+  (unsigned int)
+  ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|       /* no_mbox_unspool */
+    (1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
+    (1<<ACL_WHERE_MIME)),
+#endif
+
+  (unsigned int)
+  ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|       /* fakereject */
+    (1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
+    (1<<ACL_WHERE_MIME)),
+
   (1<<ACL_WHERE_NOTSMTP)                           /* no_multiline */
 };
 
@@ -215,6 +485,12 @@ typedef struct control_def {
 } control_def;
 
 static control_def controls_list[] = {
+#ifdef EXPERIMENTAL_BRIGHTMAIL
+  { US"bmi_run",                CONTROL_BMI_RUN, FALSE},
+#endif
+#ifdef EXPERIMENTAL_DOMAINKEYS
+  { US"dk_verify",              CONTROL_DK_VERIFY, FALSE},
+#endif
   { US"caseful_local_part",     CONTROL_CASEFUL_LOCAL_PART, FALSE},
   { US"caselower_local_part",   CONTROL_CASELOWER_LOCAL_PART, FALSE},
   { US"enforce_sync",           CONTROL_ENFORCE_SYNC, FALSE},
@@ -222,9 +498,52 @@ static control_def controls_list[] = {
   { US"no_enforce_sync",        CONTROL_NO_ENFORCE_SYNC, FALSE},
   { US"no_multiline_responses", CONTROL_NO_MULTILINE, FALSE},
   { US"queue_only",             CONTROL_QUEUE_ONLY, FALSE},
+#ifdef WITH_CONTENT_SCAN
+  { US"no_mbox_unspool",        CONTROL_NO_MBOX_UNSPOOL, FALSE},
+#endif
+  { US"fakereject",             CONTROL_FAKEREJECT, TRUE},
   { US"submission",             CONTROL_SUBMISSION, TRUE}
   };
 
+/* Support data structures for Client SMTP Authorization. acl_verify_csa()
+caches its result in a tree to avoid repeated DNS queries. The result is an
+integer code which is used as an index into the following tables of
+explanatory strings and verification return codes. */
+
+static tree_node *csa_cache = NULL;
+
+enum { CSA_UNKNOWN, CSA_OK, CSA_DEFER_SRV, CSA_DEFER_ADDR,
+ CSA_FAIL_EXPLICIT, CSA_FAIL_DOMAIN, CSA_FAIL_NOADDR, CSA_FAIL_MISMATCH };
+
+/* The acl_verify_csa() return code is translated into an acl_verify() return
+code using the following table. It is OK unless the client is definitely not
+authorized. This is because CSA is supposed to be optional for sending sites,
+so recipients should not be too strict about checking it - especially because
+DNS problems are quite likely to occur. It's possible to use $csa_status in
+further ACL conditions to distinguish ok, unknown, and defer if required, but
+the aim is to make the usual configuration simple. */
+
+static int csa_return_code[] = {
+  OK, OK, OK, OK,
+  FAIL, FAIL, FAIL, FAIL
+};
+
+static uschar *csa_status_string[] = {
+  US"unknown", US"ok", US"defer", US"defer",
+  US"fail", US"fail", US"fail", US"fail"
+};
+
+static uschar *csa_reason_string[] = {
+  US"unknown",
+  US"ok",
+  US"deferred (SRV lookup failed)",
+  US"deferred (target address lookup failed)",
+  US"failed (explicit authorization required)",
+  US"failed (host name not authorized)",
+  US"failed (no authorized addresses)",
+  US"failed (client address mismatch)"
+};
+
 /* Enable recursion between acl_check_internal() and acl_check_condition() */
 
 static int acl_check_internal(int, address_item *, uschar *, int, uschar **,
@@ -309,18 +628,11 @@ while ((s = (*func)()) != NULL)
     s++;
     }
 
-  /* Read the name of a verb or a condition, or the start of a new ACL */
+  /* Read the name of a verb or a condition, or the start of a new ACL, which
+  can be started by a name, or by a macro definition. */
 
   s = readconf_readname(name, sizeof(name), s);
-  if (*s == ':')
-    {
-    if (negated || name[0] == 0)
-      {
-      *error = string_sprintf("malformed ACL name in \"%s\"", saveline);
-      return NULL;
-      }
-    break;
-    }
+  if (*s == ':' || isupper(name[0] && *s == '=')) return yield;
 
   /* If a verb is unrecognized, it may be another condition or modifier that
   continues the previous verb. */
@@ -476,8 +788,8 @@ if (log_message != NULL && log_message != user_message)
       strcmpic(log_message, US"sender verify failed") == 0)
     text = string_sprintf("%s: %s", text, sender_verified_failed->message);
 
-  /* Search previously logged warnings. They are kept in malloc store so they
-  can be freed at the start of a new message. */
+  /* Search previously logged warnings. They are kept in malloc
+  store so they can be freed at the start of a new message. */
 
   for (logged = acl_warn_logged; logged != NULL; logged = logged->next)
     if (Ustrcmp(logged->text, text) == 0) break;
@@ -547,6 +859,11 @@ if (hlen > 0)
         newtype = htype_add_rec;
         p += 16;
         }
+      else if (strncmpic(p, US":at_start_rfc:", 14) == 0)
+        {
+        newtype = htype_add_rfc;
+        p += 14;
+        }
       else if (strncmpic(p, US":at_start:", 10) == 0)
         {
         newtype = htype_add_top;
@@ -659,6 +976,303 @@ return OK;
 
 
 
+/*************************************************
+*   Check client IP address matches CSA target   *
+*************************************************/
+
+/* Called from acl_verify_csa() below. This routine scans a section of a DNS
+response for address records belonging to the CSA target hostname. The section
+is specified by the reset argument, either RESET_ADDITIONAL or RESET_ANSWERS.
+If one of the addresses matches the client's IP address, then the client is
+authorized by CSA. If there are target IP addresses but none of them match
+then the client is using an unauthorized IP address. If there are no target IP
+addresses then the client cannot be using an authorized IP address. (This is
+an odd configuration - why didn't the SRV record have a weight of 1 instead?)
+
+Arguments:
+  dnsa       the DNS answer block
+  dnss       a DNS scan block for us to use
+  reset      option specifing what portion to scan, as described above
+  target     the target hostname to use for matching RR names
+
+Returns:     CSA_OK             successfully authorized
+             CSA_FAIL_MISMATCH  addresses found but none matched
+             CSA_FAIL_NOADDR    no target addresses found
+*/
+
+static int
+acl_verify_csa_address(dns_answer *dnsa, dns_scan *dnss, int reset,
+                       uschar *target)
+{
+dns_record *rr;
+dns_address *da;
+
+BOOL target_found = FALSE;
+
+for (rr = dns_next_rr(dnsa, dnss, reset);
+     rr != NULL;
+     rr = dns_next_rr(dnsa, dnss, RESET_NEXT))
+  {
+  /* Check this is an address RR for the target hostname. */
+
+  if (rr->type != T_A
+    #if HAVE_IPV6
+      && rr->type != T_AAAA
+      #ifdef SUPPORT_A6
+        && rr->type != T_A6
+      #endif
+    #endif
+  ) continue;
+
+  if (strcmpic(target, rr->name) != 0) continue;
+
+  target_found = TRUE;
+
+  /* Turn the target address RR into a list of textual IP addresses and scan
+  the list. There may be more than one if it is an A6 RR. */
+
+  for (da = dns_address_from_rr(dnsa, rr); da != NULL; da = da->next)
+    {
+    /* If the client IP address matches the target IP address, it's good! */
+
+    DEBUG(D_acl) debug_printf("CSA target address is %s\n", da->address);
+
+    if (strcmpic(sender_host_address, da->address) == 0) return CSA_OK;
+    }
+  }
+
+/* If we found some target addresses but none of them matched, the client is
+using an unauthorized IP address, otherwise the target has no authorized IP
+addresses. */
+
+if (target_found) return CSA_FAIL_MISMATCH;
+else return CSA_FAIL_NOADDR;
+}
+
+
+
+/*************************************************
+*       Verify Client SMTP Authorization         *
+*************************************************/
+
+/* Called from acl_verify() below. This routine calls dns_lookup_special()
+to find the CSA SRV record corresponding to the domain argument, or
+$sender_helo_name if no argument is provided. It then checks that the
+client is authorized, and that its IP address corresponds to the SRV
+target's address by calling acl_verify_csa_address() above. The address
+should have been returned in the DNS response's ADDITIONAL section, but if
+not we perform another DNS lookup to get it.
+
+Arguments:
+  domain    pointer to optional parameter following verify = csa
+
+Returns:    CSA_UNKNOWN    no valid CSA record found
+            CSA_OK         successfully authorized
+            CSA_FAIL_*     client is definitely not authorized
+            CSA_DEFER_*    there was a DNS problem
+*/
+
+static int
+acl_verify_csa(uschar *domain)
+{
+tree_node *t;
+uschar *found, *p;
+int priority, weight, port;
+dns_answer dnsa;
+dns_scan dnss;
+dns_record *rr;
+int rc, type;
+uschar target[256];
+
+/* Work out the domain we are using for the CSA lookup. The default is the
+client's HELO domain. If the client has not said HELO, use its IP address
+instead. If it's a local client (exim -bs), CSA isn't applicable. */
+
+while (isspace(*domain) && *domain != '\0') ++domain;
+if (*domain == '\0') domain = sender_helo_name;
+if (domain == NULL) domain = sender_host_address;
+if (sender_host_address == NULL) return CSA_UNKNOWN;
+
+/* If we have an address literal, strip off the framing ready for turning it
+into a domain. The framing consists of matched square brackets possibly
+containing a keyword and a colon before the actual IP address. */
+
+if (domain[0] == '[')
+  {
+  uschar *start = Ustrchr(domain, ':');
+  if (start == NULL) start = domain;
+  domain = string_copyn(start + 1, Ustrlen(start) - 2);
+  }
+
+/* Turn domains that look like bare IP addresses into domains in the reverse
+DNS. This code also deals with address literals and $sender_host_address. It's
+not quite kosher to treat bare domains such as EHLO 192.0.2.57 the same as
+address literals, but it's probably the most friendly thing to do. This is an
+extension to CSA, so we allow it to be turned off for proper conformance. */
+
+if (string_is_ip_address(domain, NULL))
+  {
+  if (!dns_csa_use_reverse) return CSA_UNKNOWN;
+  dns_build_reverse(domain, target);
+  domain = target;
+  }
+
+/* Find out if we've already done the CSA check for this domain. If we have,
+return the same result again. Otherwise build a new cached result structure
+for this domain. The name is filled in now, and the value is filled in when
+we return from this function. */
+
+t = tree_search(csa_cache, domain);
+if (t != NULL) return t->data.val;
+
+t = store_get_perm(sizeof(tree_node) + Ustrlen(domain));
+Ustrcpy(t->name, domain);
+(void)tree_insertnode(&csa_cache, t);
+
+/* Now we are ready to do the actual DNS lookup(s). */
+
+switch (dns_special_lookup(&dnsa, domain, T_CSA, &found))
+  {
+  /* If something bad happened (most commonly DNS_AGAIN), defer. */
+
+  default:
+  return t->data.val = CSA_DEFER_SRV;
+
+  /* If we found nothing, the client's authorization is unknown. */
+
+  case DNS_NOMATCH:
+  case DNS_NODATA:
+  return t->data.val = CSA_UNKNOWN;
+
+  /* We got something! Go on to look at the reply in more detail. */
+
+  case DNS_SUCCEED:
+  break;
+  }
+
+/* Scan the reply for well-formed CSA SRV records. */
+
+for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
+     rr != NULL;
+     rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
+  {
+  if (rr->type != T_SRV) continue;
+
+  /* Extract the numerical SRV fields (p is incremented) */
+
+  p = rr->data;
+  GETSHORT(priority, p);
+  GETSHORT(weight, p);
+  GETSHORT(port, p);
+
+  DEBUG(D_acl)
+    debug_printf("CSA priority=%d weight=%d port=%d\n", priority, weight, port);
+
+  /* Check the CSA version number */
+
+  if (priority != 1) continue;
+
+  /* If the domain does not have a CSA SRV record of its own (i.e. the domain
+  found by dns_special_lookup() is a parent of the one we asked for), we check
+  the subdomain assertions in the port field. At the moment there's only one
+  assertion: legitimate SMTP clients are all explicitly authorized with CSA
+  SRV records of their own. */
+
+  if (found != domain)
+    {
+    if (port & 1)
+      return t->data.val = CSA_FAIL_EXPLICIT;
+    else
+      return t->data.val = CSA_UNKNOWN;
+    }
+
+  /* This CSA SRV record refers directly to our domain, so we check the value
+  in the weight field to work out the domain's authorization. 0 and 1 are
+  unauthorized; 3 means the client is authorized but we can't check the IP
+  address in order to authenticate it, so we treat it as unknown; values
+  greater than 3 are undefined. */
+
+  if (weight < 2) return t->data.val = CSA_FAIL_DOMAIN;
+
+  if (weight > 2) continue;
+
+  /* Weight == 2, which means the domain is authorized. We must check that the
+  client's IP address is listed as one of the SRV target addresses. Save the
+  target hostname then break to scan the additional data for its addresses. */
+
+  (void)dn_expand(dnsa.answer, dnsa.answer + dnsa.answerlen, p,
+    (DN_EXPAND_ARG4_TYPE)target, sizeof(target));
+
+  DEBUG(D_acl) debug_printf("CSA target is %s\n", target);
+
+  break;
+  }
+
+/* If we didn't break the loop then no appropriate records were found. */
+
+if (rr == NULL) return t->data.val = CSA_UNKNOWN;
+
+/* Do not check addresses if the target is ".", in accordance with RFC 2782.
+A target of "." indicates there are no valid addresses, so the client cannot
+be authorized. (This is an odd configuration because weight=2 target=. is
+equivalent to weight=1, but we check for it in order to keep load off the
+root name servers.) Note that dn_expand() turns "." into "". */
+
+if (Ustrcmp(target, "") == 0) return t->data.val = CSA_FAIL_NOADDR;
+
+/* Scan the additional section of the CSA SRV reply for addresses belonging
+to the target. If the name server didn't return any additional data (e.g.
+because it does not fully support SRV records), we need to do another lookup
+to obtain the target addresses; otherwise we have a definitive result. */
+
+rc = acl_verify_csa_address(&dnsa, &dnss, RESET_ADDITIONAL, target);
+if (rc != CSA_FAIL_NOADDR) return t->data.val = rc;
+
+/* The DNS lookup type corresponds to the IP version used by the client. */
+
+#if HAVE_IPV6
+if (Ustrchr(sender_host_address, ':') != NULL)
+  type = T_AAAA;
+else
+#endif /* HAVE_IPV6 */
+  type = T_A;
+
+
+#if HAVE_IPV6 && defined(SUPPORT_A6)
+DNS_LOOKUP_AGAIN:
+#endif
+
+switch (dns_lookup(&dnsa, target, type, NULL))
+  {
+  /* If something bad happened (most commonly DNS_AGAIN), defer. */
+
+  default:
+  return t->data.val = CSA_DEFER_ADDR;
+
+  /* If the query succeeded, scan the addresses and return the result. */
+
+  case DNS_SUCCEED:
+  rc = acl_verify_csa_address(&dnsa, &dnss, RESET_ANSWERS, target);
+  if (rc != CSA_FAIL_NOADDR) return t->data.val = rc;
+  /* else fall through */
+
+  /* If the target has no IP addresses, the client cannot have an authorized
+  IP address. However, if the target site uses A6 records (not AAAA records)
+  we have to do yet another lookup in order to check them. */
+
+  case DNS_NOMATCH:
+  case DNS_NODATA:
+
+  #if HAVE_IPV6 && defined(SUPPORT_A6)
+  if (type == T_AAAA) { type = T_A6; goto DNS_LOOKUP_AGAIN; }
+  #endif
+
+  return t->data.val = CSA_FAIL_NOADDR;
+  }
+}
+
+
+
 /*************************************************
 *     Handle verification (address & other)      *
 *************************************************/
@@ -700,6 +1314,13 @@ address_item *sender_vaddr = NULL;
 uschar *verify_sender_address = NULL;
 uschar *pm_mailfrom = NULL;
 uschar *se_mailfrom = NULL;
+
+/* Some of the verify items have slash-separated options; some do not. Diagnose
+an error if options are given for items that don't expect them. This code has
+now got very message. Refactoring to use a table would be a good idea one day.
+*/
+
+uschar *slash = Ustrchr(arg, '/');
 uschar *list = arg;
 uschar *ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size);
 
@@ -709,6 +1330,7 @@ if (ss == NULL) goto BAD_VERIFY;
 
 if (strcmpic(ss, US"reverse_host_lookup") == 0)
   {
+  if (slash != NULL) goto NO_OPTIONS;
   if (sender_host_address == NULL) return OK;
   return acl_verify_reverse(user_msgptr, log_msgptr);
   }
@@ -719,6 +1341,7 @@ mandatory verification, the connection doesn't last this long.) */
 
 if (strcmpic(ss, US"certificate") == 0)
   {
+  if (slash != NULL) goto NO_OPTIONS;
   if (tls_certificate_verified) return OK;
   *user_msgptr = US"no verified certificate";
   return FAIL;
@@ -726,42 +1349,64 @@ if (strcmpic(ss, US"certificate") == 0)
 
 /* We can test the result of optional HELO verification */
 
-if (strcmpic(ss, US"helo") == 0) return helo_verified? OK : FAIL;
+if (strcmpic(ss, US"helo") == 0)
+  {
+  if (slash != NULL) goto NO_OPTIONS;
+  return helo_verified? OK : FAIL;
+  }
 
-/* Handle header verification options - permitted only after DATA or a non-SMTP
-message. */
+/* Do Client SMTP Authorization checks in a separate function, and turn the
+result code into user-friendly strings. */
 
-if (strncmpic(ss, US"header_", 7) == 0)
+if (strcmpic(ss, US"csa") == 0)
   {
+  rc = acl_verify_csa(list);
+  *log_msgptr = *user_msgptr = string_sprintf("client SMTP authorization %s",
+                                              csa_reason_string[rc]);
+  csa_status = csa_status_string[rc];
+  DEBUG(D_acl) debug_printf("CSA result %s\n", csa_status);
+  return csa_return_code[rc];
+  }
+
+/* Check that all relevant header lines have the correct syntax. If there is
+a syntax error, we return details of the error to the sender if configured to
+send out full details. (But a "message" setting on the ACL can override, as
+always). */
+
+if (strcmpic(ss, US"header_syntax") == 0)
+  {
+  if (slash != NULL) goto NO_OPTIONS;
   if (where != ACL_WHERE_DATA && where != ACL_WHERE_NOTSMTP)
     {
     *log_msgptr = string_sprintf("cannot check header contents in ACL for %s "
       "(only possible in ACL for DATA)", acl_wherenames[where]);
     return ERROR;
     }
+  rc = verify_check_headers(log_msgptr);
+  if (rc != OK && smtp_return_error_details && *log_msgptr != NULL)
+    *user_msgptr = string_sprintf("Rejected after DATA: %s", *log_msgptr);
+  return rc;
+  }
 
-  /* Check that all relevant header lines have the correct syntax. If there is
-  a syntax error, we return details of the error to the sender if configured to
-  send out full details. (But a "message" setting on the ACL can override, as
-  always). */
 
-  if (strcmpic(ss+7, US"syntax") == 0)
-    {
-    int rc = verify_check_headers(log_msgptr);
-    if (rc != OK && smtp_return_error_details && *log_msgptr != NULL)
-      *user_msgptr = string_sprintf("Rejected after DATA: %s", *log_msgptr);
-    return rc;
-    }
-
-  /* Check that there is at least one verifiable sender address in the relevant
-  header lines. This can be followed by callout and defer options, just like
-  sender and recipient. */
+/* The remaining verification tests check recipient and sender addresses,
+either from the envelope or from the header. There are a number of
+slash-separated options that are common to all of them. */
 
-  else if (strcmpic(ss+7, US"sender") == 0) verify_header_sender = TRUE;
 
-  /* Unknown verify argument starting with "header_" */
+/* Check that there is at least one verifiable sender address in the relevant
+header lines. This can be followed by callout and defer options, just like
+sender and recipient. */
 
-  else goto BAD_VERIFY;
+if (strcmpic(ss, US"header_sender") == 0)
+  {
+  if (where != ACL_WHERE_DATA && where != ACL_WHERE_NOTSMTP)
+    {
+    *log_msgptr = string_sprintf("cannot check header contents in ACL for %s "
+      "(only possible in ACL for DATA)", acl_wherenames[where]);
+    return ERROR;
+    }
+  verify_header_sender = TRUE;
   }
 
 /* Otherwise, first item in verify argument must be "sender" or "recipient".
@@ -799,7 +1444,8 @@ else
     }
   }
 
-/* Remaining items are optional */
+/* Remaining items are optional; they apply to sender and recipient
+verification, including "header sender" verification. */
 
 while ((ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size))
       != NULL)
@@ -836,11 +1482,11 @@ while ((ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size))
         uschar *opt;
         uschar buffer[256];
         while (isspace(*ss)) ss++;
-        
-        /* This callout option handling code has become a mess as new options 
-        have been added in an ad hoc manner. It should be tidied up into some 
+
+        /* This callout option handling code has become a mess as new options
+        have been added in an ad hoc manner. It should be tidied up into some
         kind of table-driven thing. */
+
         while ((opt = string_nextinlist(&ss, &optsep, buffer, sizeof(buffer)))
               != NULL)
           {
@@ -972,13 +1618,19 @@ message if giving out verification details. */
 
 if (verify_header_sender)
   {
+  int verrno;
   rc = verify_check_header_address(user_msgptr, log_msgptr, callout,
-    callout_overall, callout_connect, se_mailfrom, pm_mailfrom, verify_options);
-  if (smtp_return_error_details)
+    callout_overall, callout_connect, se_mailfrom, pm_mailfrom, verify_options,
+    &verrno);
+  if (rc != OK)
     {
-    if (*user_msgptr == NULL && *log_msgptr != NULL)
-      *user_msgptr = string_sprintf("Rejected after DATA: %s", *log_msgptr);
-    if (rc == DEFER) acl_temp_details = TRUE;
+    *basic_errno = verrno;
+    if (smtp_return_error_details)
+      {
+      if (*user_msgptr == NULL && *log_msgptr != NULL)
+        *user_msgptr = string_sprintf("Rejected after DATA: %s", *log_msgptr);
+      if (rc == DEFER) acl_temp_details = TRUE;
+      }
     }
   }
 
@@ -1038,7 +1690,7 @@ else if (verify_sender_address != NULL)
     {
     BOOL routed = TRUE;
     uschar *save_address_data = deliver_address_data;
-      
+
     sender_vaddr = deliver_make_addr(verify_sender_address, TRUE);
     if (no_details) setflag(sender_vaddr, af_sverify_told);
     if (verify_sender_address[0] != 0)
@@ -1084,16 +1736,16 @@ else if (verify_sender_address != NULL)
     sender_vaddr->special_action = rc;
     sender_vaddr->next = sender_verified_list;
     sender_verified_list = sender_vaddr;
-    
-    /* Restore the recipient address data, which might have been clobbered by 
+
+    /* Restore the recipient address data, which might have been clobbered by
     the sender verification. */
-  
+
     deliver_address_data = save_address_data;
     }
-    
+
   /* Put the sender address_data value into $sender_address_data */
 
-  sender_address_data = sender_vaddr->p.address_data; 
+  sender_address_data = sender_vaddr->p.address_data;
   }
 
 /* A recipient address just gets a straightforward verify; again we must handle
@@ -1110,8 +1762,10 @@ else
   rc = verify_address(&addr2, NULL, verify_options|vopt_is_recipient, callout,
     callout_overall, callout_connect, se_mailfrom, pm_mailfrom, NULL);
   HDEBUG(D_acl) debug_printf("----------- end verify ------------\n");
+
   *log_msgptr = addr2.message;
-  *user_msgptr = addr2.user_message;
+  *user_msgptr = (addr2.user_message != NULL)?
+    addr2.user_message : addr2.message;
   *basic_errno = addr2.basic_errno;
 
   /* Make $address_data visible */
@@ -1165,9 +1819,17 @@ return rc;
 
 BAD_VERIFY:
 *log_msgptr = string_sprintf("expected \"sender[=address]\", \"recipient\", "
-  "\"header_syntax\" or \"header_sender\" at start of ACL condition "
+  "\"helo\", \"header_syntax\", \"header_sender\" or "
+  "\"reverse_host_lookup\" at start of ACL condition "
   "\"verify %s\"", arg);
 return ERROR;
+
+/* Options supplied when not allowed come here */
+
+NO_OPTIONS:
+*log_msgptr = string_sprintf("unexpected '/' found in \"%s\" "
+  "(this verify item has no options)", arg);
+return ERROR;
 }
 
 
@@ -1253,11 +1915,14 @@ uschar *user_message = NULL;
 uschar *log_message = NULL;
 uschar *p;
 int rc = OK;
+#ifdef WITH_CONTENT_SCAN
+int sep = '/';
+#endif
 
 for (; cb != NULL; cb = cb->next)
   {
   uschar *arg;
-  int control_type; 
+  int control_type;
 
   /* The message and log_message items set up messages to be used in
   case of rejection. They are expanded later. */
@@ -1360,6 +2025,17 @@ for (; cb != NULL; cb = cb->next)
         TRUE, NULL);
     break;
 
+#ifdef EXPERIMENTAL_BRIGHTMAIL
+    case ACLC_BMI_OPTIN:
+      {
+      int old_pool = store_pool;
+      store_pool = POOL_PERM;
+      bmi_current_optin = string_copy(arg);
+      store_pool = old_pool;
+      }
+    break;
+#endif
+
     case ACLC_CONDITION:
     if (Ustrspn(arg, "0123456789") == Ustrlen(arg))     /* Digits, or empty */
       rc = (Uatoi(arg) == 0)? FAIL : OK;
@@ -1375,17 +2051,27 @@ for (; cb != NULL; cb = cb->next)
     case ACLC_CONTROL:
     control_type = decode_control(arg, &p, where, log_msgptr);
 
-    /* Check this control makes sense at this time */
+    /* Check if this control makes sense at this time */
 
     if ((control_forbids[control_type] & (1 << where)) != 0)
       {
       *log_msgptr = string_sprintf("cannot use \"control=%s\" in %s ACL",
         controls[control_type], acl_wherenames[where]);
       return ERROR;
-      }                                                     
+      }
 
     switch(control_type)
       {
+#ifdef EXPERIMENTAL_BRIGHTMAIL
+      case CONTROL_BMI_RUN:
+      bmi_run = 1;
+      break;
+#endif
+#ifdef EXPERIMENTAL_DOMAINKEYS
+      case CONTROL_DK_VERIFY:
+      dk_do_verify = 1;
+      break;
+#endif
       case CONTROL_ERROR:
       return ERROR;
 
@@ -1405,10 +2091,32 @@ for (; cb != NULL; cb = cb->next)
       smtp_enforce_sync = FALSE;
       break;
 
+#ifdef WITH_CONTENT_SCAN
+      case CONTROL_NO_MBOX_UNSPOOL:
+      no_mbox_unspool = TRUE;
+      break;
+#endif
+
       case CONTROL_NO_MULTILINE:
       no_multiline_responses = TRUE;
       break;
 
+      case CONTROL_FAKEREJECT:
+      fake_reject = TRUE;
+      if (*p == '/')
+        {
+        uschar *pp = p + 1;
+        while (*pp != 0) pp++;
+        fake_reject_text = expand_string(string_copyn(p+1, pp-p-1));
+        p = pp;
+        }
+       else
+        {
+        /* Explicitly reset to default string */
+        fake_reject_text = US"Your message has been rejected but is being kept for evaluation.\nIf it was a legitimate message, it may still be delivered to the target recipient(s).";
+        }
+      break;
+
       case CONTROL_FREEZE:
       deliver_freeze = TRUE;
       deliver_frozen_at = time(NULL);
@@ -1419,24 +2127,33 @@ for (; cb != NULL; cb = cb->next)
       break;
 
       case CONTROL_SUBMISSION:
+      originator_name = US"";
       submission_mode = TRUE;
       while (*p == '/')
-        { 
+        {
         if (Ustrncmp(p, "/sender_retain", 14) == 0)
           {
           p += 14;
           active_local_sender_retain = TRUE;
-          active_local_from_check = FALSE;   
-          }  
+          active_local_from_check = FALSE;
+          }
         else if (Ustrncmp(p, "/domain=", 8) == 0)
           {
           uschar *pp = p + 8;
-          while (*pp != 0 && *pp != '/') pp++; 
-          submission_domain = string_copyn(p+8, pp-p);
-          p = pp; 
+          while (*pp != 0 && *pp != '/') pp++;
+          submission_domain = string_copyn(p+8, pp-p-8);
+          p = pp;
+          }
+        else if (Ustrncmp(p, "/name=", 6) == 0)
+          {
+          uschar *pp = p + 6;
+          while (*pp != 0 && *pp != '/') pp++;
+          originator_name = string_copy(parse_fix_phrase(p+6, pp-p-6,
+            big_buffer, big_buffer_size));
+          p = pp;
           }
-        else break;   
-        }   
+        else break;
+        }
       if (*p != 0)
         {
         *log_msgptr = string_sprintf("syntax error in \"control=%s\"", arg);
@@ -1446,6 +2163,12 @@ for (; cb != NULL; cb = cb->next)
       }
     break;
 
+#ifdef WITH_CONTENT_SCAN
+    case ACLC_DECODE:
+    rc = mime_decode(&arg);
+    break;
+#endif
+
     case ACLC_DELAY:
       {
       int delay = readconf_readtime(arg, 0, FALSE);
@@ -1464,11 +2187,123 @@ for (; cb != NULL; cb = cb->next)
           HDEBUG(D_acl)
             debug_printf("delay skipped in -bh checking mode\n");
           }
-        else sleep(delay);
+
+        /* It appears to be impossible to detect that a TCP/IP connection has
+        gone away without reading from it. This means that we cannot shorten
+        the delay below if the client goes away, because we cannot discover
+        that the client has closed its end of the connection. (The connection
+        is actually in a half-closed state, waiting for the server to close its
+        end.) It would be nice to be able to detect this state, so that the
+        Exim process is not held up unnecessarily. However, it seems that we
+        can't. The poll() function does not do the right thing, and in any case
+        it is not always available.
+
+        NOTE: If ever this state of affairs changes, remember that we may be
+        dealing with stdin/stdout here, in addition to TCP/IP connections.
+        Whatever is done must work in both cases. To detected the stdin/stdout
+        case, check for smtp_in or smtp_out being NULL. */
+
+        else
+          {
+          while (delay > 0) delay = sleep(delay);
+          }
         }
       }
     break;
 
+#ifdef WITH_OLD_DEMIME
+    case ACLC_DEMIME:
+      rc = demime(&arg);
+    break;
+#endif
+
+#ifdef EXPERIMENTAL_DOMAINKEYS
+  case ACLC_DK_DOMAIN_SOURCE:
+    if (dk_verify_block == NULL) { rc = FAIL; break; };
+    /* check header source of domain against given string */
+    switch (dk_verify_block->address_source) {
+      case DK_EXIM_ADDRESS_FROM_FROM:
+        rc = match_isinlist(US"from", &arg, 0, NULL,
+                            NULL, MCL_STRING, TRUE, NULL);
+      break;
+      case DK_EXIM_ADDRESS_FROM_SENDER:
+        rc = match_isinlist(US"sender", &arg, 0, NULL,
+                            NULL, MCL_STRING, TRUE, NULL);
+      break;
+      case DK_EXIM_ADDRESS_NONE:
+        rc = match_isinlist(US"none", &arg, 0, NULL,
+                            NULL, MCL_STRING, TRUE, NULL);
+      break;
+    }
+  break;
+  case ACLC_DK_POLICY:
+    if (dk_verify_block == NULL) { rc = FAIL; break; };
+    /* check policy against given string, default FAIL */
+    rc = FAIL;
+    if (dk_verify_block->signsall)
+      rc = match_isinlist(US"signsall", &arg, 0, NULL,
+                          NULL, MCL_STRING, TRUE, NULL);
+    if (dk_verify_block->testing)
+      rc = match_isinlist(US"testing", &arg, 0, NULL,
+                          NULL, MCL_STRING, TRUE, NULL);
+  break;
+  case ACLC_DK_SENDER_DOMAINS:
+    if (dk_verify_block == NULL) { rc = FAIL; break; };
+    if (dk_verify_block->domain != NULL)
+      rc = match_isinlist(dk_verify_block->domain, &arg, 0, &domainlist_anchor,
+                          NULL, MCL_DOMAIN, TRUE, NULL);
+    else rc = FAIL;
+  break;
+  case ACLC_DK_SENDER_LOCAL_PARTS:
+    if (dk_verify_block == NULL) { rc = FAIL; break; };
+    if (dk_verify_block->local_part != NULL)
+      rc = match_isinlist(dk_verify_block->local_part, &arg, 0, &localpartlist_anchor,
+                          NULL, MCL_LOCALPART, TRUE, NULL);
+    else rc = FAIL;
+  break;
+  case ACLC_DK_SENDERS:
+    if (dk_verify_block == NULL) { rc = FAIL; break; };
+    if (dk_verify_block->address != NULL)
+      rc = match_address_list(dk_verify_block->address, TRUE, TRUE, &arg, NULL, -1, 0, NULL);
+    else rc = FAIL;
+  break;
+  case ACLC_DK_STATUS:
+    if (dk_verify_block == NULL) { rc = FAIL; break; };
+    if (dk_verify_block->result > 0) {
+      switch(dk_verify_block->result) {
+        case DK_EXIM_RESULT_BAD_FORMAT:
+          rc = match_isinlist(US"bad format", &arg, 0, NULL,
+                              NULL, MCL_STRING, TRUE, NULL);
+        break;
+        case DK_EXIM_RESULT_NO_KEY:
+          rc = match_isinlist(US"no key", &arg, 0, NULL,
+                              NULL, MCL_STRING, TRUE, NULL);
+        break;
+        case DK_EXIM_RESULT_NO_SIGNATURE:
+          rc = match_isinlist(US"no signature", &arg, 0, NULL,
+                              NULL, MCL_STRING, TRUE, NULL);
+        break;
+        case DK_EXIM_RESULT_REVOKED:
+          rc = match_isinlist(US"revoked", &arg, 0, NULL,
+                              NULL, MCL_STRING, TRUE, NULL);
+        break;
+        case DK_EXIM_RESULT_NON_PARTICIPANT:
+          rc = match_isinlist(US"non-participant", &arg, 0, NULL,
+                              NULL, MCL_STRING, TRUE, NULL);
+        break;
+        case DK_EXIM_RESULT_GOOD:
+          rc = match_isinlist(US"good", &arg, 0, NULL,
+                              NULL, MCL_STRING, TRUE, NULL);
+        break;
+        case DK_EXIM_RESULT_BAD:
+          rc = match_isinlist(US"bad", &arg, 0, NULL,
+                              NULL, MCL_STRING, TRUE, NULL);
+        break;
+      }
+    }
+  break;
+#endif
+
     case ACLC_DNSLISTS:
     rc = verify_check_dnsbl(&arg);
     break;
@@ -1549,11 +2384,41 @@ for (; cb != NULL; cb = cb->next)
       }
     break;
 
+#ifdef WITH_CONTENT_SCAN
+    case ACLC_MALWARE:
+      {
+      /* Seperate the regular expression and any optional parameters. */
+      uschar *ss = string_nextinlist(&arg, &sep, big_buffer, big_buffer_size);
+      /* Run the malware backend. */
+      rc = malware(&ss);
+      /* Modify return code based upon the existance of options. */
+      while ((ss = string_nextinlist(&arg, &sep, big_buffer, big_buffer_size))
+            != NULL) {
+        if (strcmpic(ss, US"defer_ok") == 0 && rc == DEFER)
+          {
+          /* FAIL so that the message is passed to the next ACL */
+          rc = FAIL;
+          }
+        }
+      }
+    break;
+
+    case ACLC_MIME_REGEX:
+      rc = mime_regex(&arg);
+    break;
+#endif
+
     case ACLC_RECIPIENTS:
     rc = match_address_list(addr->address, TRUE, TRUE, &arg, NULL, -1, 0,
       &recipient_data);
     break;
 
+#ifdef WITH_CONTENT_SCAN
+   case ACLC_REGEX:
+      rc = regex(&arg);
+    break;
+#endif
+
     case ACLC_SENDER_DOMAINS:
       {
       uschar *sdomain;
@@ -1580,12 +2445,41 @@ for (; cb != NULL; cb = cb->next)
       }
     break;
 
+#ifdef WITH_CONTENT_SCAN
+    case ACLC_SPAM:
+      {
+      /* Seperate the regular expression and any optional parameters. */
+      uschar *ss = string_nextinlist(&arg, &sep, big_buffer, big_buffer_size);
+      /* Run the spam backend. */
+      rc = spam(&ss);
+      /* Modify return code based upon the existance of options. */
+      while ((ss = string_nextinlist(&arg, &sep, big_buffer, big_buffer_size))
+            != NULL) {
+        if (strcmpic(ss, US"defer_ok") == 0 && rc == DEFER)
+          {
+          /* FAIL so that the message is passed to the next ACL */
+          rc = FAIL;
+          }
+        }
+      }
+    break;
+#endif
+
+#ifdef EXPERIMENTAL_SPF
+    case ACLC_SPF:
+      rc = spf_process(&arg, sender_address);
+    break;
+#endif
+
     /* If the verb is WARN, discard any user message from verification, because
     such messages are SMTP responses, not header additions. The latter come
-    only from explicit "message" modifiers. */
+    only from explicit "message" modifiers. However, put the user message into
+    $acl_verify_message so it can be used in subsequent conditions or modifiers
+    (until something changes it). */
 
     case ACLC_VERIFY:
     rc = acl_verify(where, addr, arg, user_msgptr, log_msgptr, basic_errno);
+    acl_verify_message = *user_msgptr;
     if (verb == ACL_WARN) *user_msgptr = NULL;
     break;
 
@@ -2065,9 +2959,10 @@ while (acl != NULL)
     if (cond == OK)
       acl_warn(where, *user_msgptr, *log_msgptr);
     else if (cond == DEFER)
-      acl_warn(where, NULL, string_sprintf("ACL \"warn\" statement skipped: "
-        "condition test deferred: %s",
-        (*log_msgptr == NULL)? US"" : *log_msgptr));
+      log_write(0, LOG_MAIN, "%s Warning: ACL \"warn\" statement skipped: "
+        "condition test deferred%s%s", host_and_ident(TRUE),
+        (*log_msgptr == NULL)? US"" : US": ",
+        (*log_msgptr == NULL)? US"" : *log_msgptr);
     *log_msgptr = *user_msgptr = NULL;  /* In case implicit DENY follows */
     break;