Track tainted data and refuse to expand it
[exim.git] / src / src / acl.c
index d243ff4af6cd3d6e967fbfcbcde65dac975d1c9a..dac2ba570cdeba68efac738be2acb632837ae936 100644 (file)
@@ -510,6 +510,8 @@ static control_def controls_list[] = {
            // ACL_BIT_PRDR|    /* Not allow one user to freeze for all */
            ACL_BIT_NOTSMTP | ACL_BIT_MIME)
   },
+
+
 [CONTROL_SUBMISSION] =
   { US"submission",              TRUE,
          (unsigned)
@@ -628,8 +630,7 @@ Returns:    index of a control entry, or -1 if not found
 static int
 find_control(const uschar * name, control_def * ol, int last)
 {
-int first = 0;
-while (last > first)
+for (int first = 0; last > first; )
   {
   int middle = (first + last)/2;
   uschar * s =  ol[middle].name;
@@ -660,8 +661,7 @@ Returns:      offset in list, or -1 if not found
 static int
 acl_checkcondition(uschar * name, condition_def * list, int end)
 {
-int start = 0;
-while (start < end)
+for (int start = 0; start < end; )
   {
   int mid = (start + end)/2;
   int c = Ustrcmp(name, list[mid].name);
@@ -690,9 +690,7 @@ Returns:      offset in list, or -1 if not found
 static int
 acl_checkname(uschar *name, uschar **list, int end)
 {
-int start = 0;
-
-while (start < end)
+for (int start = 0; start < end; )
   {
   int mid = (start + end)/2;
   int c = Ustrcmp(name, list[mid]);
@@ -730,7 +728,7 @@ acl_block **lastp = &yield;
 acl_block *this = NULL;
 acl_condition_block *cond;
 acl_condition_block **condp = NULL;
-uschar *s;
+uschar * s;
 
 *error = NULL;
 
@@ -762,7 +760,7 @@ while ((s = (*func)()) != NULL)
 
   if ((v = acl_checkname(name, verbs, nelem(verbs))) < 0)
     {
-    if (this == NULL)
+    if (!this)
       {
       *error = string_sprintf("unknown ACL verb \"%s\" in \"%s\"", name,
         saveline);
@@ -779,12 +777,14 @@ while ((s = (*func)()) != 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;
-    this->verb = v;
     this->condition = NULL;
+    this->verb = v;
+    this->srcline = config_lineno;     /* for debug output */
+    this->srcfile = config_filename;   /**/
     condp = &(this->condition);
     if (*s == 0) continue;               /* No condition on this line */
     if (*s == '!')
@@ -824,7 +824,7 @@ while ((s = (*func)()) != 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;
@@ -1022,7 +1022,9 @@ for (p = q; *p; p = q)
 
   if (!*hptr)
     {
-    header_line *h = store_get(sizeof(header_line));
+    /* The header_line struct itself is not tainted, though it points to
+    tainted data. */
+    header_line *h = store_get(sizeof(header_line), FALSE);
     h->text = hdr;
     h->next = NULL;
     h->type = newtype;
@@ -1042,9 +1044,8 @@ uschar *
 fn_hdrs_added(void)
 {
 gstring * g = NULL;
-header_line * h;
 
-for (h = acl_added_headers; h; h = h->next)
+for (header_line * h = acl_added_headers; h; h = h->next)
   {
   int i = h->slen;
   if (h->text[i-1] == '\n') i--;
@@ -1070,7 +1071,7 @@ Returns:    nothing
 static void
 setup_remove_header(const uschar *hnames)
 {
-if (*hnames != 0)
+if (*hnames)
   acl_removed_headers = acl_removed_headers
     ? string_sprintf("%s : %s", acl_removed_headers, hnames)
     : string_copy(hnames);
@@ -1119,10 +1120,10 @@ if (log_message != NULL && log_message != user_message)
   /* Search previously logged warnings. They are kept in malloc
   store so they can be freed at the start of a new message. */
 
-  for (logged = acl_warn_logged; logged != NULL; logged = logged->next)
+  for (logged = acl_warn_logged; logged; logged = logged->next)
     if (Ustrcmp(logged->text, text) == 0) break;
 
-  if (logged == NULL)
+  if (!logged)
     {
     int length = Ustrlen(text) + 1;
     log_write(0, LOG_MAIN, "%s", text);
@@ -1136,7 +1137,7 @@ if (log_message != NULL && log_message != user_message)
 
 /* If there's no user message, we are done. */
 
-if (user_message == NULL) return;
+if (!user_message) return;
 
 /* If this isn't a message ACL, we can't do anything with a user message.
 Log an error. */
@@ -1201,11 +1202,10 @@ HDEBUG(D_acl)
 
 if ((rc = host_name_lookup()) != OK)
   {
-  *log_msgptr = (rc == DEFER)?
-    US"host lookup deferred for reverse lookup check"
-    :
-    string_sprintf("host lookup failed for reverse lookup check%s",
-      host_lookup_msg);
+  *log_msgptr = rc == DEFER
+    ? US"host lookup deferred for reverse lookup check"
+    : string_sprintf("host lookup failed for reverse lookup check%s",
+       host_lookup_msg);
   return rc;    /* DEFER or FAIL */
   }
 
@@ -1243,13 +1243,10 @@ static int
 acl_verify_csa_address(dns_answer *dnsa, dns_scan *dnss, int reset,
                        uschar *target)
 {
-dns_record *rr;
-dns_address *da;
-
-BOOL target_found = FALSE;
+int rc = CSA_FAIL_NOADDR;
 
-for (rr = dns_next_rr(dnsa, dnss, reset);
-     rr != NULL;
+for (dns_record * rr = dns_next_rr(dnsa, dnss, reset);
+     rr;
      rr = dns_next_rr(dnsa, dnss, RESET_NEXT))
   {
   /* Check this is an address RR for the target hostname. */
@@ -1262,12 +1259,12 @@ for (rr = dns_next_rr(dnsa, dnss, reset);
 
   if (strcmpic(target, rr->name) != 0) continue;
 
-  target_found = TRUE;
+  rc = CSA_FAIL_MISMATCH;
 
   /* Turn the target address RR into a list of textual IP addresses and scan
   the list. There may be more than one if it is an A6 RR. */
 
-  for (da = dns_address_from_rr(dnsa, rr); da != NULL; da = da->next)
+  for (dns_address * da = dns_address_from_rr(dnsa, rr); da; da = da->next)
     {
     /* If the client IP address matches the target IP address, it's good! */
 
@@ -1281,8 +1278,7 @@ for (rr = dns_next_rr(dnsa, dnss, reset);
 using an unauthorized IP address, otherwise the target has no authorized IP
 addresses. */
 
-if (target_found) return CSA_FAIL_MISMATCH;
-else return CSA_FAIL_NOADDR;
+return rc;
 }
 
 
@@ -1361,7 +1357,7 @@ we return from this function. */
 t = tree_search(csa_cache, domain);
 if (t != NULL) return t->data.val;
 
-t = store_get_perm(sizeof(tree_node) + Ustrlen(domain));
+t = store_get_perm(sizeof(tree_node) + Ustrlen(domain), is_tainted(domain));
 Ustrcpy(t->name, domain);
 (void)tree_insertnode(&csa_cache, t);
 
@@ -1441,7 +1437,7 @@ for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
 
 /* If we didn't break the loop then no appropriate records were found. */
 
-if (rr == NULL) return t->data.val = CSA_UNKNOWN;
+if (!rr) return t->data.val = CSA_UNKNOWN;
 
 /* Do not check addresses if the target is ".", in accordance with RFC 2782.
 A target of "." indicates there are no valid addresses, so the client cannot
@@ -1518,7 +1514,7 @@ static verify_type_t verify_type_list[] = {
     { 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"not_blind",           VERIFY_NOT_BLIND,       ACL_BIT_DATA | ACL_BIT_NOTSMTP, FALSE, 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,
@@ -1614,7 +1610,7 @@ if (!ss) goto BAD_VERIFY;
 
 /* Handle name/address consistency verification in a separate function. */
 
-for (vp= verify_type_list;
+for (vp = verify_type_list;
      CS vp < CS verify_type_list + sizeof(verify_type_list);
      vp++
     )
@@ -1659,8 +1655,8 @@ switch(vp->value)
     /* We can test the result of optional HELO verification that might have
     occurred earlier. If not, we can attempt the verification now. */
 
-    if (!helo_verified && !helo_verify_failed) smtp_verify_helo();
-    return helo_verified ? OK : FAIL;
+    if (!f.helo_verified && !f.helo_verify_failed) smtp_verify_helo();
+    return f.helo_verified ? OK : FAIL;
 
   case VERIFY_CSA:
     /* Do Client SMTP Authorization checks in a separate function, and turn the
@@ -1717,14 +1713,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:. */
+    {
+    BOOL case_sensitive = TRUE;
 
-    if ((rc = verify_check_notblind()) != OK)
+    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(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;
+    }
 
   /* The remaining verification tests check recipient and sender addresses,
   either from the envelope or from the header. There are a number of
@@ -1760,8 +1769,7 @@ switch(vp->value)
 /* Remaining items are optional; they apply to sender and recipient
 verification, including "header sender" verification. */
 
-while ((ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size))
-      != NULL)
+while ((ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size)))
   {
   if (strcmpic(ss, US"defer_ok") == 0) defer_ok = TRUE;
   else if (strcmpic(ss, US"no_details") == 0) no_details = TRUE;
@@ -1794,10 +1802,10 @@ while ((ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size))
         {
        const uschar * sublist = ss;
         int optsep = ',';
-        uschar *opt;
         uschar buffer[256];
-        while (isspace(*sublist)) sublist++;
+       uschar * opt;
 
+        while (isspace(*sublist)) sublist++;
         while ((opt = string_nextinlist(&sublist, &optsep, buffer, sizeof(buffer))))
           {
          callout_opt_t * op;
@@ -1892,7 +1900,7 @@ if (verify_header_sender)
       {
       if (!*user_msgptr && *log_msgptr)
         *user_msgptr = string_sprintf("Rejected after DATA: %s", *log_msgptr);
-      if (rc == DEFER) acl_temp_details = TRUE;
+      if (rc == DEFER) f.acl_temp_details = TRUE;
       }
     }
   }
@@ -2045,7 +2053,7 @@ else
     addr2.user_message : addr2.message;
 
   /* Allow details for temporary error if the address is so flagged. */
-  if (testflag((&addr2), af_pass_message)) acl_temp_details = TRUE;
+  if (testflag((&addr2), af_pass_message)) f.acl_temp_details = TRUE;
 
   /* Make $address_data visible */
   deliver_address_data = addr2.prop.address_data;
@@ -2150,8 +2158,6 @@ Arguments:
   log_msgptr  for error messages
   format      format string
   ...         supplementary arguments
-  ss          ratelimit option name
-  where       ACL_WHERE_xxxx indicating which ACL this is
 
 Returns:      ERROR
 */
@@ -2160,14 +2166,15 @@ static int
 ratelimit_error(uschar **log_msgptr, const char *format, ...)
 {
 va_list ap;
-uschar buffer[STRING_SPRINTF_BUFFER_SIZE];
+gstring * g =
+  string_cat(NULL, US"error in arguments to \"ratelimit\" condition: ");
+
 va_start(ap, format);
-if (!string_vformat(buffer, sizeof(buffer), format, ap))
-  log_write(0, LOG_MAIN|LOG_PANIC_DIE,
-    "string_sprintf expansion was longer than " SIZE_T_FMT, sizeof(buffer));
+g = string_vformat(g, SVFMT_EXTEND|SVFMT_REBUFFER, format, ap);
 va_end(ap);
-*log_msgptr = string_sprintf(
-  "error in arguments to \"ratelimit\" condition: %s", buffer);
+
+gstring_release_unused(g);
+*log_msgptr = string_from_gstring(g);
 return ERROR;
 }
 
@@ -2401,7 +2408,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. */
 
-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;
@@ -2450,7 +2457,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);
-    dbdb = store_get(dbdb_size);
+    dbdb = store_get(dbdb_size, FALSE);                /* not tainted */
     }
   else
     {
@@ -2464,7 +2471,7 @@ if (!dbdb)
     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);
@@ -2681,9 +2688,10 @@ else
 
 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);
@@ -2756,7 +2764,7 @@ if (*portend != '\0')
   }
 
 /* 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;
@@ -2887,10 +2895,10 @@ for (; cb; cb = cb->next)
     arg = cb->arg;
   else if (!(arg = expand_string(cb->arg)))
     {
-    if (expand_string_forcedfail) continue;
+    if (f.expand_string_forcedfail) continue;
     *log_msgptr = string_sprintf("failed to expand ACL string \"%s\": %s",
       cb->arg, expand_string_message);
-    return search_find_defer ? DEFER : ERROR;
+    return f.search_find_defer ? DEFER : ERROR;
     }
 
   /* Show condition, and expanded condition if it's different */
@@ -3012,7 +3020,7 @@ for (; cb; cb = cb->next)
       switch(control_type)
        {
        case CONTROL_AUTH_UNADVERTISED:
-       allow_auth_unadvertised = TRUE;
+       f.allow_auth_unadvertised = TRUE;
        break;
 
        #ifdef EXPERIMENTAL_BRIGHTMAIL
@@ -3023,22 +3031,22 @@ for (; cb; cb = cb->next)
 
        #ifndef DISABLE_DKIM
        case CONTROL_DKIM_VERIFY:
-       dkim_disable_verify = TRUE;
+       f.dkim_disable_verify = TRUE;
        #ifdef EXPERIMENTAL_DMARC
        /* Since DKIM was blocked, skip DMARC too */
-       dmarc_disable_verify = TRUE;
-       dmarc_enable_forensic = FALSE;
+       f.dmarc_disable_verify = TRUE;
+       f.dmarc_enable_forensic = FALSE;
        #endif
        break;
        #endif
 
        #ifdef EXPERIMENTAL_DMARC
        case CONTROL_DMARC_VERIFY:
-       dmarc_disable_verify = TRUE;
+       f.dmarc_disable_verify = TRUE;
        break;
 
        case CONTROL_DMARC_FORENSIC:
-       dmarc_enable_forensic = TRUE;
+       f.dmarc_enable_forensic = TRUE;
        break;
        #endif
 
@@ -3103,24 +3111,24 @@ for (; cb; cb = cb->next)
 
        #ifdef WITH_CONTENT_SCAN
        case CONTROL_NO_MBOX_UNSPOOL:
-       no_mbox_unspool = TRUE;
+       f.no_mbox_unspool = TRUE;
        break;
        #endif
 
        case CONTROL_NO_MULTILINE:
-       no_multiline_responses = TRUE;
+       f.no_multiline_responses = TRUE;
        break;
 
        case CONTROL_NO_PIPELINING:
-       pipelining_enable = FALSE;
+       f.pipelining_enable = FALSE;
        break;
 
        case CONTROL_NO_DELAY_FLUSH:
-       disable_delay_flush = TRUE;
+       f.disable_delay_flush = TRUE;
        break;
 
        case CONTROL_NO_CALLOUT_FLUSH:
-       disable_callout_flush = TRUE;
+       f.disable_callout_flush = TRUE;
        break;
 
        case CONTROL_FAKEREJECT:
@@ -3130,7 +3138,7 @@ for (; cb; cb = cb->next)
        if (*p == '/')
          {
          const uschar *pp = p + 1;
-         while (*pp != 0) pp++;
+         while (*pp) pp++;
          fake_response_text = expand_string(string_copyn(p+1, pp-p-1));
          p = pp;
          }
@@ -3142,7 +3150,7 @@ for (; cb; cb = cb->next)
        break;
 
        case CONTROL_FREEZE:
-       deliver_freeze = TRUE;
+       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)
@@ -3159,25 +3167,25 @@ for (; cb; cb = cb->next)
        break;
 
        case CONTROL_QUEUE_ONLY:
-       queue_only_policy = TRUE;
+       f.queue_only_policy = TRUE;
        cancel_cutthrough_connection(TRUE, US"queueing forced");
        break;
 
        case CONTROL_SUBMISSION:
        originator_name = US"";
-       submission_mode = TRUE;
+       f.submission_mode = TRUE;
        while (*p == '/')
          {
          if (Ustrncmp(p, "/sender_retain", 14) == 0)
            {
            p += 14;
-           active_local_sender_retain = TRUE;
-           active_local_from_check = FALSE;
+           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 != 0 && *pp != '/') pp++;
+           while (*pp && *pp != '/') pp++;
            submission_domain = string_copyn(p+8, pp-p-8);
            p = pp;
            }
@@ -3186,7 +3194,7 @@ for (; cb; cb = cb->next)
          else if (Ustrncmp(p, "/name=", 6) == 0)
            {
            const uschar *pp = p + 6;
-           while (*pp != 0) pp++;
+           while (*pp) pp++;
            submission_name = string_copy(parse_fix_phrase(p+6, pp-p-6,
              big_buffer, big_buffer_size));
            p = pp;
@@ -3237,7 +3245,7 @@ for (; cb; cb = cb->next)
        break;
 
        case CONTROL_SUPPRESS_LOCAL_FIXUPS:
-       suppress_local_fixups = TRUE;
+       f.suppress_local_fixups = TRUE;
        break;
 
        case CONTROL_CUTTHROUGH_DELIVERY:
@@ -3254,9 +3262,9 @@ for (; cb; cb = cb->next)
          ignored = US"PRDR active";
        else
          {
-         if (deliver_freeze)
+         if (f.deliver_freeze)
            ignored = US"frozen";
-         else if (queue_only_policy)
+         else if (f.queue_only_policy)
            ignored = US"queue-only";
          else if (fake_response == FAIL)
            ignored = US"fakereject";
@@ -3384,7 +3392,7 @@ for (; cb; cb = cb->next)
 
         else
           {
-          if (smtp_out != NULL && !disable_delay_flush)
+          if (smtp_out && !f.disable_delay_flush)
            mac_smtp_fflush();
 
 #if !defined(NO_POLL_H) && defined (POLLRDHUP)
@@ -3401,16 +3409,16 @@ for (; cb; cb = cb->next)
              HDEBUG(D_acl) debug_printf_indent("delay cancelled by peer close\n");
            }
 #else
-        /* It appears to be impossible to detect that a TCP/IP connection has
-        gone away without reading from it. This means that we cannot shorten
-        the delay below if the client goes away, because we cannot discover
-        that the client has closed its end of the connection. (The connection
-        is actually in a half-closed state, waiting for the server to close its
-        end.) It would be nice to be able to detect this state, so that the
-        Exim process is not held up unnecessarily. However, it seems that we
-        can't. The poll() function does not do the right thing, and in any case
-        it is not always available.
-        */
+         /* Lacking POLLRDHUP it appears to be impossible to detect that a
+         TCP/IP connection has gone away without reading from it. This means
+         that we cannot shorten the delay below if the client goes away,
+         because we cannot discover that the client has closed its end of the
+         connection. (The connection is actually in a half-closed state,
+         waiting for the server to close its end.) It would be nice to be able
+         to detect this state, so that the Exim process is not held up
+         unnecessarily. However, it seems that we can't. The poll() function
+         does not do the right thing, and in any case it is not always
+         available.  */
 
           while (delay > 0) delay = sleep(delay);
 #endif
@@ -3436,9 +3444,9 @@ for (; cb; cb = cb->next)
 
     #ifdef EXPERIMENTAL_DMARC
     case ACLC_DMARC_STATUS:
-    if (!dmarc_has_been_checked)
+    if (!f.dmarc_has_been_checked)
       dmarc_process();
-    dmarc_has_been_checked = TRUE;
+    f.dmarc_has_been_checked = TRUE;
     /* used long way of dmarc_exim_expand_query() in case we need more
      * view into the process in the future. */
     rc = match_isinlist(dmarc_exim_expand_query(DMARC_VERIFY_STATUS),
@@ -3488,7 +3496,7 @@ for (; cb; cb = cb->next)
       (sender_host_address == NULL)? US"" : sender_host_address,
       CUSS &host_data);
     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:
@@ -3502,7 +3510,7 @@ for (; cb; cb = cb->next)
       int logbits = 0;
       int sep = 0;
       const uschar *s = arg;
-      uschar *ss;
+      uschar * ss;
       while ((ss = string_nextinlist(&s, &sep, big_buffer, big_buffer_size)))
         {
         if (Ustrcmp(ss, "main") == 0) logbits |= LOG_MAIN;
@@ -3546,7 +3554,6 @@ for (; cb; cb = cb->next)
         }
       while (isspace(*s)) s++;
 
-
       if (logbits == 0) logbits = LOG_MAIN;
       log_write(0, logbits, "%s", string_printing(s));
       }
@@ -3557,8 +3564,8 @@ for (; cb; cb = cb->next)
       {
       /* Separate the regular expression and any optional parameters. */
       const uschar * list = arg;
-      uschar *ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size);
-      uschar *opt;
+      uschar * ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size);
+      uschar * opt;
       BOOL defer_ok = FALSE;
       int timeout = 0;
 
@@ -3591,7 +3598,7 @@ for (; cb; cb = cb->next)
              "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:
@@ -3752,7 +3759,7 @@ if ((BIT(rc) & msgcond[verb]) != 0)
     expmessage = expand_string(user_message);
     if (!expmessage)
       {
-      if (!expand_string_forcedfail)
+      if (!f.expand_string_forcedfail)
         log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand ACL message \"%s\": %s",
           user_message, expand_string_message);
       }
@@ -3765,7 +3772,7 @@ if ((BIT(rc) & msgcond[verb]) != 0)
     expmessage = expand_string(log_message);
     if (!expmessage)
       {
-      if (!expand_string_forcedfail)
+      if (!f.expand_string_forcedfail)
         log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand ACL message \"%s\": %s",
           log_message, expand_string_message);
       }
@@ -3833,7 +3840,7 @@ for(;;)
   if (*acl_text == 0) return NULL;         /* No more data */
   yield = acl_text;                        /* Potential data line */
 
-  while (*acl_text != 0 && *acl_text != '\n') acl_text++;
+  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. */
@@ -3953,7 +3960,7 @@ if (acl_level == 0)
   {
   if (!(ss = expand_string(s)))
     {
-    if (expand_string_forcedfail) return OK;
+    if (f.expand_string_forcedfail) return OK;
     *log_msgptr = string_sprintf("failed to expand ACL string \"%s\": %s", s,
       expand_string_message);
     return ERROR;
@@ -3976,11 +3983,10 @@ read an ACL from a file, and save it so it can be re-used. */
 
 if (Ustrchr(ss, ' ') == NULL)
   {
-  tree_node *t = tree_search(acl_anchor, ss);
-  if (t != NULL)
+  tree_node * t = tree_search(acl_anchor, ss);
+  if (t)
     {
-    acl = (acl_block *)(t->data.ptr);
-    if (acl == NULL)
+    if (!(acl = (acl_block *)(t->data.ptr)))
       {
       HDEBUG(D_acl) debug_printf_indent("ACL \"%s\" is empty: implicit DENY\n", ss);
       return FAIL;
@@ -3992,14 +3998,20 @@ if (Ustrchr(ss, ' ') == NULL)
   else if (*ss == '/')
     {
     struct stat statbuf;
-    fd = Uopen(ss, O_RDONLY, 0);
-    if (fd < 0)
+    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 (fstat(fd, &statbuf) != 0)
       {
       *log_msgptr = string_sprintf("failed to fstat ACL file \"%s\": %s", ss,
@@ -4007,7 +4019,8 @@ if (Ustrchr(ss, ' ') == NULL)
       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)
@@ -4028,16 +4041,16 @@ if (Ustrchr(ss, ' ') == NULL)
 in the ACL tree, having read it into the POOL_PERM store pool so that it
 persists between multiple messages. */
 
-if (acl == NULL)
+if (!acl)
   {
   int old_pool = store_pool;
   if (fd >= 0) store_pool = POOL_PERM;
   acl = acl_read(acl_getline, log_msgptr);
   store_pool = old_pool;
-  if (acl == NULL && *log_msgptr != NULL) return ERROR;
+  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);
@@ -4046,7 +4059,7 @@ if (acl == NULL)
 
 /* Now we have an ACL to use. It's possible it may be NULL. */
 
-while (acl != NULL)
+while (acl)
   {
   int cond;
   int basic_errno = 0;
@@ -4055,9 +4068,10 @@ while (acl != NULL)
     && (where == ACL_WHERE_QUIT || where == ACL_WHERE_NOTQUIT);
 
   *log_msgptr = *user_msgptr = NULL;
-  acl_temp_details = FALSE;
+  f.acl_temp_details = FALSE;
 
-  HDEBUG(D_acl) debug_printf_indent("processing \"%s\"\n", verbs[acl->verb]);
+  HDEBUG(D_acl) debug_printf_indent("processing \"%s\" (%s %d)\n",
+    verbs[acl->verb], acl->srcfile, acl->srcline);
 
   /* Clear out any search error message from a previous check before testing
   this condition. */
@@ -4072,46 +4086,47 @@ while (acl != NULL)
   switch (cond)
     {
     case DEFER:
-    HDEBUG(D_acl) debug_printf_indent("%s: condition test deferred in %s\n", verbs[acl->verb], acl_name);
-    if (basic_errno != ERRNO_CALLOUTDEFER)
-      {
-      if (search_error_message != NULL && *search_error_message != 0)
-        *log_msgptr = search_error_message;
-      if (smtp_return_error_details) acl_temp_details = TRUE;
-      }
-    else
-      {
-      acl_temp_details = TRUE;
-      }
-    if (acl->verb != ACL_WARN) return DEFER;
-    break;
+      HDEBUG(D_acl) debug_printf_indent("%s: condition test deferred in %s\n",
+       verbs[acl->verb], acl_name);
+      if (basic_errno != ERRNO_CALLOUTDEFER)
+       {
+       if (search_error_message != NULL && *search_error_message != 0)
+         *log_msgptr = search_error_message;
+       if (smtp_return_error_details) f.acl_temp_details = TRUE;
+       }
+      else
+       f.acl_temp_details = TRUE;
+      if (acl->verb != ACL_WARN) return DEFER;
+      break;
 
     default:      /* Paranoia */
     case ERROR:
-    HDEBUG(D_acl) debug_printf_indent("%s: condition test error in %s\n", verbs[acl->verb], acl_name);
-    return ERROR;
+      HDEBUG(D_acl) debug_printf_indent("%s: condition test error in %s\n",
+       verbs[acl->verb], acl_name);
+      return ERROR;
 
     case OK:
-    HDEBUG(D_acl) debug_printf_indent("%s: condition test succeeded in %s\n",
-      verbs[acl->verb], acl_name);
-    break;
+      HDEBUG(D_acl) debug_printf_indent("%s: condition test succeeded in %s\n",
+       verbs[acl->verb], acl_name);
+      break;
 
     case FAIL:
-    HDEBUG(D_acl) debug_printf_indent("%s: condition test failed in %s\n", verbs[acl->verb], acl_name);
-    break;
+      HDEBUG(D_acl) debug_printf_indent("%s: condition test failed in %s\n",
+       verbs[acl->verb], acl_name);
+      break;
 
     /* DISCARD and DROP can happen only from a nested ACL condition, and
     DISCARD can happen only for an "accept" or "discard" verb. */
 
     case DISCARD:
-    HDEBUG(D_acl) debug_printf_indent("%s: condition test yielded \"discard\" in %s\n",
-      verbs[acl->verb], acl_name);
-    break;
+      HDEBUG(D_acl) debug_printf_indent("%s: condition test yielded \"discard\" in %s\n",
+       verbs[acl->verb], acl_name);
+      break;
 
     case FAIL_DROP:
-    HDEBUG(D_acl) debug_printf_indent("%s: condition test yielded \"drop\" in %s\n",
-      verbs[acl->verb], acl_name);
-    break;
+      HDEBUG(D_acl) debug_printf_indent("%s: condition test yielded \"drop\" in %s\n",
+       verbs[acl->verb], acl_name);
+      break;
     }
 
   /* At this point, cond for most verbs is either OK or FAIL or (as a result of
@@ -4121,84 +4136,85 @@ while (acl != NULL)
   switch(acl->verb)
     {
     case ACL_ACCEPT:
-    if (cond == OK || cond == DISCARD)
-      {
-      HDEBUG(D_acl) debug_printf_indent("end of %s: ACCEPT\n", acl_name);
-      return cond;
-      }
-    if (endpass_seen)
-      {
-      HDEBUG(D_acl) debug_printf_indent("accept: endpass encountered - denying access\n");
-      return cond;
-      }
-    break;
+      if (cond == OK || cond == DISCARD)
+       {
+       HDEBUG(D_acl) debug_printf_indent("end of %s: ACCEPT\n", acl_name);
+       return cond;
+       }
+      if (endpass_seen)
+       {
+       HDEBUG(D_acl) debug_printf_indent("accept: endpass encountered - denying access\n");
+       return cond;
+       }
+      break;
 
     case ACL_DEFER:
-    if (cond == OK)
-      {
-      HDEBUG(D_acl) debug_printf_indent("end of %s: DEFER\n", acl_name);
-      if (acl_quit_check) goto badquit;
-      acl_temp_details = TRUE;
-      return DEFER;
-      }
-    break;
+      if (cond == OK)
+       {
+       HDEBUG(D_acl) debug_printf_indent("end of %s: DEFER\n", acl_name);
+       if (acl_quit_check) goto badquit;
+       f.acl_temp_details = TRUE;
+       return DEFER;
+       }
+      break;
 
     case ACL_DENY:
-    if (cond == OK)
-      {
-      HDEBUG(D_acl) debug_printf_indent("end of %s: DENY\n", acl_name);
-      if (acl_quit_check) goto badquit;
-      return FAIL;
-      }
-    break;
+      if (cond == OK)
+       {
+       HDEBUG(D_acl) debug_printf_indent("end of %s: DENY\n", acl_name);
+       if (acl_quit_check) goto badquit;
+       return FAIL;
+       }
+      break;
 
     case ACL_DISCARD:
-    if (cond == OK || cond == DISCARD)
-      {
-      HDEBUG(D_acl) debug_printf_indent("end of %s: DISCARD\n", acl_name);
-      if (acl_quit_check) goto badquit;
-      return DISCARD;
-      }
-    if (endpass_seen)
-      {
-      HDEBUG(D_acl) debug_printf_indent("discard: endpass encountered - denying access\n");
-      return cond;
-      }
-    break;
+      if (cond == OK || cond == DISCARD)
+       {
+       HDEBUG(D_acl) debug_printf_indent("end of %s: DISCARD\n", acl_name);
+       if (acl_quit_check) goto badquit;
+       return DISCARD;
+       }
+      if (endpass_seen)
+       {
+       HDEBUG(D_acl)
+         debug_printf_indent("discard: endpass encountered - denying access\n");
+       return cond;
+       }
+      break;
 
     case ACL_DROP:
-    if (cond == OK)
-      {
-      HDEBUG(D_acl) debug_printf_indent("end of %s: DROP\n", acl_name);
-      if (acl_quit_check) goto badquit;
-      return FAIL_DROP;
-      }
-    break;
+      if (cond == OK)
+       {
+       HDEBUG(D_acl) debug_printf_indent("end of %s: DROP\n", acl_name);
+       if (acl_quit_check) goto badquit;
+       return FAIL_DROP;
+       }
+      break;
 
     case ACL_REQUIRE:
-    if (cond != OK)
-      {
-      HDEBUG(D_acl) debug_printf_indent("end of %s: not OK\n", acl_name);
-      if (acl_quit_check) goto badquit;
-      return cond;
-      }
-    break;
+      if (cond != OK)
+       {
+       HDEBUG(D_acl) debug_printf_indent("end of %s: not OK\n", acl_name);
+       if (acl_quit_check) goto badquit;
+       return cond;
+       }
+      break;
 
     case ACL_WARN:
-    if (cond == OK)
-      acl_warn(where, *user_msgptr, *log_msgptr);
-    else if (cond == DEFER && LOGGING(acl_warn_skipped))
-      log_write(0, LOG_MAIN, "%s Warning: ACL \"warn\" statement skipped: "
-        "condition test deferred%s%s", host_and_ident(TRUE),
-        (*log_msgptr == NULL)? US"" : US": ",
-        (*log_msgptr == NULL)? US"" : *log_msgptr);
-    *log_msgptr = *user_msgptr = NULL;  /* In case implicit DENY follows */
-    break;
+      if (cond == OK)
+       acl_warn(where, *user_msgptr, *log_msgptr);
+      else if (cond == DEFER && LOGGING(acl_warn_skipped))
+       log_write(0, LOG_MAIN, "%s Warning: ACL \"warn\" statement skipped: "
+         "condition test deferred%s%s", host_and_ident(TRUE),
+         (*log_msgptr == NULL)? US"" : US": ",
+         (*log_msgptr == NULL)? US"" : *log_msgptr);
+      *log_msgptr = *user_msgptr = NULL;  /* In case implicit DENY follows */
+      break;
 
     default:
-    log_write(0, LOG_MAIN|LOG_PANIC_DIE, "internal ACL error: unknown verb %d",
-      acl->verb);
-    break;
+      log_write(0, LOG_MAIN|LOG_PANIC_DIE, "internal ACL error: unknown verb %d",
+       acl->verb);
+      break;
     }
 
   /* Pass to the next ACL item */
@@ -4271,10 +4287,10 @@ for (i = 0; i < 9; i++) acl_arg[i] = sav_arg[i];
 return ret;
 
 bad:
-if (expand_string_forcedfail) return ERROR;
+if (f.expand_string_forcedfail) return ERROR;
 *log_msgptr = string_sprintf("failed to expand ACL string \"%s\": %s",
   tmp, expand_string_message);
-return search_find_defer?DEFER:ERROR;
+return f.search_find_defer ? DEFER : ERROR;
 }
 
 
@@ -4405,7 +4421,7 @@ switch (where)
   case ACL_WHERE_PRDR:
 #endif
 
-    if (host_checking_callout) /* -bhc mode */
+    if (f.host_checking_callout)       /* -bhc mode */
       cancel_cutthrough_connection(TRUE, US"host-checking mode");
 
     else if (  rc == OK
@@ -4421,7 +4437,7 @@ switch (where)
          while (*s) s++;
          do --s; while (!isdigit(*s));
          if (*--s && isdigit(*s) && *--s && isdigit(*s)) *user_msgptr = s;
-         acl_temp_details = TRUE;
+         f.acl_temp_details = TRUE;
          }
        else
          {
@@ -4515,7 +4531,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)))
   {
-  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);
   }
@@ -4549,6 +4565,7 @@ void
 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);
 }