Track tainted data and refuse to expand it
[exim.git] / src / src / expand.c
index 43f572ecaecc89d7ee9d866120417673a14da03d..1bfc75d2b6cf1e31cdd36864fe39679e09af92b1 100644 (file)
@@ -235,6 +235,7 @@ static uschar *op_table_main[] = {
   US"rxquote",
   US"s",
   US"sha1",
   US"rxquote",
   US"s",
   US"sha1",
+  US"sha2",
   US"sha256",
   US"sha3",
   US"stat",
   US"sha256",
   US"sha3",
   US"stat",
@@ -281,6 +282,7 @@ enum {
   EOP_RXQUOTE,
   EOP_S,
   EOP_SHA1,
   EOP_RXQUOTE,
   EOP_S,
   EOP_SHA1,
+  EOP_SHA2,
   EOP_SHA256,
   EOP_SHA3,
   EOP_STAT,
   EOP_SHA256,
   EOP_SHA3,
   EOP_STAT,
@@ -312,7 +314,11 @@ static uschar *cond_table[] = {
   US"exists",
   US"first_delivery",
   US"forall",
   US"exists",
   US"first_delivery",
   US"forall",
+  US"forall_json",
+  US"forall_jsons",
   US"forany",
   US"forany",
+  US"forany_json",
+  US"forany_jsons",
   US"ge",
   US"gei",
   US"gt",
   US"ge",
   US"gei",
   US"gt",
@@ -358,7 +364,11 @@ enum {
   ECOND_EXISTS,
   ECOND_FIRST_DELIVERY,
   ECOND_FORALL,
   ECOND_EXISTS,
   ECOND_FIRST_DELIVERY,
   ECOND_FORALL,
+  ECOND_FORALL_JSON,
+  ECOND_FORALL_JSONS,
   ECOND_FORANY,
   ECOND_FORANY,
+  ECOND_FORANY_JSON,
+  ECOND_FORANY_JSONS,
   ECOND_STR_GE,
   ECOND_STR_GEI,
   ECOND_STR_GT,
   ECOND_STR_GE,
   ECOND_STR_GEI,
   ECOND_STR_GT,
@@ -660,9 +670,6 @@ static var_entry var_table[] = {
   { "regex_match_string",  vtype_stringptr,   &regex_match_string },
 #endif
   { "reply_address",       vtype_reply,       NULL },
   { "regex_match_string",  vtype_stringptr,   &regex_match_string },
 #endif
   { "reply_address",       vtype_reply,       NULL },
-#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
-  { "requiretls",          vtype_bool,        &tls_requiretls },
-#endif
   { "return_path",         vtype_stringptr,   &return_path },
   { "return_size_limit",   vtype_int,         &bounce_return_size_limit },
   { "router_name",         vtype_stringptr,   &router_name },
   { "return_path",         vtype_stringptr,   &return_path },
   { "return_size_limit",   vtype_int,         &bounce_return_size_limit },
   { "router_name",         vtype_stringptr,   &router_name },
@@ -741,16 +748,21 @@ static var_entry var_table[] = {
   { "tls_in_bits",         vtype_int,         &tls_in.bits },
   { "tls_in_certificate_verified", vtype_int, &tls_in.certificate_verified },
   { "tls_in_cipher",       vtype_stringptr,   &tls_in.cipher },
   { "tls_in_bits",         vtype_int,         &tls_in.bits },
   { "tls_in_certificate_verified", vtype_int, &tls_in.certificate_verified },
   { "tls_in_cipher",       vtype_stringptr,   &tls_in.cipher },
+  { "tls_in_cipher_std",   vtype_stringptr,   &tls_in.cipher_stdname },
   { "tls_in_ocsp",         vtype_int,         &tls_in.ocsp },
   { "tls_in_ourcert",      vtype_cert,        &tls_in.ourcert },
   { "tls_in_peercert",     vtype_cert,        &tls_in.peercert },
   { "tls_in_peerdn",       vtype_stringptr,   &tls_in.peerdn },
   { "tls_in_ocsp",         vtype_int,         &tls_in.ocsp },
   { "tls_in_ourcert",      vtype_cert,        &tls_in.ourcert },
   { "tls_in_peercert",     vtype_cert,        &tls_in.peercert },
   { "tls_in_peerdn",       vtype_stringptr,   &tls_in.peerdn },
-#if defined(SUPPORT_TLS)
+#ifdef EXPERIMENTAL_TLS_RESUME
+  { "tls_in_resumption",   vtype_int,         &tls_in.resumption },
+#endif
+#ifndef DISABLE_TLS
   { "tls_in_sni",          vtype_stringptr,   &tls_in.sni },
 #endif
   { "tls_out_bits",        vtype_int,         &tls_out.bits },
   { "tls_out_certificate_verified", vtype_int,&tls_out.certificate_verified },
   { "tls_out_cipher",      vtype_stringptr,   &tls_out.cipher },
   { "tls_in_sni",          vtype_stringptr,   &tls_in.sni },
 #endif
   { "tls_out_bits",        vtype_int,         &tls_out.bits },
   { "tls_out_certificate_verified", vtype_int,&tls_out.certificate_verified },
   { "tls_out_cipher",      vtype_stringptr,   &tls_out.cipher },
+  { "tls_out_cipher_std",  vtype_stringptr,   &tls_out.cipher_stdname },
 #ifdef SUPPORT_DANE
   { "tls_out_dane",        vtype_bool,        &tls_out.dane_verified },
 #endif
 #ifdef SUPPORT_DANE
   { "tls_out_dane",        vtype_bool,        &tls_out.dane_verified },
 #endif
@@ -758,7 +770,10 @@ static var_entry var_table[] = {
   { "tls_out_ourcert",     vtype_cert,        &tls_out.ourcert },
   { "tls_out_peercert",    vtype_cert,        &tls_out.peercert },
   { "tls_out_peerdn",      vtype_stringptr,   &tls_out.peerdn },
   { "tls_out_ourcert",     vtype_cert,        &tls_out.ourcert },
   { "tls_out_peercert",    vtype_cert,        &tls_out.peercert },
   { "tls_out_peerdn",      vtype_stringptr,   &tls_out.peerdn },
-#if defined(SUPPORT_TLS)
+#ifdef EXPERIMENTAL_TLS_RESUME
+  { "tls_out_resumption",  vtype_int,         &tls_out.resumption },
+#endif
+#ifndef DISABLE_TLS
   { "tls_out_sni",         vtype_stringptr,   &tls_out.sni },
 #endif
 #ifdef SUPPORT_DANE
   { "tls_out_sni",         vtype_stringptr,   &tls_out.sni },
 #endif
 #ifdef SUPPORT_DANE
@@ -766,7 +781,7 @@ static var_entry var_table[] = {
 #endif
 
   { "tls_peerdn",          vtype_stringptr,   &tls_in.peerdn },        /* mind the alphabetical order! */
 #endif
 
   { "tls_peerdn",          vtype_stringptr,   &tls_in.peerdn },        /* mind the alphabetical order! */
-#if defined(SUPPORT_TLS)
+#ifndef DISABLE_TLS
   { "tls_sni",             vtype_stringptr,   &tls_in.sni },   /* mind the alphabetical order! */
 #endif
 
   { "tls_sni",             vtype_stringptr,   &tls_in.sni },   /* mind the alphabetical order! */
 #endif
 
@@ -952,7 +967,7 @@ weirdness they'll twist this into.  The result should ideally handle fork().
 However, if we're stuck unable to provide this, then we'll fall back to
 appallingly bad randomness.
 
 However, if we're stuck unable to provide this, then we'll fall back to
 appallingly bad randomness.
 
-If SUPPORT_TLS is defined then this will not be used except as an emergency
+If DISABLE_TLS is not defined then this will not be used except as an emergency
 fallback.
 
 Arguments:
 fallback.
 
 Arguments:
@@ -960,56 +975,55 @@ Arguments:
 Returns     a random number in range [0, max-1]
 */
 
 Returns     a random number in range [0, max-1]
 */
 
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
 # define vaguely_random_number vaguely_random_number_fallback
 #endif
 int
 vaguely_random_number(int max)
 {
 # define vaguely_random_number vaguely_random_number_fallback
 #endif
 int
 vaguely_random_number(int max)
 {
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
 # undef vaguely_random_number
 #endif
 # undef vaguely_random_number
 #endif
-  static pid_t pid = 0;
-  pid_t p2;
-#if defined(HAVE_SRANDOM) && !defined(HAVE_SRANDOMDEV)
-  struct timeval tv;
-#endif
+static pid_t pid = 0;
+pid_t p2;
 
 
-  p2 = getpid();
-  if (p2 != pid)
+if ((p2 = getpid()) != pid)
+  {
+  if (pid != 0)
     {
     {
-    if (pid != 0)
-      {
 
 #ifdef HAVE_ARC4RANDOM
 
 #ifdef HAVE_ARC4RANDOM
-      /* cryptographically strong randomness, common on *BSD platforms, not
-      so much elsewhere.  Alas. */
-#ifndef NOT_HAVE_ARC4RANDOM_STIR
-      arc4random_stir();
-#endif
+    /* cryptographically strong randomness, common on *BSD platforms, not
+    so much elsewhere.  Alas. */
+# ifndef NOT_HAVE_ARC4RANDOM_STIR
+    arc4random_stir();
+# endif
 #elif defined(HAVE_SRANDOM) || defined(HAVE_SRANDOMDEV)
 #elif defined(HAVE_SRANDOM) || defined(HAVE_SRANDOMDEV)
-#ifdef HAVE_SRANDOMDEV
-      /* uses random(4) for seeding */
-      srandomdev();
-#else
-      gettimeofday(&tv, NULL);
-      srandom(tv.tv_sec | tv.tv_usec | getpid());
-#endif
+# ifdef HAVE_SRANDOMDEV
+    /* uses random(4) for seeding */
+    srandomdev();
+# else
+    {
+    struct timeval tv;
+    gettimeofday(&tv, NULL);
+    srandom(tv.tv_sec | tv.tv_usec | getpid());
+    }
+# endif
 #else
 #else
-      /* Poor randomness and no seeding here */
+    /* Poor randomness and no seeding here */
 #endif
 
 #endif
 
-      }
-    pid = p2;
     }
     }
+  pid = p2;
+  }
 
 #ifdef HAVE_ARC4RANDOM
 
 #ifdef HAVE_ARC4RANDOM
-  return arc4random() % max;
+return arc4random() % max;
 #elif defined(HAVE_SRANDOM) || defined(HAVE_SRANDOMDEV)
 #elif defined(HAVE_SRANDOM) || defined(HAVE_SRANDOMDEV)
-  return random() % max;
+return random() % max;
 #else
 #else
-  /* This one returns a 16-bit number, definitely not crypto-strong */
-  return random_number(max);
+/* This one returns a 16-bit number, definitely not crypto-strong */
+return random_number(max);
 #endif
 }
 
 #endif
 }
 
@@ -1275,7 +1289,7 @@ return string_nextinlist(&list, &sep, NULL, 0);
 /* Certificate fields, by name.  Worry about by-OID later */
 /* Names are chosen to not have common prefixes */
 
 /* Certificate fields, by name.  Worry about by-OID later */
 /* Names are chosen to not have common prefixes */
 
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
 typedef struct
 {
 uschar * name;
 typedef struct
 {
 uschar * name;
@@ -1301,7 +1315,6 @@ static uschar *
 expand_getcertele(uschar * field, uschar * certvar)
 {
 var_entry * vp;
 expand_getcertele(uschar * field, uschar * certvar)
 {
 var_entry * vp;
-certfield * cp;
 
 if (!(vp = find_var_ent(certvar)))
   {
 
 if (!(vp = find_var_ent(certvar)))
   {
@@ -1323,9 +1336,9 @@ if (!*(void **)vp->value)
 if (*field >= '0' && *field <= '9')
   return tls_cert_ext_by_oid(*(void **)vp->value, field, 0);
 
 if (*field >= '0' && *field <= '9')
   return tls_cert_ext_by_oid(*(void **)vp->value, field, 0);
 
-for(cp = certfields;
-    cp < certfields + nelem(certfields);
-    cp++)
+for (certfield * cp = certfields;
+     cp < certfields + nelem(certfields);
+     cp++)
   if (Ustrncmp(cp->name, field, cp->namelen) == 0)
     {
     uschar * modifier = *(field += cp->namelen) == ','
   if (Ustrncmp(cp->name, field, cp->namelen) == 0)
     {
     uschar * modifier = *(field += cp->namelen) == ','
@@ -1337,7 +1350,7 @@ expand_string_message =
   string_sprintf("bad field selector \"%s\" for certextract", field);
 return NULL;
 }
   string_sprintf("bad field selector \"%s\" for certextract", field);
 return NULL;
 }
-#endif /*SUPPORT_TLS*/
+#endif /*DISABLE_TLS*/
 
 /*************************************************
 *        Extract a substring from a string       *
 
 /*************************************************
 *        Extract a substring from a string       *
@@ -1561,10 +1574,9 @@ find_header(uschar *name, int *newsize, unsigned flags, uschar *charset)
 BOOL found = !name;
 int len = name ? Ustrlen(name) : 0;
 BOOL comma = FALSE;
 BOOL found = !name;
 int len = name ? Ustrlen(name) : 0;
 BOOL comma = FALSE;
-header_line * h;
 gstring * g = NULL;
 
 gstring * g = NULL;
 
-for (h = header_list; h; h = h->next)
+for (header_line * h = header_list; h; h = h->next)
   if (h->type != htype_old && h->text)  /* NULL => Received: placeholder */
     if (!name || (len <= h->slen && strncmpic(name, h->text, len) == 0))
       {
   if (h->type != htype_old && h->text)  /* NULL => Received: placeholder */
     if (!name || (len <= h->slen && strncmpic(name, h->text, len) == 0))
       {
@@ -1687,7 +1699,7 @@ else
   return g;
 
 if (sender_host_address)
   return g;
 
 if (sender_host_address)
-  g = string_append(g, 2, US" smtp.client-ip=", sender_host_address);
+  g = string_append(g, 2, US" smtp.remote-ip=", sender_host_address);
 return g;
 }
 
 return g;
 }
 
@@ -1705,11 +1717,10 @@ fn_recipients(void)
 {
 uschar * s;
 gstring * g = NULL;
 {
 uschar * s;
 gstring * g = NULL;
-int i;
 
 if (!f.enable_dollar_recipients) return NULL;
 
 
 if (!f.enable_dollar_recipients) return NULL;
 
-for (i = 0; i < recipients_count; i++)
+for (int i = 0; i < recipients_count; i++)
   {
   s = recipients_list[i].address;
   g = string_append2_listele_n(g, US", ", s, Ustrlen(s));
   {
   s = recipients_list[i].address;
   g = string_append2_listele_n(g, US", ", s, Ustrlen(s));
@@ -1762,8 +1773,13 @@ set, in which case give an error. */
 if ((Ustrncmp(name, "acl_c", 5) == 0 || Ustrncmp(name, "acl_m", 5) == 0) &&
      !isalpha(name[5]))
   {
 if ((Ustrncmp(name, "acl_c", 5) == 0 || Ustrncmp(name, "acl_m", 5) == 0) &&
      !isalpha(name[5]))
   {
-  tree_node *node =
-    tree_search((name[4] == 'c')? acl_var_c : acl_var_m, name + 4);
+  tree_node * node =
+    tree_search(name[4] == 'c' ? acl_var_c : acl_var_m, name + 4);
+  return node ? node->data.ptr : strict_acl_vars ? NULL : US"";
+  }
+else if (Ustrncmp(name, "r_", 2) == 0)
+  {
+  tree_node * node = tree_search(router_var, name + 2);
   return node ? node->data.ptr : strict_acl_vars ? NULL : US"";
   }
 
   return node ? node->data.ptr : strict_acl_vars ? NULL : US"";
   }
 
@@ -1840,22 +1856,17 @@ switch (vp->type)
     return sender_host_name ? sender_host_name : US"";
 
   case vtype_localpart:                      /* Get local part from address */
     return sender_host_name ? sender_host_name : US"";
 
   case vtype_localpart:                      /* Get local part from address */
-    s = *((uschar **)(val));
-    if (s == NULL) return US"";
-    domain = Ustrrchr(s, '@');
-    if (domain == NULL) return s;
+    if (!(s = *((uschar **)(val)))) return US"";
+    if (!(domain = Ustrrchr(s, '@'))) return s;
     if (domain - s > sizeof(var_buffer) - 1)
       log_write(0, LOG_MAIN|LOG_PANIC_DIE, "local part longer than " SIZE_T_FMT
          " in string expansion", sizeof(var_buffer));
     if (domain - s > sizeof(var_buffer) - 1)
       log_write(0, LOG_MAIN|LOG_PANIC_DIE, "local part longer than " SIZE_T_FMT
          " in string expansion", sizeof(var_buffer));
-    Ustrncpy(var_buffer, s, domain - s);
-    var_buffer[domain - s] = 0;
-    return var_buffer;
+    return string_copyn(s, domain - s);
 
   case vtype_domain:                         /* Get domain from address */
 
   case vtype_domain:                         /* Get domain from address */
-    s = *((uschar **)(val));
-    if (s == NULL) return US"";
+    if (!(s = *((uschar **)(val)))) return US"";
     domain = Ustrrchr(s, '@');
     domain = Ustrrchr(s, '@');
-    return (domain == NULL)? US"" : domain + 1;
+    return domain ? domain + 1 : US"";
 
   case vtype_msgheaders:
     return find_header(NULL, newsize, exists_only ? FH_EXISTS_ONLY : 0, NULL);
 
   case vtype_msgheaders:
     return find_header(NULL, newsize, exists_only ? FH_EXISTS_ONLY : 0, NULL);
@@ -1957,7 +1968,7 @@ switch (vp->type)
   case vtype_pspace:
     {
     int inodes;
   case vtype_pspace:
     {
     int inodes;
-    sprintf(CS var_buffer, "%d",
+    sprintf(CS var_buffer, PR_EXIM_ARITH,
       receive_statvfs(val == (void *)TRUE, &inodes));
     }
   return var_buffer;
       receive_statvfs(val == (void *)TRUE, &inodes));
     }
   return var_buffer;
@@ -2028,11 +2039,10 @@ static int
 read_subs(uschar **sub, int n, int m, const uschar **sptr, BOOL skipping,
   BOOL check_end, uschar *name, BOOL *resetok)
 {
 read_subs(uschar **sub, int n, int m, const uschar **sptr, BOOL skipping,
   BOOL check_end, uschar *name, BOOL *resetok)
 {
-int i;
 const uschar *s = *sptr;
 
 while (isspace(*s)) s++;
 const uschar *s = *sptr;
 
 while (isspace(*s)) s++;
-for (i = 0; i < n; i++)
+for (int i = 0; i < n; i++)
   {
   if (*s != '{')
     {
   {
   if (*s != '{')
     {
@@ -2148,6 +2158,139 @@ return ret;
 
 
 
 
 
 
+/* Return pointer to dewrapped string, with enclosing specified chars removed.
+The given string is modified on return.  Leading whitespace is skipped while
+looking for the opening wrap character, then the rest is scanned for the trailing
+(non-escaped) wrap character.  A backslash in the string will act as an escape.
+
+A nul is written over the trailing wrap, and a pointer to the char after the
+leading wrap is returned.
+
+Arguments:
+  s    String for de-wrapping
+  wrap  Two-char string, the first being the opener, second the closer wrapping
+        character
+Return:
+  Pointer to de-wrapped string, or NULL on error (with expand_string_message set).
+*/
+
+static uschar *
+dewrap(uschar * s, const uschar * wrap)
+{
+uschar * p = s;
+unsigned depth = 0;
+BOOL quotesmode = wrap[0] == wrap[1];
+
+while (isspace(*p)) p++;
+
+if (*p == *wrap)
+  {
+  s = ++p;
+  wrap++;
+  while (*p)
+    {
+    if (*p == '\\') p++;
+    else if (!quotesmode && *p == wrap[-1]) depth++;
+    else if (*p == *wrap)
+      if (depth == 0)
+       {
+       *p = '\0';
+       return s;
+       }
+      else
+       depth--;
+    p++;
+    }
+  }
+expand_string_message = string_sprintf("missing '%c'", *wrap);
+return NULL;
+}
+
+
+/* Pull off the leading array or object element, returning
+a copy in an allocated string.  Update the list pointer.
+
+The element may itself be an abject or array.
+Return NULL when the list is empty.
+*/
+
+static uschar *
+json_nextinlist(const uschar ** list)
+{
+unsigned array_depth = 0, object_depth = 0;
+const uschar * s = *list, * item;
+
+while (isspace(*s)) s++;
+
+for (item = s;
+     *s && (*s != ',' || array_depth != 0 || object_depth != 0);
+     s++)
+  switch (*s)
+    {
+    case '[': array_depth++; break;
+    case ']': array_depth--; break;
+    case '{': object_depth++; break;
+    case '}': object_depth--; break;
+    }
+*list = *s ? s+1 : s;
+if (item == s) return NULL;
+item = string_copyn(item, s - item);
+DEBUG(D_expand) debug_printf_indent("  json ele: '%s'\n", item);
+return US item;
+}
+
+
+
+/************************************************/
+/*  Return offset in ops table, or -1 if not found.
+Repoint to just after the operator in the string.
+
+Argument:
+ ss    string representation of operator
+ opname        split-out operator name
+*/
+
+static int
+identify_operator(const uschar ** ss, uschar ** opname)
+{
+const uschar * s = *ss;
+uschar name[256];
+
+/* Numeric comparisons are symbolic */
+
+if (*s == '=' || *s == '>' || *s == '<')
+  {
+  int p = 0;
+  name[p++] = *s++;
+  if (*s == '=')
+    {
+    name[p++] = '=';
+    s++;
+    }
+  name[p] = 0;
+  }
+
+/* All other conditions are named */
+
+else
+  s = read_name(name, sizeof(name), s, US"_");
+*ss = s;
+
+/* If we haven't read a name, it means some non-alpha character is first. */
+
+if (!name[0])
+  {
+  expand_string_message = string_sprintf("condition name expected, "
+    "but found \"%.16s\"", s);
+  return -1;
+  }
+if (opname)
+  *opname = string_copy(name);
+
+return chop_match(name, cond_table, nelem(cond_table));
+}
+
+
 /*************************************************
 *        Read and evaluate a condition           *
 *************************************************/
 /*************************************************
 *        Read and evaluate a condition           *
 *************************************************/
@@ -2174,9 +2317,11 @@ BOOL testfor = TRUE;
 BOOL tempcond, combined_cond;
 BOOL *subcondptr;
 BOOL sub2_honour_dollar = TRUE;
 BOOL tempcond, combined_cond;
 BOOL *subcondptr;
 BOOL sub2_honour_dollar = TRUE;
-int i, rc, cond_type, roffset;
+BOOL is_forany, is_json, is_jsons;
+int rc, cond_type, roffset;
 int_eximarith_t num[2];
 struct stat statbuf;
 int_eximarith_t num[2];
 struct stat statbuf;
+uschar * opname;
 uschar name[256];
 const uschar *sub[10];
 
 uschar name[256];
 const uschar *sub[10];
 
@@ -2189,37 +2334,7 @@ for (;;)
   if (*s == '!') { testfor = !testfor; s++; } else break;
   }
 
   if (*s == '!') { testfor = !testfor; s++; } else break;
   }
 
-/* Numeric comparisons are symbolic */
-
-if (*s == '=' || *s == '>' || *s == '<')
-  {
-  int p = 0;
-  name[p++] = *s++;
-  if (*s == '=')
-    {
-    name[p++] = '=';
-    s++;
-    }
-  name[p] = 0;
-  }
-
-/* All other conditions are named */
-
-else s = read_name(name, 256, s, US"_");
-
-/* If we haven't read a name, it means some non-alpha character is first. */
-
-if (name[0] == 0)
-  {
-  expand_string_message = string_sprintf("condition name expected, "
-    "but found \"%.16s\"", s);
-  return NULL;
-  }
-
-/* Find which condition we are dealing with, and switch on it */
-
-cond_type = chop_match(name, cond_table, nelem(cond_table));
-switch(cond_type)
+switch(cond_type = identify_operator(&s, &opname))
   {
   /* def: tests for a non-empty variable, or for the existence of a header. If
   yield == NULL we are in a skipping state, and don't care about the answer. */
   {
   /* def: tests for a non-empty variable, or for the existence of a header. If
   yield == NULL we are in a skipping state, and don't care about the answer. */
@@ -2234,7 +2349,7 @@ switch(cond_type)
       return NULL;
       }
 
       return NULL;
       }
 
-    s = read_name(name, 256, s+1, US"_");
+    s = read_name(name, sizeof(name), s+1, US"_");
 
     /* Test for a header's existence. If the name contains a closing brace
     character, this may be a user error where the terminating colon has been
 
     /* Test for a header's existence. If the name contains a closing brace
     character, this may be a user error where the terminating colon has been
@@ -2246,7 +2361,7 @@ switch(cond_type)
        && (*++t == '_' || Ustrncmp(t, "eader_", 6) == 0)
        )
       {
        && (*++t == '_' || Ustrncmp(t, "eader_", 6) == 0)
        )
       {
-      s = read_header_name(name, 256, s);
+      s = read_header_name(name, sizeof(name), s);
       /* {-for-text-editors */
       if (Ustrchr(name, '}') != NULL) malformed_header = TRUE;
       if (yield) *yield =
       /* {-for-text-editors */
       if (Ustrchr(name, '}') != NULL) malformed_header = TRUE;
       if (yield) *yield =
@@ -2260,9 +2375,9 @@ switch(cond_type)
       {
       if (!(t = find_variable(name, TRUE, yield == NULL, NULL)))
        {
       {
       if (!(t = find_variable(name, TRUE, yield == NULL, NULL)))
        {
-       expand_string_message = (name[0] == 0)?
-         string_sprintf("variable name omitted after \"def:\"") :
-         string_sprintf("unknown variable \"%s\" after \"def:\"", name);
+       expand_string_message = name[0]
+         ? string_sprintf("unknown variable \"%s\" after \"def:\"", name)
+         : US"variable name omitted after \"def:\"";
        check_variable_error_message(name);
        return NULL;
        }
        check_variable_error_message(name);
        return NULL;
        }
@@ -2418,8 +2533,9 @@ switch(cond_type)
 
     if (yield != NULL)
       {
 
     if (yield != NULL)
       {
+      int rc;
       *resetok = FALSE;        /* eval_acl() might allocate; do not reclaim */
       *resetok = FALSE;        /* eval_acl() might allocate; do not reclaim */
-      switch(eval_acl(sub, nelem(sub), &user_msg))
+      switch(rc = eval_acl(sub, nelem(sub), &user_msg))
        {
        case OK:
          cond = TRUE;
        {
        case OK:
          cond = TRUE;
@@ -2434,7 +2550,8 @@ switch(cond_type)
           f.expand_string_forcedfail = TRUE;
          /*FALLTHROUGH*/
        default:
           f.expand_string_forcedfail = TRUE;
          /*FALLTHROUGH*/
        default:
-          expand_string_message = string_sprintf("error from acl \"%s\"", sub[0]);
+          expand_string_message = string_sprintf("%s from acl \"%s\"",
+           rc_names[rc], sub[0]);
          return NULL;
        }
       }
          return NULL;
        }
       }
@@ -2524,7 +2641,7 @@ switch(cond_type)
   case ECOND_STR_GE:
   case ECOND_STR_GEI:
 
   case ECOND_STR_GE:
   case ECOND_STR_GEI:
 
-  for (i = 0; i < 2; i++)
+  for (int i = 0; i < 2; i++)
     {
     /* Sometimes, we don't expand substrings; too many insecure configurations
     created using match_address{}{} and friends, where the second param
     {
     /* Sometimes, we don't expand substrings; too many insecure configurations
     created using match_address{}{} and friends, where the second param
@@ -2538,7 +2655,7 @@ switch(cond_type)
       {
       if (i == 0) goto COND_FAILED_CURLY_START;
       expand_string_message = string_sprintf("missing 2nd string in {} "
       {
       if (i == 0) goto COND_FAILED_CURLY_START;
       expand_string_message = string_sprintf("missing 2nd string in {} "
-        "after \"%s\"", name);
+        "after \"%s\"", opname);
       return NULL;
       }
     if (!(sub[i] = expand_string_internal(s+1, TRUE, &s, yield == NULL,
       return NULL;
       }
     if (!(sub[i] = expand_string_internal(s+1, TRUE, &s, yield == NULL,
@@ -2553,7 +2670,7 @@ switch(cond_type)
     conditions that compare numbers do not start with a letter. This just saves
     checking for them individually. */
 
     conditions that compare numbers do not start with a letter. This just saves
     checking for them individually. */
 
-    if (!isalpha(name[0]) && yield != NULL)
+    if (!isalpha(opname[0]) && yield != NULL)
       if (sub[i][0] == 0)
         {
         num[i] = 0;
       if (sub[i][0] == 0)
         {
         num[i] = 0;
@@ -2741,16 +2858,15 @@ switch(cond_type)
 
       if (sublen == 24)
         {
 
       if (sublen == 24)
         {
-        uschar *coded = b64encode(digest, 16);
+        uschar *coded = b64encode(CUS digest, 16);
         DEBUG(D_auth) debug_printf("crypteq: using MD5+B64 hashing\n"
           "  subject=%s\n  crypted=%s\n", coded, sub[1]+5);
         tempcond = (Ustrcmp(coded, sub[1]+5) == 0);
         }
       else if (sublen == 32)
         {
         DEBUG(D_auth) debug_printf("crypteq: using MD5+B64 hashing\n"
           "  subject=%s\n  crypted=%s\n", coded, sub[1]+5);
         tempcond = (Ustrcmp(coded, sub[1]+5) == 0);
         }
       else if (sublen == 32)
         {
-        int i;
         uschar coded[36];
         uschar coded[36];
-        for (i = 0; i < 16; i++) sprintf(CS (coded+2*i), "%02X", digest[i]);
+        for (int i = 0; i < 16; i++) sprintf(CS (coded+2*i), "%02X", digest[i]);
         coded[32] = 0;
         DEBUG(D_auth) debug_printf("crypteq: using MD5+hex hashing\n"
           "  subject=%s\n  crypted=%s\n", coded, sub[1]+5);
         coded[32] = 0;
         DEBUG(D_auth) debug_printf("crypteq: using MD5+hex hashing\n"
           "  subject=%s\n  crypted=%s\n", coded, sub[1]+5);
@@ -2779,16 +2895,15 @@ switch(cond_type)
 
       if (sublen == 28)
         {
 
       if (sublen == 28)
         {
-        uschar *coded = b64encode(digest, 20);
+        uschar *coded = b64encode(CUS digest, 20);
         DEBUG(D_auth) debug_printf("crypteq: using SHA1+B64 hashing\n"
           "  subject=%s\n  crypted=%s\n", coded, sub[1]+6);
         tempcond = (Ustrcmp(coded, sub[1]+6) == 0);
         }
       else if (sublen == 40)
         {
         DEBUG(D_auth) debug_printf("crypteq: using SHA1+B64 hashing\n"
           "  subject=%s\n  crypted=%s\n", coded, sub[1]+6);
         tempcond = (Ustrcmp(coded, sub[1]+6) == 0);
         }
       else if (sublen == 40)
         {
-        int i;
         uschar coded[44];
         uschar coded[44];
-        for (i = 0; i < 20; i++) sprintf(CS (coded+2*i), "%02X", digest[i]);
+        for (int i = 0; i < 20; i++) sprintf(CS (coded+2*i), "%02X", digest[i]);
         coded[40] = 0;
         DEBUG(D_auth) debug_printf("crypteq: using SHA1+hex hashing\n"
           "  subject=%s\n  crypted=%s\n", coded, sub[1]+6);
         coded[40] = 0;
         DEBUG(D_auth) debug_printf("crypteq: using SHA1+hex hashing\n"
           "  subject=%s\n  crypted=%s\n", coded, sub[1]+6);
@@ -2867,7 +2982,7 @@ switch(cond_type)
       uschar *save_iterate_item = iterate_item;
       int (*compare)(const uschar *, const uschar *);
 
       uschar *save_iterate_item = iterate_item;
       int (*compare)(const uschar *, const uschar *);
 
-      DEBUG(D_expand) debug_printf_indent("condition: %s  item: %s\n", name, sub[0]);
+      DEBUG(D_expand) debug_printf_indent("condition: %s  item: %s\n", opname, sub[0]);
 
       tempcond = FALSE;
       compare = cond_type == ECOND_INLISTI
 
       tempcond = FALSE;
       compare = cond_type == ECOND_INLISTI
@@ -2909,14 +3024,14 @@ switch(cond_type)
     if (*s != '{')                                     /* }-for-text-editors */
       {
       expand_string_message = string_sprintf("each subcondition "
     if (*s != '{')                                     /* }-for-text-editors */
       {
       expand_string_message = string_sprintf("each subcondition "
-        "inside an \"%s{...}\" condition must be in its own {}", name);
+        "inside an \"%s{...}\" condition must be in its own {}", opname);
       return NULL;
       }
 
     if (!(s = eval_condition(s+1, resetok, subcondptr)))
       {
       expand_string_message = string_sprintf("%s inside \"%s{...}\" condition",
       return NULL;
       }
 
     if (!(s = eval_condition(s+1, resetok, subcondptr)))
       {
       expand_string_message = string_sprintf("%s inside \"%s{...}\" condition",
-        expand_string_message, name);
+        expand_string_message, opname);
       return NULL;
       }
     while (isspace(*s)) s++;
       return NULL;
       }
     while (isspace(*s)) s++;
@@ -2926,7 +3041,7 @@ switch(cond_type)
       {
       /* {-for-text-editors */
       expand_string_message = string_sprintf("missing } at end of condition "
       {
       /* {-for-text-editors */
       expand_string_message = string_sprintf("missing } at end of condition "
-        "inside \"%s\" group", name);
+        "inside \"%s\" group", opname);
       return NULL;
       }
 
       return NULL;
       }
 
@@ -2951,14 +3066,20 @@ switch(cond_type)
 
   /* forall/forany: iterates a condition with different values */
 
 
   /* forall/forany: iterates a condition with different values */
 
-  case ECOND_FORALL:
-  case ECOND_FORANY:
+  case ECOND_FORALL:     is_forany = FALSE;  is_json = FALSE; is_jsons = FALSE; goto FORMANY;
+  case ECOND_FORANY:     is_forany = TRUE;   is_json = FALSE; is_jsons = FALSE; goto FORMANY;
+  case ECOND_FORALL_JSON: is_forany = FALSE;  is_json = TRUE;  is_jsons = FALSE; goto FORMANY;
+  case ECOND_FORANY_JSON: is_forany = TRUE;   is_json = TRUE;  is_jsons = FALSE; goto FORMANY;
+  case ECOND_FORALL_JSONS: is_forany = FALSE; is_json = TRUE;  is_jsons = TRUE;  goto FORMANY;
+  case ECOND_FORANY_JSONS: is_forany = TRUE;  is_json = TRUE;  is_jsons = TRUE;  goto FORMANY;
+
+  FORMANY:
     {
     const uschar * list;
     int sep = 0;
     uschar *save_iterate_item = iterate_item;
 
     {
     const uschar * list;
     int sep = 0;
     uschar *save_iterate_item = iterate_item;
 
-    DEBUG(D_expand) debug_printf_indent("condition: %s\n", name);
+    DEBUG(D_expand) debug_printf_indent("condition: %s\n", opname);
 
     while (isspace(*s)) s++;
     if (*s++ != '{') goto COND_FAILED_CURLY_START;     /* }-for-text-editors */
 
     while (isspace(*s)) s++;
     if (*s++ != '{') goto COND_FAILED_CURLY_START;     /* }-for-text-editors */
@@ -2979,7 +3100,7 @@ switch(cond_type)
     if (!(s = eval_condition(sub[1], resetok, NULL)))
       {
       expand_string_message = string_sprintf("%s inside \"%s\" condition",
     if (!(s = eval_condition(sub[1], resetok, NULL)))
       {
       expand_string_message = string_sprintf("%s inside \"%s\" condition",
-        expand_string_message, name);
+        expand_string_message, opname);
       return NULL;
       }
     while (isspace(*s)) s++;
       return NULL;
       }
     while (isspace(*s)) s++;
@@ -2989,27 +3110,39 @@ switch(cond_type)
       {
       /* {-for-text-editors */
       expand_string_message = string_sprintf("missing } at end of condition "
       {
       /* {-for-text-editors */
       expand_string_message = string_sprintf("missing } at end of condition "
-        "inside \"%s\"", name);
+        "inside \"%s\"", opname);
       return NULL;
       }
 
       return NULL;
       }
 
-    if (yield != NULL) *yield = !testfor;
+    if (yield) *yield = !testfor;
     list = sub[0];
     list = sub[0];
-    while ((iterate_item = string_nextinlist(&list, &sep, NULL, 0)) != NULL)
+    if (is_json) list = dewrap(string_copy(list), US"[]");
+    while ((iterate_item = is_json
+      ? json_nextinlist(&list) : string_nextinlist(&list, &sep, NULL, 0)))
       {
       {
-      DEBUG(D_expand) debug_printf_indent("%s: $item = \"%s\"\n", name, iterate_item);
+      if (is_jsons)
+       if (!(iterate_item = dewrap(iterate_item, US"\"\"")))
+         {
+         expand_string_message =
+           string_sprintf("%s wrapping string result for extract jsons",
+             expand_string_message);
+         iterate_item = save_iterate_item;
+         return NULL;
+         }
+
+      DEBUG(D_expand) debug_printf_indent("%s: $item = \"%s\"\n", opname, iterate_item);
       if (!eval_condition(sub[1], resetok, &tempcond))
         {
         expand_string_message = string_sprintf("%s inside \"%s\" condition",
       if (!eval_condition(sub[1], resetok, &tempcond))
         {
         expand_string_message = string_sprintf("%s inside \"%s\" condition",
-          expand_string_message, name);
+          expand_string_message, opname);
         iterate_item = save_iterate_item;
         return NULL;
         }
         iterate_item = save_iterate_item;
         return NULL;
         }
-      DEBUG(D_expand) debug_printf_indent("%s: condition evaluated to %s\n", name,
+      DEBUG(D_expand) debug_printf_indent("%s: condition evaluated to %s\n", opname,
         tempcond? "true":"false");
 
         tempcond? "true":"false");
 
-      if (yield != NULL) *yield = (tempcond == testfor);
-      if (tempcond == (cond_type == ECOND_FORANY)) break;
+      if (yield) *yield = (tempcond == testfor);
+      if (tempcond == is_forany) break;
       }
 
     iterate_item = save_iterate_item;
       }
 
     iterate_item = save_iterate_item;
@@ -3098,19 +3231,20 @@ switch(cond_type)
   /* Unknown condition */
 
   default:
   /* Unknown condition */
 
   default:
-  expand_string_message = string_sprintf("unknown condition \"%s\"", name);
-  return NULL;
+    if (!expand_string_message || !*expand_string_message)
+      expand_string_message = string_sprintf("unknown condition \"%s\"", opname);
+    return NULL;
   }   /* End switch on condition type */
 
 /* Missing braces at start and end of data */
 
 COND_FAILED_CURLY_START:
   }   /* End switch on condition type */
 
 /* Missing braces at start and end of data */
 
 COND_FAILED_CURLY_START:
-expand_string_message = string_sprintf("missing { after \"%s\"", name);
+expand_string_message = string_sprintf("missing { after \"%s\"", opname);
 return NULL;
 
 COND_FAILED_CURLY_END:
 expand_string_message = string_sprintf("missing } at end of \"%s\" condition",
 return NULL;
 
 COND_FAILED_CURLY_END:
 expand_string_message = string_sprintf("missing } at end of \"%s\" condition",
-  name);
+  opname);
 return NULL;
 
 /* A condition requires code that is not compiled */
 return NULL;
 
 /* A condition requires code that is not compiled */
@@ -3120,7 +3254,7 @@ return NULL;
     !defined(SUPPORT_CRYPTEQ) || !defined(CYRUS_SASLAUTHD_SOCKET)
 COND_FAILED_NOT_COMPILED:
 expand_string_message = string_sprintf("support for \"%s\" not compiled",
     !defined(SUPPORT_CRYPTEQ) || !defined(CYRUS_SASLAUTHD_SOCKET)
 COND_FAILED_NOT_COMPILED:
 expand_string_message = string_sprintf("support for \"%s\" not compiled",
-  name);
+  opname);
 return NULL;
 #endif
 }
 return NULL;
 #endif
 }
@@ -3145,8 +3279,7 @@ Returns:                the value of expand max to save
 static int
 save_expand_strings(uschar **save_expand_nstring, int *save_expand_nlength)
 {
 static int
 save_expand_strings(uschar **save_expand_nstring, int *save_expand_nlength)
 {
-int i;
-for (i = 0; i <= expand_nmax; i++)
+for (int i = 0; i <= expand_nmax; i++)
   {
   save_expand_nstring[i] = expand_nstring[i];
   save_expand_nlength[i] = expand_nlength[i];
   {
   save_expand_nstring[i] = expand_nstring[i];
   save_expand_nlength[i] = expand_nlength[i];
@@ -3174,9 +3307,8 @@ static void
 restore_expand_strings(int save_expand_nmax, uschar **save_expand_nstring,
   int *save_expand_nlength)
 {
 restore_expand_strings(int save_expand_nmax, uschar **save_expand_nstring,
   int *save_expand_nlength)
 {
-int i;
 expand_nmax = save_expand_nmax;
 expand_nmax = save_expand_nmax;
-for (i = 0; i <= expand_nmax; i++)
+for (int i = 0; i <= expand_nmax; i++)
   {
   expand_nstring[i] = save_expand_nstring[i];
   expand_nlength[i] = save_expand_nlength[i];
   {
   expand_nstring[i] = save_expand_nstring[i];
   expand_nlength[i] = save_expand_nlength[i];
@@ -3433,7 +3565,7 @@ Returns:  pointer to string containing the last three
 static uschar *
 prvs_daystamp(int day_offset)
 {
 static uschar *
 prvs_daystamp(int day_offset)
 {
-uschar *days = store_get(32);                /* Need at least 24 for cases */
+uschar *days = store_get(32, FALSE);         /* Need at least 24 for cases */
 (void)string_format(days, 32, TIME_T_FMT,    /* where TIME_T_FMT is %lld */
   (time(NULL) + day_offset*86400)/86400);
 return (Ustrlen(days) >= 3) ? &days[Ustrlen(days)-3] : US"100";
 (void)string_format(days, 32, TIME_T_FMT,    /* where TIME_T_FMT is %lld */
   (time(NULL) + day_offset*86400)/86400);
 return (Ustrlen(days) >= 3) ? &days[Ustrlen(days)-3] : US"100";
@@ -3465,13 +3597,12 @@ prvs_hmac_sha1(uschar *address, uschar *key, uschar *key_num, uschar *daystamp)
 {
 gstring * hash_source;
 uschar * p;
 {
 gstring * hash_source;
 uschar * p;
-int i;
 hctx h;
 uschar innerhash[20];
 uschar finalhash[20];
 uschar innerkey[64];
 uschar outerkey[64];
 hctx h;
 uschar innerhash[20];
 uschar finalhash[20];
 uschar innerkey[64];
 uschar outerkey[64];
-uschar *finalhash_hex = store_get(40);
+uschar *finalhash_hex;
 
 if (key_num == NULL)
   key_num = US"0";
 
 if (key_num == NULL)
   key_num = US"0";
@@ -3490,7 +3621,7 @@ DEBUG(D_expand)
 memset(innerkey, 0x36, 64);
 memset(outerkey, 0x5c, 64);
 
 memset(innerkey, 0x36, 64);
 memset(outerkey, 0x5c, 64);
 
-for (i = 0; i < Ustrlen(key); i++)
+for (int i = 0; i < Ustrlen(key); i++)
   {
   innerkey[i] ^= key[i];
   outerkey[i] ^= key[i];
   {
   innerkey[i] ^= key[i];
   outerkey[i] ^= key[i];
@@ -3504,8 +3635,10 @@ chash_start(HMAC_SHA1, &h);
 chash_mid(HMAC_SHA1, &h, outerkey);
 chash_end(HMAC_SHA1, &h, innerhash, 20, finalhash);
 
 chash_mid(HMAC_SHA1, &h, outerkey);
 chash_end(HMAC_SHA1, &h, innerhash, 20, finalhash);
 
-p = finalhash_hex;
-for (i = 0; i < 3; i++)
+/* Hashing is deemed sufficient to de-taint any input data */
+
+p = finalhash_hex = store_get(40, FALSE);
+for (int i = 0; i < 3; i++)
   {
   *p++ = hex_digits[(finalhash[i] & 0xf0) >> 4];
   *p++ = hex_digits[finalhash[i] & 0x0f];
   {
   *p++ = hex_digits[(finalhash[i] & 0xf0) >> 4];
   *p++ = hex_digits[finalhash[i] & 0x0f];
@@ -3553,16 +3686,17 @@ return yield;
 }
 
 
 }
 
 
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
 static gstring *
 cat_file_tls(void * tls_ctx, gstring * yield, uschar * eol)
 {
 int rc;
 static gstring *
 cat_file_tls(void * tls_ctx, gstring * yield, uschar * eol)
 {
 int rc;
-uschar * s;
 uschar buffer[1024];
 
 uschar buffer[1024];
 
+/*XXX could we read direct into a pre-grown string? */
+
 while ((rc = tls_read(tls_ctx, buffer, sizeof(buffer))) > 0)
 while ((rc = tls_read(tls_ctx, buffer, sizeof(buffer))) > 0)
-  for (s = buffer; rc--; s++)
+  for (uschar * s = buffer; rc--; s++)
     yield = eol && *s == '\n'
       ? string_cat(yield, eol) : string_catn(yield, s, 1);
 
     yield = eol && *s == '\n'
       ? string_cat(yield, eol) : string_catn(yield, s, 1);
 
@@ -3601,17 +3735,15 @@ eval_expr(uschar **sptr, BOOL decimal, uschar **error, BOOL endket)
 {
 uschar *s = *sptr;
 int_eximarith_t x = eval_op_or(&s, decimal, error);
 {
 uschar *s = *sptr;
 int_eximarith_t x = eval_op_or(&s, decimal, error);
-if (*error == NULL)
-  {
+
+if (!*error)
   if (endket)
   if (endket)
-    {
     if (*s != ')')
       *error = US"expecting closing parenthesis";
     else
       while (isspace(*(++s)));
     if (*s != ')')
       *error = US"expecting closing parenthesis";
     else
       while (isspace(*(++s)));
-    }
-  else if (*s != 0) *error = US"expecting operator";
-  }
+  else if (*s)
+    *error = US"expecting operator";
 *sptr = s;
 return x;
 }
 *sptr = s;
 return x;
 }
@@ -3620,12 +3752,12 @@ return x;
 static int_eximarith_t
 eval_number(uschar **sptr, BOOL decimal, uschar **error)
 {
 static int_eximarith_t
 eval_number(uschar **sptr, BOOL decimal, uschar **error)
 {
-register int c;
+int c;
 int_eximarith_t n;
 uschar *s = *sptr;
 int_eximarith_t n;
 uschar *s = *sptr;
+
 while (isspace(*s)) s++;
 while (isspace(*s)) s++;
-c = *s;
-if (isdigit(c))
+if (isdigit((c = *s)))
   {
   int count;
   (void)sscanf(CS s, (decimal? SC_EXIM_DEC "%n" : SC_EXIM_ARITH "%n"), &n, &count);
   {
   int count;
   (void)sscanf(CS s, (decimal? SC_EXIM_DEC "%n" : SC_EXIM_ARITH "%n"), &n, &count);
@@ -3668,9 +3800,8 @@ if (*s == '+' || *s == '-' || *s == '~')
     else if (op == '~') x = ~x;
   }
 else
     else if (op == '~') x = ~x;
   }
 else
-  {
   x = eval_number(&s, decimal, error);
   x = eval_number(&s, decimal, error);
-  }
+
 *sptr = s;
 return x;
 }
 *sptr = s;
 return x;
 }
@@ -3849,79 +3980,56 @@ return x;
 
 
 
 
 
 
-/* Return pointer to dewrapped string, with enclosing specified chars removed.
-The given string is modified on return.  Leading whitespace is skipped while
-looking for the opening wrap character, then the rest is scanned for the trailing
-(non-escaped) wrap character.  A backslash in the string will act as an escape.
-
-A nul is written over the trailing wrap, and a pointer to the char after the
-leading wrap is returned.
+/************************************************/
+/* Comparison operation for sort expansion.  We need to avoid
+re-expanding the fields being compared, so need a custom routine.
 
 Arguments:
 
 Arguments:
-  s    String for de-wrapping
-  wrap  Two-char string, the first being the opener, second the closer wrapping
-        character
-Return:
-  Pointer to de-wrapped string, or NULL on error (with expand_string_message set).
+ cond_type             Comparison operator code
+ leftarg, rightarg     Arguments for comparison
+
+Return true iff (leftarg compare rightarg)
 */
 
 */
 
-static uschar *
-dewrap(uschar * s, const uschar * wrap)
+static BOOL
+sortsbefore(int cond_type, BOOL alpha_cond,
+  const uschar * leftarg, const uschar * rightarg)
 {
 {
-uschar * p = s;
+int_eximarith_t l_num, r_num;
 
 
-while (isspace(*p)) p++;
-
-if (*p == *wrap)
+if (!alpha_cond)
   {
   {
-  s = ++p;
-  wrap++;
-  while (*p)
+  l_num = expanded_string_integer(leftarg, FALSE);
+  if (expand_string_message) return FALSE;
+  r_num = expanded_string_integer(rightarg, FALSE);
+  if (expand_string_message) return FALSE;
+
+  switch (cond_type)
     {
     {
-    if (*p == '\\') p++;
-    else if (*p == *wrap)
-      {
-      *p = '\0';
-      return s;
-      }
-    p++;
+    case ECOND_NUM_G:  return l_num >  r_num;
+    case ECOND_NUM_GE: return l_num >= r_num;
+    case ECOND_NUM_L:  return l_num <  r_num;
+    case ECOND_NUM_LE: return l_num <= r_num;
+    default: break;
     }
   }
     }
   }
-expand_string_message = string_sprintf("missing '%c'", *wrap);
-return NULL;
-}
-
-
-/* Pull off the leading array or object element, returning
-a copy in an allocated string.  Update the list pointer.
-
-The element may itself be an abject or array.
-*/
-
-uschar *
-json_nextinlist(const uschar ** list)
-{
-unsigned array_depth = 0, object_depth = 0;
-const uschar * s = *list, * item;
-
-while (isspace(*s)) s++;
-
-for (item = s;
-     *s && (*s != ',' || array_depth != 0 || object_depth != 0);
-     s++)
-  switch (*s)
+else
+  switch (cond_type)
     {
     {
-    case '[': array_depth++; break;
-    case ']': array_depth--; break;
-    case '{': object_depth++; break;
-    case '}': object_depth--; break;
+    case ECOND_STR_LT: return Ustrcmp (leftarg, rightarg) <  0;
+    case ECOND_STR_LTI:        return strcmpic(leftarg, rightarg) <  0;
+    case ECOND_STR_LE: return Ustrcmp (leftarg, rightarg) <= 0;
+    case ECOND_STR_LEI:        return strcmpic(leftarg, rightarg) <= 0;
+    case ECOND_STR_GT: return Ustrcmp (leftarg, rightarg) >  0;
+    case ECOND_STR_GTI:        return strcmpic(leftarg, rightarg) >  0;
+    case ECOND_STR_GE: return Ustrcmp (leftarg, rightarg) >= 0;
+    case ECOND_STR_GEI:        return strcmpic(leftarg, rightarg) >= 0;
+    default: break;
     }
     }
-*list = *s ? s+1 : s;
-return string_copyn(item, s - item);
+return FALSE;  /* should not happen */
 }
 
 
 }
 
 
-
 /*************************************************
 *                 Expand string                  *
 *************************************************/
 /*************************************************
 *                 Expand string                  *
 *************************************************/
@@ -3989,6 +4097,7 @@ static uschar *
 expand_string_internal(const uschar *string, BOOL ket_ends, const uschar **left,
   BOOL skipping, BOOL honour_dollar, BOOL *resetok_p)
 {
 expand_string_internal(const uschar *string, BOOL ket_ends, const uschar **left,
   BOOL skipping, BOOL honour_dollar, BOOL *resetok_p)
 {
+rmark reset_point = store_mark();
 gstring * yield = string_get(Ustrlen(string) + 64);
 int item_type;
 const uschar *s = string;
 gstring * yield = string_get(Ustrlen(string) + 64);
 int item_type;
 const uschar *s = string;
@@ -3998,15 +4107,27 @@ BOOL resetok = TRUE;
 
 expand_level++;
 DEBUG(D_expand)
 
 expand_level++;
 DEBUG(D_expand)
-  debug_printf_indent(UTF8_DOWN_RIGHT "%s: %s\n",
-    skipping
-    ? UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ "scanning"
-    : "considering",
-    string);
+  DEBUG(D_noutf8)
+    debug_printf_indent("/%s: %s\n",
+      skipping ? "---scanning" : "considering", string);
+  else
+    debug_printf_indent(UTF8_DOWN_RIGHT "%s: %s\n",
+      skipping
+      ? UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ "scanning"
+      : "considering",
+      string);
 
 f.expand_string_forcedfail = FALSE;
 expand_string_message = US"";
 
 
 f.expand_string_forcedfail = FALSE;
 expand_string_message = US"";
 
+if (is_tainted(string))
+  {
+  expand_string_message =
+    string_sprintf("attempt to expand tainted string '%s'", s);
+  log_write(0, LOG_MAIN|LOG_PANIC, "%s", expand_string_message);
+  goto EXPAND_FAILED;
+  }
+
 while (*s != 0)
   {
   uschar *value;
 while (*s != 0)
   {
   uschar *value;
@@ -4078,12 +4199,13 @@ while (*s != 0)
     buffer. */
 
     if (!yield)
     buffer. */
 
     if (!yield)
-      g = store_get(sizeof(gstring));
+      g = store_get(sizeof(gstring), FALSE);
     else if (yield->ptr == 0)
       {
     else if (yield->ptr == 0)
       {
-      if (resetok) store_reset(yield);
+      if (resetok) reset_point = store_reset(reset_point);
       yield = NULL;
       yield = NULL;
-      g = store_get(sizeof(gstring));  /* alloc _before_ calling find_variable() */
+      reset_point = store_mark();
+      g = store_get(sizeof(gstring), FALSE);   /* alloc _before_ calling find_variable() */
       }
 
     /* Header */
       }
 
     /* Header */
@@ -4209,6 +4331,7 @@ while (*s != 0)
       {
       uschar *sub[10]; /* name + arg1-arg9 (which must match number of acl_arg[]) */
       uschar *user_msg;
       {
       uschar *sub[10]; /* name + arg1-arg9 (which must match number of acl_arg[]) */
       uschar *user_msg;
+      int rc;
 
       switch(read_subs(sub, nelem(sub), 1, &s, skipping, TRUE, US"acl",
                      &resetok))
 
       switch(read_subs(sub, nelem(sub), 1, &s, skipping, TRUE, US"acl",
                      &resetok))
@@ -4220,7 +4343,7 @@ while (*s != 0)
       if (skipping) continue;
 
       resetok = FALSE;
       if (skipping) continue;
 
       resetok = FALSE;
-      switch(eval_acl(sub, nelem(sub), &user_msg))
+      switch(rc = eval_acl(sub, nelem(sub), &user_msg))
        {
        case OK:
        case FAIL:
        {
        case OK:
        case FAIL:
@@ -4234,7 +4357,8 @@ while (*s != 0)
           f.expand_string_forcedfail = TRUE;
          /*FALLTHROUGH*/
        default:
           f.expand_string_forcedfail = TRUE;
          /*FALLTHROUGH*/
        default:
-          expand_string_message = string_sprintf("error from acl \"%s\"", sub[0]);
+          expand_string_message = string_sprintf("%s from acl \"%s\"",
+           rc_names[rc], sub[0]);
          goto EXPAND_FAILED;
        }
       }
          goto EXPAND_FAILED;
        }
       }
@@ -4291,15 +4415,21 @@ while (*s != 0)
       if (next_s == NULL) goto EXPAND_FAILED;  /* message already set */
 
       DEBUG(D_expand)
       if (next_s == NULL) goto EXPAND_FAILED;  /* message already set */
 
       DEBUG(D_expand)
-       {
-        debug_printf_indent(UTF8_VERT_RIGHT UTF8_HORIZ UTF8_HORIZ
-         "condition: %.*s\n",
-         (int)(next_s - s), s);
-        debug_printf_indent(UTF8_VERT_RIGHT UTF8_HORIZ UTF8_HORIZ
-         UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ
-         "result: %s\n",
-         cond ? "true" : "false");
-       }
+       DEBUG(D_noutf8)
+         {
+         debug_printf_indent("|--condition: %.*s\n", (int)(next_s - s), s);
+         debug_printf_indent("|-----result: %s\n", cond ? "true" : "false");
+         }
+       else
+         {
+         debug_printf_indent(UTF8_VERT_RIGHT UTF8_HORIZ UTF8_HORIZ
+           "condition: %.*s\n",
+           (int)(next_s - s), s);
+         debug_printf_indent(UTF8_VERT_RIGHT UTF8_HORIZ UTF8_HORIZ
+           UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ
+           "result: %s\n",
+           cond ? "true" : "false");
+         }
 
       s = next_s;
 
 
       s = next_s;
 
@@ -4796,7 +4926,7 @@ while (*s != 0)
           (void)sscanf(CS now,"%u",&inow);
           (void)sscanf(CS daystamp,"%u",&iexpire);
 
           (void)sscanf(CS now,"%u",&inow);
           (void)sscanf(CS daystamp,"%u",&iexpire);
 
-          /* When "iexpire" is < 7, a "flip" has occured.
+          /* When "iexpire" is < 7, a "flip" has occurred.
              Adjust "inow" accordingly. */
           if ( (iexpire < 7) && (inow >= 993) ) inow = 0;
 
              Adjust "inow" accordingly. */
           if ( (iexpire < 7) && (inow >= 993) ) inow = 0;
 
@@ -4893,17 +5023,16 @@ while (*s != 0)
 
     case EITEM_READSOCK:
       {
 
     case EITEM_READSOCK:
       {
-      int fd;
+      client_conn_ctx cctx;
       int timeout = 5;
       int save_ptr = yield->ptr;
       int timeout = 5;
       int save_ptr = yield->ptr;
-      FILE * fp;
+      FILE * fp = NULL;
       uschar * arg;
       uschar * sub_arg[4];
       uschar * server_name = NULL;
       host_item host;
       BOOL do_shutdown = TRUE;
       uschar * arg;
       uschar * sub_arg[4];
       uschar * server_name = NULL;
       host_item host;
       BOOL do_shutdown = TRUE;
-      BOOL do_tls = FALSE;     /* Only set under SUPPORT_TLS */
-      void * tls_ctx = NULL;   /* ditto                      */
+      BOOL do_tls = FALSE;     /* Only set under ! DISABLE_TLS */
       blob reqstr;
 
       if (expand_forbid & RDO_READSOCK)
       blob reqstr;
 
       if (expand_forbid & RDO_READSOCK)
@@ -4947,7 +5076,7 @@ while (*s != 0)
        while ((item = string_nextinlist(&list, &sep, NULL, 0)))
          if (Ustrncmp(item, US"shutdown=", 9) == 0)
            { if (Ustrcmp(item + 9, US"no") == 0) do_shutdown = FALSE; }
        while ((item = string_nextinlist(&list, &sep, NULL, 0)))
          if (Ustrncmp(item, US"shutdown=", 9) == 0)
            { if (Ustrcmp(item + 9, US"no") == 0) do_shutdown = FALSE; }
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
          else if (Ustrncmp(item, US"tls=", 4) == 0)
            { if (Ustrcmp(item + 9, US"no") != 0) do_tls = TRUE; }
 #endif
          else if (Ustrncmp(item, US"tls=", 4) == 0)
            { if (Ustrcmp(item + 9, US"no") != 0) do_tls = TRUE; }
 #endif
@@ -5003,11 +5132,12 @@ while (*s != 0)
             port = ntohs(service_info->s_port);
             }
 
             port = ntohs(service_info->s_port);
             }
 
-         fd = ip_connectedsocket(SOCK_STREAM, server_name, port, port,
+         /*XXX we trust that the request is idempotent for TFO.  Hmm. */
+         cctx.sock = ip_connectedsocket(SOCK_STREAM, server_name, port, port,
                  timeout, &host, &expand_string_message,
                  do_tls ? NULL : &reqstr);
          callout_address = NULL;
                  timeout, &host, &expand_string_message,
                  do_tls ? NULL : &reqstr);
          callout_address = NULL;
-         if (fd < 0)
+         if (cctx.sock < 0)
            goto SOCK_FAIL;
          if (!do_tls)
            reqstr.len = 0;
            goto SOCK_FAIL;
          if (!do_tls)
            reqstr.len = 0;
@@ -5020,7 +5150,7 @@ while (*s != 0)
          struct sockaddr_un sockun;         /* don't call this "sun" ! */
           int rc;
 
          struct sockaddr_un sockun;         /* don't call this "sun" ! */
           int rc;
 
-          if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) == -1)
+          if ((cctx.sock = socket(PF_UNIX, SOCK_STREAM, 0)) == -1)
             {
             expand_string_message = string_sprintf("failed to create socket: %s",
               strerror(errno));
             {
             expand_string_message = string_sprintf("failed to create socket: %s",
               strerror(errno));
@@ -5033,9 +5163,9 @@ while (*s != 0)
          server_name = US sockun.sun_path;
 
           sigalrm_seen = FALSE;
          server_name = US sockun.sun_path;
 
           sigalrm_seen = FALSE;
-          alarm(timeout);
-          rc = connect(fd, (struct sockaddr *)(&sockun), sizeof(sockun));
-          alarm(0);
+          ALARM(timeout);
+          rc = connect(cctx.sock, (struct sockaddr *)(&sockun), sizeof(sockun));
+          ALARM_CLR(0);
           if (sigalrm_seen)
             {
             expand_string_message = US "socket connect timed out";
           if (sigalrm_seen)
             {
             expand_string_message = US "socket connect timed out";
@@ -5053,17 +5183,14 @@ while (*s != 0)
 
         DEBUG(D_expand) debug_printf_indent("connected to socket %s\n", sub_arg[0]);
 
 
         DEBUG(D_expand) debug_printf_indent("connected to socket %s\n", sub_arg[0]);
 
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
        if (do_tls)
          {
        if (do_tls)
          {
+         smtp_connect_args conn_args = {.host = &host };
          tls_support tls_dummy = {.sni=NULL};
          uschar * errstr;
 
          tls_support tls_dummy = {.sni=NULL};
          uschar * errstr;
 
-         if (!(tls_ctx = tls_client_start(fd, &host, NULL, NULL,
-# ifdef SUPPORT_DANE
-                               NULL,
-# endif
-                               &tls_dummy, &errstr)))
+         if (!tls_client_start(&cctx, &conn_args, NULL, &tls_dummy, &errstr))
            {
            expand_string_message = string_sprintf("TLS connect failed: %s", errstr);
            goto SOCK_FAIL;
            {
            expand_string_message = string_sprintf("TLS connect failed: %s", errstr);
            goto SOCK_FAIL;
@@ -5081,10 +5208,10 @@ while (*s != 0)
           DEBUG(D_expand) debug_printf_indent("writing \"%s\" to socket\n",
             reqstr.data);
           if ( (
           DEBUG(D_expand) debug_printf_indent("writing \"%s\" to socket\n",
             reqstr.data);
           if ( (
-#ifdef SUPPORT_TLS
-             tls_ctx ? tls_write(tls_ctx, reqstr.data, reqstr.len, FALSE) :
+#ifndef DISABLE_TLS
+             do_tls ? tls_write(cctx.tls_ctx, reqstr.data, reqstr.len, FALSE) :
 #endif
 #endif
-                       write(fd, reqstr.data, reqstr.len)) != reqstr.len)
+                       write(cctx.sock, reqstr.data, reqstr.len)) != reqstr.len)
             {
             expand_string_message = string_sprintf("request write to socket "
               "failed: %s", strerror(errno));
             {
             expand_string_message = string_sprintf("request write to socket "
               "failed: %s", strerror(errno));
@@ -5097,7 +5224,7 @@ while (*s != 0)
         system doesn't have this function, make it conditional. */
 
 #ifdef SHUT_WR
         system doesn't have this function, make it conditional. */
 
 #ifdef SHUT_WR
-       if (!tls_ctx && do_shutdown) shutdown(fd, SHUT_WR);
+       if (!do_tls && do_shutdown) shutdown(cctx.sock, SHUT_WR);
 #endif
 
        if (f.running_in_test_harness) millisleep(100);
 #endif
 
        if (f.running_in_test_harness) millisleep(100);
@@ -5105,22 +5232,22 @@ while (*s != 0)
         /* Now we need to read from the socket, under a timeout. The function
         that reads a file can be used. */
 
         /* Now we need to read from the socket, under a timeout. The function
         that reads a file can be used. */
 
-       if (!tls_ctx)
-         fp = fdopen(fd, "rb");
+       if (!do_tls)
+         fp = fdopen(cctx.sock, "rb");
         sigalrm_seen = FALSE;
         sigalrm_seen = FALSE;
-        alarm(timeout);
+        ALARM(timeout);
         yield =
         yield =
-#ifdef SUPPORT_TLS
-         tls_ctx ? cat_file_tls(tls_ctx, yield, sub_arg[3]) :
+#ifndef DISABLE_TLS
+         do_tls ? cat_file_tls(cctx.tls_ctx, yield, sub_arg[3]) :
 #endif
                    cat_file(fp, yield, sub_arg[3]);
 #endif
                    cat_file(fp, yield, sub_arg[3]);
-        alarm(0);
+        ALARM_CLR(0);
 
 
-#ifdef SUPPORT_TLS
-       if (tls_ctx)
+#ifndef DISABLE_TLS
+       if (do_tls)
          {
          {
-         tls_close(tls_ctx, TRUE);
-         close(fd);
+         tls_close(cctx.tls_ctx, TRUE);
+         close(cctx.sock);
          }
        else
 #endif
          }
        else
 #endif
@@ -5247,9 +5374,9 @@ while (*s != 0)
        resetok = FALSE;
         f = fdopen(fd_out, "rb");
         sigalrm_seen = FALSE;
        resetok = FALSE;
         f = fdopen(fd_out, "rb");
         sigalrm_seen = FALSE;
-        alarm(60);
+        ALARM(60);
        lookup_value = string_from_gstring(cat_file(f, NULL, NULL));
        lookup_value = string_from_gstring(cat_file(f, NULL, NULL));
-        alarm(0);
+        ALARM_CLR(0);
         (void)fclose(f);
 
         /* Wait for the process to finish, applying the timeout, and inspect its
         (void)fclose(f);
 
         /* Wait for the process to finish, applying the timeout, and inspect its
@@ -5260,7 +5387,7 @@ while (*s != 0)
           {
           if (sigalrm_seen || runrc == -256)
             {
           {
           if (sigalrm_seen || runrc == -256)
             {
-            expand_string_message = string_sprintf("command timed out");
+            expand_string_message = US"command timed out";
             killpg(pid, SIGKILL);       /* Kill the whole process group */
             }
 
             killpg(pid, SIGKILL);       /* Kill the whole process group */
             }
 
@@ -5333,7 +5460,6 @@ while (*s != 0)
     case EITEM_NHASH:
     case EITEM_SUBSTR:
       {
     case EITEM_NHASH:
     case EITEM_SUBSTR:
       {
-      int i;
       int len;
       uschar *ret;
       int val[2] = { 0, -1 };
       int len;
       uschar *ret;
       int val[2] = { 0, -1 };
@@ -5366,9 +5492,8 @@ while (*s != 0)
           }
         }
 
           }
         }
 
-      for (i = 0; i < 2; i++)
+      for (int i = 0; i < 2; i++) if (sub[i])
         {
         {
-        if (sub[i] == NULL) continue;
         val[i] = (int)Ustrtol(sub[i], &ret, 10);
         if (*ret != 0 || (i != 0 && val[i] < 0))
           {
         val[i] = (int)Ustrtol(sub[i], &ret, 10);
         if (*ret != 0 || (i != 0 && val[i] < 0))
           {
@@ -5406,7 +5531,7 @@ while (*s != 0)
       md5 md5_base;
       hctx sha1_ctx;
       void *use_base;
       md5 md5_base;
       hctx sha1_ctx;
       void *use_base;
-      int type, i;
+      int type;
       int hashlen;      /* Number of octets for the hash algorithm's output */
       int hashblocklen; /* Number of octets the hash algorithm processes */
       uschar *keyptr, *p;
       int hashlen;      /* Number of octets for the hash algorithm's output */
       int hashblocklen; /* Number of octets the hash algorithm processes */
       uschar *keyptr, *p;
@@ -5468,7 +5593,7 @@ while (*s != 0)
        memset(innerkey, 0x36, hashblocklen);
        memset(outerkey, 0x5c, hashblocklen);
 
        memset(innerkey, 0x36, hashblocklen);
        memset(outerkey, 0x5c, hashblocklen);
 
-       for (i = 0; i < keylen; i++)
+       for (int i = 0; i < keylen; i++)
          {
          innerkey[i] ^= keyptr[i];
          outerkey[i] ^= keyptr[i];
          {
          innerkey[i] ^= keyptr[i];
          outerkey[i] ^= keyptr[i];
@@ -5487,7 +5612,7 @@ while (*s != 0)
        /* Encode the final hash as a hex string */
 
        p = finalhash_hex;
        /* Encode the final hash as a hex string */
 
        p = finalhash_hex;
-       for (i = 0; i < hashlen; i++)
+       for (int i = 0; i < hashlen; i++)
          {
          *p++ = hex_digits[(finalhash[i] & 0xf0) >> 4];
          *p++ = hex_digits[finalhash[i] & 0x0f];
          {
          *p++ = hex_digits[(finalhash[i] & 0xf0) >> 4];
          *p++ = hex_digits[finalhash[i] & 0x0f];
@@ -5549,7 +5674,6 @@ while (*s != 0)
         int ovector[3*(EXPAND_MAXN+1)];
         int n = pcre_exec(re, NULL, CS subject, slen, moffset + moffsetextra,
           PCRE_EOPT | emptyopt, ovector, nelem(ovector));
         int ovector[3*(EXPAND_MAXN+1)];
         int n = pcre_exec(re, NULL, CS subject, slen, moffset + moffsetextra,
           PCRE_EOPT | emptyopt, ovector, nelem(ovector));
-        int nn;
         uschar *insert;
 
         /* No match - if we previously set PCRE_NOTEMPTY after a null match, this
         uschar *insert;
 
         /* No match - if we previously set PCRE_NOTEMPTY after a null match, this
@@ -5575,7 +5699,7 @@ while (*s != 0)
 
         if (n == 0) n = EXPAND_MAXN + 1;
         expand_nmax = 0;
 
         if (n == 0) n = EXPAND_MAXN + 1;
         expand_nmax = 0;
-        for (nn = 0; nn < n*2; nn += 2)
+        for (int nn = 0; nn < n*2; nn += 2)
           {
           expand_nstring[expand_nmax] = subject + ovector[nn];
           expand_nlength[expand_nmax++] = ovector[nn+1] - ovector[nn];
           {
           expand_nstring[expand_nmax] = subject + ovector[nn];
           expand_nlength[expand_nmax++] = ovector[nn+1] - ovector[nn];
@@ -5619,24 +5743,30 @@ while (*s != 0)
 
     case EITEM_EXTRACT:
       {
 
     case EITEM_EXTRACT:
       {
-      int i;
-      int j;
       int field_number = 1;
       BOOL field_number_set = FALSE;
       uschar *save_lookup_value = lookup_value;
       uschar *sub[3];
       int save_expand_nmax =
         save_expand_strings(save_expand_nstring, save_expand_nlength);
       int field_number = 1;
       BOOL field_number_set = FALSE;
       uschar *save_lookup_value = lookup_value;
       uschar *sub[3];
       int save_expand_nmax =
         save_expand_strings(save_expand_nstring, save_expand_nlength);
-      enum {extract_basic, extract_json} fmt = extract_basic;
+
+      /* On reflection the original behaviour of extract-json for a string
+      result, leaving it quoted, was a mistake.  But it was already published,
+      hence the addition of jsons.  In a future major version, make json
+      work like josons, and withdraw jsons. */
+
+      enum {extract_basic, extract_json, extract_jsons} fmt = extract_basic;
 
       while (isspace(*s)) s++;
 
       /* Check for a format-variant specifier */
 
       if (*s != '{')                                   /*}*/
 
       while (isspace(*s)) s++;
 
       /* Check for a format-variant specifier */
 
       if (*s != '{')                                   /*}*/
-       {
-       if (Ustrncmp(s, "json", 4) == 0) {fmt = extract_json; s += 4;}
-       }
+       if (Ustrncmp(s, "json", 4) == 0)
+         if (*(s += 4) == 's')
+           {fmt = extract_jsons; s++;}
+         else
+           fmt = extract_json;
 
       /* While skipping we cannot rely on the data for expansions being
       available (eg. $item) hence cannot decide on numeric vs. keyed.
 
       /* While skipping we cannot rely on the data for expansions being
       available (eg. $item) hence cannot decide on numeric vs. keyed.
@@ -5644,7 +5774,7 @@ while (*s != 0)
 
       if (skipping)
        {
 
       if (skipping)
        {
-        for (j = 5; j > 0 && *s == '{'; j--)                   /*'}'*/
+        for (int j = 5; j > 0 && *s == '{'; j--)               /*'}'*/
          {
           if (!expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok))
            goto EXPAND_FAILED;                                 /*'{'*/
          {
           if (!expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok))
            goto EXPAND_FAILED;                                 /*'{'*/
@@ -5669,7 +5799,7 @@ while (*s != 0)
          }
        }
 
          }
        }
 
-      else for (i = 0, j = 2; i < j; i++) /* Read the proper number of arguments */
+      else for (int i = 0, j = 2; i < j; i++) /* Read the proper number of arguments */
         {
        while (isspace(*s)) s++;
         if (*s == '{')                                                 /*'}'*/
         {
        while (isspace(*s)) s++;
         if (*s == '{')                                                 /*'}'*/
@@ -5717,7 +5847,7 @@ while (*s != 0)
            if (*p == 0)
              {
              field_number *= x;
            if (*p == 0)
              {
              field_number *= x;
-             if (fmt != extract_json) j = 3;               /* Need 3 args */
+             if (fmt == extract_basic) j = 3;               /* Need 3 args */
              field_number_set = TRUE;
              }
             }
              field_number_set = TRUE;
              }
             }
@@ -5744,6 +5874,7 @@ while (*s != 0)
          break;
 
        case extract_json:
          break;
 
        case extract_json:
+       case extract_jsons:
          {
          uschar * s, * item;
          const uschar * list;
          {
          uschar * s, * item;
          const uschar * list;
@@ -5771,10 +5902,11 @@ while (*s != 0)
              }
            while (field_number > 0 && (item = json_nextinlist(&list)))
              field_number--;
              }
            while (field_number > 0 && (item = json_nextinlist(&list)))
              field_number--;
-           s = item;
-           lookup_value = s;
-           while (*s) s++;
-           while (--s >= lookup_value && isspace(*s)) *s = '\0';
+           if ((lookup_value = s = item))
+             {
+             while (*s) s++;
+             while (--s >= lookup_value && isspace(*s)) *s = '\0';
+             }
            }
          else
            {
            }
          else
            {
@@ -5793,12 +5925,12 @@ while (*s != 0)
                }
              if (Ustrcmp(item, sub[0]) == 0)   /*XXX should be a UTF8-compare */
                {
                }
              if (Ustrcmp(item, sub[0]) == 0)   /*XXX should be a UTF8-compare */
                {
-               s = item + strlen(item) + 1;
+               s = item + Ustrlen(item) + 1;
                while (isspace(*s)) s++;
                if (*s != ':')
                  {
                while (isspace(*s)) s++;
                if (*s != ':')
                  {
-                 expand_string_message = string_sprintf(
-                   "missing object value-separator for extract json");
+                 expand_string_message =
+                   US"missing object value-separator for extract json";
                  goto EXPAND_FAILED_CURLY;
                  }
                s++;
                  goto EXPAND_FAILED_CURLY;
                  }
                s++;
@@ -5809,6 +5941,17 @@ while (*s != 0)
              }
            }
          }
              }
            }
          }
+
+         if (  fmt == extract_jsons
+            && lookup_value
+            && !(lookup_value = dewrap(lookup_value, US"\"\"")))
+           {
+           expand_string_message =
+             string_sprintf("%s wrapping string result for extract jsons",
+               expand_string_message);
+           goto EXPAND_FAILED_CURLY;
+           }
+         break;        /* json/s */
        }
 
       /* If no string follows, $value gets substituted; otherwise there can
        }
 
       /* If no string follows, $value gets substituted; otherwise there can
@@ -5839,7 +5982,6 @@ while (*s != 0)
 
     case EITEM_LISTEXTRACT:
       {
 
     case EITEM_LISTEXTRACT:
       {
-      int i;
       int field_number = 1;
       uschar *save_lookup_value = lookup_value;
       uschar *sub[2];
       int field_number = 1;
       uschar *save_lookup_value = lookup_value;
       uschar *sub[2];
@@ -5848,10 +5990,10 @@ while (*s != 0)
 
       /* Read the field & list arguments */
 
 
       /* Read the field & list arguments */
 
-      for (i = 0; i < 2; i++)
+      for (int i = 0; i < 2; i++)
         {
         while (isspace(*s)) s++;
         {
         while (isspace(*s)) s++;
-        if (*s != '{')                                 /*}*/
+        if (*s != '{')                                 /*'}'*/
          {
          expand_string_message = string_sprintf(
            "missing '{' for arg %d of listextract", i+1);
          {
          expand_string_message = string_sprintf(
            "missing '{' for arg %d of listextract", i+1);
@@ -5935,7 +6077,7 @@ while (*s != 0)
       continue;
       }
 
       continue;
       }
 
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
     case EITEM_CERTEXTRACT:
       {
       uschar *save_lookup_value = lookup_value;
     case EITEM_CERTEXTRACT:
       {
       uschar *save_lookup_value = lookup_value;
@@ -6015,7 +6157,7 @@ while (*s != 0)
         save_expand_nlength);
       continue;
       }
         save_expand_nlength);
       continue;
       }
-#endif /*SUPPORT_TLS*/
+#endif /*DISABLE_TLS*/
 
     /* Handle list operations */
 
 
     /* Handle list operations */
 
@@ -6221,9 +6363,10 @@ while (*s != 0)
 
     case EITEM_SORT:
       {
 
     case EITEM_SORT:
       {
+      int cond_type;
       int sep = 0;
       const uschar *srclist, *cmp, *xtract;
       int sep = 0;
       const uschar *srclist, *cmp, *xtract;
-      uschar *srcitem;
+      uschar * opname, * srcitem;
       const uschar *dstlist = NULL, *dstkeylist = NULL;
       uschar * tmp;
       uschar *save_iterate_item = iterate_item;
       const uschar *dstlist = NULL, *dstkeylist = NULL;
       uschar * tmp;
       uschar *save_iterate_item = iterate_item;
@@ -6258,6 +6401,25 @@ while (*s != 0)
        goto EXPAND_FAILED_CURLY;
        }
 
        goto EXPAND_FAILED_CURLY;
        }
 
+      if ((cond_type = identify_operator(&cmp, &opname)) == -1)
+       {
+       if (!expand_string_message)
+         expand_string_message = string_sprintf("unknown condition \"%s\"", s);
+       goto EXPAND_FAILED;
+       }
+      switch(cond_type)
+       {
+       case ECOND_NUM_L: case ECOND_NUM_LE:
+       case ECOND_NUM_G: case ECOND_NUM_GE:
+       case ECOND_STR_GE: case ECOND_STR_GEI: case ECOND_STR_GT: case ECOND_STR_GTI:
+       case ECOND_STR_LE: case ECOND_STR_LEI: case ECOND_STR_LT: case ECOND_STR_LTI:
+         break;
+
+       default:
+         expand_string_message = US"comparator not handled for sort";
+         goto EXPAND_FAILED;
+       }
+
       while (isspace(*s)) s++;
       if (*s++ != '{')
         {
       while (isspace(*s)) s++;
       if (*s++ != '{')
         {
@@ -6266,8 +6428,8 @@ while (*s != 0)
        }
 
       xtract = s;
        }
 
       xtract = s;
-      tmp = expand_string_internal(s, TRUE, &s, TRUE, TRUE, &resetok);
-      if (!tmp) goto EXPAND_FAILED;
+      if (!(tmp = expand_string_internal(s, TRUE, &s, TRUE, TRUE, &resetok)))
+       goto EXPAND_FAILED;
       xtract = string_copyn(xtract, s - xtract);
 
       if (*s++ != '}')
       xtract = string_copyn(xtract, s - xtract);
 
       if (*s++ != '}')
@@ -6285,11 +6447,10 @@ while (*s != 0)
       if (skipping) continue;
 
       while ((srcitem = string_nextinlist(&srclist, &sep, NULL, 0)))
       if (skipping) continue;
 
       while ((srcitem = string_nextinlist(&srclist, &sep, NULL, 0)))
-        {
-       uschar * dstitem;
+       {
+       uschar * srcfield, * dstitem;
        gstring * newlist = NULL;
        gstring * newkeylist = NULL;
        gstring * newlist = NULL;
        gstring * newkeylist = NULL;
-       uschar * srcfield;
 
         DEBUG(D_expand) debug_printf_indent("%s: $item = \"%s\"\n", name, srcitem);
 
 
         DEBUG(D_expand) debug_printf_indent("%s: $item = \"%s\"\n", name, srcitem);
 
@@ -6310,25 +6471,15 @@ while (*s != 0)
        while ((dstitem = string_nextinlist(&dstlist, &sep, NULL, 0)))
          {
          uschar * dstfield;
        while ((dstitem = string_nextinlist(&dstlist, &sep, NULL, 0)))
          {
          uschar * dstfield;
-         uschar * expr;
-         BOOL before;
 
          /* field for comparison */
          if (!(dstfield = string_nextinlist(&dstkeylist, &sep, NULL, 0)))
            goto sort_mismatch;
 
 
          /* field for comparison */
          if (!(dstfield = string_nextinlist(&dstkeylist, &sep, NULL, 0)))
            goto sort_mismatch;
 
-         /* build and run condition string */
-         expr = string_sprintf("%s{%s}{%s}", cmp, srcfield, dstfield);
-
-         DEBUG(D_expand) debug_printf_indent("%s: cond = \"%s\"\n", name, expr);
-         if (!eval_condition(expr, &resetok, &before))
-           {
-           expand_string_message = string_sprintf("comparison in sort: %s",
-               expr);
-           goto EXPAND_FAILED;
-           }
+         /* String-comparator names start with a letter; numeric names do not */
 
 
-         if (before)
+         if (sortsbefore(cond_type, isalpha(opname[0]),
+             srcfield, dstfield))
            {
            /* New-item sorts before this dst-item.  Append new-item,
            then dst-item, then remainder of dst list. */
            {
            /* New-item sorts before this dst-item.  Append new-item,
            then dst-item, then remainder of dst list. */
@@ -6340,6 +6491,7 @@ while (*s != 0)
            newlist = string_append_listele(newlist, sep, dstitem);
            newkeylist = string_append_listele(newkeylist, sep, dstfield);
 
            newlist = string_append_listele(newlist, sep, dstitem);
            newkeylist = string_append_listele(newkeylist, sep, dstfield);
 
+/*XXX why field-at-a-time copy?  Why not just dup the rest of the list? */
            while ((dstitem = string_nextinlist(&dstlist, &sep, NULL, 0)))
              {
              if (!(dstfield = string_nextinlist(&dstkeylist, &sep, NULL, 0)))
            while ((dstitem = string_nextinlist(&dstlist, &sep, NULL, 0)))
              {
              if (!(dstfield = string_nextinlist(&dstkeylist, &sep, NULL, 0)))
@@ -6425,8 +6577,7 @@ while (*s != 0)
       /* Look up the dynamically loaded object handle in the tree. If it isn't
       found, dlopen() the file and put the handle in the tree for next time. */
 
       /* Look up the dynamically loaded object handle in the tree. If it isn't
       found, dlopen() the file and put the handle in the tree for next time. */
 
-      t = tree_search(dlobj_anchor, argv[0]);
-      if (t == NULL)
+      if (!(t = tree_search(dlobj_anchor, argv[0])))
         {
         void *handle = dlopen(CS argv[0], RTLD_LAZY);
         if (handle == NULL)
         {
         void *handle = dlopen(CS argv[0], RTLD_LAZY);
         if (handle == NULL)
@@ -6436,7 +6587,7 @@ while (*s != 0)
           log_write(0, LOG_MAIN|LOG_PANIC, "%s", expand_string_message);
           goto EXPAND_FAILED;
           }
           log_write(0, LOG_MAIN|LOG_PANIC, "%s", expand_string_message);
           goto EXPAND_FAILED;
           }
-        t = store_get_perm(sizeof(tree_node) + Ustrlen(argv[0]));
+        t = store_get_perm(sizeof(tree_node) + Ustrlen(argv[0]), is_tainted(argv[0]));
         Ustrcpy(t->name, argv[0]);
         t->data.ptr = handle;
         (void)tree_insertnode(&dlobj_anchor, t);
         Ustrcpy(t->name, argv[0]);
         t->data.ptr = handle;
         (void)tree_insertnode(&dlobj_anchor, t);
@@ -6528,7 +6679,7 @@ while (*s != 0)
     int c;
     uschar *arg = NULL;
     uschar *sub;
     int c;
     uschar *arg = NULL;
     uschar *sub;
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
     var_entry *vp = NULL;
 #endif
 
     var_entry *vp = NULL;
 #endif
 
@@ -6551,7 +6702,7 @@ while (*s != 0)
     as we do not want to do the usual expansion. For most, expand the string.*/
     switch(c)
       {
     as we do not want to do the usual expansion. For most, expand the string.*/
     switch(c)
       {
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
       case EOP_MD5:
       case EOP_SHA1:
       case EOP_SHA256:
       case EOP_MD5:
       case EOP_SHA1:
       case EOP_SHA256:
@@ -6655,7 +6806,6 @@ while (*s != 0)
 
       case EOP_BASE62D:
         {
 
       case EOP_BASE62D:
         {
-        uschar buf[16];
         uschar *tt = sub;
         unsigned long int n = 0;
         while (*tt != 0)
         uschar *tt = sub;
         unsigned long int n = 0;
         while (*tt != 0)
@@ -6670,8 +6820,7 @@ while (*s != 0)
             }
           n = n * BASE_62 + (t - base62_chars);
           }
             }
           n = n * BASE_62 + (t - base62_chars);
           }
-        (void)sprintf(CS buf, "%ld", n);
-        yield = string_cat(yield, buf);
+        yield = string_fmt_append(yield, "%ld", n);
         continue;
         }
 
         continue;
         }
 
@@ -6708,7 +6857,7 @@ while (*s != 0)
         }
 
       case EOP_MD5:
         }
 
       case EOP_MD5:
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
        if (vp && *(void **)vp->value)
          {
          uschar * cp = tls_cert_fprt_md5(*(void **)vp->value);
        if (vp && *(void **)vp->value)
          {
          uschar * cp = tls_cert_fprt_md5(*(void **)vp->value);
@@ -6719,17 +6868,15 @@ while (*s != 0)
          {
          md5 base;
          uschar digest[16];
          {
          md5 base;
          uschar digest[16];
-         int j;
-         char st[33];
          md5_start(&base);
          md5_end(&base, sub, Ustrlen(sub), digest);
          md5_start(&base);
          md5_end(&base, sub, Ustrlen(sub), digest);
-         for(j = 0; j < 16; j++) sprintf(st+2*j, "%02x", digest[j]);
-         yield = string_cat(yield, US st);
+         for (int j = 0; j < 16; j++)
+           yield = string_fmt_append(yield, "%02x", digest[j]);
          }
         continue;
 
       case EOP_SHA1:
          }
         continue;
 
       case EOP_SHA1:
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
        if (vp && *(void **)vp->value)
          {
          uschar * cp = tls_cert_fprt_sha1(*(void **)vp->value);
        if (vp && *(void **)vp->value)
          {
          uschar * cp = tls_cert_fprt_sha1(*(void **)vp->value);
@@ -6740,40 +6887,41 @@ while (*s != 0)
          {
          hctx h;
          uschar digest[20];
          {
          hctx h;
          uschar digest[20];
-         int j;
-         char st[41];
          sha1_start(&h);
          sha1_end(&h, sub, Ustrlen(sub), digest);
          sha1_start(&h);
          sha1_end(&h, sub, Ustrlen(sub), digest);
-         for(j = 0; j < 20; j++) sprintf(st+2*j, "%02X", digest[j]);
-         yield = string_catn(yield, US st, 40);
+         for (int j = 0; j < 20; j++)
+           yield = string_fmt_append(yield, "%02X", digest[j]);
          }
         continue;
 
          }
         continue;
 
+      case EOP_SHA2:
       case EOP_SHA256:
 #ifdef EXIM_HAVE_SHA2
        if (vp && *(void **)vp->value)
       case EOP_SHA256:
 #ifdef EXIM_HAVE_SHA2
        if (vp && *(void **)vp->value)
-         {
-         uschar * cp = tls_cert_fprt_sha256(*(void **)vp->value);
-         yield = string_cat(yield, cp);
-         }
+         if (c == EOP_SHA256)
+           yield = string_cat(yield, tls_cert_fprt_sha256(*(void **)vp->value));
+         else
+           expand_string_message = US"sha2_N not supported with certificates";
        else
          {
          hctx h;
          blob b;
        else
          {
          hctx h;
          blob b;
-         char st[3];
+         hashmethod m = !arg ? HASH_SHA2_256
+           : Ustrcmp(arg, "256") == 0 ? HASH_SHA2_256
+           : Ustrcmp(arg, "384") == 0 ? HASH_SHA2_384
+           : Ustrcmp(arg, "512") == 0 ? HASH_SHA2_512
+           : HASH_BADTYPE;
 
 
-         if (!exim_sha_init(&h, HASH_SHA2_256))
+         if (m == HASH_BADTYPE || !exim_sha_init(&h, m))
            {
            {
-           expand_string_message = US"unrecognised sha256 variant";
+           expand_string_message = US"unrecognised sha2 variant";
            goto EXPAND_FAILED;
            }
            goto EXPAND_FAILED;
            }
+
          exim_sha_update(&h, sub, Ustrlen(sub));
          exim_sha_finish(&h, &b);
          while (b.len-- > 0)
          exim_sha_update(&h, sub, Ustrlen(sub));
          exim_sha_finish(&h, &b);
          while (b.len-- > 0)
-           {
-           sprintf(st, "%02X", *b.data++);
-           yield = string_catn(yield, US st, 2);
-           }
+           yield = string_fmt_append(yield, "%02X", *b.data++);
          }
 #else
          expand_string_message = US"sha256 only supported with TLS";
          }
 #else
          expand_string_message = US"sha256 only supported with TLS";
@@ -6785,7 +6933,6 @@ while (*s != 0)
        {
        hctx h;
        blob b;
        {
        hctx h;
        blob b;
-       char st[3];
        hashmethod m = !arg ? HASH_SHA3_256
          : Ustrcmp(arg, "224") == 0 ? HASH_SHA3_224
          : Ustrcmp(arg, "256") == 0 ? HASH_SHA3_256
        hashmethod m = !arg ? HASH_SHA3_256
          : Ustrcmp(arg, "224") == 0 ? HASH_SHA3_224
          : Ustrcmp(arg, "256") == 0 ? HASH_SHA3_256
@@ -6802,10 +6949,7 @@ while (*s != 0)
        exim_sha_update(&h, sub, Ustrlen(sub));
        exim_sha_finish(&h, &b);
        while (b.len-- > 0)
        exim_sha_update(&h, sub, Ustrlen(sub));
        exim_sha_finish(&h, &b);
        while (b.len-- > 0)
-         {
-         sprintf(st, "%02X", *b.data++);
-         yield = string_catn(yield, US st, 2);
-         }
+         yield = string_fmt_append(yield, "%02X", *b.data++);
        }
         continue;
 #else
        }
         continue;
 #else
@@ -6823,7 +6967,7 @@ while (*s != 0)
         uschar *out = sub;
         uschar *enc;
 
         uschar *out = sub;
         uschar *enc;
 
-        for (enc = sub; *enc != 0; enc++)
+        for (enc = sub; *enc; enc++)
           {
           if (!isxdigit(*enc))
             {
           {
           if (!isxdigit(*enc))
             {
@@ -6846,9 +6990,7 @@ while (*s != 0)
           if (isdigit(c)) c -= '0';
           else c = toupper(c) - 'A' + 10;
           if (b == -1)
           if (isdigit(c)) c -= '0';
           else c = toupper(c) - 'A' + 10;
           if (b == -1)
-            {
             b = c << 4;
             b = c << 4;
-            }
           else
             {
             *out++ = b | c;
           else
             {
             *out++ = b | c;
@@ -6856,7 +6998,7 @@ while (*s != 0)
             }
           }
 
             }
           }
 
-        enc = b64encode(sub, out - sub);
+        enc = b64encode(CUS sub, out - sub);
         yield = string_cat(yield, enc);
         continue;
         }
         yield = string_cat(yield, enc);
         continue;
         }
@@ -6869,7 +7011,7 @@ while (*s != 0)
         while (*(++t) != 0)
           {
           if (*t < 0x21 || 0x7E < *t)
         while (*(++t) != 0)
           {
           if (*t < 0x21 || 0x7E < *t)
-            yield = string_catn(yield, string_sprintf("\\x%02x", *t), 4);
+            yield = string_fmt_append(yield, "\\x%02x", *t);
          else
            yield = string_catn(yield, t, 1);
           }
          else
            yield = string_catn(yield, t, 1);
           }
@@ -6882,12 +7024,10 @@ while (*s != 0)
         {
        int cnt = 0;
        int sep = 0;
         {
        int cnt = 0;
        int sep = 0;
-       uschar * cp;
        uschar buffer[256];
 
        while (string_nextinlist(CUSS &sub, &sep, buffer, sizeof(buffer)) != NULL) cnt++;
        uschar buffer[256];
 
        while (string_nextinlist(CUSS &sub, &sep, buffer, sizeof(buffer)) != NULL) cnt++;
-       cp = string_sprintf("%d", cnt);
-        yield = string_cat(yield, cp);
+       yield = string_fmt_append(yield, "%d", cnt);
         continue;
         }
 
         continue;
         }
 
@@ -6919,7 +7059,7 @@ while (*s != 0)
          case 'h': t = tree_search(hostlist_anchor,      sub); suffix = US"_h"; break;
          case 'l': t = tree_search(localpartlist_anchor, sub); suffix = US"_l"; break;
          default:
          case 'h': t = tree_search(hostlist_anchor,      sub); suffix = US"_h"; break;
          case 'l': t = tree_search(localpartlist_anchor, sub); suffix = US"_l"; break;
          default:
-            expand_string_message = string_sprintf("bad suffix on \"list\" operator");
+            expand_string_message = US"bad suffix on \"list\" operator";
            goto EXPAND_FAILED;
          }
 
            goto EXPAND_FAILED;
          }
 
@@ -7065,16 +7205,11 @@ while (*s != 0)
         uschar * t = parse_extract_address(sub, &error, &start, &end, &domain,
           FALSE);
         if (t)
         uschar * t = parse_extract_address(sub, &error, &start, &end, &domain,
           FALSE);
         if (t)
-          if (c != EOP_DOMAIN)
-            {
-            if (c == EOP_LOCAL_PART && domain != 0) end = start + domain - 1;
-            yield = string_catn(yield, sub+start, end-start);
-            }
-          else if (domain != 0)
-            {
-            domain += start;
-            yield = string_catn(yield, sub+domain, end-domain);
-            }
+         yield = c == EOP_DOMAIN
+           ? string_cat(yield, t + domain)
+           : c == EOP_LOCAL_PART && domain > 0
+           ? string_catn(yield, t, domain - 1 )
+           : string_cat(yield, t);
         continue;
         }
 
         continue;
         }
 
@@ -7098,7 +7233,7 @@ while (*s != 0)
 
         for (;;)
           {
 
         for (;;)
           {
-          uschar *p = parse_find_address_end(sub, FALSE);
+          uschar * p = parse_find_address_end(sub, FALSE);
           uschar saveend = *p;
           *p = '\0';
           address = parse_extract_address(sub, &error, &start, &end, &domain,
           uschar saveend = *p;
           *p = '\0';
           address = parse_extract_address(sub, &error, &start, &end, &domain,
@@ -7111,7 +7246,7 @@ while (*s != 0)
           list, add in a space if the new address begins with the separator
           character, or is an empty string. */
 
           list, add in a space if the new address begins with the separator
           character, or is an empty string. */
 
-          if (address != NULL)
+          if (address)
             {
             if (yield->ptr != save_ptr && address[0] == *outsep)
               yield = string_catn(yield, US" ", 1);
             {
             if (yield->ptr != save_ptr && address[0] == *outsep)
               yield = string_catn(yield, US" ", 1);
@@ -7253,9 +7388,9 @@ while (*s != 0)
       case EOP_RFC2047:
         {
         uschar buffer[2048];
       case EOP_RFC2047:
         {
         uschar buffer[2048];
-       const uschar *string = parse_quote_2047(sub, Ustrlen(sub), headers_charset,
-          buffer, sizeof(buffer), FALSE);
-        yield = string_cat(yield, string);
+        yield = string_cat(yield,
+                           parse_quote_2047(sub, Ustrlen(sub), headers_charset,
+                             buffer, sizeof(buffer), FALSE));
         continue;
         }
 
         continue;
         }
 
@@ -7456,13 +7591,12 @@ while (*s != 0)
 
       case EOP_ESCAPE8BIT:
        {
 
       case EOP_ESCAPE8BIT:
        {
-       const uschar * s = sub;
        uschar c;
 
        uschar c;
 
-       for (s = sub; (c = *s); s++)
+       for (const uschar * s = sub; (c = *s); s++)
          yield = c < 127 && c != '\\'
            ? string_catn(yield, s, 1)
          yield = c < 127 && c != '\\'
            ? string_catn(yield, s, 1)
-           : string_catn(yield, string_sprintf("\\%03o", c), 4);
+           : string_fmt_append(yield, "\\%03o", c);
        continue;
        }
 
        continue;
        }
 
@@ -7474,19 +7608,18 @@ while (*s != 0)
         uschar *save_sub = sub;
         uschar *error = NULL;
         int_eximarith_t n = eval_expr(&sub, (c == EOP_EVAL10), &error, FALSE);
         uschar *save_sub = sub;
         uschar *error = NULL;
         int_eximarith_t n = eval_expr(&sub, (c == EOP_EVAL10), &error, FALSE);
-        if (error != NULL)
+        if (error)
           {
           expand_string_message = string_sprintf("error in expression "
             "evaluation: %s (after processing \"%.*s\")", error,
            (int)(sub-save_sub), save_sub);
           goto EXPAND_FAILED;
           }
           {
           expand_string_message = string_sprintf("error in expression "
             "evaluation: %s (after processing \"%.*s\")", error,
            (int)(sub-save_sub), save_sub);
           goto EXPAND_FAILED;
           }
-        sprintf(CS var_buffer, PR_EXIM_ARITH, n);
-        yield = string_cat(yield, var_buffer);
+        yield = string_fmt_append(yield, PR_EXIM_ARITH, n);
         continue;
         }
 
         continue;
         }
 
-      /* Handle time period formating */
+      /* Handle time period formatting */
 
       case EOP_TIME_EVAL:
         {
 
       case EOP_TIME_EVAL:
         {
@@ -7497,8 +7630,7 @@ while (*s != 0)
             "Exim time interval in \"%s\" operator", sub, name);
           goto EXPAND_FAILED;
           }
             "Exim time interval in \"%s\" operator", sub, name);
           goto EXPAND_FAILED;
           }
-        sprintf(CS var_buffer, "%d", n);
-        yield = string_cat(yield, var_buffer);
+        yield = string_fmt_append(yield, "%d", n);
         continue;
         }
 
         continue;
         }
 
@@ -7522,12 +7654,12 @@ while (*s != 0)
       case EOP_STR2B64:
       case EOP_BASE64:
        {
       case EOP_STR2B64:
       case EOP_BASE64:
        {
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
        uschar * s = vp && *(void **)vp->value
          ? tls_cert_der_b64(*(void **)vp->value)
        uschar * s = vp && *(void **)vp->value
          ? tls_cert_der_b64(*(void **)vp->value)
-         : b64encode(sub, Ustrlen(sub));
+         : b64encode(CUS sub, Ustrlen(sub));
 #else
 #else
-       uschar * s = b64encode(sub, Ustrlen(sub));
+       uschar * s = b64encode(CUS sub, Ustrlen(sub));
 #endif
        yield = string_cat(yield, s);
        continue;
 #endif
        yield = string_cat(yield, s);
        continue;
@@ -7550,12 +7682,8 @@ while (*s != 0)
       /* strlen returns the length of the string */
 
       case EOP_STRLEN:
       /* strlen returns the length of the string */
 
       case EOP_STRLEN:
-        {
-        uschar buff[24];
-        (void)sprintf(CS buff, "%d", Ustrlen(sub));
-        yield = string_cat(yield, buff);
+        yield = string_fmt_append(yield, "%d", Ustrlen(sub));
         continue;
         continue;
-        }
 
       /* length_n or l_n takes just the first n characters or the whole string,
       whichever is the shorter;
 
       /* length_n or l_n takes just the first n characters or the whole string,
       whichever is the shorter;
@@ -7588,7 +7716,7 @@ while (*s != 0)
         int len;
         uschar *ret;
 
         int len;
         uschar *ret;
 
-        if (arg == NULL)
+        if (!arg)
           {
           expand_string_message = string_sprintf("missing values after %s",
             name);
           {
           expand_string_message = string_sprintf("missing values after %s",
             name);
@@ -7652,14 +7780,12 @@ while (*s != 0)
 
       case EOP_STAT:
         {
 
       case EOP_STAT:
         {
-        uschar *s;
         uschar smode[12];
         uschar **modetable[3];
         uschar smode[12];
         uschar **modetable[3];
-        int i;
         mode_t mode;
         struct stat st;
 
         mode_t mode;
         struct stat st;
 
-        if ((expand_forbid & RDO_EXISTS) != 0)
+        if (expand_forbid & RDO_EXISTS)
           {
           expand_string_message = US"Use of the stat() expansion is not permitted";
           goto EXPAND_FAILED;
           {
           expand_string_message = US"Use of the stat() expansion is not permitted";
           goto EXPAND_FAILED;
@@ -7686,20 +7812,20 @@ while (*s != 0)
         modetable[1] = ((mode & 02000) == 0)? mtable_normal : mtable_setid;
         modetable[2] = ((mode & 04000) == 0)? mtable_normal : mtable_setid;
 
         modetable[1] = ((mode & 02000) == 0)? mtable_normal : mtable_setid;
         modetable[2] = ((mode & 04000) == 0)? mtable_normal : mtable_setid;
 
-        for (i = 0; i < 3; i++)
+        for (int i = 0; i < 3; i++)
           {
           memcpy(CS(smode + 7 - i*3), CS(modetable[i][mode & 7]), 3);
           mode >>= 3;
           }
 
         smode[10] = 0;
           {
           memcpy(CS(smode + 7 - i*3), CS(modetable[i][mode & 7]), 3);
           mode >>= 3;
           }
 
         smode[10] = 0;
-        s = string_sprintf("mode=%04lo smode=%s inode=%ld device=%ld links=%ld "
+        yield = string_fmt_append(yield,
+         "mode=%04lo smode=%s inode=%ld device=%ld links=%ld "
           "uid=%ld gid=%ld size=" OFF_T_FMT " atime=%ld mtime=%ld ctime=%ld",
           (long)(st.st_mode & 077777), smode, (long)st.st_ino,
           (long)st.st_dev, (long)st.st_nlink, (long)st.st_uid,
           (long)st.st_gid, st.st_size, (long)st.st_atime,
           (long)st.st_mtime, (long)st.st_ctime);
           "uid=%ld gid=%ld size=" OFF_T_FMT " atime=%ld mtime=%ld ctime=%ld",
           (long)(st.st_mode & 077777), smode, (long)st.st_ino,
           (long)st.st_dev, (long)st.st_nlink, (long)st.st_uid,
           (long)st.st_gid, st.st_size, (long)st.st_atime,
           (long)st.st_mtime, (long)st.st_ctime);
-        yield = string_cat(yield, s);
         continue;
         }
 
         continue;
         }
 
@@ -7707,14 +7833,11 @@ while (*s != 0)
 
       case EOP_RANDINT:
         {
 
       case EOP_RANDINT:
         {
-        int_eximarith_t max;
-        uschar *s;
+        int_eximarith_t max = expanded_string_integer(sub, TRUE);
 
 
-        max = expanded_string_integer(sub, TRUE);
-        if (expand_string_message != NULL)
+        if (expand_string_message)
           goto EXPAND_FAILED;
           goto EXPAND_FAILED;
-        s = string_sprintf("%d", vaguely_random_number((int)max));
-        yield = string_cat(yield, s);
+        yield = string_fmt_append(yield, "%d", vaguely_random_number((int)max));
         continue;
         }
 
         continue;
         }
 
@@ -7740,9 +7863,9 @@ while (*s != 0)
       /* Unknown operator */
 
       default:
       /* Unknown operator */
 
       default:
-      expand_string_message =
-        string_sprintf("unknown expansion operator \"%s\"", name);
-      goto EXPAND_FAILED;
+       expand_string_message =
+         string_sprintf("unknown expansion operator \"%s\"", name);
+       goto EXPAND_FAILED;
       }
     }
 
       }
     }
 
@@ -7760,12 +7883,13 @@ while (*s != 0)
     gstring * g = NULL;
 
     if (!yield)
     gstring * g = NULL;
 
     if (!yield)
-      g = store_get(sizeof(gstring));
+      g = store_get(sizeof(gstring), FALSE);
     else if (yield->ptr == 0)
       {
     else if (yield->ptr == 0)
       {
-      if (resetok) store_reset(yield);
+      if (resetok) reset_point = store_reset(reset_point);
       yield = NULL;
       yield = NULL;
-      g = store_get(sizeof(gstring));  /* alloc _before_ calling find_variable() */
+      reset_point = store_mark();
+      g = store_get(sizeof(gstring), FALSE);   /* alloc _before_ calling find_variable() */
       }
     if (!(value = find_variable(name, FALSE, skipping, &newsize)))
       {
       }
     if (!(value = find_variable(name, FALSE, skipping, &newsize)))
       {
@@ -7819,22 +7943,40 @@ if (left) *left = s;
 In many cases the final string will be the first one that was got and so there
 will be optimal store usage. */
 
 In many cases the final string will be the first one that was got and so there
 will be optimal store usage. */
 
-if (resetok) store_reset(yield->s + (yield->size = yield->ptr + 1));
+if (resetok) gstring_release_unused(yield);
 else if (resetok_p) *resetok_p = FALSE;
 
 DEBUG(D_expand)
   {
 else if (resetok_p) *resetok_p = FALSE;
 
 DEBUG(D_expand)
   {
-  debug_printf_indent(UTF8_VERT_RIGHT UTF8_HORIZ UTF8_HORIZ
-    "expanding: %.*s\n",
-    (int)(s - string), string);
-  debug_printf_indent("%s"
-    UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ
-    "result: %s\n",
-    skipping ? UTF8_VERT_RIGHT : UTF8_UP_RIGHT,
-    yield->s);
-  if (skipping)
-    debug_printf_indent(UTF8_UP_RIGHT UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ
-      "skipping: result is not used\n");
+  BOOL tainted = is_tainted(yield->s);
+  DEBUG(D_noutf8)
+    {
+    debug_printf_indent("|--expanding: %.*s\n", (int)(s - string), string);
+    debug_printf_indent("%sresult: %s\n",
+      skipping ? "|-----" : "\\_____", yield->s);
+    if (tainted)
+      debug_printf_indent("%s     \\__(tainted)\n",
+       skipping ? "|     " : "      ");
+    if (skipping)
+      debug_printf_indent("\\___skipping: result is not used\n");
+    }
+  else
+    {
+    debug_printf_indent(UTF8_VERT_RIGHT UTF8_HORIZ UTF8_HORIZ
+      "expanding: %.*s\n",
+      (int)(s - string), string);
+    debug_printf_indent("%s" UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ
+      "result: %s\n",
+      skipping ? UTF8_VERT_RIGHT : UTF8_UP_RIGHT,
+      yield->s);
+    if (tainted)
+      debug_printf_indent("%s(tainted)\n",
+       skipping
+       ? UTF8_VERT "             " : "           " UTF8_UP_RIGHT UTF8_HORIZ UTF8_HORIZ);
+    if (skipping)
+      debug_printf_indent(UTF8_UP_RIGHT UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ
+       "skipping: result is not used\n");
+    }
   }
 expand_level--;
 return yield->s;
   }
 expand_level--;
 return yield->s;
@@ -7857,16 +7999,25 @@ that is a bad idea, because expand_string_message is in dynamic store. */
 EXPAND_FAILED:
 if (left) *left = s;
 DEBUG(D_expand)
 EXPAND_FAILED:
 if (left) *left = s;
 DEBUG(D_expand)
-  {
-  debug_printf_indent(UTF8_VERT_RIGHT "failed to expand: %s\n",
-    string);
-  debug_printf_indent("%s" UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ
-    "error message: %s\n",
-    f.expand_string_forcedfail ? UTF8_VERT_RIGHT : UTF8_UP_RIGHT,
-    expand_string_message);
-  if (f.expand_string_forcedfail)
-    debug_printf_indent(UTF8_UP_RIGHT "failure was forced\n");
-  }
+  DEBUG(D_noutf8)
+    {
+    debug_printf_indent("|failed to expand: %s\n", string);
+    debug_printf_indent("%serror message: %s\n",
+      f.expand_string_forcedfail ? "|---" : "\\___", expand_string_message);
+    if (f.expand_string_forcedfail)
+      debug_printf_indent("\\failure was forced\n");
+    }
+  else
+    {
+    debug_printf_indent(UTF8_VERT_RIGHT "failed to expand: %s\n",
+      string);
+    debug_printf_indent("%s" UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ
+      "error message: %s\n",
+      f.expand_string_forcedfail ? UTF8_VERT_RIGHT : UTF8_UP_RIGHT,
+      expand_string_message);
+    if (f.expand_string_forcedfail)
+      debug_printf_indent(UTF8_UP_RIGHT "failure was forced\n");
+    }
 if (resetok_p && !resetok) *resetok_p = FALSE;
 expand_level--;
 return NULL;
 if (resetok_p && !resetok) *resetok_p = FALSE;
 expand_level--;
 return NULL;
@@ -8201,23 +8352,21 @@ assert_no_variables(void * ptr, int len, const char * filename, int linenumber)
 {
 err_ctx e = { .region_start = ptr, .region_end = US ptr + len,
              .var_name = NULL, .var_data = NULL };
 {
 err_ctx e = { .region_start = ptr, .region_end = US ptr + len,
              .var_name = NULL, .var_data = NULL };
-int i;
-var_entry * v;
 
 /* check acl_ variables */
 tree_walk(acl_var_c, assert_variable_notin, &e);
 tree_walk(acl_var_m, assert_variable_notin, &e);
 
 /* check auth<n> variables */
 
 /* check acl_ variables */
 tree_walk(acl_var_c, assert_variable_notin, &e);
 tree_walk(acl_var_m, assert_variable_notin, &e);
 
 /* check auth<n> variables */
-for (i = 0; i < AUTH_VARS; i++) if (auth_vars[i])
+for (int i = 0; i < AUTH_VARS; i++) if (auth_vars[i])
   assert_variable_notin(US"auth<n>", auth_vars[i], &e);
 
 /* check regex<n> variables */
   assert_variable_notin(US"auth<n>", auth_vars[i], &e);
 
 /* check regex<n> variables */
-for (i = 0; i < REGEX_VARS; i++) if (regex_vars[i])
+for (int i = 0; i < REGEX_VARS; i++) if (regex_vars[i])
   assert_variable_notin(US"regex<n>", regex_vars[i], &e);
 
 /* check known-name variables */
   assert_variable_notin(US"regex<n>", regex_vars[i], &e);
 
 /* check known-name variables */
-for (v = var_table; v < var_table + var_table_size; v++)
+for (var_entry * v = var_table; v < var_table + var_table_size; v++)
   if (v->type == vtype_stringptr)
     assert_variable_notin(US v->name, *(USS v->value), &e);
 
   if (v->type == vtype_stringptr)
     assert_variable_notin(US v->name, *(USS v->value), &e);
 
@@ -8254,9 +8403,8 @@ BOOL yield = n >= 0;
 if (n == 0) n = EXPAND_MAXN + 1;
 if (yield)
   {
 if (n == 0) n = EXPAND_MAXN + 1;
 if (yield)
   {
-  int nn;
-  expand_nmax = (setup < 0)? 0 : setup + 1;
-  for (nn = (setup < 0)? 0 : 2; nn < n*2; nn += 2)
+  expand_nmax = setup < 0 ? 0 : setup + 1;
+  for (int nn = setup < 0 ? 0 : 2; nn < n*2; nn += 2)
     {
     expand_nstring[expand_nmax] = subject + ovector[nn];
     expand_nlength[expand_nmax++] = ovector[nn+1] - ovector[nn];
     {
     expand_nstring[expand_nmax] = subject + ovector[nn];
     expand_nlength[expand_nmax++] = ovector[nn+1] - ovector[nn];
@@ -8269,7 +8417,6 @@ return yield;
 
 int main(int argc, uschar **argv)
 {
 
 int main(int argc, uschar **argv)
 {
-int i;
 uschar buffer[1024];
 
 debug_selector = D_v;
 uschar buffer[1024];
 
 debug_selector = D_v;
@@ -8277,7 +8424,7 @@ debug_file = stderr;
 debug_fd = fileno(debug_file);
 big_buffer = malloc(big_buffer_size);
 
 debug_fd = fileno(debug_file);
 big_buffer = malloc(big_buffer_size);
 
-for (i = 1; i < argc; i++)
+for (int i = 1; i < argc; i++)
   {
   if (argv[i][0] == '+')
     {
   {
   if (argv[i][0] == '+')
     {
@@ -8328,15 +8475,15 @@ if (opt_perl_startup != NULL)
   }
 #endif /* EXIM_PERL */
 
   }
 #endif /* EXIM_PERL */
 
+/* Thie deliberately regards the input as untainted, so that it can be
+expanded; only reasonable since this is a test for string-expansions. */
+
 while (fgets(buffer, sizeof(buffer), stdin) != NULL)
   {
 while (fgets(buffer, sizeof(buffer), stdin) != NULL)
   {
-  void *reset_point = store_get(0);
+  rmark reset_point = store_mark();
   uschar *yield = expand_string(buffer);
   uschar *yield = expand_string(buffer);
-  if (yield != NULL)
-    {
+  if (yield)
     printf("%s\n", yield);
     printf("%s\n", yield);
-    store_reset(reset_point);
-    }
   else
     {
     if (f.search_find_defer) printf("search_find deferred\n");
   else
     {
     if (f.search_find_defer) printf("search_find deferred\n");
@@ -8344,6 +8491,7 @@ while (fgets(buffer, sizeof(buffer), stdin) != NULL)
     if (f.expand_string_forcedfail) printf("Forced failure\n");
     printf("\n");
     }
     if (f.expand_string_forcedfail) printf("Forced failure\n");
     printf("\n");
     }
+  store_reset(reset_point);
   }
 
 search_tidyup();
   }
 
 search_tidyup();