Check for proper output separator in expanding ${addresses:STRING} (Closes 2171)
[exim.git] / src / src / expand.c
index dfd62e50c7d8963e784d8b5c2f706b71315cbd96..67b3d65de43108090c7837e9a6c673fc2b7ec5fe 100644 (file)
@@ -642,7 +642,7 @@ static var_entry var_table[] = {
   { "received_ip_address", vtype_stringptr,   &interface_address },
   { "received_port",       vtype_int,         &interface_port },
   { "received_protocol",   vtype_stringptr,   &received_protocol },
-  { "received_time",       vtype_int,         &received_time },
+  { "received_time",       vtype_int,         &received_time.tv_sec },
   { "recipient_data",      vtype_stringptr,   &recipient_data },
   { "recipient_verify_failure",vtype_stringptr,&recipient_verify_failure },
   { "recipients",          vtype_string_func, &fn_recipients },
@@ -680,6 +680,7 @@ static var_entry var_table[] = {
   { "smtp_active_hostname", vtype_stringptr,  &smtp_active_hostname },
   { "smtp_command",        vtype_stringptr,   &smtp_cmd_buffer },
   { "smtp_command_argument", vtype_stringptr, &smtp_cmd_argument },
+  { "smtp_command_history", vtype_string_func, &smtp_cmd_hist },
   { "smtp_count_at_connection_start", vtype_int, &smtp_accept_count },
   { "smtp_notquit_reason", vtype_stringptr,   &smtp_notquit_reason },
   { "sn0",                 vtype_filter_int,  &filter_sn[0] },
@@ -1483,9 +1484,7 @@ while (*s != 0)
 /* If value2 is unset, just compute one number */
 
 if (value2 < 0)
-  {
   s = string_sprintf("%d", total % value1);
-  }
 
 /* Otherwise do a div/mod hash */
 
@@ -1554,11 +1553,9 @@ for (i = 0; i < 2; i++)
   int size = 0;
   header_line *h;
 
-  for (h = header_list; size < header_insert_maxlen && h != NULL; h = h->next)
-    {
-    if (h->type != htype_old && h->text != NULL)  /* NULL => Received: placeholder */
-      {
-      if (name == NULL || (len <= h->slen && strncmpic(name, h->text, len) == 0))
+  for (h = header_list; size < header_insert_maxlen && h; h = h->next)
+    if (h->type != htype_old && h->text)  /* NULL => Received: placeholder */
+      if (!name || (len <= h->slen && strncmpic(name, h->text, len) == 0))
         {
         int ilen;
         uschar *t;
@@ -1580,7 +1577,7 @@ for (i = 0; i < 2; i++)
         that contains an address list, except when asked for raw headers. Only
         need to do this once. */
 
-        if (!want_raw && name != NULL && comma == 0 &&
+        if (!want_raw && name && comma == 0 &&
             Ustrchr("BCFRST", h->type) != NULL)
           comma = 1;
 
@@ -1613,8 +1610,6 @@ for (i = 0; i < 2; i++)
             }
           }
         }
-      }
-    }
 
   /* At end of first pass, return NULL if no header found. Then truncate size
   if necessary, and get the buffer to hold the data, returning the buffer size.
@@ -1632,9 +1627,7 @@ for (i = 0; i < 2; i++)
 /* That's all we do for raw header expansion. */
 
 if (want_raw)
-  {
   *ptr = 0;
-  }
 
 /* Otherwise, remove a final newline and a redundant added comma. Then we do
 RFC 2047 decoding, translating the charset if requested. The rfc2047_decode2()
@@ -1838,7 +1831,7 @@ switch (vp->type)
   case vtype_msgbody:                        /* Pointer to msgbody string */
   case vtype_msgbody_end:                    /* Ditto, the end of the msg */
     ss = (uschar **)(val);
-    if (*ss == NULL && deliver_datafile >= 0)  /* Read body when needed */
+    if (!*ss && deliver_datafile >= 0)  /* Read body when needed */
       {
       uschar *body;
       off_t start_offset = SPOOL_DATA_START_OFFSET;
@@ -1871,7 +1864,7 @@ switch (vp->type)
            { if (body[--len] == '\n' || body[len] == 0) body[len] = ' '; }
        }
       }
-    return (*ss == NULL)? US"" : *ss;
+    return *ss ? *ss : US"";
 
   case vtype_todbsdin:                       /* BSD inbox time of day */
     return tod_stamp(tod_bsdin);
@@ -1966,6 +1959,7 @@ return;          /* Unknown variable name, fail silently */
 
 
 
+
 /*************************************************
 *           Read and expand substrings           *
 *************************************************/
@@ -2098,7 +2092,7 @@ while (i < nsub)
   }
 
 DEBUG(D_expand)
-  debug_printf("expanding: acl: %s  arg: %s%s\n",
+  debug_printf_indent("expanding: acl: %s  arg: %s%s\n",
     sub[0],
     acl_narg>0 ? acl_arg[0] : US"<none>",
     acl_narg>1 ? " +more"   : "");
@@ -2383,8 +2377,10 @@ switch(cond_type)
       case 3: return NULL;
       }
 
-    *resetok = FALSE;  /* eval_acl() might allocate; do not reclaim */
-    if (yield != NULL) switch(eval_acl(sub, nelem(sub), &user_msg))
+    if (yield != NULL)
+      {
+      *resetok = FALSE;        /* eval_acl() might allocate; do not reclaim */
+      switch(eval_acl(sub, nelem(sub), &user_msg))
        {
        case OK:
          cond = TRUE;
@@ -2405,6 +2401,7 @@ switch(cond_type)
           expand_string_message = string_sprintf("error from acl \"%s\"", sub[0]);
          return NULL;
        }
+      }
     return s;
     }
 
@@ -2522,7 +2519,7 @@ switch(cond_type)
         {
         num[i] = 0;
         DEBUG(D_expand)
-          debug_printf("empty string cast to zero for numerical comparison\n");
+          debug_printf_indent("empty string cast to zero for numerical comparison\n");
         }
       else
         {
@@ -2831,7 +2828,7 @@ switch(cond_type)
       uschar *save_iterate_item = iterate_item;
       int (*compare)(const uschar *, const uschar *);
 
-      DEBUG(D_expand) debug_printf("condition: %s\n", name);
+      DEBUG(D_expand) debug_printf_indent("condition: %s\n", name);
 
       tempcond = FALSE;
       compare = cond_type == ECOND_INLISTI
@@ -2919,7 +2916,7 @@ switch(cond_type)
     int sep = 0;
     uschar *save_iterate_item = iterate_item;
 
-    DEBUG(D_expand) debug_printf("condition: %s\n", name);
+    DEBUG(D_expand) debug_printf_indent("condition: %s\n", name);
 
     while (isspace(*s)) s++;
     if (*s++ != '{') goto COND_FAILED_CURLY_START;     /* }-for-text-editors */
@@ -2958,7 +2955,7 @@ switch(cond_type)
     list = sub[0];
     while ((iterate_item = string_nextinlist(&list, &sep, NULL, 0)) != NULL)
       {
-      DEBUG(D_expand) debug_printf("%s: $item = \"%s\"\n", name, iterate_item);
+      DEBUG(D_expand) debug_printf_indent("%s: $item = \"%s\"\n", name, iterate_item);
       if (!eval_condition(sub[1], resetok, &tempcond))
         {
         expand_string_message = string_sprintf("%s inside \"%s\" condition",
@@ -2966,7 +2963,7 @@ switch(cond_type)
         iterate_item = save_iterate_item;
         return NULL;
         }
-      DEBUG(D_expand) debug_printf("%s: condition evaluated to %s\n", name,
+      DEBUG(D_expand) debug_printf_indent("%s: condition evaluated to %s\n", name,
         tempcond? "true":"false");
 
       if (yield != NULL) *yield = (tempcond == testfor);
@@ -3023,7 +3020,7 @@ switch(cond_type)
         }
       }
     DEBUG(D_expand)
-      debug_printf("considering %s: %s\n", ourname, len ? t : US"<empty>");
+      debug_printf_indent("considering %s: %s\n", ourname, len ? t : US"<empty>");
     /* logic for the lax case from expand_check_condition(), which also does
     expands, and the logic is both short and stable enough that there should
     be no maintenance burden from replicating it. */
@@ -3050,7 +3047,7 @@ switch(cond_type)
        "value \"%s\"", t);
       return NULL;
       }
-    DEBUG(D_expand) debug_printf("%s: condition evaluated to %s\n", ourname,
+    DEBUG(D_expand) debug_printf_indent("%s: condition evaluated to %s\n", ourname,
         boolvalue? "true":"false");
     if (yield != NULL) *yield = (boolvalue == testfor);
     return s;
@@ -3193,17 +3190,17 @@ items. */
 while (isspace(*s)) s++;
 if (*s == '}')
   {
-  if (!skipping)
-    if (type[0] == 'i')
-      {
-      if (yes) *yieldptr = string_catn(*yieldptr, sizeptr, ptrptr, US"true", 4);
-      }
-    else
-      {
-      if (yes && lookup_value)
-       *yieldptr = string_cat(*yieldptr, sizeptr, ptrptr, lookup_value);
-      lookup_value = save_lookup;
-      }
+  if (type[0] == 'i')
+    {
+    if (yes && !skipping)
+      *yieldptr = string_catn(*yieldptr, sizeptr, ptrptr, US"true", 4);
+    }
+  else
+    {
+    if (yes && lookup_value && !skipping)
+      *yieldptr = string_cat(*yieldptr, sizeptr, ptrptr, lookup_value);
+    lookup_value = save_lookup;
+    }
   s++;
   goto RETURN;
   }
@@ -3446,7 +3443,7 @@ 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);
+DEBUG(D_expand) debug_printf_indent("prvs: hash source is '%s'\n", hash_source);
 
 memset(innerkey, 0x36, 64);
 memset(outerkey, 0x5c, 64);
@@ -3869,8 +3866,13 @@ uschar *save_expand_nstring[EXPAND_MAXN+1];
 int save_expand_nlength[EXPAND_MAXN+1];
 BOOL resetok = TRUE;
 
+expand_level++;
 DEBUG(D_expand)
-  debug_printf("%s: %s\n", skipping ? "   scanning" : "considering", string);
+  debug_printf_indent(UTF8_DOWN_RIGHT "%s: %s\n",
+    skipping
+    ? UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ "scanning"
+    : "considering",
+    string);
 
 expand_string_forcedfail = FALSE;
 expand_string_message = US"";
@@ -4085,7 +4087,7 @@ while (*s != 0)
        case OK:
        case FAIL:
          DEBUG(D_expand)
-           debug_printf("acl expansion yield: %s\n", user_msg);
+           debug_printf_indent("acl expansion yield: %s\n", user_msg);
          if (user_msg)
             yield = string_cat(yield, &size, &ptr, user_msg);
          continue;
@@ -4116,9 +4118,15 @@ while (*s != 0)
       if (next_s == NULL) goto EXPAND_FAILED;  /* message already set */
 
       DEBUG(D_expand)
-        debug_printf("  condition: %.*s\n     result: %s\n",
-         (int)(next_s - s), s,
-          cond ? "true" : "false");
+       {
+        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");
+       }
 
       s = next_s;
 
@@ -4575,11 +4583,11 @@ while (*s != 0)
         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("prvscheck localpart: %s\n", local_part);
-        DEBUG(D_expand) debug_printf("prvscheck key number: %s\n", key_num);
-        DEBUG(D_expand) debug_printf("prvscheck daystamp: %s\n", daystamp);
-        DEBUG(D_expand) debug_printf("prvscheck hash: %s\n", hash);
-        DEBUG(D_expand) debug_printf("prvscheck domain: %s\n", domain);
+        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);
+        DEBUG(D_expand) debug_printf_indent("prvscheck daystamp: %s\n", daystamp);
+        DEBUG(D_expand) debug_printf_indent("prvscheck hash: %s\n", hash);
+        DEBUG(D_expand) debug_printf_indent("prvscheck domain: %s\n", domain);
 
         /* Set up expansion variables */
         prvscheck_address = string_cat (NULL, &mysize, &myptr, local_part);
@@ -4607,8 +4615,8 @@ while (*s != 0)
           goto EXPAND_FAILED;
           }
 
-        DEBUG(D_expand) debug_printf("prvscheck: received hash is %s\n", hash);
-        DEBUG(D_expand) debug_printf("prvscheck:      own hash is %s\n", p);
+        DEBUG(D_expand) debug_printf_indent("prvscheck: received hash is %s\n", hash);
+        DEBUG(D_expand) debug_printf_indent("prvscheck:      own hash is %s\n", p);
 
         if (Ustrcmp(p,hash) == 0)
           {
@@ -4626,18 +4634,18 @@ while (*s != 0)
           if (iexpire >= inow)
             {
             prvscheck_result = US"1";
-            DEBUG(D_expand) debug_printf("prvscheck: success, $pvrs_result set to 1\n");
+            DEBUG(D_expand) debug_printf_indent("prvscheck: success, $pvrs_result set to 1\n");
             }
             else
             {
             prvscheck_result = NULL;
-            DEBUG(D_expand) debug_printf("prvscheck: signature expired, $pvrs_result unset\n");
+            DEBUG(D_expand) debug_printf_indent("prvscheck: signature expired, $pvrs_result unset\n");
             }
           }
         else
           {
           prvscheck_result = NULL;
-          DEBUG(D_expand) debug_printf("prvscheck: hash failure, $pvrs_result unset\n");
+          DEBUG(D_expand) debug_printf_indent("prvscheck: hash failure, $pvrs_result unset\n");
           }
 
         /* Now expand the final argument. We leave this till now so that
@@ -4700,8 +4708,7 @@ while (*s != 0)
 
       /* Open the file and read it */
 
-      f = Ufopen(sub_arg[0], "rb");
-      if (f == NULL)
+      if (!(f = Ufopen(sub_arg[0], "rb")))
         {
         expand_string_message = string_open_failed(errno, "%s", sub_arg[0]);
         goto EXPAND_FAILED;
@@ -4712,7 +4719,8 @@ while (*s != 0)
       continue;
       }
 
-    /* Handle "readsocket" to insert data from a Unix domain socket */
+    /* Handle "readsocket" to insert data from a socket, either
+    Inet or Unix domain */
 
     case EITEM_READSOCK:
       {
@@ -4720,11 +4728,12 @@ while (*s != 0)
       int timeout = 5;
       int save_ptr = ptr;
       FILE *f;
-      struct sockaddr_un sockun;         /* don't call this "sun" ! */
       uschar *arg;
       uschar *sub_arg[4];
+      BOOL do_shutdown = TRUE;
+      blob reqstr;
 
-      if ((expand_forbid & RDO_READSOCK) != 0)
+      if (expand_forbid & RDO_READSOCK)
         {
         expand_string_message = US"socket insertions are not permitted";
         goto EXPAND_FAILED;
@@ -4740,17 +4749,32 @@ while (*s != 0)
         case 3: goto EXPAND_FAILED;
         }
 
-      /* Sort out timeout, if given */
+      /* Grab the request string, if any */
+
+      reqstr.data = sub_arg[1];
+      reqstr.len = Ustrlen(sub_arg[1]);
 
-      if (sub_arg[2] != NULL)
+      /* Sort out timeout, if given.  The second arg is a list with the first element
+      being a time value.  Any more are options of form "name=value".  Currently the
+      only option recognised is "shutdown". */
+
+      if (sub_arg[2])
         {
-        timeout = readconf_readtime(sub_arg[2], 0, FALSE);
-        if (timeout < 0)
+       const uschar * list = sub_arg[2];
+       uschar * item;
+       int sep = 0;
+
+       item = string_nextinlist(&list, &sep, NULL, 0);
+        if ((timeout = readconf_readtime(item, 0, FALSE)) < 0)
           {
-          expand_string_message = string_sprintf("bad time value %s",
-            sub_arg[2]);
+          expand_string_message = string_sprintf("bad time value %s", item);
           goto EXPAND_FAILED;
           }
+
+       while ((item = string_nextinlist(&list, &sep, NULL, 0)))
+         if (Ustrncmp(item, US"shutdown=", 9) == 0)
+           if (Ustrcmp(item + 9, US"no") == 0)
+             do_shutdown = FALSE;
         }
       else sub_arg[3] = NULL;                     /* No eol if no timeout */
 
@@ -4764,12 +4788,12 @@ while (*s != 0)
         if (Ustrncmp(sub_arg[0], "inet:", 5) == 0)
           {
           int port;
-          uschar *server_name = sub_arg[0] + 5;
-          uschar *port_name = Ustrrchr(server_name, ':');
+          uschar * server_name = sub_arg[0] + 5;
+          uschar * port_name = Ustrrchr(server_name, ':');
 
           /* Sort out the port */
 
-          if (port_name == NULL)
+          if (!port_name)
             {
             expand_string_message =
               string_sprintf("missing port for readsocket %s", sub_arg[0]);
@@ -4791,7 +4815,7 @@ while (*s != 0)
           else
             {
             struct servent *service_info = getservbyname(CS port_name, "tcp");
-            if (service_info == NULL)
+            if (!service_info)
               {
               expand_string_message = string_sprintf("unknown port \"%s\"",
                 port_name);
@@ -4800,16 +4824,21 @@ while (*s != 0)
             port = ntohs(service_info->s_port);
             }
 
-         if ((fd = ip_connectedsocket(SOCK_STREAM, server_name, port, port,
-                 timeout, NULL, &expand_string_message)) < 0)
+         fd = ip_connectedsocket(SOCK_STREAM, server_name, port, port,
+                 timeout, NULL, &expand_string_message, &reqstr);
+         callout_address = NULL;
+         if (fd < 0)
               goto SOCK_FAIL;
+         reqstr.len = 0;
           }
 
         /* Handle a Unix domain socket */
 
         else
           {
+         struct sockaddr_un sockun;         /* don't call this "sun" ! */
           int rc;
+
           if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) == -1)
             {
             expand_string_message = string_sprintf("failed to create socket: %s",
@@ -4838,19 +4867,18 @@ while (*s != 0)
             }
           }
 
-        DEBUG(D_expand) debug_printf("connected to socket %s\n", sub_arg[0]);
+        DEBUG(D_expand) debug_printf_indent("connected to socket %s\n", sub_arg[0]);
 
        /* Allow sequencing of test actions */
        if (running_in_test_harness) millisleep(100);
 
-        /* Write the request string, if not empty */
+        /* Write the request string, if not empty or already done */
 
-        if (sub_arg[1][0] != 0)
+        if (reqstr.len)
           {
-          int len = Ustrlen(sub_arg[1]);
-          DEBUG(D_expand) debug_printf("writing \"%s\" to socket\n",
-            sub_arg[1]);
-          if (write(fd, sub_arg[1], len) != len)
+          DEBUG(D_expand) debug_printf_indent("writing \"%s\" to socket\n",
+            reqstr.data);
+          if (write(fd, reqstr.data, reqstr.len) != reqstr.len)
             {
             expand_string_message = string_sprintf("request write to socket "
               "failed: %s", strerror(errno));
@@ -4862,9 +4890,9 @@ while (*s != 0)
         recognise that it is their turn to do some work. Just in case some
         system doesn't have this function, make it conditional. */
 
-        #ifdef SHUT_WR
-        shutdown(fd, SHUT_WR);
-        #endif
+#ifdef SHUT_WR
+       if (do_shutdown) shutdown(fd, SHUT_WR);
+#endif
 
        if (running_in_test_harness) millisleep(100);
 
@@ -4904,7 +4932,7 @@ while (*s != 0)
         while (isspace(*s)) s++;
         }
 
-    readsock_done:
+    READSOCK_DONE:
       if (*s++ != '}')
         {
        expand_string_message = US"missing '}' closing readsocket";
@@ -4916,7 +4944,7 @@ while (*s != 0)
       socket, or timeout on reading. If another substring follows, expand and
       use it. Otherwise, those conditions give expand errors. */
 
-      SOCK_FAIL:
+    SOCK_FAIL:
       if (*s != '{') goto EXPAND_FAILED;
       DEBUG(D_any) debug_printf("%s\n", expand_string_message);
       if (!(arg = expand_string_internal(s+1, TRUE, &s, FALSE, TRUE, &resetok)))
@@ -4928,7 +4956,7 @@ while (*s != 0)
        goto EXPAND_FAILED_CURLY;
        }
       while (isspace(*s)) s++;
-      goto readsock_done;
+      goto READSOCK_DONE;
       }
 
     /* Handle "run" to execute a program. */
@@ -5796,11 +5824,12 @@ while (*s != 0)
       processing for real, we perform the iteration. */
 
       if (skipping) continue;
-      while ((iterate_item = string_nextinlist(&list, &sep, NULL, 0)) != NULL)
+      while ((iterate_item = string_nextinlist(&list, &sep, NULL, 0)))
         {
         *outsep = (uschar)sep;      /* Separator as a string */
 
-        DEBUG(D_expand) debug_printf("%s: $item = \"%s\"\n", name, iterate_item);
+       DEBUG(D_expand) debug_printf_indent("%s: $item = '%s'  $value = '%s'\n",
+                         name, iterate_item, lookup_value);
 
         if (item_type == EITEM_FILTER)
           {
@@ -5813,7 +5842,7 @@ while (*s != 0)
               expand_string_message, name);
             goto EXPAND_FAILED;
             }
-          DEBUG(D_expand) debug_printf("%s: condition is %s\n", name,
+          DEBUG(D_expand) debug_printf_indent("%s: condition is %s\n", name,
             condresult? "true":"false");
           if (condresult)
             temp = iterate_item;    /* TRUE => include this item */
@@ -5965,10 +5994,12 @@ while (*s != 0)
         {
        uschar * dstitem;
        uschar * newlist = NULL;
+       int size = 0, len = 0;
        uschar * newkeylist = NULL;
+       int ksize = 0, klen = 0;
        uschar * srcfield;
 
-        DEBUG(D_expand) debug_printf("%s: $item = \"%s\"\n", name, srcitem);
+        DEBUG(D_expand) debug_printf_indent("%s: $item = \"%s\"\n", name, srcitem);
 
        /* extract field for comparisons */
        iterate_item = srcitem;
@@ -5997,7 +6028,7 @@ while (*s != 0)
          /* build and run condition string */
          expr = string_sprintf("%s{%s}{%s}", cmp, srcfield, dstfield);
 
-         DEBUG(D_expand) debug_printf("%s: cond = \"%s\"\n", name, expr);
+         DEBUG(D_expand) debug_printf_indent("%s: cond = \"%s\"\n", name, expr);
          if (!eval_condition(expr, &resetok, &before))
            {
            expand_string_message = string_sprintf("comparison in sort: %s",
@@ -6010,40 +6041,40 @@ while (*s != 0)
            /* New-item sorts before this dst-item.  Append new-item,
            then dst-item, then remainder of dst list. */
 
-           newlist = string_append_listele(newlist, sep, srcitem);
-           newkeylist = string_append_listele(newkeylist, sep, srcfield);
+           newlist = string_append_listele(newlist, &size, &len, sep, srcitem);
+           newkeylist = string_append_listele(newkeylist, &ksize, &klen, sep, srcfield);
            srcitem = NULL;
 
-           newlist = string_append_listele(newlist, sep, dstitem);
-           newkeylist = string_append_listele(newkeylist, sep, dstfield);
+           newlist = string_append_listele(newlist, &size, &len, sep, dstitem);
+           newkeylist = string_append_listele(newkeylist, &ksize, &klen, sep, dstfield);
 
            while ((dstitem = string_nextinlist(&dstlist, &sep, NULL, 0)))
              {
              if (!(dstfield = string_nextinlist(&dstkeylist, &sep, NULL, 0)))
                goto sort_mismatch;
-             newlist = string_append_listele(newlist, sep, dstitem);
-             newkeylist = string_append_listele(newkeylist, sep, dstfield);
+             newlist = string_append_listele(newlist, &size, &len, sep, dstitem);
+             newkeylist = string_append_listele(newkeylist, &ksize, &klen, sep, dstfield);
              }
 
            break;
            }
 
-         newlist = string_append_listele(newlist, sep, dstitem);
-         newkeylist = string_append_listele(newkeylist, sep, dstfield);
+         newlist = string_append_listele(newlist, &size, &len, sep, dstitem);
+         newkeylist = string_append_listele(newkeylist, &ksize, &klen, sep, dstfield);
          }
 
        /* If we ran out of dstlist without consuming srcitem, append it */
        if (srcitem)
          {
-         newlist = string_append_listele(newlist, sep, srcitem);
-         newkeylist = string_append_listele(newkeylist, sep, srcfield);
+         newlist = string_append_listele(newlist, &size, &len, sep, srcitem);
+         newkeylist = string_append_listele(newkeylist, &ksize, &klen, sep, srcfield);
          }
 
        dstlist = newlist;
        dstkeylist = newkeylist;
 
-        DEBUG(D_expand) debug_printf("%s: dstlist = \"%s\"\n", name, dstlist);
-        DEBUG(D_expand) debug_printf("%s: dstkeylist = \"%s\"\n", name, dstkeylist);
+        DEBUG(D_expand) debug_printf_indent("%s: dstlist = \"%s\"\n", name, dstlist);
+        DEBUG(D_expand) debug_printf_indent("%s: dstkeylist = \"%s\"\n", name, dstkeylist);
        }
 
       if (dstlist)
@@ -6440,7 +6471,7 @@ while (*s != 0)
          blob b;
          char st[3];
 
-         if (!exim_sha_init(&h, HASH_SHA256))
+         if (!exim_sha_init(&h, HASH_SHA2_256))
            {
            expand_string_message = US"unrecognised sha256 variant";
            goto EXPAND_FAILED;
@@ -6634,19 +6665,19 @@ while (*s != 0)
            char * cp;
            char tok[3];
            tok[0] = sep; tok[1] = ':'; tok[2] = 0;
-           while ((cp= strpbrk((const char *)item, tok)))
+           while ((cp= strpbrk(CCS item, tok)))
              {
-              yield = string_catn(yield, &size, &ptr, item, cp-(char *)item);
+              yield = string_catn(yield, &size, &ptr, item, cp-CS item);
              if (*cp++ == ':') /* colon in a non-colon-sep list item, needs doubling */
                {
                 yield = string_catn(yield, &size, &ptr, US"::", 2);
-               item = (uschar *)cp;
+               item = US cp;
                }
              else              /* sep in item; should already be doubled; emit once */
                {
-                yield = string_catn(yield, &size, &ptr, (uschar *)tok, 1);
+                yield = string_catn(yield, &size, &ptr, US tok, 1);
                if (*cp == sep) cp++;
-               item = (uschar *)cp;
+               item = US cp;
                }
              }
            }
@@ -6740,12 +6771,11 @@ while (*s != 0)
       case EOP_LOCAL_PART:
       case EOP_DOMAIN:
         {
-        uschar *error;
+        uschar * error;
         int start, end, domain;
-        uschar *t = parse_extract_address(sub, &error, &start, &end, &domain,
+        uschar * t = parse_extract_address(sub, &error, &start, &end, &domain,
           FALSE);
-        if (t != NULL)
-          {
+        if (t)
           if (c != EOP_DOMAIN)
             {
             if (c == EOP_LOCAL_PART && domain != 0) end = start + domain - 1;
@@ -6756,7 +6786,6 @@ while (*s != 0)
             domain += start;
             yield = string_catn(yield, &size, &ptr, sub+domain, end-domain);
             }
-          }
         continue;
         }
 
@@ -6768,7 +6797,13 @@ while (*s != 0)
         int start, end, domain;  /* Not really used */
 
         while (isspace(*sub)) sub++;
-        if (*sub == '>') { *outsep = *++sub; ++sub; }
+        if (*sub == '>')
+          if (*outsep = *++sub) ++sub;
+          else {
+            expand_string_message = string_sprintf("output separator "
+              "missing in expanding ${addresses:%s}", --sub);
+            goto EXPAND_FAILED;
+          }
         parse_allow_group = TRUE;
 
         for (;;)
@@ -7092,7 +7127,7 @@ while (*s != 0)
          goto EXPAND_FAILED;
          }
        yield = string_cat(yield, &size, &ptr, s);
-       DEBUG(D_expand) debug_printf("yield: '%s'\n", yield);
+       DEBUG(D_expand) debug_printf_indent("yield: '%s'\n", yield);
         continue;
        }
 
@@ -7486,10 +7521,19 @@ else if (resetok_p) *resetok_p = FALSE;
 
 DEBUG(D_expand)
   {
-  debug_printf("  expanding: %.*s\n     result: %s\n", (int)(s - string), string,
+  debug_printf_indent(UTF8_VERT_RIGHT UTF8_HORIZ UTF8_HORIZ
+    "expanding: %.*s\n",
+    (int)(s - string), string);
+  debug_printf_indent("%s"
+    UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ
+    "result: %s\n",
+    skipping ? UTF8_VERT_RIGHT : UTF8_UP_RIGHT,
     yield);
-  if (skipping) debug_printf("   skipping: result is not used\n");
+  if (skipping)
+    debug_printf_indent(UTF8_UP_RIGHT UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ
+      "skipping: result is not used\n");
   }
+expand_level--;
 return yield;
 
 /* This is the failure exit: easiest to program with a goto. We still need
@@ -7511,11 +7555,17 @@ EXPAND_FAILED:
 if (left != NULL) *left = s;
 DEBUG(D_expand)
   {
-  debug_printf("failed to expand: %s\n", string);
-  debug_printf("   error message: %s\n", expand_string_message);
-  if (expand_string_forcedfail) debug_printf("failure was forced\n");
+  debug_printf_indent(UTF8_VERT_RIGHT "failed to expand: %s\n",
+    string);
+  debug_printf_indent("%s" UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ
+    "error message: %s\n",
+    expand_string_forcedfail ? UTF8_VERT_RIGHT : UTF8_UP_RIGHT,
+    expand_string_message);
+  if (expand_string_forcedfail)
+    debug_printf_indent(UTF8_UP_RIGHT "failure was forced\n");
   }
 if (resetok_p) *resetok_p = resetok;
+expand_level--;
 return NULL;
 }
 
@@ -7642,7 +7692,7 @@ if (isspace(*s))
   if (*s == '\0')
     {
       DEBUG(D_expand)
-       debug_printf("treating blank string as number 0\n");
+       debug_printf_indent("treating blank string as number 0\n");
       return 0;
     }
   }
@@ -7776,6 +7826,60 @@ return (  (  Ustrstr(s, "failed to expand") != NULL
 
 
 
+/*************************************************
+* Error-checking for testsuite                   *
+*************************************************/
+typedef struct {
+  const char * filename;
+  int          linenumber;
+  uschar *     region_start;
+  uschar *     region_end;
+  const uschar *var_name;
+  const uschar *var_data;
+} err_ctx;
+
+static void
+assert_variable_notin(uschar * var_name, uschar * var_data, void * ctx)
+{
+err_ctx * e = ctx;
+if (var_data >= e->region_start  &&  var_data < e->region_end)
+  {
+  e->var_name = CUS var_name;
+  e->var_data = CUS var_data;
+  }
+}
+
+void
+assert_no_variables(void * ptr, int len, const char * filename, int linenumber)
+{
+err_ctx e = {filename, linenumber, ptr, US ptr + len, NULL };
+int i;
+var_entry * v;
+
+/* check acl_ variables */
+tree_walk(acl_var_c, assert_variable_notin, &e);
+tree_walk(acl_var_m, assert_variable_notin, &e);
+
+/* check auth<n> variables */
+for (i = 0; i < AUTH_VARS; i++) if (auth_vars[i])
+  assert_variable_notin(US"auth<n>", auth_vars[i], &e);
+
+/* check regex<n> variables */
+for (i = 0; i < REGEX_VARS; i++) if (regex_vars[i])
+  assert_variable_notin(US"regex<n>", regex_vars[i], &e);
+
+/* check known-name variables */
+for (v = var_table; v < var_table + var_table_size; v++)
+  if (v->type == vtype_stringptr)
+    assert_variable_notin(US v->name, *(USS v->value), &e);
+
+if (e.var_name)
+  log_write(0, LOG_MAIN|LOG_PANIC_DIE,
+    "live variable '%s' destroyed by reset_store at %s:%d\n- value '%.64s'",
+    e.var_name, e.filename, e.linenumber, e.var_data);
+}
+
+
 
 /*************************************************
 **************************************************