ARC: add $arc_state, $arc_state_reason and add reason to authres string
[exim.git] / src / src / acl.c
index cf7e42aa4bc222b846504a468f65dd27e8bd8e97..f6141165c2fa0e78d77cc7f6b449a2bb12421396 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;
 
@@ -904,7 +958,7 @@ else
 
 /* Loop for multiple header lines, taking care about continuations */
 
-for (p = q; *p != 0; )
+for (p = q; *p; p = q)
   {
   const uschar *s;
   uschar * hdr;
@@ -916,7 +970,7 @@ for (p = q; *p != 0; )
   for (;;)
     {
     q = Ustrchr(q, '\n');              /* we know there was a newline */
-    if (*(++q) != ' ' && *q != '\t') break;
+    if (*++q != ' ' && *q != '\t') break;
     }
 
   /* If the line starts with a colon, interpret the instruction for where to
@@ -951,24 +1005,22 @@ for (p = q; *p != 0; )
   to the front of it. */
 
   for (s = p; s < q - 1; s++)
-    {
     if (*s == ':' || !isgraph(*s)) break;
-    }
 
-  hdr = string_sprintf("%s%.*s", (*s == ':')? "" : "X-ACL-Warn: ", (int) (q - p), p);
+  hdr = string_sprintf("%s%.*s", *s == ':' ? "" : "X-ACL-Warn: ", (int) (q - p), p);
   hlen = Ustrlen(hdr);
 
   /* See if this line has already been added */
 
-  while (*hptr != NULL)
+  while (*hptr)
     {
     if (Ustrncmp((*hptr)->text, hdr, hlen) == 0) break;
-    hptr = &((*hptr)->next);
+    hptr = &(*hptr)->next;
     }
 
   /* Add if not previously present */
 
-  if (*hptr == NULL)
+  if (!*hptr)
     {
     header_line *h = store_get(sizeof(header_line));
     h->text = hdr;
@@ -976,12 +1028,8 @@ for (p = q; *p != 0; )
     h->type = newtype;
     h->slen = hlen;
     *hptr = h;
-    hptr = &(h->next);
+    hptr = &h->next;
     }
-
-  /* Advance for next header line within the string */
-
-  p = q;
   }
 }
 
@@ -993,9 +1041,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 +1056,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 +1144,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 +1519,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 +1529,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,   FALSE , 0 },
+#endif
   };
 
 
@@ -1510,6 +1561,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,37 +1627,38 @@ 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)
   {
   case VERIFY_REV_HOST_LKUP:
-    if (sender_host_address == NULL) return OK;
+    if (!sender_host_address) return OK;
     if ((rc = acl_verify_reverse(user_msgptr, log_msgptr)) == DEFER)
-      while ((ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size)))
+      while ((ss = string_nextinlist(&list, &sep, NULL, 0)))
        if (strcmpic(ss, US"defer_ok") == 0)
          return OK;
     return rc;
@@ -1624,7 +1677,7 @@ switch(vp->value)
     occurred earlier. If not, we can attempt the verification now. */
 
     if (!helo_verified && !helo_verify_failed) smtp_verify_helo();
-    return helo_verified? OK : FAIL;
+    return helo_verified ? OK : FAIL;
 
   case VERIFY_CSA:
     /* Do Client SMTP Authorization checks in a separate function, and turn the
@@ -1637,8 +1690,26 @@ switch(vp->value)
     DEBUG(D_acl) debug_printf_indent("CSA result %s\n", csa_status);
     return csa_return_code[rc];
 
+#ifdef EXPERIMENTAL_ARC
+  case VERIFY_ARC:
+    {  /* Do Authenticated Received Chain checks in a separate function. */
+    const uschar * condlist = CUS string_nextinlist(&list, &sep, NULL, 0);
+    int csep = 0;
+    uschar * cond;
+
+    if (!(arc_state = acl_verify_arc())) return DEFER;
+    DEBUG(D_acl) debug_printf_indent("ARC verify result %s %s%s%s\n", arc_state,
+      arc_state_reason ? "(":"", arc_state_reason, arc_state_reason ? ")":"");
+
+    if (!condlist) condlist = US"none:pass";
+    while ((cond = string_nextinlist(&condlist, &csep, NULL, 0)))
+      if (Ustrcmp(arc_state, cond) == 0) return OK;
+    return FAIL;
+    }
+#endif
+
   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). */
@@ -1656,7 +1727,7 @@ switch(vp->value)
     See RFC 5322, 2.2. and RFC 6532, 3. */
 
     rc = verify_check_header_names_ascii(log_msgptr);
-    if (rc != OK && smtp_return_error_details && *log_msgptr != NULL)
+    if (rc != OK && smtp_return_error_details && *log_msgptr)
       *user_msgptr = string_sprintf("Rejected after DATA: %s", *log_msgptr);
     return rc;
 
@@ -1664,8 +1735,7 @@ switch(vp->value)
     /* Check that no recipient of this message is "blind", that is, every envelope
     recipient must be mentioned in either To: or Cc:. */
 
-    rc = verify_check_notblind();
-    if (rc != OK)
+    if ((rc = verify_check_notblind()) != OK)
       {
       *log_msgptr = string_sprintf("bcc recipient detected");
       if (smtp_return_error_details)
@@ -2171,8 +2241,7 @@ error messages based on rate limits obtained from a table lookup. */
 size, which must be greater than or equal to zero. Zero is useful for
 rate measurement as opposed to rate limiting. */
 
-sender_rate_limit = string_nextinlist(&arg, &sep, NULL, 0);
-if (sender_rate_limit == NULL)
+if (!(sender_rate_limit = string_nextinlist(&arg, &sep, NULL, 0)))
   return ratelimit_error(log_msgptr, "sender rate limit not set");
 
 limit = Ustrtod(sender_rate_limit, &ss);
@@ -2188,9 +2257,8 @@ if (limit < 0.0 || *ss != '\0')
 constant. This must be strictly greater than zero, because zero leads to
 run-time division errors. */
 
-sender_rate_period = string_nextinlist(&arg, &sep, NULL, 0);
-if (sender_rate_period == NULL) period = -1.0;
-else period = readconf_readtime(sender_rate_period, 0, FALSE);
+period = !(sender_rate_period = string_nextinlist(&arg, &sep, NULL, 0))
+  ? -1.0 : readconf_readtime(sender_rate_period, 0, FALSE);
 if (period <= 0.0)
   return ratelimit_error(log_msgptr,
     "\"%s\" is not a time value", sender_rate_period);
@@ -2202,8 +2270,7 @@ count = 1.0;
 
 /* Parse the other options. */
 
-while ((ss = string_nextinlist(&arg, &sep, big_buffer, big_buffer_size))
-       != NULL)
+while ((ss = string_nextinlist(&arg, &sep, big_buffer, big_buffer_size)))
   {
   if (strcmpic(ss, US"leaky") == 0) leaky = TRUE;
   else if (strcmpic(ss, US"strict") == 0) strict = TRUE;
@@ -2240,25 +2307,24 @@ while ((ss = string_nextinlist(&arg, &sep, big_buffer, big_buffer_size))
     zero and let the recorded rate decay as if nothing happened. */
     RATE_SET(mode, PER_MAIL);
     if (where > ACL_WHERE_NOTSMTP) badacl = TRUE;
-      else count = message_size < 0 ? 0.0 : (double)message_size;
+    else count = message_size < 0 ? 0.0 : (double)message_size;
     }
   else if (strcmpic(ss, US"per_addr") == 0)
     {
     RATE_SET(mode, PER_RCPT);
     if (where != ACL_WHERE_RCPT) badacl = TRUE, unique = US"*";
-      else unique = string_sprintf("%s@%s", deliver_localpart, deliver_domain);
+    else unique = string_sprintf("%s@%s", deliver_localpart, deliver_domain);
     }
   else if (strncmpic(ss, US"count=", 6) == 0)
     {
     uschar *e;
     count = Ustrtod(ss+6, &e);
     if (count < 0.0 || *e != '\0')
-      return ratelimit_error(log_msgptr,
-       "\"%s\" is not a positive number", ss);
+      return ratelimit_error(log_msgptr, "\"%s\" is not a positive number", ss);
     }
   else if (strncmpic(ss, US"unique=", 7) == 0)
     unique = string_copy(ss + 7);
-  else if (key == NULL)
+  else if (!key)
     key = string_copy(ss);
   else
     key = string_sprintf("%s/%s", key, ss);
@@ -2292,8 +2358,8 @@ If there is no sender_host_address (e.g. -bs or acl_not_smtp) then we simply
 omit it. The smoothing constant (sender_rate_period) and the per_xxx options
 are added to the key because they alter the meaning of the stored data. */
 
-if (key == NULL)
-  key = (sender_host_address == NULL)? US"" : sender_host_address;
+if (!key)
+  key = !sender_host_address ? US"" : sender_host_address;
 
 key = string_sprintf("%s/%s/%s%s",
   sender_rate_period,
@@ -2314,30 +2380,30 @@ old_pool = store_pool;
 
 if (readonly)
   anchor = &ratelimiters_cmd;
-else switch(mode) {
-case RATE_PER_CONN:
-  anchor = &ratelimiters_conn;
-  store_pool = POOL_PERM;
-  break;
-case RATE_PER_BYTE:
-case RATE_PER_MAIL:
-case RATE_PER_ALLRCPTS:
-  anchor = &ratelimiters_mail;
-  break;
-case RATE_PER_ADDR:
-case RATE_PER_CMD:
-case RATE_PER_RCPT:
-  anchor = &ratelimiters_cmd;
-  break;
-default:
-  anchor = NULL; /* silence an "unused" complaint */
-  log_write(0, LOG_MAIN|LOG_PANIC_DIE,
-    "internal ACL error: unknown ratelimit mode %d", mode);
-  break;
-}
+else switch(mode)
+  {
+  case RATE_PER_CONN:
+    anchor = &ratelimiters_conn;
+    store_pool = POOL_PERM;
+    break;
+  case RATE_PER_BYTE:
+  case RATE_PER_MAIL:
+  case RATE_PER_ALLRCPTS:
+    anchor = &ratelimiters_mail;
+    break;
+  case RATE_PER_ADDR:
+  case RATE_PER_CMD:
+  case RATE_PER_RCPT:
+    anchor = &ratelimiters_cmd;
+    break;
+  default:
+    anchor = NULL; /* silence an "unused" complaint */
+    log_write(0, LOG_MAIN|LOG_PANIC_DIE,
+      "internal ACL error: unknown ratelimit mode %d", mode);
+    break;
+  }
 
-t = tree_search(*anchor, key);
-if (t != NULL)
+if ((t = tree_search(*anchor, key)))
   {
   dbd = t->data.ptr;
   /* The following few lines duplicate some of the code below. */
@@ -2365,7 +2431,7 @@ dbd = NULL;
 
 gettimeofday(&tv, NULL);
 
-if (dbdb != NULL)
+if (dbdb)
   {
   /* Locate the basic ratelimit block inside the DB data. */
   HDEBUG(D_acl) debug_printf_indent("ratelimit found key in database\n");
@@ -2376,7 +2442,7 @@ if (dbdb != NULL)
   filter because we want its size to change if the limit changes. Note that
   we keep the dbd pointer for copying the rate into the new data block. */
 
-  if(unique != NULL && tv.tv_sec > dbdb->bloom_epoch + period)
+  if(unique && tv.tv_sec > dbdb->bloom_epoch + period)
     {
     HDEBUG(D_acl) debug_printf_indent("ratelimit discarding old Bloom filter\n");
     dbdb = NULL;
@@ -2384,7 +2450,7 @@ if (dbdb != NULL)
 
   /* Sanity check. */
 
-  if(unique != NULL && dbdb_size < sizeof(*dbdb))
+  if(unique && dbdb_size < sizeof(*dbdb))
     {
     HDEBUG(D_acl) debug_printf_indent("ratelimit discarding undersize Bloom filter\n");
     dbdb = NULL;
@@ -2394,9 +2460,9 @@ if (dbdb != NULL)
 /* Allocate a new data block if the database lookup failed
 or the Bloom filter passed its age limit. */
 
-if (dbdb == NULL)
+if (!dbdb)
   {
-  if (unique == NULL)
+  if (!unique)
     {
     /* No Bloom filter. This basic ratelimit block is initialized below. */
     HDEBUG(D_acl) debug_printf_indent("ratelimit creating new rate data block\n");
@@ -2423,7 +2489,7 @@ if (dbdb == NULL)
     /* Preserve any basic ratelimit data (which is our longer-term memory)
     by copying it from the discarded block. */
 
-    if (dbd != NULL)
+    if (dbd)
       {
       dbdb->dbd = *dbd;
       dbd = &dbdb->dbd;
@@ -2437,7 +2503,7 @@ counted. We skip this code in readonly mode for efficiency, because any
 changes to the filter will be discarded and because count is already set to
 zero. */
 
-if (unique != NULL && !readonly)
+if (unique && !readonly)
   {
   /* We identify unique events using a Bloom filter. (You can find my
   notes on Bloom filters at http://fanf.livejournal.com/81696.html)
@@ -2519,7 +2585,7 @@ if (unique != NULL && !readonly)
 the new one, otherwise update the block from the database. The initial rate
 is what would be computed by the code below for an infinite interval. */
 
-if (dbd == NULL)
+if (!dbd)
   {
   HDEBUG(D_acl) debug_printf_indent("ratelimit initializing new key's rate data\n");
   dbd = &dbdb->dbd;
@@ -2611,7 +2677,7 @@ else
 This matters for edge cases such as a limit of zero, when the client
 should be completely blocked. */
 
-rc = (dbd->rate < limit)? FAIL : OK;
+rc = dbd->rate < limit ? FAIL : OK;
 
 /* Update the state if the rate is low or if we are being strict. If we
 are in leaky mode and the sender's rate is too high, we do not update
@@ -2726,8 +2792,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 +2921,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);
@@ -2899,9 +2977,8 @@ for (; cb != NULL; cb = cb->next)
     break;
 
     case ACLC_AUTHENTICATED:
-    rc = (sender_host_authenticated == NULL)? FAIL :
-      match_isinlist(sender_host_authenticated, &arg, 0, NULL, NULL, MCL_STRING,
-        TRUE, NULL);
+      rc = sender_host_authenticated ? match_isinlist(sender_host_authenticated,
+             &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL) : FAIL;
     break;
 
     #ifdef EXPERIMENTAL_BRIGHTMAIL
@@ -3181,6 +3258,8 @@ for (; cb != NULL; cb = cb->next)
        break;
 
        case CONTROL_CUTTHROUGH_DELIVERY:
+       {
+       uschar * ignored = NULL;
 #ifndef DISABLE_PRDR
        if (prdr_requested)
 #else
@@ -3189,20 +3268,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 +3296,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 +3438,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 +3446,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 +3602,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 +3651,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 +3747,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 +3763,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 +3776,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 +3795,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 +3978,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 +4428,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: