Check maximum (folded) line length in default spec file data ACL and smtp transport...
[exim.git] / src / src / expand.c
index 786d4279cbca4c667cc7eef0b03287c89a9a9f05..f9849e67c7e2de822b361d6d415187816c43b342 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2012 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -13,7 +13,8 @@
 
 /* Recursively called function */
 
-static uschar *expand_string_internal(uschar *, BOOL, uschar **, BOOL, BOOL);
+static uschar *expand_string_internal(const uschar *, BOOL, const uschar **, BOOL, BOOL, BOOL *);
+static int_eximarith_t expanded_string_integer(const uschar *, BOOL);
 
 #ifdef STAND_ALONE
 #ifndef SUPPORT_CRYPTEQ
@@ -93,7 +94,6 @@ bcrypt ({CRYPT}$2a$).
 
 
 
-
 /*************************************************
 *            Local statics and tables            *
 *************************************************/
@@ -103,13 +103,19 @@ alphabetical order. */
 
 static uschar *item_table[] = {
   US"acl",
+  US"certextract",
   US"dlfunc",
+  US"env",
   US"extract",
   US"filter",
   US"hash",
   US"hmac",
   US"if",
+#ifdef EXPERIMENTAL_INTERNATIONAL
+  US"imapfolder",
+#endif
   US"length",
+  US"listextract",
   US"lookup",
   US"map",
   US"nhash",
@@ -121,18 +127,25 @@ static uschar *item_table[] = {
   US"reduce",
   US"run",
   US"sg",
+  US"sort",
   US"substr",
   US"tr" };
 
 enum {
   EITEM_ACL,
+  EITEM_CERTEXTRACT,
   EITEM_DLFUNC,
+  EITEM_ENV,
   EITEM_EXTRACT,
   EITEM_FILTER,
   EITEM_HASH,
   EITEM_HMAC,
   EITEM_IF,
+#ifdef EXPERIMENTAL_INTERNATIONAL
+  EITEM_IMAPFOLDER,
+#endif
   EITEM_LENGTH,
+  EITEM_LISTEXTRACT,
   EITEM_LOOKUP,
   EITEM_MAP,
   EITEM_NHASH,
@@ -144,6 +157,7 @@ enum {
   EITEM_REDUCE,
   EITEM_RUN,
   EITEM_SG,
+  EITEM_SORT,
   EITEM_SUBSTR,
   EITEM_TR };
 
@@ -158,7 +172,14 @@ static uschar *op_table_underscore[] = {
   US"quote_local_part",
   US"reverse_ip",
   US"time_eval",
-  US"time_interval"};
+  US"time_interval"
+#ifdef EXPERIMENTAL_INTERNATIONAL
+ ,US"utf8_domain_from_alabel",
+  US"utf8_domain_to_alabel",
+  US"utf8_localpart_from_alabel",
+  US"utf8_localpart_to_alabel"
+#endif
+  };
 
 enum {
   EOP_FROM_UTF8,
@@ -166,7 +187,14 @@ enum {
   EOP_QUOTE_LOCAL_PART,
   EOP_REVERSE_IP,
   EOP_TIME_EVAL,
-  EOP_TIME_INTERVAL };
+  EOP_TIME_INTERVAL
+#ifdef EXPERIMENTAL_INTERNATIONAL
+ ,EOP_UTF8_DOMAIN_FROM_ALABEL,
+  EOP_UTF8_DOMAIN_TO_ALABEL,
+  EOP_UTF8_LOCALPART_FROM_ALABEL,
+  EOP_UTF8_LOCALPART_TO_ALABEL
+#endif
+  };
 
 static uschar *op_table_main[] = {
   US"address",
@@ -181,6 +209,7 @@ static uschar *op_table_main[] = {
   US"h",
   US"hash",
   US"hex2b64",
+  US"hexquote",
   US"l",
   US"lc",
   US"length",
@@ -197,14 +226,16 @@ static uschar *op_table_main[] = {
   US"rxquote",
   US"s",
   US"sha1",
+  US"sha256",
   US"stat",
   US"str2b64",
   US"strlen",
   US"substr",
-  US"uc" };
+  US"uc",
+  US"utf8clean" };
 
 enum {
-  EOP_ADDRESS =  sizeof(op_table_underscore)/sizeof(uschar *),
+  EOP_ADDRESS =  nelem(op_table_underscore),
   EOP_ADDRESSES,
   EOP_BASE62,
   EOP_BASE62D,
@@ -216,6 +247,7 @@ enum {
   EOP_H,
   EOP_HASH,
   EOP_HEX2B64,
+  EOP_HEXQUOTE,
   EOP_L,
   EOP_LC,
   EOP_LENGTH,
@@ -232,11 +264,13 @@ enum {
   EOP_RXQUOTE,
   EOP_S,
   EOP_SHA1,
+  EOP_SHA256,
   EOP_STAT,
   EOP_STR2B64,
   EOP_STRLEN,
   EOP_SUBSTR,
-  EOP_UC };
+  EOP_UC,
+  EOP_UTF8CLEAN };
 
 
 /* Table of condition names, and corresponding switch numbers. The names must
@@ -335,25 +369,9 @@ enum {
 };
 
 
-/* Type for main variable table */
-
-typedef struct {
-  const char *name;
-  int         type;
-  void       *value;
-} var_entry;
-
-/* Type for entries pointing to address/length pairs. Not currently
-in use. */
-
-typedef struct {
-  uschar **address;
-  int  *length;
-} alblock;
-
 /* Types of table entry */
 
-enum {
+enum vtypes {
   vtype_int,            /* value is address of int */
   vtype_filter_int,     /* ditto, but recognized only when filtering */
   vtype_ino,            /* value is address of ino_t (not always an int) */
@@ -381,11 +399,28 @@ enum {
   vtype_host_lookup,    /* value not used; get host name */
   vtype_load_avg,       /* value not used; result is int from os_getloadavg */
   vtype_pspace,         /* partition space; value is T/F for spool/log */
-  vtype_pinodes         /* partition inodes; value is T/F for spool/log */
+  vtype_pinodes,        /* partition inodes; value is T/F for spool/log */
+  vtype_cert           /* SSL certificate */
   #ifndef DISABLE_DKIM
   ,vtype_dkim           /* Lookup of value in DKIM signature */
   #endif
-  };
+};
+
+/* Type for main variable table */
+
+typedef struct {
+  const char *name;
+  enum vtypes type;
+  void       *value;
+} var_entry;
+
+/* Type for entries pointing to address/length pairs. Not currently
+in use. */
+
+typedef struct {
+  uschar **address;
+  int  *length;
+} alblock;
 
 static uschar * fn_recipients(void);
 
@@ -408,6 +443,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 },
@@ -426,8 +462,11 @@ static var_entry var_table[] = {
   { "bounce_return_size_limit", vtype_int,    &bounce_return_size_limit },
   { "caller_gid",          vtype_gid,         &real_gid },
   { "caller_uid",          vtype_uid,         &real_uid },
+  { "callout_address",     vtype_stringptr,   &callout_address },
   { "compile_date",        vtype_stringptr,   &version_date },
   { "compile_number",      vtype_stringptr,   &version_cnumber },
+  { "config_dir",          vtype_stringptr,   &config_main_directory },
+  { "config_file",         vtype_stringptr,   &config_main_filename },
   { "csa_status",          vtype_stringptr,   &csa_status },
 #ifdef EXPERIMENTAL_DCC
   { "dcc_header",          vtype_stringptr,   &dcc_header },
@@ -458,6 +497,13 @@ 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_domain_policy", vtype_stringptr,   &dmarc_domain_policy },
+  { "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 },
@@ -465,9 +511,18 @@ static var_entry var_table[] = {
   { "dnslist_value",       vtype_stringptr,   &dnslist_value },
   { "domain",              vtype_stringptr,   &deliver_domain },
   { "domain_data",         vtype_stringptr,   &deliver_domain_data },
+#ifdef EXPERIMENTAL_EVENT
+  { "event_data",          vtype_stringptr,   &event_data },
+
+  /*XXX want to use generic vars for as many of these as possible*/
+  { "event_defer_errno",   vtype_int,         &event_defer_errno },
+
+  { "event_name",          vtype_stringptr,   &event_name },
+#endif
   { "exim_gid",            vtype_gid,         &exim_gid },
   { "exim_path",           vtype_stringptr,   &exim_path },
   { "exim_uid",            vtype_uid,         &exim_uid },
+  { "exim_version",        vtype_stringptr,   &version_string },
 #ifdef WITH_OLD_DEMIME
   { "found_extension",     vtype_stringptr,   &found_extension },
 #endif
@@ -478,6 +533,7 @@ static var_entry var_table[] = {
   { "host_data",           vtype_stringptr,   &host_data },
   { "host_lookup_deferred",vtype_int,         &host_lookup_deferred },
   { "host_lookup_failed",  vtype_int,         &host_lookup_failed },
+  { "host_port",           vtype_int,         &deliver_host_port },
   { "inode",               vtype_ino,         &deliver_inode },
   { "interface_address",   vtype_stringptr,   &interface_address },
   { "interface_port",      vtype_int,         &interface_port },
@@ -496,6 +552,7 @@ static var_entry var_table[] = {
   { "localhost_number",    vtype_int,         &host_number },
   { "log_inodes",          vtype_pinodes,     (void *)FALSE },
   { "log_space",           vtype_pspace,      (void *)FALSE },
+  { "lookup_dnssec_authenticated",vtype_stringptr,&lookup_dnssec_authenticated},
   { "mailstore_basename",  vtype_stringptr,   &mailstore_basename },
 #ifdef WITH_CONTENT_SCAN
   { "malware_name",        vtype_stringptr,   &malware_name },
@@ -511,6 +568,9 @@ static var_entry var_table[] = {
   { "message_id",          vtype_stringptr,   &message_id },
   { "message_linecount",   vtype_int,         &message_linecount },
   { "message_size",        vtype_int,         &message_size },
+#ifdef EXPERIMENTAL_INTERNATIONAL
+  { "message_smtputf8",    vtype_bool,        &message_smtputf8 },
+#endif
 #ifdef WITH_CONTENT_SCAN
   { "mime_anomaly_level",  vtype_int,         &mime_anomaly_level },
   { "mime_anomaly_text",   vtype_stringptr,   &mime_anomaly_text },
@@ -547,6 +607,13 @@ 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_address",  vtype_stringptr,   &proxy_host_address },
+  { "proxy_host_port",     vtype_int,         &proxy_host_port },
+  { "proxy_session",       vtype_bool,        &proxy_session },
+  { "proxy_target_address",vtype_stringptr,   &proxy_target_address },
+  { "proxy_target_port",   vtype_int,         &proxy_target_port },
+#endif
   { "prvscheck_address",   vtype_stringptr,   &prvscheck_address },
   { "prvscheck_keynum",    vtype_stringptr,   &prvscheck_keynum },
   { "prvscheck_result",    vtype_stringptr,   &prvscheck_result },
@@ -571,6 +638,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 },
@@ -579,6 +647,7 @@ static var_entry var_table[] = {
   { "sender_address_local_part", vtype_localpart, &sender_address },
   { "sender_data",         vtype_stringptr,   &sender_data },
   { "sender_fullhost",     vtype_stringptr,   &sender_fullhost },
+  { "sender_helo_dnssec",  vtype_bool,        &sender_helo_dnssec },
   { "sender_helo_name",    vtype_stringptr,   &sender_helo_name },
   { "sender_host_address", vtype_stringptr,   &sender_host_address },
   { "sender_host_authenticated",vtype_stringptr, &sender_host_authenticated },
@@ -609,6 +678,7 @@ static var_entry var_table[] = {
   { "sn8",                 vtype_filter_int,  &filter_sn[8] },
   { "sn9",                 vtype_filter_int,  &filter_sn[9] },
 #ifdef WITH_CONTENT_SCAN
+  { "spam_action",         vtype_stringptr,   &spam_action },
   { "spam_bar",            vtype_stringptr,   &spam_bar },
   { "spam_report",         vtype_stringptr,   &spam_report },
   { "spam_score",          vtype_stringptr,   &spam_score },
@@ -642,20 +712,32 @@ 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_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) && !defined(USE_GNUTLS)
+#if defined(SUPPORT_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 },
+#ifdef EXPERIMENTAL_DANE
+  { "tls_out_dane",        vtype_bool,        &tls_out.dane_verified },
+#endif
+  { "tls_out_ocsp",        vtype_int,         &tls_out.ocsp },
+  { "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) && !defined(USE_GNUTLS)
+#if defined(SUPPORT_TLS)
   { "tls_out_sni",         vtype_stringptr,   &tls_out.sni },
 #endif
+#ifdef EXPERIMENTAL_DANE
+  { "tls_out_tlsa_usage",  vtype_int,         &tls_out.tlsa_usage },
+#endif
 
   { "tls_peerdn",          vtype_stringptr,   &tls_in.peerdn },        /* mind the alphabetical order! */
-#if defined(SUPPORT_TLS) && !defined(USE_GNUTLS)
+#if defined(SUPPORT_TLS)
   { "tls_sni",             vtype_stringptr,   &tls_in.sni },   /* mind the alphabetical order! */
 #endif
 
@@ -667,7 +749,9 @@ static var_entry var_table[] = {
   { "tod_logfile",         vtype_todlf,       NULL },
   { "tod_zone",            vtype_todzone,     NULL },
   { "tod_zulu",            vtype_todzulu,     NULL },
+  { "transport_name",      vtype_stringptr,   &transport_name },
   { "value",               vtype_stringptr,   &lookup_value },
+  { "verify_mode",         vtype_stringptr,   &verify_mode },
   { "version_number",      vtype_stringptr,   &version_string },
   { "warn_message_delay",  vtype_stringptr,   &warnmsg_delay },
   { "warn_message_recipient",vtype_stringptr, &warnmsg_recipients },
@@ -677,7 +761,7 @@ static var_entry var_table[] = {
   { "warnmsg_recipients",  vtype_stringptr,   &warnmsg_recipients }
 };
 
-static int var_table_size = sizeof(var_table)/sizeof(var_entry);
+static int var_table_size = nelem(var_table);
 static uschar var_buffer[256];
 static BOOL malformed_header;
 
@@ -864,7 +948,9 @@ vaguely_random_number(int max)
 #ifdef HAVE_ARC4RANDOM
       /* 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 */
@@ -912,8 +998,8 @@ Note: The test for *s != 0 in the while loop is necessary because
 Ustrchr() yields non-NULL if the character is zero (which is not something
 I expected). */
 
-static uschar *
-read_name(uschar *name, int max, uschar *s, uschar *extras)
+static const uschar *
+read_name(uschar *name, int max, const uschar *s, uschar *extras)
 {
 int ptr = 0;
 while (*s != 0 && (isalnum(*s) || Ustrchr(extras, *s) != NULL))
@@ -946,8 +1032,8 @@ Arguments:
 Returns:    a pointer to the first character after the header name
 */
 
-static uschar *
-read_header_name(uschar *name, int max, uschar *s)
+static const uschar *
+read_header_name(uschar *name, int max, const uschar *s)
 {
 int prelen = Ustrchr(name, '_') - name + 1;
 int ptr = Ustrlen(name) - prelen;
@@ -984,6 +1070,14 @@ while (isdigit(*s)) *n = *n * 10 + (*s++ - '0');
 return s;
 }
 
+static const uschar *
+read_cnumber(int *n, const uschar *s)
+{
+*n = 0;
+while (isdigit(*s)) *n = *n * 10 + (*s++ - '0');
+return s;
+}
+
 
 
 /*************************************************
@@ -1001,7 +1095,7 @@ Returns:    NULL if the subfield was not found, or
 */
 
 static uschar *
-expand_getkeyed(uschar *key, uschar *s)
+expand_getkeyed(uschar *key, const uschar *s)
 {
 int length = Ustrlen(key);
 while (isspace(*s)) s++;
@@ -1012,7 +1106,7 @@ while (*s != 0)
   {
   int dkeylength;
   uschar *data;
-  uschar *dkey = s;
+  const uschar *dkey = s;
 
   while (*s != 0 && *s != '=' && !isspace(*s)) s++;
   dkeylength = s - dkey;
@@ -1031,6 +1125,23 @@ return NULL;
 
 
 
+static var_entry *
+find_var_ent(uschar * name)
+{
+int first = 0;
+int last = var_table_size;
+
+while (last > first)
+  {
+  int middle = (first + last)/2;
+  int c = Ustrcmp(name, var_table[middle].name);
+
+  if (c > 0) { first = middle + 1; continue; }
+  if (c < 0) { last = middle; continue; }
+  return &var_table[middle];
+  }
+return NULL;
+}
 
 /*************************************************
 *   Extract numbered subfield from string        *
@@ -1105,6 +1216,90 @@ return fieldtext;
 }
 
 
+static uschar *
+expand_getlistele(int field, const uschar * list)
+{
+const uschar * tlist= list;
+int sep= 0;
+uschar dummy;
+
+if(field<0)
+  {
+  for(field++; string_nextinlist(&tlist, &sep, &dummy, 1); ) field++;
+  sep= 0;
+  }
+if(field==0) return NULL;
+while(--field>0 && (string_nextinlist(&list, &sep, &dummy, 1))) ;
+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
+typedef struct
+{
+uschar * name;
+int      namelen;
+uschar * (*getfn)(void * cert, uschar * mod);
+} certfield;
+static certfield certfields[] =
+{                      /* linear search; no special order */
+  { US"version",        7,  &tls_cert_version },
+  { US"serial_number",  13, &tls_cert_serial_number },
+  { US"subject",        7,  &tls_cert_subject },
+  { US"notbefore",      9,  &tls_cert_not_before },
+  { US"notafter",       8,  &tls_cert_not_after },
+  { US"issuer",                 6,  &tls_cert_issuer },
+  { US"signature",      9,  &tls_cert_signature },
+  { US"sig_algorithm",  13, &tls_cert_signature_algorithm },
+  { US"subj_altname",    12, &tls_cert_subject_altname },
+  { US"ocsp_uri",       8,  &tls_cert_ocsp_uri },
+  { US"crl_uri",        7,  &tls_cert_crl_uri },
+};
+
+static uschar *
+expand_getcertele(uschar * field, uschar * certvar)
+{
+var_entry * vp;
+certfield * cp;
+
+if (!(vp = find_var_ent(certvar)))
+  {
+  expand_string_message = 
+    string_sprintf("no variable named \"%s\"", certvar);
+  return NULL;          /* Unknown variable name */
+  }
+/* NB this stops us passing certs around in variable.  Might
+want to do that in future */
+if (vp->type != vtype_cert)
+  {
+  expand_string_message = 
+    string_sprintf("\"%s\" is not a certificate", certvar);
+  return NULL;          /* Unknown variable name */
+  }
+if (!*(void **)vp->value)
+  return NULL;
+
+if (*field >= '0' && *field <= '9')
+  return tls_cert_ext_by_oid(*(void **)vp->value, field, 0);
+
+for(cp = certfields;
+    cp < certfields + nelem(certfields);
+    cp++)
+  if (Ustrncmp(cp->name, field, cp->namelen) == 0)
+    {
+    uschar * modifier = *(field += cp->namelen) == ','
+      ? ++field : NULL;
+    return (*cp->getfn)( *(void **)vp->value, modifier );
+    }
+
+expand_string_message = 
+  string_sprintf("bad field selector \"%s\" for certextract", field);
+return NULL;
+}
+#endif /*SUPPORT_TLS*/
 
 /*************************************************
 *        Extract a substring from a string       *
@@ -1261,7 +1456,7 @@ unsigned long int total = 0; /* no overflow */
 
 while (*s != 0)
   {
-  if (i == 0) i = sizeof(prime)/sizeof(int) - 1;
+  if (i == 0) i = nelem(prime) - 1;
   total += prime[i--] * (unsigned int)(*s++);
   }
 
@@ -1501,8 +1696,10 @@ Returns:        NULL if the variable does not exist, or
 static uschar *
 find_variable(uschar *name, BOOL exists_only, BOOL skipping, int *newsize)
 {
-int first = 0;
-int last = var_table_size;
+var_entry * vp;
+uschar *s, *domain;
+uschar **ss;
+void * val;
 
 /* Handle ACL variables, whose names are of the form acl_cxxx or acl_mxxx.
 Originally, xxx had to be a number in the range 0-9 (later 0-19), but from
@@ -1530,208 +1727,212 @@ if (Ustrncmp(name, "auth", 4) == 0)
   uschar *endptr;
   int n = Ustrtoul(name + 4, &endptr, 10);
   if (*endptr == 0 && n != 0 && n <= AUTH_VARS)
-    return (auth_vars[n-1] == NULL)? US"" : auth_vars[n-1];
+    return !auth_vars[n-1] ? US"" : auth_vars[n-1];
+  }
+else if (Ustrncmp(name, "regex", 5) == 0)
+  {
+  uschar *endptr;
+  int n = Ustrtoul(name + 5, &endptr, 10);
+  if (*endptr == 0 && n != 0 && n <= REGEX_VARS)
+    return !regex_vars[n-1] ? US"" : regex_vars[n-1];
   }
 
 /* For all other variables, search the table */
 
-while (last > first)
-  {
-  uschar *s, *domain;
-  uschar **ss;
-  int middle = (first + last)/2;
-  int c = Ustrcmp(name, var_table[middle].name);
+if (!(vp = find_var_ent(name)))
+  return NULL;          /* Unknown variable name */
 
-  if (c > 0) { first = middle + 1; continue; }
-  if (c < 0) { last = middle; continue; }
+/* Found an existing variable. If in skipping state, the value isn't needed,
+and we want to avoid processing (such as looking up the host name). */
 
-  /* Found an existing variable. If in skipping state, the value isn't needed,
-  and we want to avoid processing (such as looking up the host name). */
+if (skipping)
+  return US"";
 
-  if (skipping) return US"";
-
-  switch (var_table[middle].type)
+val = vp->value;
+switch (vp->type)
+  {
+  case vtype_filter_int:
+  if (!filter_running) return NULL;
+  /* Fall through */
+  /* VVVVVVVVVVVV */
+  case vtype_int:
+  sprintf(CS var_buffer, "%d", *(int *)(val)); /* Integer */
+  return var_buffer;
+
+  case vtype_ino:
+  sprintf(CS var_buffer, "%ld", (long int)(*(ino_t *)(val))); /* Inode */
+  return var_buffer;
+
+  case vtype_gid:
+  sprintf(CS var_buffer, "%ld", (long int)(*(gid_t *)(val))); /* gid */
+  return var_buffer;
+
+  case vtype_uid:
+  sprintf(CS var_buffer, "%ld", (long int)(*(uid_t *)(val))); /* uid */
+  return var_buffer;
+
+  case vtype_bool:
+  sprintf(CS var_buffer, "%s", *(BOOL *)(val) ? "yes" : "no"); /* bool */
+  return var_buffer;
+
+  case vtype_stringptr:                      /* Pointer to string */
+  s = *((uschar **)(val));
+  return (s == NULL)? US"" : s;
+
+  case vtype_pid:
+  sprintf(CS var_buffer, "%d", (int)getpid()); /* pid */
+  return var_buffer;
+
+  case vtype_load_avg:
+  sprintf(CS var_buffer, "%d", OS_GETLOADAVG()); /* load_average */
+  return var_buffer;
+
+  case vtype_host_lookup:                    /* Lookup if not done so */
+  if (sender_host_name == NULL && sender_host_address != NULL &&
+      !host_lookup_failed && host_name_lookup() == OK)
+    host_build_sender_fullhost();
+  return (sender_host_name == NULL)? US"" : sender_host_name;
+
+  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 (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;
+
+  case vtype_domain:                         /* Get domain from address */
+  s = *((uschar **)(val));
+  if (s == NULL) return US"";
+  domain = Ustrrchr(s, '@');
+  return (domain == NULL)? US"" : domain + 1;
+
+  case vtype_msgheaders:
+  return find_header(NULL, exists_only, newsize, FALSE, NULL);
+
+  case vtype_msgheaders_raw:
+  return find_header(NULL, exists_only, newsize, TRUE, NULL);
+
+  case vtype_msgbody:                        /* Pointer to msgbody string */
+  case vtype_msgbody_end:                    /* Ditto, the end of the msg */
+  ss = (uschar **)(val);
+  if (*ss == NULL && deliver_datafile >= 0)  /* Read body when needed */
     {
-    case vtype_filter_int:
-    if (!filter_running) return NULL;
-    /* Fall through */
-    /* VVVVVVVVVVVV */
-    case vtype_int:
-    sprintf(CS var_buffer, "%d", *(int *)(var_table[middle].value)); /* Integer */
-    return var_buffer;
-
-    case vtype_ino:
-    sprintf(CS var_buffer, "%ld", (long int)(*(ino_t *)(var_table[middle].value))); /* Inode */
-    return var_buffer;
-
-    case vtype_gid:
-    sprintf(CS var_buffer, "%ld", (long int)(*(gid_t *)(var_table[middle].value))); /* gid */
-    return var_buffer;
-
-    case vtype_uid:
-    sprintf(CS var_buffer, "%ld", (long int)(*(uid_t *)(var_table[middle].value))); /* uid */
-    return var_buffer;
-
-    case vtype_bool:
-    sprintf(CS var_buffer, "%s", *(BOOL *)(var_table[middle].value) ? "yes" : "no"); /* bool */
-    return var_buffer;
-
-    case vtype_stringptr:                      /* Pointer to string */
-    s = *((uschar **)(var_table[middle].value));
-    return (s == NULL)? US"" : s;
-
-    case vtype_pid:
-    sprintf(CS var_buffer, "%d", (int)getpid()); /* pid */
-    return var_buffer;
-
-    case vtype_load_avg:
-    sprintf(CS var_buffer, "%d", OS_GETLOADAVG()); /* load_average */
-    return var_buffer;
-
-    case vtype_host_lookup:                    /* Lookup if not done so */
-    if (sender_host_name == NULL && sender_host_address != NULL &&
-        !host_lookup_failed && host_name_lookup() == OK)
-      host_build_sender_fullhost();
-    return (sender_host_name == NULL)? US"" : sender_host_name;
-
-    case vtype_localpart:                      /* Get local part from address */
-    s = *((uschar **)(var_table[middle].value));
-    if (s == NULL) return US"";
-    domain = Ustrrchr(s, '@');
-    if (domain == NULL) 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));
-    Ustrncpy(var_buffer, s, domain - s);
-    var_buffer[domain - s] = 0;
-    return var_buffer;
-
-    case vtype_domain:                         /* Get domain from address */
-    s = *((uschar **)(var_table[middle].value));
-    if (s == NULL) return US"";
-    domain = Ustrrchr(s, '@');
-    return (domain == NULL)? US"" : domain + 1;
-
-    case vtype_msgheaders:
-    return find_header(NULL, exists_only, newsize, FALSE, NULL);
-
-    case vtype_msgheaders_raw:
-    return find_header(NULL, exists_only, newsize, TRUE, NULL);
-
-    case vtype_msgbody:                        /* Pointer to msgbody string */
-    case vtype_msgbody_end:                    /* Ditto, the end of the msg */
-    ss = (uschar **)(var_table[middle].value);
-    if (*ss == NULL && deliver_datafile >= 0)  /* Read body when needed */
+    uschar *body;
+    off_t start_offset = SPOOL_DATA_START_OFFSET;
+    int len = message_body_visible;
+    if (len > message_size) len = message_size;
+    *ss = body = store_malloc(len+1);
+    body[0] = 0;
+    if (vp->type == vtype_msgbody_end)
       {
-      uschar *body;
-      off_t start_offset = SPOOL_DATA_START_OFFSET;
-      int len = message_body_visible;
-      if (len > message_size) len = message_size;
-      *ss = body = store_malloc(len+1);
-      body[0] = 0;
-      if (var_table[middle].type == vtype_msgbody_end)
-        {
-        struct stat statbuf;
-        if (fstat(deliver_datafile, &statbuf) == 0)
-          {
-          start_offset = statbuf.st_size - len;
-          if (start_offset < SPOOL_DATA_START_OFFSET)
-            start_offset = SPOOL_DATA_START_OFFSET;
-          }
-        }
-      lseek(deliver_datafile, start_offset, SEEK_SET);
-      len = read(deliver_datafile, body, len);
-      if (len > 0)
-        {
-        body[len] = 0;
-        if (message_body_newlines)   /* Separate loops for efficiency */
-          {
-          while (len > 0)
-            { if (body[--len] == 0) body[len] = ' '; }
-          }
-        else
-          {
-          while (len > 0)
-            { if (body[--len] == '\n' || body[len] == 0) body[len] = ' '; }
-          }
-        }
+      struct stat statbuf;
+      if (fstat(deliver_datafile, &statbuf) == 0)
+       {
+       start_offset = statbuf.st_size - len;
+       if (start_offset < SPOOL_DATA_START_OFFSET)
+         start_offset = SPOOL_DATA_START_OFFSET;
+       }
+      }
+    lseek(deliver_datafile, start_offset, SEEK_SET);
+    len = read(deliver_datafile, body, len);
+    if (len > 0)
+      {
+      body[len] = 0;
+      if (message_body_newlines)   /* Separate loops for efficiency */
+       {
+       while (len > 0)
+         { if (body[--len] == 0) body[len] = ' '; }
+       }
+      else
+       {
+       while (len > 0)
+         { if (body[--len] == '\n' || body[len] == 0) body[len] = ' '; }
+       }
       }
-    return (*ss == NULL)? US"" : *ss;
+    }
+  return (*ss == NULL)? US"" : *ss;
 
-    case vtype_todbsdin:                       /* BSD inbox time of day */
-    return tod_stamp(tod_bsdin);
+  case vtype_todbsdin:                       /* BSD inbox time of day */
+  return tod_stamp(tod_bsdin);
 
-    case vtype_tode:                           /* Unix epoch time of day */
-    return tod_stamp(tod_epoch);
+  case vtype_tode:                           /* Unix epoch time of day */
+  return tod_stamp(tod_epoch);
 
-    case vtype_todel:                          /* Unix epoch/usec time of day */
-    return tod_stamp(tod_epoch_l);
+  case vtype_todel:                          /* Unix epoch/usec time of day */
+  return tod_stamp(tod_epoch_l);
 
-    case vtype_todf:                           /* Full time of day */
-    return tod_stamp(tod_full);
+  case vtype_todf:                           /* Full time of day */
+  return tod_stamp(tod_full);
 
-    case vtype_todl:                           /* Log format time of day */
-    return tod_stamp(tod_log_bare);            /* (without timezone) */
+  case vtype_todl:                           /* Log format time of day */
+  return tod_stamp(tod_log_bare);            /* (without timezone) */
 
-    case vtype_todzone:                        /* Time zone offset only */
-    return tod_stamp(tod_zone);
+  case vtype_todzone:                        /* Time zone offset only */
+  return tod_stamp(tod_zone);
 
-    case vtype_todzulu:                        /* Zulu time */
-    return tod_stamp(tod_zulu);
+  case vtype_todzulu:                        /* Zulu time */
+  return tod_stamp(tod_zulu);
 
-    case vtype_todlf:                          /* Log file datestamp tod */
-    return tod_stamp(tod_log_datestamp_daily);
+  case vtype_todlf:                          /* Log file datestamp tod */
+  return tod_stamp(tod_log_datestamp_daily);
 
-    case vtype_reply:                          /* Get reply address */
-    s = find_header(US"reply-to:", exists_only, newsize, TRUE,
-      headers_charset);
-    if (s != NULL) while (isspace(*s)) s++;
-    if (s == NULL || *s == 0)
-      {
-      *newsize = 0;                            /* For the *s==0 case */
-      s = find_header(US"from:", exists_only, newsize, TRUE, headers_charset);
-      }
-    if (s != NULL)
-      {
-      uschar *t;
-      while (isspace(*s)) s++;
-      for (t = s; *t != 0; t++) if (*t == '\n') *t = ' ';
-      while (t > s && isspace(t[-1])) t--;
-      *t = 0;
-      }
-    return (s == NULL)? US"" : s;
+  case vtype_reply:                          /* Get reply address */
+  s = find_header(US"reply-to:", exists_only, newsize, TRUE,
+    headers_charset);
+  if (s != NULL) while (isspace(*s)) s++;
+  if (s == NULL || *s == 0)
+    {
+    *newsize = 0;                            /* For the *s==0 case */
+    s = find_header(US"from:", exists_only, newsize, TRUE, headers_charset);
+    }
+  if (s != NULL)
+    {
+    uschar *t;
+    while (isspace(*s)) s++;
+    for (t = s; *t != 0; t++) if (*t == '\n') *t = ' ';
+    while (t > s && isspace(t[-1])) t--;
+    *t = 0;
+    }
+  return (s == NULL)? US"" : s;
 
-    case vtype_string_func:
-      {
-      uschar * (*fn)() = var_table[middle].value;
-      return fn();
-      }
+  case vtype_string_func:
+    {
+    uschar * (*fn)() = val;
+    return fn();
+    }
 
-    case vtype_pspace:
-      {
-      int inodes;
-      sprintf(CS var_buffer, "%d",
-        receive_statvfs(var_table[middle].value == (void *)TRUE, &inodes));
-      }
-    return var_buffer;
+  case vtype_pspace:
+    {
+    int inodes;
+    sprintf(CS var_buffer, "%d",
+      receive_statvfs(val == (void *)TRUE, &inodes));
+    }
+  return var_buffer;
 
-    case vtype_pinodes:
-      {
-      int inodes;
-      (void) receive_statvfs(var_table[middle].value == (void *)TRUE, &inodes);
-      sprintf(CS var_buffer, "%d", inodes);
-      }
-    return var_buffer;
+  case vtype_pinodes:
+    {
+    int inodes;
+    (void) receive_statvfs(val == (void *)TRUE, &inodes);
+    sprintf(CS var_buffer, "%d", inodes);
+    }
+  return var_buffer;
 
-    #ifndef DISABLE_DKIM
-    case vtype_dkim:
-    return dkim_exim_expand_query((int)(long)var_table[middle].value);
-    #endif
+  case vtype_cert:
+  return *(void **)val ? US"<cert>" : US"";
+
+  #ifndef DISABLE_DKIM
+  case vtype_dkim:
+  return dkim_exim_expand_query((int)(long)val);
+  #endif
 
-    }
   }
 
-return NULL;          /* Unknown variable name */
+return NULL;  /* Unknown variable. Silences static checkers. */
 }
 
 
@@ -1740,21 +1941,8 @@ return NULL;          /* Unknown variable name */
 void
 modify_variable(uschar *name, void * value)
 {
-int first = 0;
-int last = var_table_size;
-
-while (last > first)
-  {
-  int middle = (first + last)/2;
-  int c = Ustrcmp(name, var_table[middle].name);
-
-  if (c > 0) { first = middle + 1; continue; }
-  if (c < 0) { last = middle; continue; }
-
-  /* Found an existing variable; change the item it refers to */
-  var_table[middle].value = value;
-  return;
-  }
+var_entry * vp;
+if ((vp = find_var_ent(name))) vp->value = value;
 return;          /* Unknown variable name, fail silently */
 }
 
@@ -1778,6 +1966,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)
@@ -1786,11 +1976,11 @@ 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)
+read_subs(uschar **sub, int n, int m, const uschar **sptr, BOOL skipping,
+  BOOL check_end, uschar *name, BOOL *resetok)
 {
 int i;
-uschar *s = *sptr;
+const uschar *s = *sptr;
 
 while (isspace(*s)) s++;
 for (i = 0; i < n; i++)
@@ -1801,7 +1991,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++;
@@ -1866,15 +2056,15 @@ static int
 eval_acl(uschar ** sub, int nsub, uschar ** user_msgp)
 {
 int i;
-uschar *tmp;
 int sav_narg = acl_narg;
 int ret;
+uschar * dummy_logmsg;
 extern int acl_where;
 
-if(--nsub > sizeof(acl_arg)/sizeof(*acl_arg)) nsub = sizeof(acl_arg)/sizeof(*acl_arg);
+if(--nsub > nelem(acl_arg)) nsub = nelem(acl_arg);
 for (i = 0; i < nsub && sub[i+1]; i++)
   {
-  tmp = acl_arg[i];
+  uschar * tmp = acl_arg[i];
   acl_arg[i] = sub[i+1];       /* place callers args in the globals */
   sub[i+1] = tmp;              /* stash the old args using our caller's storage */
   }
@@ -1888,10 +2078,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_eval(acl_where, NULL, sub[0], user_msgp, &tmp);
+ret = acl_eval(acl_where, sub[0], user_msgp, &dummy_logmsg);
 
 for (i = 0; i < nsub; i++)
   acl_arg[i] = sub[i+1];       /* restore old args */
@@ -1910,6 +2100,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,8 +2112,8 @@ Returns:   a pointer to the first character after the condition, or
            NULL after an error
 */
 
-static uschar *
-eval_condition(uschar *s, BOOL *yield)
+static const uschar *
+eval_condition(const uschar *s, BOOL *resetok, BOOL *yield)
 {
 BOOL testfor = TRUE;
 BOOL tempcond, combined_cond;
@@ -1930,7 +2123,7 @@ int i, rc, cond_type, roffset;
 int_eximarith_t num[2];
 struct stat statbuf;
 uschar name[256];
-uschar *sub[10];
+const uschar *sub[10];
 
 const pcre *re;
 const uschar *rerror;
@@ -1970,7 +2163,7 @@ if (name[0] == 0)
 
 /* Find which condition we are dealing with, and switch on it */
 
-cond_type = chop_match(name, cond_table, sizeof(cond_table)/sizeof(uschar *));
+cond_type = chop_match(name, cond_table, nelem(cond_table));
 switch(cond_type)
   {
   /* def: tests for a non-empty variable, or for the existence of a header. If
@@ -2059,7 +2252,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;
@@ -2143,11 +2336,14 @@ 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 *sub[10];
     uschar *user_msg;
     BOOL cond = FALSE;
     int size = 0;
@@ -2156,8 +2352,8 @@ switch(cond_type)
     while (isspace(*s)) s++;
     if (*s++ != '{') goto COND_FAILED_CURLY_START;     /*}*/
 
-    switch(read_subs(sub, sizeof(sub)/sizeof(*sub), 1,
-      &s, yield == NULL, TRUE, US"acl"))
+    switch(read_subs(sub, nelem(sub), 1,
+      &s, yield == NULL, TRUE, US"acl", resetok))
       {
       case 1: expand_string_message = US"too few arguments or bracketing "
         "error for acl";
@@ -2165,7 +2361,8 @@ switch(cond_type)
       case 3: return NULL;
       }
 
-    if (yield != NULL) switch(eval_acl(sub, sizeof(sub)/sizeof(*sub), &user_msg))
+    *resetok = FALSE;
+    if (yield != NULL) switch(eval_acl(sub, nelem(sub), &user_msg))
        {
        case OK:
          cond = TRUE;
@@ -2191,35 +2388,38 @@ 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. */
 
   case ECOND_SASLAUTHD:
-  #ifndef CYRUS_SASLAUTHD_SOCKET
-  goto COND_FAILED_NOT_COMPILED;
-  #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"))
-    {
-    case 1: expand_string_message = US"too few arguments or bracketing "
-      "error for saslauthd";
-    case 2:
-    case 3: return NULL;
-    }
-  if (sub[2] == NULL) sub[3] = NULL;  /* realm if no service */
-  if (yield != NULL)
+#ifndef CYRUS_SASLAUTHD_SOCKET
+    goto COND_FAILED_NOT_COMPILED;
+#else
     {
-    int rc;
-    rc = auth_call_saslauthd(sub[0], sub[1], sub[2], sub[3],
-      &expand_string_message);
-    if (rc == ERROR || rc == DEFER) return NULL;
-    *yield = (rc == OK) == testfor;
+    uschar *sub[4];
+    while (isspace(*s)) s++;
+    if (*s++ != '{') goto COND_FAILED_CURLY_START;     /* }-for-text-editors */
+    switch(read_subs(sub, nelem(sub), 2, &s, yield == NULL, TRUE, US"saslauthd",
+                   resetok))
+      {
+      case 1: expand_string_message = US"too few arguments or bracketing "
+       "error for saslauthd";
+      case 2:
+      case 3: return NULL;
+      }
+    if (sub[2] == NULL) sub[3] = NULL;  /* realm if no service */
+    if (yield != NULL)
+      {
+      int rc = auth_call_saslauthd(sub[0], sub[1], sub[2], sub[3],
+       &expand_string_message);
+      if (rc == ERROR || rc == DEFER) return NULL;
+      *yield = (rc == OK) == testfor;
+      }
+    return s;
     }
-  return s;
-  #endif /* CYRUS_SASLAUTHD_SOCKET */
+#endif /* CYRUS_SASLAUTHD_SOCKET */
 
 
   /* symbolic operators for numeric and string comparison, and a number of
@@ -2286,7 +2486,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;
 
@@ -2304,7 +2504,7 @@ switch(cond_type)
         }
       else
         {
-        num[i] = expand_string_integer(sub[i], FALSE);
+        num[i] = expanded_string_integer(sub[i], FALSE);
         if (expand_string_message != NULL) return NULL;
         }
       }
@@ -2597,17 +2797,18 @@ switch(cond_type)
     case ECOND_INLIST:
     case ECOND_INLISTI:
       {
+      const uschar * list = sub[1];
       int sep = 0;
       uschar *save_iterate_item = iterate_item;
       int (*compare)(const uschar *, const uschar *);
 
+      DEBUG(D_expand) debug_printf("condition: %s\n", name);
+
       tempcond = FALSE;
-      if (cond_type == ECOND_INLISTI)
-        compare = strcmpic;
-      else
-        compare = (int (*)(const uschar *, const uschar *)) strcmp;
+      compare = cond_type == ECOND_INLISTI
+        ? strcmpic : (int (*)(const uschar *, const uschar *)) strcmp;
 
-      while ((iterate_item = string_nextinlist(&sub[1], &sep, NULL, 0)) != NULL)
+      while ((iterate_item = string_nextinlist(&list, &sep, NULL, 0)))
         if (compare(sub[0], iterate_item) == 0)
           {
           tempcond = TRUE;
@@ -2644,8 +2845,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);
@@ -2686,12 +2886,15 @@ switch(cond_type)
   case ECOND_FORALL:
   case ECOND_FORANY:
     {
+    const uschar * list;
     int sep = 0;
     uschar *save_iterate_item = iterate_item;
 
+    DEBUG(D_expand) debug_printf("condition: %s\n", name);
+
     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 +2908,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);
@@ -2724,10 +2926,11 @@ switch(cond_type)
       }
 
     if (yield != NULL) *yield = !testfor;
-    while ((iterate_item = string_nextinlist(&sub[0], &sep, NULL, 0)) != NULL)
+    list = sub[0];
+    while ((iterate_item = string_nextinlist(&list, &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 +2970,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",
@@ -2797,7 +3000,9 @@ switch(cond_type)
     be no maintenance burden from replicating it. */
     if (len == 0)
       boolvalue = FALSE;
-    else if (Ustrspn(t, "0123456789") == len)
+    else if (*t == '-'
+            ? Ustrspn(t+1, "0123456789") == len-1
+            : Ustrspn(t,   "0123456789") == len)
       {
       boolvalue = (Uatoi(t) == 0) ? FALSE : TRUE;
       /* expand_check_condition only does a literal string "0" check */
@@ -2931,6 +3136,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
@@ -2938,11 +3145,11 @@ 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)
+process_yesno(BOOL skipping, BOOL yes, uschar *save_lookup, const uschar **sptr,
+  uschar **yieldptr, int *sizeptr, int *ptrptr, uschar *type, BOOL *resetok)
 {
 int rc = 0;
-uschar *s = *sptr;    /* Local value */
+const uschar *s = *sptr;    /* Local value */
 uschar *sub1, *sub2;
 
 /* If there are no following strings, we substitute the contents of $value for
@@ -2976,7 +3183,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 +3208,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;
@@ -3020,7 +3227,8 @@ inside another lookup or if or extract. */
 else if (*s != '}')
   {
   uschar name[256];
-  s = read_name(name, sizeof(name), s, US"_");
+  /* deconst cast ok here as source is s anyway */
+  s = US read_name(name, sizeof(name), s, US"_");
   if (Ustrcmp(name, "fail") == 0)
     {
     if (!yes && !skipping)
@@ -3389,12 +3597,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 +3774,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 +3787,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
@@ -3586,14 +3797,14 @@ Returns:         NULL if expansion fails:
 */
 
 static uschar *
-expand_string_internal(uschar *string, BOOL ket_ends, uschar **left,
-  BOOL skipping, BOOL honour_dollar)
+expand_string_internal(const uschar *string, BOOL ket_ends, const uschar **left,
+  BOOL skipping, BOOL honour_dollar, BOOL *resetok_p)
 {
 int ptr = 0;
 int size = Ustrlen(string)+ 64;
 int item_type;
 uschar *yield = store_get(size);
-uschar *s = string;
+const uschar *s = string;
 uschar *save_expand_nstring[EXPAND_MAXN+1];
 int save_expand_nlength[EXPAND_MAXN+1];
 BOOL resetok = TRUE;
@@ -3621,7 +3832,7 @@ while (*s != 0)
 
     if (s[1] == 'N')
       {
-      uschar *t = s + 2;
+      const uschar * t = s + 2;
       for (s = t; *s != 0; s++) if (*s == '\\' && s[1] == 'N') break;
       yield = string_cat(yield, &size, &ptr, t, s - t);
       if (*s != 0) s += 2;
@@ -3638,9 +3849,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 +3868,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))))
     {
@@ -3735,18 +3948,18 @@ while (*s != 0)
   if (isdigit(*s))
     {
     int n;
-    s = read_number(&n, s);
+    s = read_cnumber(&n, s);
     if (n >= 0 && n <= expand_nmax)
       yield = string_cat(yield, &size, &ptr, expand_nstring[n],
         expand_nlength[n]);
     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 +3969,9 @@ while (*s != 0)
   if (isdigit((*(++s))))
     {
     int n;
-    s = read_number(&n, s);
+    s = read_cnumber(&n, s);           /*{*/
     if (*s++ != '}')
-      {
+      {                                        /*{*/
       expand_string_message = US"} expected after number";
       goto EXPAND_FAILED;
       }
@@ -3770,7 +3983,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;
     }
 
@@ -3779,7 +3992,7 @@ while (*s != 0)
   OK. */
 
   s = read_name(name, sizeof(name), s, US"_-");
-  item_type = chop_match(name, item_table, sizeof(item_table)/sizeof(uschar *));
+  item_type = chop_match(name, item_table, nelem(item_table));
 
   switch(item_type)
     {
@@ -3798,7 +4011,8 @@ 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, nelem(sub), 1, &s, skipping, TRUE, US"acl",
+                     &resetok))
         {
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:
@@ -3807,10 +4021,12 @@ while (*s != 0)
       if (skipping) continue;
 
       resetok = FALSE;
-      switch(eval_acl(sub, sizeof(sub)/sizeof(*sub), &user_msg))
+      switch(eval_acl(sub, nelem(sub), &user_msg))
        {
        case OK:
        case FAIL:
+         DEBUG(D_expand)
+           debug_printf("acl expansion yield: %s\n", user_msg);
          if (user_msg)
             yield = string_cat(yield, &size, &ptr, user_msg, Ustrlen(user_msg));
          continue;
@@ -3831,12 +4047,12 @@ while (*s != 0)
     case EITEM_IF:
       {
       BOOL cond = FALSE;
-      uschar *next_s;
+      const uschar *next_s;
       int save_expand_nmax =
         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 +4072,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 */
@@ -3870,6 +4087,43 @@ while (*s != 0)
       continue;
       }
 
+#ifdef EXPERIMENTAL_INTERNATIONAL
+    case EITEM_IMAPFOLDER:
+      {                                /* ${imapfolder {name}{sep]{specials}} */
+      uschar *sub_arg[3];
+      uschar *encoded;
+
+      switch(read_subs(sub_arg, nelem(sub_arg), 1, &s, skipping, TRUE, name,
+                     &resetok))
+        {
+        case 1: goto EXPAND_FAILED_CURLY;
+        case 2:
+        case 3: goto EXPAND_FAILED;
+        }
+
+      if (sub_arg[1] == NULL)          /* One argument */
+       {
+       sub_arg[1] = US"/";             /* default separator */
+       sub_arg[2] = NULL;
+       }
+      else if (Ustrlen(sub_arg[1]) != 1)
+       {
+       expand_string_message = 
+         string_sprintf(
+               "IMAP folder separator must be one character, found \"%s\"", 
+               sub_arg[1]);
+       goto EXPAND_FAILED;
+       }
+
+      if (!(encoded = imap_utf7_encode(sub_arg[0], headers_charset,
+                         sub_arg[1][0], sub_arg[2], &expand_string_message)))
+       goto EXPAND_FAILED;
+      if (!skipping)
+       yield = string_cat(yield, &size, &ptr, encoded, Ustrlen(encoded));
+      continue;
+      }
+#endif
+
     /* Handle database lookups unless locked out. If "skipping" is TRUE, we are
     expanding an internal string that isn't actually going to be used. All we
     need to do is check the syntax, so don't do a lookup at all. Preserve the
@@ -3882,7 +4136,8 @@ while (*s != 0)
       int stype, partial, affixlen, starflags;
       int expand_setup = 0;
       int nameptr = 0;
-      uschar *key, *filename, *affix;
+      uschar *key, *filename;
+      const uschar *affix;
       uschar *save_lookup_value = lookup_value;
       int save_expand_nmax =
         save_expand_strings(save_expand_nstring, save_expand_nlength);
@@ -3897,10 +4152,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 +4171,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 +4220,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 +4298,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 +4322,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 +4338,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 +4410,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 +4484,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 +4516,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 +4570,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 +4594,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 +4618,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 +4664,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 */
@@ -4438,10 +4694,7 @@ while (*s != 0)
 
         if (Ustrncmp(sub_arg[0], "inet:", 5) == 0)
           {
-          BOOL connected = FALSE;
-          int namelen, port;
-          host_item shost;
-          host_item *h;
+          int port;
           uschar *server_name = sub_arg[0] + 5;
           uschar *port_name = Ustrrchr(server_name, ':');
 
@@ -4478,76 +4731,9 @@ while (*s != 0)
             port = ntohs(service_info->s_port);
             }
 
-          /* Sort out the server. */
-
-          shost.next = NULL;
-          shost.address = NULL;
-          shost.port = port;
-          shost.mx = -1;
-
-          namelen = Ustrlen(server_name);
-
-          /* Anything enclosed in [] must be an IP address. */
-
-          if (server_name[0] == '[' &&
-              server_name[namelen - 1] == ']')
-            {
-            server_name[namelen - 1] = 0;
-            server_name++;
-            if (string_is_ip_address(server_name, NULL) == 0)
-              {
-              expand_string_message =
-                string_sprintf("malformed IP address \"%s\"", server_name);
-              goto EXPAND_FAILED;
-              }
-            shost.name = shost.address = server_name;
-            }
-
-          /* Otherwise check for an unadorned IP address */
-
-          else if (string_is_ip_address(server_name, NULL) != 0)
-            shost.name = shost.address = server_name;
-
-          /* Otherwise lookup IP address(es) from the name */
-
-          else
-            {
-            shost.name = server_name;
-            if (host_find_byname(&shost, NULL, HOST_FIND_QUALIFY_SINGLE, NULL,
-                FALSE) != HOST_FOUND)
-              {
-              expand_string_message =
-                string_sprintf("no IP address found for host %s", shost.name);
-              goto EXPAND_FAILED;
-              }
-            }
-
-          /* Try to connect to the server - test each IP till one works */
-
-          for (h = &shost; h != NULL; h = h->next)
-            {
-            int af = (Ustrchr(h->address, ':') != 0)? AF_INET6 : AF_INET;
-            if ((fd = ip_socket(SOCK_STREAM, af)) == -1)
-              {
-              expand_string_message = string_sprintf("failed to create socket: "
-                "%s", strerror(errno));
+         if ((fd = ip_connectedsocket(SOCK_STREAM, server_name, port, port,
+                 timeout, NULL, &expand_string_message)) < 0)
               goto SOCK_FAIL;
-              }
-
-            if (ip_connect(fd, af, h->address, port, timeout) == 0)
-              {
-              connected = TRUE;
-              break;
-              }
-            }
-
-          if (!connected)
-            {
-            expand_string_message = string_sprintf("failed to connect to "
-              "socket %s: couldn't connect to any host", sub_arg[0],
-              strerror(errno));
-            goto SOCK_FAIL;
-            }
           }
 
         /* Handle a Unix domain socket */
@@ -4585,6 +4771,9 @@ while (*s != 0)
 
         DEBUG(D_expand) debug_printf("connected to socket %s\n", sub_arg[0]);
 
+       /* Allow sequencing of test actions */
+       if (running_in_test_harness) millisleep(100);
+
         /* Write the request string, if not empty */
 
         if (sub_arg[1][0] != 0)
@@ -4608,6 +4797,8 @@ while (*s != 0)
         shutdown(fd, SHUT_WR);
         #endif
 
+       if (running_in_test_harness) millisleep(100);
+
         /* Now we need to read from the socket, under a timeout. The function
         that reads a file can be used. */
 
@@ -4634,7 +4825,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 +4840,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;
@@ -4664,7 +4855,7 @@ while (*s != 0)
       {
       FILE *f;
       uschar *arg;
-      uschar **argv;
+      const uschar **argv;
       pid_t pid;
       int fd_in, fd_out;
       int lsize = 0;
@@ -4678,7 +4869,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;
@@ -4702,7 +4893,7 @@ while (*s != 0)
 
         /* Create the child process, making it a group leader. */
 
-        pid = child_open(argv, NULL, 0077, &fd_in, &fd_out, TRUE);
+        pid = child_open(USS argv, NULL, 0077, &fd_in, &fd_out, TRUE);
 
         if (pid < 0)
           {
@@ -4760,7 +4951,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 +4969,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 +5007,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 +5086,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 +5181,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:
@@ -5021,7 +5213,7 @@ while (*s != 0)
         {
         int ovector[3*(EXPAND_MAXN+1)];
         int n = pcre_exec(re, NULL, CS subject, slen, moffset + moffsetextra,
-          PCRE_EOPT | emptyopt, ovector, sizeof(ovector)/sizeof(int));
+          PCRE_EOPT | emptyopt, ovector, nelem(ovector));
         int nn;
         uschar *insert;
 
@@ -5107,10 +5299,10 @@ while (*s != 0)
       for (i = 0; i < j; i++)
         {
         while (isspace(*s)) s++;
-        if (*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
@@ -5131,25 +5323,28 @@ while (*s != 0)
             while (len > 0 && isspace(p[len-1])) len--;
             p[len] = 0;
 
-            if (*p == 0 && !skipping)
-              {
-              expand_string_message = US"first argument of \"extract\" must "
-                "not be empty";
-              goto EXPAND_FAILED;
-              }
+            if (!skipping)
+             {
+             if (*p == 0)
+               {
+               expand_string_message = US"first argument of \"extract\" must "
+                 "not be empty";
+               goto EXPAND_FAILED;
+               }
 
-            if (*p == '-')
-              {
-              field_number = -1;
-              p++;
-              }
-            while (*p != 0 && isdigit(*p)) x = x * 10 + *p++ - '0';
-            if (*p == 0)
-              {
-              field_number *= x;
-              j = 3;               /* Need 3 args */
-              field_number_set = TRUE;
-              }
+             if (*p == '-')
+               {
+               field_number = -1;
+               p++;
+               }
+             while (*p != 0 && isdigit(*p)) x = x * 10 + *p++ - '0';
+             if (*p == 0)
+               {
+               field_number *= x;
+               j = 3;               /* Need 3 args */
+               field_number_set = TRUE;
+               }
+             }
             }
           }
         else goto EXPAND_FAILED_CURLY;
@@ -5173,7 +5368,101 @@ 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 */
+        }
+
+      /* All done - restore numerical variables. */
+
+      restore_expand_strings(save_expand_nmax, save_expand_nstring,
+        save_expand_nlength);
+
+      continue;
+      }
+
+    /* return the Nth item from a list */
+
+    case EITEM_LISTEXTRACT:
+      {
+      int i;
+      int field_number = 1;
+      uschar *save_lookup_value = lookup_value;
+      uschar *sub[2];
+      int save_expand_nmax =
+        save_expand_strings(save_expand_nstring, save_expand_nlength);
+
+      /* Read the field & list arguments */
+
+      for (i = 0; i < 2; i++)
+        {
+        while (isspace(*s)) s++;
+        if (*s != '{')                                 /*}*/
+         goto EXPAND_FAILED_CURLY;
+
+       sub[i] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok);
+       if (!sub[i])     goto EXPAND_FAILED;            /*{*/
+       if (*s++ != '}') goto EXPAND_FAILED_CURLY;
+
+       /* After removal of leading and trailing white space, the first
+       argument must be numeric and nonempty. */
+
+       if (i == 0)
+         {
+         int len;
+         int x = 0;
+         uschar *p = sub[0];
+
+         while (isspace(*p)) p++;
+         sub[0] = p;
+
+         len = Ustrlen(p);
+         while (len > 0 && isspace(p[len-1])) len--;
+         p[len] = 0;
+
+         if (!*p && !skipping)
+           {
+           expand_string_message = US"first argument of \"listextract\" must "
+             "not be empty";
+           goto EXPAND_FAILED;
+           }
+
+         if (*p == '-')
+           {
+           field_number = -1;
+           p++;
+           }
+         while (*p && isdigit(*p)) x = x * 10 + *p++ - '0';
+         if (*p)
+           {
+           expand_string_message = US"first argument of \"listextract\" must "
+             "be numeric";
+           goto EXPAND_FAILED;
+           }
+         field_number *= x;
+         }
+        }
+
+      /* Extract the numbered element into $value. If
+      skipping, just pretend the extraction failed. */
+
+      lookup_value = skipping? NULL : expand_getlistele(field_number, sub[1]);
+
+      /* If no string follows, $value gets substituted; otherwise there can
+      be yes/no strings, as for lookup or if. */
+
+      switch(process_yesno(
+               skipping,                     /* were previously skipping */
+               lookup_value != NULL,         /* success/failure indicator */
+               save_lookup_value,            /* value to reset for string2 */
+               &s,                           /* input pointer */
+               &yield,                       /* output pointer */
+               &size,                        /* output size */
+               &ptr,                         /* output current point */
+               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,6 +5476,75 @@ while (*s != 0)
       continue;
       }
 
+#ifdef SUPPORT_TLS
+    case EITEM_CERTEXTRACT:
+      {
+      uschar *save_lookup_value = lookup_value;
+      uschar *sub[2];
+      int save_expand_nmax =
+        save_expand_strings(save_expand_nstring, save_expand_nlength);
+
+      /* Read the field argument */
+      while (isspace(*s)) s++;
+      if (*s != '{')                                   /*}*/
+       goto EXPAND_FAILED_CURLY;
+      sub[0] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok);
+      if (!sub[0])     goto EXPAND_FAILED;             /*{*/
+      if (*s++ != '}') goto EXPAND_FAILED_CURLY;
+      /* strip spaces fore & aft */
+      {
+      int len;
+      uschar *p = sub[0];
+
+      while (isspace(*p)) p++;
+      sub[0] = p;
+
+      len = Ustrlen(p);
+      while (len > 0 && isspace(p[len-1])) len--;
+      p[len] = 0;
+      }
+
+      /* inspect the cert argument */
+      while (isspace(*s)) s++;
+      if (*s != '{')                                   /*}*/
+       goto EXPAND_FAILED_CURLY;
+      if (*++s != '$')
+        {
+       expand_string_message = US"second argument of \"certextract\" must "
+         "be a certificate variable";
+       goto EXPAND_FAILED;
+       }
+      sub[1] = expand_string_internal(s+1, TRUE, &s, skipping, FALSE, &resetok);
+      if (!sub[1])     goto EXPAND_FAILED;             /*{*/
+      if (*s++ != '}') goto EXPAND_FAILED_CURLY;
+
+      if (skipping)
+       lookup_value = NULL;
+      else
+       {
+       lookup_value = expand_getcertele(sub[0], sub[1]);
+       if (*expand_string_message) goto EXPAND_FAILED;
+       }
+      switch(process_yesno(
+               skipping,                     /* were previously skipping */
+               lookup_value != NULL,         /* success/failure indicator */
+               save_lookup_value,            /* value to reset for string2 */
+               &s,                           /* input pointer */
+               &yield,                       /* output pointer */
+               &size,                        /* output size */
+               &ptr,                         /* output current point */
+               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 */
+        }
+
+      restore_expand_strings(save_expand_nmax, save_expand_nstring,
+        save_expand_nlength);
+      continue;
+      }
+#endif /*SUPPORT_TLS*/
 
     /* Handle list operations */
 
@@ -5197,24 +5555,25 @@ while (*s != 0)
       int sep = 0;
       int save_ptr = ptr;
       uschar outsep[2] = { '\0', '\0' };
-      uschar *list, *expr, *temp;
+      const uschar *list, *expr, *temp;
       uschar *save_iterate_item = iterate_item;
       uschar *save_lookup_value = lookup_value;
 
       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;
 
       if (item_type == EITEM_REDUCE)
         {
+       uschar * t;
         while (isspace(*s)) s++;
         if (*s++ != '{') goto EXPAND_FAILED_CURLY;
-        temp = expand_string_internal(s, TRUE, &s, skipping, TRUE);
-        if (temp == NULL) goto EXPAND_FAILED;
-        lookup_value = temp;
+        t = expand_string_internal(s, TRUE, &s, skipping, TRUE, &resetok);
+        if (!t) goto EXPAND_FAILED;
+        lookup_value = t;
         if (*s++ != '}') goto EXPAND_FAILED_CURLY;
         }
 
@@ -5231,13 +5590,11 @@ 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 +5605,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 +5632,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 +5652,8 @@ while (*s != 0)
 
         else
           {
-          temp = expand_string_internal(expr, TRUE, NULL, skipping, TRUE);
+         uschar * t = expand_string_internal(expr, TRUE, NULL, skipping, TRUE, &resetok);
+          temp = t;
           if (temp == NULL)
             {
             iterate_item = save_iterate_item;
@@ -5305,7 +5663,7 @@ while (*s != 0)
             }
           if (item_type == EITEM_REDUCE)
             {
-            lookup_value = temp;      /* Update the value of $value */
+            lookup_value = t;         /* Update the value of $value */
             continue;                 /* and continue the iteration */
             }
           }
@@ -5365,8 +5723,146 @@ while (*s != 0)
       continue;
       }
 
+    case EITEM_SORT:
+      {
+      int sep = 0;
+      const uschar *srclist, *cmp, *xtract;
+      uschar *srcitem;
+      const uschar *dstlist = NULL, *dstkeylist = NULL;
+      uschar * tmp;
+      uschar *save_iterate_item = iterate_item;
+
+      while (isspace(*s)) s++;
+      if (*s++ != '{') goto EXPAND_FAILED_CURLY;
+
+      srclist = expand_string_internal(s, TRUE, &s, skipping, TRUE, &resetok);
+      if (!srclist) goto EXPAND_FAILED;
+      if (*s++ != '}') goto EXPAND_FAILED_CURLY;
+
+      while (isspace(*s)) s++;
+      if (*s++ != '{') goto EXPAND_FAILED_CURLY;
+
+      cmp = expand_string_internal(s, TRUE, &s, skipping, FALSE, &resetok);
+      if (!cmp) goto EXPAND_FAILED;
+      if (*s++ != '}') goto EXPAND_FAILED_CURLY;
+
+      while (isspace(*s)) s++;
+      if (*s++ != '{') goto EXPAND_FAILED_CURLY;
+
+      xtract = s;
+      tmp = expand_string_internal(s, TRUE, &s, TRUE, TRUE, &resetok);
+      if (!tmp) goto EXPAND_FAILED;
+      xtract = string_copyn(xtract, s - xtract);
+
+      if (*s++ != '}') goto EXPAND_FAILED_CURLY;
+                                                       /*{*/
+      if (*s++ != '}')
+        {                                              /*{*/
+        expand_string_message = US"missing } at end of \"sort\"";
+        goto EXPAND_FAILED;
+        }
+
+      if (skipping) continue;
+
+      while ((srcitem = string_nextinlist(&srclist, &sep, NULL, 0)))
+        {
+       uschar * dstitem;
+       uschar * newlist = NULL;
+       uschar * newkeylist = NULL;
+       uschar * srcfield;
+
+        DEBUG(D_expand) debug_printf("%s: $item = \"%s\"\n", name, srcitem);
+
+       /* extract field for comparisons */
+       iterate_item = srcitem;
+       if (  !(srcfield = expand_string_internal(xtract, FALSE, NULL, FALSE,
+                                         TRUE, &resetok))
+          || !*srcfield)
+         {
+         expand_string_message = string_sprintf(
+             "field-extract in sort: \"%s\"", xtract);
+         goto EXPAND_FAILED;
+         }
+
+       /* Insertion sort */
+
+       /* copy output list until new-item < list-item */
+       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("%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;
+           }
+
+         if (before)
+           {
+           /* New-item sorts before this dst-item.  Append new-item,
+           then dst-item, then remainder of dst list. */
+
+           newlist = string_append_listele(newlist, sep, srcitem);
+           newkeylist = string_append_listele(newkeylist, sep, srcfield);
+           srcitem = NULL;
+
+           newlist = string_append_listele(newlist, sep, dstitem);
+           newkeylist = string_append_listele(newkeylist, sep, dstfield);
+
+           while ((dstitem = string_nextinlist(&dstlist, &sep, NULL, 0)))
+             {
+             if (!(dstfield = string_nextinlist(&dstkeylist, &sep, NULL, 0)))
+               goto sort_mismatch;
+             newlist = string_append_listele(newlist, sep, dstitem);
+             newkeylist = string_append_listele(newkeylist, sep, dstfield);
+             }
+
+           break;
+           }
+
+         newlist = string_append_listele(newlist, sep, dstitem);
+         newkeylist = string_append_listele(newkeylist, sep, dstfield);
+         }
+
+       /* If we ran out of dstlist without consuming srcitem, append it */
+       if (srcitem)
+         {
+         newlist = string_append_listele(newlist, sep, srcitem);
+         newkeylist = string_append_listele(newkeylist, sep, srcfield);
+         }
+
+       dstlist = newlist;
+       dstkeylist = newkeylist;
+
+        DEBUG(D_expand) debug_printf("%s: dstlist = \"%s\"\n", name, dstlist);
+        DEBUG(D_expand) debug_printf("%s: dstkeylist = \"%s\"\n", name, dstkeylist);
+       }
+
+      if (dstlist)
+       yield = string_cat(yield, &size, &ptr, dstlist, Ustrlen(dstlist));
+
+      /* Restore preserved $item */
+      iterate_item = save_iterate_item;
+      continue;
+
+      sort_mismatch:
+       expand_string_message = US"Internal error in sort (list mismatch)";
+       goto EXPAND_FAILED;
+      }
+
 
-    /* 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). */
@@ -5374,12 +5870,12 @@ while (*s != 0)
     #define EXPAND_DLFUNC_MAX_ARGS 8
 
     case EITEM_DLFUNC:
-    #ifndef EXPAND_DLFUNC
-    expand_string_message = US"\"${dlfunc\" encountered, but this facility "
-      "is not included in this binary";
-    goto EXPAND_FAILED;
+#ifndef EXPAND_DLFUNC
+      expand_string_message = US"\"${dlfunc\" encountered, but this facility " /*}*/
+       "is not included in this binary";
+      goto EXPAND_FAILED;
 
-    #else   /* EXPAND_DLFUNC */
+#else   /* EXPAND_DLFUNC */
       {
       tree_node *t;
       exim_dlfunc_t *func;
@@ -5395,7 +5891,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:
@@ -5465,8 +5961,40 @@ while (*s != 0)
         goto EXPAND_FAILED;
         }
       }
-    #endif /* EXPAND_DLFUNC */
-    }
+#endif /* EXPAND_DLFUNC */
+
+    case EITEM_ENV:    /* ${env {name} {val_if_found} {val_if_unfound}} */
+      {
+      uschar * key;
+      uschar *save_lookup_value = lookup_value;
+
+      while (isspace(*s)) s++;
+      if (*s != '{')                                   /*}*/
+       goto EXPAND_FAILED;
+
+      key = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok);
+      if (!key) goto EXPAND_FAILED;                    /*{*/
+      if (*s++ != '}') goto EXPAND_FAILED_CURLY;
+
+      lookup_value = US getenv(CS key);
+
+      switch(process_yesno(
+               skipping,                     /* were previously skipping */
+               lookup_value != NULL,         /* success/failure indicator */
+               save_lookup_value,            /* value to reset for string2 */
+               &s,                           /* input pointer */
+               &yield,                       /* output pointer */
+               &size,                        /* output size */
+               &ptr,                         /* output current point */
+               US"env",                      /* condition type */
+              &resetok))
+        {
+        case 1: goto EXPAND_FAILED;          /* when all is well, the */
+        case 2: goto EXPAND_FAILED_CURLY;    /* returned value is 0 */
+        }
+      continue;
+      }
+    }  /* 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,28 +6005,55 @@ while (*s != 0)
     {
     int c;
     uschar *arg = NULL;
-    uschar *sub = expand_string_internal(s+1, TRUE, &s, skipping, TRUE);
-    if (sub == NULL) goto EXPAND_FAILED;
-    s++;
+    uschar *sub;
+    var_entry *vp = NULL;
 
     /* Owing to an historical mis-design, an underscore may be part of the
     operator name, or it may introduce arguments.  We therefore first scan the
     table of names that contain underscores. If there is no match, we cut off
     the arguments and then scan the main table. */
 
-    c = chop_match(name, op_table_underscore,
-      sizeof(op_table_underscore)/sizeof(uschar *));
-
-    if (c < 0)
+    if ((c = chop_match(name, op_table_underscore,
+                       nelem(op_table_underscore))) < 0)
       {
       arg = Ustrchr(name, '_');
       if (arg != NULL) *arg = 0;
-      c = chop_match(name, op_table_main,
-        sizeof(op_table_main)/sizeof(uschar *));
-      if (c >= 0) c += sizeof(op_table_underscore)/sizeof(uschar *);
+      c = chop_match(name, op_table_main, nelem(op_table_main));
+      if (c >= 0) c += nelem(op_table_underscore);
       if (arg != NULL) *arg++ = '_';   /* Put back for error messages */
       }
 
+    /* Deal specially with operators that might take a certificate variable
+    as we do not want to do the usual expansion. For most, expand the string.*/
+    switch(c)
+      {
+#ifdef SUPPORT_TLS
+      case EOP_MD5:
+      case EOP_SHA1:
+      case EOP_SHA256:
+       if (s[1] == '$')
+         {
+         const uschar * s1 = s;
+         sub = expand_string_internal(s+2, TRUE, &s1, skipping,
+                 FALSE, &resetok);
+         if (!sub)       goto EXPAND_FAILED;           /*{*/
+         if (*s1 != '}') goto EXPAND_FAILED_CURLY;
+         if ((vp = find_var_ent(sub)) && vp->type == vtype_cert)
+           {
+           s = s1+1;
+           break;
+           }
+         vp = NULL;
+         }
+        /*FALLTHROUGH*/
+#endif
+      default:
+       sub = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok);
+       if (!sub) goto EXPAND_FAILED;
+       s++;
+       break;
+      }
+
     /* If we are skipping, we don't need to perform the operation at all.
     This matters for operations like "mask", because the data may not be
     in the correct format when skipping. For example, the expression may test
@@ -5552,7 +6107,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 =
@@ -5583,30 +6138,58 @@ while (*s != 0)
         }
 
       case EOP_MD5:
-        {
-        md5 base;
-        uschar digest[16];
-        int j;
-        char st[33];
-        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, &size, &ptr, US st, (int)strlen(st));
+#ifdef SUPPORT_TLS
+       if (vp && *(void **)vp->value)
+         {
+         uschar * cp = tls_cert_fprt_md5(*(void **)vp->value);
+         yield = string_cat(yield, &size, &ptr, cp, Ustrlen(cp));
+         }
+       else
+#endif
+         {
+         md5 base;
+         uschar digest[16];
+         int j;
+         char st[33];
+         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, &size, &ptr, US st, (int)strlen(st));
+         }
         continue;
-        }
 
       case EOP_SHA1:
-        {
-        sha1 base;
-        uschar digest[20];
-        int j;
-        char st[41];
-        sha1_start(&base);
-        sha1_end(&base, sub, Ustrlen(sub), digest);
-        for(j = 0; j < 20; j++) sprintf(st+2*j, "%02X", digest[j]);
-        yield = string_cat(yield, &size, &ptr, US st, (int)strlen(st));
+#ifdef SUPPORT_TLS
+       if (vp && *(void **)vp->value)
+         {
+         uschar * cp = tls_cert_fprt_sha1(*(void **)vp->value);
+         yield = string_cat(yield, &size, &ptr, cp, Ustrlen(cp));
+         }
+       else
+#endif
+         {
+         sha1 base;
+         uschar digest[20];
+         int j;
+         char st[41];
+         sha1_start(&base);
+         sha1_end(&base, sub, Ustrlen(sub), digest);
+         for(j = 0; j < 20; j++) sprintf(st+2*j, "%02X", digest[j]);
+         yield = string_cat(yield, &size, &ptr, US st, (int)strlen(st));
+         }
+        continue;
+
+      case EOP_SHA256:
+#ifdef SUPPORT_TLS
+       if (vp && *(void **)vp->value)
+         {
+         uschar * cp = tls_cert_fprt_sha256(*(void **)vp->value);
+         yield = string_cat(yield, &size, &ptr, cp, (int)Ustrlen(cp));
+         }
+       else
+#endif
+         expand_string_message = US"sha256 only supported for certificates";
         continue;
-        }
 
       /* Convert hex encoding to base64 encoding */
 
@@ -5656,6 +6239,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:
@@ -5665,7 +6264,7 @@ while (*s != 0)
        uschar * cp;
        uschar buffer[256];
 
-       while (string_nextinlist(&sub, &sep, buffer, sizeof(buffer)) != NULL) cnt++;
+       while (string_nextinlist(CUSS &sub, &sep, buffer, sizeof(buffer)) != NULL) cnt++;
        cp = string_sprintf("%d", cnt);
         yield = string_cat(yield, &size, &ptr, cp, Ustrlen(cp));
         continue;
@@ -5677,7 +6276,7 @@ while (*s != 0)
       case EOP_LISTNAMED:
        {
        tree_node *t = NULL;
-       uschar * list;
+       const uschar * list;
        int sep = 0;
        uschar * item;
        uschar * suffix = US"";
@@ -5728,7 +6327,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 */
            {
@@ -5996,7 +6595,7 @@ while (*s != 0)
       case EOP_RFC2047:
         {
         uschar buffer[2048];
-        uschar *string = parse_quote_2047(sub, Ustrlen(sub), headers_charset,
+       const uschar *string = parse_quote_2047(sub, Ustrlen(sub), headers_charset,
           buffer, sizeof(buffer), FALSE);
         yield = string_cat(yield, &size, &ptr, string, Ustrlen(string));
         continue;
@@ -6036,11 +6635,155 @@ while (*s != 0)
         continue;
         }
 
+         /* replace illegal UTF-8 sequences by replacement character  */
+         
+      #define UTF8_REPLACEMENT_CHAR US"?"
+
+      case EOP_UTF8CLEAN:
+        {
+        int seq_len = 0, index = 0;
+        int bytes_left = 0;
+       long codepoint = -1;
+        uschar seq_buff[4];                    /* accumulate utf-8 here */
+        
+        while (*sub != 0)
+         {
+         int complete = 0;
+         uschar c = *sub++;
+
+         if (bytes_left)
+           {
+           if ((c & 0xc0) != 0x80)
+                   /* wrong continuation byte; invalidate all bytes */
+             complete = 1; /* error */
+           else
+             {
+             codepoint = (codepoint << 6) | (c & 0x3f);
+             seq_buff[index++] = c;
+             if (--bytes_left == 0)            /* codepoint complete */
+               if(codepoint > 0x10FFFF)        /* is it too large? */
+                 complete = -1;        /* error (RFC3629 limit) */
+               else
+                 {             /* finished; output utf-8 sequence */
+                 yield = string_cat(yield, &size, &ptr, seq_buff, seq_len);
+                 index = 0;
+                 }
+             }
+           }
+         else  /* no bytes left: new sequence */
+           {
+           if((c & 0x80) == 0) /* 1-byte sequence, US-ASCII, keep it */
+             {
+             yield = string_cat(yield, &size, &ptr, &c, 1);
+             continue;
+             }
+           if((c & 0xe0) == 0xc0)              /* 2-byte sequence */
+             {
+             if(c == 0xc0 || c == 0xc1)        /* 0xc0 and 0xc1 are illegal */
+               complete = -1;
+             else
+               {
+                 bytes_left = 1;
+                 codepoint = c & 0x1f;
+               }
+             }
+           else if((c & 0xf0) == 0xe0)         /* 3-byte sequence */
+             {
+             bytes_left = 2;
+             codepoint = c & 0x0f;
+             }
+           else if((c & 0xf8) == 0xf0)         /* 4-byte sequence */
+             {
+             bytes_left = 3;
+             codepoint = c & 0x07;
+             }
+           else        /* invalid or too long (RFC3629 allows only 4 bytes) */
+             complete = -1;
+
+           seq_buff[index++] = c;
+           seq_len = bytes_left + 1;
+           }           /* if(bytes_left) */
+
+         if (complete != 0)
+           {
+           bytes_left = index = 0;
+           yield = string_cat(yield, &size, &ptr, UTF8_REPLACEMENT_CHAR, 1);
+           }
+         if ((complete == 1) && ((c & 0x80) == 0))
+                       /* ASCII character follows incomplete sequence */
+             yield = string_cat(yield, &size, &ptr, &c, 1);
+         }
+        continue;
+        }
+
+#ifdef EXPERIMENTAL_INTERNATIONAL
+      case EOP_UTF8_DOMAIN_TO_ALABEL:
+       {
+        uschar * error = NULL;
+       uschar * s = string_domain_utf8_to_alabel(sub, &error);
+       if (error)
+         {
+         expand_string_message = string_sprintf(
+           "error converting utf8 (%s) to alabel: %s",
+           string_printing(sub), error);
+         goto EXPAND_FAILED;
+         }
+       yield = string_cat(yield, &size, &ptr, s, Ustrlen(s));
+        continue;
+       }
+
+      case EOP_UTF8_DOMAIN_FROM_ALABEL:
+       {
+        uschar * error = NULL;
+       uschar * s = string_domain_alabel_to_utf8(sub, &error);
+       if (error)
+         {
+         expand_string_message = string_sprintf(
+           "error converting alabel (%s) to utf8: %s",
+           string_printing(sub), error);
+         goto EXPAND_FAILED;
+         }
+       yield = string_cat(yield, &size, &ptr, s, Ustrlen(s));
+        continue;
+       }
+
+      case EOP_UTF8_LOCALPART_TO_ALABEL:
+       {
+        uschar * error = NULL;
+       uschar * s = string_localpart_utf8_to_alabel(sub, &error);
+       if (error)
+         {
+         expand_string_message = string_sprintf(
+           "error converting utf8 (%s) to alabel: %s",
+           string_printing(sub), error);
+         goto EXPAND_FAILED;
+         }
+       yield = string_cat(yield, &size, &ptr, s, Ustrlen(s));
+       DEBUG(D_expand) debug_printf("yield: '%s'\n", yield);
+        continue;
+       }
+
+      case EOP_UTF8_LOCALPART_FROM_ALABEL:
+       {
+        uschar * error = NULL;
+       uschar * s = string_localpart_alabel_to_utf8(sub, &error);
+       if (error)
+         {
+         expand_string_message = string_sprintf(
+           "error converting alabel (%s) to utf8: %s",
+           string_printing(sub), error);
+         goto EXPAND_FAILED;
+         }
+       yield = string_cat(yield, &size, &ptr, s, Ustrlen(s));
+        continue;
+       }
+#endif /* EXPERIMENTAL_INTERNATIONAL */
+
       /* escape turns all non-printing characters into escape sequences. */
 
       case EOP_ESCAPE:
         {
-        uschar *t = string_printing(sub);
+        const uschar *t = string_printing(sub);
         yield = string_cat(yield, &size, &ptr, t, Ustrlen(t));
         continue;
         }
@@ -6268,7 +7011,7 @@ while (*s != 0)
         int_eximarith_t max;
         uschar *s;
 
-        max = expand_string_integer(sub, TRUE);
+        max = expanded_string_integer(sub, TRUE);
         if (expand_string_message != NULL)
           goto EXPAND_FAILED;
         s = string_sprintf("%d", vaguely_random_number((int)max));
@@ -6310,7 +7053,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 +7116,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 +7147,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 +7166,18 @@ 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);
+}
+
+
+
+const uschar *
+expand_cstring(const uschar *string)
+{
+search_find_defer = FALSE;
+malformed_header = FALSE;
+return (Ustrpbrk(string, "$\\") == NULL)? string :
+  expand_string_internal(string, FALSE, NULL, FALSE, TRUE, NULL);
 }
 
 
@@ -6431,17 +7188,18 @@ return (Ustrpbrk(string, "$\\") == NULL)? string :
 
 /* Now and again we want to expand a string and be sure that the result is in a
 new bit of store. This function does that.
+Since we know it has been copied, the de-const cast is safe.
 
 Argument: the string to be expanded
 Returns:  the expanded string, always in a new bit of store, or NULL
 */
 
 uschar *
-expand_string_copy(uschar *string)
+expand_string_copy(const uschar *string)
 {
-uschar *yield = expand_string(string);
+const uschar *yield = expand_cstring(string);
 if (yield == string) yield = string_copy(string);
-return yield;
+return US yield;
 }
 
 
@@ -6465,8 +7223,32 @@ Returns:  the integer value, or
 int_eximarith_t
 expand_string_integer(uschar *string, BOOL isplus)
 {
+return expanded_string_integer(expand_string(string), isplus);
+}
+
+
+/*************************************************
+ *         Interpret string as an integer        *
+ *************************************************/
+
+/* Convert a string (that has already been expanded) into an integer.
+
+This function is used inside the expansion code.
+
+Arguments:
+  s       the string to be expanded
+  isplus  TRUE if a non-negative number is expected
+
+Returns:  the integer value, or
+          -1 if string is NULL (which implies an expansion error)
+          -2 for an integer interpretation error
+          expand_string_message is set NULL for an OK integer
+*/
+
+static int_eximarith_t
+expanded_string_integer(const uschar *s, BOOL isplus)
+{
 int_eximarith_t value;
-uschar *s = expand_string(string);
 uschar *msg = US"invalid integer \"%s\"";
 uschar *endptr;
 
@@ -6514,17 +7296,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 +7316,7 @@ else
   else
     {
     while (isspace(*endptr)) endptr++;
-    if (*endptr == 0) return (int)value;
+    if (*endptr == 0) return value;
     }
   }
 
@@ -6543,6 +7325,67 @@ return -2;
 }
 
 
+/* These values are usually fixed boolean values, but they are permitted to be
+expanded strings.
+
+Arguments:
+  addr       address being routed
+  mtype      the module type
+  mname      the module name
+  dbg_opt    debug selectors
+  oname      the option name
+  bvalue     the router's boolean value
+  svalue     the router's string value
+  rvalue     where to put the returned value
+
+Returns:     OK     value placed in rvalue
+             DEFER  expansion failed
+*/
+
+int
+exp_bool(address_item *addr,
+  uschar *mtype, uschar *mname, unsigned dbg_opt,
+  uschar *oname, BOOL bvalue,
+  uschar *svalue, BOOL *rvalue)
+{
+uschar *expanded;
+if (svalue == NULL) { *rvalue = bvalue; return OK; }
+
+expanded = expand_string(svalue);
+if (expanded == NULL)
+  {
+  if (expand_string_forcedfail)
+    {
+    DEBUG(dbg_opt) debug_printf("expansion of \"%s\" forced failure\n", oname);
+    *rvalue = bvalue;
+    return OK;
+    }
+  addr->message = string_sprintf("failed to expand \"%s\" in %s %s: %s",
+      oname, mname, mtype, expand_string_message);
+  DEBUG(dbg_opt) debug_printf("%s\n", addr->message);
+  return DEFER;
+  }
+
+DEBUG(dbg_opt) debug_printf("expansion of \"%s\" yields \"%s\"\n", oname,
+  expanded);
+
+if (strcmpic(expanded, US"true") == 0 || strcmpic(expanded, US"yes") == 0)
+  *rvalue = TRUE;
+else if (strcmpic(expanded, US"false") == 0 || strcmpic(expanded, US"no") == 0)
+  *rvalue = FALSE;
+else
+  {
+  addr->message = string_sprintf("\"%s\" is not a valid value for the "
+    "\"%s\" option in the %s %s", expanded, oname, mname, mtype);
+  return DEFER;
+  }
+
+return OK;
+}
+
+
+
+
 /*************************************************
 **************************************************
 *             Stand-alone test program           *
@@ -6557,7 +7400,7 @@ regex_match_and_setup(const pcre *re, uschar *subject, int options, int setup)
 {
 int ovector[3*(EXPAND_MAXN+1)];
 int n = pcre_exec(re, NULL, subject, Ustrlen(subject), 0, PCRE_EOPT|options,
-  ovector, sizeof(ovector)/sizeof(int));
+  ovector, nelem(ovector));
 BOOL yield = n >= 0;
 if (n == 0) n = EXPAND_MAXN + 1;
 if (yield)
@@ -6607,6 +7450,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];
@@ -6658,4 +7504,6 @@ return 0;
 
 #endif
 
+/* vi: aw ai sw=2
+*/
 /* End of expand.c */