Openssl: add peer IP to cert verify log lines
[exim.git] / src / src / expand.c
index 01c6e05f41105ce2f8880af2fde8ee758d2ae41e..1f0c5cb3cfd14799cccb8822d901a1a73a2fa0c0 100644 (file)
@@ -13,7 +13,8 @@
 
 /* Recursively called function */
 
-static uschar *expand_string_internal(uschar *, BOOL, uschar **, BOOL, BOOL, BOOL *);
+static uschar *expand_string_internal(const uschar *, BOOL, const uschar **, BOOL, BOOL, BOOL *);
+static int_eximarith_t expanded_string_integer(const uschar *, BOOL);
 
 #ifdef STAND_ALONE
 #ifndef SUPPORT_CRYPTEQ
@@ -126,6 +127,7 @@ static uschar *item_table[] = {
   US"reduce",
   US"run",
   US"sg",
+  US"sort",
   US"substr",
   US"tr" };
 
@@ -151,6 +153,7 @@ enum {
   EITEM_REDUCE,
   EITEM_RUN,
   EITEM_SG,
+  EITEM_SORT,
   EITEM_SUBSTR,
   EITEM_TR };
 
@@ -205,6 +208,7 @@ static uschar *op_table_main[] = {
   US"rxquote",
   US"s",
   US"sha1",
+  US"sha256",
   US"stat",
   US"str2b64",
   US"strlen",
@@ -242,6 +246,7 @@ enum {
   EOP_RXQUOTE,
   EOP_S,
   EOP_SHA1,
+  EOP_SHA256,
   EOP_STAT,
   EOP_STR2B64,
   EOP_STRLEN,
@@ -346,25 +351,9 @@ enum {
 };
 
 
-/* Type for main variable table */
-
-typedef struct {
-  const char *name;
-  int         type;
-  void       *value;
-} var_entry;
-
-/* Type for entries pointing to address/length pairs. Not currently
-in use. */
-
-typedef struct {
-  uschar **address;
-  int  *length;
-} alblock;
-
 /* Types of table entry */
 
-enum {
+enum vtypes {
   vtype_int,            /* value is address of int */
   vtype_filter_int,     /* ditto, but recognized only when filtering */
   vtype_ino,            /* value is address of ino_t (not always an int) */
@@ -397,7 +386,23 @@ enum {
   #ifndef DISABLE_DKIM
   ,vtype_dkim           /* Lookup of value in DKIM signature */
   #endif
-  };
+};
+
+/* Type for main variable table */
+
+typedef struct {
+  const char *name;
+  enum vtypes type;
+  void       *value;
+} var_entry;
+
+/* Type for entries pointing to address/length pairs. Not currently
+in use. */
+
+typedef struct {
+  uschar **address;
+  int  *length;
+} alblock;
 
 static uschar * fn_recipients(void);
 
@@ -441,6 +446,8 @@ static var_entry var_table[] = {
   { "caller_uid",          vtype_uid,         &real_uid },
   { "compile_date",        vtype_stringptr,   &version_date },
   { "compile_number",      vtype_stringptr,   &version_cnumber },
+  { "config_dir",          vtype_stringptr,   &config_main_directory },
+  { "config_file",         vtype_stringptr,   &config_main_filename },
   { "csa_status",          vtype_stringptr,   &csa_status },
 #ifdef EXPERIMENTAL_DCC
   { "dcc_header",          vtype_stringptr,   &dcc_header },
@@ -485,9 +492,18 @@ static var_entry var_table[] = {
   { "dnslist_value",       vtype_stringptr,   &dnslist_value },
   { "domain",              vtype_stringptr,   &deliver_domain },
   { "domain_data",         vtype_stringptr,   &deliver_domain_data },
+#ifdef EXPERIMENTAL_EVENT
+  { "event_data",          vtype_stringptr,   &event_data },
+
+  /*XXX want to use generic vars for as many of these as possible*/
+  { "event_defer_errno",   vtype_int,         &event_defer_errno },
+
+  { "event_name",          vtype_stringptr,   &event_name },
+#endif
   { "exim_gid",            vtype_gid,         &exim_gid },
   { "exim_path",           vtype_stringptr,   &exim_path },
   { "exim_uid",            vtype_uid,         &exim_uid },
+  { "exim_version",        vtype_stringptr,   &version_string },
 #ifdef WITH_OLD_DEMIME
   { "found_extension",     vtype_stringptr,   &found_extension },
 #endif
@@ -498,6 +514,7 @@ static var_entry var_table[] = {
   { "host_data",           vtype_stringptr,   &host_data },
   { "host_lookup_deferred",vtype_int,         &host_lookup_deferred },
   { "host_lookup_failed",  vtype_int,         &host_lookup_failed },
+  { "host_port",           vtype_int,         &deliver_host_port },
   { "inode",               vtype_ino,         &deliver_inode },
   { "interface_address",   vtype_stringptr,   &interface_address },
   { "interface_port",      vtype_int,         &interface_port },
@@ -638,6 +655,7 @@ static var_entry var_table[] = {
   { "sn8",                 vtype_filter_int,  &filter_sn[8] },
   { "sn9",                 vtype_filter_int,  &filter_sn[9] },
 #ifdef WITH_CONTENT_SCAN
+  { "spam_action",         vtype_stringptr,   &spam_action },
   { "spam_bar",            vtype_stringptr,   &spam_bar },
   { "spam_report",         vtype_stringptr,   &spam_report },
   { "spam_score",          vtype_stringptr,   &spam_score },
@@ -675,22 +693,28 @@ static var_entry var_table[] = {
   { "tls_in_ourcert",      vtype_cert,        &tls_in.ourcert },
   { "tls_in_peercert",     vtype_cert,        &tls_in.peercert },
   { "tls_in_peerdn",       vtype_stringptr,   &tls_in.peerdn },
-#if defined(SUPPORT_TLS) && !defined(USE_GNUTLS)
+#if defined(SUPPORT_TLS)
   { "tls_in_sni",          vtype_stringptr,   &tls_in.sni },
 #endif
   { "tls_out_bits",        vtype_int,         &tls_out.bits },
   { "tls_out_certificate_verified", vtype_int,&tls_out.certificate_verified },
   { "tls_out_cipher",      vtype_stringptr,   &tls_out.cipher },
+#ifdef EXPERIMENTAL_DANE
+  { "tls_out_dane",        vtype_bool,        &tls_out.dane_verified },
+#endif
   { "tls_out_ocsp",        vtype_int,         &tls_out.ocsp },
   { "tls_out_ourcert",     vtype_cert,        &tls_out.ourcert },
   { "tls_out_peercert",    vtype_cert,        &tls_out.peercert },
   { "tls_out_peerdn",      vtype_stringptr,   &tls_out.peerdn },
-#if defined(SUPPORT_TLS) && !defined(USE_GNUTLS)
+#if defined(SUPPORT_TLS)
   { "tls_out_sni",         vtype_stringptr,   &tls_out.sni },
 #endif
+#ifdef EXPERIMENTAL_DANE
+  { "tls_out_tlsa_usage",  vtype_int,         &tls_out.tlsa_usage },
+#endif
 
   { "tls_peerdn",          vtype_stringptr,   &tls_in.peerdn },        /* mind the alphabetical order! */
-#if defined(SUPPORT_TLS) && !defined(USE_GNUTLS)
+#if defined(SUPPORT_TLS)
   { "tls_sni",             vtype_stringptr,   &tls_in.sni },   /* mind the alphabetical order! */
 #endif
 
@@ -702,18 +726,9 @@ static var_entry var_table[] = {
   { "tod_logfile",         vtype_todlf,       NULL },
   { "tod_zone",            vtype_todzone,     NULL },
   { "tod_zulu",            vtype_todzulu,     NULL },
-#ifdef EXPERIMENTAL_TPDA
-  { "tpda_defer_errno",     vtype_int,         &tpda_defer_errno },
-  { "tpda_defer_errstr",    vtype_stringptr,   &tpda_defer_errstr },
-  { "tpda_delivery_confirmation", vtype_stringptr,   &tpda_delivery_confirmation },
-  { "tpda_delivery_domain", vtype_stringptr,   &tpda_delivery_domain },
-  { "tpda_delivery_fqdn",   vtype_stringptr,   &tpda_delivery_fqdn },
-  { "tpda_delivery_ip",     vtype_stringptr,   &tpda_delivery_ip },
-  { "tpda_delivery_local_part",vtype_stringptr,&tpda_delivery_local_part },
-  { "tpda_delivery_port",   vtype_int,         &tpda_delivery_port },
-#endif
   { "transport_name",      vtype_stringptr,   &transport_name },
   { "value",               vtype_stringptr,   &lookup_value },
+  { "verify_mode",         vtype_stringptr,   &verify_mode },
   { "version_number",      vtype_stringptr,   &version_string },
   { "warn_message_delay",  vtype_stringptr,   &warnmsg_delay },
   { "warn_message_recipient",vtype_stringptr, &warnmsg_recipients },
@@ -910,7 +925,9 @@ vaguely_random_number(int max)
 #ifdef HAVE_ARC4RANDOM
       /* cryptographically strong randomness, common on *BSD platforms, not
       so much elsewhere.  Alas. */
+#ifndef NOT_HAVE_ARC4RANDOM_STIR
       arc4random_stir();
+#endif
 #elif defined(HAVE_SRANDOM) || defined(HAVE_SRANDOMDEV)
 #ifdef HAVE_SRANDOMDEV
       /* uses random(4) for seeding */
@@ -958,8 +975,8 @@ Note: The test for *s != 0 in the while loop is necessary because
 Ustrchr() yields non-NULL if the character is zero (which is not something
 I expected). */
 
-static uschar *
-read_name(uschar *name, int max, uschar *s, uschar *extras)
+static const uschar *
+read_name(uschar *name, int max, const uschar *s, uschar *extras)
 {
 int ptr = 0;
 while (*s != 0 && (isalnum(*s) || Ustrchr(extras, *s) != NULL))
@@ -992,8 +1009,8 @@ Arguments:
 Returns:    a pointer to the first character after the header name
 */
 
-static uschar *
-read_header_name(uschar *name, int max, uschar *s)
+static const uschar *
+read_header_name(uschar *name, int max, const uschar *s)
 {
 int prelen = Ustrchr(name, '_') - name + 1;
 int ptr = Ustrlen(name) - prelen;
@@ -1030,6 +1047,14 @@ while (isdigit(*s)) *n = *n * 10 + (*s++ - '0');
 return s;
 }
 
+static const uschar *
+read_cnumber(int *n, const uschar *s)
+{
+*n = 0;
+while (isdigit(*s)) *n = *n * 10 + (*s++ - '0');
+return s;
+}
+
 
 
 /*************************************************
@@ -1047,7 +1072,7 @@ Returns:    NULL if the subfield was not found, or
 */
 
 static uschar *
-expand_getkeyed(uschar *key, uschar *s)
+expand_getkeyed(uschar *key, const uschar *s)
 {
 int length = Ustrlen(key);
 while (isspace(*s)) s++;
@@ -1058,7 +1083,7 @@ while (*s != 0)
   {
   int dkeylength;
   uschar *data;
-  uschar *dkey = s;
+  const uschar *dkey = s;
 
   while (*s != 0 && *s != '=' && !isspace(*s)) s++;
   dkeylength = s - dkey;
@@ -1169,17 +1194,17 @@ return fieldtext;
 
 
 static uschar *
-expand_getlistele(int field, uschar * list)
+expand_getlistele(int field, const uschar * list)
 {
-uschar * tlist= list;
+const uschar * tlist= list;
 int sep= 0;
 uschar dummy;
 
 if(field<0)
-{
+  {
   for(field++; string_nextinlist(&tlist, &sep, &dummy, 1); ) field++;
   sep= 0;
-}
+  }
 if(field==0) return NULL;
 while(--field>0 && (string_nextinlist(&list, &sep, &dummy, 1))) ;
 return string_nextinlist(&list, &sep, NULL, 0);
@@ -1876,6 +1901,8 @@ switch (vp->type)
   #endif
 
   }
+
+return NULL;  /* Unknown variable. Silences static checkers. */
 }
 
 
@@ -1919,11 +1946,11 @@ Returns:     0 OK; string pointer updated
 */
 
 static int
-read_subs(uschar **sub, int n, int m, uschar **sptr, BOOL skipping,
+read_subs(uschar **sub, int n, int m, const uschar **sptr, BOOL skipping,
   BOOL check_end, uschar *name, BOOL *resetok)
 {
 int i;
-uschar *s = *sptr;
+const uschar *s = *sptr;
 
 while (isspace(*s)) s++;
 for (i = 0; i < n; i++)
@@ -1999,7 +2026,7 @@ static int
 eval_acl(uschar ** sub, int nsub, uschar ** user_msgp)
 {
 int i;
-uschar *tmp;
+uschar * tmp = NULL;
 int sav_narg = acl_narg;
 int ret;
 extern int acl_where;
@@ -2055,8 +2082,8 @@ Returns:   a pointer to the first character after the condition, or
            NULL after an error
 */
 
-static uschar *
-eval_condition(uschar *s, BOOL *resetok, BOOL *yield)
+static const uschar *
+eval_condition(const uschar *s, BOOL *resetok, BOOL *yield)
 {
 BOOL testfor = TRUE;
 BOOL tempcond, combined_cond;
@@ -2066,7 +2093,7 @@ int i, rc, cond_type, roffset;
 int_eximarith_t num[2];
 struct stat statbuf;
 uschar name[256];
-uschar *sub[10];
+const uschar *sub[10];
 
 const pcre *re;
 const uschar *rerror;
@@ -2286,6 +2313,7 @@ switch(cond_type)
   case ECOND_ACL:
     /* ${if acl {{name}{arg1}{arg2}...}  {yes}{no}} */
     {
+    uschar *sub[10];
     uschar *user_msg;
     BOOL cond = FALSE;
     int size = 0;
@@ -2443,7 +2471,7 @@ switch(cond_type)
         }
       else
         {
-        num[i] = expand_string_integer(sub[i], FALSE);
+        num[i] = expanded_string_integer(sub[i], FALSE);
         if (expand_string_message != NULL) return NULL;
         }
       }
@@ -2736,17 +2764,18 @@ switch(cond_type)
     case ECOND_INLIST:
     case ECOND_INLISTI:
       {
+      const uschar * list = sub[1];
       int sep = 0;
       uschar *save_iterate_item = iterate_item;
       int (*compare)(const uschar *, const uschar *);
 
+      DEBUG(D_expand) debug_printf("condition: %s\n", name);
+
       tempcond = FALSE;
-      if (cond_type == ECOND_INLISTI)
-        compare = strcmpic;
-      else
-        compare = (int (*)(const uschar *, const uschar *)) strcmp;
+      compare = cond_type == ECOND_INLISTI
+        ? strcmpic : (int (*)(const uschar *, const uschar *)) strcmp;
 
-      while ((iterate_item = string_nextinlist(&sub[1], &sep, NULL, 0)) != NULL)
+      while ((iterate_item = string_nextinlist(&list, &sep, NULL, 0)))
         if (compare(sub[0], iterate_item) == 0)
           {
           tempcond = TRUE;
@@ -2824,9 +2853,12 @@ switch(cond_type)
   case ECOND_FORALL:
   case ECOND_FORANY:
     {
+    const uschar * list;
     int sep = 0;
     uschar *save_iterate_item = iterate_item;
 
+    DEBUG(D_expand) debug_printf("condition: %s\n", name);
+
     while (isspace(*s)) s++;
     if (*s++ != '{') goto COND_FAILED_CURLY_START;     /* }-for-text-editors */
     sub[0] = expand_string_internal(s, TRUE, &s, (yield == NULL), TRUE, resetok);
@@ -2861,7 +2893,8 @@ switch(cond_type)
       }
 
     if (yield != NULL) *yield = !testfor;
-    while ((iterate_item = string_nextinlist(&sub[0], &sep, NULL, 0)) != NULL)
+    list = sub[0];
+    while ((iterate_item = string_nextinlist(&list, &sep, NULL, 0)) != NULL)
       {
       DEBUG(D_expand) debug_printf("%s: $item = \"%s\"\n", name, iterate_item);
       if (!eval_condition(sub[1], resetok, &tempcond))
@@ -3079,11 +3112,11 @@ Returns:         0 OK; lookup_value has been reset to save_lookup
 */
 
 static int
-process_yesno(BOOL skipping, BOOL yes, uschar *save_lookup, uschar **sptr,
+process_yesno(BOOL skipping, BOOL yes, uschar *save_lookup, const uschar **sptr,
   uschar **yieldptr, int *sizeptr, int *ptrptr, uschar *type, BOOL *resetok)
 {
 int rc = 0;
-uschar *s = *sptr;    /* Local value */
+const uschar *s = *sptr;    /* Local value */
 uschar *sub1, *sub2;
 
 /* If there are no following strings, we substitute the contents of $value for
@@ -3161,7 +3194,8 @@ inside another lookup or if or extract. */
 else if (*s != '}')
   {
   uschar name[256];
-  s = read_name(name, sizeof(name), s, US"_");
+  /* deconst cast ok here as source is s anyway */
+  s = US read_name(name, sizeof(name), s, US"_");
   if (Ustrcmp(name, "fail") == 0)
     {
     if (!yes && !skipping)
@@ -3730,14 +3764,14 @@ Returns:         NULL if expansion fails:
 */
 
 static uschar *
-expand_string_internal(uschar *string, BOOL ket_ends, uschar **left,
+expand_string_internal(const uschar *string, BOOL ket_ends, const uschar **left,
   BOOL skipping, BOOL honour_dollar, BOOL *resetok_p)
 {
 int ptr = 0;
 int size = Ustrlen(string)+ 64;
 int item_type;
 uschar *yield = store_get(size);
-uschar *s = string;
+const uschar *s = string;
 uschar *save_expand_nstring[EXPAND_MAXN+1];
 int save_expand_nlength[EXPAND_MAXN+1];
 BOOL resetok = TRUE;
@@ -3765,7 +3799,7 @@ while (*s != 0)
 
     if (s[1] == 'N')
       {
-      uschar *t = s + 2;
+      const uschar * t = s + 2;
       for (s = t; *s != 0; s++) if (*s == '\\' && s[1] == 'N') break;
       yield = string_cat(yield, &size, &ptr, t, s - t);
       if (*s != 0) s += 2;
@@ -3881,7 +3915,7 @@ while (*s != 0)
   if (isdigit(*s))
     {
     int n;
-    s = read_number(&n, s);
+    s = read_cnumber(&n, s);
     if (n >= 0 && n <= expand_nmax)
       yield = string_cat(yield, &size, &ptr, expand_nstring[n],
         expand_nlength[n]);
@@ -3902,7 +3936,7 @@ while (*s != 0)
   if (isdigit((*(++s))))
     {
     int n;
-    s = read_number(&n, s);            /*{*/
+    s = read_cnumber(&n, s);           /*{*/
     if (*s++ != '}')
       {                                        /*{*/
       expand_string_message = US"} expected after number";
@@ -3979,7 +4013,7 @@ while (*s != 0)
     case EITEM_IF:
       {
       BOOL cond = FALSE;
-      uschar *next_s;
+      const uschar *next_s;
       int save_expand_nmax =
         save_expand_strings(save_expand_nstring, save_expand_nlength);
 
@@ -4031,7 +4065,8 @@ while (*s != 0)
       int stype, partial, affixlen, starflags;
       int expand_setup = 0;
       int nameptr = 0;
-      uschar *key, *filename, *affix;
+      uschar *key, *filename;
+      const uschar *affix;
       uschar *save_lookup_value = lookup_value;
       int save_expand_nmax =
         save_expand_strings(save_expand_nstring, save_expand_nlength);
@@ -4665,6 +4700,9 @@ while (*s != 0)
 
         DEBUG(D_expand) debug_printf("connected to socket %s\n", sub_arg[0]);
 
+       /* Allow sequencing of test actions */
+       if (running_in_test_harness) millisleep(100);
+
         /* Write the request string, if not empty */
 
         if (sub_arg[1][0] != 0)
@@ -4688,6 +4726,8 @@ while (*s != 0)
         shutdown(fd, SHUT_WR);
         #endif
 
+       if (running_in_test_harness) millisleep(100);
+
         /* Now we need to read from the socket, under a timeout. The function
         that reads a file can be used. */
 
@@ -4744,7 +4784,7 @@ while (*s != 0)
       {
       FILE *f;
       uschar *arg;
-      uschar **argv;
+      const uschar **argv;
       pid_t pid;
       int fd_in, fd_out;
       int lsize = 0;
@@ -4782,7 +4822,7 @@ while (*s != 0)
 
         /* Create the child process, making it a group leader. */
 
-        pid = child_open(argv, NULL, 0077, &fd_in, &fd_out, TRUE);
+        pid = child_open(USS argv, NULL, 0077, &fd_in, &fd_out, TRUE);
 
         if (pid < 0)
           {
@@ -5212,25 +5252,28 @@ while (*s != 0)
             while (len > 0 && isspace(p[len-1])) len--;
             p[len] = 0;
 
-            if (*p == 0 && !skipping)
-              {
-              expand_string_message = US"first argument of \"extract\" must "
-                "not be empty";
-              goto EXPAND_FAILED;
-              }
+            if (!skipping)
+             {
+             if (*p == 0)
+               {
+               expand_string_message = US"first argument of \"extract\" must "
+                 "not be empty";
+               goto EXPAND_FAILED;
+               }
 
-            if (*p == '-')
-              {
-              field_number = -1;
-              p++;
-              }
-            while (*p != 0 && isdigit(*p)) x = x * 10 + *p++ - '0';
-            if (*p == 0)
-              {
-              field_number *= x;
-              j = 3;               /* Need 3 args */
-              field_number_set = TRUE;
-              }
+             if (*p == '-')
+               {
+               field_number = -1;
+               p++;
+               }
+             while (*p != 0 && isdigit(*p)) x = x * 10 + *p++ - '0';
+             if (*p == 0)
+               {
+               field_number *= x;
+               j = 3;               /* Need 3 args */
+               field_number_set = TRUE;
+               }
+             }
             }
           }
         else goto EXPAND_FAILED_CURLY;
@@ -5365,8 +5408,6 @@ while (*s != 0)
 #ifdef SUPPORT_TLS
     case EITEM_CERTEXTRACT:
       {
-      int i;
-      int field_number = 1;
       uschar *save_lookup_value = lookup_value;
       uschar *sub[2];
       int save_expand_nmax =
@@ -5382,7 +5423,6 @@ while (*s != 0)
       /* strip spaces fore & aft */
       {
       int len;
-      int x = 0;
       uschar *p = sub[0];
 
       while (isspace(*p)) p++;
@@ -5444,7 +5484,7 @@ while (*s != 0)
       int sep = 0;
       int save_ptr = ptr;
       uschar outsep[2] = { '\0', '\0' };
-      uschar *list, *expr, *temp;
+      const uschar *list, *expr, *temp;
       uschar *save_iterate_item = iterate_item;
       uschar *save_lookup_value = lookup_value;
 
@@ -5457,11 +5497,12 @@ while (*s != 0)
 
       if (item_type == EITEM_REDUCE)
         {
+       uschar * t;
         while (isspace(*s)) s++;
         if (*s++ != '{') goto EXPAND_FAILED_CURLY;
-        temp = expand_string_internal(s, TRUE, &s, skipping, TRUE, &resetok);
+        t = expand_string_internal(s, TRUE, &s, skipping, TRUE, &resetok);
         if (temp == NULL) goto EXPAND_FAILED;
-        lookup_value = temp;
+        lookup_value = t;
         if (*s++ != '}') goto EXPAND_FAILED_CURLY;
         }
 
@@ -5482,9 +5523,7 @@ while (*s != 0)
         if (temp != NULL) s = temp;
         }
       else
-        {
         temp = expand_string_internal(s, TRUE, &s, TRUE, TRUE, &resetok);
-        }
 
       if (temp == NULL)
         {
@@ -5542,7 +5581,8 @@ while (*s != 0)
 
         else
           {
-          temp = expand_string_internal(expr, TRUE, NULL, skipping, TRUE, &resetok);
+         uschar * t = expand_string_internal(expr, TRUE, NULL, skipping, TRUE, &resetok);
+          temp = t;
           if (temp == NULL)
             {
             iterate_item = save_iterate_item;
@@ -5552,7 +5592,7 @@ while (*s != 0)
             }
           if (item_type == EITEM_REDUCE)
             {
-            lookup_value = temp;      /* Update the value of $value */
+            lookup_value = t;         /* Update the value of $value */
             continue;                 /* and continue the iteration */
             }
           }
@@ -5612,6 +5652,144 @@ while (*s != 0)
       continue;
       }
 
+    case EITEM_SORT:
+      {
+      int sep = 0;
+      const uschar *srclist, *cmp, *xtract;
+      uschar *srcitem;
+      const uschar *dstlist = NULL, *dstkeylist = NULL;
+      uschar * tmp;
+      uschar *save_iterate_item = iterate_item;
+
+      while (isspace(*s)) s++;
+      if (*s++ != '{') goto EXPAND_FAILED_CURLY;
+
+      srclist = expand_string_internal(s, TRUE, &s, skipping, TRUE, &resetok);
+      if (!srclist) goto EXPAND_FAILED;
+      if (*s++ != '}') goto EXPAND_FAILED_CURLY;
+
+      while (isspace(*s)) s++;
+      if (*s++ != '{') goto EXPAND_FAILED_CURLY;
+
+      cmp = expand_string_internal(s, TRUE, &s, skipping, FALSE, &resetok);
+      if (!cmp) goto EXPAND_FAILED;
+      if (*s++ != '}') goto EXPAND_FAILED_CURLY;
+
+      while (isspace(*s)) s++;
+      if (*s++ != '{') goto EXPAND_FAILED_CURLY;
+
+      xtract = s;
+      tmp = expand_string_internal(s, TRUE, &s, TRUE, TRUE, &resetok);
+      if (!tmp) goto EXPAND_FAILED;
+      xtract = string_copyn(xtract, s - xtract);
+
+      if (*s++ != '}') goto EXPAND_FAILED_CURLY;
+                                                       /*{*/
+      if (*s++ != '}')
+        {                                              /*{*/
+        expand_string_message = US"missing } at end of \"sort\"";
+        goto EXPAND_FAILED;
+        }
+
+      if (skipping) continue;
+
+      while ((srcitem = string_nextinlist(&srclist, &sep, NULL, 0)))
+        {
+       uschar * dstitem;
+       uschar * newlist = NULL;
+       uschar * newkeylist = NULL;
+       uschar * srcfield;
+
+        DEBUG(D_expand) debug_printf("%s: $item = \"%s\"\n", name, srcitem);
+
+       /* extract field for comparisons */
+       iterate_item = srcitem;
+       if (  !(srcfield = expand_string_internal(xtract, FALSE, NULL, FALSE,
+                                         TRUE, &resetok))
+          || !*srcfield)
+         {
+         expand_string_message = string_sprintf(
+             "field-extract in sort: \"%s\"", xtract);
+         goto EXPAND_FAILED;
+         }
+
+       /* Insertion sort */
+
+       /* copy output list until new-item < list-item */
+       while ((dstitem = string_nextinlist(&dstlist, &sep, NULL, 0)))
+         {
+         uschar * dstfield;
+         uschar * expr;
+         BOOL before;
+
+         /* field for comparison */
+         if (!(dstfield = string_nextinlist(&dstkeylist, &sep, NULL, 0)))
+           goto sort_mismatch;
+
+         /* build and run condition string */
+         expr = string_sprintf("%s{%s}{%s}", cmp, srcfield, dstfield);
+
+         DEBUG(D_expand) debug_printf("%s: cond = \"%s\"\n", name, expr);
+         if (!eval_condition(expr, &resetok, &before))
+           {
+           expand_string_message = string_sprintf("comparison in sort: %s",
+               expr);
+           goto EXPAND_FAILED;
+           }
+
+         if (before)
+           {
+           /* New-item sorts before this dst-item.  Append new-item,
+           then dst-item, then remainder of dst list. */
+
+           newlist = string_append_listele(newlist, sep, srcitem);
+           newkeylist = string_append_listele(newkeylist, sep, srcfield);
+           srcitem = NULL;
+
+           newlist = string_append_listele(newlist, sep, dstitem);
+           newkeylist = string_append_listele(newkeylist, sep, dstfield);
+
+           while ((dstitem = string_nextinlist(&dstlist, &sep, NULL, 0)))
+             {
+             if (!(dstfield = string_nextinlist(&dstkeylist, &sep, NULL, 0)))
+               goto sort_mismatch;
+             newlist = string_append_listele(newlist, sep, dstitem);
+             newkeylist = string_append_listele(newkeylist, sep, dstfield);
+             }
+
+           break;
+           }
+
+         newlist = string_append_listele(newlist, sep, dstitem);
+         newkeylist = string_append_listele(newkeylist, sep, dstfield);
+         }
+
+       /* If we ran out of dstlist without consuming srcitem, append it */
+       if (srcitem)
+         {
+         newlist = string_append_listele(newlist, sep, srcitem);
+         newkeylist = string_append_listele(newkeylist, sep, srcfield);
+         }
+
+       dstlist = newlist;
+       dstkeylist = newkeylist;
+
+        DEBUG(D_expand) debug_printf("%s: dstlist = \"%s\"\n", name, dstlist);
+        DEBUG(D_expand) debug_printf("%s: dstkeylist = \"%s\"\n", name, dstkeylist);
+       }
+
+      if (dstlist)
+       yield = string_cat(yield, &size, &ptr, dstlist, Ustrlen(dstlist));
+
+      /* Restore preserved $item */
+      iterate_item = save_iterate_item;
+      continue;
+
+      sort_mismatch:
+       expand_string_message = US"Internal error in sort (list mismatch)";
+       goto EXPAND_FAILED;
+      }
+
 
     /* If ${dlfunc } support is configured, handle calling dynamically-loaded
     functions, unless locked out at this time. Syntax is ${dlfunc{file}{func}}
@@ -5725,7 +5903,7 @@ while (*s != 0)
     int c;
     uschar *arg = NULL;
     uschar *sub;
-    var_entry *vp;
+    var_entry *vp = NULL;
 
     /* Owing to an historical mis-design, an underscore may be part of the
     operator name, or it may introduce arguments.  We therefore first scan the
@@ -5748,11 +5926,12 @@ while (*s != 0)
     switch(c)
       {
 #ifdef SUPPORT_TLS
-      case EOP_SHA1:
       case EOP_MD5:
+      case EOP_SHA1:
+      case EOP_SHA256:
        if (s[1] == '$')
          {
-         uschar * s1 = s;
+         const uschar * s1 = s;
          sub = expand_string_internal(s+2, TRUE, &s1, skipping,
                  FALSE, &resetok);
          if (!sub)       goto EXPAND_FAILED;           /*{*/
@@ -5762,8 +5941,8 @@ while (*s != 0)
            s = s1+1;
            break;
            }
+         vp = NULL;
          }
-       vp = NULL;
         /*FALLTHROUGH*/
 #endif
       default:
@@ -5861,7 +6040,7 @@ while (*s != 0)
        if (vp && *(void **)vp->value)
          {
          uschar * cp = tls_cert_fprt_md5(*(void **)vp->value);
-         yield = string_cat(yield, &size, &ptr, cp, (int)strlen(cp));
+         yield = string_cat(yield, &size, &ptr, cp, Ustrlen(cp));
          }
        else
 #endif
@@ -5882,7 +6061,7 @@ while (*s != 0)
        if (vp && *(void **)vp->value)
          {
          uschar * cp = tls_cert_fprt_sha1(*(void **)vp->value);
-         yield = string_cat(yield, &size, &ptr, cp, (int)strlen(cp));
+         yield = string_cat(yield, &size, &ptr, cp, Ustrlen(cp));
          }
        else
 #endif
@@ -5898,6 +6077,18 @@ while (*s != 0)
          }
         continue;
 
+      case EOP_SHA256:
+#ifdef SUPPORT_TLS
+       if (vp && *(void **)vp->value)
+         {
+         uschar * cp = tls_cert_fprt_sha256(*(void **)vp->value);
+         yield = string_cat(yield, &size, &ptr, cp, (int)Ustrlen(cp));
+         }
+       else
+#endif
+         expand_string_message = US"sha256 only supported for certificates";
+        continue;
+
       /* Convert hex encoding to base64 encoding */
 
       case EOP_HEX2B64:
@@ -5971,7 +6162,7 @@ while (*s != 0)
        uschar * cp;
        uschar buffer[256];
 
-       while (string_nextinlist(&sub, &sep, buffer, sizeof(buffer)) != NULL) cnt++;
+       while (string_nextinlist(CUSS &sub, &sep, buffer, sizeof(buffer)) != NULL) cnt++;
        cp = string_sprintf("%d", cnt);
         yield = string_cat(yield, &size, &ptr, cp, Ustrlen(cp));
         continue;
@@ -5983,7 +6174,7 @@ while (*s != 0)
       case EOP_LISTNAMED:
        {
        tree_node *t = NULL;
-       uschar * list;
+       const uschar * list;
        int sep = 0;
        uschar * item;
        uschar * suffix = US"";
@@ -6302,7 +6493,7 @@ while (*s != 0)
       case EOP_RFC2047:
         {
         uschar buffer[2048];
-        uschar *string = parse_quote_2047(sub, Ustrlen(sub), headers_charset,
+       const uschar *string = parse_quote_2047(sub, Ustrlen(sub), headers_charset,
           buffer, sizeof(buffer), FALSE);
         yield = string_cat(yield, &size, &ptr, string, Ustrlen(string));
         continue;
@@ -6348,19 +6539,17 @@ while (*s != 0)
 
       case EOP_UTF8CLEAN:
         {
-        int seq_len, index = 0;
-        int bytes_left  = 0;
+        int seq_len = 0, index = 0;
+        int bytes_left = 0;
+       long codepoint = -1;
         uschar seq_buff[4];                    /* accumulate utf-8 here */
         
         while (*sub != 0)
          {
-         int complete;
-         long codepoint;
-         uschar c;
+         int complete = 0;
+         uschar c = *sub++;
 
-         complete = 0;
-         c = *sub++;
-         if(bytes_left)
+         if (bytes_left)
            {
            if ((c & 0xc0) != 0x80)
              {
@@ -6374,7 +6563,7 @@ while (*s != 0)
              if (--bytes_left == 0)            /* codepoint complete */
                {
                if(codepoint > 0x10FFFF)        /* is it too large? */
-                 complete = -1;        /* error */
+                 complete = -1;        /* error (RFC3629 limit) */
                else
                  {             /* finished; output utf-8 sequence */
                  yield = string_cat(yield, &size, &ptr, seq_buff, seq_len);
@@ -6434,7 +6623,7 @@ while (*s != 0)
 
       case EOP_ESCAPE:
         {
-        uschar *t = string_printing(sub);
+        const uschar *t = string_printing(sub);
         yield = string_cat(yield, &size, &ptr, t, Ustrlen(t));
         continue;
         }
@@ -6662,7 +6851,7 @@ while (*s != 0)
         int_eximarith_t max;
         uschar *s;
 
-        max = expand_string_integer(sub, TRUE);
+        max = expanded_string_integer(sub, TRUE);
         if (expand_string_message != NULL)
           goto EXPAND_FAILED;
         s = string_sprintf("%d", vaguely_random_number((int)max));
@@ -6822,23 +7011,35 @@ return (Ustrpbrk(string, "$\\") == NULL)? string :
 
 
 
+const uschar *
+expand_cstring(const uschar *string)
+{
+search_find_defer = FALSE;
+malformed_header = FALSE;
+return (Ustrpbrk(string, "$\\") == NULL)? string :
+  expand_string_internal(string, FALSE, NULL, FALSE, TRUE, NULL);
+}
+
+
+
 /*************************************************
 *              Expand and copy                   *
 *************************************************/
 
 /* Now and again we want to expand a string and be sure that the result is in a
 new bit of store. This function does that.
+Since we know it has been copied, the de-const cast is safe.
 
 Argument: the string to be expanded
 Returns:  the expanded string, always in a new bit of store, or NULL
 */
 
 uschar *
-expand_string_copy(uschar *string)
+expand_string_copy(const uschar *string)
 {
-uschar *yield = expand_string(string);
+const uschar *yield = expand_cstring(string);
 if (yield == string) yield = string_copy(string);
-return yield;
+return US yield;
 }
 
 
@@ -6862,8 +7063,32 @@ Returns:  the integer value, or
 int_eximarith_t
 expand_string_integer(uschar *string, BOOL isplus)
 {
+return expanded_string_integer(expand_string(string), isplus);
+}
+
+
+/*************************************************
+ *         Interpret string as an integer        *
+ *************************************************/
+
+/* Convert a string (that has already been expanded) into an integer.
+
+This function is used inside the expansion code.
+
+Arguments:
+  s       the string to be expanded
+  isplus  TRUE if a non-negative number is expected
+
+Returns:  the integer value, or
+          -1 if string is NULL (which implies an expansion error)
+          -2 for an integer interpretation error
+          expand_string_message is set NULL for an OK integer
+*/
+
+static int_eximarith_t
+expanded_string_integer(const uschar *s, BOOL isplus)
+{
 int_eximarith_t value;
-uschar *s = expand_string(string);
 uschar *msg = US"invalid integer \"%s\"";
 uschar *endptr;
 
@@ -6940,6 +7165,67 @@ return -2;
 }
 
 
+/* These values are usually fixed boolean values, but they are permitted to be
+expanded strings.
+
+Arguments:
+  addr       address being routed
+  mtype      the module type
+  mname      the module name
+  dbg_opt    debug selectors
+  oname      the option name
+  bvalue     the router's boolean value
+  svalue     the router's string value
+  rvalue     where to put the returned value
+
+Returns:     OK     value placed in rvalue
+             DEFER  expansion failed
+*/
+
+int
+exp_bool(address_item *addr,
+  uschar *mtype, uschar *mname, unsigned dbg_opt,
+  uschar *oname, BOOL bvalue,
+  uschar *svalue, BOOL *rvalue)
+{
+uschar *expanded;
+if (svalue == NULL) { *rvalue = bvalue; return OK; }
+
+expanded = expand_string(svalue);
+if (expanded == NULL)
+  {
+  if (expand_string_forcedfail)
+    {
+    DEBUG(dbg_opt) debug_printf("expansion of \"%s\" forced failure\n", oname);
+    *rvalue = bvalue;
+    return OK;
+    }
+  addr->message = string_sprintf("failed to expand \"%s\" in %s %s: %s",
+      oname, mname, mtype, expand_string_message);
+  DEBUG(dbg_opt) debug_printf("%s\n", addr->message);
+  return DEFER;
+  }
+
+DEBUG(dbg_opt) debug_printf("expansion of \"%s\" yields \"%s\"\n", oname,
+  expanded);
+
+if (strcmpic(expanded, US"true") == 0 || strcmpic(expanded, US"yes") == 0)
+  *rvalue = TRUE;
+else if (strcmpic(expanded, US"false") == 0 || strcmpic(expanded, US"no") == 0)
+  *rvalue = FALSE;
+else
+  {
+  addr->message = string_sprintf("\"%s\" is not a valid value for the "
+    "\"%s\" option in the %s %s", expanded, oname, mname, mtype);
+  return DEFER;
+  }
+
+return OK;
+}
+
+
+
+
 /*************************************************
 **************************************************
 *             Stand-alone test program           *