Dnssec observability: add variable $lookup_dnssec_authenticated
[users/jgh/exim.git] / src / src / expand.c
index 47453dc6d183b665bab2cf4c8167a6cfd4aa31bd..54b3abc5498c4017f8ff4494fdec284d3afd4bed 100644 (file)
@@ -1,10 +1,8 @@
-/* $Cambridge: exim/src/src/expand.c,v 1.103 2009/10/15 08:27:37 tom Exp $ */
-
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2007 */
+/* Copyright (c) University of Cambridge 1995 - 2014 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -15,7 +13,7 @@
 
 /* Recursively called function */
 
-static uschar *expand_string_internal(uschar *, BOOL, uschar **, BOOL);
+static uschar *expand_string_internal(uschar *, BOOL, uschar **, BOOL, BOOL, BOOL *);
 
 #ifdef STAND_ALONE
 #ifndef SUPPORT_CRYPTEQ
@@ -104,6 +102,7 @@ bcrypt ({CRYPT}$2a$).
 alphabetical order. */
 
 static uschar *item_table[] = {
+  US"acl",
   US"dlfunc",
   US"extract",
   US"filter",
@@ -111,6 +110,7 @@ static uschar *item_table[] = {
   US"hmac",
   US"if",
   US"length",
+  US"listextract",
   US"lookup",
   US"map",
   US"nhash",
@@ -126,6 +126,7 @@ static uschar *item_table[] = {
   US"tr" };
 
 enum {
+  EITEM_ACL,
   EITEM_DLFUNC,
   EITEM_EXTRACT,
   EITEM_FILTER,
@@ -133,6 +134,7 @@ enum {
   EITEM_HMAC,
   EITEM_IF,
   EITEM_LENGTH,
+  EITEM_LISTEXTRACT,
   EITEM_LOOKUP,
   EITEM_MAP,
   EITEM_NHASH,
@@ -156,6 +158,7 @@ static uschar *op_table_underscore[] = {
   US"from_utf8",
   US"local_part",
   US"quote_local_part",
+  US"reverse_ip",
   US"time_eval",
   US"time_interval"};
 
@@ -163,6 +166,7 @@ enum {
   EOP_FROM_UTF8,
   EOP_LOCAL_PART,
   EOP_QUOTE_LOCAL_PART,
+  EOP_REVERSE_IP,
   EOP_TIME_EVAL,
   EOP_TIME_INTERVAL };
 
@@ -179,14 +183,18 @@ static uschar *op_table_main[] = {
   US"h",
   US"hash",
   US"hex2b64",
+  US"hexquote",
   US"l",
   US"lc",
   US"length",
+  US"listcount",
+  US"listnamed",
   US"mask",
   US"md5",
   US"nh",
   US"nhash",
   US"quote",
+  US"randint",
   US"rfc2047",
   US"rfc2047d",
   US"rxquote",
@@ -196,7 +204,8 @@ static uschar *op_table_main[] = {
   US"str2b64",
   US"strlen",
   US"substr",
-  US"uc" };
+  US"uc",
+  US"utf8clean" };
 
 enum {
   EOP_ADDRESS =  sizeof(op_table_underscore)/sizeof(uschar *),
@@ -211,14 +220,18 @@ enum {
   EOP_H,
   EOP_HASH,
   EOP_HEX2B64,
+  EOP_HEXQUOTE,
   EOP_L,
   EOP_LC,
   EOP_LENGTH,
+  EOP_LISTCOUNT,
+  EOP_LISTNAMED,
   EOP_MASK,
   EOP_MD5,
   EOP_NH,
   EOP_NHASH,
   EOP_QUOTE,
+  EOP_RANDINT,
   EOP_RFC2047,
   EOP_RFC2047D,
   EOP_RXQUOTE,
@@ -228,7 +241,8 @@ enum {
   EOP_STR2B64,
   EOP_STRLEN,
   EOP_SUBSTR,
-  EOP_UC };
+  EOP_UC,
+  EOP_UTF8CLEAN };
 
 
 /* Table of condition names, and corresponding switch numbers. The names must
@@ -241,8 +255,10 @@ static uschar *cond_table[] = {
   US"==",     /* Backward compatibility */
   US">",
   US">=",
+  US"acl",
   US"and",
   US"bool",
+  US"bool_lax",
   US"crypteq",
   US"def",
   US"eq",
@@ -255,6 +271,8 @@ static uschar *cond_table[] = {
   US"gei",
   US"gt",
   US"gti",
+  US"inlist",
+  US"inlisti",
   US"isip",
   US"isip4",
   US"isip6",
@@ -283,8 +301,10 @@ enum {
   ECOND_NUM_EE,
   ECOND_NUM_G,
   ECOND_NUM_GE,
+  ECOND_ACL,
   ECOND_AND,
   ECOND_BOOL,
+  ECOND_BOOL_LAX,
   ECOND_CRYPTEQ,
   ECOND_DEF,
   ECOND_STR_EQ,
@@ -297,6 +317,8 @@ enum {
   ECOND_STR_GEI,
   ECOND_STR_GT,
   ECOND_STR_GTI,
+  ECOND_INLIST,
+  ECOND_INLISTI,
   ECOND_ISIP,
   ECOND_ISIP4,
   ECOND_ISIP6,
@@ -322,9 +344,9 @@ enum {
 /* Type for main variable table */
 
 typedef struct {
-  char *name;
-  int   type;
-  void *value;
+  const char *name;
+  int         type;
+  void       *value;
 } var_entry;
 
 /* Type for entries pointing to address/length pairs. Not currently
@@ -343,6 +365,7 @@ enum {
   vtype_ino,            /* value is address of ino_t (not always an int) */
   vtype_uid,            /* value is address of uid_t (not always an int) */
   vtype_gid,            /* value is address of gid_t (not always an int) */
+  vtype_bool,           /* value is address of bool */
   vtype_stringptr,      /* value is address of pointer to string */
   vtype_msgbody,        /* as stringptr, but read when first required */
   vtype_msgbody_end,    /* ditto, the end of the message */
@@ -350,11 +373,10 @@ enum {
   vtype_msgheaders_raw, /* the message's headers, unprocessed */
   vtype_localpart,      /* extract local part from string */
   vtype_domain,         /* extract domain from string */
-  vtype_recipients,     /* extract recipients from recipients list */
-                        /* (available only in system filters, ACLs, and */
-                        /* local_scan()) */
+  vtype_string_func,   /* value is string returned by given function */
   vtype_todbsdin,       /* value not used; generate BSD inbox tod */
   vtype_tode,           /* value not used; generate tod in epoch format */
+  vtype_todel,          /* value not used; generate tod in epoch/usec format */
   vtype_todf,           /* value not used; generate full tod */
   vtype_todl,           /* value not used; generate log tod */
   vtype_todlf,          /* value not used; generate log file datestamp tod */
@@ -371,18 +393,34 @@ enum {
   #endif
   };
 
+static uschar * fn_recipients(void);
+
 /* This table must be kept in alphabetical order. */
 
 static var_entry var_table[] = {
   /* WARNING: Do not invent variables whose names start acl_c or acl_m because
      they will be confused with user-creatable ACL variables. */
+  { "acl_arg1",            vtype_stringptr,   &acl_arg[0] },
+  { "acl_arg2",            vtype_stringptr,   &acl_arg[1] },
+  { "acl_arg3",            vtype_stringptr,   &acl_arg[2] },
+  { "acl_arg4",            vtype_stringptr,   &acl_arg[3] },
+  { "acl_arg5",            vtype_stringptr,   &acl_arg[4] },
+  { "acl_arg6",            vtype_stringptr,   &acl_arg[5] },
+  { "acl_arg7",            vtype_stringptr,   &acl_arg[6] },
+  { "acl_arg8",            vtype_stringptr,   &acl_arg[7] },
+  { "acl_arg9",            vtype_stringptr,   &acl_arg[8] },
+  { "acl_narg",            vtype_int,         &acl_narg },
   { "acl_verify_message",  vtype_stringptr,   &acl_verify_message },
   { "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 },
+#ifdef WITH_CONTENT_SCAN
+  { "av_failed",           vtype_int,         &av_failed },
+#endif
 #ifdef EXPERIMENTAL_BRIGHTMAIL
   { "bmi_alt_location",    vtype_stringptr,   &bmi_alt_location },
   { "bmi_base64_tracker_verdict", vtype_stringptr, &bmi_base64_tracker_verdict },
@@ -427,6 +465,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 },
@@ -440,6 +485,7 @@ static var_entry var_table[] = {
 #ifdef WITH_OLD_DEMIME
   { "found_extension",     vtype_stringptr,   &found_extension },
 #endif
+  { "headers_added",       vtype_string_func, &fn_hdrs_added },
   { "home",                vtype_stringptr,   &deliver_home },
   { "host",                vtype_stringptr,   &deliver_host },
   { "host_address",        vtype_stringptr,   &deliver_host_address },
@@ -464,6 +510,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 },
@@ -515,6 +562,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 },
@@ -531,7 +585,7 @@ static var_entry var_table[] = {
   { "received_time",       vtype_int,         &received_time },
   { "recipient_data",      vtype_stringptr,   &recipient_data },
   { "recipient_verify_failure",vtype_stringptr,&recipient_verify_failure },
-  { "recipients",          vtype_recipients,  NULL },
+  { "recipients",          vtype_string_func, &fn_recipients },
   { "recipients_count",    vtype_int,         &recipients_count },
 #ifdef WITH_CONTENT_SCAN
   { "regex_match_string",  vtype_stringptr,   &regex_match_string },
@@ -539,6 +593,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 },
@@ -550,6 +605,7 @@ static var_entry var_table[] = {
   { "sender_helo_name",    vtype_stringptr,   &sender_helo_name },
   { "sender_host_address", vtype_stringptr,   &sender_host_address },
   { "sender_host_authenticated",vtype_stringptr, &sender_host_authenticated },
+  { "sender_host_dnssec",  vtype_bool,        &sender_host_dnssec },
   { "sender_host_name",    vtype_host_lookup, NULL },
   { "sender_host_port",    vtype_int,         &sender_host_port },
   { "sender_ident",        vtype_stringptr,   &sender_ident },
@@ -600,16 +656,51 @@ static var_entry var_table[] = {
   { "srs_status",          vtype_stringptr,   &srs_status },
 #endif
   { "thisaddress",         vtype_stringptr,   &filter_thisaddress },
-  { "tls_certificate_verified", vtype_int,    &tls_certificate_verified },
-  { "tls_cipher",          vtype_stringptr,   &tls_cipher },
-  { "tls_peerdn",          vtype_stringptr,   &tls_peerdn },
+
+  /* The non-(in,out) variables are now deprecated */
+  { "tls_bits",            vtype_int,         &tls_in.bits },
+  { "tls_certificate_verified", vtype_int,    &tls_in.certificate_verified },
+  { "tls_cipher",          vtype_stringptr,   &tls_in.cipher },
+
+  { "tls_in_bits",         vtype_int,         &tls_in.bits },
+  { "tls_in_certificate_verified", vtype_int, &tls_in.certificate_verified },
+  { "tls_in_cipher",       vtype_stringptr,   &tls_in.cipher },
+  { "tls_in_peerdn",       vtype_stringptr,   &tls_in.peerdn },
+#if defined(SUPPORT_TLS) && !defined(USE_GNUTLS)
+  { "tls_in_sni",          vtype_stringptr,   &tls_in.sni },
+#endif
+  { "tls_out_bits",        vtype_int,         &tls_out.bits },
+  { "tls_out_certificate_verified", vtype_int,&tls_out.certificate_verified },
+  { "tls_out_cipher",      vtype_stringptr,   &tls_out.cipher },
+  { "tls_out_peerdn",      vtype_stringptr,   &tls_out.peerdn },
+#if defined(SUPPORT_TLS) && !defined(USE_GNUTLS)
+  { "tls_out_sni",         vtype_stringptr,   &tls_out.sni },
+#endif
+
+  { "tls_peerdn",          vtype_stringptr,   &tls_in.peerdn },        /* mind the alphabetical order! */
+#if defined(SUPPORT_TLS) && !defined(USE_GNUTLS)
+  { "tls_sni",             vtype_stringptr,   &tls_in.sni },   /* mind the alphabetical order! */
+#endif
+
   { "tod_bsdinbox",        vtype_todbsdin,    NULL },
   { "tod_epoch",           vtype_tode,        NULL },
+  { "tod_epoch_l",         vtype_todel,       NULL },
   { "tod_full",            vtype_todf,        NULL },
   { "tod_log",             vtype_todl,        NULL },
   { "tod_logfile",         vtype_todlf,       NULL },
   { "tod_zone",            vtype_todzone,     NULL },
   { "tod_zulu",            vtype_todzulu,     NULL },
+#ifdef EXPERIMENTAL_TPDA
+  { "tpda_defer_errno",     vtype_int,         &tpda_defer_errno },
+  { "tpda_defer_errstr",    vtype_stringptr,   &tpda_defer_errstr },
+  { "tpda_delivery_confirmation", vtype_stringptr,   &tpda_delivery_confirmation },
+  { "tpda_delivery_domain", vtype_stringptr,   &tpda_delivery_domain },
+  { "tpda_delivery_fqdn",   vtype_stringptr,   &tpda_delivery_fqdn },
+  { "tpda_delivery_ip",     vtype_stringptr,   &tpda_delivery_ip },
+  { "tpda_delivery_local_part",vtype_stringptr,&tpda_delivery_local_part },
+  { "tpda_delivery_port",   vtype_int,         &tpda_delivery_port },
+#endif
+  { "transport_name",      vtype_stringptr,   &transport_name },
   { "value",               vtype_stringptr,   &lookup_value },
   { "version_number",      vtype_stringptr,   &version_string },
   { "warn_message_delay",  vtype_stringptr,   &warnmsg_delay },
@@ -626,9 +717,9 @@ static BOOL malformed_header;
 
 /* For textual hashes */
 
-static char *hashcodes = "abcdefghijklmnopqrtsuvwxyz"
-                         "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
-                         "0123456789";
+static const char *hashcodes = "abcdefghijklmnopqrtsuvwxyz"
+                               "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+                               "0123456789";
 
 enum { HMAC_MD5, HMAC_SHA1 };
 
@@ -727,8 +818,13 @@ return -1;
 
 /* This function is called to expand a string, and test the result for a "true"
 or "false" value. Failure of the expansion yields FALSE; logged unless it was a
-forced fail or lookup defer. All store used by the function can be released on
-exit.
+forced fail or lookup defer.
+
+We used to release all store used, but this is not not safe due
+to ${dlfunc } and ${acl }.  In any case expand_string_internal()
+is reasonably careful to release what it can.
+
+The actual false-value tests should be replicated for ECOND_BOOL_LAX.
 
 Arguments:
   condition     the condition string
@@ -742,7 +838,6 @@ BOOL
 expand_check_condition(uschar *condition, uschar *m1, uschar *m2)
 {
 int rc;
-void *reset_point = store_get(0);
 uschar *ss = expand_string(condition);
 if (ss == NULL)
   {
@@ -753,12 +848,86 @@ if (ss == NULL)
   }
 rc = ss[0] != 0 && Ustrcmp(ss, "0") != 0 && strcmpic(ss, US"no") != 0 &&
   strcmpic(ss, US"false") != 0;
-store_reset(reset_point);
 return rc;
 }
 
 
 
+
+/*************************************************
+*        Pseudo-random number generation         *
+*************************************************/
+
+/* Pseudo-random number generation.  The result is not "expected" to be
+cryptographically strong but not so weak that someone will shoot themselves
+in the foot using it as a nonce in some email header scheme or whatever
+weirdness they'll twist this into.  The result should ideally handle fork().
+
+However, if we're stuck unable to provide this, then we'll fall back to
+appallingly bad randomness.
+
+If SUPPORT_TLS is defined then this will not be used except as an emergency
+fallback.
+
+Arguments:
+  max       range maximum
+Returns     a random number in range [0, max-1]
+*/
+
+#ifdef SUPPORT_TLS
+# define vaguely_random_number vaguely_random_number_fallback
+#endif
+int
+vaguely_random_number(int max)
+{
+#ifdef SUPPORT_TLS
+# undef vaguely_random_number
+#endif
+  static pid_t pid = 0;
+  pid_t p2;
+#if defined(HAVE_SRANDOM) && !defined(HAVE_SRANDOMDEV)
+  struct timeval tv;
+#endif
+
+  p2 = getpid();
+  if (p2 != pid)
+    {
+    if (pid != 0)
+      {
+
+#ifdef HAVE_ARC4RANDOM
+      /* cryptographically strong randomness, common on *BSD platforms, not
+      so much elsewhere.  Alas. */
+      arc4random_stir();
+#elif defined(HAVE_SRANDOM) || defined(HAVE_SRANDOMDEV)
+#ifdef HAVE_SRANDOMDEV
+      /* uses random(4) for seeding */
+      srandomdev();
+#else
+      gettimeofday(&tv, NULL);
+      srandom(tv.tv_sec | tv.tv_usec | getpid());
+#endif
+#else
+      /* Poor randomness and no seeding here */
+#endif
+
+      }
+    pid = p2;
+    }
+
+#ifdef HAVE_ARC4RANDOM
+  return arc4random() % max;
+#elif defined(HAVE_SRANDOM) || defined(HAVE_SRANDOMDEV)
+  return random() % max;
+#else
+  /* This one returns a 16-bit number, definitely not crypto-strong */
+  return random_number(max);
+#endif
+}
+
+
+
+
 /*************************************************
 *             Pick out a name from a string      *
 *************************************************/
@@ -970,6 +1139,22 @@ return fieldtext;
 }
 
 
+static uschar *
+expand_getlistele (int field, uschar *list)
+{
+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);
+}
 
 /*************************************************
 *        Extract a substring from a string       *
@@ -1313,6 +1498,34 @@ return yield;
 
 
 
+/*************************************************
+*               Return list of recipients        *
+*************************************************/
+/* A recipients list is available only during system message filtering,
+during ACL processing after DATA, and while expanding pipe commands
+generated from a system filter, but not elsewhere. */
+
+static uschar *
+fn_recipients(void)
+{
+if (!enable_dollar_recipients) return NULL; else
+  {
+  int size = 128;
+  int ptr = 0;
+  int i;
+  uschar * s = store_get(size);
+  for (i = 0; i < recipients_count; i++)
+    {
+    if (i != 0) s = string_cat(s, &size, &ptr, US", ", 2);
+    s = string_cat(s, &size, &ptr, recipients_list[i].address,
+      Ustrlen(recipients_list[i].address));
+    }
+  s[ptr] = 0;     /* string_cat() leaves room */
+  return s;
+  }
+}
+
+
 /*************************************************
 *               Find value of a variable         *
 *************************************************/
@@ -1409,6 +1622,10 @@ while (last > first)
     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;
@@ -1433,8 +1650,8 @@ while (last > first)
     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 %d in "
-        "string expansion", sizeof(var_buffer));
+      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;
@@ -1497,6 +1714,9 @@ while (last > first)
     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_todf:                           /* Full time of day */
     return tod_stamp(tod_full);
 
@@ -1510,7 +1730,7 @@ while (last > first)
     return tod_stamp(tod_zulu);
 
     case vtype_todlf:                          /* Log file datestamp tod */
-    return tod_stamp(tod_log_datestamp);
+    return tod_stamp(tod_log_datestamp_daily);
 
     case vtype_reply:                          /* Get reply address */
     s = find_header(US"reply-to:", exists_only, newsize, TRUE,
@@ -1531,26 +1751,11 @@ while (last > first)
       }
     return (s == NULL)? US"" : s;
 
-    /* A recipients list is available only during system message filtering,
-    during ACL processing after DATA, and while expanding pipe commands
-    generated from a system filter, but not elsewhere. */
-
-    case vtype_recipients:
-    if (!enable_dollar_recipients) return NULL; else
+    case vtype_string_func:
       {
-      int size = 128;
-      int ptr = 0;
-      int i;
-      s = store_get(size);
-      for (i = 0; i < recipients_count; i++)
-        {
-        if (i != 0) s = string_cat(s, &size, &ptr, US", ", 2);
-        s = string_cat(s, &size, &ptr, recipients_list[i].address,
-          Ustrlen(recipients_list[i].address));
-        }
-      s[ptr] = 0;     /* string_cat() leaves room */
+      uschar * (*fn)() = var_table[middle].value;
+      return fn();
       }
-    return s;
 
     case vtype_pspace:
       {
@@ -1582,6 +1787,31 @@ 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;
+  }
+return;          /* Unknown variable name, fail silently */
+}
+
+
+
+
+
 /*************************************************
 *           Read and expand substrings           *
 *************************************************/
@@ -1598,6 +1828,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)
@@ -1607,7 +1839,7 @@ Returns:     0 OK; string pointer updated
 
 static int
 read_subs(uschar **sub, int n, int m, uschar **sptr, BOOL skipping,
-  BOOL check_end, uschar *name)
+  BOOL check_end, uschar *name, BOOL *resetok)
 {
 int i;
 uschar *s = *sptr;
@@ -1621,7 +1853,7 @@ for (i = 0; i < n; i++)
     sub[i] = NULL;
     break;
     }
-  sub[i] = expand_string_internal(s+1, TRUE, &s, skipping);
+  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++;
@@ -1671,6 +1903,58 @@ if (Ustrncmp(name, "acl_", 4) == 0)
 
 
 
+/*
+Load args from sub array to globals, and call acl_check().
+Sub array will be corrupted on return.
+
+Returns:       OK         access is granted by an ACCEPT verb
+               DISCARD    access is granted by a DISCARD verb
+              FAIL       access is denied
+              FAIL_DROP  access is denied; drop the connection
+              DEFER      can't tell at the moment
+              ERROR      disaster
+*/
+static int
+eval_acl(uschar ** sub, int nsub, uschar ** user_msgp)
+{
+int i;
+uschar *tmp;
+int sav_narg = acl_narg;
+int ret;
+extern int acl_where;
+
+if(--nsub > sizeof(acl_arg)/sizeof(*acl_arg)) nsub = sizeof(acl_arg)/sizeof(*acl_arg);
+for (i = 0; i < nsub && sub[i+1]; i++)
+  {
+  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 */
+  }
+acl_narg = i;
+while (i < nsub)
+  {
+  sub[i+1] = acl_arg[i];
+  acl_arg[i++] = NULL;
+  }
+
+DEBUG(D_expand)
+  debug_printf("expanding: acl: %s  arg: %s%s\n",
+    sub[0],
+    acl_narg>0 ? acl_arg[0] : US"<none>",
+    acl_narg>1 ? " +more"   : "");
+
+ret = acl_eval(acl_where, sub[0], user_msgp, &tmp);
+
+for (i = 0; i < nsub; i++)
+  acl_arg[i] = sub[i+1];       /* restore old args */
+acl_narg = sav_narg;
+
+return ret;
+}
+
+
+
+
 /*************************************************
 *        Read and evaluate a condition           *
 *************************************************/
@@ -1678,6 +1962,9 @@ if (Ustrncmp(name, "acl_", 4) == 0)
 /*
 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
@@ -1688,16 +1975,17 @@ Returns:   a pointer to the first character after the condition, or
 */
 
 static uschar *
-eval_condition(uschar *s, BOOL *yield)
+eval_condition(uschar *s, BOOL *resetok, BOOL *yield)
 {
 BOOL testfor = TRUE;
 BOOL tempcond, combined_cond;
 BOOL *subcondptr;
+BOOL sub2_honour_dollar = TRUE;
 int i, rc, cond_type, roffset;
-int num[2];
+int_eximarith_t num[2];
 struct stat statbuf;
 uschar name[256];
-uschar *sub[4];
+uschar *sub[10];
 
 const pcre *re;
 const uschar *rerror;
@@ -1764,6 +2052,7 @@ switch(cond_type)
       Ustrncmp(name, "bheader_", 8) == 0)
     {
     s = read_header_name(name, 256, s);
+    /* {-for-text-editors */
     if (Ustrchr(name, '}') != NULL) malformed_header = TRUE;
     if (yield != NULL) *yield =
       (find_header(name, TRUE, NULL, FALSE, NULL) != NULL) == testfor;
@@ -1823,10 +2112,11 @@ switch(cond_type)
   case ECOND_PWCHECK:
 
   while (isspace(*s)) s++;
-  if (*s != '{') goto COND_FAILED_CURLY_START;
+  if (*s != '{') goto COND_FAILED_CURLY_START;         /* }-for-text-editors */
 
-  sub[0] = expand_string_internal(s+1, TRUE, &s, yield == NULL);
+  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;
 
   if (yield == NULL) return s;   /* No need to run the test if skipping */
@@ -1902,20 +2192,75 @@ switch(cond_type)
   return s;
 
 
+  /* call ACL (in a conditional context).  Accept true, deny false.
+  Defer is a forced-fail.  Anything set by message= goes to $value.
+  Up to ten parameters are used; we use the braces round the name+args
+  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 *user_msg;
+    BOOL cond = FALSE;
+    int size = 0;
+    int ptr = 0;
+
+    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", resetok))
+      {
+      case 1: expand_string_message = US"too few arguments or bracketing "
+        "error for acl";
+      case 2:
+      case 3: return NULL;
+      }
+
+    *resetok = FALSE;
+    if (yield != NULL) switch(eval_acl(sub, sizeof(sub)/sizeof(*sub), &user_msg))
+       {
+       case OK:
+         cond = TRUE;
+       case FAIL:
+          lookup_value = NULL;
+         if (user_msg)
+           {
+            lookup_value = string_cat(NULL, &size, &ptr, user_msg, Ustrlen(user_msg));
+            lookup_value[ptr] = '\0';
+           }
+         *yield = cond == testfor;
+         break;
+
+       case DEFER:
+          expand_string_forcedfail = TRUE;
+       default:
+          expand_string_message = string_sprintf("error from acl \"%s\"", sub[0]);
+         return NULL;
+       }
+    return s;
+    }
+
+
   /* 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 or braces. */
+  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;
-  switch(read_subs(sub, 4, 2, &s, yield == NULL, TRUE, US"saslauthd"))
+  if (*s++ != '{') goto COND_FAILED_CURLY_START;       /* }-for-text-editors */
+  switch(read_subs(sub, 4, 2, &s, yield == NULL, TRUE, US"saslauthd", resetok))
     {
     case 1: expand_string_message = US"too few arguments or bracketing "
       "error for saslauthd";
@@ -1938,22 +2283,30 @@ switch(cond_type)
   /* symbolic operators for numeric and string comparison, and a number of
   other operators, all requiring two arguments.
 
+  crypteq:           encrypts plaintext and compares against an encrypted text,
+                       using crypt(), crypt16(), MD5 or SHA-1
+  inlist/inlisti:    checks if first argument is in the list of the second
   match:             does a regular expression match and sets up the numerical
                        variables if it succeeds
   match_address:     matches in an address list
   match_domain:      matches in a domain list
   match_ip:          matches a host list that is restricted to IP addresses
   match_local_part:  matches in a local part list
-  crypteq:           encrypts plaintext and compares against an encrypted text,
-                       using crypt(), crypt16(), MD5 or SHA-1
   */
 
-  case ECOND_MATCH:
   case ECOND_MATCH_ADDRESS:
   case ECOND_MATCH_DOMAIN:
   case ECOND_MATCH_IP:
   case ECOND_MATCH_LOCAL_PART:
+#ifndef EXPAND_LISTMATCH_RHS
+    sub2_honour_dollar = FALSE;
+#endif
+    /* FALLTHROUGH */
+
   case ECOND_CRYPTEQ:
+  case ECOND_INLIST:
+  case ECOND_INLISTI:
+  case ECOND_MATCH:
 
   case ECOND_NUM_L:     /* Numerical comparisons */
   case ECOND_NUM_LE:
@@ -1975,6 +2328,13 @@ switch(cond_type)
 
   for (i = 0; i < 2; i++)
     {
+    /* Sometimes, we don't expand substrings; too many insecure configurations
+    created using match_address{}{} and friends, where the second param
+    includes information from untrustworthy sources. */
+    BOOL honour_dollar = TRUE;
+    if ((i > 0) && !sub2_honour_dollar)
+      honour_dollar = FALSE;
+
     while (isspace(*s)) s++;
     if (*s != '{')
       {
@@ -1983,7 +2343,8 @@ switch(cond_type)
         "after \"%s\"", name);
       return NULL;
       }
-    sub[i] = expand_string_internal(s+1, TRUE, &s, yield == NULL);
+    sub[i] = expand_string_internal(s+1, TRUE, &s, yield == NULL,
+        honour_dollar, resetok);
     if (sub[i] == NULL) return NULL;
     if (*s++ != '}') goto COND_FAILED_CURLY_END;
 
@@ -2017,63 +2378,63 @@ switch(cond_type)
     {
     case ECOND_NUM_E:
     case ECOND_NUM_EE:
-    *yield = (num[0] == num[1]) == testfor;
+    tempcond = (num[0] == num[1]);
     break;
 
     case ECOND_NUM_G:
-    *yield = (num[0] > num[1]) == testfor;
+    tempcond = (num[0] > num[1]);
     break;
 
     case ECOND_NUM_GE:
-    *yield = (num[0] >= num[1]) == testfor;
+    tempcond = (num[0] >= num[1]);
     break;
 
     case ECOND_NUM_L:
-    *yield = (num[0] < num[1]) == testfor;
+    tempcond = (num[0] < num[1]);
     break;
 
     case ECOND_NUM_LE:
-    *yield = (num[0] <= num[1]) == testfor;
+    tempcond = (num[0] <= num[1]);
     break;
 
     case ECOND_STR_LT:
-    *yield = (Ustrcmp(sub[0], sub[1]) < 0) == testfor;
+    tempcond = (Ustrcmp(sub[0], sub[1]) < 0);
     break;
 
     case ECOND_STR_LTI:
-    *yield = (strcmpic(sub[0], sub[1]) < 0) == testfor;
+    tempcond = (strcmpic(sub[0], sub[1]) < 0);
     break;
 
     case ECOND_STR_LE:
-    *yield = (Ustrcmp(sub[0], sub[1]) <= 0) == testfor;
+    tempcond = (Ustrcmp(sub[0], sub[1]) <= 0);
     break;
 
     case ECOND_STR_LEI:
-    *yield = (strcmpic(sub[0], sub[1]) <= 0) == testfor;
+    tempcond = (strcmpic(sub[0], sub[1]) <= 0);
     break;
 
     case ECOND_STR_EQ:
-    *yield = (Ustrcmp(sub[0], sub[1]) == 0) == testfor;
+    tempcond = (Ustrcmp(sub[0], sub[1]) == 0);
     break;
 
     case ECOND_STR_EQI:
-    *yield = (strcmpic(sub[0], sub[1]) == 0) == testfor;
+    tempcond = (strcmpic(sub[0], sub[1]) == 0);
     break;
 
     case ECOND_STR_GT:
-    *yield = (Ustrcmp(sub[0], sub[1]) > 0) == testfor;
+    tempcond = (Ustrcmp(sub[0], sub[1]) > 0);
     break;
 
     case ECOND_STR_GTI:
-    *yield = (strcmpic(sub[0], sub[1]) > 0) == testfor;
+    tempcond = (strcmpic(sub[0], sub[1]) > 0);
     break;
 
     case ECOND_STR_GE:
-    *yield = (Ustrcmp(sub[0], sub[1]) >= 0) == testfor;
+    tempcond = (Ustrcmp(sub[0], sub[1]) >= 0);
     break;
 
     case ECOND_STR_GEI:
-    *yield = (strcmpic(sub[0], sub[1]) >= 0) == testfor;
+    tempcond = (strcmpic(sub[0], sub[1]) >= 0);
     break;
 
     case ECOND_MATCH:   /* Regular expression match */
@@ -2085,7 +2446,7 @@ switch(cond_type)
         "\"%s\": %s at offset %d", sub[1], rerror, roffset);
       return NULL;
       }
-    *yield = regex_match_and_setup(re, sub[0], 0, -1) == testfor;
+    tempcond = regex_match_and_setup(re, sub[0], 0, -1);
     break;
 
     case ECOND_MATCH_ADDRESS:  /* Match in an address list */
@@ -2141,11 +2502,11 @@ switch(cond_type)
     switch(rc)
       {
       case OK:
-      *yield = testfor;
+      tempcond = TRUE;
       break;
 
       case FAIL:
-      *yield = !testfor;
+      tempcond = FALSE;
       break;
 
       case DEFER:
@@ -2159,6 +2520,7 @@ switch(cond_type)
     /* Various "encrypted" comparisons. If the second string starts with
     "{" then an encryption type is given. Default to crypt() or crypt16()
     (build-time choice). */
+    /* }-for-text-editors */
 
     case ECOND_CRYPTEQ:
     #ifndef SUPPORT_CRYPTEQ
@@ -2183,7 +2545,7 @@ switch(cond_type)
         uschar *coded = auth_b64encode((uschar *)digest, 16);
         DEBUG(D_auth) debug_printf("crypteq: using MD5+B64 hashing\n"
           "  subject=%s\n  crypted=%s\n", coded, sub[1]+5);
-        *yield = (Ustrcmp(coded, sub[1]+5) == 0) == testfor;
+        tempcond = (Ustrcmp(coded, sub[1]+5) == 0);
         }
       else if (sublen == 32)
         {
@@ -2193,13 +2555,13 @@ switch(cond_type)
         coded[32] = 0;
         DEBUG(D_auth) debug_printf("crypteq: using MD5+hex hashing\n"
           "  subject=%s\n  crypted=%s\n", coded, sub[1]+5);
-        *yield = (strcmpic(coded, sub[1]+5) == 0) == testfor;
+        tempcond = (strcmpic(coded, sub[1]+5) == 0);
         }
       else
         {
         DEBUG(D_auth) debug_printf("crypteq: length for MD5 not 24 or 32: "
           "fail\n  crypted=%s\n", sub[1]+5);
-        *yield = !testfor;
+        tempcond = FALSE;
         }
       }
 
@@ -2221,7 +2583,7 @@ switch(cond_type)
         uschar *coded = auth_b64encode((uschar *)digest, 20);
         DEBUG(D_auth) debug_printf("crypteq: using SHA1+B64 hashing\n"
           "  subject=%s\n  crypted=%s\n", coded, sub[1]+6);
-        *yield = (Ustrcmp(coded, sub[1]+6) == 0) == testfor;
+        tempcond = (Ustrcmp(coded, sub[1]+6) == 0);
         }
       else if (sublen == 40)
         {
@@ -2231,17 +2593,18 @@ switch(cond_type)
         coded[40] = 0;
         DEBUG(D_auth) debug_printf("crypteq: using SHA1+hex hashing\n"
           "  subject=%s\n  crypted=%s\n", coded, sub[1]+6);
-        *yield = (strcmpic(coded, sub[1]+6) == 0) == testfor;
+        tempcond = (strcmpic(coded, sub[1]+6) == 0);
         }
       else
         {
         DEBUG(D_auth) debug_printf("crypteq: length for SHA-1 not 28 or 40: "
           "fail\n  crypted=%s\n", sub[1]+6);
-        *yield = !testfor;
+       tempcond = FALSE;
         }
       }
 
     else   /* {crypt} or {crypt16} and non-{ at start */
+           /* }-for-text-editors */
       {
       int which = 0;
       uschar *coded;
@@ -2256,7 +2619,7 @@ switch(cond_type)
         sub[1] += 9;
         which = 2;
         }
-      else if (sub[1][0] == '{')
+      else if (sub[1][0] == '{')               /* }-for-text-editors */
         {
         expand_string_message = string_sprintf("unknown encryption mechanism "
           "in \"%s\"", sub[1]);
@@ -2283,13 +2646,37 @@ switch(cond_type)
       salt), force failure. Otherwise we get false positives: with an empty
       string the yield of crypt() is an empty string! */
 
-      *yield = (Ustrlen(sub[1]) < 2)? !testfor :
-        (Ustrcmp(coded, sub[1]) == 0) == testfor;
+      tempcond = (Ustrlen(sub[1]) < 2)? FALSE :
+        (Ustrcmp(coded, sub[1]) == 0);
       }
     break;
     #endif  /* SUPPORT_CRYPTEQ */
+
+    case ECOND_INLIST:
+    case ECOND_INLISTI:
+      {
+      int sep = 0;
+      uschar *save_iterate_item = iterate_item;
+      int (*compare)(const uschar *, const uschar *);
+
+      tempcond = FALSE;
+      if (cond_type == ECOND_INLISTI)
+        compare = strcmpic;
+      else
+        compare = (int (*)(const uschar *, const uschar *)) strcmp;
+
+      while ((iterate_item = string_nextinlist(&sub[1], &sep, NULL, 0)) != NULL)
+        if (compare(sub[0], iterate_item) == 0)
+          {
+          tempcond = TRUE;
+          break;
+          }
+      iterate_item = save_iterate_item;
+      }
+
     }   /* Switch for comparison conditions */
 
+  *yield = tempcond == testfor;
   return s;    /* End of comparison conditions */
 
 
@@ -2301,21 +2688,21 @@ switch(cond_type)
   combined_cond = (cond_type == ECOND_AND);
 
   while (isspace(*s)) s++;
-  if (*s++ != '{') goto COND_FAILED_CURLY_START;
+  if (*s++ != '{') goto COND_FAILED_CURLY_START;       /* }-for-text-editors */
 
   for (;;)
     {
     while (isspace(*s)) s++;
+    /* {-for-text-editors */
     if (*s == '}') break;
-    if (*s != '{')
+    if (*s != '{')                                     /* }-for-text-editors */
       {
       expand_string_message = string_sprintf("each subcondition "
         "inside an \"%s{...}\" condition must be in its own {}", name);
       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);
@@ -2323,8 +2710,10 @@ switch(cond_type)
       }
     while (isspace(*s)) s++;
 
+    /* {-for-text-editors */
     if (*s++ != '}')
       {
+      /* {-for-text-editors */
       expand_string_message = string_sprintf("missing } at end of condition "
         "inside \"%s\" group", name);
       return NULL;
@@ -2358,13 +2747,14 @@ switch(cond_type)
     uschar *save_iterate_item = iterate_item;
 
     while (isspace(*s)) s++;
-    if (*s++ != '{') goto COND_FAILED_CURLY_START;
-    sub[0] = expand_string_internal(s, TRUE, &s, (yield == NULL));
+    if (*s++ != '{') goto COND_FAILED_CURLY_START;     /* }-for-text-editors */
+    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;
 
     while (isspace(*s)) s++;
-    if (*s++ != '{') goto COND_FAILED_CURLY_START;
+    if (*s++ != '{') goto COND_FAILED_CURLY_START;     /* }-for-text-editors */
 
     sub[1] = s;
 
@@ -2372,8 +2762,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);
@@ -2381,8 +2770,10 @@ switch(cond_type)
       }
     while (isspace(*s)) s++;
 
+    /* {-for-text-editors */
     if (*s++ != '}')
       {
+      /* {-for-text-editors */
       expand_string_message = string_sprintf("missing } at end of condition "
         "inside \"%s\"", name);
       return NULL;
@@ -2392,7 +2783,7 @@ switch(cond_type)
     while ((iterate_item = string_nextinlist(&sub[0], &sep, NULL, 0)) != NULL)
       {
       DEBUG(D_expand) debug_printf("%s: $item = \"%s\"\n", name, iterate_item);
-      if (eval_condition(sub[1], &tempcond) == NULL)
+      if (!eval_condition(sub[1], resetok, &tempcond))
         {
         expand_string_message = string_sprintf("%s inside \"%s\" condition",
           expand_string_message, name);
@@ -2418,19 +2809,25 @@ switch(cond_type)
   interpretation, where general data can be used and only a few values
   map to FALSE.
   Note that readconf.c boolean matching, for boolean configuration options,
-  only matches true/yes/false/no. */
+  only matches true/yes/false/no.
+  The bool_lax{} condition matches the Router logic, which is much more
+  liberal. */
   case ECOND_BOOL:
+  case ECOND_BOOL_LAX:
     {
     uschar *sub_arg[1];
-    uschar *t;
+    uschar *t, *t2;
+    uschar *ourname;
     size_t len;
     BOOL boolvalue = FALSE;
     while (isspace(*s)) s++;
-    if (*s != '{') goto COND_FAILED_CURLY_START;
-    switch(read_subs(sub_arg, 1, 1, &s, yield == NULL, FALSE, US"bool"))
+    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, resetok))
       {
-      case 1: expand_string_message = US"too few arguments or bracketing "
-        "error for bool";
+      case 1: expand_string_message = string_sprintf(
+                  "too few arguments or bracketing error for %s",
+                  ourname);
       /*FALLTHROUGH*/
       case 2:
       case 3: return NULL;
@@ -2438,23 +2835,46 @@ switch(cond_type)
     t = sub_arg[0];
     while (isspace(*t)) t++;
     len = Ustrlen(t);
+    if (len)
+      {
+      /* trailing whitespace: seems like a good idea to ignore it too */
+      t2 = t + len - 1;
+      while (isspace(*t2)) t2--;
+      if (t2 != (t + len))
+        {
+        *++t2 = '\0';
+        len = t2 - t;
+        }
+      }
     DEBUG(D_expand)
-      debug_printf("considering bool: %s\n", len ? t : US"<empty>");
+      debug_printf("considering %s: %s\n", ourname, len ? t : US"<empty>");
+    /* logic for the lax case from expand_check_condition(), which also does
+    expands, and the logic is both short and stable enough that there should
+    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 */
+      if ((cond_type == ECOND_BOOL_LAX) && (len > 1))
+        boolvalue = TRUE;
+      }
     else if (strcmpic(t, US"true") == 0 || strcmpic(t, US"yes") == 0)
       boolvalue = TRUE;
     else if (strcmpic(t, US"false") == 0 || strcmpic(t, US"no") == 0)
       boolvalue = FALSE;
+    else if (cond_type == ECOND_BOOL_LAX)
+      boolvalue = TRUE;
     else
       {
       expand_string_message = string_sprintf("unrecognised boolean "
        "value \"%s\"", t);
       return NULL;
       }
-    if (yield != NULL) *yield = (boolvalue != 0);
+    if (yield != NULL) *yield = (boolvalue == testfor);
     return s;
     }
 
@@ -2569,6 +2989,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
@@ -2577,7 +2999,7 @@ Returns:         0 OK; lookup_value has been reset to save_lookup
 
 static int
 process_yesno(BOOL skipping, BOOL yes, uschar *save_lookup, uschar **sptr,
-  uschar **yieldptr, int *sizeptr, int *ptrptr, uschar *type)
+  uschar **yieldptr, int *sizeptr, int *ptrptr, uschar *type, BOOL *resetok)
 {
 int rc = 0;
 uschar *s = *sptr;    /* Local value */
@@ -2614,7 +3036,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);
+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;
@@ -2639,7 +3061,7 @@ already skipping. */
 while (isspace(*s)) s++;
 if (*s == '{')
   {
-  sub2 = expand_string_internal(s+1, TRUE, &s, yes || skipping);
+  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;
@@ -2914,14 +3336,14 @@ Returns:      on success: the value of the expression, with *error still NULL
               on failure: an undefined value, with *error = a message
 */
 
-static int eval_op_or(uschar **, BOOL, uschar **);
+static int_eximarith_t eval_op_or(uschar **, BOOL, uschar **);
 
 
-static int
+static int_eximarith_t
 eval_expr(uschar **sptr, BOOL decimal, uschar **error, BOOL endket)
 {
 uschar *s = *sptr;
-int x = eval_op_or(&s, decimal, error);
+int_eximarith_t x = eval_op_or(&s, decimal, error);
 if (*error == NULL)
   {
   if (endket)
@@ -2938,21 +3360,26 @@ return x;
 }
 
 
-static int
+static int_eximarith_t
 eval_number(uschar **sptr, BOOL decimal, uschar **error)
 {
 register int c;
-int n;
+int_eximarith_t n;
 uschar *s = *sptr;
 while (isspace(*s)) s++;
 c = *s;
 if (isdigit(c))
   {
   int count;
-  (void)sscanf(CS s, (decimal? "%d%n" : "%i%n"), &n, &count);
+  (void)sscanf(CS s, (decimal? SC_EXIM_DEC "%n" : SC_EXIM_ARITH "%n"), &n, &count);
   s += count;
-  if (tolower(*s) == 'k') { n *= 1024; s++; }
-    else if (tolower(*s) == 'm') { n *= 1024*1024; s++; }
+  switch (tolower(*s))
+    {
+    default: break;
+    case 'k': n *= 1024; s++; break;
+    case 'm': n *= 1024*1024; s++; break;
+    case 'g': n *= 1024*1024*1024; s++; break;
+    }
   while (isspace (*s)) s++;
   }
 else if (c == '(')
@@ -2970,10 +3397,11 @@ return n;
 }
 
 
-static int eval_op_unary(uschar **sptr, BOOL decimal, uschar **error)
+static int_eximarith_t
+eval_op_unary(uschar **sptr, BOOL decimal, uschar **error)
 {
 uschar *s = *sptr;
-int x;
+int_eximarith_t x;
 while (isspace(*s)) s++;
 if (*s == '+' || *s == '-' || *s == '~')
   {
@@ -2991,20 +3419,59 @@ return x;
 }
 
 
-static int eval_op_mult(uschar **sptr, BOOL decimal, uschar **error)
+static int_eximarith_t
+eval_op_mult(uschar **sptr, BOOL decimal, uschar **error)
 {
 uschar *s = *sptr;
-int x = eval_op_unary(&s, decimal, error);
+int_eximarith_t x = eval_op_unary(&s, decimal, error);
 if (*error == NULL)
   {
   while (*s == '*' || *s == '/' || *s == '%')
     {
     int op = *s++;
-    int y = eval_op_unary(&s, decimal, error);
+    int_eximarith_t y = eval_op_unary(&s, decimal, error);
     if (*error != NULL) break;
-    if (op == '*') x *= y;
-      else if (op == '/') x /= y;
-      else x %= y;
+    /* SIGFPE both on div/mod by zero and on INT_MIN / -1, which would give
+     * a value of INT_MAX+1. Note that INT_MIN * -1 gives INT_MIN for me, which
+     * is a bug somewhere in [gcc 4.2.1, FreeBSD, amd64].  In fact, -N*-M where
+     * -N*M is INT_MIN will yielf INT_MIN.
+     * Since we don't support floating point, this is somewhat simpler.
+     * Ideally, we'd return an error, but since we overflow for all other
+     * arithmetic, consistency suggests otherwise, but what's the correct value
+     * to use?  There is none.
+     * The C standard guarantees overflow for unsigned arithmetic but signed
+     * overflow invokes undefined behaviour; in practice, this is overflow
+     * except for converting INT_MIN to INT_MAX+1.  We also can't guarantee
+     * that long/longlong larger than int are available, or we could just work
+     * with larger types.  We should consider whether to guarantee 32bit eval
+     * and 64-bit working variables, with errors returned.  For now ...
+     * So, the only SIGFPEs occur with a non-shrinking div/mod, thus -1; we
+     * 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 == EXIM_ARITH_MIN && op != '*')
+      {
+      DEBUG(D_expand)
+        debug_printf("Integer exception dodging: " PR_EXIM_ARITH "%c-1 coerced to " PR_EXIM_ARITH "\n",
+            EXIM_ARITH_MIN, op, EXIM_ARITH_MAX);
+      x = EXIM_ARITH_MAX;
+      continue;
+      }
+    if (op == '*')
+      x *= y;
+    else
+      {
+      if (y == 0)
+        {
+        *error = (op == '/') ? US"divide by zero" : US"modulo by zero";
+        x = 0;
+        break;
+        }
+      if (op == '/')
+        x /= y;
+      else
+        x %= y;
+      }
     }
   }
 *sptr = s;
@@ -3012,16 +3479,17 @@ return x;
 }
 
 
-static int eval_op_sum(uschar **sptr, BOOL decimal, uschar **error)
+static int_eximarith_t
+eval_op_sum(uschar **sptr, BOOL decimal, uschar **error)
 {
 uschar *s = *sptr;
-int x = eval_op_mult(&s, decimal, error);
+int_eximarith_t x = eval_op_mult(&s, decimal, error);
 if (*error == NULL)
   {
   while (*s == '+' || *s == '-')
     {
     int op = *s++;
-    int y = eval_op_mult(&s, decimal, error);
+    int_eximarith_t y = eval_op_mult(&s, decimal, error);
     if (*error != NULL) break;
     if (op == '+') x += y; else x -= y;
     }
@@ -3031,15 +3499,16 @@ return x;
 }
 
 
-static int eval_op_shift(uschar **sptr, BOOL decimal, uschar **error)
+static int_eximarith_t
+eval_op_shift(uschar **sptr, BOOL decimal, uschar **error)
 {
 uschar *s = *sptr;
-int x = eval_op_sum(&s, decimal, error);
+int_eximarith_t x = eval_op_sum(&s, decimal, error);
 if (*error == NULL)
   {
   while ((*s == '<' || *s == '>') && s[1] == s[0])
     {
-    int y;
+    int_eximarith_t y;
     int op = *s++;
     s++;
     y = eval_op_sum(&s, decimal, error);
@@ -3052,15 +3521,16 @@ return x;
 }
 
 
-static int eval_op_and(uschar **sptr, BOOL decimal, uschar **error)
+static int_eximarith_t
+eval_op_and(uschar **sptr, BOOL decimal, uschar **error)
 {
 uschar *s = *sptr;
-int x = eval_op_shift(&s, decimal, error);
+int_eximarith_t x = eval_op_shift(&s, decimal, error);
 if (*error == NULL)
   {
   while (*s == '&')
     {
-    int y;
+    int_eximarith_t y;
     s++;
     y = eval_op_shift(&s, decimal, error);
     if (*error != NULL) break;
@@ -3072,15 +3542,16 @@ return x;
 }
 
 
-static int eval_op_xor(uschar **sptr, BOOL decimal, uschar **error)
+static int_eximarith_t
+eval_op_xor(uschar **sptr, BOOL decimal, uschar **error)
 {
 uschar *s = *sptr;
-int x = eval_op_and(&s, decimal, error);
+int_eximarith_t x = eval_op_and(&s, decimal, error);
 if (*error == NULL)
   {
   while (*s == '^')
     {
-    int y;
+    int_eximarith_t y;
     s++;
     y = eval_op_and(&s, decimal, error);
     if (*error != NULL) break;
@@ -3092,15 +3563,16 @@ return x;
 }
 
 
-static int eval_op_or(uschar **sptr, BOOL decimal, uschar **error)
+static int_eximarith_t
+eval_op_or(uschar **sptr, BOOL decimal, uschar **error)
 {
 uschar *s = *sptr;
-int x = eval_op_xor(&s, decimal, error);
+int_eximarith_t x = eval_op_xor(&s, decimal, error);
 if (*error == NULL)
   {
   while (*s == '|')
     {
-    int y;
+    int_eximarith_t y;
     s++;
     y = eval_op_xor(&s, decimal, error);
     if (*error != NULL) break;
@@ -3154,8 +3626,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. 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
@@ -3164,6 +3637,10 @@ Arguments:
                  expansion is placed here (typically used with ket_ends)
   skipping       TRUE for recursive calls when the value isn't actually going
                  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
@@ -3173,7 +3650,7 @@ Returns:         NULL if expansion fails:
 
 static uschar *
 expand_string_internal(uschar *string, BOOL ket_ends, uschar **left,
-  BOOL skipping)
+  BOOL skipping, BOOL honour_dollar, BOOL *resetok_p)
 {
 int ptr = 0;
 int size = Ustrlen(string)+ 64;
@@ -3224,12 +3701,14 @@ 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 != '$')
+  if (*s != '$' || !honour_dollar)
     {
     yield = string_cat(yield, &size, &ptr, s++, 1);
     continue;
@@ -3241,7 +3720,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))))
     {
@@ -3328,11 +3807,11 @@ while (*s != 0)
     continue;
     }
 
-  /* Otherwise, if there's no '{' after $ it's an error. */
+  /* Otherwise, if there's no '{' after $ it's an error. */            /*}*/
 
-  if (*s != '{')
+  if (*s != '{')                                                       /*}*/
     {
-    expand_string_message = US"$ not followed by letter, digit, or {";
+    expand_string_message = US"$ not followed by letter, digit, or {"; /*}*/
     goto EXPAND_FAILED;
     }
 
@@ -3342,9 +3821,9 @@ while (*s != 0)
   if (isdigit((*(++s))))
     {
     int n;
-    s = read_number(&n, s);
+    s = read_number(&n, s);            /*{*/
     if (*s++ != '}')
-      {
+      {                                        /*{*/
       expand_string_message = US"} expected after number";
       goto EXPAND_FAILED;
       }
@@ -3356,7 +3835,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;
     }
 
@@ -3369,6 +3848,46 @@ while (*s != 0)
 
   switch(item_type)
     {
+    /* Call an ACL from an expansion.  We feed data in via $acl_arg1 - $acl_arg9.
+    If the ACL returns accept or reject we return content set by "message ="
+    There is currently no limit on recursion; this would have us call
+    acl_check_internal() directly and get a current level from somewhere.
+    See also the acl expansion condition ECOND_ACL and the traditional
+    acl modifier ACLC_ACL.
+    Assume that the function has side-effects on the store that must be preserved.
+    */
+
+    case EITEM_ACL:
+      /* ${acl {name} {arg1}{arg2}...} */
+      {
+      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", &resetok))
+        {
+        case 1: goto EXPAND_FAILED_CURLY;
+        case 2:
+        case 3: goto EXPAND_FAILED;
+        }
+      if (skipping) continue;
+
+      resetok = FALSE;
+      switch(eval_acl(sub, sizeof(sub)/sizeof(*sub), &user_msg))
+       {
+       case OK:
+       case FAIL:
+         if (user_msg)
+            yield = string_cat(yield, &size, &ptr, user_msg, Ustrlen(user_msg));
+         continue;
+
+       case DEFER:
+          expand_string_forcedfail = TRUE;
+       default:
+          expand_string_message = string_sprintf("error from acl \"%s\"", sub[0]);
+         goto EXPAND_FAILED;
+       }
+      }
+
     /* Handle conditionals - preserve the values of the numerical expansion
     variables in case they get changed by a regular expression match in the
     condition. If not, they retain their external settings. At the end
@@ -3382,7 +3901,7 @@ while (*s != 0)
         save_expand_strings(save_expand_nstring, save_expand_nlength);
 
       while (isspace(*s)) s++;
-      next_s = eval_condition(s, skipping? NULL : &cond);
+      next_s = eval_condition(s, &resetok, skipping? NULL : &cond);
       if (next_s == NULL) goto EXPAND_FAILED;  /* message already set */
 
       DEBUG(D_expand)
@@ -3402,7 +3921,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 */
@@ -3443,10 +3963,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);
-        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++;
         }
@@ -3462,9 +3982,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++;
@@ -3511,7 +4031,7 @@ while (*s != 0)
       first. */
 
       if (*s != '{') goto EXPAND_FAILED_CURLY;
-      filename = expand_string_internal(s+1, TRUE, &s, skipping);
+      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++;
@@ -3571,8 +4091,8 @@ while (*s != 0)
         if (search_find_defer)
           {
           expand_string_message =
-            string_sprintf("lookup of \"%s\" gave DEFER: %s", key,
-              search_error_message);
+            string_sprintf("lookup of \"%s\" gave DEFER: %s",
+              string_printing2(key, FALSE), search_error_message);
           goto EXPAND_FAILED;
           }
         if (expand_setup > 0) expand_nmax = expand_setup;
@@ -3589,7 +4109,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 */
@@ -3612,7 +4133,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;
 
@@ -3628,7 +4149,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:
@@ -3700,7 +4221,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:
@@ -3774,7 +4295,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:
@@ -3806,7 +4327,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:
@@ -3860,7 +4381,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:
@@ -3884,7 +4405,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:
@@ -3908,7 +4429,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:
@@ -3954,7 +4475,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 */
@@ -3984,10 +4505,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, ':');
 
@@ -4024,76 +4542,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 */
@@ -4180,7 +4631,7 @@ while (*s != 0)
 
       if (*s == '{')
         {
-        if (expand_string_internal(s+1, TRUE, &s, 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++;
@@ -4195,7 +4646,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);
+      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;
@@ -4224,7 +4675,7 @@ while (*s != 0)
 
       while (isspace(*s)) s++;
       if (*s != '{') goto EXPAND_FAILED_CURLY;
-      arg = expand_string_internal(s+1, TRUE, &s, skipping);
+      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;
@@ -4261,13 +4712,24 @@ while (*s != 0)
 
         (void)close(fd_in);
 
+        /* Read the pipe to get the command's output into $value (which is kept
+        in lookup_value). Read during execution, so that if the output exceeds
+        the OS pipe buffer limit, we don't block forever. */
+
+        f = fdopen(fd_out, "rb");
+        sigalrm_seen = FALSE;
+        alarm(60);
+        lookup_value = cat_file(f, lookup_value, &lsize, &lptr, NULL);
+        alarm(0);
+        (void)fclose(f);
+
         /* Wait for the process to finish, applying the timeout, and inspect its
         return code for serious disasters. Simple non-zero returns are passed on.
         */
 
-        if ((runrc = child_close(pid, 60)) < 0)
+        if (sigalrm_seen == TRUE || (runrc = child_close(pid, 30)) < 0)
           {
-          if (runrc == -256)
+          if (sigalrm_seen == TRUE || runrc == -256)
             {
             expand_string_message = string_sprintf("command timed out");
             killpg(pid, SIGKILL);       /* Kill the whole process group */
@@ -4283,14 +4745,6 @@ while (*s != 0)
 
           goto EXPAND_FAILED;
           }
-
-        /* Read the pipe to get the command's output into $value (which is kept
-        in lookup_value). */
-
-        f = fdopen(fd_out, "rb");
-        lookup_value = NULL;
-        lookup_value = cat_file(f, lookup_value, &lsize, &lptr, NULL);
-        (void)fclose(f);
         }
 
       /* Process the yes/no strings; $value may be useful in both cases */
@@ -4303,7 +4757,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 */
@@ -4320,7 +4775,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:
@@ -4358,11 +4813,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:
@@ -4437,7 +4892,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:
@@ -4532,7 +4987,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:
@@ -4650,10 +5105,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);
-          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
@@ -4716,7 +5171,8 @@ while (*s != 0)
                &yield,                       /* output pointer */
                &size,                        /* output size */
                &ptr,                         /* output current point */
-               US"extract"))                 /* condition type */
+               US"extract",                  /* condition type */
+              &resetok))
         {
         case 1: goto EXPAND_FAILED;          /* when all is well, the */
         case 2: goto EXPAND_FAILED_CURLY;    /* returned value is 0 */
@@ -4730,6 +5186,98 @@ while (*s != 0)
       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 */
+        }
+
+      /* All done - restore numerical variables. */
+
+      restore_expand_strings(save_expand_nmax, save_expand_nstring,
+        save_expand_nlength);
+
+      continue;
+      }
 
     /* Handle list operations */
 
@@ -4747,7 +5295,7 @@ while (*s != 0)
       while (isspace(*s)) s++;
       if (*s++ != '{') goto EXPAND_FAILED_CURLY;
 
-      list = expand_string_internal(s, TRUE, &s, skipping);
+      list = expand_string_internal(s, TRUE, &s, skipping, TRUE, &resetok);
       if (list == NULL) goto EXPAND_FAILED;
       if (*s++ != '}') goto EXPAND_FAILED_CURLY;
 
@@ -4755,7 +5303,7 @@ while (*s != 0)
         {
         while (isspace(*s)) s++;
         if (*s++ != '{') goto EXPAND_FAILED_CURLY;
-        temp = expand_string_internal(s, TRUE, &s, skipping);
+        temp = expand_string_internal(s, TRUE, &s, skipping, TRUE, &resetok);
         if (temp == NULL) goto EXPAND_FAILED;
         lookup_value = temp;
         if (*s++ != '}') goto EXPAND_FAILED_CURLY;
@@ -4774,12 +5322,12 @@ while (*s != 0)
 
       if (item_type == EITEM_FILTER)
         {
-        temp = eval_condition(expr, NULL);
+        temp = eval_condition(expr, &resetok, NULL);
         if (temp != NULL) s = temp;
         }
       else
         {
-        temp = expand_string_internal(s, TRUE, &s, TRUE);
+        temp = expand_string_internal(s, TRUE, &s, TRUE, TRUE, &resetok);
         }
 
       if (temp == NULL)
@@ -4791,15 +5339,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;
@@ -4818,7 +5366,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;
@@ -4838,7 +5386,7 @@ while (*s != 0)
 
         else
           {
-          temp = expand_string_internal(expr, TRUE, NULL, skipping);
+          temp = expand_string_internal(expr, TRUE, NULL, skipping, TRUE, &resetok);
           if (temp == NULL)
             {
             iterate_item = save_iterate_item;
@@ -4909,7 +5457,7 @@ while (*s != 0)
       }
 
 
-    /* If ${dlfunc support is configured, handle calling dynamically-loaded
+    /* If ${dlfunc support is configured, handle calling dynamically-loaded
     functions, unless locked out at this time. Syntax is ${dlfunc{file}{func}}
     or ${dlfunc{file}{func}{arg}} or ${dlfunc{file}{func}{arg1}{arg2}} or up to
     a maximum of EXPAND_DLFUNC_MAX_ARGS arguments (defined below). */
@@ -4918,7 +5466,7 @@ while (*s != 0)
 
     case EITEM_DLFUNC:
     #ifndef EXPAND_DLFUNC
-    expand_string_message = US"\"${dlfunc\" encountered, but this facility "
+    expand_string_message = US"\"${dlfunc\" encountered, but this facility "   /*}*/
       "is not included in this binary";
     goto EXPAND_FAILED;
 
@@ -4938,7 +5486,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:
@@ -5009,7 +5557,7 @@ while (*s != 0)
         }
       }
     #endif /* EXPAND_DLFUNC */
-    }
+    }  /* EITEM_* switch */
 
   /* Control reaches here if the name is not recognized as one of the more
   complicated expansion items. Check for the "operator" syntax (name terminated
@@ -5020,7 +5568,7 @@ while (*s != 0)
     {
     int c;
     uschar *arg = NULL;
-    uschar *sub = expand_string_internal(s+1, TRUE, &s, skipping);
+    uschar *sub = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok);
     if (sub == NULL) goto EXPAND_FAILED;
     s++;
 
@@ -5095,7 +5643,7 @@ while (*s != 0)
 
       case EOP_EXPAND:
         {
-        uschar *expanded = expand_string_internal(sub, FALSE, NULL, skipping);
+        uschar *expanded = expand_string_internal(sub, FALSE, NULL, skipping, TRUE, &resetok);
         if (expanded == NULL)
           {
           expand_string_message =
@@ -5199,6 +5747,122 @@ 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:
+        {
+       int cnt = 0;
+       int sep = 0;
+       uschar * cp;
+       uschar buffer[256];
+
+       while (string_nextinlist(&sub, &sep, buffer, sizeof(buffer)) != NULL) cnt++;
+       cp = string_sprintf("%d", cnt);
+        yield = string_cat(yield, &size, &ptr, cp, Ustrlen(cp));
+        continue;
+        }
+
+      /* expand a named list given the name */
+      /* handles nested named lists; requotes as colon-sep list */
+
+      case EOP_LISTNAMED:
+       {
+       tree_node *t = NULL;
+       uschar * list;
+       int sep = 0;
+       uschar * item;
+       uschar * suffix = US"";
+       BOOL needsep = FALSE;
+       uschar buffer[256];
+
+       if (*sub == '+') sub++;
+       if (arg == NULL)        /* no-argument version */
+         {
+         if (!(t = tree_search(addresslist_anchor, sub)) &&
+             !(t = tree_search(domainlist_anchor,  sub)) &&
+             !(t = tree_search(hostlist_anchor,    sub)))
+           t = tree_search(localpartlist_anchor, sub);
+         }
+       else switch(*arg)       /* specific list-type version */
+         {
+         case 'a': t = tree_search(addresslist_anchor,   sub); suffix = US"_a"; break;
+         case 'd': t = tree_search(domainlist_anchor,    sub); suffix = US"_d"; break;
+         case 'h': t = tree_search(hostlist_anchor,      sub); suffix = US"_h"; break;
+         case 'l': t = tree_search(localpartlist_anchor, sub); suffix = US"_l"; break;
+         default:
+            expand_string_message = string_sprintf("bad suffix on \"list\" operator");
+           goto EXPAND_FAILED;
+         }
+
+       if(!t)
+         {
+          expand_string_message = string_sprintf("\"%s\" is not a %snamed list",
+            sub, !arg?""
+             : *arg=='a'?"address "
+             : *arg=='d'?"domain "
+             : *arg=='h'?"host "
+             : *arg=='l'?"localpart "
+             : 0);
+         goto EXPAND_FAILED;
+         }
+
+       list = ((namedlist_block *)(t->data.ptr))->string;
+
+       while ((item = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
+         {
+         uschar * buf = US" : ";
+         if (needsep)
+           yield = string_cat(yield, &size, &ptr, buf, 3);
+         else
+           needsep = TRUE;
+
+         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, &resetok);
+           }
+         else if (sep != ':')  /* item from non-colon-sep list, re-quote for colon list-separator */
+           {
+           char * cp;
+           char tok[3];
+           tok[0] = sep; tok[1] = ':'; tok[2] = 0;
+           while ((cp= strpbrk((const char *)item, tok)))
+             {
+              yield = string_cat(yield, &size, &ptr, item, cp-(char *)item);
+             if (*cp++ == ':') /* colon in a non-colon-sep list item, needs doubling */
+               {
+                yield = string_cat(yield, &size, &ptr, US"::", 2);
+               item = (uschar *)cp;
+               }
+             else              /* sep in item; should already be doubled; emit once */
+               {
+                yield = string_cat(yield, &size, &ptr, (uschar *)tok, 1);
+               if (*cp == sep) cp++;
+               item = (uschar *)cp;
+               }
+             }
+           }
+          yield = string_cat(yield, &size, &ptr, item, Ustrlen(item));
+         }
+        continue;
+       }
+
       /* mask applies a mask to an IP address; for example the result of
       ${mask:131.111.10.206/28} is 131.111.10.192/28. */
 
@@ -5402,8 +6066,8 @@ while (*s != 0)
           goto EXPAND_FAILED;
           }
 
-        if (lookup_list[n].quote != NULL)
-          sub = (lookup_list[n].quote)(sub, opt);
+        if (lookup_list[n]->quote != NULL)
+          sub = (lookup_list[n]->quote)(sub, opt);
         else if (opt != NULL) sub = NULL;
 
         if (sub == NULL)
@@ -5479,6 +6143,94 @@ while (*s != 0)
         continue;
         }
 
+         /* replace illegal UTF-8 sequences by replacement character  */
+         
+      #define UTF8_REPLACEMENT_CHAR US"?"
+
+      case EOP_UTF8CLEAN:
+        {
+        int seq_len, index = 0;
+        int bytes_left  = 0;
+        uschar seq_buff[4];                    /* accumulate utf-8 here */
+        
+        while (*sub != 0)
+         {
+         int complete;
+         long codepoint;
+         uschar c;
+
+         complete = 0;
+         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 */
+               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;
+        }
+
       /* escape turns all non-printing characters into escape sequences. */
 
       case EOP_ESCAPE:
@@ -5495,7 +6247,7 @@ while (*s != 0)
         {
         uschar *save_sub = sub;
         uschar *error = NULL;
-        int n = eval_expr(&sub, (c == EOP_EVAL10), &error, FALSE);
+        int_eximarith_t n = eval_expr(&sub, (c == EOP_EVAL10), &error, FALSE);
         if (error != NULL)
           {
           expand_string_message = string_sprintf("error in expression "
@@ -5503,7 +6255,7 @@ while (*s != 0)
               save_sub);
           goto EXPAND_FAILED;
           }
-        sprintf(CS var_buffer, "%d", n);
+        sprintf(CS var_buffer, PR_EXIM_ARITH, n);
         yield = string_cat(yield, &size, &ptr, var_buffer, Ustrlen(var_buffer));
         continue;
         }
@@ -5704,6 +6456,40 @@ while (*s != 0)
         continue;
         }
 
+      /* vaguely random number less than N */
+
+      case EOP_RANDINT:
+        {
+        int_eximarith_t max;
+        uschar *s;
+
+        max = expand_string_integer(sub, TRUE);
+        if (expand_string_message != NULL)
+          goto EXPAND_FAILED;
+        s = string_sprintf("%d", vaguely_random_number((int)max));
+        yield = string_cat(yield, &size, &ptr, s, Ustrlen(s));
+        continue;
+        }
+
+      /* Reverse IP, including IPv6 to dotted-nibble */
+
+      case EOP_REVERSE_IP:
+        {
+        int family, maskptr;
+        uschar reversed[128];
+
+        family = string_is_ip_address(sub, &maskptr);
+        if (family == 0)
+          {
+          expand_string_message = string_sprintf(
+              "reverse_ip() not given an IP address [%s]", sub);
+          goto EXPAND_FAILED;
+          }
+        invert_address(reversed, sub);
+        yield = string_cat(yield, &size, &ptr, reversed, Ustrlen(reversed));
+        continue;
+        }
+
       /* Unknown operator */
 
       default:
@@ -5719,7 +6505,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;
@@ -5782,6 +6568,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,
@@ -5811,6 +6599,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;
 }
 
@@ -5829,7 +6618,7 @@ expand_string(uschar *string)
 search_find_defer = FALSE;
 malformed_header = FALSE;
 return (Ustrpbrk(string, "$\\") == NULL)? string :
-  expand_string_internal(string, FALSE, NULL, FALSE);
+  expand_string_internal(string, FALSE, NULL, FALSE, TRUE, NULL);
 }
 
 
@@ -5871,10 +6660,10 @@ Returns:  the integer value, or
           expand_string_message is set NULL for an OK integer
 */
 
-int
+int_eximarith_t
 expand_string_integer(uschar *string, BOOL isplus)
 {
-long int value;
+int_eximarith_t value;
 uschar *s = expand_string(string);
 uschar *msg = US"invalid integer \"%s\"";
 uschar *endptr;
@@ -5906,7 +6695,7 @@ if (isspace(*s))
     }
   }
 
-value = strtol(CS s, CSS &endptr, 10);
+value = strtoll(CS s, CSS &endptr, 10);
 
 if (endptr == s)
   {
@@ -5918,31 +6707,32 @@ else if (value < 0 && isplus)
   }
 else
   {
-  /* Ensure we can cast this down to an int */
-  if (value > INT_MAX  || value < INT_MIN) errno = ERANGE;
-
-  if (errno != ERANGE)
+  switch (tolower(*endptr))
     {
-    if (tolower(*endptr) == 'k')
-      {
-      if (value > INT_MAX/1024 || value < INT_MIN/1024) errno = ERANGE;
-        else value *= 1024;
+    default:
+      break;
+    case 'k':
+      if (value > EXIM_ARITH_MAX/1024 || value < EXIM_ARITH_MIN/1024) errno = ERANGE;
+      else value *= 1024;
       endptr++;
-      }
-    else if (tolower(*endptr) == 'm')
-      {
-      if (value > INT_MAX/(1024*1024) || value < INT_MIN/(1024*1024))
-        errno = ERANGE;
+      break;
+    case 'm':
+      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 > EXIM_ARITH_MAX/(1024*1024*1024) || value < EXIM_ARITH_MIN/(1024*1024*1024)) errno = ERANGE;
+      else value *= 1024*1024*1024;
+      endptr++;
+      break;
     }
   if (errno == ERANGE)
     msg = US"absolute value of integer \"%s\" is too large (overflow)";
   else
     {
     while (isspace(*endptr)) endptr++;
-    if (*endptr == 0) return (int)value;
+    if (*endptr == 0) return value;
     }
   }
 
@@ -6015,6 +6805,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];
@@ -6066,4 +6859,7 @@ return 0;
 
 #endif
 
+/*
+ vi: aw ai sw=2
+*/
 /* End of expand.c */