Avoid re-expansion in ${sort }
[exim.git] / src / src / expand.c
index a7a09fa7a6207f3d729c2ea4dccb79767a4383a5..65c585d1c0cd6641eedcc52429ee6f7c33f76fb3 100644 (file)
@@ -235,6 +235,7 @@ static uschar *op_table_main[] = {
   US"rxquote",
   US"s",
   US"sha1",
+  US"sha2",
   US"sha256",
   US"sha3",
   US"stat",
@@ -281,6 +282,7 @@ enum {
   EOP_RXQUOTE,
   EOP_S,
   EOP_SHA1,
+  EOP_SHA2,
   EOP_SHA256,
   EOP_SHA3,
   EOP_STAT,
@@ -312,7 +314,11 @@ static uschar *cond_table[] = {
   US"exists",
   US"first_delivery",
   US"forall",
+  US"forall_json",
+  US"forall_jsons",
   US"forany",
+  US"forany_json",
+  US"forany_jsons",
   US"ge",
   US"gei",
   US"gt",
@@ -358,7 +364,11 @@ enum {
   ECOND_EXISTS,
   ECOND_FIRST_DELIVERY,
   ECOND_FORALL,
+  ECOND_FORALL_JSON,
+  ECOND_FORALL_JSONS,
   ECOND_FORANY,
+  ECOND_FORANY_JSON,
+  ECOND_FORANY_JSONS,
   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 },
-#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 },
@@ -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_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 },
-#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_out_cipher_std",  vtype_stringptr,   &tls_out.cipher_stdname },
 #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 },
-#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
@@ -766,7 +781,7 @@ static var_entry var_table[] = {
 #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
 
@@ -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.
 
-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:
@@ -960,56 +975,55 @@ Arguments:
 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)
 {
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
 # 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
-      /* 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)
-#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
-      /* Poor randomness and no seeding here */
+    /* Poor randomness and no seeding here */
 #endif
 
-      }
-    pid = p2;
     }
+  pid = p2;
+  }
 
 #ifdef HAVE_ARC4RANDOM
-  return arc4random() % max;
+return arc4random() % max;
 #elif defined(HAVE_SRANDOM) || defined(HAVE_SRANDOMDEV)
-  return random() % max;
+return random() % max;
 #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
 }
 
@@ -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 */
 
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
 typedef struct
 {
 uschar * name;
@@ -1336,7 +1350,7 @@ expand_string_message =
   string_sprintf("bad field selector \"%s\" for certextract", field);
 return NULL;
 }
-#endif /*SUPPORT_TLS*/
+#endif /*DISABLE_TLS*/
 
 /*************************************************
 *        Extract a substring from a string       *
@@ -1759,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]))
   {
-  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"";
   }
 
@@ -2144,6 +2163,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           *
 *************************************************/
@@ -2170,9 +2322,11 @@ BOOL testfor = TRUE;
 BOOL tempcond, combined_cond;
 BOOL *subcondptr;
 BOOL sub2_honour_dollar = TRUE;
+BOOL is_forany, is_json, is_jsons;
 int rc, cond_type, roffset;
 int_eximarith_t num[2];
 struct stat statbuf;
+uschar * opname;
 uschar name[256];
 const uschar *sub[10];
 
@@ -2185,37 +2339,7 @@ for (;;)
   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. */
@@ -2414,8 +2538,9 @@ switch(cond_type)
 
     if (yield != NULL)
       {
+      int rc;
       *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;
@@ -2430,7 +2555,8 @@ switch(cond_type)
           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;
        }
       }
@@ -2534,7 +2660,7 @@ switch(cond_type)
       {
       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,
@@ -2549,7 +2675,7 @@ switch(cond_type)
     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;
@@ -2737,7 +2863,7 @@ switch(cond_type)
 
       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);
@@ -2774,7 +2900,7 @@ switch(cond_type)
 
       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);
@@ -2861,7 +2987,7 @@ switch(cond_type)
       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
@@ -2903,14 +3029,14 @@ switch(cond_type)
     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",
-        expand_string_message, name);
+        expand_string_message, opname);
       return NULL;
       }
     while (isspace(*s)) s++;
@@ -2920,7 +3046,7 @@ switch(cond_type)
       {
       /* {-for-text-editors */
       expand_string_message = string_sprintf("missing } at end of condition "
-        "inside \"%s\" group", name);
+        "inside \"%s\" group", opname);
       return NULL;
       }
 
@@ -2945,14 +3071,20 @@ switch(cond_type)
 
   /* 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;
 
-    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 */
@@ -2973,7 +3105,7 @@ switch(cond_type)
     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++;
@@ -2983,27 +3115,39 @@ switch(cond_type)
       {
       /* {-for-text-editors */
       expand_string_message = string_sprintf("missing } at end of condition "
-        "inside \"%s\"", name);
+        "inside \"%s\"", opname);
       return NULL;
       }
 
-    if (yield != NULL) *yield = !testfor;
+    if (yield) *yield = !testfor;
     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)))
       {
+      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", name, iterate_item);
       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;
         }
-      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");
 
-      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;
@@ -3092,19 +3236,20 @@ switch(cond_type)
   /* 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:
-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",
-  name);
+  opname);
 return NULL;
 
 /* A condition requires code that is not compiled */
@@ -3114,7 +3259,7 @@ return NULL;
     !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
 }
@@ -3544,7 +3689,7 @@ return yield;
 }
 
 
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
 static gstring *
 cat_file_tls(void * tls_ctx, gstring * yield, uschar * eol)
 {
@@ -3593,17 +3738,15 @@ eval_expr(uschar **sptr, BOOL decimal, uschar **error, BOOL endket)
 {
 uschar *s = *sptr;
 int_eximarith_t x = eval_op_or(&s, decimal, error);
-if (*error == NULL)
-  {
+
+if (!*error)
   if (endket)
-    {
     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;
 }
@@ -3612,12 +3755,12 @@ return x;
 static int_eximarith_t
 eval_number(uschar **sptr, BOOL decimal, uschar **error)
 {
-register int c;
+int c;
 int_eximarith_t n;
 uschar *s = *sptr;
+
 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);
@@ -3660,9 +3803,8 @@ if (*s == '+' || *s == '-' || *s == '~')
     else if (op == '~') x = ~x;
   }
 else
-  {
   x = eval_number(&s, decimal, error);
-  }
+
 *sptr = s;
 return x;
 }
@@ -3841,87 +3983,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:
-  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;
-unsigned depth = 0;
-BOOL quotesmode = wrap[0] == wrap[1];
+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 (!quotesmode && *p == wrap[-1]) depth++;
-    else if (*p == *wrap)
-      if (depth == 0)
-       {
-       *p = '\0';
-       return s;
-       }
-      else
-       depth--;
-    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;
-item = string_copyn(item, s - item);
-DEBUG(D_expand) debug_printf_indent("  json ele: '%s'\n", item);
-return US item;
+return FALSE;  /* should not happen */
 }
 
 
-
 /*************************************************
 *                 Expand string                  *
 *************************************************/
@@ -4213,6 +4324,7 @@ while (*s != 0)
       {
       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))
@@ -4224,7 +4336,7 @@ while (*s != 0)
       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:
@@ -4238,7 +4350,8 @@ while (*s != 0)
           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;
        }
       }
@@ -4806,7 +4919,7 @@ while (*s != 0)
           (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;
 
@@ -4903,17 +5016,16 @@ while (*s != 0)
 
     case EITEM_READSOCK:
       {
-      int fd;
+      client_conn_ctx cctx;
       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;
-      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)
@@ -4957,7 +5069,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; }
-#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
@@ -5013,12 +5125,12 @@ while (*s != 0)
             port = ntohs(service_info->s_port);
             }
 
-         /*XXX we trust that the request is idempotent.  Hmm. */
-         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;
-         if (fd < 0)
+         if (cctx.sock < 0)
            goto SOCK_FAIL;
          if (!do_tls)
            reqstr.len = 0;
@@ -5031,7 +5143,7 @@ while (*s != 0)
          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));
@@ -5045,7 +5157,7 @@ while (*s != 0)
 
           sigalrm_seen = FALSE;
           ALARM(timeout);
-          rc = connect(fd, (struct sockaddr *)(&sockun), sizeof(sockun));
+          rc = connect(cctx.sock, (struct sockaddr *)(&sockun), sizeof(sockun));
           ALARM_CLR(0);
           if (sigalrm_seen)
             {
@@ -5064,17 +5176,14 @@ while (*s != 0)
 
         DEBUG(D_expand) debug_printf_indent("connected to socket %s\n", sub_arg[0]);
 
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
        if (do_tls)
          {
+         smtp_connect_args conn_args = {.host = &host };
          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;
@@ -5092,10 +5201,10 @@ while (*s != 0)
           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
-                       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));
@@ -5108,7 +5217,7 @@ while (*s != 0)
         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);
@@ -5116,22 +5225,22 @@ while (*s != 0)
         /* 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;
         ALARM(timeout);
         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]);
         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
@@ -5633,7 +5742,13 @@ while (*s != 0)
       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++;
 
@@ -5641,7 +5756,10 @@ while (*s != 0)
 
       if (*s != '{')                                   /*}*/
        if (Ustrncmp(s, "json", 4) == 0)
-         {fmt = extract_json; s += 4;}
+         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.
@@ -5722,7 +5840,7 @@ while (*s != 0)
            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;
              }
             }
@@ -5749,6 +5867,7 @@ while (*s != 0)
          break;
 
        case extract_json:
+       case extract_jsons:
          {
          uschar * s, * item;
          const uschar * list;
@@ -5776,10 +5895,11 @@ while (*s != 0)
              }
            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
            {
@@ -5814,6 +5934,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
@@ -5939,7 +6070,7 @@ while (*s != 0)
       continue;
       }
 
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
     case EITEM_CERTEXTRACT:
       {
       uschar *save_lookup_value = lookup_value;
@@ -6019,7 +6150,7 @@ while (*s != 0)
         save_expand_nlength);
       continue;
       }
-#endif /*SUPPORT_TLS*/
+#endif /*DISABLE_TLS*/
 
     /* Handle list operations */
 
@@ -6225,9 +6356,10 @@ while (*s != 0)
 
     case EITEM_SORT:
       {
+      int cond_type;
       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;
@@ -6262,6 +6394,25 @@ while (*s != 0)
        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++ != '{')
         {
@@ -6289,11 +6440,10 @@ while (*s != 0)
       if (skipping) continue;
 
       while ((srcitem = string_nextinlist(&srclist, &sep, NULL, 0)))
-        {
-       uschar * dstitem;
+       {
+       uschar * srcfield, * dstitem;
        gstring * newlist = NULL;
        gstring * newkeylist = NULL;
-       uschar * srcfield;
 
         DEBUG(D_expand) debug_printf_indent("%s: $item = \"%s\"\n", name, srcitem);
 
@@ -6314,25 +6464,15 @@ while (*s != 0)
        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;
 
-         /* 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. */
@@ -6531,7 +6671,7 @@ while (*s != 0)
     int c;
     uschar *arg = NULL;
     uschar *sub;
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
     var_entry *vp = NULL;
 #endif
 
@@ -6554,7 +6694,7 @@ while (*s != 0)
     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:
@@ -6709,7 +6849,7 @@ while (*s != 0)
         }
 
       case EOP_MD5:
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
        if (vp && *(void **)vp->value)
          {
          uschar * cp = tls_cert_fprt_md5(*(void **)vp->value);
@@ -6728,7 +6868,7 @@ while (*s != 0)
         continue;
 
       case EOP_SHA1:
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
        if (vp && *(void **)vp->value)
          {
          uschar * cp = tls_cert_fprt_sha1(*(void **)vp->value);
@@ -6746,23 +6886,30 @@ while (*s != 0)
          }
         continue;
 
+      case EOP_SHA2:
       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;
+         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;
            }
+
          exim_sha_update(&h, sub, Ustrlen(sub));
          exim_sha_finish(&h, &b);
          while (b.len-- > 0)
@@ -6843,7 +6990,7 @@ while (*s != 0)
             }
           }
 
-        enc = b64encode(sub, out - sub);
+        enc = b64encode(CUS sub, out - sub);
         yield = string_cat(yield, enc);
         continue;
         }
@@ -7050,16 +7197,11 @@ while (*s != 0)
         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;
         }
 
@@ -7083,7 +7225,7 @@ while (*s != 0)
 
         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,
@@ -7096,7 +7238,7 @@ while (*s != 0)
           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);
@@ -7469,7 +7611,7 @@ while (*s != 0)
         continue;
         }
 
-      /* Handle time period formating */
+      /* Handle time period formatting */
 
       case EOP_TIME_EVAL:
         {
@@ -7504,12 +7646,12 @@ while (*s != 0)
       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)
-         : b64encode(sub, Ustrlen(sub));
+         : b64encode(CUS sub, Ustrlen(sub));
 #else
-       uschar * s = b64encode(sub, Ustrlen(sub));
+       uschar * s = b64encode(CUS sub, Ustrlen(sub));
 #endif
        yield = string_cat(yield, s);
        continue;