Proxy Protocol - Server support
[exim.git] / src / src / expand.c
index 4dea8b12dba82fee08eea034ebb03482f652023d..bf1f82c6facacd332de02338667a7bcf2662a7d9 100644 (file)
@@ -13,7 +13,7 @@
 
 /* Recursively called function */
 
-static uschar *expand_string_internal(uschar *, BOOL, uschar **, BOOL, BOOL);
+static uschar *expand_string_internal(uschar *, BOOL, uschar **, BOOL, BOOL, BOOL *);
 
 #ifdef STAND_ALONE
 #ifndef SUPPORT_CRYPTEQ
@@ -181,6 +181,7 @@ static uschar *op_table_main[] = {
   US"h",
   US"hash",
   US"hex2b64",
+  US"hexquote",
   US"l",
   US"lc",
   US"length",
@@ -216,6 +217,7 @@ enum {
   EOP_H,
   EOP_HASH,
   EOP_HEX2B64,
+  EOP_HEXQUOTE,
   EOP_L,
   EOP_LC,
   EOP_LENGTH,
@@ -408,6 +410,7 @@ static var_entry var_table[] = {
   { "address_data",        vtype_stringptr,   &deliver_address_data },
   { "address_file",        vtype_stringptr,   &address_file },
   { "address_pipe",        vtype_stringptr,   &address_pipe },
+  { "authenticated_fail_id",vtype_stringptr,  &authenticated_fail_id },
   { "authenticated_id",    vtype_stringptr,   &authenticated_id },
   { "authenticated_sender",vtype_stringptr,   &authenticated_sender },
   { "authentication_failed",vtype_int,        &authentication_failed },
@@ -458,6 +461,12 @@ static var_entry var_table[] = {
   { "dkim_signers",        vtype_stringptr,   &dkim_signers },
   { "dkim_verify_reason",  vtype_dkim,        (void *)DKIM_VERIFY_REASON },
   { "dkim_verify_status",  vtype_dkim,        (void *)DKIM_VERIFY_STATUS},
+#endif
+#ifdef EXPERIMENTAL_DMARC
+  { "dmarc_ar_header",     vtype_stringptr,   &dmarc_ar_header },
+  { "dmarc_status",        vtype_stringptr,   &dmarc_status },
+  { "dmarc_status_text",   vtype_stringptr,   &dmarc_status_text },
+  { "dmarc_used_domain",   vtype_stringptr,   &dmarc_used_domain },
 #endif
   { "dnslist_domain",      vtype_stringptr,   &dnslist_domain },
   { "dnslist_matched",     vtype_stringptr,   &dnslist_matched },
@@ -547,6 +556,11 @@ static var_entry var_table[] = {
   { "parent_local_part",   vtype_stringptr,   &deliver_localpart_parent },
   { "pid",                 vtype_pid,         NULL },
   { "primary_hostname",    vtype_stringptr,   &primary_hostname },
+#ifdef EXPERIMENTAL_PROXY
+  { "proxy_host",          vtype_stringptr,   &proxy_host },
+  { "proxy_port",          vtype_int,         &proxy_port },
+  { "proxy_session",       vtype_bool,        &proxy_session },
+#endif
   { "prvscheck_address",   vtype_stringptr,   &prvscheck_address },
   { "prvscheck_keynum",    vtype_stringptr,   &prvscheck_keynum },
   { "prvscheck_result",    vtype_stringptr,   &prvscheck_result },
@@ -571,6 +585,7 @@ static var_entry var_table[] = {
   { "reply_address",       vtype_reply,       NULL },
   { "return_path",         vtype_stringptr,   &return_path },
   { "return_size_limit",   vtype_int,         &bounce_return_size_limit },
+  { "router_name",         vtype_stringptr,   &router_name },
   { "runrc",               vtype_int,         &runrc },
   { "self_hostname",       vtype_stringptr,   &self_hostname },
   { "sender_address",      vtype_stringptr,   &sender_address },
@@ -667,6 +682,17 @@ static var_entry var_table[] = {
   { "tod_logfile",         vtype_todlf,       NULL },
   { "tod_zone",            vtype_todzone,     NULL },
   { "tod_zulu",            vtype_todzulu,     NULL },
+#ifdef EXPERIMENTAL_TPDA
+  { "tpda_defer_errno",     vtype_int,         &tpda_defer_errno },
+  { "tpda_defer_errstr",    vtype_stringptr,   &tpda_defer_errstr },
+  { "tpda_delivery_confirmation", vtype_stringptr,   &tpda_delivery_confirmation },
+  { "tpda_delivery_domain", vtype_stringptr,   &tpda_delivery_domain },
+  { "tpda_delivery_fqdn",   vtype_stringptr,   &tpda_delivery_fqdn },
+  { "tpda_delivery_ip",     vtype_stringptr,   &tpda_delivery_ip },
+  { "tpda_delivery_local_part",vtype_stringptr,&tpda_delivery_local_part },
+  { "tpda_delivery_port",   vtype_int,         &tpda_delivery_port },
+#endif
+  { "transport_name",      vtype_stringptr,   &transport_name },
   { "value",               vtype_stringptr,   &lookup_value },
   { "version_number",      vtype_stringptr,   &version_string },
   { "warn_message_delay",  vtype_stringptr,   &warnmsg_delay },
@@ -1778,6 +1804,8 @@ Arguments:
   skipping   the skipping flag
   check_end  if TRUE, check for final '}'
   name       name of item, for error message
+  resetok    if not NULL, pointer to flag - write FALSE if unsafe to reset
+            the store.
 
 Returns:     0 OK; string pointer updated
              1 curly bracketing error (too few arguments)
@@ -1787,7 +1815,7 @@ Returns:     0 OK; string pointer updated
 
 static int
 read_subs(uschar **sub, int n, int m, uschar **sptr, BOOL skipping,
-  BOOL check_end, uschar *name)
+  BOOL check_end, uschar *name, BOOL *resetok)
 {
 int i;
 uschar *s = *sptr;
@@ -1801,7 +1829,7 @@ for (i = 0; i < n; i++)
     sub[i] = NULL;
     break;
     }
-  sub[i] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE);
+  sub[i] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, resetok);
   if (sub[i] == NULL) return 3;
   if (*s++ != '}') return 1;
   while (isspace(*s)) s++;
@@ -1869,6 +1897,7 @@ int i;
 uschar *tmp;
 int sav_narg = acl_narg;
 int ret;
+extern int acl_where;
 
 if(--nsub > sizeof(acl_arg)/sizeof(*acl_arg)) nsub = sizeof(acl_arg)/sizeof(*acl_arg);
 for (i = 0; i < nsub && sub[i+1]; i++)
@@ -1887,10 +1916,10 @@ while (i < nsub)
 DEBUG(D_expand)
   debug_printf("expanding: acl: %s  arg: %s%s\n",
     sub[0],
-    acl_narg>0 ? sub[1]   : US"<none>",
-    acl_narg>1 ? " +more" : "");
+    acl_narg>0 ? acl_arg[0] : US"<none>",
+    acl_narg>1 ? " +more"   : "");
 
-ret = acl_check(ACL_WHERE_EXPANSION, NULL, sub[0], user_msgp, &tmp);
+ret = acl_eval(acl_where, sub[0], user_msgp, &tmp);
 
 for (i = 0; i < nsub; i++)
   acl_arg[i] = sub[i+1];       /* restore old args */
@@ -1909,6 +1938,9 @@ return ret;
 /*
 Arguments:
   s        points to the start of the condition text
+  resetok  points to a BOOL which is written false if it is unsafe to
+          free memory. Certain condition types (acl) may have side-effect
+          allocation which must be preserved.
   yield    points to a BOOL to hold the result of the condition test;
            if NULL, we are just reading through a condition that is
            part of an "or" combination to check syntax, or in a state
@@ -1919,7 +1951,7 @@ Returns:   a pointer to the first character after the condition, or
 */
 
 static uschar *
-eval_condition(uschar *s, BOOL *yield)
+eval_condition(uschar *s, BOOL *resetok, BOOL *yield)
 {
 BOOL testfor = TRUE;
 BOOL tempcond, combined_cond;
@@ -2058,7 +2090,7 @@ switch(cond_type)
   while (isspace(*s)) s++;
   if (*s != '{') goto COND_FAILED_CURLY_START;         /* }-for-text-editors */
 
-  sub[0] = expand_string_internal(s+1, TRUE, &s, yield == NULL, TRUE);
+  sub[0] = expand_string_internal(s+1, TRUE, &s, yield == NULL, TRUE, resetok);
   if (sub[0] == NULL) return NULL;
   /* {-for-text-editors */
   if (*s++ != '}') goto COND_FAILED_CURLY_END;
@@ -2142,22 +2174,23 @@ switch(cond_type)
   like the saslauthd condition does, to permit a variable number of args.
   See also the expansion-item version EITEM_ACL and the traditional
   acl modifier ACLC_ACL.
+  Since the ACL may allocate new global variables, tell our caller to not
+  reclaim memory.
   */
 
   case ECOND_ACL:
     /* ${if acl {{name}{arg1}{arg2}...}  {yes}{no}} */
     {
-    uschar *nameargs;
     uschar *user_msg;
     BOOL cond = FALSE;
     int size = 0;
     int ptr = 0;
 
     while (isspace(*s)) s++;
-    if (*s++ != '{') goto COND_FAILED_CURLY_START;
+    if (*s++ != '{') goto COND_FAILED_CURLY_START;     /*}*/
 
     switch(read_subs(sub, sizeof(sub)/sizeof(*sub), 1,
-      &s, yield == NULL, TRUE, US"acl"))
+      &s, yield == NULL, TRUE, US"acl", resetok))
       {
       case 1: expand_string_message = US"too few arguments or bracketing "
         "error for acl";
@@ -2165,6 +2198,7 @@ switch(cond_type)
       case 3: return NULL;
       }
 
+    *resetok = FALSE;
     if (yield != NULL) switch(eval_acl(sub, sizeof(sub)/sizeof(*sub), &user_msg))
        {
        case OK:
@@ -2191,7 +2225,7 @@ switch(cond_type)
 
   /* saslauthd: does Cyrus saslauthd authentication. Four parameters are used:
 
-     ${if saslauthd {{username}{password}{service}{realm}}  {yes}[no}}
+     ${if saslauthd {{username}{password}{service}{realm}}  {yes}{no}}
 
   However, the last two are optional. That is why the whole set is enclosed
   in their own set of braces. */
@@ -2202,7 +2236,7 @@ switch(cond_type)
   #else
   while (isspace(*s)) s++;
   if (*s++ != '{') goto COND_FAILED_CURLY_START;       /* }-for-text-editors */
-  switch(read_subs(sub, 4, 2, &s, yield == NULL, TRUE, US"saslauthd"))
+  switch(read_subs(sub, 4, 2, &s, yield == NULL, TRUE, US"saslauthd", resetok))
     {
     case 1: expand_string_message = US"too few arguments or bracketing "
       "error for saslauthd";
@@ -2286,7 +2320,7 @@ switch(cond_type)
       return NULL;
       }
     sub[i] = expand_string_internal(s+1, TRUE, &s, yield == NULL,
-        honour_dollar);
+        honour_dollar, resetok);
     if (sub[i] == NULL) return NULL;
     if (*s++ != '}') goto COND_FAILED_CURLY_END;
 
@@ -2644,8 +2678,7 @@ switch(cond_type)
       return NULL;
       }
 
-    s = eval_condition(s+1, subcondptr);
-    if (s == NULL)
+    if (!(s = eval_condition(s+1, resetok, subcondptr)))
       {
       expand_string_message = string_sprintf("%s inside \"%s{...}\" condition",
         expand_string_message, name);
@@ -2691,7 +2724,7 @@ switch(cond_type)
 
     while (isspace(*s)) s++;
     if (*s++ != '{') goto COND_FAILED_CURLY_START;     /* }-for-text-editors */
-    sub[0] = expand_string_internal(s, TRUE, &s, (yield == NULL), TRUE);
+    sub[0] = expand_string_internal(s, TRUE, &s, (yield == NULL), TRUE, resetok);
     if (sub[0] == NULL) return NULL;
     /* {-for-text-editors */
     if (*s++ != '}') goto COND_FAILED_CURLY_END;
@@ -2705,8 +2738,7 @@ switch(cond_type)
     "false" part). This allows us to find the end of the condition, because if
     the list it empty, we won't actually evaluate the condition for real. */
 
-    s = eval_condition(sub[1], NULL);
-    if (s == NULL)
+    if (!(s = eval_condition(sub[1], resetok, NULL)))
       {
       expand_string_message = string_sprintf("%s inside \"%s\" condition",
         expand_string_message, name);
@@ -2727,7 +2759,7 @@ switch(cond_type)
     while ((iterate_item = string_nextinlist(&sub[0], &sep, NULL, 0)) != NULL)
       {
       DEBUG(D_expand) debug_printf("%s: $item = \"%s\"\n", name, iterate_item);
-      if (eval_condition(sub[1], &tempcond) == NULL)
+      if (!eval_condition(sub[1], resetok, &tempcond))
         {
         expand_string_message = string_sprintf("%s inside \"%s\" condition",
           expand_string_message, name);
@@ -2767,7 +2799,7 @@ switch(cond_type)
     while (isspace(*s)) s++;
     if (*s != '{') goto COND_FAILED_CURLY_START;       /* }-for-text-editors */
     ourname = cond_type == ECOND_BOOL_LAX ? US"bool_lax" : US"bool";
-    switch(read_subs(sub_arg, 1, 1, &s, yield == NULL, FALSE, ourname))
+    switch(read_subs(sub_arg, 1, 1, &s, yield == NULL, FALSE, ourname, resetok))
       {
       case 1: expand_string_message = string_sprintf(
                   "too few arguments or bracketing error for %s",
@@ -2931,6 +2963,8 @@ Arguments:
   sizeptr        points to the output string size
   ptrptr         points to the output string pointer
   type           "lookup" or "if" or "extract" or "run", for error message
+  resetok       if not NULL, pointer to flag - write FALSE if unsafe to reset
+               the store.
 
 Returns:         0 OK; lookup_value has been reset to save_lookup
                  1 expansion failed
@@ -2939,7 +2973,7 @@ Returns:         0 OK; lookup_value has been reset to save_lookup
 
 static int
 process_yesno(BOOL skipping, BOOL yes, uschar *save_lookup, uschar **sptr,
-  uschar **yieldptr, int *sizeptr, int *ptrptr, uschar *type)
+  uschar **yieldptr, int *sizeptr, int *ptrptr, uschar *type, BOOL *resetok)
 {
 int rc = 0;
 uschar *s = *sptr;    /* Local value */
@@ -2976,7 +3010,7 @@ if (*s++ != '{') goto FAILED_CURLY;
 want this string. Set skipping in the call in the fail case (this will always
 be the case if we were already skipping). */
 
-sub1 = expand_string_internal(s, TRUE, &s, !yes, TRUE);
+sub1 = expand_string_internal(s, TRUE, &s, !yes, TRUE, resetok);
 if (sub1 == NULL && (yes || !expand_string_forcedfail)) goto FAILED;
 expand_string_forcedfail = FALSE;
 if (*s++ != '}') goto FAILED_CURLY;
@@ -3001,7 +3035,7 @@ already skipping. */
 while (isspace(*s)) s++;
 if (*s == '{')
   {
-  sub2 = expand_string_internal(s+1, TRUE, &s, yes || skipping, TRUE);
+  sub2 = expand_string_internal(s+1, TRUE, &s, yes || skipping, TRUE, resetok);
   if (sub2 == NULL && (!yes || !expand_string_forcedfail)) goto FAILED;
   expand_string_forcedfail = FALSE;
   if (*s++ != '}') goto FAILED_CURLY;
@@ -3389,12 +3423,12 @@ if (*error == NULL)
      * can just let the other invalid results occur otherwise, as they have
      * until now.  For this one case, we can coerce.
      */
-    if (y == -1 && x == LLONG_MIN && op != '*')
+    if (y == -1 && x == EXIM_ARITH_MIN && op != '*')
       {
       DEBUG(D_expand)
         debug_printf("Integer exception dodging: " PR_EXIM_ARITH "%c-1 coerced to " PR_EXIM_ARITH "\n",
-            LLONG_MIN, op, LLONG_MAX);
-      x = LLONG_MAX;
+            EXIM_ARITH_MIN, op, EXIM_ARITH_MAX);
+      x = EXIM_ARITH_MAX;
       continue;
       }
     if (op == '*')
@@ -3566,8 +3600,9 @@ $message_headers which can get very long.
 There's a problem if a ${dlfunc item has side-effects that cause allocation,
 since resetting the store at the end of the expansion will free store that was
 allocated by the plugin code as well as the slop after the expanded string. So
-we skip any resets if ${dlfunc has been used. The same applies for ${acl. This
-is an unfortunate consequence of string expansion becoming too powerful.
+we skip any resets if ${dlfunc } has been used. The same applies for ${acl }
+and, given the acl condition, ${if }. This is an unfortunate consequence of
+string expansion becoming too powerful.
 
 Arguments:
   string         the string to be expanded
@@ -3578,6 +3613,8 @@ Arguments:
                  to be used (to allow for optimisation)
   honour_dollar  TRUE if $ is to be expanded,
                  FALSE if it's just another character
+  resetok_p     if not NULL, pointer to flag - write FALSE if unsafe to reset
+                the store.
 
 Returns:         NULL if expansion fails:
                    expand_string_forcedfail is set TRUE if failure was forced
@@ -3587,7 +3624,7 @@ Returns:         NULL if expansion fails:
 
 static uschar *
 expand_string_internal(uschar *string, BOOL ket_ends, uschar **left,
-  BOOL skipping, BOOL honour_dollar)
+  BOOL skipping, BOOL honour_dollar, BOOL *resetok_p)
 {
 int ptr = 0;
 int size = Ustrlen(string)+ 64;
@@ -3638,9 +3675,11 @@ while (*s != 0)
     continue;
     }
 
+  /*{*/
   /* Anything other than $ is just copied verbatim, unless we are
   looking for a terminating } character. */
 
+  /*{*/
   if (ket_ends && *s == '}') break;
 
   if (*s != '$' || !honour_dollar)
@@ -3655,7 +3694,7 @@ while (*s != 0)
   names can contain any printing characters except space and colon.
   For those that don't like typing this much, "$h_" is a synonym for
   "$header_". A non-existent header yields a NULL value; nothing is
-  inserted. */
+  inserted. */ /*}*/
 
   if (isalpha((*(++s))))
     {
@@ -3742,11 +3781,11 @@ while (*s != 0)
     continue;
     }
 
-  /* Otherwise, if there's no '{' after $ it's an error. */
+  /* Otherwise, if there's no '{' after $ it's an error. */            /*}*/
 
-  if (*s != '{')
+  if (*s != '{')                                                       /*}*/
     {
-    expand_string_message = US"$ not followed by letter, digit, or {";
+    expand_string_message = US"$ not followed by letter, digit, or {"; /*}*/
     goto EXPAND_FAILED;
     }
 
@@ -3756,9 +3795,9 @@ while (*s != 0)
   if (isdigit((*(++s))))
     {
     int n;
-    s = read_number(&n, s);
+    s = read_number(&n, s);            /*{*/
     if (*s++ != '}')
-      {
+      {                                        /*{*/
       expand_string_message = US"} expected after number";
       goto EXPAND_FAILED;
       }
@@ -3770,7 +3809,7 @@ while (*s != 0)
 
   if (!isalpha(*s))
     {
-    expand_string_message = US"letter or digit expected after ${";
+    expand_string_message = US"letter or digit expected after ${";     /*}*/
     goto EXPAND_FAILED;
     }
 
@@ -3798,7 +3837,7 @@ while (*s != 0)
       uschar *sub[10]; /* name + arg1-arg9 (which must match number of acl_arg[]) */
       uschar *user_msg;
 
-      switch(read_subs(sub, 10, 1, &s, skipping, TRUE, US"acl"))
+      switch(read_subs(sub, 10, 1, &s, skipping, TRUE, US"acl", &resetok))
         {
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:
@@ -3836,7 +3875,7 @@ while (*s != 0)
         save_expand_strings(save_expand_nstring, save_expand_nlength);
 
       while (isspace(*s)) s++;
-      next_s = eval_condition(s, skipping? NULL : &cond);
+      next_s = eval_condition(s, &resetok, skipping? NULL : &cond);
       if (next_s == NULL) goto EXPAND_FAILED;  /* message already set */
 
       DEBUG(D_expand)
@@ -3856,7 +3895,8 @@ while (*s != 0)
                &yield,                       /* output pointer */
                &size,                        /* output size */
                &ptr,                         /* output current point */
-               US"if"))                      /* condition type */
+               US"if",                       /* condition type */
+              &resetok))
         {
         case 1: goto EXPAND_FAILED;          /* when all is well, the */
         case 2: goto EXPAND_FAILED_CURLY;    /* returned value is 0 */
@@ -3897,10 +3937,10 @@ while (*s != 0)
       Otherwise set the key NULL pro-tem. */
 
       while (isspace(*s)) s++;
-      if (*s == '{')
+      if (*s == '{')                                   /*}*/
         {
-        key = expand_string_internal(s+1, TRUE, &s, skipping, TRUE);
-        if (key == NULL) goto EXPAND_FAILED;
+        key = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok);
+        if (key == NULL) goto EXPAND_FAILED;           /*{*/
         if (*s++ != '}') goto EXPAND_FAILED_CURLY;
         while (isspace(*s)) s++;
         }
@@ -3916,9 +3956,9 @@ while (*s != 0)
 
       /* The type is a string that may contain special characters of various
       kinds. Allow everything except space or { to appear; the actual content
-      is checked by search_findtype_partial. */
+      is checked by search_findtype_partial. */                /*}*/
 
-      while (*s != 0 && *s != '{' && !isspace(*s))
+      while (*s != 0 && *s != '{' && !isspace(*s))     /*}*/
         {
         if (nameptr < sizeof(name) - 1) name[nameptr++] = *s;
         s++;
@@ -3965,7 +4005,7 @@ while (*s != 0)
       first. */
 
       if (*s != '{') goto EXPAND_FAILED_CURLY;
-      filename = expand_string_internal(s+1, TRUE, &s, skipping, TRUE);
+      filename = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok);
       if (filename == NULL) goto EXPAND_FAILED;
       if (*s++ != '}') goto EXPAND_FAILED_CURLY;
       while (isspace(*s)) s++;
@@ -4043,7 +4083,8 @@ while (*s != 0)
                &yield,                       /* output pointer */
                &size,                        /* output size */
                &ptr,                         /* output current point */
-               US"lookup"))                  /* condition type */
+               US"lookup",                   /* condition type */
+              &resetok))
         {
         case 1: goto EXPAND_FAILED;          /* when all is well, the */
         case 2: goto EXPAND_FAILED_CURLY;    /* returned value is 0 */
@@ -4066,7 +4107,7 @@ while (*s != 0)
 
     case EITEM_PERL:
     #ifndef EXIM_PERL
-    expand_string_message = US"\"${perl\" encountered, but this facility "
+    expand_string_message = US"\"${perl\" encountered, but this facility "     /*}*/
       "is not included in this binary";
     goto EXPAND_FAILED;
 
@@ -4082,7 +4123,7 @@ while (*s != 0)
         }
 
       switch(read_subs(sub_arg, EXIM_PERL_MAX_ARGS + 1, 1, &s, skipping, TRUE,
-           US"perl"))
+           US"perl", &resetok))
         {
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:
@@ -4154,7 +4195,7 @@ while (*s != 0)
       uschar *sub_arg[3];
       uschar *p,*domain;
 
-      switch(read_subs(sub_arg, 3, 2, &s, skipping, TRUE, US"prvs"))
+      switch(read_subs(sub_arg, 3, 2, &s, skipping, TRUE, US"prvs", &resetok))
         {
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:
@@ -4228,7 +4269,7 @@ while (*s != 0)
       prvscheck_address = NULL;
       prvscheck_keynum = NULL;
 
-      switch(read_subs(sub_arg, 1, 1, &s, skipping, FALSE, US"prvs"))
+      switch(read_subs(sub_arg, 1, 1, &s, skipping, FALSE, US"prvs", &resetok))
         {
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:
@@ -4260,7 +4301,7 @@ while (*s != 0)
         prvscheck_keynum = string_copy(key_num);
 
         /* Now expand the second argument */
-        switch(read_subs(sub_arg, 1, 1, &s, skipping, FALSE, US"prvs"))
+        switch(read_subs(sub_arg, 1, 1, &s, skipping, FALSE, US"prvs", &resetok))
           {
           case 1: goto EXPAND_FAILED_CURLY;
           case 2:
@@ -4314,7 +4355,7 @@ while (*s != 0)
         /* Now expand the final argument. We leave this till now so that
         it can include $prvscheck_result. */
 
-        switch(read_subs(sub_arg, 1, 0, &s, skipping, TRUE, US"prvs"))
+        switch(read_subs(sub_arg, 1, 0, &s, skipping, TRUE, US"prvs", &resetok))
           {
           case 1: goto EXPAND_FAILED_CURLY;
           case 2:
@@ -4338,7 +4379,7 @@ while (*s != 0)
            We need to make sure all subs are expanded first, so as to skip over
            the entire item. */
 
-        switch(read_subs(sub_arg, 2, 1, &s, skipping, TRUE, US"prvs"))
+        switch(read_subs(sub_arg, 2, 1, &s, skipping, TRUE, US"prvs", &resetok))
           {
           case 1: goto EXPAND_FAILED_CURLY;
           case 2:
@@ -4362,7 +4403,7 @@ while (*s != 0)
         goto EXPAND_FAILED;
         }
 
-      switch(read_subs(sub_arg, 2, 1, &s, skipping, TRUE, US"readfile"))
+      switch(read_subs(sub_arg, 2, 1, &s, skipping, TRUE, US"readfile", &resetok))
         {
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:
@@ -4408,7 +4449,7 @@ while (*s != 0)
       /* Read up to 4 arguments, but don't do the end of item check afterwards,
       because there may be a string for expansion on failure. */
 
-      switch(read_subs(sub_arg, 4, 2, &s, skipping, FALSE, US"readsocket"))
+      switch(read_subs(sub_arg, 4, 2, &s, skipping, FALSE, US"readsocket", &resetok))
         {
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:                             /* Won't occur: no end check */
@@ -4634,7 +4675,7 @@ while (*s != 0)
 
       if (*s == '{')
         {
-        if (expand_string_internal(s+1, TRUE, &s, TRUE, TRUE) == NULL)
+        if (expand_string_internal(s+1, TRUE, &s, TRUE, TRUE, &resetok) == NULL)
           goto EXPAND_FAILED;
         if (*s++ != '}') goto EXPAND_FAILED_CURLY;
         while (isspace(*s)) s++;
@@ -4649,7 +4690,7 @@ while (*s != 0)
       SOCK_FAIL:
       if (*s != '{') goto EXPAND_FAILED;
       DEBUG(D_any) debug_printf("%s\n", expand_string_message);
-      arg = expand_string_internal(s+1, TRUE, &s, FALSE, TRUE);
+      arg = expand_string_internal(s+1, TRUE, &s, FALSE, TRUE, &resetok);
       if (arg == NULL) goto EXPAND_FAILED;
       yield = string_cat(yield, &size, &ptr, arg, Ustrlen(arg));
       if (*s++ != '}') goto EXPAND_FAILED_CURLY;
@@ -4678,7 +4719,7 @@ while (*s != 0)
 
       while (isspace(*s)) s++;
       if (*s != '{') goto EXPAND_FAILED_CURLY;
-      arg = expand_string_internal(s+1, TRUE, &s, skipping, TRUE);
+      arg = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok);
       if (arg == NULL) goto EXPAND_FAILED;
       while (isspace(*s)) s++;
       if (*s++ != '}') goto EXPAND_FAILED_CURLY;
@@ -4760,7 +4801,8 @@ while (*s != 0)
                &yield,                       /* output pointer */
                &size,                        /* output size */
                &ptr,                         /* output current point */
-               US"run"))                     /* condition type */
+               US"run",                      /* condition type */
+              &resetok))
         {
         case 1: goto EXPAND_FAILED;          /* when all is well, the */
         case 2: goto EXPAND_FAILED_CURLY;    /* returned value is 0 */
@@ -4777,7 +4819,7 @@ while (*s != 0)
       int o2m;
       uschar *sub[3];
 
-      switch(read_subs(sub, 3, 3, &s, skipping, TRUE, US"tr"))
+      switch(read_subs(sub, 3, 3, &s, skipping, TRUE, US"tr", &resetok))
         {
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:
@@ -4815,11 +4857,11 @@ while (*s != 0)
       uschar *sub[3];
 
       /* "length" takes only 2 arguments whereas the others take 2 or 3.
-      Ensure that sub[2] is set in the ${length case. */
+      Ensure that sub[2] is set in the ${length case. */
 
       sub[2] = NULL;
       switch(read_subs(sub, (item_type == EITEM_LENGTH)? 2:3, 2, &s, skipping,
-             TRUE, name))
+             TRUE, name, &resetok))
         {
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:
@@ -4894,7 +4936,7 @@ while (*s != 0)
       uschar innerkey[MAX_HASHBLOCKLEN];
       uschar outerkey[MAX_HASHBLOCKLEN];
 
-      switch (read_subs(sub, 3, 3, &s, skipping, TRUE, name))
+      switch (read_subs(sub, 3, 3, &s, skipping, TRUE, name, &resetok))
         {
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:
@@ -4989,7 +5031,7 @@ while (*s != 0)
       int save_expand_nmax =
         save_expand_strings(save_expand_nstring, save_expand_nlength);
 
-      switch(read_subs(sub, 3, 3, &s, skipping, TRUE, US"sg"))
+      switch(read_subs(sub, 3, 3, &s, skipping, TRUE, US"sg", &resetok))
         {
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:
@@ -5109,8 +5151,8 @@ while (*s != 0)
         while (isspace(*s)) s++;
         if (*s == '{')
           {
-          sub[i] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE);
-          if (sub[i] == NULL) goto EXPAND_FAILED;
+          sub[i] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok);
+          if (sub[i] == NULL) goto EXPAND_FAILED;              /*{*/
           if (*s++ != '}') goto EXPAND_FAILED_CURLY;
 
           /* After removal of leading and trailing white space, the first
@@ -5173,7 +5215,8 @@ while (*s != 0)
                &yield,                       /* output pointer */
                &size,                        /* output size */
                &ptr,                         /* output current point */
-               US"extract"))                 /* condition type */
+               US"extract",                  /* condition type */
+              &resetok))
         {
         case 1: goto EXPAND_FAILED;          /* when all is well, the */
         case 2: goto EXPAND_FAILED_CURLY;    /* returned value is 0 */
@@ -5187,7 +5230,6 @@ while (*s != 0)
       continue;
       }
 
-
     /* Handle list operations */
 
     case EITEM_FILTER:
@@ -5200,11 +5242,12 @@ while (*s != 0)
       uschar *list, *expr, *temp;
       uschar *save_iterate_item = iterate_item;
       uschar *save_lookup_value = lookup_value;
+      BOOL dummy;
 
       while (isspace(*s)) s++;
       if (*s++ != '{') goto EXPAND_FAILED_CURLY;
 
-      list = expand_string_internal(s, TRUE, &s, skipping, TRUE);
+      list = expand_string_internal(s, TRUE, &s, skipping, TRUE, &resetok);
       if (list == NULL) goto EXPAND_FAILED;
       if (*s++ != '}') goto EXPAND_FAILED_CURLY;
 
@@ -5212,7 +5255,7 @@ while (*s != 0)
         {
         while (isspace(*s)) s++;
         if (*s++ != '{') goto EXPAND_FAILED_CURLY;
-        temp = expand_string_internal(s, TRUE, &s, skipping, TRUE);
+        temp = expand_string_internal(s, TRUE, &s, skipping, TRUE, &resetok);
         if (temp == NULL) goto EXPAND_FAILED;
         lookup_value = temp;
         if (*s++ != '}') goto EXPAND_FAILED_CURLY;
@@ -5231,12 +5274,12 @@ while (*s != 0)
 
       if (item_type == EITEM_FILTER)
         {
-        temp = eval_condition(expr, NULL);
+        temp = eval_condition(expr, &resetok, NULL);
         if (temp != NULL) s = temp;
         }
       else
         {
-        temp = expand_string_internal(s, TRUE, &s, TRUE, TRUE);
+        temp = expand_string_internal(s, TRUE, &s, TRUE, TRUE, &resetok);
         }
 
       if (temp == NULL)
@@ -5248,15 +5291,15 @@ while (*s != 0)
 
       while (isspace(*s)) s++;
       if (*s++ != '}')
-        {
+        {                                              /*{*/
         expand_string_message = string_sprintf("missing } at end of condition "
           "or expression inside \"%s\"", name);
         goto EXPAND_FAILED;
         }
 
-      while (isspace(*s)) s++;
+      while (isspace(*s)) s++;                         /*{*/
       if (*s++ != '}')
-        {
+        {                                              /*{*/
         expand_string_message = string_sprintf("missing } at end of \"%s\"",
           name);
         goto EXPAND_FAILED;
@@ -5275,7 +5318,7 @@ while (*s != 0)
         if (item_type == EITEM_FILTER)
           {
           BOOL condresult;
-          if (eval_condition(expr, &condresult) == NULL)
+          if (eval_condition(expr, &resetok, &condresult) == NULL)
             {
             iterate_item = save_iterate_item;
             lookup_value = save_lookup_value;
@@ -5295,7 +5338,7 @@ while (*s != 0)
 
         else
           {
-          temp = expand_string_internal(expr, TRUE, NULL, skipping, TRUE);
+          temp = expand_string_internal(expr, TRUE, NULL, skipping, TRUE, &resetok);
           if (temp == NULL)
             {
             iterate_item = save_iterate_item;
@@ -5366,7 +5409,7 @@ while (*s != 0)
       }
 
 
-    /* If ${dlfunc support is configured, handle calling dynamically-loaded
+    /* If ${dlfunc support is configured, handle calling dynamically-loaded
     functions, unless locked out at this time. Syntax is ${dlfunc{file}{func}}
     or ${dlfunc{file}{func}{arg}} or ${dlfunc{file}{func}{arg1}{arg2}} or up to
     a maximum of EXPAND_DLFUNC_MAX_ARGS arguments (defined below). */
@@ -5375,7 +5418,7 @@ while (*s != 0)
 
     case EITEM_DLFUNC:
     #ifndef EXPAND_DLFUNC
-    expand_string_message = US"\"${dlfunc\" encountered, but this facility "
+    expand_string_message = US"\"${dlfunc\" encountered, but this facility "   /*}*/
       "is not included in this binary";
     goto EXPAND_FAILED;
 
@@ -5395,7 +5438,7 @@ while (*s != 0)
         }
 
       switch(read_subs(argv, EXPAND_DLFUNC_MAX_ARGS + 2, 2, &s, skipping,
-           TRUE, US"dlfunc"))
+           TRUE, US"dlfunc", &resetok))
         {
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:
@@ -5466,7 +5509,7 @@ while (*s != 0)
         }
       }
     #endif /* EXPAND_DLFUNC */
-    }
+    }  /* EITEM_* switch */
 
   /* Control reaches here if the name is not recognized as one of the more
   complicated expansion items. Check for the "operator" syntax (name terminated
@@ -5477,7 +5520,7 @@ while (*s != 0)
     {
     int c;
     uschar *arg = NULL;
-    uschar *sub = expand_string_internal(s+1, TRUE, &s, skipping, TRUE);
+    uschar *sub = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok);
     if (sub == NULL) goto EXPAND_FAILED;
     s++;
 
@@ -5552,7 +5595,7 @@ while (*s != 0)
 
       case EOP_EXPAND:
         {
-        uschar *expanded = expand_string_internal(sub, FALSE, NULL, skipping, TRUE);
+        uschar *expanded = expand_string_internal(sub, FALSE, NULL, skipping, TRUE, &resetok);
         if (expanded == NULL)
           {
           expand_string_message =
@@ -5656,6 +5699,22 @@ while (*s != 0)
         continue;
         }
 
+      /* Convert octets outside 0x21..0x7E to \xXX form */
+
+      case EOP_HEXQUOTE:
+       {
+        uschar *t = sub - 1;
+        while (*(++t) != 0)
+          {
+          if (*t < 0x21 || 0x7E < *t)
+            yield = string_cat(yield, &size, &ptr,
+             string_sprintf("\\x%02x", *t), 4);
+         else
+           yield = string_cat(yield, &size, &ptr, t, 1);
+          }
+       continue;
+       }
+
       /* count the number of list elements */
 
       case EOP_LISTCOUNT:
@@ -5680,7 +5739,7 @@ while (*s != 0)
        uschar * list;
        int sep = 0;
        uschar * item;
-       uschar * suffix = "";
+       uschar * suffix = US"";
        BOOL needsep = FALSE;
        uschar buffer[256];
 
@@ -5694,10 +5753,10 @@ while (*s != 0)
          }
        else switch(*arg)       /* specific list-type version */
          {
-         case 'a': t = tree_search(addresslist_anchor,   sub); suffix = "_a"; break;
-         case 'd': t = tree_search(domainlist_anchor,    sub); suffix = "_d"; break;
-         case 'h': t = tree_search(hostlist_anchor,      sub); suffix = "_h"; break;
-         case 'l': t = tree_search(localpartlist_anchor, sub); suffix = "_l"; break;
+         case 'a': t = tree_search(addresslist_anchor,   sub); suffix = US"_a"; break;
+         case 'd': t = tree_search(domainlist_anchor,    sub); suffix = US"_d"; break;
+         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");
            goto EXPAND_FAILED;
@@ -5728,7 +5787,7 @@ while (*s != 0)
          if (*item == '+')     /* list item is itself a named list */
            {
            uschar * sub = string_sprintf("${listnamed%s:%s}", suffix, item);
-           item = expand_string_internal(sub, FALSE, NULL, FALSE, TRUE);
+           item = expand_string_internal(sub, FALSE, NULL, FALSE, TRUE, &resetok);
            }
          else if (sep != ':')  /* item from non-colon-sep list, re-quote for colon list-separator */
            {
@@ -5741,13 +5800,13 @@ while (*s != 0)
              if (*cp++ == ':') /* colon in a non-colon-sep list item, needs doubling */
                {
                 yield = string_cat(yield, &size, &ptr, US"::", 2);
-               item = cp;
+               item = (uschar *)cp;
                }
              else              /* sep in item; should already be doubled; emit once */
                {
                 yield = string_cat(yield, &size, &ptr, (uschar *)tok, 1);
                if (*cp == sep) cp++;
-               item = cp;
+               item = (uschar *)cp;
                }
              }
            }
@@ -6310,7 +6369,7 @@ while (*s != 0)
   store instead of copying. Many expansion strings contain just one reference,
   so this is a useful optimization, especially for humungous headers
   ($message_headers). */
-
+                                               /*{*/
   if (*s++ == '}')
     {
     int len;
@@ -6373,6 +6432,8 @@ 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 + ptr + 1);
+else if (resetok_p) *resetok_p = FALSE;
+
 DEBUG(D_expand)
   {
   debug_printf("expanding: %.*s\n   result: %s\n", (int)(s - string), string,
@@ -6402,6 +6463,7 @@ DEBUG(D_expand)
   debug_printf("   error message: %s\n", expand_string_message);
   if (expand_string_forcedfail) debug_printf("failure was forced\n");
   }
+if (resetok_p) *resetok_p = resetok;
 return NULL;
 }
 
@@ -6420,7 +6482,7 @@ expand_string(uschar *string)
 search_find_defer = FALSE;
 malformed_header = FALSE;
 return (Ustrpbrk(string, "$\\") == NULL)? string :
-  expand_string_internal(string, FALSE, NULL, FALSE, TRUE);
+  expand_string_internal(string, FALSE, NULL, FALSE, TRUE, NULL);
 }
 
 
@@ -6514,17 +6576,17 @@ else
     default:
       break;
     case 'k':
-      if (value > LLONG_MAX/1024 || value < LLONG_MIN/1024) errno = ERANGE;
+      if (value > EXIM_ARITH_MAX/1024 || value < EXIM_ARITH_MIN/1024) errno = ERANGE;
       else value *= 1024;
       endptr++;
       break;
     case 'm':
-      if (value > LLONG_MAX/(1024*1024) || value < LLONG_MIN/(1024*1024)) errno = ERANGE;
+      if (value > EXIM_ARITH_MAX/(1024*1024) || value < EXIM_ARITH_MIN/(1024*1024)) errno = ERANGE;
       else value *= 1024*1024;
       endptr++;
       break;
     case 'g':
-      if (value > LLONG_MAX/(1024*1024*1024) || value < LLONG_MIN/(1024*1024*1024)) errno = ERANGE;
+      if (value > EXIM_ARITH_MAX/(1024*1024*1024) || value < EXIM_ARITH_MIN/(1024*1024*1024)) errno = ERANGE;
       else value *= 1024*1024*1024;
       endptr++;
       break;
@@ -6534,7 +6596,7 @@ else
   else
     {
     while (isspace(*endptr)) endptr++;
-    if (*endptr == 0) return (int)value;
+    if (*endptr == 0) return value;
     }
   }
 
@@ -6607,6 +6669,9 @@ for (i = 1; i < argc; i++)
       #ifdef LOOKUP_PGSQL
       pgsql_servers = argv[i];
       #endif
+      #ifdef EXPERIMENTAL_REDIS
+      redis_servers = argv[i];
+      #endif
       }
   #ifdef EXIM_PERL
   else opt_perl_startup = argv[i];