Tidying: coverity issues
[exim.git] / src / src / expand.c
index 73cb99ef8bcdd7577d230d231e64df6cb763ba81..922519eb9bf9d78857bc3098d7a78f3fc83103b3 100644 (file)
@@ -480,10 +480,6 @@ static var_entry var_table[] = {
   { "dcc_header",          vtype_stringptr,   &dcc_header },
   { "dcc_result",          vtype_stringptr,   &dcc_result },
 #endif
-#ifdef WITH_OLD_DEMIME
-  { "demime_errorlevel",   vtype_int,         &demime_errorlevel },
-  { "demime_reason",       vtype_stringptr,   &demime_reason },
-#endif
 #ifndef DISABLE_DKIM
   { "dkim_algo",           vtype_dkim,        (void *)DKIM_ALGO },
   { "dkim_bodylength",     vtype_dkim,        (void *)DKIM_BODYLENGTH },
@@ -532,9 +528,6 @@ static var_entry var_table[] = {
   { "exim_path",           vtype_stringptr,   &exim_path },
   { "exim_uid",            vtype_uid,         &exim_uid },
   { "exim_version",        vtype_stringptr,   &version_string },
-#ifdef WITH_OLD_DEMIME
-  { "found_extension",     vtype_stringptr,   &found_extension },
-#endif
   { "headers_added",       vtype_string_func, &fn_hdrs_added },
   { "home",                vtype_stringptr,   &deliver_home },
   { "host",                vtype_stringptr,   &deliver_host },
@@ -1676,9 +1669,8 @@ if (!enable_dollar_recipients) return NULL; else
   uschar * s = store_get(size);
   for (i = 0; i < recipients_count; i++)
     {
-    if (i != 0) s = string_cat(s, &size, &ptr, US", ", 2);
-    s = string_cat(s, &size, &ptr, recipients_list[i].address,
-      Ustrlen(recipients_list[i].address));
+    if (i != 0) s = string_catn(s, &size, &ptr, US", ", 2);
+    s = string_cat(s, &size, &ptr, recipients_list[i].address);
     }
   s[ptr] = 0;     /* string_cat() leaves room */
   return s;
@@ -1999,12 +1991,17 @@ for (i = 0; i < n; i++)
   {
   if (*s != '{')
     {
-    if (i < m) return 1;
+    if (i < m)
+      {
+      expand_string_message = string_sprintf("Not enough arguments for '%s' "
+       "(min is %d)", name, m);
+      return 1;
+      }
     sub[i] = NULL;
     break;
     }
-  sub[i] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, resetok);
-  if (sub[i] == NULL) return 3;
+  if (!(sub[i] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, resetok)))
+    return 3;
   if (*s++ != '}') return 1;
   while (isspace(*s)) s++;
   }
@@ -2012,10 +2009,11 @@ if (check_end && *s++ != '}')
   {
   if (s[-1] == '{')
     {
-    expand_string_message = string_sprintf("Too many arguments for \"%s\" "
+    expand_string_message = string_sprintf("Too many arguments for '%s' "
       "(max is %d)", name, n);
     return 2;
     }
+  expand_string_message = string_sprintf("missing '}' after '%s'", name);
   return 1;
   }
 
@@ -2382,7 +2380,7 @@ switch(cond_type)
           lookup_value = NULL;
          if (user_msg)
            {
-            lookup_value = string_cat(NULL, &size, &ptr, user_msg, Ustrlen(user_msg));
+            lookup_value = string_cat(NULL, &size, &ptr, user_msg);
             lookup_value[ptr] = '\0';
            }
          *yield = cond == testfor;
@@ -3173,6 +3171,7 @@ process_yesno(BOOL skipping, BOOL yes, uschar *save_lookup, const uschar **sptr,
 int rc = 0;
 const uschar *s = *sptr;    /* Local value */
 uschar *sub1, *sub2;
+const uschar * errwhere;
 
 /* If there are no following strings, we substitute the contents of $value for
 lookups and for extractions in the success case. For the ${if item, the string
@@ -3184,13 +3183,12 @@ if (*s == '}')
   {
   if (type[0] == 'i')
     {
-    if (yes) *yieldptr = string_cat(*yieldptr, sizeptr, ptrptr, US"true", 4);
+    if (yes) *yieldptr = string_catn(*yieldptr, sizeptr, ptrptr, US"true", 4);
     }
   else
     {
     if (yes && lookup_value)
-      *yieldptr = string_cat(*yieldptr, sizeptr, ptrptr, lookup_value,
-        Ustrlen(lookup_value));
+      *yieldptr = string_cat(*yieldptr, sizeptr, ptrptr, lookup_value);
     lookup_value = save_lookup;
     }
   s++;
@@ -3199,7 +3197,11 @@ if (*s == '}')
 
 /* The first following string must be braced. */
 
-if (*s++ != '{') goto FAILED_CURLY;
+if (*s++ != '{')
+  {
+  errwhere = US"'yes' part did not start with '{'";
+  goto FAILED_CURLY;
+  }
 
 /* Expand the first substring. Forced failures are noticed only if we actually
 want this string. Set skipping in the call in the fail case (this will always
@@ -3208,12 +3210,16 @@ be the case if we were already skipping). */
 sub1 = expand_string_internal(s, TRUE, &s, !yes, TRUE, resetok);
 if (sub1 == NULL && (yes || !expand_string_forcedfail)) goto FAILED;
 expand_string_forcedfail = FALSE;
-if (*s++ != '}') goto FAILED_CURLY;
+if (*s++ != '}')
+  {
+  errwhere = US"'yes' part did not end with '}'";
+  goto FAILED_CURLY;
+  }
 
 /* If we want the first string, add it to the output */
 
 if (yes)
-  *yieldptr = string_cat(*yieldptr, sizeptr, ptrptr, sub1, Ustrlen(sub1));
+  *yieldptr = string_cat(*yieldptr, sizeptr, ptrptr, sub1);
 
 /* If this is called from a lookup/env or a (cert)extract, we want to restore
 $value to what it was at the start of the item, so that it has this value
@@ -3234,12 +3240,16 @@ if (*s == '{')
   sub2 = expand_string_internal(s+1, TRUE, &s, yes || skipping, TRUE, resetok);
   if (sub2 == NULL && (!yes || !expand_string_forcedfail)) goto FAILED;
   expand_string_forcedfail = FALSE;
-  if (*s++ != '}') goto FAILED_CURLY;
+  if (*s++ != '}')
+    {
+    errwhere = US"'no' part did not start with '{'";
+    goto FAILED_CURLY;
+    }
 
   /* If we want the second string, add it to the output */
 
   if (!yes)
-    *yieldptr = string_cat(*yieldptr, sizeptr, ptrptr, sub2, Ustrlen(sub2));
+    *yieldptr = string_cat(*yieldptr, sizeptr, ptrptr, sub2);
   }
 
 /* If there is no second string, but the word "fail" is present when the use of
@@ -3257,7 +3267,11 @@ else if (*s != '}')
     if (!yes && !skipping)
       {
       while (isspace(*s)) s++;
-      if (*s++ != '}') goto FAILED_CURLY;
+      if (*s++ != '}')
+        {
+       errwhere = US"did not close with '}' after forcedfail";
+       goto FAILED_CURLY;
+       }
       expand_string_message =
         string_sprintf("\"%s\" failed and \"fail\" requested", type);
       expand_string_forcedfail = TRUE;
@@ -3275,23 +3289,30 @@ else if (*s != '}')
 /* All we have to do now is to check on the final closing brace. */
 
 while (isspace(*s)) s++;
-if (*s++ == '}') goto RETURN;
-
-/* Get here if there is a bracketing failure */
-
-FAILED_CURLY:
-rc++;
-
-/* Get here for other failures */
-
-FAILED:
-rc++;
+if (*s++ != '}')
+  {
+  errwhere = US"did not close with '}'";
+  goto FAILED_CURLY;
+  }
 
-/* Update the input pointer value before returning */
 
 RETURN:
+/* Update the input pointer value before returning */
 *sptr = s;
 return rc;
+
+FAILED_CURLY:
+  /* Get here if there is a bracketing failure */
+  expand_string_message = string_sprintf(
+    "curly-bracket problem in conditional yes/no parsing: %s\n"
+    " remaining string is '%s'", errwhere, --s);
+  rc = 2;
+  goto RETURN;
+
+FAILED:
+  /* Get here for other failures */
+  rc = 1;
+  goto RETURN;
 }
 
 
@@ -3408,9 +3429,9 @@ if (key_num == NULL)
 if (Ustrlen(key) > 64)
   return NULL;
 
-hash_source = string_cat(NULL,&size,&offset,key_num,1);
-hash_source = string_cat(hash_source,&size,&offset,daystamp,3);
-hash_source = string_cat(hash_source,&size,&offset,address,Ustrlen(address));
+hash_source = string_catn(NULL, &size, &offset, key_num, 1);
+hash_source = string_catn(hash_source, &size, &offset, daystamp, 3);
+hash_source = string_cat(hash_source, &size, &offset, address);
 hash_source[offset] = '\0';
 
 DEBUG(D_expand) debug_printf("prvs: hash source is '%s'\n", hash_source);
@@ -3474,9 +3495,9 @@ while (Ufgets(buffer, sizeof(buffer), f))
   {
   int len = Ustrlen(buffer);
   if (eol && buffer[len-1] == '\n') len--;
-  yield = string_cat(yield, sizep, ptrp, buffer, len);
+  yield = string_catn(yield, sizep, ptrp, buffer, len);
   if (buffer[len] != 0)
-    yield = string_cat(yield, sizep, ptrp, eol, eollen);
+    yield = string_catn(yield, sizep, ptrp, eol, eollen);
   }
 
 if (yield) yield[*ptrp] = 0;
@@ -3830,13 +3851,16 @@ expand_string_internal(const uschar *string, BOOL ket_ends, const uschar **left,
 {
 int ptr = 0;
 int size = Ustrlen(string)+ 64;
-int item_type;
 uschar *yield = store_get(size);
+int item_type;
 const uschar *s = string;
 uschar *save_expand_nstring[EXPAND_MAXN+1];
 int save_expand_nlength[EXPAND_MAXN+1];
 BOOL resetok = TRUE;
 
+DEBUG(D_expand)
+  debug_printf("%s: %s\n", skipping ? "   scanning" : "considering", string);
+
 expand_string_forcedfail = FALSE;
 expand_string_message = US"";
 
@@ -3862,7 +3886,7 @@ while (*s != 0)
       {
       const uschar * t = s + 2;
       for (s = t; *s != 0; s++) if (*s == '\\' && s[1] == 'N') break;
-      yield = string_cat(yield, &size, &ptr, t, s - t);
+      yield = string_catn(yield, &size, &ptr, t, s - t);
       if (*s != 0) s += 2;
       }
 
@@ -3871,7 +3895,7 @@ while (*s != 0)
       uschar ch[1];
       ch[0] = string_interpret_escape(&s);
       s++;
-      yield = string_cat(yield, &size, &ptr, ch, 1);
+      yield = string_catn(yield, &size, &ptr, ch, 1);
       }
 
     continue;
@@ -3886,7 +3910,7 @@ while (*s != 0)
 
   if (*s != '$' || !honour_dollar)
     {
-    yield = string_cat(yield, &size, &ptr, s++, 1);
+    yield = string_catn(yield, &size, &ptr, s++, 1);
     continue;
     }
 
@@ -3964,7 +3988,7 @@ while (*s != 0)
       size = newsize;
       ptr = len;
       }
-    else yield = string_cat(yield, &size, &ptr, value, len);
+    else yield = string_catn(yield, &size, &ptr, value, len);
 
     continue;
     }
@@ -3974,7 +3998,7 @@ while (*s != 0)
     int n;
     s = read_cnumber(&n, s);
     if (n >= 0 && n <= expand_nmax)
-      yield = string_cat(yield, &size, &ptr, expand_nstring[n],
+      yield = string_catn(yield, &size, &ptr, expand_nstring[n],
         expand_nlength[n]);
     continue;
     }
@@ -4000,7 +4024,7 @@ while (*s != 0)
       goto EXPAND_FAILED;
       }
     if (n >= 0 && n <= expand_nmax)
-      yield = string_cat(yield, &size, &ptr, expand_nstring[n],
+      yield = string_catn(yield, &size, &ptr, expand_nstring[n],
         expand_nlength[n]);
     continue;
     }
@@ -4052,7 +4076,7 @@ while (*s != 0)
          DEBUG(D_expand)
            debug_printf("acl expansion yield: %s\n", user_msg);
          if (user_msg)
-            yield = string_cat(yield, &size, &ptr, user_msg, Ustrlen(user_msg));
+            yield = string_cat(yield, &size, &ptr, user_msg);
          continue;
 
        case DEFER:
@@ -4144,7 +4168,7 @@ while (*s != 0)
                          sub_arg[1][0], sub_arg[2], &expand_string_message)))
        goto EXPAND_FAILED;
       if (!skipping)
-       yield = string_cat(yield, &size, &ptr, encoded, Ustrlen(encoded));
+       yield = string_cat(yield, &size, &ptr, encoded);
       continue;
       }
 #endif
@@ -4180,8 +4204,12 @@ while (*s != 0)
       if (*s == '{')                                   /*}*/
         {
         key = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok);
-        if (key == NULL) goto EXPAND_FAILED;           /*{*/
-        if (*s++ != '}') goto EXPAND_FAILED_CURLY;
+        if (!key) goto EXPAND_FAILED;                  /*{{*/
+        if (*s++ != '}')
+         {
+         expand_string_message = US"missing '}' after lookup key";
+         goto EXPAND_FAILED_CURLY;
+         }
         while (isspace(*s)) s++;
         }
       else key = NULL;
@@ -4244,10 +4272,18 @@ while (*s != 0)
       queries that also require a file name (e.g. sqlite), the file name comes
       first. */
 
-      if (*s != '{') goto EXPAND_FAILED_CURLY;
+      if (*s != '{')
+        {
+       expand_string_message = US"missing '{' for lookup file-or-query arg";
+       goto EXPAND_FAILED_CURLY;
+       }
       filename = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok);
       if (filename == NULL) goto EXPAND_FAILED;
-      if (*s++ != '}') goto EXPAND_FAILED_CURLY;
+      if (*s++ != '}')
+        {
+       expand_string_message = US"missing '}' closing lookup file-or-query arg";
+       goto EXPAND_FAILED_CURLY;
+       }
       while (isspace(*s)) s++;
 
       /* If this isn't a single-key+file lookup, re-arrange the variables
@@ -4255,15 +4291,13 @@ while (*s != 0)
       there is just a "key", and no file name. For the special query-style +
       file types, the query (i.e. "key") starts with a file name. */
 
-      if (key == NULL)
+      if (!key)
         {
         while (isspace(*filename)) filename++;
         key = filename;
 
         if (mac_islookup(stype, lookup_querystyle))
-          {
           filename = NULL;
-          }
         else
           {
           if (*filename != '/')
@@ -4473,14 +4507,14 @@ while (*s != 0)
       /* Now separate the domain from the local part */
       *domain++ = '\0';
 
-      yield = string_cat(yield,&size,&ptr,US"prvs=",5);
-      string_cat(yield,&size,&ptr,(sub_arg[2] != NULL) ? sub_arg[2] : US"0", 1);
-      string_cat(yield,&size,&ptr,prvs_daystamp(7),3);
-      string_cat(yield,&size,&ptr,p,6);
-      string_cat(yield,&size,&ptr,US"=",1);
-      string_cat(yield,&size,&ptr,sub_arg[0],Ustrlen(sub_arg[0]));
-      string_cat(yield,&size,&ptr,US"@",1);
-      string_cat(yield,&size,&ptr,domain,Ustrlen(domain));
+      yield = string_catn(yield, &size, &ptr, US"prvs=", 5);
+      yield = string_catn(yield, &size, &ptr, sub_arg[2] ? sub_arg[2] : US"0", 1);
+      yield = string_catn(yield, &size, &ptr, prvs_daystamp(7), 3);
+      yield = string_catn(yield, &size, &ptr, p, 6);
+      yield = string_catn(yield, &size, &ptr, US"=", 1);
+      yield = string_cat (yield, &size, &ptr, sub_arg[0]);
+      yield = string_catn(yield, &size, &ptr, US"@", 1);
+      yield = string_cat (yield, &size, &ptr, domain);
 
       continue;
       }
@@ -4534,9 +4568,9 @@ while (*s != 0)
         DEBUG(D_expand) debug_printf("prvscheck domain: %s\n", domain);
 
         /* Set up expansion variables */
-        prvscheck_address = string_cat(NULL, &mysize, &myptr, local_part, Ustrlen(local_part));
-        string_cat(prvscheck_address,&mysize,&myptr,US"@",1);
-        string_cat(prvscheck_address,&mysize,&myptr,domain,Ustrlen(domain));
+        prvscheck_address = string_cat (NULL, &mysize, &myptr, local_part);
+        prvscheck_address = string_catn(prvscheck_address, &mysize, &myptr, US"@", 1);
+        prvscheck_address = string_cat (prvscheck_address, &mysize, &myptr, domain);
         prvscheck_address[myptr] = '\0';
         prvscheck_keynum = string_copy(key_num);
 
@@ -4602,10 +4636,8 @@ while (*s != 0)
           case 3: goto EXPAND_FAILED;
           }
 
-        if (sub_arg[0] == NULL || *sub_arg[0] == '\0')
-          yield = string_cat(yield,&size,&ptr,prvscheck_address,Ustrlen(prvscheck_address));
-        else
-          yield = string_cat(yield,&size,&ptr,sub_arg[0],Ustrlen(sub_arg[0]));
+       yield = string_cat(yield, &size, &ptr,
+         !sub_arg[0] || !*sub_arg[0] ? prvscheck_address : sub_arg[0]);
 
         /* Reset the "internal" variables afterwards, because they are in
         dynamic store that will be reclaimed if the expansion succeeded. */
@@ -4852,10 +4884,20 @@ while (*s != 0)
         {
         if (expand_string_internal(s+1, TRUE, &s, TRUE, TRUE, &resetok) == NULL)
           goto EXPAND_FAILED;
-        if (*s++ != '}') goto EXPAND_FAILED_CURLY;
+        if (*s++ != '}')
+         {
+         expand_string_message = US"missing '}' closing failstring for readsocket";
+         goto EXPAND_FAILED_CURLY;
+         }
         while (isspace(*s)) s++;
         }
-      if (*s++ != '}') goto EXPAND_FAILED_CURLY;
+
+    readsock_done:
+      if (*s++ != '}')
+        {
+       expand_string_message = US"missing '}' closing readsocket";
+       goto EXPAND_FAILED_CURLY;
+       }
       continue;
 
       /* Come here on failure to create socket, connect socket, write to the
@@ -4865,13 +4907,16 @@ while (*s != 0)
       SOCK_FAIL:
       if (*s != '{') goto EXPAND_FAILED;
       DEBUG(D_any) debug_printf("%s\n", expand_string_message);
-      arg = expand_string_internal(s+1, TRUE, &s, FALSE, TRUE, &resetok);
-      if (arg == NULL) goto EXPAND_FAILED;
-      yield = string_cat(yield, &size, &ptr, arg, Ustrlen(arg));
-      if (*s++ != '}') goto EXPAND_FAILED_CURLY;
+      if (!(arg = expand_string_internal(s+1, TRUE, &s, FALSE, TRUE, &resetok)))
+        goto EXPAND_FAILED;
+      yield = string_cat(yield, &size, &ptr, arg);
+      if (*s++ != '}')
+        {
+       expand_string_message = US"missing '}' closing failstring for readsocket";
+       goto EXPAND_FAILED_CURLY;
+       }
       while (isspace(*s)) s++;
-      if (*s++ != '}') goto EXPAND_FAILED_CURLY;
-      continue;
+      goto readsock_done;
       }
 
     /* Handle "run" to execute a program. */
@@ -4892,16 +4937,22 @@ while (*s != 0)
         }
 
       while (isspace(*s)) s++;
-      if (*s != '{') goto EXPAND_FAILED_CURLY;
+      if (*s != '{')
+        {
+       expand_string_message = US"missing '{' for command arg of run";
+       goto EXPAND_FAILED_CURLY;
+       }
       arg = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok);
       if (arg == NULL) goto EXPAND_FAILED;
       while (isspace(*s)) s++;
-      if (*s++ != '}') goto EXPAND_FAILED_CURLY;
+      if (*s++ != '}')
+        {
+       expand_string_message = US"missing '}' closing command arg of run";
+       goto EXPAND_FAILED_CURLY;
+       }
 
       if (skipping)   /* Just pretend it worked when we're skipping */
-        {
         runrc = 0;
-        }
       else
         {
         if (!transport_set_up_command(&argv,    /* anchor for arg list */
@@ -4998,7 +5049,7 @@ while (*s != 0)
         case 3: goto EXPAND_FAILED;
         }
 
-      yield = string_cat(yield, &size, &ptr, sub[0], Ustrlen(sub[0]));
+      yield = string_cat(yield, &size, &ptr, sub[0]);
       o2m = Ustrlen(sub[2]) - 1;
 
       if (o2m >= 0) for (; oldptr < ptr; oldptr++)
@@ -5075,7 +5126,7 @@ while (*s != 0)
           extract_substr(sub[2], val[0], val[1], &len);
 
       if (ret == NULL) goto EXPAND_FAILED;
-      yield = string_cat(yield, &size, &ptr, ret, len);
+      yield = string_catn(yield, &size, &ptr, ret, len);
       continue;
       }
 
@@ -5183,7 +5234,7 @@ while (*s != 0)
       DEBUG(D_any) debug_printf("HMAC[%s](%.*s,%.*s)=%.*s\n", sub[0],
         (int)keylen, keyptr, Ustrlen(sub[2]), sub[2], hashlen*2, finalhash_hex);
 
-      yield = string_cat(yield, &size, &ptr, finalhash_hex, hashlen*2);
+      yield = string_catn(yield, &size, &ptr, finalhash_hex, hashlen*2);
       }
 
     continue;
@@ -5254,7 +5305,7 @@ while (*s != 0)
             emptyopt = 0;
             continue;
             }
-          yield = string_cat(yield, &size, &ptr, subject+moffset, slen-moffset);
+          yield = string_catn(yield, &size, &ptr, subject+moffset, slen-moffset);
           break;
           }
 
@@ -5271,11 +5322,11 @@ while (*s != 0)
 
         /* Copy the characters before the match, plus the expanded insertion. */
 
-        yield = string_cat(yield, &size, &ptr, subject + moffset,
+        yield = string_catn(yield, &size, &ptr, subject + moffset,
           ovector[0] - moffset);
         insert = expand_string(sub[2]);
         if (insert == NULL) goto EXPAND_FAILED;
-        yield = string_cat(yield, &size, &ptr, insert, Ustrlen(insert));
+        yield = string_cat(yield, &size, &ptr, insert);
 
         moffset = ovector[1];
         moffsetextra = 0;
@@ -5308,7 +5359,7 @@ while (*s != 0)
     case EITEM_EXTRACT:
       {
       int i;
-      int j = 2;
+      int j;
       int field_number = 1;
       BOOL field_number_set = FALSE;
       uschar *save_lookup_value = lookup_value;
@@ -5318,30 +5369,49 @@ while (*s != 0)
 
       /* While skipping we cannot rely on the data for expansions being
       available (eg. $item) hence cannot decide on numeric vs. keyed.
-      Just read as many arguments as there are. */
+      Read a maximum of 5 arguments (inclding the yes/no) */
 
       if (skipping)
        {
         while (isspace(*s)) s++;
-        while (*s == '{')
+        for (j = 5; j > 0 && *s == '{'; j--)
          {
           if (!expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok))
            goto EXPAND_FAILED;                                 /*{*/
-          if (*s++ != '}') goto EXPAND_FAILED_CURLY;
+          if (*s++ != '}')
+           {
+           expand_string_message = US"missing '{' for arg of extract";
+           goto EXPAND_FAILED_CURLY;
+           }
+         while (isspace(*s)) s++;
+         }
+       if (  Ustrncmp(s, "fail", 4) == 0
+          && (s[4] == '}' || s[4] == ' ' || s[4] == '\t' || !s[4])
+          )
+         {
+         s += 4;
          while (isspace(*s)) s++;
          }
        if (*s != '}')
+         {
+         expand_string_message = US"missing '}' closing extract";
          goto EXPAND_FAILED_CURLY;
+         }
        }
 
-      else for (i = 0; i < j; i++) /* Read the proper number of arguments */
+      else for (i = 0, j = 2; i < j; i++) /* Read the proper number of arguments */
         {
         while (isspace(*s)) s++;
         if (*s == '{')                                                 /*}*/
           {
           sub[i] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok);
           if (sub[i] == NULL) goto EXPAND_FAILED;              /*{*/
-          if (*s++ != '}') goto EXPAND_FAILED_CURLY;
+          if (*s++ != '}')
+           {
+           expand_string_message = string_sprintf(
+             US"missing '}' closing arg %d of extract", i+1);
+           goto EXPAND_FAILED_CURLY;
+           }
 
           /* After removal of leading and trailing white space, the first
           argument must not be empty; if it consists entirely of digits
@@ -5382,7 +5452,12 @@ while (*s != 0)
              }
             }
           }
-        else goto EXPAND_FAILED_CURLY;
+        else
+         {
+         expand_string_message = string_sprintf(
+           US"missing '{' for arg %d of extract", i+1);
+         goto EXPAND_FAILED_CURLY;
+         }
         }
 
       /* Extract either the numbered or the keyed substring into $value. If
@@ -5435,11 +5510,20 @@ while (*s != 0)
         {
         while (isspace(*s)) s++;
         if (*s != '{')                                 /*}*/
+         {
+         expand_string_message = string_sprintf(
+           US"missing '{' for arg %d of listextract", i+1);
          goto EXPAND_FAILED_CURLY;
+         }
 
        sub[i] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok);
        if (!sub[i])     goto EXPAND_FAILED;            /*{*/
-       if (*s++ != '}') goto EXPAND_FAILED_CURLY;
+       if (*s++ != '}')
+         {
+         expand_string_message = string_sprintf(
+           US"missing '}' closing arg %d of listextract", i+1);
+         goto EXPAND_FAILED_CURLY;
+         }
 
        /* After removal of leading and trailing white space, the first
        argument must be numeric and nonempty. */
@@ -5522,10 +5606,17 @@ while (*s != 0)
       /* Read the field argument */
       while (isspace(*s)) s++;
       if (*s != '{')                                   /*}*/
+       {
+       expand_string_message = US"missing '{' for field arg of certextract";
        goto EXPAND_FAILED_CURLY;
+       }
       sub[0] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok);
       if (!sub[0])     goto EXPAND_FAILED;             /*{*/
-      if (*s++ != '}') goto EXPAND_FAILED_CURLY;
+      if (*s++ != '}')
+        {
+       expand_string_message = US"missing '}' closing field arg of certextract";
+       goto EXPAND_FAILED_CURLY;
+       }
       /* strip spaces fore & aft */
       {
       int len;
@@ -5542,7 +5633,10 @@ while (*s != 0)
       /* inspect the cert argument */
       while (isspace(*s)) s++;
       if (*s != '{')                                   /*}*/
+       {
+       expand_string_message = US"missing '{' for cert variable arg of certextract";
        goto EXPAND_FAILED_CURLY;
+       }
       if (*++s != '$')
         {
        expand_string_message = US"second argument of \"certextract\" must "
@@ -5551,7 +5645,11 @@ while (*s != 0)
        }
       sub[1] = expand_string_internal(s+1, TRUE, &s, skipping, FALSE, &resetok);
       if (!sub[1])     goto EXPAND_FAILED;             /*{*/
-      if (*s++ != '}') goto EXPAND_FAILED_CURLY;
+      if (*s++ != '}')
+        {
+       expand_string_message = US"missing '}' closing cert variable arg of certextract";
+       goto EXPAND_FAILED_CURLY;
+       }
 
       if (skipping)
        lookup_value = NULL;
@@ -5595,25 +5693,48 @@ while (*s != 0)
       uschar *save_lookup_value = lookup_value;
 
       while (isspace(*s)) s++;
-      if (*s++ != '{') goto EXPAND_FAILED_CURLY;
+      if (*s++ != '{')
+        {
+       expand_string_message =
+         string_sprintf("missing '{' for first arg of %s", name);
+       goto EXPAND_FAILED_CURLY;
+       }
 
       list = expand_string_internal(s, TRUE, &s, skipping, TRUE, &resetok);
       if (list == NULL) goto EXPAND_FAILED;
-      if (*s++ != '}') goto EXPAND_FAILED_CURLY;
+      if (*s++ != '}')
+        {
+       expand_string_message =
+         string_sprintf("missing '}' closing first arg of %s", name);
+       goto EXPAND_FAILED_CURLY;
+       }
 
       if (item_type == EITEM_REDUCE)
         {
        uschar * t;
         while (isspace(*s)) s++;
-        if (*s++ != '{') goto EXPAND_FAILED_CURLY;
+        if (*s++ != '{')
+         {
+         expand_string_message = US"missing '{' for second arg of reduce";
+         goto EXPAND_FAILED_CURLY;
+         }
         t = expand_string_internal(s, TRUE, &s, skipping, TRUE, &resetok);
         if (!t) goto EXPAND_FAILED;
         lookup_value = t;
-        if (*s++ != '}') goto EXPAND_FAILED_CURLY;
+        if (*s++ != '}')
+         {
+         expand_string_message = US"missing '}' closing second arg of reduce";
+         goto EXPAND_FAILED_CURLY;
+         }
         }
 
       while (isspace(*s)) s++;
-      if (*s++ != '{') goto EXPAND_FAILED_CURLY;
+      if (*s++ != '{')
+        {
+       expand_string_message =
+         string_sprintf("missing '{' for last arg of %s", name);
+       goto EXPAND_FAILED_CURLY;
+       }
 
       expr = s;
 
@@ -5711,7 +5832,7 @@ while (*s != 0)
         separator character, or is an empty string. */
 
         if (ptr != save_ptr && (temp[0] == *outsep || temp[0] == 0))
-          yield = string_cat(yield, &size, &ptr, US" ", 1);
+          yield = string_catn(yield, &size, &ptr, US" ", 1);
 
         /* Add the string in "temp" to the output list that we are building,
         This is done in chunks by searching for the separator character. */
@@ -5719,21 +5840,22 @@ while (*s != 0)
         for (;;)
           {
           size_t seglen = Ustrcspn(temp, outsep);
-            yield = string_cat(yield, &size, &ptr, temp, seglen + 1);
+
+         yield = string_catn(yield, &size, &ptr, temp, seglen + 1);
 
           /* If we got to the end of the string we output one character
           too many; backup and end the loop. Otherwise arrange to double the
           separator. */
 
           if (temp[seglen] == '\0') { ptr--; break; }
-          yield = string_cat(yield, &size, &ptr, outsep, 1);
+          yield = string_catn(yield, &size, &ptr, outsep, 1);
           temp += seglen + 1;
           }
 
         /* Output a separator after the string: we will remove the redundant
         final one at the end. */
 
-        yield = string_cat(yield, &size, &ptr, outsep, 1);
+        yield = string_catn(yield, &size, &ptr, outsep, 1);
         }   /* End of iteration over the list loop */
 
       /* REDUCE has generated no output above: output the final value of
@@ -5741,8 +5863,7 @@ while (*s != 0)
 
       if (item_type == EITEM_REDUCE)
         {
-        yield = string_cat(yield, &size, &ptr, lookup_value,
-          Ustrlen(lookup_value));
+        yield = string_cat(yield, &size, &ptr, lookup_value);
         lookup_value = save_lookup_value;  /* Restore $value */
         }
 
@@ -5768,28 +5889,52 @@ while (*s != 0)
       uschar *save_iterate_item = iterate_item;
 
       while (isspace(*s)) s++;
-      if (*s++ != '{') goto EXPAND_FAILED_CURLY;
+      if (*s++ != '{')
+        {
+        expand_string_message = US"missing '{' for list arg of sort";
+       goto EXPAND_FAILED_CURLY;
+       }
 
       srclist = expand_string_internal(s, TRUE, &s, skipping, TRUE, &resetok);
       if (!srclist) goto EXPAND_FAILED;
-      if (*s++ != '}') goto EXPAND_FAILED_CURLY;
+      if (*s++ != '}')
+        {
+        expand_string_message = US"missing '}' closing list arg of sort";
+       goto EXPAND_FAILED_CURLY;
+       }
 
       while (isspace(*s)) s++;
-      if (*s++ != '{') goto EXPAND_FAILED_CURLY;
+      if (*s++ != '{')
+        {
+        expand_string_message = US"missing '{' for comparator arg of sort";
+       goto EXPAND_FAILED_CURLY;
+       }
 
       cmp = expand_string_internal(s, TRUE, &s, skipping, FALSE, &resetok);
       if (!cmp) goto EXPAND_FAILED;
-      if (*s++ != '}') goto EXPAND_FAILED_CURLY;
+      if (*s++ != '}')
+        {
+        expand_string_message = US"missing '}' closing comparator arg of sort";
+       goto EXPAND_FAILED_CURLY;
+       }
 
       while (isspace(*s)) s++;
-      if (*s++ != '{') goto EXPAND_FAILED_CURLY;
+      if (*s++ != '{')
+        {
+        expand_string_message = US"missing '{' for extractor arg of sort";
+       goto EXPAND_FAILED_CURLY;
+       }
 
       xtract = s;
       tmp = expand_string_internal(s, TRUE, &s, TRUE, TRUE, &resetok);
       if (!tmp) goto EXPAND_FAILED;
       xtract = string_copyn(xtract, s - xtract);
 
-      if (*s++ != '}') goto EXPAND_FAILED_CURLY;
+      if (*s++ != '}')
+        {
+        expand_string_message = US"missing '}' closing extractor arg of sort";
+       goto EXPAND_FAILED_CURLY;
+       }
                                                        /*{*/
       if (*s++ != '}')
         {                                              /*{*/
@@ -5885,7 +6030,7 @@ while (*s != 0)
        }
 
       if (dstlist)
-       yield = string_cat(yield, &size, &ptr, dstlist, Ustrlen(dstlist));
+       yield = string_cat(yield, &size, &ptr, dstlist);
 
       /* Restore preserved $item */
       iterate_item = save_iterate_item;
@@ -5983,7 +6128,7 @@ while (*s != 0)
       if(status == OK)
         {
         if (result == NULL) result = US"";
-        yield = string_cat(yield, &size, &ptr, result, Ustrlen(result));
+        yield = string_cat(yield, &size, &ptr, result);
         continue;
         }
       else
@@ -6009,7 +6154,11 @@ while (*s != 0)
 
       key = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok);
       if (!key) goto EXPAND_FAILED;                    /*{*/
-      if (*s++ != '}') goto EXPAND_FAILED_CURLY;
+      if (*s++ != '}')
+        {
+        expand_string_message = US"missing '{' for name arg of env";
+       goto EXPAND_FAILED_CURLY;
+       }
 
       lookup_value = US getenv(CS key);
 
@@ -6073,7 +6222,12 @@ while (*s != 0)
          sub = expand_string_internal(s+2, TRUE, &s1, skipping,
                  FALSE, &resetok);
          if (!sub)       goto EXPAND_FAILED;           /*{*/
-         if (*s1 != '}') goto EXPAND_FAILED_CURLY;
+         if (*s1 != '}')
+           {
+           expand_string_message =
+             string_sprintf("missing '}' closing cert arg of %s", name);
+           goto EXPAND_FAILED_CURLY;
+           }
          if ((vp = find_var_ent(sub)) && vp->type == vtype_cert)
            {
            s = s1+1;
@@ -6113,7 +6267,7 @@ while (*s != 0)
           goto EXPAND_FAILED;
           }
         t = string_base62(n);
-        yield = string_cat(yield, &size, &ptr, t, Ustrlen(t));
+        yield = string_cat(yield, &size, &ptr, t);
         continue;
         }
 
@@ -6137,7 +6291,7 @@ while (*s != 0)
           n = n * BASE_62 + (t - base62_chars);
           }
         (void)sprintf(CS buf, "%ld", n);
-        yield = string_cat(yield, &size, &ptr, buf, Ustrlen(buf));
+        yield = string_cat(yield, &size, &ptr, buf);
         continue;
         }
 
@@ -6151,7 +6305,7 @@ while (*s != 0)
               expand_string_message);
           goto EXPAND_FAILED;
           }
-        yield = string_cat(yield, &size, &ptr, expanded, Ustrlen(expanded));
+        yield = string_cat(yield, &size, &ptr, expanded);
         continue;
         }
 
@@ -6160,7 +6314,7 @@ while (*s != 0)
         int count = 0;
         uschar *t = sub - 1;
         while (*(++t) != 0) { *t = tolower(*t); count++; }
-        yield = string_cat(yield, &size, &ptr, sub, count);
+        yield = string_catn(yield, &size, &ptr, sub, count);
         continue;
         }
 
@@ -6169,7 +6323,7 @@ while (*s != 0)
         int count = 0;
         uschar *t = sub - 1;
         while (*(++t) != 0) { *t = toupper(*t); count++; }
-        yield = string_cat(yield, &size, &ptr, sub, count);
+        yield = string_catn(yield, &size, &ptr, sub, count);
         continue;
         }
 
@@ -6178,7 +6332,7 @@ while (*s != 0)
        if (vp && *(void **)vp->value)
          {
          uschar * cp = tls_cert_fprt_md5(*(void **)vp->value);
-         yield = string_cat(yield, &size, &ptr, cp, Ustrlen(cp));
+         yield = string_cat(yield, &size, &ptr, cp);
          }
        else
 #endif
@@ -6190,7 +6344,7 @@ while (*s != 0)
          md5_start(&base);
          md5_end(&base, sub, Ustrlen(sub), digest);
          for(j = 0; j < 16; j++) sprintf(st+2*j, "%02x", digest[j]);
-         yield = string_cat(yield, &size, &ptr, US st, (int)strlen(st));
+         yield = string_cat(yield, &size, &ptr, US st);
          }
         continue;
 
@@ -6199,7 +6353,7 @@ while (*s != 0)
        if (vp && *(void **)vp->value)
          {
          uschar * cp = tls_cert_fprt_sha1(*(void **)vp->value);
-         yield = string_cat(yield, &size, &ptr, cp, Ustrlen(cp));
+         yield = string_cat(yield, &size, &ptr, cp);
          }
        else
 #endif
@@ -6211,7 +6365,7 @@ while (*s != 0)
          sha1_start(&base);
          sha1_end(&base, sub, Ustrlen(sub), digest);
          for(j = 0; j < 20; j++) sprintf(st+2*j, "%02X", digest[j]);
-         yield = string_cat(yield, &size, &ptr, US st, (int)strlen(st));
+         yield = string_cat(yield, &size, &ptr, US st);
          }
         continue;
 
@@ -6220,7 +6374,7 @@ while (*s != 0)
        if (vp && *(void **)vp->value)
          {
          uschar * cp = tls_cert_fprt_sha256(*(void **)vp->value);
-         yield = string_cat(yield, &size, &ptr, cp, (int)Ustrlen(cp));
+         yield = string_cat(yield, &size, &ptr, cp);
          }
        else
 #endif
@@ -6271,7 +6425,7 @@ while (*s != 0)
           }
 
         enc = b64encode(sub, out - sub);
-        yield = string_cat(yield, &size, &ptr, enc, Ustrlen(enc));
+        yield = string_cat(yield, &size, &ptr, enc);
         continue;
         }
 
@@ -6283,10 +6437,10 @@ while (*s != 0)
         while (*(++t) != 0)
           {
           if (*t < 0x21 || 0x7E < *t)
-            yield = string_cat(yield, &size, &ptr,
+            yield = string_catn(yield, &size, &ptr,
              string_sprintf("\\x%02x", *t), 4);
          else
-           yield = string_cat(yield, &size, &ptr, t, 1);
+           yield = string_catn(yield, &size, &ptr, t, 1);
           }
        continue;
        }
@@ -6302,7 +6456,7 @@ while (*s != 0)
 
        while (string_nextinlist(CUSS &sub, &sep, buffer, sizeof(buffer)) != NULL) cnt++;
        cp = string_sprintf("%d", cnt);
-        yield = string_cat(yield, &size, &ptr, cp, Ustrlen(cp));
+        yield = string_cat(yield, &size, &ptr, cp);
         continue;
         }
 
@@ -6356,7 +6510,7 @@ while (*s != 0)
          {
          uschar * buf = US" : ";
          if (needsep)
-           yield = string_cat(yield, &size, &ptr, buf, 3);
+           yield = string_catn(yield, &size, &ptr, buf, 3);
          else
            needsep = TRUE;
 
@@ -6372,21 +6526,21 @@ while (*s != 0)
            tok[0] = sep; tok[1] = ':'; tok[2] = 0;
            while ((cp= strpbrk((const char *)item, tok)))
              {
-              yield = string_cat(yield, &size, &ptr, item, cp-(char *)item);
+              yield = string_catn(yield, &size, &ptr, item, cp-(char *)item);
              if (*cp++ == ':') /* colon in a non-colon-sep list item, needs doubling */
                {
-                yield = string_cat(yield, &size, &ptr, US"::", 2);
+                yield = string_catn(yield, &size, &ptr, US"::", 2);
                item = (uschar *)cp;
                }
              else              /* sep in item; should already be doubled; emit once */
                {
-                yield = string_cat(yield, &size, &ptr, (uschar *)tok, 1);
+                yield = string_catn(yield, &size, &ptr, (uschar *)tok, 1);
                if (*cp == sep) cp++;
                item = (uschar *)cp;
                }
              }
            }
-          yield = string_cat(yield, &size, &ptr, item, Ustrlen(item));
+          yield = string_cat(yield, &size, &ptr, item);
          }
         continue;
        }
@@ -6434,7 +6588,7 @@ while (*s != 0)
 
         /* Convert to masked textual format and add to output. */
 
-        yield = string_cat(yield, &size, &ptr, buffer,
+        yield = string_catn(yield, &size, &ptr, buffer,
           host_nmtoa(count, binary, mask, buffer, '.'));
         continue;
         }
@@ -6464,7 +6618,7 @@ while (*s != 0)
            goto EXPAND_FAILED;
          }
 
-       yield = string_cat(yield, &size, &ptr, buffer,
+       yield = string_catn(yield, &size, &ptr, buffer,
                  c == EOP_IPV6NORM
                    ? ipv6_nmtoa(binary, buffer)
                    : host_nmtoa(4, binary, -1, buffer, ':')
@@ -6485,12 +6639,12 @@ while (*s != 0)
           if (c != EOP_DOMAIN)
             {
             if (c == EOP_LOCAL_PART && domain != 0) end = start + domain - 1;
-            yield = string_cat(yield, &size, &ptr, sub+start, end-start);
+            yield = string_catn(yield, &size, &ptr, sub+start, end-start);
             }
           else if (domain != 0)
             {
             domain += start;
-            yield = string_cat(yield, &size, &ptr, sub+domain, end-domain);
+            yield = string_catn(yield, &size, &ptr, sub+domain, end-domain);
             }
           }
         continue;
@@ -6525,25 +6679,25 @@ while (*s != 0)
           if (address != NULL)
             {
             if (ptr != save_ptr && address[0] == *outsep)
-              yield = string_cat(yield, &size, &ptr, US" ", 1);
+              yield = string_catn(yield, &size, &ptr, US" ", 1);
 
             for (;;)
               {
               size_t seglen = Ustrcspn(address, outsep);
-              yield = string_cat(yield, &size, &ptr, address, seglen + 1);
+              yield = string_catn(yield, &size, &ptr, address, seglen + 1);
 
               /* If we got to the end of the string we output one character
               too many. */
 
               if (address[seglen] == '\0') { ptr--; break; }
-              yield = string_cat(yield, &size, &ptr, outsep, 1);
+              yield = string_catn(yield, &size, &ptr, outsep, 1);
               address += seglen + 1;
               }
 
             /* Output a separator after the string: we will remove the
             redundant final one at the end. */
 
-            yield = string_cat(yield, &size, &ptr, outsep, 1);
+            yield = string_catn(yield, &size, &ptr, outsep, 1);
             }
 
           if (saveend == '\0') break;
@@ -6590,24 +6744,24 @@ while (*s != 0)
 
         if (needs_quote)
           {
-          yield = string_cat(yield, &size, &ptr, US"\"", 1);
+          yield = string_catn(yield, &size, &ptr, US"\"", 1);
           t = sub - 1;
           while (*(++t) != 0)
             {
             if (*t == '\n')
-              yield = string_cat(yield, &size, &ptr, US"\\n", 2);
+              yield = string_catn(yield, &size, &ptr, US"\\n", 2);
             else if (*t == '\r')
-              yield = string_cat(yield, &size, &ptr, US"\\r", 2);
+              yield = string_catn(yield, &size, &ptr, US"\\r", 2);
             else
               {
               if (*t == '\\' || *t == '"')
-                yield = string_cat(yield, &size, &ptr, US"\\", 1);
-              yield = string_cat(yield, &size, &ptr, t, 1);
+                yield = string_catn(yield, &size, &ptr, US"\\", 1);
+              yield = string_catn(yield, &size, &ptr, t, 1);
               }
             }
-          yield = string_cat(yield, &size, &ptr, US"\"", 1);
+          yield = string_catn(yield, &size, &ptr, US"\"", 1);
           }
-        else yield = string_cat(yield, &size, &ptr, sub, Ustrlen(sub));
+        else yield = string_cat(yield, &size, &ptr, sub);
         continue;
         }
 
@@ -6639,7 +6793,7 @@ while (*s != 0)
           goto EXPAND_FAILED;
           }
 
-        yield = string_cat(yield, &size, &ptr, sub, Ustrlen(sub));
+        yield = string_cat(yield, &size, &ptr, sub);
         continue;
         }
 
@@ -6652,8 +6806,8 @@ while (*s != 0)
         while (*(++t) != 0)
           {
           if (!isalnum(*t))
-            yield = string_cat(yield, &size, &ptr, US"\\", 1);
-          yield = string_cat(yield, &size, &ptr, t, 1);
+            yield = string_catn(yield, &size, &ptr, US"\\", 1);
+          yield = string_catn(yield, &size, &ptr, t, 1);
           }
         continue;
         }
@@ -6666,7 +6820,7 @@ while (*s != 0)
         uschar buffer[2048];
        const uschar *string = parse_quote_2047(sub, Ustrlen(sub), headers_charset,
           buffer, sizeof(buffer), FALSE);
-        yield = string_cat(yield, &size, &ptr, string, Ustrlen(string));
+        yield = string_cat(yield, &size, &ptr, string);
         continue;
         }
 
@@ -6683,7 +6837,7 @@ while (*s != 0)
           expand_string_message = error;
           goto EXPAND_FAILED;
           }
-        yield = string_cat(yield, &size, &ptr, decoded, len);
+        yield = string_catn(yield, &size, &ptr, decoded, len);
         continue;
         }
 
@@ -6699,7 +6853,7 @@ while (*s != 0)
           GETUTF8INC(c, sub);
           if (c > 255) c = '_';
           buff[0] = c;
-          yield = string_cat(yield, &size, &ptr, buff, 1);
+          yield = string_catn(yield, &size, &ptr, buff, 1);
           }
         continue;
         }
@@ -6734,7 +6888,7 @@ while (*s != 0)
                  complete = -1;        /* error (RFC3629 limit) */
                else
                  {             /* finished; output utf-8 sequence */
-                 yield = string_cat(yield, &size, &ptr, seq_buff, seq_len);
+                 yield = string_catn(yield, &size, &ptr, seq_buff, seq_len);
                  index = 0;
                  }
              }
@@ -6743,7 +6897,7 @@ while (*s != 0)
            {
            if((c & 0x80) == 0) /* 1-byte sequence, US-ASCII, keep it */
              {
-             yield = string_cat(yield, &size, &ptr, &c, 1);
+             yield = string_catn(yield, &size, &ptr, &c, 1);
              continue;
              }
            if((c & 0xe0) == 0xc0)              /* 2-byte sequence */
@@ -6776,11 +6930,11 @@ while (*s != 0)
          if (complete != 0)
            {
            bytes_left = index = 0;
-           yield = string_cat(yield, &size, &ptr, UTF8_REPLACEMENT_CHAR, 1);
+           yield = string_catn(yield, &size, &ptr, UTF8_REPLACEMENT_CHAR, 1);
            }
          if ((complete == 1) && ((c & 0x80) == 0))
                        /* ASCII character follows incomplete sequence */
-             yield = string_cat(yield, &size, &ptr, &c, 1);
+             yield = string_catn(yield, &size, &ptr, &c, 1);
          }
         continue;
         }
@@ -6797,7 +6951,7 @@ while (*s != 0)
            string_printing(sub), error);
          goto EXPAND_FAILED;
          }
-       yield = string_cat(yield, &size, &ptr, s, Ustrlen(s));
+       yield = string_cat(yield, &size, &ptr, s);
         continue;
        }
 
@@ -6812,7 +6966,7 @@ while (*s != 0)
            string_printing(sub), error);
          goto EXPAND_FAILED;
          }
-       yield = string_cat(yield, &size, &ptr, s, Ustrlen(s));
+       yield = string_cat(yield, &size, &ptr, s);
         continue;
        }
 
@@ -6827,7 +6981,7 @@ while (*s != 0)
            string_printing(sub), error);
          goto EXPAND_FAILED;
          }
-       yield = string_cat(yield, &size, &ptr, s, Ustrlen(s));
+       yield = string_cat(yield, &size, &ptr, s);
        DEBUG(D_expand) debug_printf("yield: '%s'\n", yield);
         continue;
        }
@@ -6843,7 +6997,7 @@ while (*s != 0)
            string_printing(sub), error);
          goto EXPAND_FAILED;
          }
-       yield = string_cat(yield, &size, &ptr, s, Ustrlen(s));
+       yield = string_cat(yield, &size, &ptr, s);
         continue;
        }
 #endif /* EXPERIMENTAL_INTERNATIONAL */
@@ -6853,7 +7007,7 @@ while (*s != 0)
       case EOP_ESCAPE:
         {
         const uschar *t = string_printing(sub);
-        yield = string_cat(yield, &size, &ptr, t, Ustrlen(t));
+        yield = string_cat(yield, &size, &ptr, t);
         continue;
         }
 
@@ -6873,7 +7027,7 @@ while (*s != 0)
           goto EXPAND_FAILED;
           }
         sprintf(CS var_buffer, PR_EXIM_ARITH, n);
-        yield = string_cat(yield, &size, &ptr, var_buffer, Ustrlen(var_buffer));
+        yield = string_cat(yield, &size, &ptr, var_buffer);
         continue;
         }
 
@@ -6889,7 +7043,7 @@ while (*s != 0)
           goto EXPAND_FAILED;
           }
         sprintf(CS var_buffer, "%d", n);
-        yield = string_cat(yield, &size, &ptr, var_buffer, Ustrlen(var_buffer));
+        yield = string_cat(yield, &size, &ptr, var_buffer);
         continue;
         }
 
@@ -6904,7 +7058,7 @@ while (*s != 0)
           goto EXPAND_FAILED;
           }
         t = readconf_printtime(n);
-        yield = string_cat(yield, &size, &ptr, t, Ustrlen(t));
+        yield = string_cat(yield, &size, &ptr, t);
         continue;
         }
 
@@ -6920,7 +7074,7 @@ while (*s != 0)
 #else
        uschar * s = b64encode(sub, Ustrlen(sub));
 #endif
-       yield = string_cat(yield, &size, &ptr, s, Ustrlen(s));
+       yield = string_cat(yield, &size, &ptr, s);
        continue;
        }
 
@@ -6934,7 +7088,7 @@ while (*s != 0)
             "well-formed for \"%s\" operator", sub, name);
           goto EXPAND_FAILED;
           }
-        yield = string_cat(yield, &size, &ptr, s, Ustrlen(s));
+        yield = string_cat(yield, &size, &ptr, s);
         continue;
         }
 
@@ -6944,7 +7098,7 @@ while (*s != 0)
         {
         uschar buff[24];
         (void)sprintf(CS buff, "%d", Ustrlen(sub));
-        yield = string_cat(yield, &size, &ptr, buff, Ustrlen(buff));
+        yield = string_cat(yield, &size, &ptr, buff);
         continue;
         }
 
@@ -7035,7 +7189,7 @@ while (*s != 0)
              extract_substr(sub, value1, value2, &len);
 
         if (ret == NULL) goto EXPAND_FAILED;
-        yield = string_cat(yield, &size, &ptr, ret, len);
+        yield = string_catn(yield, &size, &ptr, ret, len);
         continue;
         }
 
@@ -7090,7 +7244,7 @@ while (*s != 0)
           (long)st.st_dev, (long)st.st_nlink, (long)st.st_uid,
           (long)st.st_gid, st.st_size, (long)st.st_atime,
           (long)st.st_mtime, (long)st.st_ctime);
-        yield = string_cat(yield, &size, &ptr, s, Ustrlen(s));
+        yield = string_cat(yield, &size, &ptr, s);
         continue;
         }
 
@@ -7105,7 +7259,7 @@ while (*s != 0)
         if (expand_string_message != NULL)
           goto EXPAND_FAILED;
         s = string_sprintf("%d", vaguely_random_number((int)max));
-        yield = string_cat(yield, &size, &ptr, s, Ustrlen(s));
+        yield = string_cat(yield, &size, &ptr, s);
         continue;
         }
 
@@ -7124,7 +7278,7 @@ while (*s != 0)
           goto EXPAND_FAILED;
           }
         invert_address(reversed, sub);
-        yield = string_cat(yield, &size, &ptr, reversed, Ustrlen(reversed));
+        yield = string_cat(yield, &size, &ptr, reversed);
         continue;
         }
 
@@ -7169,7 +7323,7 @@ while (*s != 0)
       size = newsize;
       ptr = len;
       }
-    else yield = string_cat(yield, &size, &ptr, value, len);
+    else yield = string_catn(yield, &size, &ptr, value, len);
     continue;
     }
 
@@ -7210,9 +7364,9 @@ else if (resetok_p) *resetok_p = FALSE;
 
 DEBUG(D_expand)
   {
-  debug_printf("expanding: %.*s\n   result: %s\n", (int)(s - string), string,
+  debug_printf("  expanding: %.*s\n     result: %s\n", (int)(s - string), string,
     yield);
-  if (skipping) debug_printf("skipping: result is not used\n");
+  if (skipping) debug_printf("   skipping: result is not used\n");
   }
 return yield;
 
@@ -7221,10 +7375,12 @@ to update the pointer to the terminator, for cases of nested calls with "fail".
 */
 
 EXPAND_FAILED_CURLY:
-expand_string_message = malformed_header?
-  US"missing or misplaced { or } - could be header name not terminated by colon"
-  :
-  US"missing or misplaced { or }";
+if (malformed_header)
+  expand_string_message =
+    US"missing or misplaced { or } - could be header name not terminated by colon";
+
+else if (!expand_string_message || !*expand_string_message)
+  expand_string_message = US"missing or misplaced { or }";
 
 /* At one point, Exim reset the store to yield (if yield was not NULL), but
 that is a bad idea, because expand_string_message is in dynamic store. */