Hints DB interface: convert from macros to inlinable functions.
[exim.git] / src / src / filter.c
index 98b6bc3e84a72c57cb5987d036ba213f98fa2b15..210f7b0e2441c0119ac47d815197f6e2c84392fc 100644 (file)
@@ -3,6 +3,7 @@
 *************************************************/
 
 /* Copyright (c) University of Cambridge 1995 - 2018 */
+/* Copyright (c) The Exim Maintainers 2020 - 2021 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -26,7 +27,7 @@ union argtypes {
   struct condition_block *c;
   struct filter_cmd      *f;
   int                     i;
-  uschar                 *u;
+  const uschar            *u;
 };
 
 /* Local structures used in this module */
@@ -50,7 +51,7 @@ typedef struct condition_block {
 /* Miscellaneous other declarations */
 
 static uschar **error_pointer;
-static uschar *log_filename;
+static const uschar *log_filename;
 static int  filter_options;
 static int  line_number;
 static int  expect_endif;
@@ -66,7 +67,7 @@ static BOOL noerror_force;
 
 enum { had_neither, had_else, had_elif, had_endif };
 
-static BOOL read_command_list(uschar **, filter_cmd ***, BOOL);
+static BOOL read_command_list(const uschar **, filter_cmd ***, BOOL);
 
 
 /* The string arguments for the mail command. The header line ones (that are
@@ -251,8 +252,8 @@ Arguments:
 Returns:           pointer to next non-whitespace character
 */
 
-static uschar *
-nextsigchar(uschar *ptr, BOOL comment_allowed)
+static const uschar *
+nextsigchar(const uschar *ptr, BOOL comment_allowed)
 {
 for (;;)
   {
@@ -289,8 +290,8 @@ Arguments
 Returns:    pointer to the next significant character after the word
 */
 
-static uschar *
-nextword(uschar *ptr, uschar *buffer, int size, BOOL bracket)
+static const uschar *
+nextword(const uschar *ptr, uschar *buffer, int size, BOOL bracket)
 {
 uschar *bp = buffer;
 while (*ptr != 0 && !isspace(*ptr) &&
@@ -325,13 +326,13 @@ Arguments:
 Returns:     the next significant character after the item
 */
 
-static uschar *
-nextitem(uschar *ptr, uschar *buffer, int size, BOOL bracket)
+static const uschar *
+nextitem(const uschar *ptr, uschar *buffer, int size, BOOL bracket)
 {
 uschar *bp = buffer;
 if (*ptr != '\"') return nextword(ptr, buffer, size, bracket);
 
-while (*(++ptr) != 0 && *ptr != '\"' && *ptr != '\n')
+while (*++ptr && *ptr != '\"' && *ptr != '\n')
   {
   if (bp - buffer >= size - 1)
     {
@@ -344,7 +345,7 @@ while (*(++ptr) != 0 && *ptr != '\"' && *ptr != '\n')
     {
     if (isspace(ptr[1]))    /* \<whitespace>NL<whitespace> ignored */
       {
-      uschar *p = ptr + 1;
+      const uschar *p = ptr + 1;
       while (*p != '\n' && isspace(*p)) p++;
       if (*p == '\n')
         {
@@ -384,7 +385,7 @@ Returns:   the number, or 0 on error (with *OK FALSE)
 */
 
 static int
-get_number(uschar *s, BOOL *ok)
+get_number(const uschar *s, BOOL *ok)
 {
 int value, count;
 *ok = FALSE;
@@ -415,8 +416,8 @@ Arguments:
 Returns:          points to next character after "then"
 */
 
-static uschar *
-read_condition(uschar *ptr, condition_block **cond, BOOL toplevel)
+static const uschar *
+read_condition(const uschar *ptr, condition_block **cond, BOOL toplevel)
 {
 uschar buffer[1024];
 BOOL testfor = TRUE;
@@ -433,7 +434,7 @@ for (;;)
 
   /* reaching the end of the input is an error. */
 
-  if (*ptr == 0)
+  if (!*ptr)
     {
     *error_pointer = US"\"then\" missing at end of filter file";
     break;
@@ -476,7 +477,7 @@ for (;;)
   else
     {
     ptr = nextitem(ptr, buffer, sizeof(buffer), TRUE);
-    if (*error_pointer != NULL) break;
+    if (*error_pointer) break;
 
     /* "Then" at the start of a condition is an error */
 
@@ -497,7 +498,7 @@ for (;;)
 
     /* Build a condition block from the specific word. */
 
-    c = store_get(sizeof(condition_block), FALSE);
+    c = store_get(sizeof(condition_block), GET_UNTAINTED);
     c->left.u = c->right.u = NULL;
     c->testfor = testfor;
     testfor = TRUE;
@@ -517,7 +518,7 @@ for (;;)
       for (;;)
         {
         string_item *aa;
-        uschar *saveptr = ptr;
+        const uschar * saveptr = ptr;
         ptr = nextword(ptr, buffer, sizeof(buffer), TRUE);
         if (*error_pointer) break;
         if (Ustrcmp(buffer, "alias") != 0)
@@ -527,7 +528,7 @@ for (;;)
           }
         ptr = nextitem(ptr, buffer, sizeof(buffer), TRUE);
         if (*error_pointer) break;
-        aa = store_get(sizeof(string_item), FALSE);
+        aa = store_get(sizeof(string_item), GET_UNTAINTED);
         aa->text = string_copy(buffer);
         aa->next = c->left.a;
         c->left.a = aa;
@@ -568,7 +569,7 @@ for (;;)
     else
       {
       int i;
-      uschar *isptr = NULL;
+      const uschar *isptr = NULL;
 
       c->left.u = string_copy(buffer);
       ptr = nextword(ptr, buffer, sizeof(buffer), TRUE);
@@ -654,18 +655,23 @@ for (;;)
 
   else
     {
-    uschar *saveptr = ptr;
+    const uschar *saveptr = ptr;
     ptr = nextword(ptr, buffer, sizeof(buffer), FALSE);
     if (*error_pointer) break;
 
     /* "Then" terminates a toplevel condition; otherwise a closing bracket
     has been omitted. Put a string terminator at the start of "then" so
     that reflecting the condition can be done when testing. */
+    /*XXX This stops us doing a constification job in this file, unfortunately.
+    Comment it out and see if anything breaks.
+    With one addition down at DEFERFREEZEFAIL it passes the testsuite. */
 
     if (Ustrcmp(buffer, "then") == 0)
       {
-      if (toplevel) *saveptr = 0;
-      else *error_pointer = string_sprintf("missing \")\" at end of "
+//      if (toplevel) *saveptr = 0;
+//      else
+   if (!toplevel)
+      *error_pointer = string_sprintf("missing \")\" at end of "
           "condition near line %d of filter file", line_number);
       break;
       }
@@ -678,7 +684,7 @@ for (;;)
 
     else if (Ustrcmp(buffer, "and") == 0)
       {
-      condition_block *andc = store_get(sizeof(condition_block), FALSE);
+      condition_block * andc = store_get(sizeof(condition_block), GET_UNTAINTED);
       andc->parent = current_parent;
       andc->type = cond_and;
       andc->testfor = TRUE;
@@ -696,8 +702,8 @@ for (;;)
 
     else if (Ustrcmp(buffer, "or") == 0)
       {
-      condition_block *orc = store_get(sizeof(condition_block), FALSE);
-      condition_block *or_parent = NULL;
+      condition_block * orc = store_get(sizeof(condition_block), GET_UNTAINTED);
+      condition_block * or_parent = NULL;
 
       if (current_parent)
         {
@@ -836,7 +842,7 @@ Returns:       TRUE if command successfully read, else FALSE
 */
 
 static BOOL
-read_command(uschar **pptr, filter_cmd ***lastcmdptr)
+read_command(const uschar **pptr, filter_cmd ***lastcmdptr)
 {
 int command, i, cmd_bit;
 filter_cmd *new, **newlastcmdptr;
@@ -844,8 +850,8 @@ BOOL yield = TRUE;
 BOOL was_seen_or_unseen = FALSE;
 BOOL was_noerror = FALSE;
 uschar buffer[1024];
-uschar *ptr = *pptr;
-uschar *saveptr;
+const uschar *ptr = *pptr;
+const uschar *saveptr;
 uschar *fmsg = NULL;
 
 /* Read the next word and find which command it is. Command words are normally
@@ -854,6 +860,8 @@ terminated by white space, but there are two exceptions, which are the "if" and
 as brackets are allowed in conditions and users will expect not to require
 white space here. */
 
+*buffer = '\0';        /* compiler quietening */
+
 if (Ustrncmp(ptr, "if(", 3) == 0)
   {
   Ustrcpy(buffer, US"if");
@@ -867,7 +875,7 @@ else if (Ustrncmp(ptr, "elif(", 5) == 0)
 else
   {
   ptr = nextword(ptr, buffer, sizeof(buffer), FALSE);
-  if (*error_pointer != NULL) return FALSE;
+  if (*error_pointer) return FALSE;
   }
 
 for (command = 0; command < command_list_count; command++)
@@ -906,11 +914,11 @@ switch (command)
   case testprint_command:
 
   ptr = nextitem(ptr, buffer, sizeof(buffer), FALSE);
-  if (*buffer == 0)
+  if (!*buffer)
     *error_pointer = string_sprintf("\"%s\" requires an argument "
       "near line %d of filter file", command_list[command], line_number);
 
-  if (*error_pointer != NULL) yield = FALSE; else
+  if (*error_pointer) yield = FALSE; else
     {
     union argtypes argument, second_argument;
 
@@ -920,13 +928,13 @@ switch (command)
       {
       argument.u = string_copy(buffer);
       ptr = nextitem(ptr, buffer, sizeof(buffer), FALSE);
-      if (*buffer == 0 || Ustrcmp(buffer, "to") != 0)
+      if (!*buffer || Ustrcmp(buffer, "to") != 0)
         *error_pointer = string_sprintf("\"to\" expected in \"add\" command "
           "near line %d of filter file", line_number);
       else
         {
         ptr = nextitem(ptr, buffer, sizeof(buffer), FALSE);
-        if (*buffer == 0)
+        if (!*buffer)
           *error_pointer = string_sprintf("value missing after \"to\" "
             "near line %d of filter file", line_number);
         else second_argument.u = string_copy(buffer);
@@ -962,7 +970,7 @@ switch (command)
       if (yield)
         {
         ptr = nextitem(ptr, buffer, sizeof(buffer), FALSE);
-        if (*buffer == 0)
+        if (!*buffer)
           *error_pointer = string_sprintf("value missing after \"add\", "
             "\"remove\", or \"charset\" near line %d of filter file",
               line_number);
@@ -998,7 +1006,7 @@ switch (command)
 
       else if (command == deliver_command)
         {
-        uschar *save_ptr = ptr;
+        const uschar *save_ptr = ptr;
         ptr = nextword(ptr, buffer, sizeof(buffer), FALSE);
         if (Ustrcmp(buffer, "errors_to") == 0)
           {
@@ -1013,9 +1021,10 @@ switch (command)
     FALSE for logging commands, and it doesn't matter for testprint, as
     that doesn't change the "delivered" status. */
 
-    if (*error_pointer != NULL) yield = FALSE; else
+    if (*error_pointer) yield = FALSE;
+    else
       {
-      new = store_get(sizeof(filter_cmd) + sizeof(union argtypes), FALSE);
+      new = store_get(sizeof(filter_cmd) + sizeof(union argtypes), GET_UNTAINTED);
       new->next = NULL;
       **lastcmdptr = new;
       *lastcmdptr = &(new->next);
@@ -1080,7 +1089,7 @@ switch (command)
 
   saveptr = ptr;
   ptr = nextitem(ptr, buffer, sizeof(buffer), FALSE);
-  if (*saveptr != '\"' && (*buffer == 0 || Ustrcmp(buffer, "text") != 0))
+  if (*saveptr != '\"' && (!*buffer || Ustrcmp(buffer, "text") != 0))
     {
     ptr = saveptr;
     fmsg = US"";
@@ -1099,12 +1108,12 @@ switch (command)
   /* Finish has no arguments; fmsg defaults to NULL */
 
   case finish_command:
-  new = store_get(sizeof(filter_cmd), FALSE);
+  new = store_get(sizeof(filter_cmd), GET_UNTAINTED);
   new->next = NULL;
   **lastcmdptr = new;
   *lastcmdptr = &(new->next);
   new->command = command;
-  new->seen = seen_force? seen_value : FALSE;
+  new->seen = seen_force ? seen_value : FALSE;
   new->args[0].u = fmsg;
   break;
 
@@ -1123,10 +1132,10 @@ switch (command)
 
   /* Set up the command block for if */
 
-  new = store_get(sizeof(filter_cmd) + 4 * sizeof(union argtypes), FALSE);
+  new = store_get(sizeof(filter_cmd) + 4 * sizeof(union argtypes), GET_UNTAINTED);
   new->next = NULL;
   **lastcmdptr = new;
-  *lastcmdptr = &(new->next);
+  *lastcmdptr = &new->next;
   new->command = command;
   new->seen = FALSE;
   new->args[0].u = NULL;
@@ -1135,8 +1144,8 @@ switch (command)
 
   /* Read the condition */
 
-  ptr = read_condition(ptr, &(new->args[0].c), TRUE);
-  if (*error_pointer != NULL) { yield = FALSE; break; }
+  ptr = read_condition(ptr, &new->args[0].c, TRUE);
+  if (*error_pointer) { yield = FALSE; break; }
 
   /* Read the commands to be obeyed if the condition is true */
 
@@ -1151,7 +1160,7 @@ switch (command)
     while (had_else_endif == had_elif)
       {
       filter_cmd *newnew =
-        store_get(sizeof(filter_cmd) + 4 * sizeof(union argtypes), FALSE);
+        store_get(sizeof(filter_cmd) + 4 * sizeof(union argtypes), GET_UNTAINTED);
       new->args[2].f = newnew;
       new = newnew;
       new->next = NULL;
@@ -1161,8 +1170,8 @@ switch (command)
       new->args[1].u = new->args[2].u = NULL;
       new->args[3].u = ptr;
 
-      ptr = read_condition(ptr, &(new->args[0].c), TRUE);
-      if (*error_pointer != NULL) { yield = FALSE; break; }
+      ptr = read_condition(ptr, &new->args[0].c, TRUE);
+      if (*error_pointer) { yield = FALSE; break; }
       newlastcmdptr = &(new->args[1].f);
       if (!read_command_list(&ptr, &newlastcmdptr, TRUE))
         yield = FALSE;
@@ -1204,10 +1213,10 @@ switch (command)
 
   case mail_command:
   case vacation_command:
-  new = store_get(sizeof(filter_cmd) + mailargs_total * sizeof(union argtypes), FALSE);
+  new = store_get(sizeof(filter_cmd) + mailargs_total * sizeof(union argtypes), GET_UNTAINTED);
   new->next = NULL;
   new->command = command;
-  new->seen = seen_force? seen_value : FALSE;
+  new->seen = seen_force ? seen_value : FALSE;
   new->noerror = noerror_force;
   for (i = 0; i < mailargs_total; i++) new->args[i].u = NULL;
 
@@ -1218,13 +1227,10 @@ switch (command)
 
   for (;;)
     {
-    uschar *saveptr = ptr;
+    const uschar *saveptr = ptr;
     ptr = nextword(ptr, buffer, sizeof(buffer), FALSE);
-    if (*error_pointer != NULL)
-      {
-      yield = FALSE;
-      break;
-      }
+    if (*error_pointer)
+      { yield = FALSE; break; }
 
     /* Ensure "return" is followed by "message"; that's a complete option */
 
@@ -1274,11 +1280,8 @@ switch (command)
     /* Found keyword, read the data item */
 
     ptr = nextitem(ptr, buffer, sizeof(buffer), FALSE);
-    if (*error_pointer != NULL)
-      {
-      yield = FALSE;
-      break;
-      }
+    if (*error_pointer)
+      { yield = FALSE; break; }
     else new->args[i].u = string_copy(buffer);
     }
 
@@ -1287,18 +1290,18 @@ switch (command)
 
   if (command == vacation_command)
     {
-    if (new->args[mailarg_index_file].u == NULL)
+    if (!new->args[mailarg_index_file].u)
       {
       new->args[mailarg_index_file].u = string_copy(US".vacation.msg");
       new->args[mailarg_index_expand].u = US"";   /* not NULL => TRUE */
       }
-    if (new->args[mailarg_index_log].u == NULL)
+    if (!new->args[mailarg_index_log].u)
       new->args[mailarg_index_log].u = string_copy(US".vacation.log");
-    if (new->args[mailarg_index_once].u == NULL)
+    if (!new->args[mailarg_index_once].u)
       new->args[mailarg_index_once].u = string_copy(US".vacation");
-    if (new->args[mailarg_index_once_repeat].u == NULL)
+    if (!new->args[mailarg_index_once_repeat].u)
       new->args[mailarg_index_once_repeat].u = string_copy(US"7d");
-    if (new->args[mailarg_index_subject].u == NULL)
+    if (!new->args[mailarg_index_subject].u)
       new->args[mailarg_index_subject].u = string_copy(US"On vacation");
     }
 
@@ -1313,7 +1316,7 @@ switch (command)
 
   case seen_command:
   case unseen_command:
-  if (*ptr == 0)
+  if (!*ptr)
     {
     *error_pointer = string_sprintf("\"seen\" or \"unseen\" "
       "near line %d is not followed by a command", line_number);
@@ -1334,7 +1337,7 @@ switch (command)
   /* So does noerror */
 
   case noerror_command:
-  if (*ptr == 0)
+  if (!*ptr)
     {
     *error_pointer = string_sprintf("\"noerror\" "
       "near line %d is not followed by a command", line_number);
@@ -1383,11 +1386,11 @@ Returns:      TRUE on success
 */
 
 static BOOL
-read_command_list(uschar **pptr, filter_cmd ***lastcmdptr, BOOL conditional)
+read_command_list(const uschar **pptr, filter_cmd ***lastcmdptr, BOOL conditional)
 {
 if (conditional) expect_endif++;
 had_else_endif = had_neither;
-while (**pptr != 0 && had_else_endif == had_neither)
+while (**pptr && had_else_endif == had_neither)
   {
   if (!read_command(pptr, lastcmdptr)) return FALSE;
   *pptr = nextsigchar(*pptr, TRUE);
@@ -1424,10 +1427,7 @@ static BOOL
 test_condition(condition_block *c, BOOL toplevel)
 {
 BOOL yield = FALSE;
-const pcre *re;
-uschar *exp[2], *p, *pp;
-const uschar *regcomp_error = NULL;
-int regcomp_error_offset;
+const uschar *exp[2], * p, * pp;
 int val[2];
 int i;
 
@@ -1485,8 +1485,7 @@ switch (c->type)
 
   case cond_foranyaddress:
   p = c->left.u;
-  pp = expand_string(p);
-  if (pp == NULL)
+  if (!(pp = expand_cstring(p)))
     {
     *error_pointer = string_sprintf("failed to expand \"%s\" in "
       "filter file: %s", p, expand_string_message);
@@ -1496,21 +1495,19 @@ switch (c->type)
   yield = FALSE;
   f.parse_allow_group = TRUE;     /* Allow group syntax */
 
-  while (*pp != 0)
+  while (*pp)
     {
     uschar *error;
     int start, end, domain;
-    int saveend;
+    uschar * s;
 
     p = parse_find_address_end(pp, FALSE);
-    saveend = *p;
+    s = string_copyn(pp, p - pp);
 
-    *p = 0;
     filter_thisaddress =
-      parse_extract_address(pp, &error, &start, &end, &domain, FALSE);
-    *p = saveend;
+      parse_extract_address(s, &error, &start, &end, &domain, FALSE);
 
-    if (filter_thisaddress != NULL)
+    if (filter_thisaddress)
       {
       if ((filter_test != FTEST_NONE && debug_selector != 0) ||
           (debug_selector & D_filter) != 0)
@@ -1522,7 +1519,7 @@ switch (c->type)
       }
 
     if (yield) break;
-    if (saveend == 0) break;
+    if (!*p) break;
     pp = p + 1;
     }
 
@@ -1537,8 +1534,7 @@ switch (c->type)
   p = c->left.u;
   for (i = 0; i < 2; i++)
     {
-    exp[i] = expand_string(p);
-    if (exp[i] == NULL)
+    if (!(exp[i] = expand_cstring(p)))
       {
       *error_pointer = string_sprintf("failed to expand \"%s\" in "
         "filter file: %s", p, expand_string_message);
@@ -1560,7 +1556,7 @@ switch (c->type)
     break;
 
     case cond_contains:
-    yield = strstric(exp[0], exp[1], FALSE) != NULL;
+    yield = strstric_c(exp[0], exp[1], FALSE) != NULL;
     break;
 
     case cond_CONTAINS:
@@ -1579,34 +1575,43 @@ switch (c->type)
     case cond_ENDS:
       {
       int len = Ustrlen(exp[1]);
-      uschar *s = exp[0] + Ustrlen(exp[0]) - len;
-      yield = (s < exp[0])? FALSE :
-        ((c->type == cond_ends)? strcmpic(s, exp[1]) : Ustrcmp(s, exp[1])) == 0;
+      const uschar *s = exp[0] + Ustrlen(exp[0]) - len;
+      yield = s < exp[0]
+       ? FALSE
+       : (c->type == cond_ends ? strcmpic(s, exp[1]) : Ustrcmp(s, exp[1])) == 0;
       }
     break;
 
     case cond_matches:
     case cond_MATCHES:
-    if ((filter_test != FTEST_NONE && debug_selector != 0) ||
-        (debug_selector & D_filter) != 0)
       {
-      debug_printf_indent("Match expanded arguments:\n");
-      debug_printf_indent("  Subject = %s\n", exp[0]);
-      debug_printf_indent("  Pattern = %s\n", exp[1]);
-      }
+      const pcre2_code *re;
+      int err;
+      PCRE2_SIZE offset;
 
-    if (!(re = pcre_compile(CS exp[1],
-      PCRE_COPT | ((c->type == cond_matches)? PCRE_CASELESS : 0),
-      CCSS &regcomp_error, &regcomp_error_offset, NULL)))
-      {
-      *error_pointer = string_sprintf("error while compiling "
-        "regular expression \"%s\": %s at offset %d",
-        exp[1], regcomp_error, regcomp_error_offset);
-      return FALSE;
-      }
+      if ((filter_test != FTEST_NONE && debug_selector != 0) ||
+         (debug_selector & D_filter) != 0)
+       {
+       debug_printf_indent("Match expanded arguments:\n");
+       debug_printf_indent("  Subject = %s\n", exp[0]);
+       debug_printf_indent("  Pattern = %s\n", exp[1]);
+       }
 
-    yield = regex_match_and_setup(re, exp[0], PCRE_EOPT, -1);
-    break;
+      if (!(re = pcre2_compile((PCRE2_SPTR)exp[1], PCRE2_ZERO_TERMINATED,
+                 PCRE_COPT | (c->type == cond_matches ? PCRE2_CASELESS : 0),
+                 &err, &offset, pcre_cmp_ctx)))
+       {
+       uschar errbuf[128];
+       pcre2_get_error_message(err, errbuf, sizeof(errbuf));
+       *error_pointer = string_sprintf("error while compiling "
+         "regular expression \"%s\": %s at offset %ld",
+         exp[1], errbuf, (long)offset);
+       return FALSE;
+       }
+
+      yield = regex_match_and_setup(re, exp[0], PCRE_EOPT, -1);
+      break;
+      }
 
     /* For above and below, convert the strings to numbers */
 
@@ -1667,7 +1672,7 @@ Returns:      FF_DELIVERED     success, a significant action was taken
 static int
 interpret_commands(filter_cmd *commands, address_item **generated)
 {
-uschar *s;
+const uschar *s;
 int mode;
 address_item *addr;
 BOOL condition_value;
@@ -1676,7 +1681,7 @@ while (commands)
   {
   int ff_ret;
   uschar *fmsg, *ff_name;
-  uschar *expargs[MAILARGS_STRING_COUNT];
+  const uschar *expargs[MAILARGS_STRING_COUNT];
 
   int i, n[2];
 
@@ -1685,17 +1690,16 @@ while (commands)
 
   for (i = 0; i < (command_exparg_count[commands->command] & 15); i++)
     {
-    uschar *ss = commands->args[i].u;
+    const uschar *ss = commands->args[i].u;
     if (!ss)
       expargs[i] = NULL;
-    else
-      if (!(expargs[i] = expand_string(ss)))
-        {
-        *error_pointer = string_sprintf("failed to expand \"%s\" in "
-          "%s command: %s", ss, command_list[commands->command],
-          expand_string_message);
-        return FF_ERROR;
-        }
+    else if (!(expargs[i] = expand_cstring(ss)))
+      {
+      *error_pointer = string_sprintf("failed to expand \"%s\" in "
+       "%s command: %s", ss, command_list[commands->command],
+       expand_string_message);
+      return FF_ERROR;
+      }
     }
 
   /* Now switch for each command, setting the "delivered" flag if any of them
@@ -1708,7 +1712,7 @@ while (commands)
     case add_command:
       for (i = 0; i < 2; i++)
        {
-       uschar *ss = expargs[i];
+       const uschar *ss = expargs[i];
        uschar *end;
 
        if (i == 1 && (*ss++ != 'n' || ss[1] != 0))
@@ -1747,11 +1751,11 @@ while (commands)
          uschar *error;
          uschar *ss = parse_extract_address(s, &error, &start, &end, &domain,
            FALSE);
-         if (ss != NULL)
-           expargs[i] = ((filter_options & RDO_REWRITE) != 0)?
-             rewrite_address(ss, TRUE, FALSE, global_rewrite_rules,
-               rewrite_existflags) :
-             rewrite_address_qualify(ss, TRUE);
+         if (ss)
+           expargs[i] = filter_options & RDO_REWRITE
+             rewrite_address(ss, TRUE, FALSE, global_rewrite_rules,
+                               rewrite_existflags)
+             rewrite_address_qualify(ss, TRUE);
          else
            {
            *error_pointer = string_sprintf("malformed address \"%s\" in "
@@ -1805,9 +1809,8 @@ while (commands)
        af_ignore_error flag if necessary, and the errors address, which can be
        set in a system filter and to the local address in user filters. */
 
-       addr = deliver_make_addr(expargs[0], TRUE);  /* TRUE => copy s */
-       addr->prop.errors_address = (s == NULL)?
-         s : string_copy(s);                        /* Default is NULL */
+       addr = deliver_make_addr(US expargs[0], TRUE);  /* TRUE => copy s, so deconst ok */
+       addr->prop.errors_address = !s ? NULL : string_copy(s); /* Default is NULL */
        if (commands->noerror) addr->prop.ignore_error = TRUE;
        addr->next = *generated;
        *generated = addr;
@@ -1847,7 +1850,7 @@ while (commands)
        af_pfr and af_file flags, the af_ignore_error flag if necessary, and the
        mode value. */
 
-       addr = deliver_make_addr(s, TRUE);  /* TRUE => copy s */
+       addr = deliver_make_addr(US s, TRUE);  /* TRUE => copy s, so deconst ok */
        setflag(addr, af_pfr);
        setflag(addr, af_file);
        if (commands->noerror) addr->prop.ignore_error = TRUE;
@@ -1877,7 +1880,7 @@ while (commands)
        each command argument is expanded in the transport after the command
        has been split up into separate arguments. */
 
-       addr = deliver_make_addr(s, TRUE);  /* TRUE => copy s */
+       addr = deliver_make_addr(US s, TRUE);  /* TRUE => copy s, so deconst ok */
        setflag(addr, af_pfr);
        setflag(addr, af_expand_pipe);
        if (commands->noerror) addr->prop.ignore_error = TRUE;
@@ -1892,7 +1895,7 @@ while (commands)
        if (expand_nmax >= 0 || filter_thisaddress != NULL)
          {
          int ecount = expand_nmax >= 0 ? expand_nmax : -1;
-         uschar **ss = store_get(sizeof(uschar *) * (ecount + 3), FALSE);
+         uschar ** ss = store_get(sizeof(uschar *) * (ecount + 3), GET_UNTAINTED);
 
          addr->pipe_expandn = ss;
          if (!filter_thisaddress) filter_thisaddress = US"";
@@ -1951,7 +1954,7 @@ while (commands)
          (long int)geteuid());
        if (log_fd < 0)
          {
-         if (log_filename == NULL)
+         if (!log_filename)
            {
            *error_pointer = US"attempt to obey \"logwrite\" command "
              "without a previous \"logfile\"";
@@ -1960,7 +1963,7 @@ while (commands)
          log_fd = Uopen(log_filename, O_CREAT|O_APPEND|O_WRONLY, log_mode);
          if (log_fd < 0)
            {
-           *error_pointer = string_open_failed(errno, "filter log file \"%s\"",
+           *error_pointer = string_open_failed("filter log file \"%s\"",
              log_filename);
            return FF_ERROR;
            }
@@ -1974,9 +1977,8 @@ while (commands)
          }
        }
       else
-       {
-       DEBUG(D_filter) debug_printf_indent("skipping logwrite (verifying or testing)\n");
-       }
+       DEBUG(D_filter)
+         debug_printf_indent("skipping logwrite (verifying or testing)\n");
       break;
 
       /* Header addition and removal is available only in the system filter. The
@@ -1989,16 +1991,19 @@ while (commands)
        s = expargs[0];
 
        if (filter_test != FTEST_NONE)
-         printf("Headers %s \"%s\"\n", (subtype == TRUE)? "add" :
-           (subtype == FALSE)? "remove" : "charset", string_printing(s));
+         printf("Headers %s \"%s\"\n",
+           subtype == TRUE ? "add"
+           : subtype == FALSE ? "remove"
+           : "charset",
+           string_printing(s));
 
        if (subtype == TRUE)
          {
          while (isspace(*s)) s++;
-         if (s[0] != 0)
+         if (*s)
            {
-           header_add(htype_other, "%s%s", s, (s[Ustrlen(s)-1] == '\n')?
-             "" : "\n");
+           header_add(htype_other, "%s%s", s,
+             s[Ustrlen(s)-1] == '\n' ? "" : "\n");
            header_last->type = header_checkname(header_last, FALSE);
            if (header_last->type >= 'a') header_last->type = htype_other;
            }
@@ -2007,11 +2012,9 @@ while (commands)
        else if (subtype == FALSE)
          {
          int sep = 0;
-         uschar *ss;
-         const uschar *list = s;
-         uschar buffer[128];
-         while ((ss = string_nextinlist(&list, &sep, buffer, sizeof(buffer)))
-                != NULL)
+         const uschar * list = s;
+
+         for (uschar * ss; ss = string_nextinlist(&list, &sep, NULL, 0); )
            header_remove(0, ss);
          }
 
@@ -2041,18 +2044,20 @@ while (commands)
       ff_name = US"freeze";
       ff_ret = FF_FREEZE;
 
-      DEFERFREEZEFAIL:
-      fmsg = expargs[0];
-      if (Ustrlen(fmsg) > 1024) Ustrcpy(fmsg + 1000, US" ... (truncated)");
-      fmsg = US string_printing(fmsg);
-      *error_pointer = fmsg;
+    DEFERFREEZEFAIL:
+      *error_pointer = fmsg = US string_printing(Ustrlen(expargs[0]) > 1024
+       ? string_sprintf("%.1000s ... (truncated)", expargs[0])
+       : string_copy(expargs[0]));
+      for(uschar * s = fmsg; *s; s++)
+       if (!s[1] && *s == '\n') { *s = '\0'; break; }  /* drop trailing newline */
 
       if (filter_test != FTEST_NONE)
        {
        indent();
        printf("%c%s text \"%s\"\n", toupper(ff_name[0]), ff_name+1, fmsg);
        }
-      else DEBUG(D_filter) debug_printf_indent("Filter: %s \"%s\"\n", ff_name, fmsg);
+      else
+        DEBUG(D_filter) debug_printf_indent("Filter: %s \"%s\"\n", ff_name, fmsg);
       return ff_ret;
 
     case finish_command:
@@ -2062,19 +2067,19 @@ while (commands)
        printf("%sinish\n", (commands->seen)? "Seen f" : "F");
        }
       else
-       {
        DEBUG(D_filter) debug_printf_indent("Filter: %sfinish\n",
-         (commands->seen)? " Seen " : "");
-       }
+         commands->seen ? " Seen " : "");
       finish_obeyed = TRUE;
-      return filter_delivered? FF_DELIVERED : FF_NOTDELIVERED;
+      return filter_delivered ? FF_DELIVERED : FF_NOTDELIVERED;
 
     case if_command:
        {
        uschar *save_address = filter_thisaddress;
        int ok = FF_DELIVERED;
        condition_value = test_condition(commands->args[0].c, TRUE);
-       if (*error_pointer != NULL) ok = FF_ERROR; else
+       if (*error_pointer)
+         ok = FF_ERROR;
+       else
          {
          output_indent += 2;
          ok = interpret_commands(commands->args[condition_value? 1:2].f,
@@ -2082,7 +2087,7 @@ while (commands)
          output_indent -= 2;
          }
        filter_thisaddress = save_address;
-       if (finish_obeyed || (ok != FF_DELIVERED && ok != FF_NOTDELIVERED))
+       if (finish_obeyed  ||  ok != FF_DELIVERED && ok != FF_NOTDELIVERED)
          return ok;
        }
       break;
@@ -2094,7 +2099,7 @@ while (commands)
 
     case mail_command:
     case vacation_command:
-       if (return_path == NULL || return_path[0] == 0)
+       if (!return_path || !*return_path)
          {
          if (filter_test != FTEST_NONE)
            printf("%s command ignored because return_path is empty\n",
@@ -2124,12 +2129,11 @@ while (commands)
 
        for (i = 0; i < MAILARGS_STRING_COUNT; i++)
          {
-         uschar *p;
-         uschar *s = expargs[i];
+         const uschar *s = expargs[i];
 
-         if (s == NULL) continue;
+         if (!s) continue;
 
-         if (i != mailarg_index_text) for (p = s; *p != 0; p++)
+         if (i != mailarg_index_text) for (const uschar * p = s; *p; p++)
            {
            int c = *p;
            if (i > mailarg_index_text)
@@ -2159,12 +2163,12 @@ while (commands)
 
              else
                {
-               uschar *pp;
+               const uschar *pp;
                for (pp = p + 1;; pp++)
                  {
                  c = *pp;
                  if (c == ':' && pp != p + 1) break;
-                 if (c == 0 || c == ':' || isspace(*pp))
+                 if (!c || c == ':' || isspace(c))
                    {
                    *error_pointer = string_sprintf("\\n not followed by space or "
                      "valid header name in \"%.1024s\" in %s command",
@@ -2186,7 +2190,7 @@ while (commands)
 
        if (filter_test != FTEST_NONE)
          {
-         uschar *to = commands->args[mailarg_index_to].u;
+         const uschar *to = commands->args[mailarg_index_to].u;
          indent();
          printf("%sail to: %s%s%s\n", (commands->seen)? "Seen m" : "M",
            to ? to : US"<default>",
@@ -2194,7 +2198,7 @@ while (commands)
            commands->noerror ? " (noerror)" : "");
          for (i = 1; i < MAILARGS_STRING_COUNT; i++)
            {
-           uschar *arg = commands->args[i].u;
+           const uschar *arg = commands->args[i].u;
            if (arg)
              {
              int len = Ustrlen(mailargs[i]);
@@ -2210,15 +2214,22 @@ while (commands)
          }
        else
          {
-         uschar *tt;
-         uschar *to = commands->args[mailarg_index_to].u;
+         const uschar *tt;
+         const uschar *to = commands->args[mailarg_index_to].u;
          gstring * log_addr = NULL;
 
          if (!to) to = expand_string(US"$reply_address");
          while (isspace(*to)) to++;
 
-         for (tt = to; *tt != 0; tt++)     /* Get rid of newlines */
-           if (*tt == '\n') *tt = ' ';
+         for (tt = to; *tt; tt++)     /* Get rid of newlines */
+           if (*tt == '\n')
+             {
+             uschar * s = string_copy(to);
+             for (uschar * ss = s; *ss; ss++)
+               if (*ss == '\n') *ss = ' ';
+             to = s;
+             break;
+             }
 
          DEBUG(D_filter)
            {
@@ -2229,8 +2240,8 @@ while (commands)
              commands->noerror ? " (noerror)" : "");
            for (i = 1; i < MAILARGS_STRING_COUNT; i++)
              {
-             uschar *arg = commands->args[i].u;
-             if (arg != NULL)
+             const uschar *arg = commands->args[i].u;
+             if (arg)
                {
                int len = Ustrlen(mailargs[i]);
                while (len++ < 15) debug_printf_indent(" ");
@@ -2248,7 +2259,7 @@ while (commands)
          string gets too long. */
 
          tt = to;
-         while (*tt != 0)
+         while (*tt)
            {
            uschar *ss = parse_find_address_end(tt, FALSE);
            uschar *recipient, *errmess;
@@ -2296,7 +2307,7 @@ while (commands)
          addr->next = *generated;
          *generated = addr;
 
-         addr->reply = store_get(sizeof(reply_item), FALSE);
+         addr->reply = store_get(sizeof(reply_item), GET_UNTAINTED);
          addr->reply->from = NULL;
          addr->reply->to = string_copy(to);
          addr->reply->file_expand =
@@ -2324,7 +2335,7 @@ while (commands)
 
          for (i = 1; i < mailargs_string_passed; i++)
            {
-           uschar *ss = commands->args[i].u;
+           const uschar *ss = commands->args[i].u;
            *(USS((US addr->reply) + reply_offsets[i])) =
              ss ? string_copy(ss) : NULL;
            }
@@ -2367,8 +2378,9 @@ Returns:     TRUE if the message is deemed to be personal
 BOOL
 filter_personal(string_item *aliases, BOOL scan_cc)
 {
-uschar *self, *self_from, *self_to;
-uschar *psself = NULL, *psself_from = NULL, *psself_to = NULL;
+const uschar *self, *self_from, *self_to;
+uschar *psself = NULL;
+const uschar *psself_from = NULL, *psself_to = NULL;
 rmark reset_point = store_mark();
 BOOL yield;
 header_line *h;
@@ -2386,12 +2398,11 @@ Previously the test was for "auto-". */
 
 for (h = header_list; h; h = h->next)
   {
-  uschar *s;
   if (h->type == htype_old) continue;
 
   if (strncmpic(h->text, US"List-", 5) == 0)
     {
-    s = h->text + 5;
+    uschar * s = h->text + 5;
     if (strncmpic(s, US"Id:", 3) == 0 ||
         strncmpic(s, US"Help:", 5) == 0 ||
         strncmpic(s, US"Subscribe:", 10) == 0 ||
@@ -2404,12 +2415,12 @@ for (h = header_list; h; h = h->next)
 
   else if (strncmpic(h->text, US"Auto-submitted:", 15) == 0)
     {
-    s = h->text + 15;
-    while (isspace(*s)) s++;
+    uschar * s = h->text + 15;
+    Uskip_whitespace(&s);
     if (strncmpic(s, US"no", 2) != 0) return FALSE;
     s += 2;
-    while (isspace(*s)) s++;
-    if (*s != 0) return FALSE;
+    Uskip_whitespace(&s);
+    if (*s) return FALSE;
     }
   }
 
@@ -2422,18 +2433,18 @@ self_to   = rewrite_one(self, rewrite_to, NULL, FALSE, US"",
   global_rewrite_rules);
 
 
-if (self_from == NULL) self_from = self;
-if (self_to == NULL) self_to = self;
+if (!self_from) self_from = self;
+if (self_to) self_to = self;
 
 /* If there's a prefix or suffix set, we must include the prefixed/
 suffixed version of the local part in the tests. */
 
-if (deliver_localpart_prefix != NULL || deliver_localpart_suffix != NULL)
+if (deliver_localpart_prefix || deliver_localpart_suffix)
   {
   psself = string_sprintf("%s%s%s@%s",
-    (deliver_localpart_prefix == NULL)? US"" : deliver_localpart_prefix,
+    deliver_localpart_prefix ? deliver_localpart_prefix : US"",
     deliver_localpart,
-    (deliver_localpart_suffix == NULL)? US"" : deliver_localpart_suffix,
+    deliver_localpart_suffix ? deliver_localpart_suffix : US"",
     deliver_domain);
   psself_from = rewrite_one(psself, rewrite_from, NULL, FALSE, US"",
     global_rewrite_rules);
@@ -2497,13 +2508,13 @@ Returns:      FF_DELIVERED     success, a significant action was taken
 */
 
 int
-filter_interpret(uschar *filter, int options, address_item **generated,
+filter_interpret(const uschar *filter, int options, address_item **generated,
   uschar **error)
 {
 int i;
 int yield = FF_ERROR;
-uschar *ptr = filter;
-uschar *save_headers_charset = headers_charset;
+const uschar *ptr = filter;
+const uschar *save_headers_charset = headers_charset;
 filter_cmd *commands = NULL;
 filter_cmd **lastcmdptr = &commands;