constify
[exim.git] / src / src / expand.c
index f7c8f05374e03f28c15911f631830dbb60e13200..2d55a884666ac4d8e8fd1709b8f76673709a2949 100644 (file)
@@ -444,9 +444,9 @@ enum vtypes {
   vtype_pspace,         /* partition space; value is T/F for spool/log */
   vtype_pinodes,        /* partition inodes; value is T/F for spool/log */
   vtype_cert           /* SSL certificate */
-  #ifndef DISABLE_DKIM
+#ifndef DISABLE_DKIM
   ,vtype_dkim           /* Lookup of value in DKIM signature */
-  #endif
+#endif
 };
 
 /* Type for main variable table */
@@ -583,9 +583,9 @@ static var_entry var_table[] = {
   { "interface_address",   vtype_stringptr,   &interface_address },
   { "interface_port",      vtype_int,         &interface_port },
   { "item",                vtype_stringptr,   &iterate_item },
-  #ifdef LOOKUP_LDAP
+#ifdef LOOKUP_LDAP
   { "ldap_dn",             vtype_stringptr,   &eldap_dn },
-  #endif
+#endif
   { "load_average",        vtype_load_avg,    NULL },
   { "local_part",          vtype_stringptr,   &deliver_localpart },
   { "local_part_data",     vtype_stringptr,   &deliver_localpart_data },
@@ -750,17 +750,8 @@ static var_entry var_table[] = {
   { "spool_directory",     vtype_stringptr,   &spool_directory },
   { "spool_inodes",        vtype_pinodes,     (void *)TRUE },
   { "spool_space",         vtype_pspace,      (void *)TRUE },
-#ifdef EXPERIMENTAL_SRS_ALT
-  { "srs_db_address",      vtype_stringptr,   &srs_db_address },
-  { "srs_db_key",          vtype_stringptr,   &srs_db_key },
-  { "srs_orig_recipient",  vtype_stringptr,   &srs_orig_recipient },
-  { "srs_orig_sender",     vtype_stringptr,   &srs_orig_sender },
-#endif
-#if defined(EXPERIMENTAL_SRS_ALT) || defined(SUPPORT_SRS)
+#ifdef SUPPORT_SRS
   { "srs_recipient",       vtype_stringptr,   &srs_recipient },
-#endif
-#ifdef EXPERIMENTAL_SRS_ALT
-  { "srs_status",          vtype_stringptr,   &srs_status },
 #endif
   { "thisaddress",         vtype_stringptr,   &filter_thisaddress },
 
@@ -1297,7 +1288,7 @@ expand_getlistele(int field, const uschar * list)
 const uschar * tlist = list;
 int sep = 0;
 /* Tainted mem for the throwaway element copies */
-uschar * dummy = store_get(2, TRUE);
+uschar * dummy = store_get(2, GET_TAINTED);
 
 if (field < 0)
   {
@@ -1984,7 +1975,7 @@ switch (vp->type)
       int len = message_body_visible;
 
       if (len > message_size) len = message_size;
-      *ss = body = store_get(len+1, TRUE);
+      *ss = body = store_get(len+1, GET_TAINTED);
       body[0] = 0;
       if (vp->type == vtype_msgbody_end)
        {
@@ -2907,64 +2898,49 @@ switch(cond_type = identify_operator(&s, &opname))
     {
     case ECOND_NUM_E:
     case ECOND_NUM_EE:
-    tempcond = (num[0] == num[1]);
-    break;
+      tempcond = (num[0] == num[1]); break;
 
     case ECOND_NUM_G:
-    tempcond = (num[0] > num[1]);
-    break;
+      tempcond = (num[0] > num[1]); break;
 
     case ECOND_NUM_GE:
-    tempcond = (num[0] >= num[1]);
-    break;
+      tempcond = (num[0] >= num[1]); break;
 
     case ECOND_NUM_L:
-    tempcond = (num[0] < num[1]);
-    break;
+      tempcond = (num[0] < num[1]); break;
 
     case ECOND_NUM_LE:
-    tempcond = (num[0] <= num[1]);
-    break;
+      tempcond = (num[0] <= num[1]); break;
 
     case ECOND_STR_LT:
-    tempcond = (Ustrcmp(sub[0], sub[1]) < 0);
-    break;
+      tempcond = (Ustrcmp(sub[0], sub[1]) < 0); break;
 
     case ECOND_STR_LTI:
-    tempcond = (strcmpic(sub[0], sub[1]) < 0);
-    break;
+      tempcond = (strcmpic(sub[0], sub[1]) < 0); break;
 
     case ECOND_STR_LE:
-    tempcond = (Ustrcmp(sub[0], sub[1]) <= 0);
-    break;
+      tempcond = (Ustrcmp(sub[0], sub[1]) <= 0); break;
 
     case ECOND_STR_LEI:
-    tempcond = (strcmpic(sub[0], sub[1]) <= 0);
-    break;
+      tempcond = (strcmpic(sub[0], sub[1]) <= 0); break;
 
     case ECOND_STR_EQ:
-    tempcond = (Ustrcmp(sub[0], sub[1]) == 0);
-    break;
+      tempcond = (Ustrcmp(sub[0], sub[1]) == 0); break;
 
     case ECOND_STR_EQI:
-    tempcond = (strcmpic(sub[0], sub[1]) == 0);
-    break;
+      tempcond = (strcmpic(sub[0], sub[1]) == 0); break;
 
     case ECOND_STR_GT:
-    tempcond = (Ustrcmp(sub[0], sub[1]) > 0);
-    break;
+      tempcond = (Ustrcmp(sub[0], sub[1]) > 0); break;
 
     case ECOND_STR_GTI:
-    tempcond = (strcmpic(sub[0], sub[1]) > 0);
-    break;
+      tempcond = (strcmpic(sub[0], sub[1]) > 0); break;
 
     case ECOND_STR_GE:
-    tempcond = (Ustrcmp(sub[0], sub[1]) >= 0);
-    break;
+      tempcond = (Ustrcmp(sub[0], sub[1]) >= 0); break;
 
     case ECOND_STR_GEI:
-    tempcond = (strcmpic(sub[0], sub[1]) >= 0);
-    break;
+      tempcond = (strcmpic(sub[0], sub[1]) >= 0); break;
 
     case ECOND_MATCH:   /* Regular expression match */
       {
@@ -2987,72 +2963,68 @@ switch(cond_type = identify_operator(&s, &opname))
       }
 
     case ECOND_MATCH_ADDRESS:  /* Match in an address list */
-    rc = match_address_list(sub[0], TRUE, FALSE, &(sub[1]), NULL, -1, 0, NULL);
-    goto MATCHED_SOMETHING;
+      rc = match_address_list(sub[0], TRUE, FALSE, &(sub[1]), NULL, -1, 0,
+                             CUSS &lookup_value);
+      goto MATCHED_SOMETHING;
 
     case ECOND_MATCH_DOMAIN:   /* Match in a domain list */
-    rc = match_isinlist(sub[0], &(sub[1]), 0, &domainlist_anchor, NULL,
-      MCL_DOMAIN + MCL_NOEXPAND, TRUE, NULL);
-    goto MATCHED_SOMETHING;
+      rc = match_isinlist(sub[0], &(sub[1]), 0, &domainlist_anchor, NULL,
+       MCL_DOMAIN + MCL_NOEXPAND, TRUE, CUSS &lookup_value);
+      goto MATCHED_SOMETHING;
 
     case ECOND_MATCH_IP:       /* Match IP address in a host list */
-    if (sub[0][0] != 0 && string_is_ip_address(sub[0], NULL) == 0)
-      {
-      expand_string_message = string_sprintf("\"%s\" is not an IP address",
-        sub[0]);
-      return NULL;
-      }
-    else
-      {
-      unsigned int *nullcache = NULL;
-      check_host_block cb;
-
-      cb.host_name = US"";
-      cb.host_address = sub[0];
-
-      /* If the host address starts off ::ffff: it is an IPv6 address in
-      IPv4-compatible mode. Find the IPv4 part for checking against IPv4
-      addresses. */
-
-      cb.host_ipv4 = (Ustrncmp(cb.host_address, "::ffff:", 7) == 0)?
-        cb.host_address + 7 : cb.host_address;
-
-      rc = match_check_list(
-             &sub[1],                   /* the list */
-             0,                         /* separator character */
-             &hostlist_anchor,          /* anchor pointer */
-             &nullcache,                /* cache pointer */
-             check_host,                /* function for testing */
-             &cb,                       /* argument for function */
-             MCL_HOST,                  /* type of check */
-             sub[0],                    /* text for debugging */
-             NULL);                     /* where to pass back data */
-      }
-    goto MATCHED_SOMETHING;
+      if (sub[0][0] != 0 && string_is_ip_address(sub[0], NULL) == 0)
+       {
+       expand_string_message = string_sprintf("\"%s\" is not an IP address",
+         sub[0]);
+       return NULL;
+       }
+      else
+       {
+       unsigned int *nullcache = NULL;
+       check_host_block cb;
+
+       cb.host_name = US"";
+       cb.host_address = sub[0];
+
+       /* If the host address starts off ::ffff: it is an IPv6 address in
+       IPv4-compatible mode. Find the IPv4 part for checking against IPv4
+       addresses. */
+
+       cb.host_ipv4 = (Ustrncmp(cb.host_address, "::ffff:", 7) == 0)?
+         cb.host_address + 7 : cb.host_address;
+
+       rc = match_check_list(
+              &sub[1],                   /* the list */
+              0,                         /* separator character */
+              &hostlist_anchor,          /* anchor pointer */
+              &nullcache,                /* cache pointer */
+              check_host,                /* function for testing */
+              &cb,                       /* argument for function */
+              MCL_HOST,                  /* type of check */
+              sub[0],                    /* text for debugging */
+              CUSS &lookup_value);       /* where to pass back data */
+       }
+      goto MATCHED_SOMETHING;
 
     case ECOND_MATCH_LOCAL_PART:
-    rc = match_isinlist(sub[0], &(sub[1]), 0, &localpartlist_anchor, NULL,
-      MCL_LOCALPART + MCL_NOEXPAND, TRUE, NULL);
-    /* Fall through */
-    /* VVVVVVVVVVVV */
-    MATCHED_SOMETHING:
-    switch(rc)
-      {
-      case OK:
-      tempcond = TRUE;
-      break;
-
-      case FAIL:
-      tempcond = FALSE;
-      break;
+      rc = match_isinlist(sub[0], &(sub[1]), 0, &localpartlist_anchor, NULL,
+       MCL_LOCALPART + MCL_NOEXPAND, TRUE, CUSS &lookup_value);
+      /* Fall through */
+      /* VVVVVVVVVVVV */
+      MATCHED_SOMETHING:
+      switch(rc)
+       {
+       case OK:   tempcond = TRUE;  break;
+       case FAIL: tempcond = FALSE; break;
 
-      case DEFER:
-      expand_string_message = string_sprintf("unable to complete match "
-        "against \"%s\": %s", sub[1], search_error_message);
-      return NULL;
-      }
+       case DEFER:
+         expand_string_message = string_sprintf("unable to complete match "
+           "against \"%s\": %s", sub[1], search_error_message);
+         return NULL;
+       }
 
-    break;
+      break;
 
     /* Various "encrypted" comparisons. If the second string starts with
     "{" then an encryption type is given. Default to crypt() or crypt16()
@@ -3061,138 +3033,138 @@ switch(cond_type = identify_operator(&s, &opname))
 
     case ECOND_CRYPTEQ:
     #ifndef SUPPORT_CRYPTEQ
-    goto COND_FAILED_NOT_COMPILED;
+      goto COND_FAILED_NOT_COMPILED;
     #else
-    if (strncmpic(sub[1], US"{md5}", 5) == 0)
-      {
-      int sublen = Ustrlen(sub[1]+5);
-      md5 base;
-      uschar digest[16];
+      if (strncmpic(sub[1], US"{md5}", 5) == 0)
+       {
+       int sublen = Ustrlen(sub[1]+5);
+       md5 base;
+       uschar digest[16];
 
-      md5_start(&base);
-      md5_end(&base, sub[0], Ustrlen(sub[0]), digest);
+       md5_start(&base);
+       md5_end(&base, sub[0], Ustrlen(sub[0]), digest);
 
-      /* If the length that we are comparing against is 24, the MD5 digest
-      is expressed as a base64 string. This is the way LDAP does it. However,
-      some other software uses a straightforward hex representation. We assume
-      this if the length is 32. Other lengths fail. */
+       /* If the length that we are comparing against is 24, the MD5 digest
+       is expressed as a base64 string. This is the way LDAP does it. However,
+       some other software uses a straightforward hex representation. We assume
+       this if the length is 32. Other lengths fail. */
 
-      if (sublen == 24)
-        {
-        uschar *coded = b64encode(CUS digest, 16);
-        DEBUG(D_auth) debug_printf("crypteq: using MD5+B64 hashing\n"
-          "  subject=%s\n  crypted=%s\n", coded, sub[1]+5);
-        tempcond = (Ustrcmp(coded, sub[1]+5) == 0);
-        }
-      else if (sublen == 32)
-        {
-        uschar coded[36];
-        for (int i = 0; i < 16; i++) sprintf(CS (coded+2*i), "%02X", digest[i]);
-        coded[32] = 0;
-        DEBUG(D_auth) debug_printf("crypteq: using MD5+hex hashing\n"
-          "  subject=%s\n  crypted=%s\n", coded, sub[1]+5);
-        tempcond = (strcmpic(coded, sub[1]+5) == 0);
-        }
-      else
-        {
-        DEBUG(D_auth) debug_printf("crypteq: length for MD5 not 24 or 32: "
-          "fail\n  crypted=%s\n", sub[1]+5);
-        tempcond = FALSE;
-        }
-      }
+       if (sublen == 24)
+         {
+         uschar *coded = b64encode(CUS digest, 16);
+         DEBUG(D_auth) debug_printf("crypteq: using MD5+B64 hashing\n"
+           "  subject=%s\n  crypted=%s\n", coded, sub[1]+5);
+         tempcond = (Ustrcmp(coded, sub[1]+5) == 0);
+         }
+       else if (sublen == 32)
+         {
+         uschar coded[36];
+         for (int i = 0; i < 16; i++) sprintf(CS (coded+2*i), "%02X", digest[i]);
+         coded[32] = 0;
+         DEBUG(D_auth) debug_printf("crypteq: using MD5+hex hashing\n"
+           "  subject=%s\n  crypted=%s\n", coded, sub[1]+5);
+         tempcond = (strcmpic(coded, sub[1]+5) == 0);
+         }
+       else
+         {
+         DEBUG(D_auth) debug_printf("crypteq: length for MD5 not 24 or 32: "
+           "fail\n  crypted=%s\n", sub[1]+5);
+         tempcond = FALSE;
+         }
+       }
 
-    else if (strncmpic(sub[1], US"{sha1}", 6) == 0)
-      {
-      int sublen = Ustrlen(sub[1]+6);
-      hctx h;
-      uschar digest[20];
+      else if (strncmpic(sub[1], US"{sha1}", 6) == 0)
+       {
+       int sublen = Ustrlen(sub[1]+6);
+       hctx h;
+       uschar digest[20];
 
-      sha1_start(&h);
-      sha1_end(&h, sub[0], Ustrlen(sub[0]), digest);
+       sha1_start(&h);
+       sha1_end(&h, sub[0], Ustrlen(sub[0]), digest);
 
-      /* If the length that we are comparing against is 28, assume the SHA1
-      digest is expressed as a base64 string. If the length is 40, assume a
-      straightforward hex representation. Other lengths fail. */
+       /* If the length that we are comparing against is 28, assume the SHA1
+       digest is expressed as a base64 string. If the length is 40, assume a
+       straightforward hex representation. Other lengths fail. */
 
-      if (sublen == 28)
-        {
-        uschar *coded = b64encode(CUS digest, 20);
-        DEBUG(D_auth) debug_printf("crypteq: using SHA1+B64 hashing\n"
-          "  subject=%s\n  crypted=%s\n", coded, sub[1]+6);
-        tempcond = (Ustrcmp(coded, sub[1]+6) == 0);
-        }
-      else if (sublen == 40)
-        {
-        uschar coded[44];
-        for (int i = 0; i < 20; i++) sprintf(CS (coded+2*i), "%02X", digest[i]);
-        coded[40] = 0;
-        DEBUG(D_auth) debug_printf("crypteq: using SHA1+hex hashing\n"
-          "  subject=%s\n  crypted=%s\n", coded, sub[1]+6);
-        tempcond = (strcmpic(coded, sub[1]+6) == 0);
-        }
-      else
-        {
-        DEBUG(D_auth) debug_printf("crypteq: length for SHA-1 not 28 or 40: "
-          "fail\n  crypted=%s\n", sub[1]+6);
-       tempcond = FALSE;
-        }
-      }
+       if (sublen == 28)
+         {
+         uschar *coded = b64encode(CUS digest, 20);
+         DEBUG(D_auth) debug_printf("crypteq: using SHA1+B64 hashing\n"
+           "  subject=%s\n  crypted=%s\n", coded, sub[1]+6);
+         tempcond = (Ustrcmp(coded, sub[1]+6) == 0);
+         }
+       else if (sublen == 40)
+         {
+         uschar coded[44];
+         for (int i = 0; i < 20; i++) sprintf(CS (coded+2*i), "%02X", digest[i]);
+         coded[40] = 0;
+         DEBUG(D_auth) debug_printf("crypteq: using SHA1+hex hashing\n"
+           "  subject=%s\n  crypted=%s\n", coded, sub[1]+6);
+         tempcond = (strcmpic(coded, sub[1]+6) == 0);
+         }
+       else
+         {
+         DEBUG(D_auth) debug_printf("crypteq: length for SHA-1 not 28 or 40: "
+           "fail\n  crypted=%s\n", sub[1]+6);
+         tempcond = FALSE;
+         }
+       }
 
-    else   /* {crypt} or {crypt16} and non-{ at start */
-           /* }-for-text-editors */
-      {
-      int which = 0;
-      uschar *coded;
+      else   /* {crypt} or {crypt16} and non-{ at start */
+            /* }-for-text-editors */
+       {
+       int which = 0;
+       uschar *coded;
 
-      if (strncmpic(sub[1], US"{crypt}", 7) == 0)
-        {
-        sub[1] += 7;
-        which = 1;
-        }
-      else if (strncmpic(sub[1], US"{crypt16}", 9) == 0)
-        {
-        sub[1] += 9;
-        which = 2;
-        }
-      else if (sub[1][0] == '{')               /* }-for-text-editors */
-        {
-        expand_string_message = string_sprintf("unknown encryption mechanism "
-          "in \"%s\"", sub[1]);
-        return NULL;
-        }
+       if (strncmpic(sub[1], US"{crypt}", 7) == 0)
+         {
+         sub[1] += 7;
+         which = 1;
+         }
+       else if (strncmpic(sub[1], US"{crypt16}", 9) == 0)
+         {
+         sub[1] += 9;
+         which = 2;
+         }
+       else if (sub[1][0] == '{')              /* }-for-text-editors */
+         {
+         expand_string_message = string_sprintf("unknown encryption mechanism "
+           "in \"%s\"", sub[1]);
+         return NULL;
+         }
 
-      switch(which)
-        {
-        case 0:  coded = US DEFAULT_CRYPT(CS sub[0], CS sub[1]); break;
-        case 1:  coded = US crypt(CS sub[0], CS sub[1]); break;
-        default: coded = US crypt16(CS sub[0], CS sub[1]); break;
-        }
+       switch(which)
+         {
+         case 0:  coded = US DEFAULT_CRYPT(CS sub[0], CS sub[1]); break;
+         case 1:  coded = US crypt(CS sub[0], CS sub[1]); break;
+         default: coded = US crypt16(CS sub[0], CS sub[1]); break;
+         }
 
-      #define STR(s) # s
-      #define XSTR(s) STR(s)
-      DEBUG(D_auth) debug_printf("crypteq: using %s()\n"
-        "  subject=%s\n  crypted=%s\n",
-        which == 0 ? XSTR(DEFAULT_CRYPT) : which == 1 ? "crypt" : "crypt16",
-        coded, sub[1]);
-      #undef STR
-      #undef XSTR
-
-      /* If the encrypted string contains fewer than two characters (for the
-      salt), force failure. Otherwise we get false positives: with an empty
-      string the yield of crypt() is an empty string! */
-
-      if (coded)
-       tempcond = Ustrlen(sub[1]) < 2 ? FALSE : Ustrcmp(coded, sub[1]) == 0;
-      else if (errno == EINVAL)
-       tempcond = FALSE;
-      else
-       {
-       expand_string_message = string_sprintf("crypt error: %s\n",
-         US strerror(errno));
-       return NULL;
+       #define STR(s) # s
+       #define XSTR(s) STR(s)
+       DEBUG(D_auth) debug_printf("crypteq: using %s()\n"
+         "  subject=%s\n  crypted=%s\n",
+         which == 0 ? XSTR(DEFAULT_CRYPT) : which == 1 ? "crypt" : "crypt16",
+         coded, sub[1]);
+       #undef STR
+       #undef XSTR
+
+       /* If the encrypted string contains fewer than two characters (for the
+       salt), force failure. Otherwise we get false positives: with an empty
+       string the yield of crypt() is an empty string! */
+
+       if (coded)
+         tempcond = Ustrlen(sub[1]) < 2 ? FALSE : Ustrcmp(coded, sub[1]) == 0;
+       else if (errno == EINVAL)
+         tempcond = FALSE;
+       else
+         {
+         expand_string_message = string_sprintf("crypt error: %s\n",
+           US strerror(errno));
+         return NULL;
+         }
        }
-      }
-    break;
+      break;
     #endif  /* SUPPORT_CRYPTEQ */
 
     case ECOND_INLIST:
@@ -3215,6 +3187,7 @@ switch(cond_type = identify_operator(&s, &opname))
         if (compare(sub[0], iterate_item) == 0)
           {
           tempcond = TRUE;
+         lookup_value = iterate_item;
           break;
           }
        }
@@ -3843,8 +3816,8 @@ Returns:  pointer to string containing the last three
 static uschar *
 prvs_daystamp(int day_offset)
 {
-uschar *days = store_get(32, FALSE);         /* Need at least 24 for cases */
-(void)string_format(days, 32, TIME_T_FMT,    /* where TIME_T_FMT is %lld */
+uschar * days = store_get(32, GET_UNTAINTED);      /* Need at least 24 for cases */
+(void)string_format(days, 32, TIME_T_FMT,         /* where TIME_T_FMT is %lld */
   (time(NULL) + day_offset*86400)/86400);
 return (Ustrlen(days) >= 3) ? &days[Ustrlen(days)-3] : US"100";
 }
@@ -3915,7 +3888,7 @@ chash_end(HMAC_SHA1, &h, innerhash, 20, finalhash);
 
 /* Hashing is deemed sufficient to de-taint any input data */
 
-p = finalhash_hex = store_get(40, FALSE);
+p = finalhash_hex = store_get(40, GET_UNTAINTED);
 for (int i = 0; i < 3; i++)
   {
   *p++ = hex_digits[(finalhash[i] & 0xf0) >> 4];
@@ -3946,7 +3919,7 @@ Returns:       new pointer for expandable string, terminated if non-null
 */
 
 gstring *
-cat_file(FILE *f, gstring *yield, uschar *eol)
+cat_file(FILE * f, gstring * yield, uschar * eol)
 {
 uschar buffer[1024];
 
@@ -3958,8 +3931,6 @@ while (Ufgets(buffer, sizeof(buffer), f))
   if (eol && buffer[len])
     yield = string_cat(yield, eol);
   }
-
-(void) string_from_gstring(yield);
 return yield;
 }
 
@@ -3981,7 +3952,6 @@ while ((rc = tls_read(tls_ctx, buffer, sizeof(buffer))) > 0)
 /* We assume that all errors, and any returns of zero bytes,
 are actually EOF. */
 
-(void) string_from_gstring(yield);
 return yield;
 }
 #endif
@@ -4356,7 +4326,7 @@ list = ((namedlist_block *)(t->data.ptr))->string;
 /* The list could be quite long so we (re)use a buffer for each element
 rather than getting each in new memory */
 
-if (is_tainted(list)) buffer = store_get(LISTNAMED_BUF_SIZE, TRUE);
+if (is_tainted(list)) buffer = store_get(LISTNAMED_BUF_SIZE, GET_TAINTED);
 while ((item = string_nextinlist(&list, &sep, buffer, LISTNAMED_BUF_SIZE)))
   {
   uschar * buf = US" : ";
@@ -4398,6 +4368,36 @@ return yield;
 
 
 
+/************************************************/
+static void
+debug_expansion_interim(const uschar * what, const uschar * value, int nchar,
+  BOOL skipping)
+{
+DEBUG(D_noutf8)
+  debug_printf_indent("|");
+else
+  debug_printf_indent(UTF8_VERT_RIGHT);
+
+for (int fill = 11 - Ustrlen(what); fill > 0; fill--)
+  DEBUG(D_noutf8)
+    debug_printf("-");
+  else
+    debug_printf(UTF8_HORIZ);
+
+debug_printf("%s: %.*s\n", what, nchar, value);
+if (is_tainted(value))
+  {
+  DEBUG(D_noutf8)
+    debug_printf_indent("%s     \\__", skipping ? "|     " : "      ");
+  else
+    debug_printf_indent("%s",
+      skipping
+      ? UTF8_VERT "             " : "           " UTF8_UP_RIGHT UTF8_HORIZ UTF8_HORIZ);
+  debug_printf("(tainted)\n");
+  }
+}
+
+
 /*************************************************
 *                 Expand string                  *
 *************************************************/
@@ -4477,13 +4477,13 @@ expand_level++;
 f.expand_string_forcedfail = FALSE;
 expand_string_message = US"";
 
-{ uschar *m;
-if ((m = is_tainted2(string, LOG_MAIN|LOG_PANIC, "Tainted string '%s' in expansion", s)))
+if (is_tainted(string))
   {
-  expand_string_message = m;
+  expand_string_message =
+    string_sprintf("attempt to expand tainted string '%s'", s);
+  log_write(0, LOG_MAIN|LOG_PANIC, "%s", expand_string_message);
   goto EXPAND_FAILED;
   }
-}
 
 while (*s)
   {
@@ -4522,14 +4522,11 @@ while (*s)
       {
       const uschar * t = s + 2;
       for (s = t; *s ; s++) if (*s == '\\' && s[1] == 'N') break;
+
       DEBUG(D_expand)
-       DEBUG(D_noutf8)
-         debug_printf_indent("|--protected: %.*s\n", (int)(s - t), t);
-       else
-         debug_printf_indent(UTF8_VERT_RIGHT UTF8_HORIZ UTF8_HORIZ
-           "protected: %.*s\n", (int)(s - t), t);
+       debug_expansion_interim(US"protected", t, (int)(s - t), skipping);
       yield = string_catn(yield, t, s - t);
-      if (*s != 0) s += 2;
+      if (*s) s += 2;
       }
     else
       {
@@ -4558,13 +4555,7 @@ while (*s)
     for (const uschar * t = s+1;
        *t && *t != '$' && *t != '}' && *t != '\\'; t++) i++;
 
-    DEBUG(D_expand)
-      DEBUG(D_noutf8)
-       debug_printf_indent("|-------text: %.*s\n", i, s);
-      else
-       debug_printf_indent(UTF8_VERT_RIGHT
-         UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ
-         "text: %.*s\n", i, s);
+    DEBUG(D_expand) debug_expansion_interim(US"text", s, i, skipping);
 
     yield = string_catn(yield, s, i);
     s += i;
@@ -4592,13 +4583,13 @@ while (*s)
     buffer. */
 
     if (!yield)
-      g = store_get(sizeof(gstring), FALSE);
+      g = store_get(sizeof(gstring), GET_UNTAINTED);
     else if (yield->ptr == 0)
       {
       if (resetok) reset_point = store_reset(reset_point);
       yield = NULL;
       reset_point = store_mark();
-      g = store_get(sizeof(gstring), FALSE);   /* alloc _before_ calling find_variable() */
+      g = store_get(sizeof(gstring), GET_UNTAINTED);   /* alloc _before_ calling find_variable() */
       }
 
     /* Header */
@@ -4708,6 +4699,11 @@ while (*s)
   s = read_name(name, sizeof(name), s, US"_-");
   item_type = chop_match(name, item_table, nelem(item_table));
 
+  /* Switch on item type.  All nondefault choices should "continue* when
+  skipping, but "break" otherwise so we get debug output for the item
+  expansion. */
+  {
+  int start = gstring_length(yield);
   switch(item_type)
     {
     /* Call an ACL from an expansion.  We feed data in via $acl_arg1 - $acl_arg9.
@@ -4722,8 +4718,8 @@ while (*s)
     case EITEM_ACL:
       /* ${acl {name} {arg1}{arg2}...} */
       {
-      uschar *sub[10]; /* name + arg1-arg9 (which must match number of acl_arg[]) */
-      uschar *user_msg;
+      uschar * sub[10];        /* name + arg1-arg9 (which must match number of acl_arg[]) */
+      uschar * user_msg;
       int rc;
 
       switch(read_subs(sub, nelem(sub), 1, &s, skipping, TRUE, name,
@@ -4744,7 +4740,7 @@ while (*s)
            debug_printf_indent("acl expansion yield: %s\n", user_msg);
          if (user_msg)
             yield = string_cat(yield, user_msg);
-         continue;
+         break;
 
        case DEFER:
           f.expand_string_forcedfail = TRUE;
@@ -4754,12 +4750,13 @@ while (*s)
            rc_names[rc], sub[0]);
          goto EXPAND_FAILED;
        }
+      break;
       }
 
     case EITEM_AUTHRESULTS:
       /* ${authresults {mysystemname}} */
       {
-      uschar *sub_arg[1];
+      uschar * sub_arg[1];
 
       switch(read_subs(sub_arg, nelem(sub_arg), 1, &s, skipping, TRUE, name,
                      &resetok))
@@ -4788,7 +4785,7 @@ while (*s)
 #ifdef EXPERIMENTAL_ARC
       yield = authres_arc(yield);
 #endif
-      continue;
+      break;
       }
 
     /* Handle conditionals - preserve the values of the numerical expansion
@@ -4802,27 +4799,18 @@ while (*s)
       const uschar *next_s;
       int save_expand_nmax =
         save_expand_strings(save_expand_nstring, save_expand_nlength);
+      uschar * save_lookup_value = lookup_value;
 
       Uskip_whitespace(&s);
       if (!(next_s = eval_condition(s, &resetok, skipping ? NULL : &cond)))
        goto EXPAND_FAILED;  /* message already set */
 
       DEBUG(D_expand)
-       DEBUG(D_noutf8)
-         {
-         debug_printf_indent("|--condition: %.*s\n", (int)(next_s - s), s);
-         debug_printf_indent("|-----result: %s\n", cond ? "true" : "false");
-         }
-       else
-         {
-         debug_printf_indent(UTF8_VERT_RIGHT UTF8_HORIZ UTF8_HORIZ
-           "condition: %.*s\n",
-           (int)(next_s - s), s);
-         debug_printf_indent(UTF8_VERT_RIGHT UTF8_HORIZ UTF8_HORIZ
-           UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ
-           "result: %s\n",
-           cond ? "true" : "false");
-         }
+       {
+       debug_expansion_interim(US"condition", s, (int)(next_s - s), skipping);
+       debug_expansion_interim(US"result",
+         cond ? US"true" : US"false", cond ? 4 : 5, skipping);
+       }
 
       s = next_s;
 
@@ -4845,9 +4833,10 @@ while (*s)
       /* Restore external setting of expansion variables for continuation
       at this level. */
 
+      lookup_value = save_lookup_value;
       restore_expand_strings(save_expand_nmax, save_expand_nstring,
         save_expand_nlength);
-      continue;
+      break;
       }
 
 #ifdef SUPPORT_I18N
@@ -4878,14 +4867,13 @@ while (*s)
        goto EXPAND_FAILED;
        }
 
-      if (!skipping)
-       {
-       if (!(encoded = imap_utf7_encode(sub_arg[0], headers_charset,
-                           sub_arg[1][0], sub_arg[2], &expand_string_message)))
-         goto EXPAND_FAILED;
-       yield = string_cat(yield, encoded);
-       }
-      continue;
+      if (skipping) continue;
+
+      if (!(encoded = imap_utf7_encode(sub_arg[0], headers_charset,
+                         sub_arg[1][0], sub_arg[2], &expand_string_message)))
+       goto EXPAND_FAILED;
+      yield = string_cat(yield, encoded);
+      break;
       }
 #endif
 
@@ -4901,9 +4889,9 @@ while (*s)
       int stype, partial, affixlen, starflags;
       int expand_setup = 0;
       int nameptr = 0;
-      uschar *key, *filename;
+      uschar * key, * filename;
       const uschar * affix, * opts;
-      uschar *save_lookup_value = lookup_value;
+      uschar * save_lookup_value = lookup_value;
       int save_expand_nmax =
         save_expand_strings(save_expand_nstring, save_expand_nlength);
 
@@ -4971,15 +4959,12 @@ while (*s)
           goto EXPAND_FAILED;
           }
         }
-      else
-        {
-        if (key)
-          {
-          expand_string_message = string_sprintf("a single key was given for "
-            "lookup type \"%s\", which is not a single-key lookup type", name);
-          goto EXPAND_FAILED;
-          }
-        }
+      else if (key)
+       {
+       expand_string_message = string_sprintf("a single key was given for "
+         "lookup type \"%s\", which is not a single-key lookup type", name);
+       goto EXPAND_FAILED;
+       }
 
       /* Get the next string in brackets and expand it. It is the file name for
       single-key+file lookups, and the whole query otherwise. In the case of
@@ -5065,7 +5050,9 @@ while (*s)
 
       restore_expand_strings(save_expand_nmax, save_expand_nstring,
         save_expand_nlength);
-      continue;
+
+      if (skipping) continue;
+      break;
       }
 
     /* If Perl support is configured, handle calling embedded perl subroutines,
@@ -5083,10 +5070,10 @@ while (*s)
 
 #else   /* EXIM_PERL */
       {
-      uschar *sub_arg[EXIM_PERL_MAX_ARGS + 2];
-      gstring *new_yield;
+      uschar * sub_arg[EXIM_PERL_MAX_ARGS + 2];
+      gstring * new_yield;
 
-      if ((expand_forbid & RDO_PERL) != 0)
+      if (expand_forbid & RDO_PERL)
         {
         expand_string_message = US"Perl calls are not permitted";
         goto EXPAND_FAILED;
@@ -5108,7 +5095,7 @@ while (*s)
 
       if (!opt_perl_started)
         {
-        uschar *initerror;
+        uschar * initerror;
         if (!opt_perl_startup)
           {
           expand_string_message = US"A setting of perl_startup is needed when "
@@ -5152,7 +5139,7 @@ while (*s)
 
       f.expand_string_forcedfail = FALSE;
       yield = new_yield;
-      continue;
+      break;
       }
 #endif /* EXIM_PERL */
 
@@ -5161,8 +5148,7 @@ while (*s)
 
     case EITEM_PRVS:
       {
-      uschar *sub_arg[3];
-      uschar *p,*domain;
+      uschar * sub_arg[3], * p, * domain;
 
       switch(read_subs(sub_arg, 3, 2, &s, skipping, TRUE, name, &resetok))
         {
@@ -5211,17 +5197,16 @@ while (*s)
       yield = string_catn(yield, US"@", 1);
       yield = string_cat (yield, domain);
 
-      continue;
+      break;
       }
 
     /* Check a prvs-encoded address for validity */
 
     case EITEM_PRVSCHECK:
       {
-      uschar *sub_arg[3];
+      uschar * sub_arg[3], * p;
       gstring * g;
-      const pcre2_code *re;
-      uschar *p;
+      const pcre2_code * re;
 
       /* TF: Ugliness: We want to expand parameter 1 first, then set
          up expansion variables that are used in the expansion of
@@ -5250,11 +5235,11 @@ while (*s)
 
       if (regex_match_and_setup(re,sub_arg[0],0,-1))
         {
-        uschar *local_part = string_copyn(expand_nstring[4],expand_nlength[4]);
-        uschar *key_num = string_copyn(expand_nstring[1],expand_nlength[1]);
-        uschar *daystamp = string_copyn(expand_nstring[2],expand_nlength[2]);
-        uschar *hash = string_copyn(expand_nstring[3],expand_nlength[3]);
-        uschar *domain = string_copyn(expand_nstring[5],expand_nlength[5]);
+        uschar * local_part = string_copyn(expand_nstring[4],expand_nlength[4]);
+        uschar * key_num = string_copyn(expand_nstring[1],expand_nlength[1]);
+        uschar * daystamp = string_copyn(expand_nstring[2],expand_nlength[2]);
+        uschar * hash = string_copyn(expand_nstring[3],expand_nlength[3]);
+        uschar * domain = string_copyn(expand_nstring[5],expand_nlength[5]);
 
         DEBUG(D_expand) debug_printf_indent("prvscheck localpart: %s\n", local_part);
         DEBUG(D_expand) debug_printf_indent("prvscheck key number: %s\n", key_num);
@@ -5352,15 +5337,16 @@ while (*s)
           case 3: goto EXPAND_FAILED;
           }
 
-      continue;
+      if (skipping) continue;
+      break;
       }
 
     /* Handle "readfile" to insert an entire file */
 
     case EITEM_READFILE:
       {
-      FILE *f;
-      uschar *sub_arg[2];
+      FILE * f;
+      uschar * sub_arg[2];
 
       if ((expand_forbid & RDO_READFILE) != 0)
         {
@@ -5389,7 +5375,7 @@ while (*s)
 
       yield = cat_file(f, yield, sub_arg[1]);
       (void)fclose(f);
-      continue;
+      break;
       }
 
     /* Handle "readsocket" to insert data from a socket, either
@@ -5513,7 +5499,8 @@ while (*s)
        expand_string_message = US"missing '}' closing readsocket";
        goto EXPAND_FAILED_CURLY;
        }
-      continue;
+      if (skipping) continue;
+      break;
 
       /* Come here on failure to create socket, connect socket, write to the
       socket, or timeout on reading. If another substring follows, expand and
@@ -5538,11 +5525,8 @@ while (*s)
 
     case EITEM_RUN:
       {
-      FILE *f;
-      uschar *arg;
-      const uschar **argv;
-      pid_t pid;
-      int fd_in, fd_out;
+      FILE * f;
+      const uschar * arg, ** argv;
 
       if ((expand_forbid & RDO_RUN) != 0)
         {
@@ -5551,16 +5535,17 @@ while (*s)
         }
 
       Uskip_whitespace(&s);
-      if (*s != '{')
+      if (*s != '{')                                   /*}*/
         {
        expand_string_message = US"missing '{' for command arg of run";
-       goto EXPAND_FAILED_CURLY;
+       goto EXPAND_FAILED_CURLY;                       /*"}*/
        }
       if (!(arg = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok)))
        goto EXPAND_FAILED;
       Uskip_whitespace(&s);
+                                                       /*{*/
       if (*s++ != '}')
-        {
+        {                                              /*{*/
        expand_string_message = US"missing '}' closing command arg of run";
        goto EXPAND_FAILED_CURLY;
        }
@@ -5572,6 +5557,9 @@ while (*s)
        }
       else
         {
+       int fd_in, fd_out;
+       pid_t pid;
+
         if (!transport_set_up_command(&argv,    /* anchor for arg list */
             arg,                                /* raw command */
             FALSE,                              /* don't expand the arguments */
@@ -5589,7 +5577,7 @@ while (*s)
           expand_string_message =
             string_sprintf("couldn't create child process: %s", strerror(errno));
           goto EXPAND_FAILED;
-          }
+         }
 
         /* Nothing is written to the standard input. */
 
@@ -5647,7 +5635,8 @@ while (*s)
         case 2: goto EXPAND_FAILED_CURLY;    /* returned value is 0 */
         }
 
-      continue;
+      if (skipping) continue;
+      break;
       }
 
     /* Handle character translation for "tr" */
@@ -5656,7 +5645,7 @@ while (*s)
       {
       int oldptr = gstring_length(yield);
       int o2m;
-      uschar *sub[3];
+      uschar * sub[3];
 
       switch(read_subs(sub, 3, 3, &s, skipping, TRUE, name, &resetok))
         {
@@ -5678,7 +5667,8 @@ while (*s)
           }
         }
 
-      continue;
+      if (skipping) continue;
+      break;
       }
 
     /* Handle "hash", "length", "nhash", and "substr" when they are given with
@@ -5692,7 +5682,7 @@ while (*s)
       int len;
       uschar *ret;
       int val[2] = { 0, -1 };
-      uschar *sub[3];
+      uschar * sub[3];
 
       /* "length" takes only 2 arguments whereas the others take 2 or 3.
       Ensure that sub[2] is set in the ${length } case. */
@@ -5741,7 +5731,8 @@ while (*s)
       if (!ret)
        goto EXPAND_FAILED;
       yield = string_catn(yield, ret, len);
-      continue;
+      if (skipping) continue;
+      break;
       }
 
     /* Handle HMAC computation: ${hmac{<algorithm>}{<secret>}{<text>}}
@@ -5756,14 +5747,14 @@ while (*s)
 
     case EITEM_HMAC:
       {
-      uschar *sub[3];
+      uschar * sub[3];
       md5 md5_base;
       hctx sha1_ctx;
-      void *use_base;
+      void * use_base;
       int type;
       int hashlen;      /* Number of octets for the hash algorithm's output */
       int hashblocklen; /* Number of octets the hash algorithm processes */
-      uschar *keyptr, *p;
+      uschar * keyptr, * p;
       unsigned int keylen;
 
       uschar keyhash[MAX_HASHLEN];
@@ -5780,79 +5771,78 @@ while (*s)
         case 3: goto EXPAND_FAILED;
         }
 
-      if (!skipping)
+      if (skipping) continue;
+
+      if (Ustrcmp(sub[0], "md5") == 0)
        {
-       if (Ustrcmp(sub[0], "md5") == 0)
-         {
-         type = HMAC_MD5;
-         use_base = &md5_base;
-         hashlen = 16;
-         hashblocklen = 64;
-         }
-       else if (Ustrcmp(sub[0], "sha1") == 0)
-         {
-         type = HMAC_SHA1;
-         use_base = &sha1_ctx;
-         hashlen = 20;
-         hashblocklen = 64;
-         }
-       else
-         {
-         expand_string_message =
-           string_sprintf("hmac algorithm \"%s\" is not recognised", sub[0]);
-         goto EXPAND_FAILED;
-         }
+       type = HMAC_MD5;
+       use_base = &md5_base;
+       hashlen = 16;
+       hashblocklen = 64;
+       }
+      else if (Ustrcmp(sub[0], "sha1") == 0)
+       {
+       type = HMAC_SHA1;
+       use_base = &sha1_ctx;
+       hashlen = 20;
+       hashblocklen = 64;
+       }
+      else
+       {
+       expand_string_message =
+         string_sprintf("hmac algorithm \"%s\" is not recognised", sub[0]);
+       goto EXPAND_FAILED;
+       }
 
-       keyptr = sub[1];
-       keylen = Ustrlen(keyptr);
+      keyptr = sub[1];
+      keylen = Ustrlen(keyptr);
 
-       /* If the key is longer than the hash block length, then hash the key
-       first */
+      /* If the key is longer than the hash block length, then hash the key
+      first */
 
-       if (keylen > hashblocklen)
-         {
-         chash_start(type, use_base);
-         chash_end(type, use_base, keyptr, keylen, keyhash);
-         keyptr = keyhash;
-         keylen = hashlen;
-         }
+      if (keylen > hashblocklen)
+       {
+       chash_start(type, use_base);
+       chash_end(type, use_base, keyptr, keylen, keyhash);
+       keyptr = keyhash;
+       keylen = hashlen;
+       }
 
-       /* Now make the inner and outer key values */
+      /* Now make the inner and outer key values */
 
-       memset(innerkey, 0x36, hashblocklen);
-       memset(outerkey, 0x5c, hashblocklen);
+      memset(innerkey, 0x36, hashblocklen);
+      memset(outerkey, 0x5c, hashblocklen);
 
-       for (int i = 0; i < keylen; i++)
-         {
-         innerkey[i] ^= keyptr[i];
-         outerkey[i] ^= keyptr[i];
-         }
+      for (int i = 0; i < keylen; i++)
+       {
+       innerkey[i] ^= keyptr[i];
+       outerkey[i] ^= keyptr[i];
+       }
 
-       /* Now do the hashes */
+      /* Now do the hashes */
 
-       chash_start(type, use_base);
-       chash_mid(type, use_base, innerkey);
-       chash_end(type, use_base, sub[2], Ustrlen(sub[2]), innerhash);
+      chash_start(type, use_base);
+      chash_mid(type, use_base, innerkey);
+      chash_end(type, use_base, sub[2], Ustrlen(sub[2]), innerhash);
 
-       chash_start(type, use_base);
-       chash_mid(type, use_base, outerkey);
-       chash_end(type, use_base, innerhash, hashlen, finalhash);
+      chash_start(type, use_base);
+      chash_mid(type, use_base, outerkey);
+      chash_end(type, use_base, innerhash, hashlen, finalhash);
 
-       /* Encode the final hash as a hex string */
+      /* Encode the final hash as a hex string */
 
-       p = finalhash_hex;
-       for (int i = 0; i < hashlen; i++)
-         {
-         *p++ = hex_digits[(finalhash[i] & 0xf0) >> 4];
-         *p++ = hex_digits[finalhash[i] & 0x0f];
-         }
+      p = finalhash_hex;
+      for (int i = 0; i < hashlen; i++)
+       {
+       *p++ = hex_digits[(finalhash[i] & 0xf0) >> 4];
+       *p++ = hex_digits[finalhash[i] & 0x0f];
+       }
 
-       DEBUG(D_any) debug_printf("HMAC[%s](%.*s,%s)=%.*s\n",
-         sub[0], (int)keylen, keyptr, sub[2], hashlen*2, finalhash_hex);
+      DEBUG(D_any) debug_printf("HMAC[%s](%.*s,%s)=%.*s\n",
+       sub[0], (int)keylen, keyptr, sub[2], hashlen*2, finalhash_hex);
 
-       yield = string_catn(yield, finalhash_hex, hashlen*2);
-       }
-      continue;
+      yield = string_catn(yield, finalhash_hex, hashlen*2);
+      break;
       }
 
     /* Handle global substitution for "sg" - like Perl's s/xxx/yyy/g operator.
@@ -5876,6 +5866,7 @@ while (*s)
         case 3: goto EXPAND_FAILED;
         }
 
+      /*XXX no handling of skipping? */
       /* Compile the regular expression */
 
       if (!(re = pcre2_compile((PCRE2_SPTR)sub[1], PCRE2_ZERO_TERMINATED,
@@ -5938,8 +5929,7 @@ while (*s)
 
         /* Copy the characters before the match, plus the expanded insertion. */
 
-       if (ovec[0] > moffset)
-         yield = string_catn(yield, subject + moffset, ovec[0] - moffset);
+       yield = string_catn(yield, subject + moffset, ovec[0] - moffset);
 
         if (!(insert = expand_string(sub[2])))
          goto EXPAND_FAILED;
@@ -5967,7 +5957,8 @@ while (*s)
 
       restore_expand_strings(save_expand_nmax, save_expand_nstring,
         save_expand_nlength);
-      continue;
+      if (skipping) continue;
+      break;
       }
 
     /* Handle keyed and numbered substring extraction. If the first argument
@@ -6202,7 +6193,8 @@ while (*s)
       restore_expand_strings(save_expand_nmax, save_expand_nstring,
         save_expand_nlength);
 
-      continue;
+      if (skipping) continue;
+      break;
       }
 
     /* return the Nth item from a list */
@@ -6299,7 +6291,8 @@ while (*s)
       restore_expand_strings(save_expand_nmax, save_expand_nstring,
         save_expand_nlength);
 
-      continue;
+      if (skipping) continue;
+      break;
       }
 
     case EITEM_LISTQUOTE:
@@ -6317,7 +6310,8 @@ while (*s)
        yield = string_catn(yield, sub[1], 1);
        }
       else yield = string_catn(yield, US" ", 1);
-      continue;
+      if (skipping) continue;
+      break;
       }
 
 #ifndef DISABLE_TLS
@@ -6395,7 +6389,8 @@ while (*s)
 
       restore_expand_strings(save_expand_nmax, save_expand_nstring,
         save_expand_nlength);
-      continue;
+      if (skipping) continue;
+      break;
       }
 #endif /*DISABLE_TLS*/
 
@@ -6549,6 +6544,9 @@ while (*s)
         item of the output list, add in a space if the new item begins with the
         separator character, or is an empty string. */
 
+/*XXX is there not a standard support function for this, appending to a list? */
+/* yes, string_append_listele(), but it depends on lack of text before the list */
+
         if (  yield && yield->ptr != save_ptr
           && (temp[0] == *outsep || temp[0] == 0))
           yield = string_catn(yield, US" ", 1);
@@ -6595,7 +6593,8 @@ while (*s)
       /* Restore preserved $item */
 
       iterate_item = save_iterate_item;
-      continue;
+      if (skipping) continue;
+      break;
       }
 
     case EITEM_SORT:
@@ -6708,7 +6707,7 @@ while (*s)
 
          /* field for comparison */
          if (!(dstfield = string_nextinlist(&dstkeylist, &sep, NULL, 0)))
-           goto sort_mismatch;
+           goto SORT_MISMATCH;
 
          /* String-comparator names start with a letter; numeric names do not */
 
@@ -6729,7 +6728,7 @@ while (*s)
            while ((dstitem = string_nextinlist(&dstlist, &sep, NULL, 0)))
              {
              if (!(dstfield = string_nextinlist(&dstkeylist, &sep, NULL, 0)))
-               goto sort_mismatch;
+               goto SORT_MISMATCH;
              newlist = string_append_listele(newlist, sep, dstitem);
              newkeylist = string_append_listele(newkeylist, sep, dstfield);
              }
@@ -6760,9 +6759,9 @@ while (*s)
 
       /* Restore preserved $item */
       iterate_item = save_iterate_item;
-      continue;
+      break;
 
-      sort_mismatch:
+      SORT_MISMATCH:
        expand_string_message = US"Internal error in sort (list mismatch)";
        goto EXPAND_FAILED;
       }
@@ -6821,7 +6820,7 @@ while (*s)
           log_write(0, LOG_MAIN|LOG_PANIC, "%s", expand_string_message);
           goto EXPAND_FAILED;
           }
-        t = store_get_perm(sizeof(tree_node) + Ustrlen(argv[0]), is_tainted(argv[0]));
+        t = store_get_perm(sizeof(tree_node) + Ustrlen(argv[0]), argv[0]);
         Ustrcpy(t->name, argv[0]);
         t->data.ptr = handle;
         (void)tree_insertnode(&dlobj_anchor, t);
@@ -6861,7 +6860,7 @@ while (*s)
         }
 
       if (result) yield = string_cat(yield, result);
-      continue;
+      break;
       }
 #endif /* EXPAND_DLFUNC */
 
@@ -6895,7 +6894,8 @@ while (*s)
         case 1: goto EXPAND_FAILED;          /* when all is well, the */
         case 2: goto EXPAND_FAILED_CURLY;    /* returned value is 0 */
         }
-      continue;
+      if (skipping) continue;
+      break;
       }
 
 #ifdef SUPPORT_SRS
@@ -6976,10 +6976,25 @@ while (*s)
       /* @$original_domain */
       yield = string_catn(yield, US"@", 1);
       yield = string_cat(yield, sub[2]);
-      continue;
+
+      if (skipping) continue;
+      break;
       }
 #endif /*SUPPORT_SRS*/
+
+    default:
+      goto NOT_ITEM;
     }  /* EITEM_* switch */
+    /*NOTREACHED*/
+
+  DEBUG(D_expand)
+    if (yield && (start > 0 || *s))    /* only if not the sole expansion of the line */
+      debug_expansion_interim(US"item-res",
+                             yield->s + start, yield->ptr - start, skipping);
+  continue;
+
+NOT_ITEM: ;
+  }
 
   /* Control reaches here if the name is not recognized as one of the more
   complicated expansion items. Check for the "operator" syntax (name terminated
@@ -6989,10 +7004,9 @@ while (*s)
   if (*s == ':')
     {
     int c;
-    uschar *arg = NULL;
-    uschar *sub;
+    uschar * arg = NULL, * sub;
 #ifndef DISABLE_TLS
-    var_entry *vp = NULL;
+    var_entry * vp = NULL;
 #endif
 
     /* Owing to an historical mis-design, an underscore may be part of the
@@ -7169,14 +7183,14 @@ while (*s)
        }
 
       case EOP_MD5:
-  #ifndef DISABLE_TLS
+#ifndef DISABLE_TLS
        if (vp && *(void **)vp->value)
          {
          uschar * cp = tls_cert_fprt_md5(*(void **)vp->value);
          yield = string_cat(yield, cp);
          }
        else
-  #endif
+#endif
          {
          md5 base;
          uschar digest[16];
@@ -7188,14 +7202,14 @@ while (*s)
        break;
 
       case EOP_SHA1:
-  #ifndef DISABLE_TLS
+#ifndef DISABLE_TLS
        if (vp && *(void **)vp->value)
          {
          uschar * cp = tls_cert_fprt_sha1(*(void **)vp->value);
          yield = string_cat(yield, cp);
          }
        else
-  #endif
+#endif
          {
          hctx h;
          uschar digest[20];
@@ -7208,7 +7222,7 @@ while (*s)
 
       case EOP_SHA2:
       case EOP_SHA256:
-  #ifdef EXIM_HAVE_SHA2
+#ifdef EXIM_HAVE_SHA2
        if (vp && *(void **)vp->value)
          if (c == EOP_SHA256)
            yield = string_cat(yield, tls_cert_fprt_sha256(*(void **)vp->value));
@@ -7235,13 +7249,13 @@ while (*s)
          while (b.len-- > 0)
            yield = string_fmt_append(yield, "%02X", *b.data++);
          }
-  #else
+#else
          expand_string_message = US"sha256 only supported with TLS";
-  #endif
+#endif
        break;
 
       case EOP_SHA3:
-  #ifdef EXIM_HAVE_SHA3
+#ifdef EXIM_HAVE_SHA3
        {
        hctx h;
        blob b;
@@ -7264,10 +7278,10 @@ while (*s)
          yield = string_fmt_append(yield, "%02X", *b.data++);
        }
        break;
-  #else
+#else
        expand_string_message = US"sha3 only supported with GnuTLS 3.5.0 + or OpenSSL 1.1.1 +";
        goto EXPAND_FAILED;
-  #endif
+#endif
 
       /* Convert hex encoding to base64 encoding */
 
@@ -7335,7 +7349,7 @@ while (*s)
       case EOP_LISTCOUNT:
        {
        int cnt = 0, sep = 0;
-       uschar * buf = store_get(2, is_tainted(sub));
+       uschar * buf = store_get(2, sub);
 
        while (string_nextinlist(CUSS &sub, &sep, buf, 1)) cnt++;
        yield = string_fmt_append(yield, "%d", cnt);
@@ -7595,10 +7609,10 @@ while (*s)
            goto EXPAND_FAILED;
            }
 
-        if (lookup_list[n]->quote)
-          sub = (lookup_list[n]->quote)(sub, opt);
-        else if (opt)
-         sub = NULL;
+         if (lookup_list[n]->quote)
+           sub = (lookup_list[n]->quote)(sub, opt, (unsigned)n);
+         else if (opt)
+           sub = NULL;
 
          if (!sub)
            {
@@ -7658,7 +7672,7 @@ while (*s)
 
        case EOP_FROM_UTF8:
          {
-         uschar * buff = store_get(4, is_tainted(sub));
+         uschar * buff = store_get(4, sub);
          while (*sub)
            {
            int c;
@@ -7684,13 +7698,10 @@ while (*s)
 
          /* 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), is_tainted(sub));
-           }
+         if (!yield->s || !yield->ptr)
+           yield->s = store_get(yield->size = Ustrlen(sub), sub);
+         else if (is_incompatible(yield->s, sub))
+           gstring_rebuffer(yield, sub);
 
          /* Check the UTF-8, byte-by-byte */
 
@@ -7771,7 +7782,7 @@ while (*s)
          break;
          }
 
-  #ifdef SUPPORT_I18N
+#ifdef SUPPORT_I18N
        case EOP_UTF8_DOMAIN_TO_ALABEL:
          {
          uschar * error = NULL;
@@ -7832,7 +7843,7 @@ while (*s)
          yield = string_cat(yield, s);
          break;
          }
-  #endif       /* EXPERIMENTAL_INTERNATIONAL */
+#endif /* EXPERIMENTAL_INTERNATIONAL */
 
        /* escape turns all non-printing characters into escape sequences. */
 
@@ -7908,13 +7919,13 @@ while (*s)
        case EOP_STR2B64:
        case EOP_BASE64:
          {
-  #ifndef DISABLE_TLS
+#ifndef DISABLE_TLS
          uschar * s = vp && *(void **)vp->value
            ? tls_cert_der_b64(*(void **)vp->value)
            : b64encode(CUS sub, Ustrlen(sub));
-  #else
+#else
          uschar * s = b64encode(CUS sub, Ustrlen(sub));
-  #endif
+#endif
          yield = string_cat(yield, s);
          break;
          }
@@ -8133,7 +8144,7 @@ while (*s)
          if (tainted)
            {
            debug_printf_indent("%s     \\__", skipping ? "|     " : "      ");
-           debug_printf("(tainted)\n");
+           debug_print_taint(yield->s);
            }
          }
        else
@@ -8146,7 +8157,7 @@ while (*s)
            debug_printf_indent("%s",
              skipping
              ? UTF8_VERT "             " : "           " UTF8_UP_RIGHT UTF8_HORIZ UTF8_HORIZ);
-           debug_printf("(tainted)\n");
+           debug_print_taint(yield->s);
            }
          }
        }
@@ -8170,13 +8181,13 @@ while (*s)
     gstring * g = NULL;
 
     if (!yield)
-      g = store_get(sizeof(gstring), FALSE);
+      g = store_get(sizeof(gstring), GET_UNTAINTED);
     else if (yield->ptr == 0)
       {
       if (resetok) reset_point = store_reset(reset_point);
       yield = NULL;
       reset_point = store_mark();
-      g = store_get(sizeof(gstring), FALSE);   /* alloc _before_ calling find_variable() */
+      g = store_get(sizeof(gstring), GET_UNTAINTED);   /* alloc _before_ calling find_variable() */
       }
     if (!(value = find_variable(name, FALSE, skipping, &newsize)))
       {
@@ -8242,8 +8253,10 @@ DEBUG(D_expand)
     debug_printf_indent("%sresult: %s\n",
       skipping ? "|-----" : "\\_____", yield->s);
     if (tainted)
-      debug_printf_indent("%s     \\__(tainted)\n",
-       skipping ? "|     " : "      ");
+      {
+      debug_printf_indent("%s     \\__", skipping ? "|     " : "      ");
+      debug_print_taint(yield->s);
+      }
     if (skipping)
       debug_printf_indent("\\___skipping: result is not used\n");
     }
@@ -8257,9 +8270,12 @@ DEBUG(D_expand)
       skipping ? UTF8_VERT_RIGHT : UTF8_UP_RIGHT,
       yield->s);
     if (tainted)
-      debug_printf_indent("%s(tainted)\n",
+      {
+      debug_printf_indent("%s",
        skipping
        ? UTF8_VERT "             " : "           " UTF8_UP_RIGHT UTF8_HORIZ UTF8_HORIZ);
+      debug_print_taint(yield->s);
+      }
     if (skipping)
       debug_printf_indent(UTF8_UP_RIGHT UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ
        "skipping: result is not used\n");