TLS SNI support for OpenSSL ($tls_sni)
[exim.git] / src / src / expand.c
index cd84294ae59ad66799451a153f718c532c48bf4c..22f7d9a66ff0c366d7cafc085caf85c3906913df 100644 (file)
@@ -1,10 +1,8 @@
-/* $Cambridge: exim/src/src/expand.c,v 1.96 2008/08/07 11:05:03 fanf2 Exp $ */
-
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2007 */
+/* Copyright (c) University of Cambridge 1995 - 2009 */
 /* 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);
 
 #ifdef STAND_ALONE
 #ifndef SUPPORT_CRYPTEQ
@@ -156,6 +154,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 +162,7 @@ enum {
   EOP_FROM_UTF8,
   EOP_LOCAL_PART,
   EOP_QUOTE_LOCAL_PART,
+  EOP_REVERSE_IP,
   EOP_TIME_EVAL,
   EOP_TIME_INTERVAL };
 
@@ -187,6 +187,7 @@ static uschar *op_table_main[] = {
   US"nh",
   US"nhash",
   US"quote",
+  US"randint",
   US"rfc2047",
   US"rfc2047d",
   US"rxquote",
@@ -219,6 +220,7 @@ enum {
   EOP_NH,
   EOP_NHASH,
   EOP_QUOTE,
+  EOP_RANDINT,
   EOP_RFC2047,
   EOP_RFC2047D,
   EOP_RXQUOTE,
@@ -242,6 +244,8 @@ static uschar *cond_table[] = {
   US">",
   US">=",
   US"and",
+  US"bool",
+  US"bool_lax",
   US"crypteq",
   US"def",
   US"eq",
@@ -254,6 +258,8 @@ static uschar *cond_table[] = {
   US"gei",
   US"gt",
   US"gti",
+  US"inlist",
+  US"inlisti",
   US"isip",
   US"isip4",
   US"isip6",
@@ -283,6 +289,8 @@ enum {
   ECOND_NUM_G,
   ECOND_NUM_GE,
   ECOND_AND,
+  ECOND_BOOL,
+  ECOND_BOOL_LAX,
   ECOND_CRYPTEQ,
   ECOND_DEF,
   ECOND_STR_EQ,
@@ -295,6 +303,8 @@ enum {
   ECOND_STR_GEI,
   ECOND_STR_GT,
   ECOND_STR_GTI,
+  ECOND_INLIST,
+  ECOND_INLISTI,
   ECOND_ISIP,
   ECOND_ISIP4,
   ECOND_ISIP6,
@@ -320,9 +330,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
@@ -364,9 +374,9 @@ enum {
   vtype_load_avg,       /* value not used; result is int from os_getloadavg */
   vtype_pspace,         /* partition space; value is T/F for spool/log */
   vtype_pinodes         /* partition inodes; value is T/F for spool/log */
-#ifdef EXPERIMENTAL_DOMAINKEYS
,vtype_dk_verify       /* Serve request out of DomainKeys verification structure */
-#endif
+  #ifndef DISABLE_DKIM
 ,vtype_dkim           /* Lookup of value in DKIM signature */
+  #endif
   };
 
 /* This table must be kept in alphabetical order. */
@@ -381,6 +391,9 @@ static var_entry var_table[] = {
   { "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 },
@@ -404,22 +417,27 @@ static var_entry var_table[] = {
   { "demime_errorlevel",   vtype_int,         &demime_errorlevel },
   { "demime_reason",       vtype_stringptr,   &demime_reason },
 #endif
-#ifdef EXPERIMENTAL_DOMAINKEYS
-  { "dk_domain",           vtype_stringptr,   &dk_signing_domain },
-  { "dk_is_signed",        vtype_dk_verify,   NULL },
-  { "dk_result",           vtype_dk_verify,   NULL },
-  { "dk_selector",         vtype_stringptr,   &dk_signing_selector },
-  { "dk_sender",           vtype_dk_verify,   NULL },
-  { "dk_sender_domain",    vtype_dk_verify,   NULL },
-  { "dk_sender_local_part",vtype_dk_verify,   NULL },
-  { "dk_sender_source",    vtype_dk_verify,   NULL },
-  { "dk_signsall",         vtype_dk_verify,   NULL },
-  { "dk_status",           vtype_dk_verify,   NULL },
-  { "dk_testing",          vtype_dk_verify,   NULL },
-#endif
-#ifdef EXPERIMENTAL_DKIM
+#ifndef DISABLE_DKIM
+  { "dkim_algo",           vtype_dkim,        (void *)DKIM_ALGO },
+  { "dkim_bodylength",     vtype_dkim,        (void *)DKIM_BODYLENGTH },
+  { "dkim_canon_body",     vtype_dkim,        (void *)DKIM_CANON_BODY },
+  { "dkim_canon_headers",  vtype_dkim,        (void *)DKIM_CANON_HEADERS },
+  { "dkim_copiedheaders",  vtype_dkim,        (void *)DKIM_COPIEDHEADERS },
+  { "dkim_created",        vtype_dkim,        (void *)DKIM_CREATED },
+  { "dkim_cur_signer",     vtype_stringptr,   &dkim_cur_signer },
   { "dkim_domain",         vtype_stringptr,   &dkim_signing_domain },
+  { "dkim_expires",        vtype_dkim,        (void *)DKIM_EXPIRES },
+  { "dkim_headernames",    vtype_dkim,        (void *)DKIM_HEADERNAMES },
+  { "dkim_identity",       vtype_dkim,        (void *)DKIM_IDENTITY },
+  { "dkim_key_granularity",vtype_dkim,        (void *)DKIM_KEY_GRANULARITY },
+  { "dkim_key_nosubdomains",vtype_dkim,       (void *)DKIM_NOSUBDOMAINS },
+  { "dkim_key_notes",      vtype_dkim,        (void *)DKIM_KEY_NOTES },
+  { "dkim_key_srvtype",    vtype_dkim,        (void *)DKIM_KEY_SRVTYPE },
+  { "dkim_key_testing",    vtype_dkim,        (void *)DKIM_KEY_TESTING },
   { "dkim_selector",       vtype_stringptr,   &dkim_signing_selector },
+  { "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
   { "dnslist_domain",      vtype_stringptr,   &dnslist_domain },
   { "dnslist_matched",     vtype_stringptr,   &dnslist_matched },
@@ -593,9 +611,13 @@ static var_entry var_table[] = {
   { "srs_status",          vtype_stringptr,   &srs_status },
 #endif
   { "thisaddress",         vtype_stringptr,   &filter_thisaddress },
+  { "tls_bits",            vtype_int,         &tls_bits },
   { "tls_certificate_verified", vtype_int,    &tls_certificate_verified },
   { "tls_cipher",          vtype_stringptr,   &tls_cipher },
   { "tls_peerdn",          vtype_stringptr,   &tls_peerdn },
+#if defined(SUPPORT_TLS) && !defined(USE_GNUTLS)
+  { "tls_sni",             vtype_stringptr,   &tls_sni },
+#endif
   { "tod_bsdinbox",        vtype_todbsdin,    NULL },
   { "tod_epoch",           vtype_tode,        NULL },
   { "tod_full",            vtype_todf,        NULL },
@@ -619,9 +641,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 };
 
@@ -723,6 +745,8 @@ 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.
 
+The actual false-value tests should be replicated for ECOND_BOOL_LAX.
+
 Arguments:
   condition     the condition string
   m1            text to be incorporated in panic error
@@ -752,6 +776,75 @@ 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 and OpenSSL is used, then this will not be used.
+The GNUTLS randomness functions found do not seem amenable to extracting
+random numbers outside of a TLS context.  Any volunteers?
+
+Arguments:
+  max       range maximum
+Returns     a random number in range [0, max-1]
+*/
+
+#if !defined(SUPPORT_TLS) || defined(USE_GNUTLS)
+int
+pseudo_random_number(int max)
+{
+  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
+}
+
+#endif
+
 /*************************************************
 *             Pick out a name from a string      *
 *************************************************/
@@ -1382,51 +1475,6 @@ while (last > first)
 
   switch (var_table[middle].type)
     {
-#ifdef EXPERIMENTAL_DOMAINKEYS
-
-    case vtype_dk_verify:
-    if (dk_verify_block == NULL) return US"";
-    s = NULL;
-    if (Ustrcmp(var_table[middle].name, "dk_result") == 0)
-      s = dk_verify_block->result_string;
-    if (Ustrcmp(var_table[middle].name, "dk_sender") == 0)
-      s = dk_verify_block->address;
-    if (Ustrcmp(var_table[middle].name, "dk_sender_domain") == 0)
-      s = dk_verify_block->domain;
-    if (Ustrcmp(var_table[middle].name, "dk_sender_local_part") == 0)
-      s = dk_verify_block->local_part;
-
-    if (Ustrcmp(var_table[middle].name, "dk_sender_source") == 0)
-      switch(dk_verify_block->address_source) {
-        case DK_EXIM_ADDRESS_NONE: s = US"0"; break;
-        case DK_EXIM_ADDRESS_FROM_FROM: s = US"from"; break;
-        case DK_EXIM_ADDRESS_FROM_SENDER: s = US"sender"; break;
-      }
-
-    if (Ustrcmp(var_table[middle].name, "dk_status") == 0)
-      switch(dk_verify_block->result) {
-        case DK_EXIM_RESULT_ERR: s = US"error"; break;
-        case DK_EXIM_RESULT_BAD_FORMAT: s = US"bad format"; break;
-        case DK_EXIM_RESULT_NO_KEY: s = US"no key"; break;
-        case DK_EXIM_RESULT_NO_SIGNATURE: s = US"no signature"; break;
-        case DK_EXIM_RESULT_REVOKED: s = US"revoked"; break;
-        case DK_EXIM_RESULT_NON_PARTICIPANT: s = US"non-participant"; break;
-        case DK_EXIM_RESULT_GOOD: s = US"good"; break;
-        case DK_EXIM_RESULT_BAD: s = US"bad"; break;
-      }
-
-    if (Ustrcmp(var_table[middle].name, "dk_signsall") == 0)
-      s = (dk_verify_block->signsall)? US"1" : US"0";
-
-    if (Ustrcmp(var_table[middle].name, "dk_testing") == 0)
-      s = (dk_verify_block->testing)? US"1" : US"0";
-
-    if (Ustrcmp(var_table[middle].name, "dk_is_signed") == 0)
-      s = (dk_verify_block->is_signed)? US"1" : US"0";
-
-    return (s == NULL)? US"" : s;
-#endif
-
     case vtype_filter_int:
     if (!filter_running) return NULL;
     /* Fall through */
@@ -1548,7 +1596,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,
@@ -1605,6 +1653,12 @@ while (last > first)
       sprintf(CS var_buffer, "%d", inodes);
       }
     return var_buffer;
+
+    #ifndef DISABLE_DKIM
+    case vtype_dkim:
+    return dkim_exim_expand_query((int)(long)var_table[middle].value);
+    #endif
+
     }
   }
 
@@ -1653,7 +1707,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);
   if (sub[i] == NULL) return 3;
   if (*s++ != '}') return 1;
   while (isspace(*s)) s++;
@@ -1725,6 +1779,7 @@ eval_condition(uschar *s, 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];
 struct stat statbuf;
@@ -1857,7 +1912,7 @@ switch(cond_type)
   while (isspace(*s)) s++;
   if (*s != '{') goto COND_FAILED_CURLY_START;
 
-  sub[0] = expand_string_internal(s+1, TRUE, &s, yield == NULL);
+  sub[0] = expand_string_internal(s+1, TRUE, &s, yield == NULL, TRUE);
   if (sub[0] == NULL) return NULL;
   if (*s++ != '}') goto COND_FAILED_CURLY_END;
 
@@ -1970,22 +2025,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:
@@ -2007,6 +2070,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 != '{')
       {
@@ -2015,7 +2085,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);
     if (sub[i] == NULL) return NULL;
     if (*s++ != '}') goto COND_FAILED_CURLY_END;
 
@@ -2274,6 +2345,7 @@ switch(cond_type)
       }
 
     else   /* {crypt} or {crypt16} and non-{ at start */
+           /* }-for-text-editors */
       {
       int which = 0;
       uschar *coded;
@@ -2320,6 +2392,30 @@ switch(cond_type)
       }
     break;
     #endif  /* SUPPORT_CRYPTEQ */
+
+    case ECOND_INLIST:
+    case ECOND_INLISTI:
+      {
+      int sep = 0;
+      BOOL found = FALSE;
+      uschar *save_iterate_item = iterate_item;
+      int (*compare)(const uschar *, const uschar *);
+
+      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)
+          {
+          found = TRUE;
+          break;
+          }
+      iterate_item = save_iterate_item;
+      *yield = found;
+      }
+
     }   /* Switch for comparison conditions */
 
   return s;    /* End of comparison conditions */
@@ -2391,7 +2487,7 @@ switch(cond_type)
 
     while (isspace(*s)) s++;
     if (*s++ != '{') goto COND_FAILED_CURLY_START;
-    sub[0] = expand_string_internal(s, TRUE, &s, (yield == NULL));
+    sub[0] = expand_string_internal(s, TRUE, &s, (yield == NULL), TRUE);
     if (sub[0] == NULL) return NULL;
     if (*s++ != '}') goto COND_FAILED_CURLY_END;
 
@@ -2443,6 +2539,80 @@ switch(cond_type)
     }
 
 
+  /* The bool{} expansion condition maps a string to boolean.
+  The values supported should match those supported by the ACL condition
+  (acl.c, ACLC_CONDITION) so that we keep to a minimum the different ideas
+  of true/false.  Note that Router "condition" rules have a different
+  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.
+  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, *t2;
+    uschar *ourname;
+    size_t len;
+    BOOL boolvalue = FALSE;
+    while (isspace(*s)) s++;
+    if (*s != '{') goto COND_FAILED_CURLY_START;
+    ourname = cond_type == ECOND_BOOL_LAX ? US"bool_lax" : US"bool";
+    switch(read_subs(sub_arg, 1, 1, &s, yield == NULL, FALSE, ourname))
+      {
+      case 1: expand_string_message = string_sprintf(
+                  "too few arguments or bracketing error for %s",
+                  ourname);
+      /*FALLTHROUGH*/
+      case 2:
+      case 3: return NULL;
+      }
+    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 %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)
+      {
+      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 == testfor);
+    return s;
+    }
+
   /* Unknown condition */
 
   default:
@@ -2599,7 +2769,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);
 if (sub1 == NULL && (yes || !expand_string_forcedfail)) goto FAILED;
 expand_string_forcedfail = FALSE;
 if (*s++ != '}') goto FAILED_CURLY;
@@ -2624,7 +2794,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);
   if (sub2 == NULL && (!yes || !expand_string_forcedfail)) goto FAILED;
   expand_string_forcedfail = FALSE;
   if (*s++ != '}') goto FAILED_CURLY;
@@ -2987,9 +3157,47 @@ if (*error == NULL)
     int op = *s++;
     int 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 == INT_MIN && op != '*')
+      {
+      DEBUG(D_expand)
+        debug_printf("Integer exception dodging: %d%c-1 coerced to %d\n",
+            INT_MIN, op, INT_MAX);
+      x = INT_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;
@@ -3149,6 +3357,8 @@ 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
 
 Returns:         NULL if expansion fails:
                    expand_string_forcedfail is set TRUE if failure was forced
@@ -3158,7 +3368,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)
 {
 int ptr = 0;
 int size = Ustrlen(string)+ 64;
@@ -3214,7 +3424,7 @@ while (*s != 0)
 
   if (ket_ends && *s == '}') break;
 
-  if (*s != '$')
+  if (*s != '$' || !honour_dollar)
     {
     yield = string_cat(yield, &size, &ptr, s++, 1);
     continue;
@@ -3430,7 +3640,7 @@ while (*s != 0)
       while (isspace(*s)) s++;
       if (*s == '{')
         {
-        key = expand_string_internal(s+1, TRUE, &s, skipping);
+        key = expand_string_internal(s+1, TRUE, &s, skipping, TRUE);
         if (key == NULL) goto EXPAND_FAILED;
         if (*s++ != '}') goto EXPAND_FAILED_CURLY;
         while (isspace(*s)) s++;
@@ -3496,7 +3706,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);
       if (filename == NULL) goto EXPAND_FAILED;
       if (*s++ != '}') goto EXPAND_FAILED_CURLY;
       while (isspace(*s)) s++;
@@ -3556,8 +3766,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;
@@ -4165,7 +4375,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) == NULL)
           goto EXPAND_FAILED;
         if (*s++ != '}') goto EXPAND_FAILED_CURLY;
         while (isspace(*s)) s++;
@@ -4180,7 +4390,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);
       if (arg == NULL) goto EXPAND_FAILED;
       yield = string_cat(yield, &size, &ptr, arg, Ustrlen(arg));
       if (*s++ != '}') goto EXPAND_FAILED_CURLY;
@@ -4209,7 +4419,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);
       if (arg == NULL) goto EXPAND_FAILED;
       while (isspace(*s)) s++;
       if (*s++ != '}') goto EXPAND_FAILED_CURLY;
@@ -4246,13 +4456,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 */
@@ -4268,14 +4489,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 */
@@ -4637,7 +4850,7 @@ while (*s != 0)
         while (isspace(*s)) s++;
         if (*s == '{')
           {
-          sub[i] = expand_string_internal(s+1, TRUE, &s, skipping);
+          sub[i] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE);
           if (sub[i] == NULL) goto EXPAND_FAILED;
           if (*s++ != '}') goto EXPAND_FAILED_CURLY;
 
@@ -4732,7 +4945,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);
       if (list == NULL) goto EXPAND_FAILED;
       if (*s++ != '}') goto EXPAND_FAILED_CURLY;
 
@@ -4740,7 +4953,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);
         if (temp == NULL) goto EXPAND_FAILED;
         lookup_value = temp;
         if (*s++ != '}') goto EXPAND_FAILED_CURLY;
@@ -4764,7 +4977,7 @@ while (*s != 0)
         }
       else
         {
-        temp = expand_string_internal(s, TRUE, &s, TRUE);
+        temp = expand_string_internal(s, TRUE, &s, TRUE, TRUE);
         }
 
       if (temp == NULL)
@@ -4823,7 +5036,7 @@ while (*s != 0)
 
         else
           {
-          temp = expand_string_internal(expr, TRUE, NULL, skipping);
+          temp = expand_string_internal(expr, TRUE, NULL, skipping, TRUE);
           if (temp == NULL)
             {
             iterate_item = save_iterate_item;
@@ -5005,7 +5218,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);
     if (sub == NULL) goto EXPAND_FAILED;
     s++;
 
@@ -5080,7 +5293,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);
         if (expanded == NULL)
           {
           expand_string_message =
@@ -5387,8 +5600,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)
@@ -5689,6 +5902,40 @@ while (*s != 0)
         continue;
         }
 
+      /* pseudo-random number less than N */
+
+      case EOP_RANDINT:
+        {
+        int max;
+        uschar *s;
+
+        max = expand_string_integer(sub, TRUE);
+        if (expand_string_message != NULL)
+          goto EXPAND_FAILED;
+        s = string_sprintf("%d", pseudo_random_number(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:
@@ -5814,7 +6061,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);
 }
 
 
@@ -5874,6 +6121,23 @@ systems, so we set it zero ourselves. */
 
 errno = 0;
 expand_string_message = NULL;               /* Indicates no error */
+
+/* Before Exim 4.64, strings consisting entirely of whitespace compared
+equal to 0.  Unfortunately, people actually relied upon that, so preserve
+the behaviour explicitly.  Stripping leading whitespace is a harmless
+noop change since strtol skips it anyway (provided that there is a number
+to find at all). */
+if (isspace(*s))
+  {
+  while (isspace(*s)) ++s;
+  if (*s == '\0')
+    {
+      DEBUG(D_expand)
+       debug_printf("treating blank string as number 0\n");
+      return 0;
+    }
+  }
+
 value = strtol(CS s, CSS &endptr, 10);
 
 if (endptr == s)