tidying
[exim.git] / src / src / expand.c
index 1cd08df899eecdf4b31754be6488dd25da9be8f1..cdc914f5e13c31a6789b5d4f0327d427410b1235 100644 (file)
@@ -117,6 +117,7 @@ static uschar *item_table[] = {
 #endif
   US"length",
   US"listextract",
 #endif
   US"length",
   US"listextract",
+  US"listquote",
   US"lookup",
   US"map",
   US"nhash",
   US"lookup",
   US"map",
   US"nhash",
@@ -151,6 +152,7 @@ enum {
 #endif
   EITEM_LENGTH,
   EITEM_LISTEXTRACT,
 #endif
   EITEM_LENGTH,
   EITEM_LISTEXTRACT,
+  EITEM_LISTQUOTE,
   EITEM_LOOKUP,
   EITEM_MAP,
   EITEM_NHASH,
   EITEM_LOOKUP,
   EITEM_MAP,
   EITEM_NHASH,
@@ -213,6 +215,7 @@ static uschar *op_table_main[] = {
   US"base62d",
   US"base64",
   US"base64d",
   US"base62d",
   US"base64",
   US"base64d",
+  US"bless",
   US"domain",
   US"escape",
   US"escape8bit",
   US"domain",
   US"escape",
   US"escape8bit",
@@ -260,6 +263,7 @@ enum {
   EOP_BASE62D,
   EOP_BASE64,
   EOP_BASE64D,
   EOP_BASE62D,
   EOP_BASE64,
   EOP_BASE64D,
+  EOP_BLESS,
   EOP_DOMAIN,
   EOP_ESCAPE,
   EOP_ESCAPE8BIT,
   EOP_DOMAIN,
   EOP_ESCAPE,
   EOP_ESCAPE8BIT,
@@ -464,6 +468,7 @@ typedef struct {
 
 static uschar * fn_recipients(void);
 typedef uschar * stringptr_fn_t(void);
 
 static uschar * fn_recipients(void);
 typedef uschar * stringptr_fn_t(void);
+static uschar * fn_queue_size(void);
 
 /* This table must be kept in alphabetical order. */
 
 
 /* This table must be kept in alphabetical order. */
 
@@ -586,7 +591,10 @@ static var_entry var_table[] = {
   { "local_part",          vtype_stringptr,   &deliver_localpart },
   { "local_part_data",     vtype_stringptr,   &deliver_localpart_data },
   { "local_part_prefix",   vtype_stringptr,   &deliver_localpart_prefix },
   { "local_part",          vtype_stringptr,   &deliver_localpart },
   { "local_part_data",     vtype_stringptr,   &deliver_localpart_data },
   { "local_part_prefix",   vtype_stringptr,   &deliver_localpart_prefix },
+  { "local_part_prefix_v", vtype_stringptr,   &deliver_localpart_prefix_v },
   { "local_part_suffix",   vtype_stringptr,   &deliver_localpart_suffix },
   { "local_part_suffix",   vtype_stringptr,   &deliver_localpart_suffix },
+  { "local_part_suffix_v", vtype_stringptr,   &deliver_localpart_suffix_v },
+  { "local_part_verified", vtype_stringptr,   &deliver_localpart_verified },
 #ifdef HAVE_LOCAL_SCAN
   { "local_scan_data",     vtype_stringptr,   &local_scan_data },
 #endif
 #ifdef HAVE_LOCAL_SCAN
   { "local_scan_data",     vtype_stringptr,   &local_scan_data },
 #endif
@@ -666,6 +674,7 @@ static var_entry var_table[] = {
   { "qualify_domain",      vtype_stringptr,   &qualify_domain_sender },
   { "qualify_recipient",   vtype_stringptr,   &qualify_domain_recipient },
   { "queue_name",          vtype_stringptr,   &queue_name },
   { "qualify_domain",      vtype_stringptr,   &qualify_domain_sender },
   { "qualify_recipient",   vtype_stringptr,   &qualify_domain_recipient },
   { "queue_name",          vtype_stringptr,   &queue_name },
+  { "queue_size",          vtype_string_func, &fn_queue_size },
   { "rcpt_count",          vtype_int,         &rcpt_count },
   { "rcpt_defer_count",    vtype_int,         &rcpt_defer_count },
   { "rcpt_fail_count",     vtype_int,         &rcpt_fail_count },
   { "rcpt_count",          vtype_int,         &rcpt_count },
   { "rcpt_defer_count",    vtype_int,         &rcpt_defer_count },
   { "rcpt_fail_count",     vtype_int,         &rcpt_fail_count },
@@ -776,6 +785,7 @@ static var_entry var_table[] = {
 #ifndef DISABLE_TLS
   { "tls_in_sni",          vtype_stringptr,   &tls_in.sni },
 #endif
 #ifndef DISABLE_TLS
   { "tls_in_sni",          vtype_stringptr,   &tls_in.sni },
 #endif
+  { "tls_in_ver",          vtype_stringptr,   &tls_in.ver },
   { "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_bits",        vtype_int,         &tls_out.bits },
   { "tls_out_certificate_verified", vtype_int,&tls_out.certificate_verified },
   { "tls_out_cipher",      vtype_stringptr,   &tls_out.cipher },
@@ -796,6 +806,7 @@ static var_entry var_table[] = {
 #ifdef SUPPORT_DANE
   { "tls_out_tlsa_usage",  vtype_int,         &tls_out.tlsa_usage },
 #endif
 #ifdef SUPPORT_DANE
   { "tls_out_tlsa_usage",  vtype_int,         &tls_out.tlsa_usage },
 #endif
+  { "tls_out_ver",         vtype_stringptr,   &tls_out.ver },
 
   { "tls_peerdn",          vtype_stringptr,   &tls_in.peerdn },        /* mind the alphabetical order! */
 #ifndef DISABLE_TLS
 
   { "tls_peerdn",          vtype_stringptr,   &tls_in.peerdn },        /* mind the alphabetical order! */
 #ifndef DISABLE_TLS
@@ -1165,7 +1176,7 @@ static uschar *
 expand_getkeyed(uschar * key, const uschar * s)
 {
 int length = Ustrlen(key);
 expand_getkeyed(uschar * key, const uschar * s)
 {
 int length = Ustrlen(key);
-while (isspace(*s)) s++;
+Uskip_whitespace(&s);
 
 /* Loop to search for the key */
 
 
 /* Loop to search for the key */
 
@@ -1177,14 +1188,13 @@ while (*s)
 
   while (*s && *s != '=' && !isspace(*s)) s++;
   dkeylength = s - dkey;
 
   while (*s && *s != '=' && !isspace(*s)) s++;
   dkeylength = s - dkey;
-  while (isspace(*s)) s++;
-  if (*s == '=') while (isspace((*(++s))));
+  if (Uskip_whitespace(&s) == '=') while (isspace(*++s));
 
   data = string_dequote(&s);
   if (length == dkeylength && strncmpic(key, dkey, length) == 0)
     return data;
 
 
   data = string_dequote(&s);
   if (length == dkeylength && strncmpic(key, dkey, length) == 0)
     return data;
 
-  while (isspace(*s)) s++;
+  Uskip_whitespace(&s);
   }
 
 return NULL;
   }
 
 return NULL;
@@ -1604,7 +1614,7 @@ for (header_line * h = header_list; h; h = h->next)
       found = TRUE;
       s = h->text + len;               /* text to insert */
       if (!(flags & FH_WANT_RAW))      /* unless wanted raw, */
       found = TRUE;
       s = h->text + len;               /* text to insert */
       if (!(flags & FH_WANT_RAW))      /* unless wanted raw, */
-       while (isspace(*s)) s++;        /* remove leading white space */
+       Uskip_whitespace(&s);           /* remove leading white space */
       t = h->text + h->slen;           /* end-point */
 
       /* Unless wanted raw, remove trailing whitespace, including the
       t = h->text + h->slen;           /* end-point */
 
       /* Unless wanted raw, remove trailing whitespace, including the
@@ -1625,8 +1635,8 @@ for (header_line * h = header_list; h; h = h->next)
 
       /* Trim the header roughly if we're approaching limits */
       inc = t - s;
 
       /* Trim the header roughly if we're approaching limits */
       inc = t - s;
-      if ((g ? g->ptr : 0) + inc > header_insert_maxlen)
-       inc = header_insert_maxlen - (g ? g->ptr : 0);
+      if (gstring_length(g) + inc > header_insert_maxlen)
+       inc = header_insert_maxlen - gstring_length(g);
 
       /* For raw just copy the data; for a list, add the data as a colon-sep
       list-element; for comma-list add as an unchecked comma,newline sep
 
       /* For raw just copy the data; for a list, add the data as a colon-sep
       list-element; for comma-list add as an unchecked comma,newline sep
@@ -1638,17 +1648,12 @@ for (header_line * h = header_list; h; h = h->next)
       if (flags & FH_WANT_LIST)
         g = string_append_listele_n(g, ':', s, (unsigned)inc);
       else if (flags & FH_WANT_RAW)
       if (flags & FH_WANT_LIST)
         g = string_append_listele_n(g, ':', s, (unsigned)inc);
       else if (flags & FH_WANT_RAW)
-       {
        g = string_catn(g, s, (unsigned)inc);
        g = string_catn(g, s, (unsigned)inc);
-       (void) string_from_gstring(g);
-       }
       else if (inc > 0)
       else if (inc > 0)
-       if (comma)
-         g = string_append2_listele_n(g, US",\n", s, (unsigned)inc);
-       else
-         g = string_append2_listele_n(g, US"\n", s, (unsigned)inc);
+       g = string_append2_listele_n(g, comma ? US",\n" : US"\n",
+         s, (unsigned)inc);
 
 
-      if (g && g->ptr >= header_insert_maxlen) break;
+      if (gstring_length(g) >= header_insert_maxlen) break;
       }
 
 if (!found) return NULL;       /* No header found */
       }
 
 if (!found) return NULL;       /* No header found */
@@ -1658,7 +1663,7 @@ if (!g) return US"";
 
 *newsize = g->size;
 if (flags & FH_WANT_RAW)
 
 *newsize = g->size;
 if (flags & FH_WANT_RAW)
-  return g->s;
+  return string_from_gstring(g);
 
 /* Otherwise do RFC 2047 decoding, translating the charset if requested.
 The rfc2047_decode2() function can return an error with decoded data if the
 
 /* Otherwise do RFC 2047 decoding, translating the charset if requested.
 The rfc2047_decode2() function can return an error with decoded data if the
@@ -1666,16 +1671,12 @@ charset translation fails. If decoding fails, it returns NULL. */
 
 else
   {
 
 else
   {
-  uschar *decoded, *error;
-
-  decoded = rfc2047_decode2(g->s, check_rfc2047_length, charset, '?', NULL,
-    newsize, &error);
+  uschar * error, * decoded = rfc2047_decode2(string_from_gstring(g),
+    check_rfc2047_length, charset, '?', NULL, newsize, &error);
   if (error)
   if (error)
-    {
     DEBUG(D_any) debug_printf("*** error in RFC 2047 decoding: %s\n"
       "    input was: %s\n", error, g->s);
     DEBUG(D_any) debug_printf("*** error in RFC 2047 decoding: %s\n"
       "    input was: %s\n", error, g->s);
-    }
-  return decoded ? decoded : g->s;
+  return decoded ? decoded : string_from_gstring(g);
   }
 }
 
   }
 }
 
@@ -1744,6 +1745,94 @@ return g ? g->s : NULL;
 }
 
 
 }
 
 
+/*************************************************
+*               Return size of queue             *
+*************************************************/
+/* Ask the daemon for the queue size */
+
+static uschar *
+fn_queue_size(void)
+{
+struct sockaddr_un sa_un = {.sun_family = AF_UNIX};
+uschar buf[16];
+int fd;
+ssize_t len;
+const uschar * where;
+#ifndef EXIM_HAVE_ABSTRACT_UNIX_SOCKETS
+uschar * sname;
+#endif
+fd_set fds;
+struct timeval tv;
+
+if ((fd = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0)
+  {
+  DEBUG(D_expand) debug_printf(" socket: %s\n", strerror(errno));
+  return NULL;
+  }
+
+#ifdef EXIM_HAVE_ABSTRACT_UNIX_SOCKETS
+sa_un.sun_path[0] = 0; /* Abstract local socket addr - Linux-specific? */
+len = offsetof(struct sockaddr_un, sun_path) + 1
+  + snprintf(sa_un.sun_path+1, sizeof(sa_un.sun_path)-1, "exim_%d", getpid());
+#else
+sname = string_sprintf("%s/p_%d", spool_directory, getpid());
+len = offsetof(struct sockaddr_un, sun_path)
+  + snprintf(sa_un.sun_path, sizeof(sa_un.sun_path), "%s", sname);
+#endif
+
+if (bind(fd, (const struct sockaddr *)&sa_un, len) < 0)
+  { where = US"bind"; goto bad; }
+
+#ifdef notdef
+debug_printf("local addr '%s%s'\n",
+  *sa_un.sun_path ? "" : "@",
+  sa_un.sun_path + (*sa_un.sun_path ? 0 : 1));
+#endif
+
+#ifdef EXIM_HAVE_ABSTRACT_UNIX_SOCKETS
+sa_un.sun_path[0] = 0; /* Abstract local socket addr - Linux-specific? */
+len = offsetof(struct sockaddr_un, sun_path) + 1
+  + snprintf(sa_un.sun_path+1, sizeof(sa_un.sun_path)-1, "%s",
+             expand_string(notifier_socket));
+#else
+len = offsetof(struct sockaddr_un, sun_path)
+  + snprintf(sa_un.sun_path, sizeof(sa_un.sun_path), "%s",
+             expand_string(notifier_socket));
+#endif
+
+if (connect(fd, (const struct sockaddr *)&sa_un, len) < 0)
+  { where = US"connect"; goto bad2; }
+
+buf[0] = NOTIFY_QUEUE_SIZE_REQ;
+if (send(fd, buf, 1, 0) < 0) { where = US"send"; goto bad; }
+
+FD_ZERO(&fds); FD_SET(fd, &fds);
+tv.tv_sec = 2; tv.tv_usec = 0;
+if (select(fd + 1, (SELECT_ARG2_TYPE *)&fds, NULL, NULL, &tv) != 1)
+  {
+  DEBUG(D_expand) debug_printf("no daemon response; using local evaluation\n");
+  len = snprintf(CS buf, sizeof(buf), "%u", queue_count_cached());
+  }
+else if ((len = recv(fd, buf, sizeof(buf), 0)) < 0)
+  { where = US"recv"; goto bad2; }
+
+close(fd);
+#ifndef EXIM_HAVE_ABSTRACT_UNIX_SOCKETS
+Uunlink(sname);
+#endif
+return string_copyn(buf, len);
+
+bad2:
+#ifndef EXIM_HAVE_ABSTRACT_UNIX_SOCKETS
+  Uunlink(sname);
+#endif
+bad:
+  close(fd);
+  DEBUG(D_expand) debug_printf(" %s: %s\n", where, strerror(errno));
+  return NULL;
+}
+
+
 /*************************************************
 *               Find value of a variable         *
 *************************************************/
 /*************************************************
 *               Find value of a variable         *
 *************************************************/
@@ -1956,7 +2045,7 @@ switch (vp->type)
     s = find_header(US"reply-to:", newsize,
                exists_only ? FH_EXISTS_ONLY|FH_WANT_RAW : FH_WANT_RAW,
                headers_charset);
     s = find_header(US"reply-to:", newsize,
                exists_only ? FH_EXISTS_ONLY|FH_WANT_RAW : FH_WANT_RAW,
                headers_charset);
-    if (s) while (isspace(*s)) s++;
+    if (s) Uskip_whitespace(&s);
     if (!s || !*s)
       {
       *newsize = 0;                            /* For the *s==0 case */
     if (!s || !*s)
       {
       *newsize = 0;                            /* For the *s==0 case */
@@ -1967,8 +2056,8 @@ switch (vp->type)
     if (s)
       {
       uschar *t;
     if (s)
       {
       uschar *t;
-      while (isspace(*s)) s++;
-      for (t = s; *t != 0; t++) if (*t == '\n') *t = ' ';
+      Uskip_whitespace(&s);
+      for (t = s; *t; t++) if (*t == '\n') *t = ' ';
       while (t > s && isspace(t[-1])) t--;
       *t = 0;
       }
       while (t > s && isspace(t[-1])) t--;
       *t = 0;
       }
@@ -2056,7 +2145,7 @@ read_subs(uschar **sub, int n, int m, const uschar **sptr, BOOL skipping,
 {
 const uschar *s = *sptr;
 
 {
 const uschar *s = *sptr;
 
-while (isspace(*s)) s++;
+Uskip_whitespace(&s);
 for (int i = 0; i < n; i++)
   {
   if (*s != '{')
 for (int i = 0; i < n; i++)
   {
   if (*s != '{')
@@ -2073,7 +2162,7 @@ for (int i = 0; i < n; i++)
   if (!(sub[i] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, resetok)))
     return 3;
   if (*s++ != '}') return 1;
   if (!(sub[i] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, resetok)))
     return 3;
   if (*s++ != '}') return 1;
-  while (isspace(*s)) s++;
+  Uskip_whitespace(&s);
   }
 if (check_end && *s++ != '}')
   {
   }
 if (check_end && *s++ != '}')
   {
@@ -2196,9 +2285,7 @@ uschar * p = s;
 unsigned depth = 0;
 BOOL quotesmode = wrap[0] == wrap[1];
 
 unsigned depth = 0;
 BOOL quotesmode = wrap[0] == wrap[1];
 
-while (isspace(*p)) p++;
-
-if (*p == *wrap)
+if (Uskip_whitespace(&p) == *wrap)
   {
   s = ++p;
   wrap++;
   {
   s = ++p;
   wrap++;
@@ -2235,7 +2322,7 @@ json_nextinlist(const uschar ** list)
 unsigned array_depth = 0, object_depth = 0;
 const uschar * s = *list, * item;
 
 unsigned array_depth = 0, object_depth = 0;
 const uschar * s = *list, * item;
 
-while (isspace(*s)) s++;
+skip_whitespace(&s);
 
 for (item = s;
      *s && (*s != ',' || array_depth != 0 || object_depth != 0);
 
 for (item = s;
      *s && (*s != ',' || array_depth != 0 || object_depth != 0);
@@ -2465,10 +2552,7 @@ const pcre *re;
 const uschar *rerror;
 
 for (;;)
 const uschar *rerror;
 
 for (;;)
-  {
-  while (isspace(*s)) s++;
-  if (*s == '!') { testfor = !testfor; s++; } else break;
-  }
+  if (Uskip_whitespace(&s) == '!') { testfor = !testfor; s++; } else break;
 
 switch(cond_type = identify_operator(&s, &opname))
   {
 
 switch(cond_type = identify_operator(&s, &opname))
   {
@@ -2557,8 +2641,7 @@ switch(cond_type = identify_operator(&s, &opname))
   case ECOND_LDAPAUTH:
   case ECOND_PWCHECK:
 
   case ECOND_LDAPAUTH:
   case ECOND_PWCHECK:
 
-  while (isspace(*s)) s++;
-  if (*s != '{') goto COND_FAILED_CURLY_START;         /* }-for-text-editors */
+  if (Uskip_whitespace(&s) != '{') goto COND_FAILED_CURLY_START; /* }-for-text-editors */
 
   sub[0] = expand_string_internal(s+1, TRUE, &s, yield == NULL, TRUE, resetok);
   if (!sub[0]) return NULL;
 
   sub[0] = expand_string_internal(s+1, TRUE, &s, yield == NULL, TRUE, resetok);
   if (!sub[0]) return NULL;
@@ -2655,11 +2738,11 @@ switch(cond_type = identify_operator(&s, &opname))
     uschar *user_msg;
     BOOL cond = FALSE;
 
     uschar *user_msg;
     BOOL cond = FALSE;
 
-    while (isspace(*s)) s++;
+    Uskip_whitespace(&s);
     if (*s++ != '{') goto COND_FAILED_CURLY_START;     /*}*/
 
     switch(read_subs(sub, nelem(sub), 1,
     if (*s++ != '{') goto COND_FAILED_CURLY_START;     /*}*/
 
     switch(read_subs(sub, nelem(sub), 1,
-      &s, yield == NULL, TRUE, US"acl", resetok))
+      &s, yield == NULL, TRUE, name, resetok))
       {
       case 1: expand_string_message = US"too few arguments or bracketing "
         "error for acl";
       {
       case 1: expand_string_message = US"too few arguments or bracketing "
         "error for acl";
@@ -2708,9 +2791,9 @@ switch(cond_type = identify_operator(&s, &opname))
 #else
     {
     uschar *sub[4];
 #else
     {
     uschar *sub[4];
-    while (isspace(*s)) s++;
+    Uskip_whitespace(&s);
     if (*s++ != '{') goto COND_FAILED_CURLY_START;     /* }-for-text-editors */
     if (*s++ != '{') goto COND_FAILED_CURLY_START;     /* }-for-text-editors */
-    switch(read_subs(sub, nelem(sub), 2, &s, yield == NULL, TRUE, US"saslauthd",
+    switch(read_subs(sub, nelem(sub), 2, &s, yield == NULL, TRUE, name,
                    resetok))
       {
       case 1: expand_string_message = US"too few arguments or bracketing "
                    resetok))
       {
       case 1: expand_string_message = US"too few arguments or bracketing "
@@ -2786,8 +2869,7 @@ switch(cond_type = identify_operator(&s, &opname))
     if ((i > 0) && !sub2_honour_dollar)
       honour_dollar = FALSE;
 
     if ((i > 0) && !sub2_honour_dollar)
       honour_dollar = FALSE;
 
-    while (isspace(*s)) s++;
-    if (*s != '{')
+    if (Uskip_whitespace(&s) != '{')
       {
       if (i == 0) goto COND_FAILED_CURLY_START;
       expand_string_message = string_sprintf("missing 2nd string in {} "
       {
       if (i == 0) goto COND_FAILED_CURLY_START;
       expand_string_message = string_sprintf("missing 2nd string in {} "
@@ -2890,7 +2972,7 @@ switch(cond_type = identify_operator(&s, &opname))
     break;
 
     case ECOND_MATCH:   /* Regular expression match */
     break;
 
     case ECOND_MATCH:   /* Regular expression match */
-    if (!(re = pcre_compile(CS sub[1], PCRE_COPT, (const char **)&rerror,
+    if (!(re = pcre_compile(CS sub[1], PCRE_COPT, CCSS &rerror,
                            &roffset, NULL)))
       {
       expand_string_message = string_sprintf("regular expression error in "
                            &roffset, NULL)))
       {
       expand_string_message = string_sprintf("regular expression error in "
@@ -3148,14 +3230,13 @@ switch(cond_type = identify_operator(&s, &opname))
   subcondptr = (yield == NULL) ? NULL : &tempcond;
   combined_cond = (cond_type == ECOND_AND);
 
   subcondptr = (yield == NULL) ? NULL : &tempcond;
   combined_cond = (cond_type == ECOND_AND);
 
-  while (isspace(*s)) s++;
+  Uskip_whitespace(&s);
   if (*s++ != '{') goto COND_FAILED_CURLY_START;       /* }-for-text-editors */
 
   for (;;)
     {
   if (*s++ != '{') goto COND_FAILED_CURLY_START;       /* }-for-text-editors */
 
   for (;;)
     {
-    while (isspace(*s)) s++;
     /* {-for-text-editors */
     /* {-for-text-editors */
-    if (*s == '}') break;
+    if (Uskip_whitespace(&s) == '}') break;
     if (*s != '{')                                     /* }-for-text-editors */
       {
       expand_string_message = string_sprintf("each subcondition "
     if (*s != '{')                                     /* }-for-text-editors */
       {
       expand_string_message = string_sprintf("each subcondition "
@@ -3169,7 +3250,7 @@ switch(cond_type = identify_operator(&s, &opname))
         expand_string_message, opname);
       return NULL;
       }
         expand_string_message, opname);
       return NULL;
       }
-    while (isspace(*s)) s++;
+    Uskip_whitespace(&s);
 
     /* {-for-text-editors */
     if (*s++ != '}')
 
     /* {-for-text-editors */
     if (*s++ != '}')
@@ -3214,14 +3295,14 @@ switch(cond_type = identify_operator(&s, &opname))
 
     DEBUG(D_expand) debug_printf_indent("condition: %s\n", opname);
 
 
     DEBUG(D_expand) debug_printf_indent("condition: %s\n", opname);
 
-    while (isspace(*s)) s++;
+    Uskip_whitespace(&s);
     if (*s++ != '{') goto COND_FAILED_CURLY_START;     /* }-for-text-editors */
     if (!(sub[0] = expand_string_internal(s, TRUE, &s, yield == NULL, TRUE, resetok)))
       return NULL;
     /* {-for-text-editors */
     if (*s++ != '}') goto COND_FAILED_CURLY_END;
 
     if (*s++ != '{') goto COND_FAILED_CURLY_START;     /* }-for-text-editors */
     if (!(sub[0] = expand_string_internal(s, TRUE, &s, yield == NULL, TRUE, resetok)))
       return NULL;
     /* {-for-text-editors */
     if (*s++ != '}') goto COND_FAILED_CURLY_END;
 
-    while (isspace(*s)) s++;
+    Uskip_whitespace(&s);
     if (*s++ != '{') goto COND_FAILED_CURLY_START;     /* }-for-text-editors */
 
     sub[1] = s;
     if (*s++ != '{') goto COND_FAILED_CURLY_START;     /* }-for-text-editors */
 
     sub[1] = s;
@@ -3236,7 +3317,7 @@ switch(cond_type = identify_operator(&s, &opname))
         expand_string_message, opname);
       return NULL;
       }
         expand_string_message, opname);
       return NULL;
       }
-    while (isspace(*s)) s++;
+    Uskip_whitespace(&s);
 
     /* {-for-text-editors */
     if (*s++ != '}')
 
     /* {-for-text-editors */
     if (*s++ != '}')
@@ -3301,8 +3382,8 @@ switch(cond_type = identify_operator(&s, &opname))
     uschar *ourname;
     size_t len;
     BOOL boolvalue = FALSE;
     uschar *ourname;
     size_t len;
     BOOL boolvalue = FALSE;
-    while (isspace(*s)) s++;
-    if (*s != '{') goto COND_FAILED_CURLY_START;       /* }-for-text-editors */
+
+    if (Uskip_whitespace(&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))
       {
     ourname = cond_type == ECOND_BOOL_LAX ? US"bool_lax" : US"bool";
     switch(read_subs(sub_arg, 1, 1, &s, yield == NULL, FALSE, ourname, resetok))
       {
@@ -3314,9 +3395,8 @@ switch(cond_type = identify_operator(&s, &opname))
       case 3: return NULL;
       }
     t = sub_arg[0];
       case 3: return NULL;
       }
     t = sub_arg[0];
-    while (isspace(*t)) t++;
-    len = Ustrlen(t);
-    if (len)
+    Uskip_whitespace(&t);
+    if ((len = Ustrlen(t)))
       {
       /* trailing whitespace: seems like a good idea to ignore it too */
       t2 = t + len - 1;
       {
       /* trailing whitespace: seems like a good idea to ignore it too */
       t2 = t + len - 1;
@@ -3372,7 +3452,7 @@ switch(cond_type = identify_operator(&s, &opname))
     uschar cksum[4];
     BOOL boolvalue = FALSE;
 
     uschar cksum[4];
     BOOL boolvalue = FALSE;
 
-    switch(read_subs(sub, 2, 2, CUSS &s, yield == NULL, FALSE, US"inbound_srs", resetok))
+    switch(read_subs(sub, 2, 2, CUSS &s, yield == NULL, FALSE, name, resetok))
       {
       case 1: expand_string_message = US"too few arguments or bracketing "
        "error for inbound_srs";
       {
       case 1: expand_string_message = US"too few arguments or bracketing "
        "error for inbound_srs";
@@ -3586,8 +3666,7 @@ lookups and for extractions in the success case. For the ${if item, the string
 "true" is substituted. In the fail case, nothing is substituted for all three
 items. */
 
 "true" is substituted. In the fail case, nothing is substituted for all three
 items. */
 
-while (isspace(*s)) s++;
-if (*s == '}')
+if (skip_whitespace(&s) == '}')
   {
   if (type[0] == 'i')
     {
   {
   if (type[0] == 'i')
     {
@@ -3643,8 +3722,7 @@ time, forced failures are noticed only if we want the second string. We must
 set skipping in the nested call if we don't want this string, or if we were
 already skipping. */
 
 set skipping in the nested call if we don't want this string, or if we were
 already skipping. */
 
-while (isspace(*s)) s++;
-if (*s == '{')
+if (skip_whitespace(&s) == '{')
   {
   sub2 = expand_string_internal(s+1, TRUE, &s, yes || skipping, TRUE, resetok);
   if (sub2 == NULL && (!yes || !f.expand_string_forcedfail)) goto FAILED;
   {
   sub2 = expand_string_internal(s+1, TRUE, &s, yes || skipping, TRUE, resetok);
   if (sub2 == NULL && (!yes || !f.expand_string_forcedfail)) goto FAILED;
@@ -3675,7 +3753,7 @@ else if (*s != '}')
     {
     if (!yes && !skipping)
       {
     {
     if (!yes && !skipping)
       {
-      while (isspace(*s)) s++;
+      Uskip_whitespace(&s);
       if (*s++ != '}')
         {
        errwhere = US"did not close with '}' after forcedfail";
       if (*s++ != '}')
         {
        errwhere = US"did not close with '}' after forcedfail";
@@ -3697,7 +3775,7 @@ else if (*s != '}')
 
 /* All we have to do now is to check on the final closing brace. */
 
 
 /* All we have to do now is to check on the final closing brace. */
 
-while (isspace(*s)) s++;
+skip_whitespace(&s);
 if (*s++ != '}')
   {
   errwhere = US"did not close with '}'";
 if (*s++ != '}')
   {
   errwhere = US"did not close with '}'";
@@ -3923,7 +4001,7 @@ if (!*error)
     if (*s != ')')
       *error = US"expecting closing parenthesis";
     else
     if (*s != ')')
       *error = US"expecting closing parenthesis";
     else
-      while (isspace(*(++s)));
+      while (isspace(*++s));
   else if (*s)
     *error = US"expecting operator";
 *sptr = s;
   else if (*s)
     *error = US"expecting operator";
 *sptr = s;
@@ -3938,8 +4016,7 @@ int c;
 int_eximarith_t n;
 uschar *s = *sptr;
 
 int_eximarith_t n;
 uschar *s = *sptr;
 
-while (isspace(*s)) s++;
-if (isdigit((c = *s)))
+if (isdigit((c = Uskip_whitespace(&s))))
   {
   int count;
   (void)sscanf(CS s, (decimal? SC_EXIM_DEC "%n" : SC_EXIM_ARITH "%n"), &n, &count);
   {
   int count;
   (void)sscanf(CS s, (decimal? SC_EXIM_DEC "%n" : SC_EXIM_ARITH "%n"), &n, &count);
@@ -3951,7 +4028,7 @@ if (isdigit((c = *s)))
     case 'm': n *= 1024*1024; s++; break;
     case 'g': n *= 1024*1024*1024; s++; break;
     }
     case 'm': n *= 1024*1024; s++; break;
     case 'g': n *= 1024*1024*1024; s++; break;
     }
-  while (isspace (*s)) s++;
+  Uskip_whitespace(&s);
   }
 else if (c == '(')
   {
   }
 else if (c == '(')
   {
@@ -3973,7 +4050,7 @@ eval_op_unary(uschar **sptr, BOOL decimal, uschar **error)
 {
 uschar *s = *sptr;
 int_eximarith_t x;
 {
 uschar *s = *sptr;
 int_eximarith_t x;
-while (isspace(*s)) s++;
+Uskip_whitespace(&s);
 if (*s == '+' || *s == '-' || *s == '~')
   {
   int op = *s++;
 if (*s == '+' || *s == '-' || *s == '~')
   {
   int op = *s++;
@@ -4515,7 +4592,7 @@ while (*s != 0)
       uschar *user_msg;
       int rc;
 
       uschar *user_msg;
       int rc;
 
-      switch(read_subs(sub, nelem(sub), 1, &s, skipping, TRUE, US"acl",
+      switch(read_subs(sub, nelem(sub), 1, &s, skipping, TRUE, name,
                      &resetok))
         {
         case 1: goto EXPAND_FAILED_CURLY;
                      &resetok))
         {
         case 1: goto EXPAND_FAILED_CURLY;
@@ -4592,7 +4669,7 @@ while (*s != 0)
       int save_expand_nmax =
         save_expand_strings(save_expand_nstring, save_expand_nlength);
 
       int save_expand_nmax =
         save_expand_strings(save_expand_nstring, save_expand_nlength);
 
-      while (isspace(*s)) s++;
+      Uskip_whitespace(&s);
       if (!(next_s = eval_condition(s, &resetok, skipping ? NULL : &cond)))
        goto EXPAND_FAILED;  /* message already set */
 
       if (!(next_s = eval_condition(s, &resetok, skipping ? NULL : &cond)))
        goto EXPAND_FAILED;  /* message already set */
 
@@ -4691,7 +4768,7 @@ while (*s != 0)
       int expand_setup = 0;
       int nameptr = 0;
       uschar *key, *filename;
       int expand_setup = 0;
       int nameptr = 0;
       uschar *key, *filename;
-      const uschar *affix;
+      const uschar * affix, * opts;
       uschar *save_lookup_value = lookup_value;
       int save_expand_nmax =
         save_expand_strings(save_expand_nstring, save_expand_nlength);
       uschar *save_lookup_value = lookup_value;
       int save_expand_nmax =
         save_expand_strings(save_expand_nstring, save_expand_nlength);
@@ -4705,8 +4782,7 @@ while (*s != 0)
       /* Get the key we are to look up for single-key+file style lookups.
       Otherwise set the key NULL pro-tem. */
 
       /* Get the key we are to look up for single-key+file style lookups.
       Otherwise set the key NULL pro-tem. */
 
-      while (isspace(*s)) s++;
-      if (*s == '{')                                   /*}*/
+      if (Uskip_whitespace(&s) == '{')                                 /*}*/
         {
         key = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok);
         if (!key) goto EXPAND_FAILED;                  /*{{*/
         {
         key = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok);
         if (!key) goto EXPAND_FAILED;                  /*{{*/
@@ -4715,7 +4791,7 @@ while (*s != 0)
          expand_string_message = US"missing '}' after lookup key";
          goto EXPAND_FAILED_CURLY;
          }
          expand_string_message = US"missing '}' after lookup key";
          goto EXPAND_FAILED_CURLY;
          }
-        while (isspace(*s)) s++;
+        Uskip_whitespace(&s);
         }
       else key = NULL;
 
         }
       else key = NULL;
 
@@ -4731,20 +4807,19 @@ while (*s != 0)
       kinds. Allow everything except space or { to appear; the actual content
       is checked by search_findtype_partial. */                /*}*/
 
       kinds. Allow everything except space or { to appear; the actual content
       is checked by search_findtype_partial. */                /*}*/
 
-      while (*s != 0 && *s != '{' && !isspace(*s))     /*}*/
+      while (*s && *s != '{' && !isspace(*s))          /*}*/
         {
         if (nameptr < sizeof(name) - 1) name[nameptr++] = *s;
         s++;
         }
         {
         if (nameptr < sizeof(name) - 1) name[nameptr++] = *s;
         s++;
         }
-      name[nameptr] = 0;
-      while (isspace(*s)) s++;
+      name[nameptr] = '\0';
+      Uskip_whitespace(&s);
 
       /* Now check for the individual search type and any partial or default
       options. Only those types that are actually in the binary are valid. */
 
 
       /* Now check for the individual search type and any partial or default
       options. Only those types that are actually in the binary are valid. */
 
-      stype = search_findtype_partial(name, &partial, &affix, &affixlen,
-        &starflags);
-      if (stype < 0)
+      if ((stype = search_findtype_partial(name, &partial, &affix, &affixlen,
+         &starflags, &opts)) < 0)
         {
         expand_string_message = search_error_message;
         goto EXPAND_FAILED;
         {
         expand_string_message = search_error_message;
         goto EXPAND_FAILED;
@@ -4789,7 +4864,7 @@ while (*s != 0)
        expand_string_message = US"missing '}' closing lookup file-or-query arg";
        goto EXPAND_FAILED_CURLY;
        }
        expand_string_message = US"missing '}' closing lookup file-or-query arg";
        goto EXPAND_FAILED_CURLY;
        }
-      while (isspace(*s)) s++;
+      Uskip_whitespace(&s);
 
       /* If this isn't a single-key+file lookup, re-arrange the variables
       to be appropriate for the search_ functions. For query-style lookups,
 
       /* If this isn't a single-key+file lookup, re-arrange the variables
       to be appropriate for the search_ functions. For query-style lookups,
@@ -4798,22 +4873,19 @@ while (*s != 0)
 
       if (!key)
         {
 
       if (!key)
         {
-        while (isspace(*filename)) filename++;
+       Uskip_whitespace(&filename);
         key = filename;
 
         if (mac_islookup(stype, lookup_querystyle))
           filename = NULL;
         else
         key = filename;
 
         if (mac_islookup(stype, lookup_querystyle))
           filename = NULL;
         else
-          {
-          if (*filename != '/')
-            {
-            expand_string_message = string_sprintf(
-              "absolute file name expected for \"%s\" lookup", name);
-            goto EXPAND_FAILED;
-            }
-          while (*key != 0 && !isspace(*key)) key++;
-          if (*key != 0) *key++ = 0;
-          }
+          if (*filename == '/')
+           {
+           while (*key && !isspace(*key)) key++;
+           if (*key) *key++ = '\0';
+           }
+         else
+           filename = NULL;
         }
 
       /* If skipping, don't do the next bit - just lookup_value == NULL, as if
         }
 
       /* If skipping, don't do the next bit - just lookup_value == NULL, as if
@@ -4840,7 +4912,7 @@ while (*s != 0)
           goto EXPAND_FAILED;
           }
         lookup_value = search_find(handle, filename, key, partial, affix,
           goto EXPAND_FAILED;
           }
         lookup_value = search_find(handle, filename, key, partial, affix,
-          affixlen, starflags, &expand_setup);
+          affixlen, starflags, &expand_setup, opts);
         if (f.search_find_defer)
           {
           expand_string_message =
         if (f.search_find_defer)
           {
           expand_string_message =
@@ -4900,7 +4972,7 @@ while (*s != 0)
         }
 
       switch(read_subs(sub_arg, EXIM_PERL_MAX_ARGS + 1, 1, &s, skipping, TRUE,
         }
 
       switch(read_subs(sub_arg, EXIM_PERL_MAX_ARGS + 1, 1, &s, skipping, TRUE,
-           US"perl", &resetok))
+           name, &resetok))
         {
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:
         {
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:
@@ -4971,7 +5043,7 @@ while (*s != 0)
       uschar *sub_arg[3];
       uschar *p,*domain;
 
       uschar *sub_arg[3];
       uschar *p,*domain;
 
-      switch(read_subs(sub_arg, 3, 2, &s, skipping, TRUE, US"prvs", &resetok))
+      switch(read_subs(sub_arg, 3, 2, &s, skipping, TRUE, name, &resetok))
         {
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:
         {
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:
@@ -5045,7 +5117,7 @@ while (*s != 0)
       prvscheck_address = NULL;
       prvscheck_keynum = NULL;
 
       prvscheck_address = NULL;
       prvscheck_keynum = NULL;
 
-      switch(read_subs(sub_arg, 1, 1, &s, skipping, FALSE, US"prvs", &resetok))
+      switch(read_subs(sub_arg, 1, 1, &s, skipping, FALSE, name, &resetok))
         {
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:
         {
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:
@@ -5077,7 +5149,7 @@ while (*s != 0)
         prvscheck_keynum = string_copy(key_num);
 
         /* Now expand the second argument */
         prvscheck_keynum = string_copy(key_num);
 
         /* Now expand the second argument */
-        switch(read_subs(sub_arg, 1, 1, &s, skipping, FALSE, US"prvs", &resetok))
+        switch(read_subs(sub_arg, 1, 1, &s, skipping, FALSE, name, &resetok))
           {
           case 1: goto EXPAND_FAILED_CURLY;
           case 2:
           {
           case 1: goto EXPAND_FAILED_CURLY;
           case 2:
@@ -5131,7 +5203,7 @@ while (*s != 0)
         /* Now expand the final argument. We leave this till now so that
         it can include $prvscheck_result. */
 
         /* 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", &resetok))
+        switch(read_subs(sub_arg, 1, 0, &s, skipping, TRUE, name, &resetok))
           {
           case 1: goto EXPAND_FAILED_CURLY;
           case 2:
           {
           case 1: goto EXPAND_FAILED_CURLY;
           case 2:
@@ -5152,7 +5224,7 @@ while (*s != 0)
            We need to make sure all subs are expanded first, so as to skip over
            the entire item. */
 
            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", &resetok))
+        switch(read_subs(sub_arg, 2, 1, &s, skipping, TRUE, name, &resetok))
           {
           case 1: goto EXPAND_FAILED_CURLY;
           case 2:
           {
           case 1: goto EXPAND_FAILED_CURLY;
           case 2:
@@ -5175,7 +5247,7 @@ while (*s != 0)
         goto EXPAND_FAILED;
         }
 
         goto EXPAND_FAILED;
         }
 
-      switch(read_subs(sub_arg, 2, 1, &s, skipping, TRUE, US"readfile", &resetok))
+      switch(read_subs(sub_arg, 2, 1, &s, skipping, TRUE, name, &resetok))
         {
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:
         {
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:
@@ -5206,7 +5278,7 @@ while (*s != 0)
       {
       client_conn_ctx cctx;
       int timeout = 5;
       {
       client_conn_ctx cctx;
       int timeout = 5;
-      int save_ptr = yield->ptr;
+      int save_ptr = gstring_length(yield);
       FILE * fp = NULL;
       uschar * arg;
       uschar * sub_arg[4];
       FILE * fp = NULL;
       uschar * arg;
       uschar * sub_arg[4];
@@ -5225,7 +5297,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. */
 
       /* 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", &resetok))
+      switch(read_subs(sub_arg, 4, 2, &s, skipping, FALSE, name, &resetok))
         {
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:                             /* Won't occur: no end check */
         {
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:                             /* Won't occur: no end check */
@@ -5239,7 +5311,7 @@ while (*s != 0)
 
       /* Sort out timeout, if given.  The second arg is a list with the first element
       being a time value.  Any more are options of form "name=value".  Currently the
 
       /* Sort out timeout, if given.  The second arg is a list with the first element
       being a time value.  Any more are options of form "name=value".  Currently the
-      only option recognised is "shutdown". */
+      only options recognised are "shutdown" and "tls". */
 
       if (sub_arg[2])
         {
 
       if (sub_arg[2])
         {
@@ -5247,8 +5319,9 @@ while (*s != 0)
        uschar * item;
        int sep = 0;
 
        uschar * item;
        int sep = 0;
 
-       item = string_nextinlist(&list, &sep, NULL, 0);
-        if ((timeout = readconf_readtime(item, 0, FALSE)) < 0)
+       if (  !(item = string_nextinlist(&list, &sep, NULL, 0))
+          || !*item
+          || (timeout = readconf_readtime(item, 0, FALSE)) < 0)
           {
           expand_string_message = string_sprintf("bad time value %s", item);
           goto EXPAND_FAILED;
           {
           expand_string_message = string_sprintf("bad time value %s", item);
           goto EXPAND_FAILED;
@@ -5439,7 +5512,7 @@ while (*s != 0)
 
         if (sigalrm_seen)
           {
 
         if (sigalrm_seen)
           {
-          yield->ptr = save_ptr;
+          if (yield) yield->ptr = save_ptr;
           expand_string_message = US "socket read timed out";
           goto SOCK_FAIL;
           }
           expand_string_message = US "socket read timed out";
           goto SOCK_FAIL;
           }
@@ -5457,7 +5530,7 @@ while (*s != 0)
          expand_string_message = US"missing '}' closing failstring for readsocket";
          goto EXPAND_FAILED_CURLY;
          }
          expand_string_message = US"missing '}' closing failstring for readsocket";
          goto EXPAND_FAILED_CURLY;
          }
-        while (isspace(*s)) s++;
+        Uskip_whitespace(&s);
         }
 
     READSOCK_DONE:
         }
 
     READSOCK_DONE:
@@ -5483,7 +5556,7 @@ while (*s != 0)
        expand_string_message = US"missing '}' closing failstring for readsocket";
        goto EXPAND_FAILED_CURLY;
        }
        expand_string_message = US"missing '}' closing failstring for readsocket";
        goto EXPAND_FAILED_CURLY;
        }
-      while (isspace(*s)) s++;
+      Uskip_whitespace(&s);
       goto READSOCK_DONE;
       }
 
       goto READSOCK_DONE;
       }
 
@@ -5503,7 +5576,7 @@ while (*s != 0)
         goto EXPAND_FAILED;
         }
 
         goto EXPAND_FAILED;
         }
 
-      while (isspace(*s)) s++;
+      Uskip_whitespace(&s);
       if (*s != '{')
         {
        expand_string_message = US"missing '{' for command arg of run";
       if (*s != '{')
         {
        expand_string_message = US"missing '{' for command arg of run";
@@ -5511,7 +5584,7 @@ while (*s != 0)
        }
       if (!(arg = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok)))
        goto EXPAND_FAILED;
        }
       if (!(arg = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok)))
        goto EXPAND_FAILED;
-      while (isspace(*s)) s++;
+      Uskip_whitespace(&s);
       if (*s++ != '}')
         {
        expand_string_message = US"missing '}' closing command arg of run";
       if (*s++ != '}')
         {
        expand_string_message = US"missing '}' closing command arg of run";
@@ -5536,7 +5609,8 @@ while (*s != 0)
 
         /* Create the child process, making it a group leader. */
 
 
         /* Create the child process, making it a group leader. */
 
-        if ((pid = child_open(USS argv, NULL, 0077, &fd_in, &fd_out, TRUE)) < 0)
+        if ((pid = child_open(USS argv, NULL, 0077, &fd_in, &fd_out, TRUE,
+                             US"expand-run")) < 0)
           {
           expand_string_message =
             string_sprintf("couldn't create child process: %s", strerror(errno));
           {
           expand_string_message =
             string_sprintf("couldn't create child process: %s", strerror(errno));
@@ -5606,11 +5680,11 @@ while (*s != 0)
 
     case EITEM_TR:
       {
 
     case EITEM_TR:
       {
-      int oldptr = yield->ptr;
+      int oldptr = gstring_length(yield);
       int o2m;
       uschar *sub[3];
 
       int o2m;
       uschar *sub[3];
 
-      switch(read_subs(sub, 3, 3, &s, skipping, TRUE, US"tr", &resetok))
+      switch(read_subs(sub, 3, 3, &s, skipping, TRUE, name, &resetok))
         {
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:
         {
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:
@@ -5822,7 +5896,7 @@ while (*s != 0)
       int save_expand_nmax =
         save_expand_strings(save_expand_nstring, save_expand_nlength);
 
       int save_expand_nmax =
         save_expand_strings(save_expand_nstring, save_expand_nlength);
 
-      switch(read_subs(sub, 3, 3, &s, skipping, TRUE, US"sg", &resetok))
+      switch(read_subs(sub, 3, 3, &s, skipping, TRUE, name, &resetok))
         {
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:
         {
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:
@@ -5831,7 +5905,7 @@ while (*s != 0)
 
       /* Compile the regular expression */
 
 
       /* Compile the regular expression */
 
-      if (!(re = pcre_compile(CS sub[1], PCRE_COPT, (const char **)&rerror,
+      if (!(re = pcre_compile(CS sub[1], PCRE_COPT, CCSS &rerror,
                              &roffset, NULL)))
         {
         expand_string_message = string_sprintf("regular expression error in "
                              &roffset, NULL)))
         {
         expand_string_message = string_sprintf("regular expression error in "
@@ -5936,11 +6010,9 @@ while (*s != 0)
 
       enum {extract_basic, extract_json, extract_jsons} fmt = extract_basic;
 
 
       enum {extract_basic, extract_json, extract_jsons} fmt = extract_basic;
 
-      while (isspace(*s)) s++;
-
       /* Check for a format-variant specifier */
 
       /* Check for a format-variant specifier */
 
-      if (*s != '{')                                   /*}*/
+      if (Uskip_whitespace(&s) != '{')                                 /*}*/
        if (Ustrncmp(s, "json", 4) == 0)
          if (*(s += 4) == 's')
            {fmt = extract_jsons; s++;}
        if (Ustrncmp(s, "json", 4) == 0)
          if (*(s += 4) == 's')
            {fmt = extract_jsons; s++;}
@@ -5962,14 +6034,14 @@ while (*s != 0)
            expand_string_message = US"missing '{' for arg of extract";
            goto EXPAND_FAILED_CURLY;
            }
            expand_string_message = US"missing '{' for arg of extract";
            goto EXPAND_FAILED_CURLY;
            }
-         while (isspace(*s)) s++;
+         Uskip_whitespace(&s);
          }
        if (  Ustrncmp(s, "fail", 4) == 0                       /*'{'*/
           && (s[4] == '}' || s[4] == ' ' || s[4] == '\t' || !s[4])
           )
          {
          s += 4;
          }
        if (  Ustrncmp(s, "fail", 4) == 0                       /*'{'*/
           && (s[4] == '}' || s[4] == ' ' || s[4] == '\t' || !s[4])
           )
          {
          s += 4;
-         while (isspace(*s)) s++;
+         Uskip_whitespace(&s);
          }                                                     /*'{'*/
        if (*s != '}')
          {
          }                                                     /*'{'*/
        if (*s != '}')
          {
@@ -5980,8 +6052,7 @@ while (*s != 0)
 
       else for (int i = 0, j = 2; i < j; i++) /* Read the proper number of arguments */
         {
 
       else for (int i = 0, j = 2; i < j; i++) /* Read the proper number of arguments */
         {
-       while (isspace(*s)) s++;
-        if (*s == '{')                                                 /*'}'*/
+       if (Uskip_whitespace(&s) == '{')                                                /*'}'*/
           {
           if (!(sub[i] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok)))
            goto EXPAND_FAILED;                                 /*'{'*/
           {
           if (!(sub[i] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok)))
            goto EXPAND_FAILED;                                 /*'{'*/
@@ -6003,7 +6074,7 @@ while (*s != 0)
             int x = 0;
             uschar *p = sub[0];
 
             int x = 0;
             uschar *p = sub[0];
 
-            while (isspace(*p)) p++;
+            Uskip_whitespace(&p);
             sub[0] = p;
 
             len = Ustrlen(p);
             sub[0] = p;
 
             len = Ustrlen(p);
@@ -6105,15 +6176,14 @@ while (*s != 0)
              if (Ustrcmp(item, sub[0]) == 0)   /*XXX should be a UTF8-compare */
                {
                s = item + Ustrlen(item) + 1;
              if (Ustrcmp(item, sub[0]) == 0)   /*XXX should be a UTF8-compare */
                {
                s = item + Ustrlen(item) + 1;
-               while (isspace(*s)) s++;
-               if (*s != ':')
+               if (Uskip_whitespace(&s) != ':')
                  {
                  expand_string_message =
                    US"missing object value-separator for extract json";
                  goto EXPAND_FAILED_CURLY;
                  }
                s++;
                  {
                  expand_string_message =
                    US"missing object value-separator for extract json";
                  goto EXPAND_FAILED_CURLY;
                  }
                s++;
-               while (isspace(*s)) s++;
+               Uskip_whitespace(&s);
                lookup_value = s;
                break;
                }
                lookup_value = s;
                break;
                }
@@ -6171,8 +6241,7 @@ while (*s != 0)
 
       for (int i = 0; i < 2; i++)
         {
 
       for (int i = 0; i < 2; i++)
         {
-        while (isspace(*s)) s++;
-        if (*s != '{')                                 /*'}'*/
+        if (Uskip_whitespace(&s) != '{')                                       /*'}'*/
          {
          expand_string_message = string_sprintf(
            "missing '{' for arg %d of listextract", i+1);
          {
          expand_string_message = string_sprintf(
            "missing '{' for arg %d of listextract", i+1);
@@ -6197,7 +6266,7 @@ while (*s != 0)
          int x = 0;
          uschar *p = sub[0];
 
          int x = 0;
          uschar *p = sub[0];
 
-         while (isspace(*p)) p++;
+         Uskip_whitespace(&p);
          sub[0] = p;
 
          len = Ustrlen(p);
          sub[0] = p;
 
          len = Ustrlen(p);
@@ -6256,6 +6325,23 @@ while (*s != 0)
       continue;
       }
 
       continue;
       }
 
+    case EITEM_LISTQUOTE:
+      {
+      uschar * sub[2];
+      switch(read_subs(sub, 2, 2, &s, skipping, TRUE, name, &resetok))
+        {
+        case 1: goto EXPAND_FAILED_CURLY;
+        case 2:
+        case 3: goto EXPAND_FAILED;
+        }
+      for (uschar sep = *sub[0], c; c = *sub[1]; sub[1]++)
+       {
+       if (c == sep) yield = string_catn(yield, sub[1], 1);
+       yield = string_catn(yield, sub[1], 1);
+       }
+      continue;
+      }
+
 #ifndef DISABLE_TLS
     case EITEM_CERTEXTRACT:
       {
 #ifndef DISABLE_TLS
     case EITEM_CERTEXTRACT:
       {
@@ -6265,8 +6351,7 @@ while (*s != 0)
         save_expand_strings(save_expand_nstring, save_expand_nlength);
 
       /* Read the field argument */
         save_expand_strings(save_expand_nstring, save_expand_nlength);
 
       /* Read the field argument */
-      while (isspace(*s)) s++;
-      if (*s != '{')                                   /*}*/
+      if (Uskip_whitespace(&s) != '{')                                 /*}*/
        {
        expand_string_message = US"missing '{' for field arg of certextract";
        goto EXPAND_FAILED_CURLY;
        {
        expand_string_message = US"missing '{' for field arg of certextract";
        goto EXPAND_FAILED_CURLY;
@@ -6283,7 +6368,7 @@ while (*s != 0)
       int len;
       uschar *p = sub[0];
 
       int len;
       uschar *p = sub[0];
 
-      while (isspace(*p)) p++;
+      Uskip_whitespace(&p);
       sub[0] = p;
 
       len = Ustrlen(p);
       sub[0] = p;
 
       len = Ustrlen(p);
@@ -6292,8 +6377,7 @@ while (*s != 0)
       }
 
       /* inspect the cert argument */
       }
 
       /* inspect the cert argument */
-      while (isspace(*s)) s++;
-      if (*s != '{')                                   /*}*/
+      if (Uskip_whitespace(&s) != '{')                                 /*}*/
        {
        expand_string_message = US"missing '{' for cert variable arg of certextract";
        goto EXPAND_FAILED_CURLY;
        {
        expand_string_message = US"missing '{' for cert variable arg of certextract";
        goto EXPAND_FAILED_CURLY;
@@ -6345,13 +6429,13 @@ while (*s != 0)
     case EITEM_REDUCE:
       {
       int sep = 0;
     case EITEM_REDUCE:
       {
       int sep = 0;
-      int save_ptr = yield->ptr;
+      int save_ptr = gstring_length(yield);
       uschar outsep[2] = { '\0', '\0' };
       const uschar *list, *expr, *temp;
       uschar *save_iterate_item = iterate_item;
       uschar *save_lookup_value = lookup_value;
 
       uschar outsep[2] = { '\0', '\0' };
       const uschar *list, *expr, *temp;
       uschar *save_iterate_item = iterate_item;
       uschar *save_lookup_value = lookup_value;
 
-      while (isspace(*s)) s++;
+      Uskip_whitespace(&s);
       if (*s++ != '{')
         {
        expand_string_message =
       if (*s++ != '{')
         {
        expand_string_message =
@@ -6371,7 +6455,7 @@ while (*s != 0)
       if (item_type == EITEM_REDUCE)
         {
        uschar * t;
       if (item_type == EITEM_REDUCE)
         {
        uschar * t;
-        while (isspace(*s)) s++;
+        Uskip_whitespace(&s);
         if (*s++ != '{')
          {
          expand_string_message = US"missing '{' for second arg of reduce";
         if (*s++ != '{')
          {
          expand_string_message = US"missing '{' for second arg of reduce";
@@ -6387,7 +6471,7 @@ while (*s != 0)
          }
         }
 
          }
         }
 
-      while (isspace(*s)) s++;
+      Uskip_whitespace(&s);
       if (*s++ != '{')
         {
        expand_string_message =
       if (*s++ != '{')
         {
        expand_string_message =
@@ -6418,7 +6502,7 @@ while (*s != 0)
         goto EXPAND_FAILED;
         }
 
         goto EXPAND_FAILED;
         }
 
-      while (isspace(*s)) s++;
+      Uskip_whitespace(&s);
       if (*s++ != '}')
         {                                              /*{*/
         expand_string_message = string_sprintf("missing } at end of condition "
       if (*s++ != '}')
         {                                              /*{*/
         expand_string_message = string_sprintf("missing } at end of condition "
@@ -6427,7 +6511,7 @@ while (*s != 0)
         goto EXPAND_FAILED;
         }
 
         goto EXPAND_FAILED;
         }
 
-      while (isspace(*s)) s++;                         /*{*/
+      Uskip_whitespace(&s);                            /*{*/
       if (*s++ != '}')
         {                                              /*{*/
         expand_string_message = string_sprintf("missing } at end of \"%s\"",
       if (*s++ != '}')
         {                                              /*{*/
         expand_string_message = string_sprintf("missing } at end of \"%s\"",
@@ -6492,7 +6576,8 @@ while (*s != 0)
         item of the output list, add in a space if the new item begins with the
         separator character, or is an empty string. */
 
         item of the output list, add in a space if the new item begins with the
         separator character, or is an empty string. */
 
-        if (yield->ptr != save_ptr && (temp[0] == *outsep || temp[0] == 0))
+        if (  yield && yield->ptr != save_ptr
+          && (temp[0] == *outsep || temp[0] == 0))
           yield = string_catn(yield, US" ", 1);
 
         /* Add the string in "temp" to the output list that we are building,
           yield = string_catn(yield, US" ", 1);
 
         /* Add the string in "temp" to the output list that we are building,
@@ -6532,7 +6617,7 @@ while (*s != 0)
       the redundant final separator. Even though an empty item at the end of a
       list does not count, this is tidier. */
 
       the redundant final separator. Even though an empty item at the end of a
       list does not count, this is tidier. */
 
-      else if (yield->ptr != save_ptr) yield->ptr--;
+      else if (yield && yield->ptr != save_ptr) yield->ptr--;
 
       /* Restore preserved $item */
 
 
       /* Restore preserved $item */
 
@@ -6550,7 +6635,7 @@ while (*s != 0)
       uschar * tmp;
       uschar *save_iterate_item = iterate_item;
 
       uschar * tmp;
       uschar *save_iterate_item = iterate_item;
 
-      while (isspace(*s)) s++;
+      Uskip_whitespace(&s);
       if (*s++ != '{')
         {
         expand_string_message = US"missing '{' for list arg of sort";
       if (*s++ != '{')
         {
         expand_string_message = US"missing '{' for list arg of sort";
@@ -6565,7 +6650,7 @@ while (*s != 0)
        goto EXPAND_FAILED_CURLY;
        }
 
        goto EXPAND_FAILED_CURLY;
        }
 
-      while (isspace(*s)) s++;
+      Uskip_whitespace(&s);
       if (*s++ != '{')
         {
         expand_string_message = US"missing '{' for comparator arg of sort";
       if (*s++ != '{')
         {
         expand_string_message = US"missing '{' for comparator arg of sort";
@@ -6599,7 +6684,7 @@ while (*s != 0)
          goto EXPAND_FAILED;
        }
 
          goto EXPAND_FAILED;
        }
 
-      while (isspace(*s)) s++;
+      Uskip_whitespace(&s);
       if (*s++ != '{')
         {
         expand_string_message = US"missing '{' for extractor arg of sort";
       if (*s++ != '{')
         {
         expand_string_message = US"missing '{' for extractor arg of sort";
@@ -6742,7 +6827,7 @@ while (*s != 0)
         }
 
       switch(read_subs(argv, EXPAND_DLFUNC_MAX_ARGS + 2, 2, &s, skipping,
         }
 
       switch(read_subs(argv, EXPAND_DLFUNC_MAX_ARGS + 2, 2, &s, skipping,
-           TRUE, US"dlfunc", &resetok))
+           TRUE, name, &resetok))
         {
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:
         {
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:
@@ -6818,8 +6903,7 @@ while (*s != 0)
       uschar * key;
       uschar *save_lookup_value = lookup_value;
 
       uschar * key;
       uschar *save_lookup_value = lookup_value;
 
-      while (isspace(*s)) s++;
-      if (*s != '{')                                   /*}*/
+      if (Uskip_whitespace(&s) != '{')                                 /*}*/
        goto EXPAND_FAILED;
 
       key = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok);
        goto EXPAND_FAILED;
 
       key = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok);
@@ -7056,6 +7140,20 @@ while (*s != 0)
         continue;
         }
 
         continue;
         }
 
+      case EOP_BLESS:
+       /* This is purely for the convenience of the test harness.  Do not enable
+       it otherwise as it defeats the taint-checking security. */
+
+       if (f.running_in_test_harness)
+         yield = string_cat(yield, is_tainted(sub)
+                                   ? string_copy_taint(sub, FALSE) : sub);
+       else
+         {
+         DEBUG(D_expand) debug_printf_indent("bless operator not supported\n");
+         yield = string_cat(yield, sub);
+         }
+       continue;
+
       case EOP_EXPAND:
         {
         uschar *expanded = expand_string_internal(sub, FALSE, NULL, skipping, TRUE, &resetok);
       case EOP_EXPAND:
         {
         uschar *expanded = expand_string_internal(sub, FALSE, NULL, skipping, TRUE, &resetok);
@@ -7348,6 +7446,8 @@ while (*s != 0)
         continue;
        }
 
         continue;
        }
 
+      /* quote a list-item for the given list-separator */
+
       /* 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. */
 
       /* 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. */
 
@@ -7450,11 +7550,10 @@ while (*s != 0)
         {
         uschar outsep[2] = { ':', '\0' };
         uschar *address, *error;
         {
         uschar outsep[2] = { ':', '\0' };
         uschar *address, *error;
-        int save_ptr = yield->ptr;
+        int save_ptr = gstring_length(yield);
         int start, end, domain;  /* Not really used */
 
         int start, end, domain;  /* Not really used */
 
-        while (isspace(*sub)) sub++;
-        if (*sub == '>')
+       if (Uskip_whitespace(&sub) == '>')
           if (*outsep = *++sub) ++sub;
           else
            {
           if (*outsep = *++sub) ++sub;
           else
            {
@@ -7481,7 +7580,7 @@ while (*s != 0)
 
           if (address)
             {
 
           if (address)
             {
-            if (yield->ptr != save_ptr && address[0] == *outsep)
+            if (yield && yield->ptr != save_ptr && address[0] == *outsep)
               yield = string_catn(yield, US" ", 1);
 
             for (;;)
               yield = string_catn(yield, US" ", 1);
 
             for (;;)
@@ -7510,7 +7609,7 @@ while (*s != 0)
         /* If we have generated anything, remove the redundant final
         separator. */
 
         /* If we have generated anything, remove the redundant final
         separator. */
 
-        if (yield->ptr != save_ptr) yield->ptr--;
+        if (yield && yield->ptr != save_ptr) yield->ptr--;
         f.parse_allow_group = FALSE;
         continue;
         }
         f.parse_allow_group = FALSE;
         continue;
         }
@@ -7529,7 +7628,7 @@ while (*s != 0)
       case EOP_QUOTE_LOCAL_PART:
       if (!arg)
         {
       case EOP_QUOTE_LOCAL_PART:
       if (!arg)
         {
-        BOOL needs_quote = (*sub == 0);      /* TRUE for empty string */
+        BOOL needs_quote = (!*sub);      /* TRUE for empty string */
         uschar *t = sub - 1;
 
         if (c == EOP_QUOTE)
         uschar *t = sub - 1;
 
         if (c == EOP_QUOTE)
@@ -7649,10 +7748,10 @@ while (*s != 0)
 
       case EOP_FROM_UTF8:
         {
 
       case EOP_FROM_UTF8:
         {
-        while (*sub != 0)
+       uschar * buff = store_get(4, is_tainted(sub));
+        while (*sub)
           {
           int c;
           {
           int c;
-          uschar buff[4];
           GETUTF8INC(c, sub);
           if (c > 255) c = '_';
           buff[0] = c;
           GETUTF8INC(c, sub);
           if (c > 255) c = '_';
           buff[0] = c;
@@ -7661,7 +7760,7 @@ while (*s != 0)
         continue;
         }
 
         continue;
         }
 
-         /* replace illegal UTF-8 sequences by replacement character  */
+      /* replace illegal UTF-8 sequences by replacement character  */
 
       #define UTF8_REPLACEMENT_CHAR US"?"
 
 
       #define UTF8_REPLACEMENT_CHAR US"?"
 
@@ -7673,7 +7772,17 @@ while (*s != 0)
         int complete;
         uschar seq_buff[4];                    /* accumulate utf-8 here */
 
         int complete;
         uschar seq_buff[4];                    /* accumulate utf-8 here */
 
-        while (*sub != 0)
+       /* Manually track tainting, as we deal in individual chars below */
+
+       if (is_tainted(sub))
+         if (yield->s && yield->ptr)
+           gstring_rebuffer(yield);
+         else
+           yield->s = store_get(yield->size = Ustrlen(sub), TRUE);
+
+       /* Check the UTF-8, byte-by-byte */
+
+        while (*sub)
          {
          complete = 0;
          uschar c = *sub++;
          {
          complete = 0;
          uschar c = *sub++;
@@ -7699,7 +7808,7 @@ while (*s != 0)
            }
          else  /* no bytes left: new sequence */
            {
            }
          else  /* no bytes left: new sequence */
            {
-           if((c & 0x80) == 0) /* 1-byte sequence, US-ASCII, keep it */
+           if(!(c & 0x80))     /* 1-byte sequence, US-ASCII, keep it */
              {
              yield = string_catn(yield, &c, 1);
              continue;
              {
              yield = string_catn(yield, &c, 1);
              continue;
@@ -7744,9 +7853,8 @@ while (*s != 0)
         * Eg, ${length_1:フィル} is one byte, not one character, so we expect
         * ${utf8clean:${length_1:フィル}} to yield '?' */
         if (bytes_left != 0)
         * Eg, ${length_1:フィル} is one byte, not one character, so we expect
         * ${utf8clean:${length_1:フィル}} to yield '?' */
         if (bytes_left != 0)
-          {
           yield = string_catn(yield, UTF8_REPLACEMENT_CHAR, 1);
           yield = string_catn(yield, UTF8_REPLACEMENT_CHAR, 1);
-          }
+
         continue;
         }
 
         continue;
         }
 
@@ -8380,26 +8488,19 @@ 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))
 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')
+  if (Uskip_whitespace(&s) == '\0')
     {
       DEBUG(D_expand)
        debug_printf_indent("treating blank string as number 0\n");
       return 0;
     }
     {
       DEBUG(D_expand)
        debug_printf_indent("treating blank string as number 0\n");
       return 0;
     }
-  }
 
 value = strtoll(CS s, CSS &endptr, 10);
 
 if (endptr == s)
 
 value = strtoll(CS s, CSS &endptr, 10);
 
 if (endptr == s)
-  {
   msg = US"integer expected but \"%s\" found";
   msg = US"integer expected but \"%s\" found";
-  }
 else if (value < 0 && isplus)
 else if (value < 0 && isplus)
-  {
   msg = US"non-negative integer expected but \"%s\" found";
   msg = US"non-negative integer expected but \"%s\" found";
-  }
 else
   {
   switch (tolower(*endptr))
 else
   {
   switch (tolower(*endptr))
@@ -8425,10 +8526,7 @@ else
   if (errno == ERANGE)
     msg = US"absolute value of integer \"%s\" is too large (overflow)";
   else
   if (errno == ERANGE)
     msg = US"absolute value of integer \"%s\" is too large (overflow)";
   else
-    {
-    while (isspace(*endptr)) endptr++;
-    if (*endptr == 0) return value;
-    }
+    if (Uskip_whitespace(&endptr) == 0) return value;
   }
 
 expand_string_message = string_sprintf(CS msg, s);
   }
 
 expand_string_message = string_sprintf(CS msg, s);
@@ -8531,7 +8629,7 @@ expand_file_big_buffer(const uschar * filename)
 {
 int fd, off = 0, len;
 
 {
 int fd, off = 0, len;
 
-if ((fd = open(CS filename, O_RDONLY)) < 0)
+if ((fd = exim_open2(CS filename, O_RDONLY)) < 0)
   {
   log_write(0, LOG_MAIN | LOG_PANIC, "unable to open file for reading: %s",
             filename);
   {
   log_write(0, LOG_MAIN | LOG_PANIC, "unable to open file for reading: %s",
             filename);