ACL: Enforce non-usability of control=utf8_downconvert in MAIL ACL. Bug 2239
[exim.git] / src / src / acl.c
index cf7e42aa4bc222b846504a468f65dd27e8bd8e97..fb8b75bc7b28c72e489b465c83de23336f50a900 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Code for handling Access Control Lists (ACLs) */
@@ -22,13 +22,14 @@ enum { ACL_ACCEPT, ACL_DEFER, ACL_DENY, ACL_DISCARD, ACL_DROP, ACL_REQUIRE,
 /* ACL verbs */
 
 static uschar *verbs[] = {
-    US"accept",
-    US"defer",
-    US"deny",
-    US"discard",
-    US"drop",
-    US"require",
-    US"warn" };
+    [ACL_ACCEPT] =     US"accept",
+    [ACL_DEFER] =      US"defer",
+    [ACL_DENY] =       US"deny",
+    [ACL_DISCARD] =    US"discard",
+    [ACL_DROP] =       US"drop",
+    [ACL_REQUIRE] =    US"require",
+    [ACL_WARN] =       US"warn"
+};
 
 /* For each verb, the conditions for which "message" or "log_message" are used
 are held as a bitmap. This is to avoid expanding the strings unnecessarily. For
@@ -36,13 +37,13 @@ are held as a bitmap. This is to avoid expanding the strings unnecessarily. For
 the code. */
 
 static int msgcond[] = {
-  (1<<OK) | (1<<FAIL) | (1<<FAIL_DROP),  /* accept */
-  (1<<OK),                               /* defer */
-  (1<<OK),                               /* deny */
-  (1<<OK) | (1<<FAIL) | (1<<FAIL_DROP),  /* discard */
-  (1<<OK),                               /* drop */
-  (1<<FAIL) | (1<<FAIL_DROP),            /* require */
-  (1<<OK)                                /* warn */
+  [ACL_ACCEPT] =       BIT(OK) | BIT(FAIL) | BIT(FAIL_DROP),
+  [ACL_DEFER] =                BIT(OK),
+  [ACL_DENY] =         BIT(OK),
+  [ACL_DISCARD] =      BIT(OK) | BIT(FAIL) | BIT(FAIL_DROP),
+  [ACL_DROP] =         BIT(OK),
+  [ACL_REQUIRE] =      BIT(FAIL) | BIT(FAIL_DROP),
+  [ACL_WARN] =         BIT(OK)
   };
 
 /* ACL condition and modifier codes - keep in step with the table that
@@ -101,7 +102,7 @@ enum { ACLC_ACL,
 #ifdef WITH_CONTENT_SCAN
        ACLC_SPAM,
 #endif
-#ifdef EXPERIMENTAL_SPF
+#ifdef SUPPORT_SPF
        ACLC_SPF,
        ACLC_SPF_GUESS,
 #endif
@@ -132,213 +133,200 @@ times. */
 } condition_def;
 
 static condition_def conditions[] = {
-  { US"acl",           FALSE, FALSE,   0 },
+  [ACLC_ACL] =                 { US"acl",              FALSE, FALSE,   0 },
 
-  { US"add_header",    TRUE, TRUE,
-    (unsigned int)
-    ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|
-      (1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
+  [ACLC_ADD_HEADER] =          { US"add_header",       TRUE, TRUE,
+                                 (unsigned int)
+                                 ~(ACL_BIT_MAIL | ACL_BIT_RCPT |
+                                   ACL_BIT_PREDATA | ACL_BIT_DATA |
 #ifndef DISABLE_PRDR
-      (1<<ACL_WHERE_PRDR)|
+                                   ACL_BIT_PRDR |
 #endif
-      (1<<ACL_WHERE_MIME)|(1<<ACL_WHERE_NOTSMTP)|
-      (1<<ACL_WHERE_DKIM)|
-      (1<<ACL_WHERE_NOTSMTP_START)),
+                                   ACL_BIT_MIME | ACL_BIT_NOTSMTP |
+                                   ACL_BIT_DKIM |
+                                   ACL_BIT_NOTSMTP_START),
   },
 
-  { US"authenticated", FALSE, FALSE,
-    (1<<ACL_WHERE_NOTSMTP)|
-      (1<<ACL_WHERE_NOTSMTP_START)|
-      (1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO),
+  [ACLC_AUTHENTICATED] =       { US"authenticated",    FALSE, FALSE,
+                                 ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START |
+                                   ACL_BIT_CONNECT | ACL_BIT_HELO,
   },
 #ifdef EXPERIMENTAL_BRIGHTMAIL
-  { US"bmi_optin",     TRUE, TRUE,
-    (1<<ACL_WHERE_AUTH)|
-      (1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO)|
-      (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_MIME)|
+  [ACLC_BMI_OPTIN] =           { US"bmi_optin",        TRUE, TRUE,
+                                 ACL_BIT_AUTH |
+                                   ACL_BIT_CONNECT | ACL_BIT_HELO |
+                                   ACL_BIT_DATA | ACL_BIT_MIME |
 # ifndef DISABLE_PRDR
-      (1<<ACL_WHERE_PRDR)|
+                                   ACL_BIT_PRDR |
 # endif
-      (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)|
-      (1<<ACL_WHERE_NOTSMTP_START),
+                                   ACL_BIT_ETRN | ACL_BIT_EXPN |
+                                   ACL_BIT_MAILAUTH |
+                                   ACL_BIT_MAIL | ACL_BIT_STARTTLS |
+                                   ACL_BIT_VRFY | ACL_BIT_PREDATA |
+                                   ACL_BIT_NOTSMTP_START,
   },
 #endif
-  { US"condition",     TRUE, FALSE,    0 },
-  { US"continue",      TRUE, TRUE,     0 },
+  [ACLC_CONDITION] =           { US"condition",        TRUE, FALSE,    0 },
+  [ACLC_CONTINUE] =            { US"continue", TRUE, TRUE,     0 },
 
   /* Certain types of control are always allowed, so we let it through
   always and check in the control processing itself. */
-  { US"control",       TRUE, TRUE,     0 },
+  [ACLC_CONTROL] =             { US"control",  TRUE, TRUE,     0 },
 
 #ifdef EXPERIMENTAL_DCC
-  { US"dcc",           TRUE, FALSE,
-    (unsigned int)
-    ~((1<<ACL_WHERE_DATA)|
+  [ACLC_DCC] =                 { US"dcc",              TRUE, FALSE,
+                                 (unsigned int)
+                                 ~(ACL_BIT_DATA |
 # ifndef DISABLE_PRDR
-      (1<<ACL_WHERE_PRDR)|
+                                 ACL_BIT_PRDR |
 # endif
-      (1<<ACL_WHERE_NOTSMTP)),
+                                 ACL_BIT_NOTSMTP),
   },
 #endif
 #ifdef WITH_CONTENT_SCAN
-  { US"decode",                TRUE, FALSE, (unsigned int) ~(1<<ACL_WHERE_MIME) },
+  [ACLC_DECODE] =              { US"decode",           TRUE, FALSE, (unsigned int) ~ACL_BIT_MIME },
 
 #endif
-  { US"delay",         TRUE, TRUE, (1<<ACL_WHERE_NOTQUIT) },
+  [ACLC_DELAY] =               { US"delay",            TRUE, TRUE, ACL_BIT_NOTQUIT },
 #ifndef DISABLE_DKIM
-  { US"dkim_signers",  TRUE, FALSE, (unsigned int) ~(1<<ACL_WHERE_DKIM) },
-  { US"dkim_status",   TRUE, FALSE, (unsigned int) ~(1<<ACL_WHERE_DKIM) },
+  [ACLC_DKIM_SIGNER] =         { US"dkim_signers",     TRUE, FALSE, (unsigned int) ~ACL_BIT_DKIM },
+  [ACLC_DKIM_STATUS] =         { US"dkim_status",      TRUE, FALSE, (unsigned int) ~ACL_BIT_DKIM },
 #endif
 #ifdef EXPERIMENTAL_DMARC
-  { US"dmarc_status",  TRUE, FALSE, (unsigned int) ~(1<<ACL_WHERE_DATA) },
+  [ACLC_DMARC_STATUS] =                { US"dmarc_status",     TRUE, FALSE, (unsigned int) ~ACL_BIT_DATA },
 #endif
 
   /* Explicit key lookups can be made in non-smtp ACLs so pass
   always and check in the verify processing itself. */
-  { US"dnslists",      TRUE, FALSE,    0 },
+  [ACLC_DNSLISTS] =            { US"dnslists", TRUE, FALSE,    0 },
 
-  { US"domains",       FALSE, FALSE,
-    (unsigned int)
-    ~((1<<ACL_WHERE_RCPT)
-      |(1<<ACL_WHERE_VRFY)
+  [ACLC_DOMAINS] =             { US"domains",  FALSE, FALSE,
+                                 (unsigned int)
+                                 ~(ACL_BIT_RCPT | ACL_BIT_VRFY
 #ifndef DISABLE_PRDR
-      |(1<<ACL_WHERE_PRDR)
+                                 |ACL_BIT_PRDR
 #endif
       ),
   },
-  { US"encrypted",     FALSE, FALSE,
-    (1<<ACL_WHERE_NOTSMTP)|
-      (1<<ACL_WHERE_CONNECT)|
-      (1<<ACL_WHERE_NOTSMTP_START)|
-      (1<<ACL_WHERE_HELO),
+  [ACLC_ENCRYPTED] =           { US"encrypted",        FALSE, FALSE,
+                                 ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START |
+                                   ACL_BIT_HELO,
   },
 
-  { US"endpass",       TRUE, TRUE,     0 },
+  [ACLC_ENDPASS] =             { US"endpass",  TRUE, TRUE,     0 },
 
-  { US"hosts",         FALSE, FALSE,
-    (1<<ACL_WHERE_NOTSMTP)|
-      (1<<ACL_WHERE_NOTSMTP_START),
+  [ACLC_HOSTS] =               { US"hosts",            FALSE, FALSE,
+                                 ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START,
   },
-  { US"local_parts",   FALSE, FALSE,
-    (unsigned int)
-    ~((1<<ACL_WHERE_RCPT)
-      |(1<<ACL_WHERE_VRFY)
-    #ifndef DISABLE_PRDR
-      |(1<<ACL_WHERE_PRDR)
-    #endif
+  [ACLC_LOCAL_PARTS] =         { US"local_parts",      FALSE, FALSE,
+                                 (unsigned int)
+                                 ~(ACL_BIT_RCPT | ACL_BIT_VRFY
+#ifndef DISABLE_PRDR
+                                 | ACL_BIT_PRDR
+#endif
       ),
   },
 
-  { US"log_message",   TRUE, TRUE,     0 },
-  { US"log_reject_target", TRUE, TRUE, 0 },
-  { US"logwrite",      TRUE, TRUE,     0 },
+  [ACLC_LOG_MESSAGE] =         { US"log_message",      TRUE, TRUE,     0 },
+  [ACLC_LOG_REJECT_TARGET] =   { US"log_reject_target", TRUE, TRUE,    0 },
+  [ACLC_LOGWRITE] =            { US"logwrite", TRUE, TRUE,     0 },
 
 #ifdef WITH_CONTENT_SCAN
-  { US"malware",       TRUE, FALSE,
-    (unsigned int)
-    ~((1<<ACL_WHERE_DATA)|
+  [ACLC_MALWARE] =             { US"malware",  TRUE, FALSE,
+                                 (unsigned int)
+                                   ~(ACL_BIT_DATA |
 # ifndef DISABLE_PRDR
-      (1<<ACL_WHERE_PRDR)|
+                                   ACL_BIT_PRDR |
 # endif
-      (1<<ACL_WHERE_NOTSMTP)),
+                                   ACL_BIT_NOTSMTP),
   },
 #endif
 
-  { US"message",       TRUE, TRUE,     0 },
+  [ACLC_MESSAGE] =             { US"message",  TRUE, TRUE,     0 },
 #ifdef WITH_CONTENT_SCAN
-  { US"mime_regex",    TRUE, FALSE, (unsigned int) ~(1<<ACL_WHERE_MIME) },
+  [ACLC_MIME_REGEX] =          { US"mime_regex",       TRUE, FALSE, (unsigned int) ~ACL_BIT_MIME },
 #endif
 
-  { US"queue",         TRUE, TRUE,
-    (1<<ACL_WHERE_NOTSMTP)|
+  [ACLC_QUEUE] =               { US"queue",            TRUE, TRUE,
+                                 ACL_BIT_NOTSMTP |
 #ifndef DISABLE_PRDR
-      (1<<ACL_WHERE_PRDR)|
+                                 ACL_BIT_PRDR |
 #endif
-      (1<<ACL_WHERE_DATA),
+                                 ACL_BIT_DATA,
   },
 
-  { US"ratelimit",     TRUE, FALSE,    0 },
-  { US"recipients",    FALSE, FALSE, (unsigned int) ~(1<<ACL_WHERE_RCPT) },
+  [ACLC_RATELIMIT] =           { US"ratelimit",        TRUE, FALSE,    0 },
+  [ACLC_RECIPIENTS] =          { US"recipients",       FALSE, FALSE, (unsigned int) ~ACL_BIT_RCPT },
 
 #ifdef WITH_CONTENT_SCAN
-  { US"regex",         TRUE, FALSE,
-    (unsigned int)
-    ~((1<<ACL_WHERE_DATA)|
+  [ACLC_REGEX] =               { US"regex",            TRUE, FALSE,
+                                 (unsigned int)
+                                 ~(ACL_BIT_DATA |
 # ifndef DISABLE_PRDR
-      (1<<ACL_WHERE_PRDR)|
+                                   ACL_BIT_PRDR |
 # endif
-      (1<<ACL_WHERE_NOTSMTP)|
-      (1<<ACL_WHERE_MIME)),
+                                   ACL_BIT_NOTSMTP |
+                                   ACL_BIT_MIME),
   },
 
 #endif
-  { US"remove_header", TRUE, TRUE,
-    (unsigned int)
-    ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|
-      (1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
+  [ACLC_REMOVE_HEADER] =       { US"remove_header",    TRUE, TRUE,
+                                 (unsigned int)
+                                 ~(ACL_BIT_MAIL|ACL_BIT_RCPT |
+                                   ACL_BIT_PREDATA | ACL_BIT_DATA |
 #ifndef DISABLE_PRDR
-      (1<<ACL_WHERE_PRDR)|
+                                   ACL_BIT_PRDR |
 #endif
-      (1<<ACL_WHERE_MIME)|(1<<ACL_WHERE_NOTSMTP)|
-      (1<<ACL_WHERE_NOTSMTP_START)),
+                                   ACL_BIT_MIME | ACL_BIT_NOTSMTP |
+                                   ACL_BIT_NOTSMTP_START),
   },
-  { US"sender_domains",        FALSE, FALSE,
-    (1<<ACL_WHERE_AUTH)|(1<<ACL_WHERE_CONNECT)|
-      (1<<ACL_WHERE_HELO)|
-      (1<<ACL_WHERE_MAILAUTH)|(1<<ACL_WHERE_QUIT)|
-      (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
-      (1<<ACL_WHERE_STARTTLS)|(1<<ACL_WHERE_VRFY),
+  [ACLC_SENDER_DOMAINS] =      { US"sender_domains",   FALSE, FALSE,
+                                 ACL_BIT_AUTH | ACL_BIT_CONNECT |
+                                   ACL_BIT_HELO |
+                                   ACL_BIT_MAILAUTH | ACL_BIT_QUIT |
+                                   ACL_BIT_ETRN | ACL_BIT_EXPN |
+                                   ACL_BIT_STARTTLS | ACL_BIT_VRFY,
   },
-  { US"senders",       FALSE, FALSE,
-    (1<<ACL_WHERE_AUTH)|(1<<ACL_WHERE_CONNECT)|
-      (1<<ACL_WHERE_HELO)|
-      (1<<ACL_WHERE_MAILAUTH)|(1<<ACL_WHERE_QUIT)|
-      (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
-      (1<<ACL_WHERE_STARTTLS)|(1<<ACL_WHERE_VRFY),
+  [ACLC_SENDERS] =             { US"senders",  FALSE, FALSE,
+                                 ACL_BIT_AUTH | ACL_BIT_CONNECT |
+                                   ACL_BIT_HELO |
+                                   ACL_BIT_MAILAUTH | ACL_BIT_QUIT |
+                                   ACL_BIT_ETRN | ACL_BIT_EXPN |
+                                   ACL_BIT_STARTTLS | ACL_BIT_VRFY,
   },
 
-  { US"set",           TRUE, TRUE,     0 },
+  [ACLC_SET] =                 { US"set",              TRUE, TRUE,     0 },
 
 #ifdef WITH_CONTENT_SCAN
-  { US"spam",          TRUE, FALSE,
-    (unsigned int)
-    ~((1<<ACL_WHERE_DATA)|
+  [ACLC_SPAM] =                        { US"spam",             TRUE, FALSE,
+                                 (unsigned int) ~(ACL_BIT_DATA |
 # ifndef DISABLE_PRDR
-      (1<<ACL_WHERE_PRDR)|
+                                 ACL_BIT_PRDR |
 # endif
-      (1<<ACL_WHERE_NOTSMTP)),
+                                 ACL_BIT_NOTSMTP),
   },
 #endif
-#ifdef EXPERIMENTAL_SPF
-  { US"spf",           TRUE, FALSE,
-    (1<<ACL_WHERE_AUTH)|(1<<ACL_WHERE_CONNECT)|
-      (1<<ACL_WHERE_HELO)|
-      (1<<ACL_WHERE_MAILAUTH)|
-      (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
-      (1<<ACL_WHERE_STARTTLS)|(1<<ACL_WHERE_VRFY)|
-      (1<<ACL_WHERE_NOTSMTP)|
-      (1<<ACL_WHERE_NOTSMTP_START),
+#ifdef SUPPORT_SPF
+  [ACLC_SPF] =                 { US"spf",              TRUE, FALSE,
+                                 ACL_BIT_AUTH | ACL_BIT_CONNECT |
+                                   ACL_BIT_HELO | ACL_BIT_MAILAUTH |
+                                   ACL_BIT_ETRN | ACL_BIT_EXPN |
+                                   ACL_BIT_STARTTLS | ACL_BIT_VRFY |
+                                   ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START,
   },
-  { US"spf_guess",     TRUE, FALSE,
-    (1<<ACL_WHERE_AUTH)|(1<<ACL_WHERE_CONNECT)|
-      (1<<ACL_WHERE_HELO)|
-      (1<<ACL_WHERE_MAILAUTH)|
-      (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
-      (1<<ACL_WHERE_STARTTLS)|(1<<ACL_WHERE_VRFY)|
-      (1<<ACL_WHERE_NOTSMTP)|
-      (1<<ACL_WHERE_NOTSMTP_START),
+  [ACLC_SPF_GUESS] =           { US"spf_guess",        TRUE, FALSE,
+                                 ACL_BIT_AUTH | ACL_BIT_CONNECT |
+                                   ACL_BIT_HELO | ACL_BIT_MAILAUTH |
+                                   ACL_BIT_ETRN | ACL_BIT_EXPN |
+                                   ACL_BIT_STARTTLS | ACL_BIT_VRFY |
+                                   ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START,
   },
 #endif
-  { US"udpsend",       TRUE, TRUE,     0 },
+  [ACLC_UDPSEND] =             { US"udpsend",          TRUE, TRUE,     0 },
 
   /* Certain types of verify are always allowed, so we let it through
   always and check in the verify function itself */
-  { US"verify",                TRUE, FALSE,
-    0
-  },
+  [ACLC_VERIFY] =              { US"verify",           TRUE, FALSE, 0 },
 };
 
 
@@ -399,117 +387,144 @@ typedef struct control_def {
 } control_def;
 
 static control_def controls_list[] = {
+  /*   name                    has_option      forbids */
+[CONTROL_AUTH_UNADVERTISED] =
   { US"allow_auth_unadvertised", FALSE,
-    (unsigned)
-    ~((1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO))
+                                 (unsigned)
+                                 ~(ACL_BIT_CONNECT | ACL_BIT_HELO)
   },
 #ifdef EXPERIMENTAL_BRIGHTMAIL
-  { US"bmi_run",                 FALSE, 0 },
+[CONTROL_BMI_RUN] =
+  { US"bmi_run",                 FALSE,                0 },
 #endif
-  { US"caseful_local_part",      FALSE, (unsigned) ~(1<<ACL_WHERE_RCPT) },
-  { US"caselower_local_part",    FALSE, (unsigned) ~(1<<ACL_WHERE_RCPT) },
-  { US"cutthrough_delivery",     TRUE, 0 },
-  { US"debug",                   TRUE, 0 },
+[CONTROL_CASEFUL_LOCAL_PART] =
+  { US"caseful_local_part",      FALSE, (unsigned) ~ACL_BIT_RCPT },
+[CONTROL_CASELOWER_LOCAL_PART] =
+  { US"caselower_local_part",    FALSE, (unsigned) ~ACL_BIT_RCPT },
+[CONTROL_CUTTHROUGH_DELIVERY] =
+  { US"cutthrough_delivery",     TRUE,         0 },
+[CONTROL_DEBUG] =
+  { US"debug",                   TRUE,         0 },
 
 #ifndef DISABLE_DKIM
+[CONTROL_DKIM_VERIFY] =
   { US"dkim_disable_verify",     FALSE,
-    (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP)|
+                                 ACL_BIT_DATA | ACL_BIT_NOTSMTP |
 # ifndef DISABLE_PRDR
-      (1<<ACL_WHERE_PRDR)|
+                                 ACL_BIT_PRDR |
 # endif
-      (1<<ACL_WHERE_NOTSMTP_START)
+                                 ACL_BIT_NOTSMTP_START
   },
 #endif
 
 #ifdef EXPERIMENTAL_DMARC
+[CONTROL_DMARC_VERIFY] =
   { US"dmarc_disable_verify",    FALSE,
-    (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_NOTSMTP_START)
+         ACL_BIT_DATA | ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START
   },
+[CONTROL_DMARC_FORENSIC] =
   { US"dmarc_enable_forensic",   FALSE,
-    (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_NOTSMTP_START)
+         ACL_BIT_DATA | ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START
   },
 #endif
 
+[CONTROL_DSCP] =
   { US"dscp",                    TRUE,
-    (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_NOTSMTP_START)|(1<<ACL_WHERE_NOTQUIT)
+         ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START | ACL_BIT_NOTQUIT
   },
+[CONTROL_ENFORCE_SYNC] =
   { US"enforce_sync",            FALSE,
-    (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_NOTSMTP_START)
+         ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START
   },
 
   /* Pseudo-value for decode errors */
+[CONTROL_ERROR] =
   { US"error",                   FALSE, 0 },
 
+[CONTROL_FAKEDEFER] =
   { US"fakedefer",               TRUE,
-    (unsigned)
-    ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|
-      (1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
+         (unsigned)
+         ~(ACL_BIT_MAIL | ACL_BIT_RCPT |
+           ACL_BIT_PREDATA | ACL_BIT_DATA |
 #ifndef DISABLE_PRDR
-      (1<<ACL_WHERE_PRDR)|
+           ACL_BIT_PRDR |
 #endif
-      (1<<ACL_WHERE_MIME))
+           ACL_BIT_MIME)
   },
+[CONTROL_FAKEREJECT] =
   { US"fakereject",              TRUE,
-    (unsigned)
-    ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|
-      (1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
+         (unsigned)
+         ~(ACL_BIT_MAIL | ACL_BIT_RCPT |
+           ACL_BIT_PREDATA | ACL_BIT_DATA |
 #ifndef DISABLE_PRDR
-      (1<<ACL_WHERE_PRDR)|
+         ACL_BIT_PRDR |
 #endif
-      (1<<ACL_WHERE_MIME))
+         ACL_BIT_MIME)
   },
+[CONTROL_FREEZE] =
   { US"freeze",                  TRUE,
-    (unsigned)
-    ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|
-      (1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
-      // (1<<ACL_WHERE_PRDR)|    /* Not allow one user to freeze for all */
-      (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_MIME))
+         (unsigned)
+         ~(ACL_BIT_MAIL | ACL_BIT_RCPT |
+           ACL_BIT_PREDATA | ACL_BIT_DATA |
+           // ACL_BIT_PRDR|    /* Not allow one user to freeze for all */
+           ACL_BIT_NOTSMTP | ACL_BIT_MIME)
   },
 
+[CONTROL_NO_CALLOUT_FLUSH] =
   { US"no_callout_flush",        FALSE,
-    (1<<ACL_WHERE_NOTSMTP)| (1<<ACL_WHERE_NOTSMTP_START)
+         ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START
   },
+[CONTROL_NO_DELAY_FLUSH] =
   { US"no_delay_flush",          FALSE,
-    (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_NOTSMTP_START)
+         ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START
   },
   
+[CONTROL_NO_ENFORCE_SYNC] =
   { US"no_enforce_sync",         FALSE,
-    (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_NOTSMTP_START)
+         ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START
   },
 #ifdef WITH_CONTENT_SCAN
+[CONTROL_NO_MBOX_UNSPOOL] =
   { US"no_mbox_unspool",         FALSE,
-    (unsigned)
-    ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|
-      (1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
-      // (1<<ACL_WHERE_PRDR)|    /* Not allow one user to freeze for all */
-      (1<<ACL_WHERE_MIME))
+       (unsigned)
+       ~(ACL_BIT_MAIL | ACL_BIT_RCPT |
+         ACL_BIT_PREDATA | ACL_BIT_DATA |
+         // ACL_BIT_PRDR|    /* Not allow one user to freeze for all */
+         ACL_BIT_MIME)
   },
 #endif
+[CONTROL_NO_MULTILINE] =
   { US"no_multiline_responses",  FALSE,
-    (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_NOTSMTP_START)
+         ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START
   },
+[CONTROL_NO_PIPELINING] =
   { US"no_pipelining",           FALSE,
-    (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_NOTSMTP_START)
+         ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START
   },
 
+[CONTROL_QUEUE_ONLY] =
   { US"queue_only",              FALSE,
-    (unsigned)
-    ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|
-      (1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
-      // (1<<ACL_WHERE_PRDR)|    /* Not allow one user to freeze for all */
-      (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_MIME))
+         (unsigned)
+         ~(ACL_BIT_MAIL | ACL_BIT_RCPT |
+           ACL_BIT_PREDATA | ACL_BIT_DATA |
+           // ACL_BIT_PRDR|    /* Not allow one user to freeze for all */
+           ACL_BIT_NOTSMTP | ACL_BIT_MIME)
   },
+[CONTROL_SUBMISSION] =
   { US"submission",              TRUE,
-    (unsigned)
-    ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|(1<<ACL_WHERE_PREDATA))
+         (unsigned)
+         ~(ACL_BIT_MAIL | ACL_BIT_RCPT | ACL_BIT_PREDATA)
   },
+[CONTROL_SUPPRESS_LOCAL_FIXUPS] =
   { US"suppress_local_fixups",   FALSE,
     (unsigned)
-    ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|(1<<ACL_WHERE_PREDATA)|
-      (1<<ACL_WHERE_NOTSMTP_START))
+    ~(ACL_BIT_MAIL | ACL_BIT_RCPT | ACL_BIT_PREDATA |
+      ACL_BIT_NOTSMTP_START)
   },
 #ifdef SUPPORT_I18N
-  { US"utf8_downconvert",        TRUE, 0 }
+[CONTROL_UTF8_DOWNCONVERT] =
+  { US"utf8_downconvert",        TRUE, (unsigned) ~(ACL_BIT_RCPT | ACL_BIT_VRFY)
+  }
 #endif
 };
 
@@ -532,24 +547,36 @@ 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
+  [CSA_UNKNOWN] =      OK,
+  [CSA_OK] =           OK,
+  [CSA_DEFER_SRV] =    OK,
+  [CSA_DEFER_ADDR] =   OK,
+  [CSA_FAIL_EXPLICIT] =        FAIL,
+  [CSA_FAIL_DOMAIN] =  FAIL,
+  [CSA_FAIL_NOADDR] =  FAIL,
+  [CSA_FAIL_MISMATCH] =        FAIL
 };
 
 static uschar *csa_status_string[] = {
-  US"unknown", US"ok", US"defer", US"defer",
-  US"fail", US"fail", US"fail", US"fail"
+  [CSA_UNKNOWN] =      US"unknown",
+  [CSA_OK] =           US"ok",
+  [CSA_DEFER_SRV] =    US"defer",
+  [CSA_DEFER_ADDR] =   US"defer",
+  [CSA_FAIL_EXPLICIT] =        US"fail",
+  [CSA_FAIL_DOMAIN] =  US"fail",
+  [CSA_FAIL_NOADDR] =  US"fail",
+  [CSA_FAIL_MISMATCH] =        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)"
+  [CSA_UNKNOWN] =      US"unknown",
+  [CSA_OK] =           US"ok",
+  [CSA_DEFER_SRV] =    US"deferred (SRV lookup failed)",
+  [CSA_DEFER_ADDR] =   US"deferred (target address lookup failed)",
+  [CSA_FAIL_EXPLICIT] =        US"failed (explicit authorization required)",
+  [CSA_FAIL_DOMAIN] =  US"failed (host name not authorized)",
+  [CSA_FAIL_NOADDR] =  US"failed (no authorized addresses)",
+  [CSA_FAIL_MISMATCH] =        US"failed (client address mismatch)"
 };
 
 /* Options for the ratelimit condition. Note that there are two variants of
@@ -567,8 +594,15 @@ enum {
   (((var) == RATE_PER_WHAT) ? ((var) = RATE_##new) : ((var) = RATE_PER_CLASH))
 
 static uschar *ratelimit_option_string[] = {
-  US"?", US"!", US"per_addr", US"per_byte", US"per_cmd",
-  US"per_conn", US"per_mail", US"per_rcpt", US"per_rcpt"
+  [RATE_PER_WHAT] =    US"?",
+  [RATE_PER_CLASH] =   US"!",
+  [RATE_PER_ADDR] =    US"per_addr",
+  [RATE_PER_BYTE] =    US"per_byte",
+  [RATE_PER_CMD] =     US"per_cmd",
+  [RATE_PER_CONN] =    US"per_conn",
+  [RATE_PER_MAIL] =    US"per_mail",
+  [RATE_PER_RCPT] =    US"per_rcpt",
+  [RATE_PER_ALLRCPTS] =        US"per_rcpt"
 };
 
 /* Enable recursion between acl_check_internal() and acl_check_condition() */
@@ -809,6 +843,26 @@ while ((s = (*func)()) != NULL)
   compatibility. */
 
   if (c == ACLC_SET)
+#ifndef DISABLE_DKIM
+    if (  Ustrncmp(s, "dkim_verify_status", 18) == 0
+       || Ustrncmp(s, "dkim_verify_reason", 18) == 0)
+      {
+      uschar * endptr = s+18;
+
+      if (isalnum(*endptr))
+       {
+       *error = string_sprintf("invalid variable name after \"set\" in ACL "
+         "modifier \"set %s\" "
+         "(only \"dkim_verify_status\" or \"dkim_verify_reason\" permitted)",
+         s);
+       return NULL;
+       }
+      cond->u.varname = string_copyn(s, 18);
+      s = endptr;
+      while (isspace(*s)) s++;
+      }
+    else
+#endif
     {
     uschar *endptr;
 
@@ -993,9 +1047,7 @@ for (p = q; *p != 0; )
 uschar *
 fn_hdrs_added(void)
 {
-uschar * ret = NULL;
-int size = 0;
-int ptr = 0;
+gstring * g = NULL;
 header_line * h = acl_added_headers;
 uschar * s;
 uschar * cp;
@@ -1010,18 +1062,19 @@ do
     if (cp[1] == '\0') break;
 
     /* contains embedded newline; needs doubling */
-    ret = string_catn(ret, &size, &ptr, s, cp-s+1);
-    ret = string_catn(ret, &size, &ptr, US"\n", 1);
+    g = string_catn(g, s, cp-s+1);
+    g = string_catn(g, US"\n", 1);
     s = cp+1;
     }
   /* last bit of header */
 
-  ret = string_catn(ret, &size, &ptr, s, cp-s+1);      /* newline-sep list */
+/*XXX could we use add_listele? */
+  g = string_catn(g, s, cp-s+1);       /* newline-sep list */
   }
 while((h = h->next));
 
-ret[ptr-1] = '\0';     /* overwrite last newline */
-return ret;
+g->s[g->ptr - 1] = '\0';       /* overwrite last newline */
+return g->s;
 }
 
 
@@ -1097,7 +1150,7 @@ if (log_message != NULL && log_message != user_message)
     int length = Ustrlen(text) + 1;
     log_write(0, LOG_MAIN, "%s", text);
     logged = store_malloc(sizeof(string_item) + length);
-    logged->text = (uschar *)logged + sizeof(string_item);
+    logged->text = US logged + sizeof(string_item);
     memcpy(logged->text, text, length);
     logged->next = acl_warn_logged;
     acl_warn_logged = logged;
@@ -1472,7 +1525,7 @@ switch (dns_lookup(&dnsa, target, type, NULL))
 
 enum { VERIFY_REV_HOST_LKUP, VERIFY_CERT, VERIFY_HELO, VERIFY_CSA, VERIFY_HDR_SYNTAX,
        VERIFY_NOT_BLIND, VERIFY_HDR_SNDR, VERIFY_SNDR, VERIFY_RCPT,
-       VERIFY_HDR_NAMES_ASCII
+       VERIFY_HDR_NAMES_ASCII, VERIFY_ARC
   };
 typedef struct {
   uschar * name;
@@ -1482,18 +1535,22 @@ typedef struct {
   unsigned alt_opt_sep;                /* >0 Non-/ option separator (custom parser) */
   } verify_type_t;
 static verify_type_t verify_type_list[] = {
+    /* name                    value                   where   no-opt opt-sep */
     { US"reverse_host_lookup", VERIFY_REV_HOST_LKUP,   ~0,     FALSE, 0 },
-    { US"certificate",         VERIFY_CERT,            ~0,     TRUE, 0 },
-    { US"helo",                        VERIFY_HELO,            ~0,     TRUE, 0 },
+    { US"certificate",         VERIFY_CERT,            ~0,     TRUE,  0 },
+    { US"helo",                        VERIFY_HELO,            ~0,     TRUE,  0 },
     { US"csa",                 VERIFY_CSA,             ~0,     FALSE, 0 },
-    { US"header_syntax",       VERIFY_HDR_SYNTAX,      (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP), TRUE, 0 },
-    { US"not_blind",           VERIFY_NOT_BLIND,       (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP), TRUE, 0 },
-    { US"header_sender",       VERIFY_HDR_SNDR,        (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP), FALSE, 0 },
-    { US"sender",              VERIFY_SNDR,            (1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)
-                       |(1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP),
+    { US"header_syntax",       VERIFY_HDR_SYNTAX,      ACL_BIT_DATA | ACL_BIT_NOTSMTP, TRUE, 0 },
+    { US"not_blind",           VERIFY_NOT_BLIND,       ACL_BIT_DATA | ACL_BIT_NOTSMTP, TRUE, 0 },
+    { US"header_sender",       VERIFY_HDR_SNDR,        ACL_BIT_DATA | ACL_BIT_NOTSMTP, FALSE, 0 },
+    { US"sender",              VERIFY_SNDR,            ACL_BIT_MAIL | ACL_BIT_RCPT
+                       |ACL_BIT_PREDATA | ACL_BIT_DATA | ACL_BIT_NOTSMTP,
                                                                                FALSE, 6 },
-    { US"recipient",           VERIFY_RCPT,            (1<<ACL_WHERE_RCPT),    FALSE, 0 },
-    { US"header_names_ascii",  VERIFY_HDR_NAMES_ASCII, (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP), TRUE, 0 }
+    { US"recipient",           VERIFY_RCPT,            ACL_BIT_RCPT,   FALSE, 0 },
+    { US"header_names_ascii",  VERIFY_HDR_NAMES_ASCII, ACL_BIT_DATA | ACL_BIT_NOTSMTP, TRUE, 0 },
+#ifdef EXPERIMENTAL_ARC
+    { US"arc",                 VERIFY_ARC,             ACL_BIT_DATA,   TRUE , 0 },
+#endif
   };
 
 
@@ -1510,6 +1567,7 @@ typedef struct {
   BOOL     timeval;    /* Has a time value */
   } callout_opt_t;
 static callout_opt_t callout_opt_list[] = {
+    /* name                    value                   flag            has-opt         has-time */
     { US"defer_ok",      CALLOUT_DEFER_OK,      0,                             FALSE, FALSE },
     { US"no_cache",      CALLOUT_NOCACHE,       vopt_callout_no_cache,         FALSE, FALSE },
     { US"random",        CALLOUT_RANDOM,        vopt_callout_random,           FALSE, FALSE },
@@ -1575,29 +1633,30 @@ const uschar *list = arg;
 uschar *ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size);
 verify_type_t * vp;
 
-if (ss == NULL) goto BAD_VERIFY;
+if (!ss) goto BAD_VERIFY;
 
 /* Handle name/address consistency verification in a separate function. */
 
 for (vp= verify_type_list;
-     (char *)vp < (char *)verify_type_list + sizeof(verify_type_list);
+     CS vp < CS verify_type_list + sizeof(verify_type_list);
      vp++
     )
   if (vp->alt_opt_sep ? strncmpic(ss, vp->name, vp->alt_opt_sep) == 0
                       : strcmpic (ss, vp->name) == 0)
    break;
-if ((char *)vp >= (char *)verify_type_list + sizeof(verify_type_list))
+if (CS vp >= CS verify_type_list + sizeof(verify_type_list))
   goto BAD_VERIFY;
 
-if (vp->no_options && slash != NULL)
+if (vp->no_options && slash)
   {
   *log_msgptr = string_sprintf("unexpected '/' found in \"%s\" "
     "(this verify item has no options)", arg);
   return ERROR;
   }
-if (!(vp->where_allowed & (1<<where)))
+if (!(vp->where_allowed & BIT(where)))
   {
-  *log_msgptr = string_sprintf("cannot verify %s in ACL for %s", vp->name, acl_wherenames[where]);
+  *log_msgptr = string_sprintf("cannot verify %s in ACL for %s",
+                 vp->name, acl_wherenames[where]);
   return ERROR;
   }
 switch(vp->value)
@@ -1638,7 +1697,7 @@ switch(vp->value)
     return csa_return_code[rc];
 
   case VERIFY_HDR_SYNTAX:
-    /* Check that all relevant header lines have the correct syntax. If there is
+    /* Check that all relevant header lines have the correct 5322-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). */
@@ -2726,8 +2785,9 @@ if (r == HOST_FIND_FAILED || r == HOST_FIND_AGAIN)
 HDEBUG(D_acl)
   debug_printf_indent("udpsend [%s]:%d %s\n", h->address, portnum, arg);
 
+/*XXX this could better use sendto */
 r = s = ip_connectedsocket(SOCK_DGRAM, h->address, portnum, portnum,
-               1, NULL, &errstr);
+               1, NULL, &errstr, NULL);
 if (r < 0) goto defer;
 len = Ustrlen(arg);
 r = send(s, arg, len, 0);
@@ -2854,8 +2914,19 @@ for (; cb != NULL; cb = cb->next)
 
     if (cb->type == ACLC_SET)
       {
-      debug_printf("acl_%s ", cb->u.varname);
-      lhswidth += 5 + Ustrlen(cb->u.varname);
+#ifndef DISABLE_DKIM
+      if (  Ustrcmp(cb->u.varname, "dkim_verify_status") == 0
+        || Ustrcmp(cb->u.varname, "dkim_verify_reason") == 0)
+       {
+       debug_printf("%s ", cb->u.varname);
+       lhswidth += 19;
+       }
+      else
+#endif
+       {
+       debug_printf("acl_%s ", cb->u.varname);
+       lhswidth += 5 + Ustrlen(cb->u.varname);
+       }
       }
 
     debug_printf("= %s\n", cb->arg);
@@ -3181,6 +3252,8 @@ for (; cb != NULL; cb = cb->next)
        break;
 
        case CONTROL_CUTTHROUGH_DELIVERY:
+       {
+       uschar * ignored = NULL;
 #ifndef DISABLE_PRDR
        if (prdr_requested)
 #else
@@ -3189,20 +3262,20 @@ for (; cb != NULL; cb = cb->next)
          /* Too hard to think about for now.  We might in future cutthrough
          the case where both sides handle prdr and this-node prdr acl
          is "accept" */
-         *log_msgptr = string_sprintf("PRDR on %s reception\n", arg);
+         ignored = US"PRDR active";
        else
          {
          if (deliver_freeze)
-           *log_msgptr = US"frozen";
+           ignored = US"frozen";
          else if (queue_only_policy)
-           *log_msgptr = US"queue-only";
+           ignored = US"queue-only";
          else if (fake_response == FAIL)
-           *log_msgptr = US"fakereject";
+           ignored = US"fakereject";
          else
            {
            if (rcpt_count == 1)
              {
-             cutthrough.delivery = TRUE;
+             cutthrough.delivery = TRUE;       /* control accepted */
              while (*p == '/')
                {
                const uschar * pp = p+1;
@@ -3217,12 +3290,14 @@ for (; cb != NULL; cb = cb->next)
                p = pp;
                }
              }
-           break;
+           else
+             ignored = US"nonfirst rcpt";
            }
-         *log_msgptr = string_sprintf("\"control=%s\" on %s item",
-                                       arg, *log_msgptr);
          }
-       return ERROR;
+       DEBUG(D_acl) if (ignored)
+         debug_printf(" cutthrough request ignored on %s item\n", ignored);
+       }
+       break;
 
 #ifdef SUPPORT_I18N
        case CONTROL_UTF8_DOWNCONVERT:
@@ -3357,7 +3432,7 @@ for (; cb != NULL; cb = cb->next)
 
     #ifndef DISABLE_DKIM
     case ACLC_DKIM_SIGNER:
-    if (dkim_cur_signer != NULL)
+    if (dkim_cur_signer)
       rc = match_isinlist(dkim_cur_signer,
                           &arg,0,NULL,NULL,MCL_STRING,TRUE,NULL);
     else
@@ -3365,7 +3440,7 @@ for (; cb != NULL; cb = cb->next)
     break;
 
     case ACLC_DKIM_STATUS:
-    rc = match_isinlist(dkim_exim_expand_query(DKIM_VERIFY_STATUS),
+    rc = match_isinlist(dkim_verify_status,
                         &arg,0,NULL,NULL,MCL_STRING,TRUE,NULL);
     break;
     #endif
@@ -3521,6 +3596,12 @@ for (; cb != NULL; cb = cb->next)
     #endif
 
     case ACLC_QUEUE:
+    if (Ustrchr(arg, '/'))
+      {
+      *log_msgptr = string_sprintf(
+             "Directory separator not permitted in queue name: '%s'", arg);
+      return ERROR;
+      }
     queue_name = string_copy_malloc(arg);
     break;
 
@@ -3564,45 +3645,50 @@ for (; cb != NULL; cb = cb->next)
       {
       int old_pool = store_pool;
       if (  cb->u.varname[0] == 'c'
+#ifndef DISABLE_DKIM
+         || cb->u.varname[0] == 'd'
+#endif
 #ifndef DISABLE_EVENT
         || event_name          /* An event is being delivered */
 #endif
         )
         store_pool = POOL_PERM;
-      acl_var_create(cb->u.varname)->data.ptr = string_copy(arg);
+#ifndef DISABLE_DKIM   /* Overwriteable dkim result variables */
+      if (Ustrcmp(cb->u.varname, "dkim_verify_status") == 0)
+       dkim_verify_status = string_copy(arg);
+      else if (Ustrcmp(cb->u.varname, "dkim_verify_reason") == 0)
+       dkim_verify_reason = string_copy(arg);
+      else
+#endif
+       acl_var_create(cb->u.varname)->data.ptr = string_copy(arg);
       store_pool = old_pool;
       }
     break;
 
-    #ifdef WITH_CONTENT_SCAN
+#ifdef WITH_CONTENT_SCAN
     case ACLC_SPAM:
       {
       /* Separate the regular expression and any optional parameters. */
       const uschar * list = arg;
       uschar *ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size);
-      /* Run the spam backend. */
+
       rc = spam(CUSS &ss);
       /* Modify return code based upon the existence of options. */
-      while ((ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size))
-            != NULL) {
+      while ((ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size)))
         if (strcmpic(ss, US"defer_ok") == 0 && rc == DEFER)
-          {
-          /* FAIL so that the message is passed to the next ACL */
-          rc = FAIL;
-          }
-        }
+          rc = FAIL;   /* FAIL so that the message is passed to the next ACL */
       }
     break;
-    #endif
+#endif
 
-    #ifdef EXPERIMENTAL_SPF
+#ifdef SUPPORT_SPF
     case ACLC_SPF:
       rc = spf_process(&arg, sender_address, SPF_PROCESS_NORMAL);
     break;
     case ACLC_SPF_GUESS:
       rc = spf_process(&arg, sender_address, SPF_PROCESS_GUESS);
     break;
-    #endif
+#endif
 
     case ACLC_UDPSEND:
     rc = acl_udpsend(arg, log_msgptr);
@@ -3655,7 +3741,7 @@ present. */
 
 if (*epp && rc == OK) user_message = NULL;
 
-if (((1<<rc) & msgcond[verb]) != 0)
+if ((BIT(rc) & msgcond[verb]) != 0)
   {
   uschar *expmessage;
   uschar *old_user_msgptr = *user_msgptr;
@@ -3671,11 +3757,11 @@ if (((1<<rc) & msgcond[verb]) != 0)
       (rc == OK && (verb == ACL_ACCEPT || verb == ACL_DISCARD)))
     *log_msgptr = *user_msgptr = NULL;
 
-  if (user_message != NULL)
+  if (user_message)
     {
     acl_verify_message = old_user_msgptr;
     expmessage = expand_string(user_message);
-    if (expmessage == NULL)
+    if (!expmessage)
       {
       if (!expand_string_forcedfail)
         log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand ACL message \"%s\": %s",
@@ -3684,11 +3770,11 @@ if (((1<<rc) & msgcond[verb]) != 0)
     else if (expmessage[0] != 0) *user_msgptr = expmessage;
     }
 
-  if (log_message != NULL)
+  if (log_message)
     {
     acl_verify_message = old_log_msgptr;
     expmessage = expand_string(log_message);
-    if (expmessage == NULL)
+    if (!expmessage)
       {
       if (!expand_string_forcedfail)
         log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand ACL message \"%s\": %s",
@@ -3703,7 +3789,7 @@ if (((1<<rc) & msgcond[verb]) != 0)
 
   /* If no log message, default it to the user message */
 
-  if (*log_msgptr == NULL) *log_msgptr = *user_msgptr;
+  if (!*log_msgptr) *log_msgptr = *user_msgptr;
   }
 
 acl_verify_message = NULL;
@@ -3886,7 +3972,7 @@ if (acl_level == 0)
   }
 else ss = s;
 
-while (isspace(*ss))ss++;
+while (isspace(*ss)) ss++;
 
 /* If we can't find a named ACL, the default is to parse it as an inline one.
 (Unless it begins with a slash; non-existent files give rise to an error.) */
@@ -4336,22 +4422,29 @@ switch (where)
     else if (  rc == OK
            && cutthrough.delivery
            && rcpt_count > cutthrough.nrcpt
-           && (rc = open_cutthrough_connection(addr)) == DEFER
            )
-      if (cutthrough.defer_pass)
-       {
-       uschar * s = addr->message;
-       /* Horrid kludge to recover target's SMTP message */
-       while (*s) s++;
-       do --s; while (!isdigit(*s));
-       if (*--s && isdigit(*s) && *--s && isdigit(*s)) *user_msgptr = s;
-       acl_temp_details = TRUE;
-       }
-      else
-       {
-       HDEBUG(D_acl) debug_printf_indent("cutthrough defer; will spool\n");
-       rc = OK;
-       }
+      {
+      if ((rc = open_cutthrough_connection(addr)) == DEFER)
+       if (cutthrough.defer_pass)
+         {
+         uschar * s = addr->message;
+         /* Horrid kludge to recover target's SMTP message */
+         while (*s) s++;
+         do --s; while (!isdigit(*s));
+         if (*--s && isdigit(*s) && *--s && isdigit(*s)) *user_msgptr = s;
+         acl_temp_details = TRUE;
+         }
+       else
+         {
+         HDEBUG(D_acl) debug_printf_indent("cutthrough defer; will spool\n");
+         rc = OK;
+         }
+      }
+    else HDEBUG(D_acl) if (cutthrough.delivery)
+      if (rcpt_count <= cutthrough.nrcpt)
+       debug_printf_indent("ignore cutthrough request; nonfirst message\n");
+      else if (rc != OK)
+       debug_printf_indent("ignore cutthrough request; ACL did not accept\n");
     break;
 
   case ACL_WHERE_PREDATA: