Support ret-full on lsearch. Bug 2611
[exim.git] / src / src / acl.c
index 8fbd2250f7499f08dbd003b2e653c6e4022f94db..8e17a0e9c618ba0cc90d6dc605dd45c92935b915 100644 (file)
@@ -3,6 +3,7 @@
 *************************************************/
 
 /* Copyright (c) University of Cambridge 1995 - 2018 */
 *************************************************/
 
 /* Copyright (c) University of Cambridge 1995 - 2018 */
+/* Copyright (c) The Exim Maintainers 2020 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Code for handling Access Control Lists (ACLs) */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Code for handling Access Control Lists (ACLs) */
 
 #define CALLOUT_TIMEOUT_DEFAULT 30
 
 
 #define CALLOUT_TIMEOUT_DEFAULT 30
 
+/* Default quota cache TTLs */
+
+#define QUOTA_POS_DEFAULT (5*60)
+#define QUOTA_NEG_DEFAULT (60*60)
+
+
 /* ACL verb codes - keep in step with the table of verbs that follows */
 
 enum { ACL_ACCEPT, ACL_DEFER, ACL_DENY, ACL_DISCARD, ACL_DROP, ACL_REQUIRE,
 /* ACL verb codes - keep in step with the table of verbs that follows */
 
 enum { ACL_ACCEPT, ACL_DEFER, ACL_DENY, ACL_DISCARD, ACL_DROP, ACL_REQUIRE,
@@ -70,7 +77,7 @@ enum { ACLC_ACL,
        ACLC_DKIM_SIGNER,
        ACLC_DKIM_STATUS,
 #endif
        ACLC_DKIM_SIGNER,
        ACLC_DKIM_STATUS,
 #endif
-#ifdef EXPERIMENTAL_DMARC
+#ifdef SUPPORT_DMARC
        ACLC_DMARC_STATUS,
 #endif
        ACLC_DNSLISTS,
        ACLC_DMARC_STATUS,
 #endif
        ACLC_DNSLISTS,
@@ -112,7 +119,8 @@ enum { ACLC_ACL,
 /* ACL conditions/modifiers: "delay", "control", "continue", "endpass",
 "message", "log_message", "log_reject_target", "logwrite", "queue" and "set" are
 modifiers that look like conditions but always return TRUE. They are used for
 /* ACL conditions/modifiers: "delay", "control", "continue", "endpass",
 "message", "log_message", "log_reject_target", "logwrite", "queue" and "set" are
 modifiers that look like conditions but always return TRUE. They are used for
-their side effects. */
+their side effects.  Do not invent new modifier names that result in one name
+being the prefix of another; the binary-search in the list will go wrong. */
 
 typedef struct condition_def {
   uschar       *name;
 
 typedef struct condition_def {
   uschar       *name;
@@ -192,7 +200,7 @@ static condition_def conditions[] = {
   [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
   [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
+#ifdef SUPPORT_DMARC
   [ACLC_DMARC_STATUS] =                { US"dmarc_status",     TRUE, FALSE, (unsigned int) ~ACL_BIT_DATA },
 #endif
 
   [ACLC_DMARC_STATUS] =                { US"dmarc_status",     TRUE, FALSE, (unsigned int) ~ACL_BIT_DATA },
 #endif
 
@@ -346,7 +354,7 @@ enum {
 #ifndef DISABLE_DKIM
   CONTROL_DKIM_VERIFY,
 #endif
 #ifndef DISABLE_DKIM
   CONTROL_DKIM_VERIFY,
 #endif
-#ifdef EXPERIMENTAL_DMARC
+#ifdef SUPPORT_DMARC
   CONTROL_DMARC_VERIFY,
   CONTROL_DMARC_FORENSIC,
 #endif
   CONTROL_DMARC_VERIFY,
   CONTROL_DMARC_FORENSIC,
 #endif
@@ -366,10 +374,7 @@ enum {
   CONTROL_NO_MULTILINE,
   CONTROL_NO_PIPELINING,
 
   CONTROL_NO_MULTILINE,
   CONTROL_NO_PIPELINING,
 
-  CONTROL_QUEUE_ONLY,
-#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
-  CONTROL_REQUIRETLS,
-#endif
+  CONTROL_QUEUE,
   CONTROL_SUBMISSION,
   CONTROL_SUPPRESS_LOCAL_FIXUPS,
 #ifdef SUPPORT_I18N
   CONTROL_SUBMISSION,
   CONTROL_SUPPRESS_LOCAL_FIXUPS,
 #ifdef SUPPORT_I18N
@@ -420,7 +425,7 @@ static control_def controls_list[] = {
   },
 #endif
 
   },
 #endif
 
-#ifdef EXPERIMENTAL_DMARC
+#ifdef SUPPORT_DMARC
 [CONTROL_DMARC_VERIFY] =
   { US"dmarc_disable_verify",    FALSE,
          ACL_BIT_DATA | ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START
 [CONTROL_DMARC_VERIFY] =
   { US"dmarc_disable_verify",    FALSE,
          ACL_BIT_DATA | ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START
@@ -505,8 +510,8 @@ static control_def controls_list[] = {
          ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START
   },
 
          ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START
   },
 
-[CONTROL_QUEUE_ONLY] =
-  { US"queue_only",              FALSE,
+[CONTROL_QUEUE] =
+  { US"queue",                 TRUE,
          (unsigned)
          ~(ACL_BIT_MAIL | ACL_BIT_RCPT |
            ACL_BIT_PREDATA | ACL_BIT_DATA |
          (unsigned)
          ~(ACL_BIT_MAIL | ACL_BIT_RCPT |
            ACL_BIT_PREDATA | ACL_BIT_DATA |
@@ -514,17 +519,6 @@ static control_def controls_list[] = {
            ACL_BIT_NOTSMTP | ACL_BIT_MIME)
   },
 
            ACL_BIT_NOTSMTP | ACL_BIT_MIME)
   },
 
-
-#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
-[CONTROL_REQUIRETLS] =
-  { US"requiretls",             FALSE,
-         (unsigned)
-         ~(ACL_BIT_MAIL | ACL_BIT_RCPT | ACL_BIT_PREDATA |
-           ACL_BIT_DATA | ACL_BIT_MIME |
-           ACL_BIT_NOTSMTP)
-  },
-#endif
-
 [CONTROL_SUBMISSION] =
   { US"submission",              TRUE,
          (unsigned)
 [CONTROL_SUBMISSION] =
   { US"submission",              TRUE,
          (unsigned)
@@ -745,7 +739,7 @@ uschar * s;
 
 *error = NULL;
 
 
 *error = NULL;
 
-while ((s = (*func)()) != NULL)
+while ((s = (*func)()))
   {
   int v, c;
   BOOL negated = FALSE;
   {
   int v, c;
   BOOL negated = FALSE;
@@ -755,8 +749,7 @@ while ((s = (*func)()) != NULL)
   /* Conditions (but not verbs) are allowed to be negated by an initial
   exclamation mark. */
 
   /* Conditions (but not verbs) are allowed to be negated by an initial
   exclamation mark. */
 
-  while (isspace(*s)) s++;
-  if (*s == '!')
+  if (Uskip_whitespace(&s) == '!')
     {
     negated = TRUE;
     s++;
     {
     negated = TRUE;
     s++;
@@ -790,7 +783,7 @@ while ((s = (*func)()) != NULL)
       *error = string_sprintf("malformed ACL line \"%s\"", saveline);
       return NULL;
       }
       *error = string_sprintf("malformed ACL line \"%s\"", saveline);
       return NULL;
       }
-    this = store_get(sizeof(acl_block));
+    this = store_get(sizeof(acl_block), FALSE);
     *lastp = this;
     lastp = &(this->next);
     this->next = NULL;
     *lastp = this;
     lastp = &(this->next);
     this->next = NULL;
@@ -837,7 +830,7 @@ while ((s = (*func)()) != NULL)
     return NULL;
     }
 
     return NULL;
     }
 
-  cond = store_get(sizeof(acl_condition_block));
+  cond = store_get(sizeof(acl_condition_block), FALSE);
   cond->next = NULL;
   cond->type = c;
   cond->u.negated = negated;
   cond->next = NULL;
   cond->type = c;
   cond->u.negated = negated;
@@ -872,18 +865,17 @@ while ((s = (*func)()) != NULL)
        }
       cond->u.varname = string_copyn(s, 18);
       s = endptr;
        }
       cond->u.varname = string_copyn(s, 18);
       s = endptr;
-      while (isspace(*s)) s++;
+      Uskip_whitespace(&s);
       }
     else
 #endif
     {
     uschar *endptr;
 
       }
     else
 #endif
     {
     uschar *endptr;
 
-    if (Ustrncmp(s, "acl_c", 5) != 0 &&
-        Ustrncmp(s, "acl_m", 5) != 0)
+    if (Ustrncmp(s, "acl_c", 5) != 0 && Ustrncmp(s, "acl_m", 5) != 0)
       {
       *error = string_sprintf("invalid variable name after \"set\" in ACL "
       {
       *error = string_sprintf("invalid variable name after \"set\" in ACL "
-        "modifier \"set %s\" (must start \"acl_c\" or \"acl_m\")", s);
+       "modifier \"set %s\" (must start \"acl_c\" or \"acl_m\")", s);
       return NULL;
       }
 
       return NULL;
       }
 
@@ -891,25 +883,25 @@ while ((s = (*func)()) != NULL)
     if (!isdigit(*endptr) && *endptr != '_')
       {
       *error = string_sprintf("invalid variable name after \"set\" in ACL "
     if (!isdigit(*endptr) && *endptr != '_')
       {
       *error = string_sprintf("invalid variable name after \"set\" in ACL "
-        "modifier \"set %s\" (digit or underscore must follow acl_c or acl_m)",
-        s);
+       "modifier \"set %s\" (digit or underscore must follow acl_c or acl_m)",
+       s);
       return NULL;
       }
 
       return NULL;
       }
 
-    while (*endptr != 0 && *endptr != '=' && !isspace(*endptr))
+    while (*endptr && *endptr != '=' && !isspace(*endptr))
       {
       if (!isalnum(*endptr) && *endptr != '_')
       {
       if (!isalnum(*endptr) && *endptr != '_')
-        {
-        *error = string_sprintf("invalid character \"%c\" in variable name "
-          "in ACL modifier \"set %s\"", *endptr, s);
-        return NULL;
-        }
+       {
+       *error = string_sprintf("invalid character \"%c\" in variable name "
+         "in ACL modifier \"set %s\"", *endptr, s);
+       return NULL;
+       }
       endptr++;
       }
 
     cond->u.varname = string_copyn(s + 4, endptr - s - 4);
     s = endptr;
       endptr++;
       }
 
     cond->u.varname = string_copyn(s + 4, endptr - s - 4);
     s = endptr;
-    while (isspace(*s)) s++;
+    Uskip_whitespace(&s);
     }
 
   /* For "set", we are now positioned for the data. For the others, only
     }
 
   /* For "set", we are now positioned for the data. For the others, only
@@ -923,7 +915,7 @@ while ((s = (*func)()) != NULL)
         conditions[c].is_modifier ? US"modifier" : US"condition");
       return NULL;
       }
         conditions[c].is_modifier ? US"modifier" : US"condition");
       return NULL;
       }
-    while (isspace(*s)) s++;
+    Uskip_whitespace(&s);
     cond->arg = string_copy(s);
     }
   }
     cond->arg = string_copy(s);
     }
   }
@@ -1035,7 +1027,9 @@ for (p = q; *p; p = q)
 
   if (!*hptr)
     {
 
   if (!*hptr)
     {
-    header_line *h = store_get(sizeof(header_line));
+    /* The header_line struct itself is not tainted, though it points to
+    possibly tainted data. */
+    header_line * h = store_get(sizeof(header_line), FALSE);
     h->text = hdr;
     h->next = NULL;
     h->type = newtype;
     h->text = hdr;
     h->next = NULL;
     h->type = newtype;
@@ -1192,8 +1186,6 @@ acl_verify_reverse(uschar **user_msgptr, uschar **log_msgptr)
 {
 int rc;
 
 {
 int rc;
 
-user_msgptr = user_msgptr;  /* stop compiler warning */
-
 /* Previous success */
 
 if (sender_host_name != NULL) return OK;
 /* Previous success */
 
 if (sender_host_name != NULL) return OK;
@@ -1321,7 +1313,7 @@ acl_verify_csa(const uschar *domain)
 tree_node *t;
 const uschar *found;
 int priority, weight, port;
 tree_node *t;
 const uschar *found;
 int priority, weight, port;
-dns_answer dnsa;
+dns_answer * dnsa = store_get_dns_answer();
 dns_scan dnss;
 dns_record *rr;
 int rc, type;
 dns_scan dnss;
 dns_record *rr;
 int rc, type;
@@ -1356,8 +1348,7 @@ extension to CSA, so we allow it to be turned off for proper conformance. */
 if (string_is_ip_address(domain, NULL) != 0)
   {
   if (!dns_csa_use_reverse) return CSA_UNKNOWN;
 if (string_is_ip_address(domain, NULL) != 0)
   {
   if (!dns_csa_use_reverse) return CSA_UNKNOWN;
-  dns_build_reverse(domain, target);
-  domain = target;
+  domain = dns_build_reverse(domain);
   }
 
 /* Find out if we've already done the CSA check for this domain. If we have,
   }
 
 /* Find out if we've already done the CSA check for this domain. If we have,
@@ -1368,14 +1359,14 @@ we return from this function. */
 t = tree_search(csa_cache, domain);
 if (t != NULL) return t->data.val;
 
 t = tree_search(csa_cache, domain);
 if (t != NULL) return t->data.val;
 
-t = store_get_perm(sizeof(tree_node) + Ustrlen(domain));
+t = store_get_perm(sizeof(tree_node) + Ustrlen(domain), is_tainted(domain));
 Ustrcpy(t->name, domain);
 (void)tree_insertnode(&csa_cache, t);
 
 /* Now we are ready to do the actual DNS lookup(s). */
 
 found = domain;
 Ustrcpy(t->name, domain);
 (void)tree_insertnode(&csa_cache, t);
 
 /* Now we are ready to do the actual DNS lookup(s). */
 
 found = domain;
-switch (dns_special_lookup(&dnsa, domain, T_CSA, &found))
+switch (dns_special_lookup(dnsa, domain, T_CSA, &found))
   {
   /* If something bad happened (most commonly DNS_AGAIN), defer. */
 
   {
   /* If something bad happened (most commonly DNS_AGAIN), defer. */
 
@@ -1396,9 +1387,9 @@ switch (dns_special_lookup(&dnsa, domain, T_CSA, &found))
 
 /* Scan the reply for well-formed CSA SRV records. */
 
 
 /* Scan the reply for well-formed CSA SRV records. */
 
-for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
+for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS);
      rr;
      rr;
-     rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT)) if (rr->type == T_SRV)
+     rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)) if (rr->type == T_SRV)
   {
   const uschar * p = rr->data;
 
   {
   const uschar * p = rr->data;
 
@@ -1438,7 +1429,7 @@ for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
   client's IP address is listed as one of the SRV target addresses. Save the
   target hostname then break to scan the additional data for its addresses. */
 
   client's IP address is listed as one of the SRV target addresses. Save the
   target hostname then break to scan the additional data for its addresses. */
 
-  (void)dn_expand(dnsa.answer, dnsa.answer + dnsa.answerlen, p,
+  (void)dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, p,
     (DN_EXPAND_ARG4_TYPE)target, sizeof(target));
 
   DEBUG(D_acl) debug_printf_indent("CSA target is %s\n", target);
     (DN_EXPAND_ARG4_TYPE)target, sizeof(target));
 
   DEBUG(D_acl) debug_printf_indent("CSA target is %s\n", target);
@@ -1463,7 +1454,7 @@ to the target. If the name server didn't return any additional data (e.g.
 because it does not fully support SRV records), we need to do another lookup
 to obtain the target addresses; otherwise we have a definitive result. */
 
 because it does not fully support SRV records), we need to do another lookup
 to obtain the target addresses; otherwise we have a definitive result. */
 
-rc = acl_verify_csa_address(&dnsa, &dnss, RESET_ADDITIONAL, target);
+rc = acl_verify_csa_address(dnsa, &dnss, RESET_ADDITIONAL, target);
 if (rc != CSA_FAIL_NOADDR) return t->data.val = rc;
 
 /* The DNS lookup type corresponds to the IP version used by the client. */
 if (rc != CSA_FAIL_NOADDR) return t->data.val = rc;
 
 /* The DNS lookup type corresponds to the IP version used by the client. */
@@ -1477,7 +1468,7 @@ else
 
 
 lookup_dnssec_authenticated = NULL;
 
 
 lookup_dnssec_authenticated = NULL;
-switch (dns_lookup(&dnsa, target, type, NULL))
+switch (dns_lookup(dnsa, target, type, NULL))
   {
   /* If something bad happened (most commonly DNS_AGAIN), defer. */
 
   {
   /* If something bad happened (most commonly DNS_AGAIN), defer. */
 
@@ -1487,7 +1478,7 @@ switch (dns_lookup(&dnsa, target, type, NULL))
   /* If the query succeeded, scan the addresses and return the result. */
 
   case DNS_SUCCEED:
   /* If the query succeeded, scan the addresses and return the result. */
 
   case DNS_SUCCEED:
-    rc = acl_verify_csa_address(&dnsa, &dnss, RESET_ANSWERS, target);
+    rc = acl_verify_csa_address(dnsa, &dnss, RESET_ANSWERS, target);
     if (rc != CSA_FAIL_NOADDR) return t->data.val = rc;
     /* else fall through */
 
     if (rc != CSA_FAIL_NOADDR) return t->data.val = rc;
     /* else fall through */
 
@@ -1519,19 +1510,19 @@ typedef struct {
   unsigned alt_opt_sep;                /* >0 Non-/ option separator (custom parser) */
   } verify_type_t;
 static verify_type_t verify_type_list[] = {
   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"csa",                 VERIFY_CSA,             ~0,     FALSE, 0 },
-    { 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 },
+    /* name                    value                   where           no-opt opt-sep */
+    { US"reverse_host_lookup", VERIFY_REV_HOST_LKUP,   (unsigned)~0,   FALSE, 0 },
+    { US"certificate",         VERIFY_CERT,            (unsigned)~0,   TRUE,  0 },
+    { US"helo",                        VERIFY_HELO,            (unsigned)~0,   TRUE,  0 },
+    { US"csa",                 VERIFY_CSA,             (unsigned)~0,   FALSE, 0 },
+    { US"header_syntax",       VERIFY_HDR_SYNTAX,      ACL_BITS_HAVEDATA, TRUE, 0 },
+    { US"not_blind",           VERIFY_NOT_BLIND,       ACL_BITS_HAVEDATA, FALSE, 0 },
+    { US"header_sender",       VERIFY_HDR_SNDR,        ACL_BITS_HAVEDATA, FALSE, 0 },
     { US"sender",              VERIFY_SNDR,            ACL_BIT_MAIL | ACL_BIT_RCPT
     { US"sender",              VERIFY_SNDR,            ACL_BIT_MAIL | ACL_BIT_RCPT
-                       |ACL_BIT_PREDATA | ACL_BIT_DATA | ACL_BIT_NOTSMTP,
+                       | ACL_BIT_PREDATA | ACL_BIT_DATA | ACL_BIT_NOTSMTP,
                                                                                FALSE, 6 },
     { US"recipient",           VERIFY_RCPT,            ACL_BIT_RCPT,   FALSE, 0 },
                                                                                FALSE, 6 },
     { 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 },
+    { US"header_names_ascii",  VERIFY_HDR_NAMES_ASCII, ACL_BITS_HAVEDATA, TRUE, 0 },
 #ifdef EXPERIMENTAL_ARC
     { US"arc",                 VERIFY_ARC,             ACL_BIT_DATA,   FALSE , 0 },
 #endif
 #ifdef EXPERIMENTAL_ARC
     { US"arc",                 VERIFY_ARC,             ACL_BIT_DATA,   FALSE , 0 },
 #endif
@@ -1569,6 +1560,20 @@ static callout_opt_t callout_opt_list[] = {
 
 
 
 
 
 
+static int
+v_period(const uschar * s, const uschar * arg, uschar ** log_msgptr)
+{
+int period;
+if ((period = readconf_readtime(s, 0, FALSE)) < 0)
+  {
+  *log_msgptr = string_sprintf("bad time value in ACL condition "
+    "\"verify %s\"", arg);
+  }
+return period;
+}
+
+
+
 /* This function implements the "verify" condition. It is called when
 encountered in any ACL, because some tests are almost always permitted. Some
 just don't make sense, and always fail (for example, an attempt to test a host
 /* This function implements the "verify" condition. It is called when
 encountered in any ACL, because some tests are almost always permitted. Some
 just don't make sense, and always fail (for example, an attempt to test a host
@@ -1603,6 +1608,8 @@ BOOL defer_ok = FALSE;
 BOOL callout_defer_ok = FALSE;
 BOOL no_details = FALSE;
 BOOL success_on_redirect = FALSE;
 BOOL callout_defer_ok = FALSE;
 BOOL no_details = FALSE;
 BOOL success_on_redirect = FALSE;
+BOOL quota = FALSE;
+int quota_pos_cache = QUOTA_POS_DEFAULT, quota_neg_cache = QUOTA_NEG_DEFAULT;
 address_item *sender_vaddr = NULL;
 uschar *verify_sender_address = NULL;
 uschar *pm_mailfrom = NULL;
 address_item *sender_vaddr = NULL;
 uschar *verify_sender_address = NULL;
 uschar *pm_mailfrom = NULL;
@@ -1614,7 +1621,7 @@ an error if options are given for items that don't expect them.
 
 uschar *slash = Ustrchr(arg, '/');
 const uschar *list = arg;
 
 uschar *slash = Ustrchr(arg, '/');
 const uschar *list = arg;
-uschar *ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size);
+uschar *ss = string_nextinlist(&list, &sep, NULL, 0);
 verify_type_t * vp;
 
 if (!ss) goto BAD_VERIFY;
 verify_type_t * vp;
 
 if (!ss) goto BAD_VERIFY;
@@ -1724,14 +1731,27 @@ switch(vp->value)
   case VERIFY_NOT_BLIND:
     /* Check that no recipient of this message is "blind", that is, every envelope
     recipient must be mentioned in either To: or Cc:. */
   case VERIFY_NOT_BLIND:
     /* Check that no recipient of this message is "blind", that is, every envelope
     recipient must be mentioned in either To: or Cc:. */
+    {
+    BOOL case_sensitive = TRUE;
+
+    while ((ss = string_nextinlist(&list, &sep, NULL, 0)))
+      if (strcmpic(ss, US"case_insensitive") == 0)
+        case_sensitive = FALSE;
+      else
+        {
+        *log_msgptr = string_sprintf("unknown option \"%s\" in ACL "
+           "condition \"verify %s\"", ss, arg);
+        return ERROR;
+        }
 
 
-    if ((rc = verify_check_notblind()) != OK)
+    if ((rc = verify_check_notblind(case_sensitive)) != OK)
       {
       {
-      *log_msgptr = string_sprintf("bcc recipient detected");
+      *log_msgptr = US"bcc recipient detected";
       if (smtp_return_error_details)
         *user_msgptr = string_sprintf("Rejected after DATA: %s", *log_msgptr);
       }
     return rc;
       if (smtp_return_error_details)
         *user_msgptr = string_sprintf("Rejected after DATA: %s", *log_msgptr);
       }
     return rc;
+    }
 
   /* The remaining verification tests check recipient and sender addresses,
   either from the envelope or from the header. There are a number of
 
   /* The remaining verification tests check recipient and sender addresses,
   either from the envelope or from the header. There are a number of
@@ -1746,7 +1766,7 @@ switch(vp->value)
     in place of the actual sender (rare special-case requirement). */
     {
     uschar *s = ss + 6;
     in place of the actual sender (rare special-case requirement). */
     {
     uschar *s = ss + 6;
-    if (*s == 0)
+    if (!*s)
       verify_sender_address = sender_address;
     else
       {
       verify_sender_address = sender_address;
     else
       {
@@ -1767,7 +1787,7 @@ switch(vp->value)
 /* Remaining items are optional; they apply to sender and recipient
 verification, including "header sender" verification. */
 
 /* Remaining items are optional; they apply to sender and recipient
 verification, including "header sender" verification. */
 
-while ((ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size)))
+while ((ss = string_nextinlist(&list, &sep, NULL, 0)))
   {
   if (strcmpic(ss, US"defer_ok") == 0) defer_ok = TRUE;
   else if (strcmpic(ss, US"no_details") == 0) no_details = TRUE;
   {
   if (strcmpic(ss, US"defer_ok") == 0) defer_ok = TRUE;
   else if (strcmpic(ss, US"no_details") == 0) no_details = TRUE;
@@ -1792,19 +1812,16 @@ while ((ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size)))
   else if (strncmpic(ss, US"callout", 7) == 0)
     {
     callout = CALLOUT_TIMEOUT_DEFAULT;
   else if (strncmpic(ss, US"callout", 7) == 0)
     {
     callout = CALLOUT_TIMEOUT_DEFAULT;
-    ss += 7;
-    if (*ss != 0)
+    if (*(ss += 7))
       {
       while (isspace(*ss)) ss++;
       if (*ss++ == '=')
         {
        const uschar * sublist = ss;
         int optsep = ',';
       {
       while (isspace(*ss)) ss++;
       if (*ss++ == '=')
         {
        const uschar * sublist = ss;
         int optsep = ',';
-        uschar buffer[256];
-       uschar * opt;
 
         while (isspace(*sublist)) sublist++;
 
         while (isspace(*sublist)) sublist++;
-        while ((opt = string_nextinlist(&sublist, &optsep, buffer, sizeof(buffer))))
+        for (uschar * opt; opt = string_nextinlist(&sublist, &optsep, NULL, 0); )
           {
          callout_opt_t * op;
          double period = 1.0F;
           {
          callout_opt_t * op;
          double period = 1.0F;
@@ -1826,12 +1843,8 @@ while ((ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size)))
               }
             while (isspace(*opt)) opt++;
            }
               }
             while (isspace(*opt)) opt++;
            }
-         if (op->timeval && (period = readconf_readtime(opt, 0, FALSE)) < 0)
-           {
-           *log_msgptr = string_sprintf("bad time value in ACL condition "
-             "\"verify %s\"", arg);
+         if (op->timeval && (period = v_period(opt, arg, log_msgptr)) < 0)
            return ERROR;
            return ERROR;
-           }
 
          switch(op->value)
            {
 
          switch(op->value)
            {
@@ -1864,6 +1877,38 @@ while ((ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size)))
       }
     }
 
       }
     }
 
+  /* The quota option has sub-options, comma-separated */
+
+  else if (strncmpic(ss, US"quota", 5) == 0)
+    {
+    quota = TRUE;
+    if (*(ss += 5))
+      {
+      while (isspace(*ss)) ss++;
+      if (*ss++ == '=')
+        {
+       const uschar * sublist = ss;
+        int optsep = ',';
+       int period;
+
+        while (isspace(*sublist)) sublist++;
+        for (uschar * opt; opt = string_nextinlist(&sublist, &optsep, NULL, 0); )
+         if (Ustrncmp(opt, "cachepos=", 9) == 0)
+           if ((period = v_period(opt += 9, arg, log_msgptr)) < 0)
+             return ERROR;
+           else
+             quota_pos_cache = period;
+         else if (Ustrncmp(opt, "cacheneg=", 9) == 0)
+           if ((period = v_period(opt += 9, arg, log_msgptr)) < 0)
+             return ERROR;
+           else
+             quota_neg_cache = period;
+         else if (Ustrcmp(opt, "no_cache") == 0)
+           quota_pos_cache = quota_neg_cache = 0;
+       }
+      }
+    }
+
   /* Option not recognized */
 
   else
   /* Option not recognized */
 
   else
@@ -1882,6 +1927,31 @@ if ((verify_options & (vopt_callout_recipsender|vopt_callout_recippmaster)) ==
   return ERROR;
   }
 
   return ERROR;
   }
 
+/* Handle quota verification */
+if (quota)
+  {
+  if (vp->value != VERIFY_RCPT)
+    {
+    *log_msgptr = US"can only verify quota of recipient";
+    return ERROR;
+    }
+
+  if ((rc = verify_quota_call(addr->address,
+             quota_pos_cache, quota_neg_cache, log_msgptr)) != OK)
+    {
+    *basic_errno = errno;
+    if (smtp_return_error_details)
+      {
+      if (!*user_msgptr && *log_msgptr)
+        *user_msgptr = string_sprintf("Rejected after %s: %s",
+           smtp_names[smtp_connection_had[smtp_ch_index-1]], *log_msgptr);
+      if (rc == DEFER) f.acl_temp_details = TRUE;
+      }
+    }
+
+  return rc;
+  }
+
 /* Handle sender-in-header verification. Default the user message to the log
 message if giving out verification details. */
 
 /* Handle sender-in-header verification. Default the user message to the log
 message if giving out verification details. */
 
@@ -1928,8 +1998,8 @@ else if (verify_sender_address)
     }
 
   sender_vaddr = verify_checked_sender(verify_sender_address);
     }
 
   sender_vaddr = verify_checked_sender(verify_sender_address);
-  if (sender_vaddr != NULL &&               /* Previously checked */
-      callout <= 0)                         /* No callout needed this time */
+  if (   sender_vaddr                          /* Previously checked */
+      && callout <= 0)                         /* No callout needed this time */
     {
     /* If the "routed" flag is set, it means that routing worked before, so
     this check can give OK (the saved return code value, if set, belongs to a
     {
     /* If the "routed" flag is set, it means that routing worked before, so
     this check can give OK (the saved return code value, if set, belongs to a
@@ -1996,14 +2066,12 @@ else if (verify_sender_address)
         *basic_errno = sender_vaddr->basic_errno;
       else
        DEBUG(D_acl)
         *basic_errno = sender_vaddr->basic_errno;
       else
        DEBUG(D_acl)
-         {
          if (Ustrcmp(sender_vaddr->address, verify_sender_address) != 0)
            debug_printf_indent("sender %s verified ok as %s\n",
              verify_sender_address, sender_vaddr->address);
          else
            debug_printf_indent("sender %s verified ok\n",
              verify_sender_address);
          if (Ustrcmp(sender_vaddr->address, verify_sender_address) != 0)
            debug_printf_indent("sender %s verified ok as %s\n",
              verify_sender_address, sender_vaddr->address);
          else
            debug_printf_indent("sender %s verified ok\n",
              verify_sender_address);
-         }
       }
     else
       rc = OK;  /* Null sender */
       }
     else
       rc = OK;  /* Null sender */
@@ -2047,8 +2115,7 @@ else
 
   *basic_errno = addr2.basic_errno;
   *log_msgptr = addr2.message;
 
   *basic_errno = addr2.basic_errno;
   *log_msgptr = addr2.message;
-  *user_msgptr = (addr2.user_message != NULL)?
-    addr2.user_message : addr2.message;
+  *user_msgptr = addr2.user_message ? addr2.user_message : addr2.message;
 
   /* Allow details for temporary error if the address is so flagged. */
   if (testflag((&addr2), af_pass_message)) f.acl_temp_details = TRUE;
 
   /* Allow details for temporary error if the address is so flagged. */
   if (testflag((&addr2), af_pass_message)) f.acl_temp_details = TRUE;
@@ -2059,8 +2126,10 @@ else
 
 /* We have a result from the relevant test. Handle defer overrides first. */
 
 
 /* We have a result from the relevant test. Handle defer overrides first. */
 
-if (rc == DEFER && (defer_ok ||
-   (callout_defer_ok && *basic_errno == ERRNO_CALLOUTDEFER)))
+if (  rc == DEFER
+   && (  defer_ok
+      || callout_defer_ok && *basic_errno == ERRNO_CALLOUTDEFER
+   )  )
   {
   HDEBUG(D_acl) debug_printf_indent("verify defer overridden by %s\n",
     defer_ok? "defer_ok" : "callout_defer_ok");
   {
   HDEBUG(D_acl) debug_printf_indent("verify defer overridden by %s\n",
     defer_ok? "defer_ok" : "callout_defer_ok");
@@ -2070,7 +2139,7 @@ if (rc == DEFER && (defer_ok ||
 /* If we've failed a sender, set up a recipient message, and point
 sender_verified_failed to the address item that actually failed. */
 
 /* If we've failed a sender, set up a recipient message, and point
 sender_verified_failed to the address item that actually failed. */
 
-if (rc != OK && verify_sender_address != NULL)
+if (rc != OK && verify_sender_address)
   {
   if (rc != DEFER)
     *log_msgptr = *user_msgptr = US"Sender verify failed";
   {
   if (rc != DEFER)
     *log_msgptr = *user_msgptr = US"Sender verify failed";
@@ -2089,7 +2158,7 @@ if (rc != OK && verify_sender_address != NULL)
 /* Verifying an address messes up the values of $domain and $local_part,
 so reset them before returning if this is a RCPT ACL. */
 
 /* Verifying an address messes up the values of $domain and $local_part,
 so reset them before returning if this is a RCPT ACL. */
 
-if (addr != NULL)
+if (addr)
   {
   deliver_domain = addr->domain;
   deliver_localpart = addr->local_part;
   {
   deliver_domain = addr->domain;
   deliver_localpart = addr->local_part;
@@ -2113,7 +2182,9 @@ return ERROR;
 *        Check argument for control= modifier    *
 *************************************************/
 
 *        Check argument for control= modifier    *
 *************************************************/
 
-/* Called from acl_check_condition() below
+/* Called from acl_check_condition() below.
+To handle the case "queue_only" we accept an _ in the
+initial / option-switch position.
 
 Arguments:
   arg         the argument string for control=
 
 Arguments:
   arg         the argument string for control=
@@ -2129,10 +2200,11 @@ decode_control(const uschar *arg, const uschar **pptr, int where, uschar **log_m
 {
 int idx, len;
 control_def * d;
 {
 int idx, len;
 control_def * d;
+uschar c;
 
 if (  (idx = find_control(arg, controls_list, nelem(controls_list))) < 0
 
 if (  (idx = find_control(arg, controls_list, nelem(controls_list))) < 0
-   || (  arg[len = Ustrlen((d = controls_list+idx)->name)] != 0
-      && (!d->has_option || arg[len] != '/')
+   || (  (c = arg[len = Ustrlen((d = controls_list+idx)->name)]) != 0
+      && (!d->has_option || c != '/' && c != '_')
    )  )
   {
   *log_msgptr = string_sprintf("syntax error in \"control=%s\"", arg);
    )  )
   {
   *log_msgptr = string_sprintf("syntax error in \"control=%s\"", arg);
@@ -2168,10 +2240,10 @@ gstring * g =
   string_cat(NULL, US"error in arguments to \"ratelimit\" condition: ");
 
 va_start(ap, format);
   string_cat(NULL, US"error in arguments to \"ratelimit\" condition: ");
 
 va_start(ap, format);
-g = string_vformat(g, TRUE, format, ap);
+g = string_vformat(g, SVFMT_EXTEND|SVFMT_REBUFFER, format, ap);
 va_end(ap);
 
 va_end(ap);
 
-gstring_reset_unused(g);
+gstring_release_unused(g);
 *log_msgptr = string_from_gstring(g);
 return ERROR;
 }
 *log_msgptr = string_from_gstring(g);
 return ERROR;
 }
@@ -2258,7 +2330,7 @@ count = 1.0;
 
 /* Parse the other options. */
 
 
 /* Parse the other options. */
 
-while ((ss = string_nextinlist(&arg, &sep, big_buffer, big_buffer_size)))
+while ((ss = string_nextinlist(&arg, &sep, NULL, 0)))
   {
   if (strcmpic(ss, US"leaky") == 0) leaky = TRUE;
   else if (strcmpic(ss, US"strict") == 0) strict = TRUE;
   {
   if (strcmpic(ss, US"leaky") == 0) leaky = TRUE;
   else if (strcmpic(ss, US"strict") == 0) strict = TRUE;
@@ -2406,7 +2478,7 @@ if ((t = tree_search(*anchor, key)))
 /* We aren't using a pre-computed rate, so get a previously recorded rate
 from the database, which will be updated and written back if required. */
 
 /* We aren't using a pre-computed rate, so get a previously recorded rate
 from the database, which will be updated and written back if required. */
 
-if (!(dbm = dbfn_open(US"ratelimit", O_RDWR, &dbblock, TRUE)))
+if (!(dbm = dbfn_open(US"ratelimit", O_RDWR, &dbblock, TRUE, TRUE)))
   {
   store_pool = old_pool;
   sender_rate = NULL;
   {
   store_pool = old_pool;
   sender_rate = NULL;
@@ -2455,7 +2527,7 @@ if (!dbdb)
     /* No Bloom filter. This basic ratelimit block is initialized below. */
     HDEBUG(D_acl) debug_printf_indent("ratelimit creating new rate data block\n");
     dbdb_size = sizeof(*dbd);
     /* No Bloom filter. This basic ratelimit block is initialized below. */
     HDEBUG(D_acl) debug_printf_indent("ratelimit creating new rate data block\n");
     dbdb_size = sizeof(*dbd);
-    dbdb = store_get(dbdb_size);
+    dbdb = store_get(dbdb_size, FALSE);                /* not tainted */
     }
   else
     {
     }
   else
     {
@@ -2469,7 +2541,7 @@ if (!dbdb)
     extra = (int)limit * 2 - sizeof(dbdb->bloom);
     if (extra < 0) extra = 0;
     dbdb_size = sizeof(*dbdb) + extra;
     extra = (int)limit * 2 - sizeof(dbdb->bloom);
     if (extra < 0) extra = 0;
     dbdb_size = sizeof(*dbdb) + extra;
-    dbdb = store_get(dbdb_size);
+    dbdb = store_get(dbdb_size, FALSE);                /* not tainted */
     dbdb->bloom_epoch = tv.tv_sec;
     dbdb->bloom_size = sizeof(dbdb->bloom) + extra;
     memset(dbdb->bloom, 0, dbdb->bloom_size);
     dbdb->bloom_epoch = tv.tv_sec;
     dbdb->bloom_size = sizeof(dbdb->bloom) + extra;
     memset(dbdb->bloom, 0, dbdb->bloom_size);
@@ -2686,9 +2758,10 @@ else
 
 dbfn_close(dbm);
 
 
 dbfn_close(dbm);
 
-/* Store the result in the tree for future reference. */
+/* Store the result in the tree for future reference.  Take the taint status
+from the key for consistency even though it's unlikely we'll ever expand this. */
 
 
-t = store_get(sizeof(tree_node) + Ustrlen(key));
+t = store_get(sizeof(tree_node) + Ustrlen(key), is_tainted(key));
 t->data.ptr = dbd;
 Ustrcpy(t->name, key);
 (void)tree_insertnode(anchor, t);
 t->data.ptr = dbd;
 Ustrcpy(t->name, key);
 (void)tree_insertnode(anchor, t);
@@ -2761,7 +2834,7 @@ if (*portend != '\0')
   }
 
 /* Make a single-item host list. */
   }
 
 /* Make a single-item host list. */
-h = store_get(sizeof(host_item));
+h = store_get(sizeof(host_item), FALSE);
 memset(h, 0, sizeof(host_item));
 h->name = hostname;
 h->port = portnum;
 memset(h, 0, sizeof(host_item));
 h->name = hostname;
 h->port = portnum;
@@ -3017,198 +3090,196 @@ for (; cb; cb = cb->next)
       switch(control_type)
        {
        case CONTROL_AUTH_UNADVERTISED:
       switch(control_type)
        {
        case CONTROL_AUTH_UNADVERTISED:
-       f.allow_auth_unadvertised = TRUE;
-       break;
+         f.allow_auth_unadvertised = TRUE;
+         break;
 
 
-       #ifdef EXPERIMENTAL_BRIGHTMAIL
+#ifdef EXPERIMENTAL_BRIGHTMAIL
        case CONTROL_BMI_RUN:
        case CONTROL_BMI_RUN:
-       bmi_run = 1;
-       break;
-       #endif
+         bmi_run = 1;
+         break;
+#endif
 
 
-       #ifndef DISABLE_DKIM
+#ifndef DISABLE_DKIM
        case CONTROL_DKIM_VERIFY:
        case CONTROL_DKIM_VERIFY:
-       f.dkim_disable_verify = TRUE;
-       #ifdef EXPERIMENTAL_DMARC
-       /* Since DKIM was blocked, skip DMARC too */
-       f.dmarc_disable_verify = TRUE;
-       f.dmarc_enable_forensic = FALSE;
-       #endif
+         f.dkim_disable_verify = TRUE;
+# ifdef SUPPORT_DMARC
+         /* Since DKIM was blocked, skip DMARC too */
+         f.dmarc_disable_verify = TRUE;
+         f.dmarc_enable_forensic = FALSE;
+endif
        break;
        break;
-       #endif
+#endif
 
 
-       #ifdef EXPERIMENTAL_DMARC
+#ifdef SUPPORT_DMARC
        case CONTROL_DMARC_VERIFY:
        case CONTROL_DMARC_VERIFY:
-       f.dmarc_disable_verify = TRUE;
-       break;
+         f.dmarc_disable_verify = TRUE;
+         break;
 
        case CONTROL_DMARC_FORENSIC:
 
        case CONTROL_DMARC_FORENSIC:
-       f.dmarc_enable_forensic = TRUE;
-       break;
-       #endif
+         f.dmarc_enable_forensic = TRUE;
+         break;
+#endif
 
        case CONTROL_DSCP:
 
        case CONTROL_DSCP:
-       if (*p == '/')
-         {
-         int fd, af, level, optname, value;
-         /* If we are acting on stdin, the setsockopt may fail if stdin is not
-         a socket; we can accept that, we'll just debug-log failures anyway. */
-         fd = fileno(smtp_in);
-         af = ip_get_address_family(fd);
-         if (af < 0)
+         if (*p == '/')
            {
            {
-           HDEBUG(D_acl)
-             debug_printf_indent("smtp input is probably not a socket [%s], not setting DSCP\n",
-                 strerror(errno));
-           break;
-           }
-         if (dscp_lookup(p+1, af, &level, &optname, &value))
-           {
-           if (setsockopt(fd, level, optname, &value, sizeof(value)) < 0)
+           int fd, af, level, optname, value;
+           /* If we are acting on stdin, the setsockopt may fail if stdin is not
+           a socket; we can accept that, we'll just debug-log failures anyway. */
+           fd = fileno(smtp_in);
+           if ((af = ip_get_address_family(fd)) < 0)
              {
              {
-             HDEBUG(D_acl) debug_printf_indent("failed to set input DSCP[%s]: %s\n",
-                 p+1, strerror(errno));
+             HDEBUG(D_acl)
+               debug_printf_indent("smtp input is probably not a socket [%s], not setting DSCP\n",
+                   strerror(errno));
+             break;
              }
              }
+           if (dscp_lookup(p+1, af, &level, &optname, &value))
+             if (setsockopt(fd, level, optname, &value, sizeof(value)) < 0)
+               {
+               HDEBUG(D_acl) debug_printf_indent("failed to set input DSCP[%s]: %s\n",
+                   p+1, strerror(errno));
+               }
+             else
+               {
+               HDEBUG(D_acl) debug_printf_indent("set input DSCP to \"%s\"\n", p+1);
+               }
            else
              {
            else
              {
-             HDEBUG(D_acl) debug_printf_indent("set input DSCP to \"%s\"\n", p+1);
+             *log_msgptr = string_sprintf("unrecognised DSCP value in \"control=%s\"", arg);
+             return ERROR;
              }
            }
          else
            {
              }
            }
          else
            {
-           *log_msgptr = string_sprintf("unrecognised DSCP value in \"control=%s\"", arg);
+           *log_msgptr = string_sprintf("syntax error in \"control=%s\"", arg);
            return ERROR;
            }
            return ERROR;
            }
-         }
-       else
-         {
-         *log_msgptr = string_sprintf("syntax error in \"control=%s\"", arg);
-         return ERROR;
-         }
-       break;
+         break;
 
        case CONTROL_ERROR:
 
        case CONTROL_ERROR:
-       return ERROR;
+         return ERROR;
 
        case CONTROL_CASEFUL_LOCAL_PART:
 
        case CONTROL_CASEFUL_LOCAL_PART:
-       deliver_localpart = addr->cc_local_part;
-       break;
+         deliver_localpart = addr->cc_local_part;
+         break;
 
        case CONTROL_CASELOWER_LOCAL_PART:
 
        case CONTROL_CASELOWER_LOCAL_PART:
-       deliver_localpart = addr->lc_local_part;
-       break;
+         deliver_localpart = addr->lc_local_part;
+         break;
 
        case CONTROL_ENFORCE_SYNC:
 
        case CONTROL_ENFORCE_SYNC:
-       smtp_enforce_sync = TRUE;
-       break;
+         smtp_enforce_sync = TRUE;
+         break;
 
        case CONTROL_NO_ENFORCE_SYNC:
 
        case CONTROL_NO_ENFORCE_SYNC:
-       smtp_enforce_sync = FALSE;
-       break;
+         smtp_enforce_sync = FALSE;
+         break;
 
 
-       #ifdef WITH_CONTENT_SCAN
+#ifdef WITH_CONTENT_SCAN
        case CONTROL_NO_MBOX_UNSPOOL:
        case CONTROL_NO_MBOX_UNSPOOL:
-       f.no_mbox_unspool = TRUE;
-       break;
-       #endif
+         f.no_mbox_unspool = TRUE;
+         break;
+#endif
 
        case CONTROL_NO_MULTILINE:
 
        case CONTROL_NO_MULTILINE:
-       f.no_multiline_responses = TRUE;
-       break;
+         f.no_multiline_responses = TRUE;
+         break;
 
        case CONTROL_NO_PIPELINING:
 
        case CONTROL_NO_PIPELINING:
-       f.pipelining_enable = FALSE;
-       break;
+         f.pipelining_enable = FALSE;
+         break;
 
        case CONTROL_NO_DELAY_FLUSH:
 
        case CONTROL_NO_DELAY_FLUSH:
-       f.disable_delay_flush = TRUE;
-       break;
+         f.disable_delay_flush = TRUE;
+         break;
 
        case CONTROL_NO_CALLOUT_FLUSH:
 
        case CONTROL_NO_CALLOUT_FLUSH:
-       f.disable_callout_flush = TRUE;
-       break;
+         f.disable_callout_flush = TRUE;
+         break;
 
        case CONTROL_FAKEREJECT:
 
        case CONTROL_FAKEREJECT:
-       cancel_cutthrough_connection(TRUE, US"fakereject");
-       case CONTROL_FAKEDEFER:
-       fake_response = (control_type == CONTROL_FAKEDEFER) ? DEFER : FAIL;
-       if (*p == '/')
-         {
-         const uschar *pp = p + 1;
-         while (*pp) pp++;
-         fake_response_text = expand_string(string_copyn(p+1, pp-p-1));
-         p = pp;
-         }
-        else
-         {
-         /* Explicitly reset to default string */
-         fake_response_text = US"Your message has been rejected but is being kept for evaluation.\nIf it was a legitimate message, it may still be delivered to the target recipient(s).";
-         }
-       break;
+         cancel_cutthrough_connection(TRUE, US"fakereject");
+         case CONTROL_FAKEDEFER:
+         fake_response = (control_type == CONTROL_FAKEDEFER) ? DEFER : FAIL;
+         if (*p == '/')
+           {
+           const uschar *pp = p + 1;
+           while (*pp) pp++;
+           fake_response_text = expand_string(string_copyn(p+1, pp-p-1));
+           p = pp;
+           }
+          else /* Explicitly reset to default string */
+           fake_response_text = US"Your message has been rejected but is being kept for evaluation.\nIf it was a legitimate message, it may still be delivered to the target recipient(s).";
+         break;
 
        case CONTROL_FREEZE:
 
        case CONTROL_FREEZE:
-       f.deliver_freeze = TRUE;
-       deliver_frozen_at = time(NULL);
-       freeze_tell = freeze_tell_config;       /* Reset to configured value */
-       if (Ustrncmp(p, "/no_tell", 8) == 0)
-         {
-         p += 8;
-         freeze_tell = NULL;
-         }
-       if (*p != 0)
-         {
-         *log_msgptr = string_sprintf("syntax error in \"control=%s\"", arg);
-         return ERROR;
-         }
-       cancel_cutthrough_connection(TRUE, US"item frozen");
-       break;
+         f.deliver_freeze = TRUE;
+         deliver_frozen_at = time(NULL);
+         freeze_tell = freeze_tell_config;       /* Reset to configured value */
+         if (Ustrncmp(p, "/no_tell", 8) == 0)
+           {
+           p += 8;
+           freeze_tell = NULL;
+           }
+         if (*p)
+           {
+           *log_msgptr = string_sprintf("syntax error in \"control=%s\"", arg);
+           return ERROR;
+           }
+         cancel_cutthrough_connection(TRUE, US"item frozen");
+         break;
 
 
-       case CONTROL_QUEUE_ONLY:
-       f.queue_only_policy = TRUE;
-       cancel_cutthrough_connection(TRUE, US"queueing forced");
-       break;
+       case CONTROL_QUEUE:
+         f.queue_only_policy = TRUE;
+         if (Ustrcmp(p, "_only") == 0)
+           p += 5;
+         else while (*p == '/')
+           if (Ustrncmp(p, "/only", 5) == 0)
+             { p += 5; f.queue_smtp = FALSE; }
+           else if (Ustrncmp(p, "/first_pass_route", 17) == 0)
+             { p += 17; f.queue_smtp = TRUE; }
+           else
+             break;
+         cancel_cutthrough_connection(TRUE, US"queueing forced");
+         break;
 
 
-#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
-       case CONTROL_REQUIRETLS:
-       tls_requiretls |= REQUIRETLS_MSG;
-       break;
-#endif
        case CONTROL_SUBMISSION:
        case CONTROL_SUBMISSION:
-       originator_name = US"";
-       f.submission_mode = TRUE;
-       while (*p == '/')
-         {
-         if (Ustrncmp(p, "/sender_retain", 14) == 0)
-           {
-           p += 14;
-           f.active_local_sender_retain = TRUE;
-           f.active_local_from_check = FALSE;
-           }
-         else if (Ustrncmp(p, "/domain=", 8) == 0)
+         originator_name = US"";
+         f.submission_mode = TRUE;
+         while (*p == '/')
            {
            {
-           const uschar *pp = p + 8;
-           while (*pp && *pp != '/') pp++;
-           submission_domain = string_copyn(p+8, pp-p-8);
-           p = pp;
+           if (Ustrncmp(p, "/sender_retain", 14) == 0)
+             {
+             p += 14;
+             f.active_local_sender_retain = TRUE;
+             f.active_local_from_check = FALSE;
+             }
+           else if (Ustrncmp(p, "/domain=", 8) == 0)
+             {
+             const uschar *pp = p + 8;
+             while (*pp && *pp != '/') pp++;
+             submission_domain = string_copyn(p+8, pp-p-8);
+             p = pp;
+             }
+           /* The name= option must be last, because it swallows the rest of
+           the string. */
+           else if (Ustrncmp(p, "/name=", 6) == 0)
+             {
+             const uschar *pp = p + 6;
+             while (*pp) pp++;
+             submission_name = string_copy(parse_fix_phrase(p+6, pp-p-6,
+               big_buffer, big_buffer_size));
+             p = pp;
+             }
+           else break;
            }
            }
-         /* The name= option must be last, because it swallows the rest of
-         the string. */
-         else if (Ustrncmp(p, "/name=", 6) == 0)
+         if (*p)
            {
            {
-           const uschar *pp = p + 6;
-           while (*pp) pp++;
-           submission_name = string_copy(parse_fix_phrase(p+6, pp-p-6,
-             big_buffer, big_buffer_size));
-           p = pp;
+           *log_msgptr = string_sprintf("syntax error in \"control=%s\"", arg);
+           return ERROR;
            }
            }
-         else break;
-         }
-       if (*p != 0)
-         {
-         *log_msgptr = string_sprintf("syntax error in \"control=%s\"", arg);
-         return ERROR;
-         }
-       break;
+         break;
 
        case CONTROL_DEBUG:
          {
 
        case CONTROL_DEBUG:
          {
@@ -3243,99 +3314,103 @@ for (; cb; cb = cb->next)
              debug_logging_stop();
            else
              debug_logging_activate(debug_tag, debug_opts);
              debug_logging_stop();
            else
              debug_logging_activate(debug_tag, debug_opts);
+         break;
          }
          }
-       break;
 
        case CONTROL_SUPPRESS_LOCAL_FIXUPS:
 
        case CONTROL_SUPPRESS_LOCAL_FIXUPS:
-       f.suppress_local_fixups = TRUE;
-       break;
+         f.suppress_local_fixups = TRUE;
+         break;
 
        case CONTROL_CUTTHROUGH_DELIVERY:
 
        case CONTROL_CUTTHROUGH_DELIVERY:
-       {
-       uschar * ignored = NULL;
+         {
+         uschar * ignored = NULL;
 #ifndef DISABLE_PRDR
 #ifndef DISABLE_PRDR
-       if (prdr_requested)
+         if (prdr_requested)
 #else
 #else
-       if (0)
+         if (0)
 #endif
 #endif
-         /* 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" */
-         ignored = US"PRDR active";
-       else
-         {
-         if (f.deliver_freeze)
+           /* 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" */
+           ignored = US"PRDR active";
+         else if (f.deliver_freeze)
            ignored = US"frozen";
          else if (f.queue_only_policy)
            ignored = US"queue-only";
          else if (fake_response == FAIL)
            ignored = US"fakereject";
            ignored = US"frozen";
          else if (f.queue_only_policy)
            ignored = US"queue-only";
          else if (fake_response == FAIL)
            ignored = US"fakereject";
+         else if (rcpt_count != 1)
+           ignored = US"nonfirst rcpt";
+         else if (cutthrough.delivery)
+           ignored = US"repeated";
+         else if (cutthrough.callout_hold_only)
+           {
+           DEBUG(D_acl)
+             debug_printf_indent(" cutthrough request upgrades callout hold\n");
+           cutthrough.callout_hold_only = FALSE;
+           cutthrough.delivery = TRUE; /* control accepted */
+           }
          else
            {
          else
            {
-           if (rcpt_count == 1)
+           cutthrough.delivery = TRUE; /* control accepted */
+           while (*p == '/')
              {
              {
-             cutthrough.delivery = TRUE;       /* control accepted */
-             while (*p == '/')
+             const uschar * pp = p+1;
+             if (Ustrncmp(pp, "defer=", 6) == 0)
                {
                {
-               const uschar * pp = p+1;
-               if (Ustrncmp(pp, "defer=", 6) == 0)
-                 {
-                 pp += 6;
-                 if (Ustrncmp(pp, "pass", 4) == 0) cutthrough.defer_pass = TRUE;
-                 /* else if (Ustrncmp(pp, "spool") == 0) ;     default */
-                 }
-               else
-                 while (*pp && *pp != '/') pp++;
-               p = pp;
+               pp += 6;
+               if (Ustrncmp(pp, "pass", 4) == 0) cutthrough.defer_pass = TRUE;
+               /* else if (Ustrncmp(pp, "spool") == 0) ;       default */
                }
                }
+             else
+               while (*pp && *pp != '/') pp++;
+             p = pp;
              }
              }
-           else
-             ignored = US"nonfirst rcpt";
            }
            }
+
+         DEBUG(D_acl) if (ignored)
+           debug_printf(" cutthrough request ignored on %s item\n", ignored);
          }
          }
-       DEBUG(D_acl) if (ignored)
-         debug_printf(" cutthrough request ignored on %s item\n", ignored);
-       }
        break;
 
 #ifdef SUPPORT_I18N
        case CONTROL_UTF8_DOWNCONVERT:
        break;
 
 #ifdef SUPPORT_I18N
        case CONTROL_UTF8_DOWNCONVERT:
-       if (*p == '/')
-         {
-         if (p[1] == '1')
+         if (*p == '/')
            {
            {
-           message_utf8_downconvert = 1;
-           addr->prop.utf8_downcvt = TRUE;
-           addr->prop.utf8_downcvt_maybe = FALSE;
-           p += 2;
-           break;
+           if (p[1] == '1')
+             {
+             message_utf8_downconvert = 1;
+             addr->prop.utf8_downcvt = TRUE;
+             addr->prop.utf8_downcvt_maybe = FALSE;
+             p += 2;
+             break;
+             }
+           if (p[1] == '0')
+             {
+             message_utf8_downconvert = 0;
+             addr->prop.utf8_downcvt = FALSE;
+             addr->prop.utf8_downcvt_maybe = FALSE;
+             p += 2;
+             break;
+             }
+           if (p[1] == '-' && p[2] == '1')
+             {
+             message_utf8_downconvert = -1;
+             addr->prop.utf8_downcvt = FALSE;
+             addr->prop.utf8_downcvt_maybe = TRUE;
+             p += 3;
+             break;
+             }
+           *log_msgptr = US"bad option value for control=utf8_downconvert";
            }
            }
-         if (p[1] == '0')
+         else
            {
            {
-           message_utf8_downconvert = 0;
-           addr->prop.utf8_downcvt = FALSE;
+           message_utf8_downconvert = 1;
+           addr->prop.utf8_downcvt = TRUE;
            addr->prop.utf8_downcvt_maybe = FALSE;
            addr->prop.utf8_downcvt_maybe = FALSE;
-           p += 2;
-           break;
-           }
-         if (p[1] == '-' && p[2] == '1')
-           {
-           message_utf8_downconvert = -1;
-           addr->prop.utf8_downcvt = FALSE;
-           addr->prop.utf8_downcvt_maybe = TRUE;
-           p += 3;
            break;
            }
            break;
            }
-         *log_msgptr = US"bad option value for control=utf8_downconvert";
-         }
-       else
-         {
-         message_utf8_downconvert = 1;
-         addr->prop.utf8_downcvt = TRUE;
-         addr->prop.utf8_downcvt_maybe = FALSE;
-         break;
-         }
-       return ERROR;
+         return ERROR;
 #endif
 
        }
 #endif
 
        }
@@ -3347,11 +3422,11 @@ for (; cb; cb = cb->next)
       {
       /* Separate the regular expression and any optional parameters. */
       const uschar * list = arg;
       {
       /* Separate the regular expression and any optional parameters. */
       const uschar * list = arg;
-      uschar *ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size);
+      uschar *ss = string_nextinlist(&list, &sep, NULL, 0);
       /* Run the dcc backend. */
       rc = dcc_process(&ss);
       /* Modify return code based upon the existence of options. */
       /* Run the dcc backend. */
       rc = dcc_process(&ss);
       /* Modify return code based upon the existence of options. */
-      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 && rc == DEFER)
           rc = FAIL;   /* FAIL so that the message is passed to the next ACL */
       }
         if (strcmpic(ss, US"defer_ok") == 0 && rc == DEFER)
           rc = FAIL;   /* FAIL so that the message is passed to the next ACL */
       }
@@ -3444,7 +3519,7 @@ for (; cb; cb = cb->next)
     break;
     #endif
 
     break;
     #endif
 
-    #ifdef EXPERIMENTAL_DMARC
+#ifdef SUPPORT_DMARC
     case ACLC_DMARC_STATUS:
     if (!f.dmarc_has_been_checked)
       dmarc_process();
     case ACLC_DMARC_STATUS:
     if (!f.dmarc_has_been_checked)
       dmarc_process();
@@ -3454,7 +3529,7 @@ for (; cb; cb = cb->next)
     rc = match_isinlist(dmarc_exim_expand_query(DMARC_VERIFY_STATUS),
                         &arg,0,NULL,NULL,MCL_STRING,TRUE,NULL);
     break;
     rc = match_isinlist(dmarc_exim_expand_query(DMARC_VERIFY_STATUS),
                         &arg,0,NULL,NULL,MCL_STRING,TRUE,NULL);
     break;
-    #endif
+#endif
 
     case ACLC_DNSLISTS:
     rc = verify_check_dnsbl(where, &arg, log_msgptr);
 
     case ACLC_DNSLISTS:
     rc = verify_check_dnsbl(where, &arg, log_msgptr);
@@ -3476,13 +3551,13 @@ for (; cb; cb = cb->next)
       {
       uschar *endcipher = NULL;
       uschar *cipher = Ustrchr(tls_in.cipher, ':');
       {
       uschar *endcipher = NULL;
       uschar *cipher = Ustrchr(tls_in.cipher, ':');
-      if (cipher == NULL) cipher = tls_in.cipher; else
+      if (!cipher) cipher = tls_in.cipher; else
         {
         endcipher = Ustrchr(++cipher, ':');
         {
         endcipher = Ustrchr(++cipher, ':');
-        if (endcipher != NULL) *endcipher = 0;
+        if (endcipher) *endcipher = 0;
         }
       rc = match_isinlist(cipher, &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL);
         }
       rc = match_isinlist(cipher, &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL);
-      if (endcipher != NULL) *endcipher = ':';
+      if (endcipher) *endcipher = ':';
       }
     break;
 
       }
     break;
 
@@ -3495,10 +3570,9 @@ for (; cb; cb = cb->next)
 
     case ACLC_HOSTS:
     rc = verify_check_this_host(&arg, sender_host_cache, NULL,
 
     case ACLC_HOSTS:
     rc = verify_check_this_host(&arg, sender_host_cache, NULL,
-      (sender_host_address == NULL)? US"" : sender_host_address,
-      CUSS &host_data);
+      sender_host_address ? sender_host_address : US"", CUSS &host_data);
     if (rc == DEFER) *log_msgptr = search_error_message;
     if (rc == DEFER) *log_msgptr = search_error_message;
-    if (host_data) host_data = string_copy_malloc(host_data);
+    if (host_data) host_data = string_copy_perm(host_data, TRUE);
     break;
 
     case ACLC_LOCAL_PARTS:
     break;
 
     case ACLC_LOCAL_PARTS:
@@ -3513,7 +3587,7 @@ for (; cb; cb = cb->next)
       int sep = 0;
       const uschar *s = arg;
       uschar * ss;
       int sep = 0;
       const uschar *s = arg;
       uschar * ss;
-      while ((ss = string_nextinlist(&s, &sep, big_buffer, big_buffer_size)))
+      while ((ss = string_nextinlist(&s, &sep, NULL, 0)))
         {
         if (Ustrcmp(ss, "main") == 0) logbits |= LOG_MAIN;
         else if (Ustrcmp(ss, "panic") == 0) logbits |= LOG_PANIC;
         {
         if (Ustrcmp(ss, "main") == 0) logbits |= LOG_MAIN;
         else if (Ustrcmp(ss, "panic") == 0) logbits |= LOG_PANIC;
@@ -3566,7 +3640,7 @@ for (; cb; cb = cb->next)
       {
       /* Separate the regular expression and any optional parameters. */
       const uschar * list = arg;
       {
       /* Separate the regular expression and any optional parameters. */
       const uschar * list = arg;
-      uschar * ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size);
+      uschar * ss = string_nextinlist(&list, &sep, NULL, 0);
       uschar * opt;
       BOOL defer_ok = FALSE;
       int timeout = 0;
       uschar * opt;
       BOOL defer_ok = FALSE;
       int timeout = 0;
@@ -3594,13 +3668,19 @@ for (; cb; cb = cb->next)
     #endif
 
     case ACLC_QUEUE:
     #endif
 
     case ACLC_QUEUE:
+    if (is_tainted(arg))
+      {
+      *log_msgptr = string_sprintf("Tainted name '%s' for queue not permitted",
+                                   arg);
+      return ERROR;
+      }
     if (Ustrchr(arg, '/'))
       {
       *log_msgptr = string_sprintf(
              "Directory separator not permitted in queue name: '%s'", arg);
       return ERROR;
       }
     if (Ustrchr(arg, '/'))
       {
       *log_msgptr = string_sprintf(
              "Directory separator not permitted in queue name: '%s'", arg);
       return ERROR;
       }
-    queue_name = string_copy_malloc(arg);
+    queue_name = string_copy_perm(arg, FALSE);
     break;
 
     case ACLC_RATELIMIT:
     break;
 
     case ACLC_RATELIMIT:
@@ -3637,15 +3717,12 @@ for (; cb; cb = cb->next)
       sender_address_cache, -1, 0, CUSS &sender_data);
     break;
 
       sender_address_cache, -1, 0, CUSS &sender_data);
     break;
 
-    /* Connection variables must persist forever */
+    /* Connection variables must persist forever; message variables not */
 
     case ACLC_SET:
       {
       int old_pool = store_pool;
 
     case ACLC_SET:
       {
       int old_pool = store_pool;
-      if (  cb->u.varname[0] == 'c'
-#ifndef DISABLE_DKIM
-         || cb->u.varname[0] == 'd'
-#endif
+      if (  cb->u.varname[0] != 'm'
 #ifndef DISABLE_EVENT
         || event_name          /* An event is being delivered */
 #endif
 #ifndef DISABLE_EVENT
         || event_name          /* An event is being delivered */
 #endif
@@ -3668,11 +3745,11 @@ for (; cb; cb = cb->next)
       {
       /* Separate the regular expression and any optional parameters. */
       const uschar * list = arg;
       {
       /* Separate the regular expression and any optional parameters. */
       const uschar * list = arg;
-      uschar *ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size);
+      uschar *ss = string_nextinlist(&list, &sep, NULL, 0);
 
       rc = spam(CUSS &ss);
       /* Modify return code based upon the existence of options. */
 
       rc = spam(CUSS &ss);
       /* Modify return code based upon the existence of options. */
-      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 && rc == DEFER)
           rc = FAIL;   /* FAIL so that the message is passed to the next ACL */
       }
         if (strcmpic(ss, US"defer_ok") == 0 && rc == DEFER)
           rc = FAIL;   /* FAIL so that the message is passed to the next ACL */
       }
@@ -3838,16 +3915,16 @@ uschar *yield;
 
 for(;;)
   {
 
 for(;;)
   {
-  while (isspace(*acl_text)) acl_text++;   /* Leading spaces/empty lines */
-  if (*acl_text == 0) return NULL;         /* No more data */
-  yield = acl_text;                        /* Potential data line */
+  Uskip_whitespace(&acl_text);         /* Leading spaces/empty lines */
+  if (!*acl_text) return NULL;         /* No more data */
+  yield = acl_text;                    /* Potential data line */
 
   while (*acl_text && *acl_text != '\n') acl_text++;
 
   /* If we hit the end before a newline, we have the whole logical line. If
   it's a comment, there's no more data to be given. Otherwise, yield it. */
 
 
   while (*acl_text && *acl_text != '\n') acl_text++;
 
   /* If we hit the end before a newline, we have the whole logical line. If
   it's a comment, there's no more data to be given. Otherwise, yield it. */
 
-  if (*acl_text == 0) return (*yield == '#')? NULL : yield;
+  if (!*acl_text) return *yield == '#' ? NULL : yield;
 
   /* After reaching a newline, end this loop if the physical line does not
   start with '#'. If it does, it's a comment, and the loop continues. */
 
   /* After reaching a newline, end this loop if the physical line does not
   start with '#'. If it does, it's a comment, and the loop continues. */
@@ -4000,13 +4077,20 @@ if (Ustrchr(ss, ' ') == NULL)
   else if (*ss == '/')
     {
     struct stat statbuf;
   else if (*ss == '/')
     {
     struct stat statbuf;
+    if (is_tainted(ss))
+      {
+      log_write(0, LOG_MAIN|LOG_PANIC,
+       "attempt to open tainted ACL file name \"%s\"", ss);
+      /* Avoid leaking info to an attacker */
+      *log_msgptr = US"internal configuration error";
+      return ERROR;
+      }
     if ((fd = Uopen(ss, O_RDONLY, 0)) < 0)
       {
       *log_msgptr = string_sprintf("failed to open ACL file \"%s\": %s", ss,
         strerror(errno));
       return ERROR;
       }
     if ((fd = Uopen(ss, O_RDONLY, 0)) < 0)
       {
       *log_msgptr = string_sprintf("failed to open ACL file \"%s\": %s", ss,
         strerror(errno));
       return ERROR;
       }
-
     if (fstat(fd, &statbuf) != 0)
       {
       *log_msgptr = string_sprintf("failed to fstat ACL file \"%s\": %s", ss,
     if (fstat(fd, &statbuf) != 0)
       {
       *log_msgptr = string_sprintf("failed to fstat ACL file \"%s\": %s", ss,
@@ -4014,7 +4098,8 @@ if (Ustrchr(ss, ' ') == NULL)
       return ERROR;
       }
 
       return ERROR;
       }
 
-    acl_text = store_get(statbuf.st_size + 1);
+    /* If the string being used as a filename is tainted, so is the file content */
+    acl_text = store_get(statbuf.st_size + 1, is_tainted(ss));
     acl_text_end = acl_text + statbuf.st_size + 1;
 
     if (read(fd, acl_text, statbuf.st_size) != statbuf.st_size)
     acl_text_end = acl_text + statbuf.st_size + 1;
 
     if (read(fd, acl_text, statbuf.st_size) != statbuf.st_size)
@@ -4044,7 +4129,7 @@ if (!acl)
   if (!acl && *log_msgptr) return ERROR;
   if (fd >= 0)
     {
   if (!acl && *log_msgptr) return ERROR;
   if (fd >= 0)
     {
-    tree_node *t = store_get_perm(sizeof(tree_node) + Ustrlen(ss));
+    tree_node *t = store_get_perm(sizeof(tree_node) + Ustrlen(ss), is_tainted(ss));
     Ustrcpy(t->name, ss);
     t->data.ptr = acl;
     (void)tree_insertnode(&acl_anchor, t);
     Ustrcpy(t->name, ss);
     t->data.ptr = acl;
     (void)tree_insertnode(&acl_anchor, t);
@@ -4525,7 +4610,7 @@ acl_var_create(uschar * name)
 tree_node * node, ** root = name[0] == 'c' ? &acl_var_c : &acl_var_m;
 if (!(node = tree_search(*root, name)))
   {
 tree_node * node, ** root = name[0] == 'c' ? &acl_var_c : &acl_var_m;
 if (!(node = tree_search(*root, name)))
   {
-  node = store_get(sizeof(tree_node) + Ustrlen(name));
+  node = store_get(sizeof(tree_node) + Ustrlen(name), is_tainted(name));
   Ustrcpy(node->name, name);
   (void)tree_insertnode(root, node);
   }
   Ustrcpy(node->name, name);
   (void)tree_insertnode(root, node);
   }
@@ -4559,6 +4644,7 @@ void
 acl_var_write(uschar *name, uschar *value, void *ctx)
 {
 FILE *f = (FILE *)ctx;
 acl_var_write(uschar *name, uschar *value, void *ctx)
 {
 FILE *f = (FILE *)ctx;
+if (is_tainted(value)) putc('-', f);
 fprintf(f, "-acl%c %s %d\n%s\n", name[0], name+1, Ustrlen(value), value);
 }
 
 fprintf(f, "-acl%c %s %d\n%s\n", name[0], name+1, Ustrlen(value), value);
 }